You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-10 22:11:50 +02:00
Desktop: Rich Text Editor: Add KaTeX to supported auto-replacements (#12081)
This commit is contained in:
@@ -266,6 +266,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHan
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTextPatternsLookup.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteEditor.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -241,6 +241,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHan
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTextPatternsLookup.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteEditor.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
|
@@ -6,10 +6,10 @@ import * as exportDeletionLog from './exportDeletionLog';
|
||||
import * as exportFolders from './exportFolders';
|
||||
import * as exportNotes from './exportNotes';
|
||||
import * as focusElement from './focusElement';
|
||||
import * as openSecondaryAppInstance from './openSecondaryAppInstance';
|
||||
import * as openPrimaryAppInstance from './openPrimaryAppInstance';
|
||||
import * as openNoteInNewWindow from './openNoteInNewWindow';
|
||||
import * as openPrimaryAppInstance from './openPrimaryAppInstance';
|
||||
import * as openProfileDirectory from './openProfileDirectory';
|
||||
import * as openSecondaryAppInstance from './openSecondaryAppInstance';
|
||||
import * as replaceMisspelling from './replaceMisspelling';
|
||||
import * as restoreNoteRevision from './restoreNoteRevision';
|
||||
import * as startExternalEditing from './startExternalEditing';
|
||||
@@ -30,10 +30,10 @@ const index: any[] = [
|
||||
exportFolders,
|
||||
exportNotes,
|
||||
focusElement,
|
||||
openSecondaryAppInstance,
|
||||
openPrimaryAppInstance,
|
||||
openNoteInNewWindow,
|
||||
openPrimaryAppInstance,
|
||||
openProfileDirectory,
|
||||
openSecondaryAppInstance,
|
||||
replaceMisspelling,
|
||||
restoreNoteRevision,
|
||||
startExternalEditing,
|
||||
|
@@ -43,6 +43,7 @@ import useKeyboardRefocusHandler from './utils/useKeyboardRefocusHandler';
|
||||
import useDocument from '../../../hooks/useDocument';
|
||||
import useEditDialog from './utils/useEditDialog';
|
||||
import useEditDialogEventListeners from './utils/useEditDialogEventListeners';
|
||||
import useTextPatternsLookup from './utils/useTextPatternsLookup';
|
||||
|
||||
const logger = Logger.create('TinyMCE');
|
||||
|
||||
@@ -654,6 +655,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
// Create and setup the editor
|
||||
// -----------------------------------------------------------------------------------------
|
||||
|
||||
const textPatternsLookupRef = useTextPatternsLookup({ enabled: props.enableTextPatterns, enableMath: props.mathEnabled });
|
||||
useEffect(() => {
|
||||
if (!scriptLoaded) return;
|
||||
if (!editorContainer) return;
|
||||
@@ -740,25 +742,38 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
// button to work. See https://github.com/tinymce/tinymce/issues/5026.
|
||||
forecolor: { inline: 'span', styles: { color: '%value' }, remove_similar: true },
|
||||
},
|
||||
text_patterns: props.enableTextPatterns ? [
|
||||
// See https://www.tiny.cloud/docs/tinymce/latest/content-behavior-options/#text_patterns
|
||||
// for the default value
|
||||
{ start: '==', end: '==', format: 'joplinHighlight' },
|
||||
{ start: '`', end: '`', format: 'code' },
|
||||
{ start: '*', end: '*', format: 'italic' },
|
||||
{ start: '**', end: '**', format: 'bold' },
|
||||
{ start: '#', format: 'h1' },
|
||||
{ start: '##', format: 'h2' },
|
||||
{ start: '###', format: 'h3' },
|
||||
{ start: '####', format: 'h4' },
|
||||
{ start: '#####', format: 'h5' },
|
||||
{ start: '######', format: 'h6' },
|
||||
{ start: '1.', cmd: 'InsertOrderedList' },
|
||||
{ start: '*', cmd: 'InsertUnorderedList' },
|
||||
{ start: '-', cmd: 'InsertUnorderedList' },
|
||||
] : [],
|
||||
text_patterns: [],
|
||||
text_patterns_lookup: () => textPatternsLookupRef.current(),
|
||||
|
||||
setup: (editor: Editor) => {
|
||||
editor.addCommand('joplinMath', async () => {
|
||||
const katex = editor.selection.getContent();
|
||||
const md = `$${katex}$`;
|
||||
|
||||
// Save and clear the selection -- when this command is activated by a text pattern,
|
||||
// TinyMCE:
|
||||
// 1. Adjusts the selection just before calling the command to include the to-be-formatted text.
|
||||
// 2. Calls the command.
|
||||
// 3. Removes the "$" characters and restores the selection.
|
||||
//
|
||||
// As a result, the selection needs to be saved and restored.
|
||||
const mathSelection = editor.selection.getBookmark();
|
||||
|
||||
const result = await markupToHtml.current(MarkupLanguage.Markdown, md, { bodyOnly: true });
|
||||
|
||||
// Replace the math...
|
||||
const finalSelection = editor.selection.getBookmark();
|
||||
editor.selection.moveToBookmark(mathSelection);
|
||||
editor.selection.setContent(result.html);
|
||||
editor.selection.moveToBookmark(finalSelection); // ...then move the selection back.
|
||||
|
||||
// Fire update events
|
||||
editor.fire(TinyMceEditorEvents.JoplinChange);
|
||||
dispatchDidUpdate(editor);
|
||||
// The last replacement seems to need to be manually added to the undo history
|
||||
editor.undoManager.add();
|
||||
});
|
||||
|
||||
editor.addCommand('joplinAttach', () => {
|
||||
insertResourcesIntoContentRef.current();
|
||||
});
|
||||
|
@@ -0,0 +1,40 @@
|
||||
import { useRef } from 'react';
|
||||
|
||||
interface TextPatternOptions {
|
||||
enabled: boolean;
|
||||
enableMath: boolean;
|
||||
}
|
||||
|
||||
const useTextPatternsLookup = ({ enabled, enableMath }: TextPatternOptions) => {
|
||||
const getTextPatterns = () => {
|
||||
if (!enabled) return [];
|
||||
|
||||
return [
|
||||
// See https://www.tiny.cloud/docs/tinymce/latest/content-behavior-options/#text_patterns
|
||||
// for the default TinyMCE text patterns
|
||||
{ start: '==', end: '==', format: 'joplinHighlight' },
|
||||
// Only replace math if math rendering is enabled.
|
||||
enableMath && { start: '$', end: '$', cmd: 'joplinMath' },
|
||||
{ start: '`', end: '`', format: 'code' },
|
||||
{ start: '*', end: '*', format: 'italic' },
|
||||
{ start: '**', end: '**', format: 'bold' },
|
||||
{ start: '#', format: 'h1' },
|
||||
{ start: '##', format: 'h2' },
|
||||
{ start: '###', format: 'h3' },
|
||||
{ start: '####', format: 'h4' },
|
||||
{ start: '#####', format: 'h5' },
|
||||
{ start: '######', format: 'h6' },
|
||||
{ start: '1.', cmd: 'InsertOrderedList' },
|
||||
{ start: '*', cmd: 'InsertUnorderedList' },
|
||||
{ start: '-', cmd: 'InsertUnorderedList' },
|
||||
].filter(pattern => !!pattern);
|
||||
};
|
||||
|
||||
// Store the lookup callback in a ref so that the editor doesn't need to be reloaded
|
||||
// to use the new patterns:
|
||||
const patternLookupRef = useRef(getTextPatterns);
|
||||
patternLookupRef.current = getTextPatterns;
|
||||
return patternLookupRef;
|
||||
};
|
||||
|
||||
export default useTextPatternsLookup;
|
@@ -423,6 +423,7 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
|
||||
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
|
||||
|
||||
const markupLanguage = formNote.markup_language;
|
||||
const editorProps: NoteBodyEditorProps = {
|
||||
ref: editorRef,
|
||||
contentKey: formNote.id,
|
||||
@@ -432,7 +433,7 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
onWillChange: onBodyWillChange,
|
||||
onMessage: onMessage,
|
||||
content: formNote.body,
|
||||
contentMarkupLanguage: formNote.markup_language,
|
||||
contentMarkupLanguage: markupLanguage,
|
||||
contentOriginalCss: formNote.originalCss,
|
||||
resourceInfos: resourceInfos,
|
||||
resourceDirectory: Setting.value('resourceDir'),
|
||||
@@ -457,6 +458,8 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
onDrop: onDrop,
|
||||
noteToolbarButtonInfos: props.toolbarButtonInfos,
|
||||
plugins: props.plugins,
|
||||
// KaTeX isn't supported in HTML notes
|
||||
mathEnabled: markupLanguage === MarkupLanguage.Markdown && Setting.value('markdown.plugin.katex'),
|
||||
fontSize: Setting.value('style.editor.fontSize'),
|
||||
contentMaxWidth: props.contentMaxWidth,
|
||||
scrollbarSize: props.scrollbarSize,
|
||||
|
@@ -132,6 +132,7 @@ export interface NoteBodyEditorProps {
|
||||
onDrop: DropHandler;
|
||||
noteToolbarButtonInfos: ToolbarItem[];
|
||||
plugins: PluginStates;
|
||||
mathEnabled: boolean;
|
||||
fontSize: number;
|
||||
contentMaxWidth: number;
|
||||
isSafeMode: boolean;
|
||||
|
@@ -6,6 +6,8 @@ At its core, Joplin stores notes in [Markdown format](https://github.com/laurent
|
||||
|
||||
In some cases however, the extra markup format that appears in notes can be seen as a drawback. Bold text will `look **like this**` for example, and tables might not be particularly readable. For that reason, Joplin also features a Rich Text editor, which allows you to edit notes with a [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) editing experience. Bold text will "look **like this**" and tables will be more readable, among others.
|
||||
|
||||
## Limitations
|
||||
|
||||
However **there is a catch**: in Joplin, notes, even when edited with this Rich Text editor, are **still Markdown** under the hood. This is generally a good thing, because it means you can switch at any time between Markdown and Rich Text editor, and the note is still readable. It is also good if you sync with the mobile application, which doesn't have a rich text editor. The catch is that since Markdown is used under the hood, it means the rich text editor has a number of limitations it inherits from that format:
|
||||
|
||||
- For a start, **most Markdown plugins will not be compatible**. If you open a Markdown note that makes use of such plugin in the Rich Text editor, it is likely you will lose the plugin special formatting. The only supported plugins are the "fenced" plugins - those that wrap a section of text in triple backticks (for example, KaTeX, Mermaid, etc. are working). You can see a plugin's compatibility on the Markdown config screen.
|
||||
@@ -21,3 +23,24 @@ However **there is a catch**: in Joplin, notes, even when edited with this Rich
|
||||
- All reference links (`[title][link-name]`) are converted to inline links (`[title](https://example.com)`) when Joplin saves changes from the Rich Text editor.
|
||||
|
||||
Those are the known limitations but if you notice any other issue not listed here, please let us know [in the forum](https://discourse.joplinapp.org/).
|
||||
|
||||
## Markup autocompletion
|
||||
|
||||
By default, the Rich Text Editor automatically replaces certain text patterns with formatted content. Replacements are applied after each pattern is typed.
|
||||
|
||||
By default, the following patterns are replaced:
|
||||
|
||||
- `**bold**`: Formats `bold` as **bold**.
|
||||
- `*italic*`: Formats `italic` as *italic*.
|
||||
- `==highlighted==`: Highlights `highlighted`.
|
||||
- <code>`code`</code>: Formats `code` as inline code.
|
||||
- `$math$`: Auto-formats to inline math (using KaTeX math syntax). After rendering, equations can be edited by double-clicking or with the "edit" option in the right click menu.
|
||||
- `# Heading 1`: Creates a level 1 heading. The `#` should be at the start of the line.
|
||||
- `## Heading 2`: Creates a level 2 heading.
|
||||
- `## Heading 3`: Creates a level 3 heading.
|
||||
- `- List`: Creates a bulleted list.
|
||||
- `1. List`: Creates a numbered list.
|
||||
|
||||
Most replacements require pressing the <kbd>space</kbd> or <kbd>enter</kbd> key after the closing formatting character. For example, typing `==test==` does not highlight "test", but pressing a space after the last `=` does.
|
||||
|
||||
These replacements can be disabled in settings > note, using the "Auto-format Markdown" setting.
|
||||
|
Reference in New Issue
Block a user