Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/roam/src/components/DiscourseNodeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {
Popover,
Position,
Button,
InputGroup,
getKeyCombo,
IKeyCombo,
Icon,
} from "@blueprintjs/core";
import SettingKeycapInput from "~/components/settings/components/SettingKeycapInput";
import React, {
useCallback,
useEffect,
Expand Down Expand Up @@ -496,7 +496,8 @@ export const NodeMenuTriggerComponent = ({
const shortcut = useMemo(() => comboToString(comboKey), [comboKey]);

return (
<InputGroup
<SettingKeycapInput
wide
inputRef={inputRef}
placeholder={
isActive ? "Press keys" : (placeholder ?? "Click to set trigger")
Expand Down
4 changes: 2 additions & 2 deletions apps/roam/src/components/DiscourseNodeSearchMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
Popover,
Position,
Button,
InputGroup,
Intent,
} from "@blueprintjs/core";
import SettingKeycapInput from "~/components/settings/components/SettingKeycapInput";
import ReactDOM from "react-dom";
import getUids from "roamjs-components/dom/getUids";
import getTextByBlockUid from "roamjs-components/queries/getTextByBlockUid";
Expand Down Expand Up @@ -712,7 +712,7 @@ export const NodeSearchMenuTriggerSetting = ({
setPersonalSetting([PERSONAL_KEYS.nodeSearchMenuTrigger], trigger);
};
return (
<InputGroup
<SettingKeycapInput
value={nodeSearchTrigger}
onChange={handleNodeSearchTriggerChange}
placeholder="Click to set trigger"
Expand Down
122 changes: 87 additions & 35 deletions apps/roam/src/components/Export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
RadioGroup,
Radio,
FormGroup,
Collapse,
} from "@blueprintjs/core";
import React, { useState, useEffect, useMemo, FormEvent } from "react";
import React, { useState, useEffect, useMemo, useRef, FormEvent } from "react";
import MenuItemSelect from "roamjs-components/components/MenuItemSelect";
import { saveAs } from "file-saver";
import { Result } from "roamjs-components/types/query-builder";
Expand Down Expand Up @@ -91,7 +92,13 @@ import {
type NodeUidWithType,
} from "~/utils/publishNodesToGroups";
import { getLoggedInClient, getSupabaseContext } from "~/utils/supabaseContext";
import { isNodeSharingEnabled } from "~/components/settings/utils/accessors";
import {
bulkReadSettings,
isNodeSharingEnabled,
} from "~/components/settings/utils/accessors";
import refreshConfigTree from "~/utils/refreshConfigTree";
import { flushPendingSettingWrites } from "~/utils/pendingSettingWrites";
import ExportOptions from "./ExportOptions";

const ExportProgress = ({ id }: { id: string }) => {
const [progress, setProgress] = useState(0);
Expand Down Expand Up @@ -225,6 +232,26 @@ const ExportDialog: ExportDialogComponent = ({
if (initialPanel) setSelectedTabId(INITIAL_PANEL_TO_TAB_ID[initialPanel]);
}, [initialPanel, sharingEnabled]);
const [includeDiscourseContext, setIncludeDiscourseContext] = useState(false);
const [exportOptionsOpen, setExportOptionsOpen] = useState(false);
const exportOptionsOpened = useRef(false);
// Re-read on every open rather than once at mount: Collapse unmounts the option
// panels while closed, so each open seeds them from the current stored values
// instead of whatever the dialog saw when it first rendered.
const exportGlobalSettings = useMemo(
() => bulkReadSettings().globalSettings,
// eslint-disable-next-line react-hooks/exhaustive-deps
[exportOptionsOpen],
);

// The export option panels write legacy config blocks alongside block props, so
// the cached config tree has to be refreshed the way SettingsDialog does. Gated
// on the section having been opened because refreshConfigTree re-reads every
// node page and re-registers the datalog translators, which is too heavy to run
// on every close of a dialog that is opened for each export.
const closeDialog = (): void => {
if (exportOptionsOpened.current) refreshConfigTree();
onClose();
};
const [gitHubAccessToken, setGitHubAccessToken] = useState<string | null>(
getSetting<string | null>("oauth-github", null),
);
Expand Down Expand Up @@ -756,7 +783,7 @@ const ExportDialog: ExportDialogComponent = ({
});
} finally {
setLoading(false);
onClose();
closeDialog();
}
};

Expand Down Expand Up @@ -796,7 +823,7 @@ const ExportDialog: ExportDialogComponent = ({
fileCount: files.length,
});
}
onClose();
closeDialog();
} catch (e) {
setError("Failed to export files.");
posthog.capture("Export Dialog: Export Failed", {
Expand Down Expand Up @@ -888,7 +915,7 @@ const ExportDialog: ExportDialogComponent = ({
: "success",
id: "query-builder-publish-success",
});
if (hasPublishedNodes) onClose();
if (hasPublishedNodes) closeDialog();
} catch (e) {
internalError({
error: e as Error,
Expand Down Expand Up @@ -951,41 +978,60 @@ const ExportDialog: ExportDialogComponent = ({
/>
</Label>

<div className="flex items-end justify-between">
<div className="mt-2 flex justify-end">
<FormGroup className={`m-0`} inline>
<Checkbox
alignIndicator={"right"}
checked={includeDiscourseContext}
onChange={(e) => {
setIncludeDiscourseContext(
(e.target as HTMLInputElement).checked,
);
}}
labelElement={
<Tooltip
className="m-0"
content={
"Include the discourse context of each result in the export."
}
>
<span>Discourse context</span>
</Tooltip>
}
/>
</FormGroup>
</div>
<div className="mt-1 flex items-center justify-between gap-4">
<Button
minimal={true}
small={true}
icon={exportOptionsOpen ? "chevron-down" : "chevron-right"}
text="Export options"
onClick={() => {
const nextOpen = !exportOptionsOpen;
setExportOptionsOpen(nextOpen);
if (nextOpen) exportOptionsOpened.current = true;
posthog.capture("Export Dialog: Options Toggled", {
open: nextOpen,
});
}}
/>
<span>
{typeof results === "function"
? "Calculating number of results..."
: `Exporting ${results.length} results`}
</span>
<div className="flex flex-col items-end">
<FormGroup className={`m-0`} inline>
<Checkbox
alignIndicator={"right"}
checked={includeDiscourseContext}
onChange={(e) => {
setIncludeDiscourseContext(
(e.target as HTMLInputElement).checked,
);
}}
labelElement={
<Tooltip
className="m-0"
content={
"Include the discourse context of each result in the export."
}
>
<span>Discourse context</span>
</Tooltip>
}
/>
</FormGroup>
</div>
</div>
<Collapse isOpen={exportOptionsOpen}>
<div className="max-h-64 overflow-y-auto">
<ExportOptions globalSettings={exportGlobalSettings} />
</div>
</Collapse>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<span className="text-red-700">{error}</span>
<Button text={"Cancel"} intent={Intent.NONE} onClick={onClose} />
<Button text={"Cancel"} intent={Intent.NONE} onClick={closeDialog} />
<Button
text={"Export"}
intent={Intent.PRIMARY}
Expand All @@ -1009,6 +1055,12 @@ const ExportDialog: ExportDialogComponent = ({
// eslint-disable-next-line @typescript-eslint/no-misused-promises
setTimeout(async () => {
try {
// The export reads settings inside its callback, and the number
// and select panels defer their write behind a short timer. The
// await matters as much as the flush: committing only starts the
// Roam block update, so an option edited a moment ago would
// otherwise still read as its previous value here.
await flushPendingSettingWrites();
const exportType = exportTypes.find(
(e) => e.name === activeExportType,
);
Expand Down Expand Up @@ -1052,7 +1104,7 @@ const ExportDialog: ExportDialogComponent = ({
destination: activeExportDestination,
fileCount: files.length,
});
onClose();
closeDialog();
}
} catch (error) {
const e = error as Error;
Expand All @@ -1072,7 +1124,7 @@ const ExportDialog: ExportDialogComponent = ({
destination: activeExportDestination,
fileCount: files.length,
});
onClose();
closeDialog();
return;
}

Expand All @@ -1089,7 +1141,7 @@ const ExportDialog: ExportDialogComponent = ({
destination: activeExportDestination,
fileCount: files.length,
});
onClose();
closeDialog();
});
} else {
setError(`Unsupported export type: ${exportType}`);
Expand Down Expand Up @@ -1164,7 +1216,7 @@ const ExportDialog: ExportDialogComponent = ({
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button text={"Cancel"} intent={Intent.NONE} onClick={onClose} />
<Button text={"Cancel"} intent={Intent.NONE} onClick={closeDialog} />
<Button
text={`Send ${
isSendToGraph ? livePages.length : results.length
Expand Down Expand Up @@ -1224,7 +1276,7 @@ const ExportDialog: ExportDialogComponent = ({
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<span className="text-red-700">{publishError}</span>
<Button text={"Cancel"} intent={Intent.NONE} onClick={onClose} />
<Button text={"Cancel"} intent={Intent.NONE} onClick={closeDialog} />
<Button
text={"Publish"}
intent={Intent.PRIMARY}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import {
GlobalNumberPanel,
GlobalMultiTextPanel,
GlobalSelectPanel,
} from "./components/BlockPropSettingPanels";
} from "~/components/settings/components/BlockPropSettingPanels";
import {
GLOBAL_KEYS,
EXPORT_KEYS,
} from "~/components/settings/utils/settingKeys";
import { type SettingsSnapshot } from "./utils/accessors";
import { ROAM_DOCS, withDocsLink } from "./utils/docs";
import { type SettingsSnapshot } from "~/components/settings/utils/accessors";
import { ROAM_DOCS, withDocsLink } from "~/components/settings/utils/docs";

const DiscourseGraphExport = ({
const ExportOptions = ({
globalSettings,
}: {
globalSettings: SettingsSnapshot["globalSettings"];
Expand All @@ -40,6 +40,7 @@ const DiscourseGraphExport = ({
order={1}
uid={exportSettings.removeSpecialCharacters.uid}
parentUid={parentUid}
compact
/>

<GlobalFlagPanel
Expand All @@ -53,6 +54,7 @@ const DiscourseGraphExport = ({
order={3}
uid={exportSettings.optsRefs.uid}
parentUid={parentUid}
compact
/>
<GlobalFlagPanel
title="resolve block embeds"
Expand All @@ -65,6 +67,7 @@ const DiscourseGraphExport = ({
order={4}
uid={exportSettings.optsEmbeds.uid}
parentUid={parentUid}
compact
/>

<GlobalFlagPanel
Expand All @@ -78,6 +81,7 @@ const DiscourseGraphExport = ({
order={6}
uid={exportSettings.appendRefNodeContext.uid}
parentUid={parentUid}
compact
/>
</div>
<div className="link-type-select-wrapper">
Expand All @@ -93,6 +97,7 @@ const DiscourseGraphExport = ({
options={["alias", "wikilinks", "roam url"]}
uid={exportSettings.linkType.uid}
parentUid={parentUid}
compact
/>
</div>
<GlobalNumberPanel
Expand All @@ -106,6 +111,7 @@ const DiscourseGraphExport = ({
order={0}
uid={exportSettings.maxFilenameLength.uid}
parentUid={parentUid}
compact
/>
<GlobalMultiTextPanel
title="frontmatter"
Expand All @@ -118,9 +124,10 @@ const DiscourseGraphExport = ({
order={2}
uid={exportSettings.frontmatter.uid}
parentUid={parentUid}
compact
/>
</div>
);
};

export default DiscourseGraphExport;
export default ExportOptions;
Loading