diff --git a/.eslintignore b/.eslintignore
index b7f7372f4..db0300d39 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -372,6 +372,9 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js.map
+packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.d.ts
+packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
+packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js.map
diff --git a/.gitignore b/.gitignore
index c9b9fb1d4..2aa5a343a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -357,6 +357,9 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js.map
+packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.d.ts
+packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
+packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js.map
diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx
index f5d0df4bc..70c150624 100644
--- a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx
+++ b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx
@@ -16,11 +16,11 @@ import { copyHtmlToClipboard } from '../../utils/clipboardUtils';
import shim from '@joplin/lib/shim';
const { MarkupToHtml } = require('@joplin/renderer');
-const taboverride = require('taboverride');
import { reg } from '@joplin/lib/registry';
import BaseItem from '@joplin/lib/models/BaseItem';
import setupToolbarButtons from './utils/setupToolbarButtons';
import { plainTextToHtml } from '@joplin/lib/htmlUtils';
+import openEditDialog from './utils/openEditDialog';
const { themeStyle } = require('@joplin/lib/theme');
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
@@ -40,33 +40,6 @@ function markupRenderOptions(override: any = null) {
};
}
-function findBlockSource(node: any) {
- 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') || '',
- };
-}
-
-function newBlockSource(language: string = '', content: string = ''): any {
- const fence = language === 'katex' ? '$$' : '```';
- const fenceLanguage = language === 'katex' ? '' : language;
-
- return {
- openCharacters: `\n${fence}${fenceLanguage}\n`,
- closeCharacters: `\n${fence}\n`,
- content: content,
- node: null,
- language: language,
- };
-}
-
// In TinyMCE 5.2, when setting the body to '
',
// it would end up as '
' once rendered
// (an additional
was inserted).
@@ -95,42 +68,12 @@ function findEditableContainer(node: any): any {
return null;
}
-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;
-}
-
-function dialogTextArea_keyDown(event: any) {
- if (event.key === 'Tab') {
- window.requestAnimationFrame(() => event.target.focus());
- }
-}
-
let markupToHtml_ = new MarkupToHtml();
function stripMarkup(markupLanguage: number, markup: string, options: any = null) {
if (!markupToHtml_) markupToHtml_ = new MarkupToHtml();
return markupToHtml_.stripMarkup(markupLanguage, markup, options);
}
-// 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);
- }
- }
-}
-
interface TinyMceCommand {
name: string;
value?: any;
@@ -618,70 +561,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
joplinSup: { inline: 'sup', remove: 'all' },
},
setup: (editor: any) => {
-
- function openEditDialog(editable: any) {
- const source = editable ? findBlockSource(editable) : newBlockSource();
-
- editor.windowManager.open({
- title: _('Edit'),
- size: 'large',
- initialData: {
- codeTextArea: source.content,
- languageInput: source.language,
- },
- onSubmit: async (dialogApi: any) => {
- const newSource = newBlockSource(dialogApi.getData().languageInput, dialogApi.getData().codeTextArea);
- const md = `${newSource.openCharacters}${newSource.content.trim()}${newSource.closeCharacters}`;
- 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();
- editor.fire('joplinChange');
- 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);
- });
- }
-
editor.ui.registry.addButton('joplinAttach', {
tooltip: _('Attach file'),
icon: 'paperclip',
@@ -696,7 +575,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
tooltip: _('Code Block'),
icon: 'code-sample',
onAction: async function() {
- openEditDialog(null);
+ openEditDialog(editor, markupToHtml, dispatchDidUpdate, null);
},
});
@@ -738,12 +617,10 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
editor.addShortcut('Meta+Shift+8', '', () => editor.execCommand('InsertUnorderedList'));
editor.addShortcut('Meta+Shift+9', '', () => editor.execCommand('InsertJoplinChecklist'));
- // setupContextMenu(editor);
-
// TODO: remove event on unmount?
editor.on('DblClick', (event: any) => {
const editable = findEditableContainer(event.target);
- if (editable) openEditDialog(editable);
+ if (editable) openEditDialog(editor, markupToHtml, dispatchDidUpdate, editable);
});
// This is triggered when an external file is dropped on the editor
diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.ts b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.ts
new file mode 100644
index 000000000..6b1bf604b
--- /dev/null
+++ b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.ts
@@ -0,0 +1,140 @@
+import { _ } from '@joplin/lib/locale';
+import { MarkupToHtml } from '@joplin/renderer';
+const taboverride = require('taboverride');
+
+interface SourceInfo {
+ openCharacters: string;
+ closeCharacters: string;
+ content: string;
+ node: any;
+ language: string;
+}
+
+function dialogTextArea_keyDown(event: any) {
+ if (event.key === 'Tab') {
+ window.requestAnimationFrame(() => event.target.focus());
+ }
+}
+
+// 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);
+ }
+ }
+}
+
+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') || '',
+ };
+}
+
+function newBlockSource(language: string = '', content: string = '', previousSource: SourceInfo = null): SourceInfo {
+ 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;
+}
+
+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,
+ },
+ onSubmit: async (dialogApi: any) => {
+ const newSource = newBlockSource(dialogApi.getData().languageInput, dialogApi.getData().codeTextArea, source);
+ const md = `${newSource.openCharacters}${newSource.content.trim()}${newSource.closeCharacters}`;
+ 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();
+ editor.fire('joplinChange');
+ 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);
+ });
+}