@ -4,14 +4,23 @@ export function triggerEditorContentChanged(target: HTMLElement) {
target . dispatchEvent ( new CustomEvent ( EventEditorContentChanged , { bubbles : true } ) ) ;
}
export function textareaInsertText ( textarea : HTMLTextAreaElement , value : string ) {
const startPos = textarea . selectionStart ;
const endPos = textarea . selectionEnd ;
textarea . value = textarea . value . substring ( 0 , startPos ) + value + textarea . value . substring ( endPos ) ;
textarea . selectionStart = startPos ;
textarea . selectionEnd = startPos + value . length ;
/ * * r e p l a c e s e l e c t e d t e x t o r i n s e r t t e x t b y c r e a t i n g a n e w e d i t h i s t o r y e n t r y ,
* e . g . CTRL - Z works after this * /
export function replaceTextareaSelection ( textarea : HTMLTextAreaElement , text : string ) {
const before = textarea . value . slice ( 0 , textarea . selectionStart ) ;
const after = textarea . value . slice ( textarea . selectionEnd ) ;
textarea . focus ( ) ;
triggerEditorContentChanged ( textarea ) ;
let success = false ;
try {
success = document . execCommand ( 'insertText' , false , text ) ; // eslint-disable-line @typescript-eslint/no-deprecated
} catch { }
// fall back to regular replacement
if ( ! success ) {
textarea . value = ` ${ before } ${ text } ${ after } ` ;
triggerEditorContentChanged ( textarea ) ;
}
}
type TextareaValueSelection = {
@ -176,7 +185,7 @@ export function markdownHandleIndention(tvs: TextareaValueSelection): MarkdownHa
return { handled : true , valueSelection : { value : linesBuf.lines.join ( '\n' ) , selStart : newPos , selEnd : newPos } } ;
}
function handleNewline ( textarea : HTMLTextAreaElement , e : Event) {
function handleNewline ( textarea : HTMLTextAreaElement , e : Keyboard Event) {
const ret = markdownHandleIndention ( { value : textarea.value , selStart : textarea.selectionStart , selEnd : textarea.selectionEnd } ) ;
if ( ! ret . handled || ! ret . valueSelection ) return ; // FIXME: the "handled" seems redundant, only valueSelection is enough (null for unhandled)
e . preventDefault ( ) ;
@ -185,6 +194,28 @@ function handleNewline(textarea: HTMLTextAreaElement, e: Event) {
triggerEditorContentChanged ( textarea ) ;
}
// Keys that act as dead keys will not work because the spec dictates that such keys are
// emitted as `Dead` in e.key instead of the actual key.
const pairs = new Map < string , string > ( [
[ "'" , "'" ] ,
[ '"' , '"' ] ,
[ '`' , '`' ] ,
[ '(' , ')' ] ,
[ '[' , ']' ] ,
[ '{' , '}' ] ,
[ '<' , '>' ] ,
] ) ;
function handlePairCharacter ( textarea : HTMLTextAreaElement , e : KeyboardEvent ) : void {
const selStart = textarea . selectionStart ;
const selEnd = textarea . selectionEnd ;
if ( selEnd === selStart ) return ; // do not process when no selection
e . preventDefault ( ) ;
const inner = textarea . value . substring ( selStart , selEnd ) ;
replaceTextareaSelection ( textarea , ` ${ e . key } ${ inner } ${ pairs . get ( e . key ) } ` ) ;
textarea . setSelectionRange ( selStart + 1 , selEnd + 1 ) ;
}
function isTextExpanderShown ( textarea : HTMLElement ) : boolean {
return Boolean ( textarea . closest ( 'text-expander' ) ? . querySelector ( '.suggestions' ) ) ;
}
@ -198,6 +229,8 @@ export function initTextareaMarkdown(textarea: HTMLTextAreaElement) {
} else if ( e . key === 'Enter' && ! e . shiftKey && ! e . ctrlKey && ! e . metaKey && ! e . altKey ) {
// use Enter to insert a new line with the same indention and prefix
handleNewline ( textarea , e ) ;
} else if ( pairs . has ( e . key ) ) {
handlePairCharacter ( textarea , e ) ;
}
} ) ;
}