Skip to content
Merged
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
25 changes: 15 additions & 10 deletions apps/roam/src/components/DiscourseNodeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { getNewDiscourseNodeText } from "~/utils/formatUtils";
import { OnloadArgs } from "roamjs-components/types";
import { formatHexColor } from "./settings/DiscourseNodeCanvasSettings";
import posthog from "posthog-js";
import { setPersonalSetting } from "~/components/settings/utils/accessors";

type Props = {
textarea?: HTMLTextAreaElement;
Expand Down Expand Up @@ -406,6 +407,13 @@ export const getModifiersFromCombo = (comboKey: IKeyCombo) => {
].filter(Boolean);
};

export const comboToString = (combo: IKeyCombo): string => {
if (!combo.key) return "";
const modifiers = getModifiersFromCombo(combo);
const comboString = [...modifiers, combo.key].join("+");
return normalizeKeyCombo(comboString).join("+");
};

export const NodeMenuTriggerComponent = ({
extensionAPI,
}: {
Expand All @@ -427,19 +435,15 @@ export const NodeMenuTriggerComponent = ({
const comboObj = getKeyCombo(e.nativeEvent);
if (!comboObj.key) return;

setComboKey({ key: comboObj.key, modifiers: comboObj.modifiers });
extensionAPI.settings.set("personal-node-menu-trigger", comboObj);
const combo = { key: comboObj.key, modifiers: comboObj.modifiers };
setComboKey(combo);
void extensionAPI.settings.set("personal-node-menu-trigger", combo);
setPersonalSetting(["Personal node menu trigger"], combo);
},
[extensionAPI],
);

const shortcut = useMemo(() => {
if (!comboKey.key) return "";

const modifiers = getModifiersFromCombo(comboKey);
const comboString = [...modifiers, comboKey.key].join("+");
return normalizeKeyCombo(comboString).join("+");
}, [comboKey]);
const shortcut = useMemo(() => comboToString(comboKey), [comboKey]);

return (
<InputGroup
Expand All @@ -455,7 +459,8 @@ export const NodeMenuTriggerComponent = ({
icon={"remove"}
onClick={() => {
setComboKey({ modifiers: 0, key: "" });
extensionAPI.settings.set("personal-node-menu-trigger", "");
void extensionAPI.settings.set("personal-node-menu-trigger", "");
setPersonalSetting(["Personal node menu trigger"], "");
}}
minimal
/>
Expand Down
114 changes: 58 additions & 56 deletions apps/roam/src/components/DiscourseNodeSearchMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import getDiscourseNodeFormatExpression from "~/utils/getDiscourseNodeFormatExpr
import { Result } from "~/utils/types";
import { getSetting } from "~/utils/extensionSettings";
import fuzzy from "fuzzy";
import { setPersonalSetting } from "~/components/settings/utils/accessors";

type Props = {
textarea: HTMLTextAreaElement;
Expand Down Expand Up @@ -232,61 +233,61 @@ const NodeSearchMenu = ({

const onSelect = useCallback(
(item: Result) => {
if (!blockUid) {
onClose();
return;
}
void waitForBlock({ uid: blockUid, text: textarea.value })
.then(() => {
onClose();

setTimeout(() => {
const originalText = getTextByBlockUid(blockUid);

const prefix = originalText.substring(0, triggerPosition);
const suffix = originalText.substring(textarea.selectionStart);
const pageRef = `[[${item.text}]]`;

const newText = `${prefix}${pageRef}${suffix}`;
void updateBlock({ uid: blockUid, text: newText }).then(() => {
const newCursorPosition = triggerPosition + pageRef.length;

if (window.roamAlphaAPI.ui.setBlockFocusAndSelection) {
void window.roamAlphaAPI.ui.setBlockFocusAndSelection({
location: {
// eslint-disable-next-line @typescript-eslint/naming-convention
"block-uid": blockUid,
// eslint-disable-next-line @typescript-eslint/naming-convention
"window-id": windowId,
},
selection: { start: newCursorPosition },
});
} else {
setTimeout(() => {
const textareaElements =
document.querySelectorAll("textarea");
for (const el of textareaElements) {
if (getUids(el).blockUid === blockUid) {
el.focus();
el.setSelectionRange(
newCursorPosition,
newCursorPosition,
);
break;
}
}
}, 50);
}
});
posthog.capture("Discourse Node: Selected from Search Menu", {
id: item.id,
text: item.text,
});
}, 10);
})
.catch((error) => {
console.error("Error waiting for block:", error);
});
if (!blockUid) {
onClose();
return;
}
void waitForBlock({ uid: blockUid, text: textarea.value })
.then(() => {
onClose();

setTimeout(() => {
const originalText = getTextByBlockUid(blockUid);

const prefix = originalText.substring(0, triggerPosition);
const suffix = originalText.substring(textarea.selectionStart);
const pageRef = `[[${item.text}]]`;

const newText = `${prefix}${pageRef}${suffix}`;
void updateBlock({ uid: blockUid, text: newText }).then(() => {
const newCursorPosition = triggerPosition + pageRef.length;

if (window.roamAlphaAPI.ui.setBlockFocusAndSelection) {
void window.roamAlphaAPI.ui.setBlockFocusAndSelection({
location: {
// eslint-disable-next-line @typescript-eslint/naming-convention
"block-uid": blockUid,
// eslint-disable-next-line @typescript-eslint/naming-convention
"window-id": windowId,
},
selection: { start: newCursorPosition },
});
} else {
setTimeout(() => {
const textareaElements =
document.querySelectorAll("textarea");
for (const el of textareaElements) {
if (getUids(el).blockUid === blockUid) {
el.focus();
el.setSelectionRange(
newCursorPosition,
newCursorPosition,
);
break;
}
}
}, 50);
}
});
posthog.capture("Discourse Node: Selected from Search Menu", {
id: item.id,
text: item.text,
});
}, 10);
})
.catch((error) => {
console.error("Error waiting for block:", error);
});
},
[blockUid, onClose, textarea, triggerPosition, windowId],
);
Expand Down Expand Up @@ -627,7 +628,8 @@ export const NodeSearchMenuTriggerSetting = ({
.trim();

setNodeSearchTrigger(trigger);
extensionAPI.settings.set("node-search-trigger", trigger);
void extensionAPI.settings.set("node-search-trigger", trigger);
setPersonalSetting(["Node search menu trigger"], trigger);
};
return (
<InputGroup
Expand Down
1 change: 1 addition & 0 deletions apps/roam/src/components/settings/HomePersonalSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => {
<KeyboardShortcutInput
onloadArgs={onloadArgs}
settingKey={DISCOURSE_TOOL_SHORTCUT_KEY}
blockPropKey="Discourse tool shortcut"
label="Discourse tool keyboard shortcut"
description="Set a single key to activate the discourse tool in tldraw. Only single keys (no modifiers) are supported. Leave empty for no shortcut."
placeholder="Click to set single key"
Expand Down
73 changes: 16 additions & 57 deletions apps/roam/src/components/settings/KeyboardShortcutInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,22 @@ import {
} from "@blueprintjs/core";
import Description from "roamjs-components/components/Description";
import { DISCOURSE_TOOL_SHORTCUT_KEY } from "~/data/userSettings";
import { setPersonalSetting } from "~/components/settings/utils/accessors";
import { comboToString } from "~/components/DiscourseNodeMenu";

type KeyboardShortcutInputProps = {
onloadArgs: OnloadArgs;
settingKey: string;
blockPropKey: string;
label: string;
description: string;
placeholder?: string;
};

// Reuse the keyboard combo utilities from NodeMenuTriggerComponent
const isMac = () => {
const platform =
typeof navigator !== "undefined" ? navigator.platform : undefined;
return platform == null ? false : /Mac|iPod|iPhone|iPad/.test(platform);
};

const MODIFIER_BIT_MASKS = {
alt: 1,
ctrl: 2,
meta: 4,
shift: 8,
};

const ALIASES: { [key: string]: string } = {
cmd: "meta",
command: "meta",
escape: "esc",
minus: "-",
mod: isMac() ? "meta" : "ctrl",
option: "alt",
plus: "+",
return: "enter",
win: "meta",
};

const normalizeKeyCombo = (combo: string) => {
const keys = combo.replace(/\s/g, "").split("+");
return keys.map((key) => {
const keyName = ALIASES[key] != null ? ALIASES[key] : key;
return keyName === "meta" ? (isMac() ? "cmd" : "win") : keyName;
});
};

const getModifiersFromCombo = (comboKey: IKeyCombo) => {
if (!comboKey) return [];
return [
comboKey.modifiers & MODIFIER_BIT_MASKS.alt && "alt",
comboKey.modifiers & MODIFIER_BIT_MASKS.ctrl && "ctrl",
comboKey.modifiers & MODIFIER_BIT_MASKS.shift && "shift",
comboKey.modifiers & MODIFIER_BIT_MASKS.meta && "meta",
].filter(Boolean);
};

const KeyboardShortcutInput = ({
onloadArgs,
settingKey,
blockPropKey,
label,
description,
placeholder = "Click to set shortcut",
Expand Down Expand Up @@ -104,6 +64,7 @@ const KeyboardShortcutInput = ({
extensionAPI.settings
.set(settingKey, comboObj)
.catch(() => console.error("Failed to set setting"));
setPersonalSetting([blockPropKey], comboObj);
}
return;
}
Expand All @@ -112,28 +73,26 @@ const KeyboardShortcutInput = ({
const comboObj = getKeyCombo(e.nativeEvent);
if (!comboObj.key) return;

setComboKey({ key: comboObj.key, modifiers: comboObj.modifiers });
const combo = { key: comboObj.key, modifiers: comboObj.modifiers };
setComboKey(combo);
extensionAPI.settings
.set(settingKey, comboObj)
.set(settingKey, combo)
.catch(() => console.error("Failed to set setting"));
setPersonalSetting([blockPropKey], combo);
},
[extensionAPI, settingKey],
[extensionAPI, settingKey, blockPropKey],
);

const shortcut = useMemo(() => {
if (!comboKey.key) return "";

const modifiers = getModifiersFromCombo(comboKey);
const comboString = [...modifiers, comboKey.key].join("+");
return normalizeKeyCombo(comboString).join("+");
}, [comboKey]);
const shortcut = useMemo(() => comboToString(comboKey), [comboKey]);

const handleClear = useCallback(() => {
setComboKey({ modifiers: 0, key: "" });
const clearedCombo = { modifiers: 0, key: "" };
setComboKey(clearedCombo);
extensionAPI.settings
.set(settingKey, { modifiers: 0, key: "" })
.set(settingKey, clearedCombo)
.catch(() => console.error("Failed to set setting"));
}, [extensionAPI, settingKey]);
setPersonalSetting([blockPropKey], clearedCombo);
}, [extensionAPI, settingKey, blockPropKey]);

return (
<Label>
Expand Down
8 changes: 4 additions & 4 deletions apps/roam/src/components/settings/utils/zodSchema.example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,9 @@ const personalSettings: PersonalSettings = {
},
},
},
"Personal node menu trigger": ";;",
"Personal node menu trigger": { modifiers: 0, key: ";;" },
"Node search menu trigger": "//",
"Discourse tool shortcut": "d",
"Discourse tool shortcut": { modifiers: 0, key: "d" },
"Discourse context overlay": true,
"Suggestive mode overlay": true,
"Overlay in canvas": false,
Expand All @@ -383,9 +383,9 @@ const personalSettings: PersonalSettings = {

const defaultPersonalSettings: PersonalSettings = {
"Left sidebar": {},
"Personal node menu trigger": "",
"Personal node menu trigger": { modifiers: 0, key: "" },
"Node search menu trigger": "",
"Discourse tool shortcut": "",
"Discourse tool shortcut": { modifiers: 0, key: "" },
"Discourse context overlay": false,
"Suggestive mode overlay": false,
"Overlay in canvas": false,
Expand Down
11 changes: 9 additions & 2 deletions apps/roam/src/components/settings/utils/zodSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,16 @@ export const QuerySettingsSchema = z.object({

export const PersonalSettingsSchema = z.object({
"Left sidebar": LeftSidebarPersonalSettingsSchema,
"Personal node menu trigger": z.string().default(""),
"Personal node menu trigger": z
.union([
z.object({ modifiers: z.number(), key: z.string() }),
z.literal(""),
])
.default({ modifiers: 0, key: "" }),
"Node search menu trigger": z.string().default("@"),
"Discourse tool shortcut": z.string().default(""),
"Discourse tool shortcut": z
.object({ modifiers: z.number(), key: z.string() })
.default({ modifiers: 0, key: "" }),
"Discourse context overlay": z.boolean().default(false),
"Suggestive mode overlay": z.boolean().default(false),
"Overlay in canvas": z.boolean().default(false),
Expand Down