@@ -12,6 +12,9 @@ import getDiscourseNodes from "./getDiscourseNodes";
1212import fireQuery from "./fireQuery" ;
1313import { excludeDefaultNodes } from "~/utils/getDiscourseNodes" ;
1414import { render as renderSettings } from "~/components/settings/Settings" ;
15+ import { renderModifyNodeDialog } from "~/components/ModifyNodeDialog" ;
16+ import getTextByBlockUid from "roamjs-components/queries/getTextByBlockUid" ;
17+ import getUids from "roamjs-components/dom/getUids" ;
1518import {
1619 getOverlayHandler ,
1720 onPageRefObserverChange ,
@@ -155,6 +158,74 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => {
155158 renderSettings ( { onloadArgs } ) ;
156159 } ;
157160
161+ const getSelectionStartForBlock = ( uid : string ) : number => {
162+ const activeElement = document . activeElement ;
163+ const isFocusedTextarea =
164+ activeElement instanceof HTMLTextAreaElement &&
165+ activeElement . classList . contains ( "rm-block-input" ) &&
166+ getUids ( activeElement ) . blockUid === uid ;
167+ if ( isFocusedTextarea ) return activeElement . selectionStart ;
168+ const textareas = document . querySelectorAll ( "textarea.rm-block-input" ) ;
169+ for ( const el of textareas ) {
170+ const textarea = el as HTMLTextAreaElement ;
171+ if ( getUids ( textarea ) . blockUid === uid ) return textarea . selectionStart ;
172+ }
173+ return ( getTextByBlockUid ( uid ) || "" ) . length ;
174+ } ;
175+
176+ const createDiscourseNodeFromCommand = ( ) => {
177+ posthog . capture ( "Discourse Node: Create Command Triggered" ) ;
178+ const focusedBlock = window . roamAlphaAPI . ui . getFocusedBlock ( ) ;
179+ const uid = focusedBlock ?. [ "block-uid" ] ;
180+ const windowId = focusedBlock ?. [ "window-id" ] || "main-window" ;
181+
182+ const selectionStart = uid ? getSelectionStartForBlock ( uid ) : 0 ;
183+
184+ const defaultNodeType =
185+ getDiscourseNodes ( ) . filter ( excludeDefaultNodes ) [ 0 ] ?. type ;
186+ if ( ! defaultNodeType ) {
187+ renderToast ( {
188+ id : "create-discourse-node-command-no-types" ,
189+ content : "No discourse node types found in settings." ,
190+ } ) ;
191+ return ;
192+ }
193+
194+ renderModifyNodeDialog ( {
195+ mode : "create" ,
196+ nodeType : defaultNodeType ,
197+ initialValue : { text : "" , uid : "" } ,
198+ extensionAPI,
199+ onSuccess : async ( result ) => {
200+ if ( ! uid ) {
201+ renderToast ( {
202+ id : "create-discourse-node-command-no-block" ,
203+ content : "No block focused to insert a discourse node." ,
204+ } ) ;
205+ return ;
206+ }
207+ const originalText = getTextByBlockUid ( uid ) || "" ;
208+ const pageRef = `[[${ result . text } ]]` ;
209+ const newText = `${ originalText . substring ( 0 , selectionStart ) } ${ pageRef } ${ originalText . substring ( selectionStart ) } ` ;
210+ const newCursorPosition = selectionStart + pageRef . length ;
211+
212+ await updateBlock ( { uid, text : newText } ) ;
213+
214+ await window . roamAlphaAPI . ui . setBlockFocusAndSelection ( {
215+ location : {
216+ // eslint-disable-next-line @typescript-eslint/naming-convention
217+ "block-uid" : uid ,
218+ // eslint-disable-next-line @typescript-eslint/naming-convention
219+ "window-id" : windowId ,
220+ } ,
221+ selection : { start : newCursorPosition } ,
222+ } ) ;
223+ return ;
224+ } ,
225+ onClose : ( ) => { } ,
226+ } ) ;
227+ } ;
228+
158229 const toggleDiscourseContextOverlay = async ( ) => {
159230 const currentValue =
160231 ( extensionAPI . settings . get ( "discourse-context-overlay" ) as boolean ) ??
@@ -212,6 +283,10 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => {
212283 } ;
213284
214285 // Roam organizes commands alphabetically
286+ void addCommand (
287+ "DG: Create/Insert discourse node" ,
288+ createDiscourseNodeFromCommand ,
289+ ) ;
215290 void addCommand ( "DG: Export - Current page" , exportCurrentPage ) ;
216291 void addCommand ( "DG: Export - Discourse graph" , exportDiscourseGraph ) ;
217292 void addCommand ( "DG: Open - Discourse settings" , renderSettingsPopup ) ;
0 commit comments