@@ -54,107 +54,127 @@ export const useMarkdownHotkeys = (
5454 const handleHotkey = useCallback (
5555 ( hotkey : Hotkey ) => ( e : KeyboardEvent ) => {
5656 e . preventDefault ( ) ;
57- if ( textareaRef . current ) {
58- const textarea = textareaRef . current ;
59- const startPos = textarea . selectionStart ;
60- const endPos = textarea . selectionEnd ;
61- const currentValue = textarea . value ;
62- const { markup, type } = hotkey ;
63- let newText ;
64-
65- switch ( type ) {
66- case "pre" :
67- newText = `${ markup } ${ currentValue . slice ( startPos , endPos ) } ` ;
68- break ;
69-
70- case "wrap" :
71- // check for codeBlock, url then default wrap
72- if ( hotkey . key === "c" && hotkey . useShift ) {
73- newText = `${ markup } \n\n${ markup } ` ;
74- } else if ( hotkey . key === "u" ) {
75- newText = `${ markup [ 0 ] } ${ currentValue . slice ( startPos , endPos ) } ${
76- markup [ 1 ]
77- } `;
78- } else {
79- newText = `${ markup } ${ currentValue . slice (
80- startPos ,
81- endPos ,
82- ) } ${ markup } `;
83- }
84- break ;
85-
86- case "blockQuote" :
87- const lines = currentValue . slice ( startPos , endPos ) . split ( "\n" ) ;
88- const quotedLines = lines . map ( ( line ) => `${ markup } ${ line } ` ) ;
89- newText = quotedLines . join ( "\n" ) ;
90- break ;
91-
92- case "linkOrImage" :
93- const selectedText = currentValue . slice ( startPos , endPos ) ;
94- if ( ! selectedText ) return ; // Do nothing if no text is selected
95-
96- const url = prompt ( "Enter the URL:" ) ;
97- if ( ! url ) return ;
98-
99- const tag = markup
100- . replace ( "text" , selectedText )
101- . replace ( "url" , url ) ;
102- textarea . value = `${ currentValue . slice (
103- 0 ,
57+ e . stopPropagation ( ) ;
58+
59+ const textarea = textareaRef . current ;
60+ if ( ! textarea ) return ;
61+
62+ const startPos = textarea . selectionStart ;
63+ const endPos = textarea . selectionEnd ;
64+ const currentValue = textarea . value ;
65+ const { markup, type } = hotkey ;
66+ let newText ;
67+
68+ switch ( type ) {
69+ case "pre" :
70+ newText = `${ markup } ${ currentValue . slice ( startPos , endPos ) } ` ;
71+ break ;
72+
73+ case "wrap" :
74+ // check for codeBlock, url then default wrap
75+ if ( hotkey . key === "c" && hotkey . useShift ) {
76+ newText = `${ markup } \n\n${ markup } ` ;
77+ } else if ( hotkey . key === "u" ) {
78+ newText = `${ markup [ 0 ] } ${ currentValue . slice ( startPos , endPos ) } ${
79+ markup [ 1 ]
80+ } `;
81+ } else {
82+ newText = `${ markup } ${ currentValue . slice (
10483 startPos ,
105- ) } ${ tag } ${ currentValue . slice ( endPos ) } `;
106- const cursorPos = startPos + tag . length ;
107- textarea . setSelectionRange ( cursorPos , cursorPos ) ;
108- return ;
109-
110- case "select" :
111- let start = startPos - 1 ;
112-
113- // Move left while the cursor is on whitespace
114- while ( start >= 0 && / \s / . test ( currentValue [ start ] ) ) {
115- start -- ;
116- }
117-
118- // Move left while the cursor is on non-whitespace
119- while ( start >= 0 && / \S / . test ( currentValue [ start ] ) ) {
120- start -- ;
121- }
122-
123- start ++ ; // Move to the beginning of the word
124-
125- // Trim right whitespace
126- let trimmedEnd = endPos ;
127- while ( / \s / . test ( currentValue [ trimmedEnd - 1 ] ) ) {
128- trimmedEnd -- ;
129- }
130- textarea . setSelectionRange ( start , trimmedEnd ) ;
131- return ;
132-
133- default :
134- setSelectCount ( 0 ) ;
135- return ;
136- }
137-
138- textarea . value = `${ currentValue . slice (
139- 0 ,
140- startPos ,
141- ) } ${ newText } ${ currentValue . slice ( endPos ) } `;
142- const cursorPos =
143- type === "wrap" && hotkey . key === "c" && hotkey . useShift
144- ? startPos + markup . length + 1
145- : startPos + newText . length ;
146- textarea . setSelectionRange ( cursorPos , cursorPos ) ;
84+ endPos ,
85+ ) } ${ markup } `;
86+ }
87+ break ;
88+
89+ case "blockQuote" :
90+ const lines = currentValue . slice ( startPos , endPos ) . split ( "\n" ) ;
91+ const quotedLines = lines . map ( ( line ) => `${ markup } ${ line } ` ) ;
92+ newText = quotedLines . join ( "\n" ) ;
93+ break ;
94+
95+ case "linkOrImage" :
96+ const selectedText = currentValue . slice ( startPos , endPos ) ;
97+ if ( ! selectedText ) return ; // Do nothing if no text is selected
98+
99+ const url = prompt ( "Enter the URL:" ) ;
100+ if ( ! url ) return ;
101+
102+ const tag = markup
103+ . replace ( "text" , selectedText )
104+ . replace ( "url" , url ) ;
105+ textarea . value = `${ currentValue . slice (
106+ 0 ,
107+ startPos ,
108+ ) } ${ tag } ${ currentValue . slice ( endPos ) } `;
109+ const cursorPos = startPos + tag . length ;
110+ textarea . setSelectionRange ( cursorPos , cursorPos ) ;
111+ return ;
112+
113+ case "select" :
114+ let start = startPos - 1 ;
115+
116+ // Move left while the cursor is on whitespace
117+ while ( start >= 0 && / \s / . test ( currentValue [ start ] ) ) {
118+ start -- ;
119+ }
120+
121+ // Move left while the cursor is on non-whitespace
122+ while ( start >= 0 && / \S / . test ( currentValue [ start ] ) ) {
123+ start -- ;
124+ }
125+
126+ start ++ ; // Move to the beginning of the word
127+
128+ // Trim right whitespace
129+ let trimmedEnd = endPos ;
130+ while ( / \s / . test ( currentValue [ trimmedEnd - 1 ] ) ) {
131+ trimmedEnd -- ;
132+ }
133+ textarea . setSelectionRange ( start , trimmedEnd ) ;
134+ return ;
135+
136+ default :
137+ return ;
147138 }
139+
140+ textarea . value = `${ currentValue . slice (
141+ 0 ,
142+ startPos ,
143+ ) } ${ newText } ${ currentValue . slice ( endPos ) } `;
144+ const cursorPos =
145+ type === "wrap" && hotkey . key === "c" && hotkey . useShift
146+ ? startPos + markup . length + 1
147+ : startPos + newText . length ;
148+ textarea . setSelectionRange ( cursorPos , cursorPos ) ;
148149 } ,
149- [ ] ,
150+ [ textareaRef ] ,
150151 ) ;
151152
152- // Map each hotkey to its corresponding callback
153- Object . values ( hotkeys ) . forEach ( ( hotkey ) => {
154- useHotkeys (
155- `${ hotkey . key } ${ hotkey . useShift ? "+meta+shift" : "+meta" } ` ,
156- handleHotkey ( hotkey ) ,
157- { enableOnFormTags : true } ,
158- ) ;
159- } ) ;
153+ // Use useEffect to bind event listeners directly to the textarea
154+ useEffect ( ( ) => {
155+ const textarea = textareaRef . current ;
156+ if ( ! textarea ) return ;
157+
158+ const keydownHandler = ( e : KeyboardEvent ) => {
159+ // Check if it's a meta/ctrl key combination
160+ if ( ! e . metaKey && ! e . ctrlKey ) return ;
161+
162+ // Find matching hotkey
163+ const matchingHotkey = Object . values ( hotkeys ) . find ( ( hotkey ) => {
164+ const isCorrectKey = e . key === hotkey . key ;
165+ const hasCorrectShift = hotkey . useShift ? e . shiftKey : ! e . shiftKey ;
166+ return isCorrectKey && hasCorrectShift ;
167+ } ) ;
168+
169+ if ( matchingHotkey ) {
170+ handleHotkey ( matchingHotkey ) ( e ) ;
171+ }
172+ } ;
173+
174+ textarea . addEventListener ( 'keydown' , keydownHandler ) ;
175+
176+ return ( ) => {
177+ textarea . removeEventListener ( 'keydown' , keydownHandler ) ;
178+ } ;
179+ } , [ textareaRef , handleHotkey ] ) ;
160180} ;
0 commit comments