2021-07-26 14:50:31 +01:00
import { _ } from '@joplin/lib/locale' ;
import { MarkupToHtml } from '@joplin/renderer' ;
2023-02-15 10:59:32 -03:00
import { TinyMceEditorEvents } from './types' ;
2024-04-01 15:34:22 +01:00
import { focus } from '@joplin/lib/utils/focusHandler' ;
2021-07-26 14:50:31 +01:00
const taboverride = require ( 'taboverride' ) ;
interface SourceInfo {
openCharacters : string ;
closeCharacters : string ;
content : string ;
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-07-26 14:50:31 +01:00
node : any ;
language : string ;
}
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-07-26 14:50:31 +01:00
function dialogTextArea_keyDown ( event : any ) {
if ( event . key === 'Tab' ) {
2024-04-01 15:34:22 +01:00
window . requestAnimationFrame ( ( ) = > focus ( 'openEditDialog::dialogTextArea_keyDown' , event . target ) ) ;
2021-07-26 14:50:31 +01:00
}
}
// Allows pressing tab in a textarea to input an actual tab (instead of changing focus)
// taboverride will take care of actually inserting the tab character, while the keydown
// event listener will override the default behaviour, which is to focus the next field.
function enableTextAreaTab ( enable : boolean ) {
const textAreas = document . getElementsByClassName ( 'tox-textarea' ) ;
for ( const textArea of textAreas ) {
taboverride . set ( textArea , enable ) ;
if ( enable ) {
textArea . addEventListener ( 'keydown' , dialogTextArea_keyDown ) ;
} else {
textArea . removeEventListener ( 'keydown' , dialogTextArea_keyDown ) ;
}
}
}
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-07-26 14:50:31 +01:00
function findBlockSource ( node : any ) : SourceInfo {
const sources = node . getElementsByClassName ( 'joplin-source' ) ;
if ( ! sources . length ) throw new Error ( 'No source for node' ) ;
const source = sources [ 0 ] ;
return {
openCharacters : source.getAttribute ( 'data-joplin-source-open' ) ,
closeCharacters : source.getAttribute ( 'data-joplin-source-close' ) ,
content : source.textContent ,
node : source ,
language : source.getAttribute ( 'data-joplin-language' ) || '' ,
} ;
}
2023-06-30 09:11:26 +01:00
function newBlockSource ( language = '' , content = '' , previousSource : SourceInfo = null ) : SourceInfo {
2021-07-26 14:50:31 +01:00
let fence = '```' ;
if ( language === 'katex' ) {
if ( previousSource && previousSource . openCharacters === '$' ) {
fence = '$' ;
} else {
fence = '$$' ;
}
}
const fenceLanguage = language === 'katex' ? '' : language ;
return {
openCharacters : fence === '$' ? '$' : ` \ n ${ fence } ${ fenceLanguage } \ n ` ,
closeCharacters : fence === '$' ? '$' : ` \ n ${ fence } \ n ` ,
content : content ,
node : null ,
language : language ,
} ;
}
function editableInnerHtml ( html : string ) : string {
const temp = document . createElement ( 'div' ) ;
temp . innerHTML = html ;
const editable = temp . getElementsByClassName ( 'joplin-editable' ) ;
if ( ! editable . length ) throw new Error ( ` Invalid joplin-editable: ${ html } ` ) ;
return editable [ 0 ] . innerHTML ;
}
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied
2021-07-26 14:50:31 +01:00
export default function openEditDialog ( editor : any , markupToHtml : any , dispatchDidUpdate : Function , editable : any ) {
const source = editable ? findBlockSource ( editable ) : newBlockSource ( ) ;
editor . windowManager . open ( {
title : _ ( 'Edit' ) ,
size : 'large' ,
initialData : {
codeTextArea : source.content ,
languageInput : source.language ,
} ,
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-07-26 14:50:31 +01:00
onSubmit : async ( dialogApi : any ) = > {
const newSource = newBlockSource ( dialogApi . getData ( ) . languageInput , dialogApi . getData ( ) . codeTextArea , source ) ;
2024-04-17 02:19:25 -07:00
const md = ` ${ newSource . openCharacters } ${ newSource . content } ${ newSource . closeCharacters } ` ;
2021-07-26 14:50:31 +01:00
const result = await markupToHtml . current ( MarkupToHtml . MARKUP_LANGUAGE_MARKDOWN , md , { bodyOnly : true } ) ;
// markupToHtml will return the complete editable HTML, but we only
// want to update the inner HTML, so as not to break additional props that
// are added by TinyMCE on the main node.
if ( editable ) {
editable . innerHTML = editableInnerHtml ( result . html ) ;
} else {
editor . insertContent ( result . html ) ;
}
dialogApi . close ( ) ;
2023-02-15 10:59:32 -03:00
editor . fire ( TinyMceEditorEvents . JoplinChange ) ;
2021-07-26 14:50:31 +01:00
dispatchDidUpdate ( editor ) ;
} ,
onClose : ( ) = > {
enableTextAreaTab ( false ) ;
} ,
body : {
type : 'panel' ,
items : [
{
type : 'input' ,
name : 'languageInput' ,
label : 'Language' ,
// Katex is a special case with special opening/closing tags
// and we don't currently handle switching the language in this case.
disabled : source.language === 'katex' ,
} ,
{
type : 'textarea' ,
name : 'codeTextArea' ,
value : source.content ,
} ,
] ,
} ,
buttons : [
{
type : 'submit' ,
text : 'OK' ,
} ,
] ,
} ) ;
window . requestAnimationFrame ( ( ) = > {
enableTextAreaTab ( true ) ;
} ) ;
}