-
Notifications
You must be signed in to change notification settings - Fork 4
[ENG-299] Trigger for DG summoning menu #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,8 @@ import { | |||||||||||||||||||||||||||||
| createButtonObserver, | ||||||||||||||||||||||||||||||
| getPageTitleValueByHtmlElement, | ||||||||||||||||||||||||||||||
| } from "roamjs-components/dom"; | ||||||||||||||||||||||||||||||
| import { createBlock } from "roamjs-components/writes"; | ||||||||||||||||||||||||||||||
| import { createBlock, updateBlock } from "roamjs-components/writes"; | ||||||||||||||||||||||||||||||
| import getUids from "roamjs-components/dom/getUids"; | ||||||||||||||||||||||||||||||
| import { renderLinkedReferenceAdditions } from "~/utils/renderLinkedReferenceAdditions"; | ||||||||||||||||||||||||||||||
| import { createConfigObserver } from "roamjs-components/components/ConfigPage"; | ||||||||||||||||||||||||||||||
| import { renderTldrawCanvas } from "~/components/canvas/Tldraw"; | ||||||||||||||||||||||||||||||
|
|
@@ -148,6 +149,11 @@ export const initObservers = async ({ | |||||||||||||||||||||||||||||
| ) as IKeyCombo) || undefined; | ||||||||||||||||||||||||||||||
| const personalTrigger = personalTriggerCombo?.key; | ||||||||||||||||||||||||||||||
| const personalModifiers = getModifiersFromCombo(personalTriggerCombo); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const nodeSearchTriggerCombo = | ||||||||||||||||||||||||||||||
| (onloadArgs.extensionAPI.settings.get( | ||||||||||||||||||||||||||||||
| "discourse-node-search-trigger", | ||||||||||||||||||||||||||||||
| ) as IKeyCombo) || undefined; | ||||||||||||||||||||||||||||||
| const handleNodeMenuRender = (target: HTMLElement, evt: KeyboardEvent) => { | ||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||
| target.tagName === "TEXTAREA" && | ||||||||||||||||||||||||||||||
|
|
@@ -162,6 +168,50 @@ export const initObservers = async ({ | |||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const handleNodeSearchRender = (target: HTMLElement, evt: KeyboardEvent) => { | ||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||
| target.tagName === "TEXTAREA" && | ||||||||||||||||||||||||||||||
| target.classList.contains("rm-block-input") | ||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||
| const textarea = target as HTMLTextAreaElement; | ||||||||||||||||||||||||||||||
| const location = window.roamAlphaAPI.ui.getFocusedBlock(); | ||||||||||||||||||||||||||||||
| if (!location) return; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const cursorPos = textarea.selectionStart; | ||||||||||||||||||||||||||||||
| const isBeginningOrAfterSpace = | ||||||||||||||||||||||||||||||
| cursorPos === 0 || | ||||||||||||||||||||||||||||||
| textarea.value.charAt(cursorPos - 1) === " " || | ||||||||||||||||||||||||||||||
| textarea.value.charAt(cursorPos - 1) === "\n"; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (isBeginningOrAfterSpace) { | ||||||||||||||||||||||||||||||
| const triggerChar = nodeSearchTriggerCombo?.key || "@"; | ||||||||||||||||||||||||||||||
| let triggerPosition = cursorPos; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (!evt.isComposing && evt.key !== triggerChar) { | ||||||||||||||||||||||||||||||
| const text = textarea.value; | ||||||||||||||||||||||||||||||
| const newText = | ||||||||||||||||||||||||||||||
| text.slice(0, cursorPos) + triggerChar + text.slice(cursorPos); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const blockUid = getUids(textarea).blockUid; | ||||||||||||||||||||||||||||||
| if (blockUid) { | ||||||||||||||||||||||||||||||
| updateBlock({ uid: blockUid, text: newText }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| triggerPosition = cursorPos; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+190
to
+200
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Asynchronous write may race with UI state and cursor management - updateBlock({ uid: blockUid, text: newText });
+ await updateBlock({ uid: blockUid, text: newText });
+ // Keep the local textarea in-sync and restore cursor
+ textarea.value = newText;
+ textarea.setSelectionRange(triggerPosition + 1, triggerPosition + 1);Turning
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| renderDiscourseNodeSearchMenu({ | ||||||||||||||||||||||||||||||
| onClose: () => {}, | ||||||||||||||||||||||||||||||
| textarea: textarea, | ||||||||||||||||||||||||||||||
| triggerPosition: triggerPosition, | ||||||||||||||||||||||||||||||
| extensionAPI: onloadArgs.extensionAPI, | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| evt.preventDefault(); | ||||||||||||||||||||||||||||||
| evt.stopPropagation(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const nodeMenuTriggerListener = (e: Event) => { | ||||||||||||||||||||||||||||||
| const evt = e as KeyboardEvent; | ||||||||||||||||||||||||||||||
| const target = evt.target as HTMLElement; | ||||||||||||||||||||||||||||||
|
|
@@ -191,28 +241,26 @@ export const initObservers = async ({ | |||||||||||||||||||||||||||||
| const target = evt.target as HTMLElement; | ||||||||||||||||||||||||||||||
| if (document.querySelector(".discourse-node-search-menu")) return; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (evt.key === "@" || (evt.key === "2" && evt.shiftKey)) { | ||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||
| target.tagName === "TEXTAREA" && | ||||||||||||||||||||||||||||||
| target.classList.contains("rm-block-input") | ||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||
| const textarea = target as HTMLTextAreaElement; | ||||||||||||||||||||||||||||||
| const location = window.roamAlphaAPI.ui.getFocusedBlock(); | ||||||||||||||||||||||||||||||
| if (!location) return; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const cursorPos = textarea.selectionStart; | ||||||||||||||||||||||||||||||
| const isBeginningOrAfterSpace = | ||||||||||||||||||||||||||||||
| cursorPos === 0 || | ||||||||||||||||||||||||||||||
| textarea.value.charAt(cursorPos - 1) === " " || | ||||||||||||||||||||||||||||||
| textarea.value.charAt(cursorPos - 1) === "\n"; | ||||||||||||||||||||||||||||||
| if (isBeginningOrAfterSpace) { | ||||||||||||||||||||||||||||||
| renderDiscourseNodeSearchMenu({ | ||||||||||||||||||||||||||||||
| onClose: () => {}, | ||||||||||||||||||||||||||||||
| textarea: textarea, | ||||||||||||||||||||||||||||||
| triggerPosition: cursorPos, | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| // If no personal trigger is set or key is empty, the feature is disabled | ||||||||||||||||||||||||||||||
| if (!nodeSearchTriggerCombo?.key) return; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const personalTrigger = nodeSearchTriggerCombo.key; | ||||||||||||||||||||||||||||||
| const personalModifiers = getModifiersFromCombo(nodeSearchTriggerCombo); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let triggerMatched = false; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| console.log("evt.key", evt.key); | ||||||||||||||||||||||||||||||
| console.log("personal trigger", personalTrigger, nodeSearchTriggerCombo); | ||||||||||||||||||||||||||||||
| if (evt.key === personalTrigger) { | ||||||||||||||||||||||||||||||
| triggerMatched = | ||||||||||||||||||||||||||||||
| (!personalModifiers.includes("ctrl") || evt.ctrlKey) && | ||||||||||||||||||||||||||||||
| (!personalModifiers.includes("shift") || evt.shiftKey) && | ||||||||||||||||||||||||||||||
| (!personalModifiers.includes("alt") || evt.altKey) && | ||||||||||||||||||||||||||||||
| (!personalModifiers.includes("meta") || evt.metaKey); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+254
to
+260
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Trigger matching is fragile – make it case-insensitive and reuse - if (evt.key === personalTrigger) {
+ if (evt.key.toLowerCase() === personalTrigger.toLowerCase()) {Even better, call the already-exported 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (triggerMatched) { | ||||||||||||||||||||||||||||||
| handleNodeSearchRender(target, evt); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Empty query leaves menu open – UI feels “stuck”
handleTextAreaInputno longer closes the menu when the user backspaces past the trigger.That means the pop-over lingers with an empty result list, which can feel broken.
Alternatively, keep it open but call
debouncedSearchTerm("")so the UI refreshes to an explicit “No matches” state.📝 Committable suggestion
🤖 Prompt for AI Agents