├── .gitignore
├── .idea
├── .gitignore
├── joplin-plugin-better-image.iml
├── modules.xml
└── vcs.xml
├── .npmignore
├── GENERATOR_DOC.md
├── License
├── README.md
├── api
├── Global.d.ts
├── Joplin.d.ts
├── JoplinClipboard.d.ts
├── JoplinCommands.d.ts
├── JoplinContentScripts.d.ts
├── JoplinData.d.ts
├── JoplinFilters.d.ts
├── JoplinImaging.d.ts
├── JoplinInterop.d.ts
├── JoplinPlugins.d.ts
├── JoplinSettings.d.ts
├── JoplinViews.d.ts
├── JoplinViewsDialogs.d.ts
├── JoplinViewsMenuItems.d.ts
├── JoplinViewsMenus.d.ts
├── JoplinViewsNoteList.d.ts
├── JoplinViewsPanels.d.ts
├── JoplinViewsToolbarButtons.d.ts
├── JoplinWindow.d.ts
├── JoplinWorkspace.d.ts
├── index.ts
├── noteListType.d.ts
├── noteListType.ts
└── types.ts
├── package-lock.json
├── package.json
├── plugin.config.json
├── screenshot
├── admonition.png
├── codeblock.png
├── colorfulBlockquote.png
├── enhancedQuote.png
├── example.png
├── frontmatter.png
├── indentBorder.png
├── linkRender.png
├── mathRender.png
├── mermaidRender.png
├── pseudocode.png
├── quoteRender.png
└── taskRender.png
├── src
├── common.ts
├── driver
│ ├── codemirror
│ │ ├── admonition
│ │ │ ├── admonition.css
│ │ │ └── index.ts
│ │ ├── blockquote
│ │ │ ├── blockquote.css
│ │ │ └── index.ts
│ │ ├── commands
│ │ │ └── index.ts
│ │ ├── common.ts
│ │ ├── focusMode
│ │ │ ├── focusModeStyle.css
│ │ │ └── index.ts
│ │ ├── formattingBar
│ │ │ ├── formattingBar.css
│ │ │ ├── initFormattingBar.ts
│ │ │ ├── v5
│ │ │ │ └── formattingBar.ts
│ │ │ └── v6
│ │ │ │ └── formattingBar.ts
│ │ ├── indentBorder
│ │ │ ├── indentBorder.css
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── inlineMarker
│ │ │ ├── index.ts
│ │ │ └── inlineMarkerStyle.css
│ │ ├── linkFolder
│ │ │ ├── codeMarker.ts
│ │ │ ├── default.min.css
│ │ │ ├── fonts
│ │ │ │ ├── KaTeX_AMS-Regular.ttf
│ │ │ │ ├── KaTeX_AMS-Regular.woff
│ │ │ │ ├── KaTeX_AMS-Regular.woff2
│ │ │ │ ├── KaTeX_Caligraphic-Bold.ttf
│ │ │ │ ├── KaTeX_Caligraphic-Bold.woff
│ │ │ │ ├── KaTeX_Caligraphic-Bold.woff2
│ │ │ │ ├── KaTeX_Caligraphic-Regular.ttf
│ │ │ │ ├── KaTeX_Caligraphic-Regular.woff
│ │ │ │ ├── KaTeX_Caligraphic-Regular.woff2
│ │ │ │ ├── KaTeX_Fraktur-Bold.ttf
│ │ │ │ ├── KaTeX_Fraktur-Bold.woff
│ │ │ │ ├── KaTeX_Fraktur-Bold.woff2
│ │ │ │ ├── KaTeX_Fraktur-Regular.ttf
│ │ │ │ ├── KaTeX_Fraktur-Regular.woff
│ │ │ │ ├── KaTeX_Fraktur-Regular.woff2
│ │ │ │ ├── KaTeX_Main-Bold.ttf
│ │ │ │ ├── KaTeX_Main-Bold.woff
│ │ │ │ ├── KaTeX_Main-Bold.woff2
│ │ │ │ ├── KaTeX_Main-BoldItalic.ttf
│ │ │ │ ├── KaTeX_Main-BoldItalic.woff
│ │ │ │ ├── KaTeX_Main-BoldItalic.woff2
│ │ │ │ ├── KaTeX_Main-Italic.ttf
│ │ │ │ ├── KaTeX_Main-Italic.woff
│ │ │ │ ├── KaTeX_Main-Italic.woff2
│ │ │ │ ├── KaTeX_Main-Regular.ttf
│ │ │ │ ├── KaTeX_Main-Regular.woff
│ │ │ │ ├── KaTeX_Main-Regular.woff2
│ │ │ │ ├── KaTeX_Math-BoldItalic.ttf
│ │ │ │ ├── KaTeX_Math-BoldItalic.woff
│ │ │ │ ├── KaTeX_Math-BoldItalic.woff2
│ │ │ │ ├── KaTeX_Math-Italic.ttf
│ │ │ │ ├── KaTeX_Math-Italic.woff
│ │ │ │ ├── KaTeX_Math-Italic.woff2
│ │ │ │ ├── KaTeX_SansSerif-Bold.ttf
│ │ │ │ ├── KaTeX_SansSerif-Bold.woff
│ │ │ │ ├── KaTeX_SansSerif-Bold.woff2
│ │ │ │ ├── KaTeX_SansSerif-Italic.ttf
│ │ │ │ ├── KaTeX_SansSerif-Italic.woff
│ │ │ │ ├── KaTeX_SansSerif-Italic.woff2
│ │ │ │ ├── KaTeX_SansSerif-Regular.ttf
│ │ │ │ ├── KaTeX_SansSerif-Regular.woff
│ │ │ │ ├── KaTeX_SansSerif-Regular.woff2
│ │ │ │ ├── KaTeX_Script-Regular.ttf
│ │ │ │ ├── KaTeX_Script-Regular.woff
│ │ │ │ ├── KaTeX_Script-Regular.woff2
│ │ │ │ ├── KaTeX_Size1-Regular.ttf
│ │ │ │ ├── KaTeX_Size1-Regular.woff
│ │ │ │ ├── KaTeX_Size1-Regular.woff2
│ │ │ │ ├── KaTeX_Size2-Regular.ttf
│ │ │ │ ├── KaTeX_Size2-Regular.woff
│ │ │ │ ├── KaTeX_Size2-Regular.woff2
│ │ │ │ ├── KaTeX_Size3-Regular.ttf
│ │ │ │ ├── KaTeX_Size3-Regular.woff
│ │ │ │ ├── KaTeX_Size3-Regular.woff2
│ │ │ │ ├── KaTeX_Size4-Regular.ttf
│ │ │ │ ├── KaTeX_Size4-Regular.woff
│ │ │ │ ├── KaTeX_Size4-Regular.woff2
│ │ │ │ ├── KaTeX_Typewriter-Regular.ttf
│ │ │ │ ├── KaTeX_Typewriter-Regular.woff
│ │ │ │ └── KaTeX_Typewriter-Regular.woff2
│ │ │ ├── footnoteMarker.ts
│ │ │ ├── horizontalLineMarker.ts
│ │ │ ├── htmlTagRender.ts
│ │ │ ├── imageMarker.ts
│ │ │ ├── index.ts
│ │ │ ├── katex.min.css
│ │ │ ├── linkFolder.css
│ │ │ ├── linkMarker.ts
│ │ │ ├── mathMaker.ts
│ │ │ ├── plantumlMarker.ts
│ │ │ └── regexps.ts
│ │ ├── listNumber
│ │ │ └── index.ts
│ │ ├── mermaidRender
│ │ │ ├── index.ts
│ │ │ └── mermaid.css
│ │ ├── mode
│ │ │ └── index.ts
│ │ ├── overlay
│ │ │ ├── bullet-list.css
│ │ │ ├── bulletList.ts
│ │ │ ├── indent.ts
│ │ │ ├── index.ts
│ │ │ └── overlay.css
│ │ ├── quickCommands
│ │ │ ├── DateHints.ts
│ │ │ ├── MermaidHints.ts
│ │ │ ├── PlantumlHints.ts
│ │ │ ├── ShortTypeHints.ts
│ │ │ ├── quickCommands.css
│ │ │ └── quickCommands.ts
│ │ ├── searchReplace
│ │ │ ├── index.ts
│ │ │ └── search.ts
│ │ ├── tableFormatter
│ │ │ ├── index.ts
│ │ │ ├── markdownTableData.ts
│ │ │ ├── markdownTableUtility.ts
│ │ │ ├── markdowntable.ts
│ │ │ ├── tableCommands.ts
│ │ │ └── tableFormatterBridge.ts
│ │ └── taskAndHeaderRenderer
│ │ │ ├── index.ts
│ │ │ ├── taskRender.css
│ │ │ ├── v5
│ │ │ ├── index.ts
│ │ │ ├── render-h-tags.ts
│ │ │ ├── render-tables.ts
│ │ │ ├── renderer
│ │ │ │ └── context.ts
│ │ │ ├── table-editor
│ │ │ │ ├── build-grid.ts
│ │ │ │ ├── build-pipe.ts
│ │ │ │ ├── build-simple.ts
│ │ │ │ ├── calculate-col-sizes.ts
│ │ │ │ ├── compute-css.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── parse-grid.ts
│ │ │ │ ├── parse-pipe.ts
│ │ │ │ ├── parse-simple.ts
│ │ │ │ ├── table-editor.ts
│ │ │ │ └── types.ts
│ │ │ ├── taskRender.ts
│ │ │ └── window-register
│ │ │ │ └── application-menu-helper.ts
│ │ │ └── v6
│ │ │ ├── checklistPlugin.ts
│ │ │ └── index.ts
│ ├── markdownItRenderer
│ │ ├── filePreview
│ │ │ ├── filePreviewRenderer.ts
│ │ │ └── index.ts
│ │ ├── image
│ │ │ ├── imageRenderer.ts
│ │ │ ├── index.ts
│ │ │ └── markdownItPlugin.css
│ │ ├── pseudocode
│ │ │ ├── index.ts
│ │ │ ├── pseudocode.min.css
│ │ │ └── pseudocode.ts
│ │ └── quote
│ │ │ ├── index.ts
│ │ │ ├── quoteRender.css
│ │ │ └── quoteRender.ts
│ └── markdownItRuler
│ │ ├── frontMatter
│ │ └── index.ts
│ │ └── image
│ │ ├── index.ts
│ │ └── utils.ts
├── index.ts
├── manifest.json
├── settings.ts
└── utils
│ ├── CMBlockMarkerHelper.ts
│ ├── CMBlockMarkerHelperV2.ts
│ ├── CMInlineMarkerHelper.ts
│ ├── CMInlineMarkerHelperV2.ts
│ ├── can-render-element.ts
│ ├── citeproc.d.ts
│ ├── click-and-clear.ts
│ ├── cm-dynamic-require.ts
│ ├── cm-utils.ts
│ ├── extract-citations.ts
│ ├── md-to-html.ts
│ ├── reg.ts
│ ├── regular-expressions.ts
│ └── string-render.ts
├── tsconfig.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | publish/
4 | dev_commons.ts
5 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/.idea/joplin-plugin-better-image.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.md
2 | !README.md
3 | /*.jpl
4 | /api
5 | /src
6 | /dist
7 | tsconfig.json
8 | webpack.config.js
9 |
--------------------------------------------------------------------------------
/GENERATOR_DOC.md:
--------------------------------------------------------------------------------
1 | # Plugin development
2 |
3 | This documentation describes how to create a plugin, and how to work with the plugin builder framework and API.
4 |
5 | ## Installation
6 |
7 | First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
8 |
9 | ```bash
10 | npm install -g yo@4.3.1
11 | npm install -g generator-joplin
12 | ```
13 |
14 | Then generate your new project:
15 |
16 | ```bash
17 | yo --node-package-manager npm joplin
18 | ```
19 |
20 | ## Structure
21 |
22 | The main two files you will want to look at are:
23 |
24 | - `/src/index.ts`, which contains the entry point for the plugin source code.
25 | - `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
26 |
27 | The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
28 |
29 | ## Building the plugin
30 |
31 | The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
32 |
33 | To build the plugin, simply run `npm run dist`.
34 |
35 | The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
36 |
37 | ## Updating the manifest version number
38 |
39 | You can run `npm run updateVersion` to bump the patch part of the version number, so for example 1.0.3 will become 1.0.4. This script will update both the package.json and manifest.json version numbers so as to keep them in sync.
40 |
41 | ## Publishing the plugin
42 |
43 | To publish the plugin, add it to npmjs.com by running `npm publish`. Later on, a script will pick up your plugin and add it automatically to the Joplin plugin repository as long as the package satisfies these conditions:
44 |
45 | - In `package.json`, the name starts with "joplin-plugin-". For example, "joplin-plugin-toc".
46 | - In `package.json`, the keywords include "joplin-plugin".
47 | - In the `publish/` directory, there should be a .jpl and .json file (which are built by `npm run dist`)
48 |
49 | In general all this is done automatically by the plugin generator, which will set the name and keywords of package.json, and will put the right files in the "publish" directory. But if something doesn't work and your plugin doesn't appear in the repository, double-check the above conditions.
50 |
51 | ## Updating the plugin framework
52 |
53 | To update the plugin framework, run `npm run update`.
54 |
55 | In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
56 |
57 | The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
58 |
59 | ## External script files
60 |
61 | By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplincontentscripts.html) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
62 |
63 | - The script is a TypeScript file - in which case it has to be compiled to JavaScript.
64 |
65 | - The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
66 |
67 | To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
68 |
69 | ## More information
70 |
71 | - [Joplin Plugin API](https://joplinapp.org/api/references/plugin_api/classes/joplin.html)
72 | - [Joplin Data API](https://joplinapp.org/help/api/references/rest_api)
73 | - [Joplin Plugin Manifest](https://joplinapp.org/api/references/plugin_manifest/)
74 | - Ask for help on the [forum](https://discourse.joplinapp.org/) or our [Discord channel](https://discord.gg/VSj7AFHvpq)
75 |
76 | ## License
77 |
78 | MIT © Laurent Cozic
79 |
--------------------------------------------------------------------------------
/api/Global.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | import Joplin from './Joplin';
3 | /**
4 | * @ignore
5 | */
6 | /**
7 | * @ignore
8 | */
9 | export default class Global {
10 | private joplin_;
11 | constructor(implementation: any, plugin: Plugin, store: any);
12 | get joplin(): Joplin;
13 | get process(): any;
14 | }
15 |
--------------------------------------------------------------------------------
/api/Joplin.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | import JoplinData from './JoplinData';
3 | import JoplinPlugins from './JoplinPlugins';
4 | import JoplinWorkspace from './JoplinWorkspace';
5 | import JoplinFilters from './JoplinFilters';
6 | import JoplinCommands from './JoplinCommands';
7 | import JoplinViews from './JoplinViews';
8 | import JoplinInterop from './JoplinInterop';
9 | import JoplinSettings from './JoplinSettings';
10 | import JoplinContentScripts from './JoplinContentScripts';
11 | import JoplinClipboard from './JoplinClipboard';
12 | import JoplinWindow from './JoplinWindow';
13 | import BasePlatformImplementation from '../BasePlatformImplementation';
14 | import JoplinImaging from './JoplinImaging';
15 | /**
16 | * This is the main entry point to the Joplin API. You can access various services using the provided accessors.
17 | *
18 | * The API is now relatively stable and in general maintaining backward compatibility is a top priority, so you shouldn't except much breakages.
19 | *
20 | * If a breaking change ever becomes needed, best effort will be done to:
21 | *
22 | * - Deprecate features instead of removing them, so as to give you time to fix the issue;
23 | * - Document breaking changes in the changelog;
24 | *
25 | * So if you are developing a plugin, please keep an eye on the changelog as everything will be in there with information about how to update your code.
26 | */
27 | export default class Joplin {
28 | private data_;
29 | private plugins_;
30 | private imaging_;
31 | private workspace_;
32 | private filters_;
33 | private commands_;
34 | private views_;
35 | private interop_;
36 | private settings_;
37 | private contentScripts_;
38 | private clipboard_;
39 | private window_;
40 | private implementation_;
41 | constructor(implementation: BasePlatformImplementation, plugin: Plugin, store: any);
42 | get data(): JoplinData;
43 | get clipboard(): JoplinClipboard;
44 | get imaging(): JoplinImaging;
45 | get window(): JoplinWindow;
46 | get plugins(): JoplinPlugins;
47 | get workspace(): JoplinWorkspace;
48 | get contentScripts(): JoplinContentScripts;
49 | /**
50 | * @ignore
51 | *
52 | * Not sure if it's the best way to hook into the app
53 | * so for now disable filters.
54 | */
55 | get filters(): JoplinFilters;
56 | get commands(): JoplinCommands;
57 | get views(): JoplinViews;
58 | get interop(): JoplinInterop;
59 | get settings(): JoplinSettings;
60 | /**
61 | * It is not possible to bundle native packages with a plugin, because they
62 | * need to work cross-platforms. Instead access to certain useful native
63 | * packages is provided using this function.
64 | *
65 | * Currently these packages are available:
66 | *
67 | * - [sqlite3](https://www.npmjs.com/package/sqlite3)
68 | * - [fs-extra](https://www.npmjs.com/package/fs-extra)
69 | *
70 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/nativeModule)
71 | */
72 | require(_path: string): any;
73 | versionInfo(): Promise;
74 | }
75 |
--------------------------------------------------------------------------------
/api/JoplinClipboard.d.ts:
--------------------------------------------------------------------------------
1 | export default class JoplinClipboard {
2 | private electronClipboard_;
3 | private electronNativeImage_;
4 | constructor(electronClipboard: any, electronNativeImage: any);
5 | readText(): Promise;
6 | writeText(text: string): Promise;
7 | readHtml(): Promise;
8 | writeHtml(html: string): Promise;
9 | /**
10 | * Returns the image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
11 | */
12 | readImage(): Promise;
13 | /**
14 | * Takes an image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
15 | */
16 | writeImage(dataUrl: string): Promise;
17 | /**
18 | * Returns the list available formats (mime types).
19 | *
20 | * For example [ 'text/plain', 'text/html' ]
21 | */
22 | availableFormats(): Promise;
23 | }
24 |
--------------------------------------------------------------------------------
/api/JoplinCommands.d.ts:
--------------------------------------------------------------------------------
1 | import { Command } from './types';
2 | /**
3 | * This class allows executing or registering new Joplin commands. Commands
4 | * can be executed or associated with
5 | * {@link JoplinViewsToolbarButtons | toolbar buttons} or
6 | * {@link JoplinViewsMenuItems | menu items}.
7 | *
8 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
9 | *
10 | * ## Executing Joplin's internal commands
11 | *
12 | * It is also possible to execute internal Joplin's commands which, as of
13 | * now, are not well documented. You can find the list directly on GitHub
14 | * though at the following locations:
15 | *
16 | * * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)
17 | * * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)
18 | * * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts)
19 | *
20 | * To view what arguments are supported, you can open any of these files
21 | * and look at the `execute()` command.
22 | *
23 | * ## Executing editor commands
24 | *
25 | * There might be a situation where you want to invoke editor commands
26 | * without using a {@link JoplinContentScripts | contentScript}. For this
27 | * reason Joplin provides the built in `editor.execCommand` command.
28 | *
29 | * `editor.execCommand` should work with any core command in both the
30 | * [CodeMirror](https://codemirror.net/doc/manual.html#execCommand) and
31 | * [TinyMCE](https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand) editors,
32 | * as well as most functions calls directly on a CodeMirror editor object (extensions).
33 | *
34 | * * [CodeMirror commands](https://codemirror.net/doc/manual.html#commands)
35 | * * [TinyMCE core editor commands](https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands)
36 | *
37 | * `editor.execCommand` supports adding arguments for the commands.
38 | *
39 | * ```typescript
40 | * await joplin.commands.execute('editor.execCommand', {
41 | * name: 'madeUpCommand', // CodeMirror and TinyMCE
42 | * args: [], // CodeMirror and TinyMCE
43 | * ui: false, // TinyMCE only
44 | * value: '', // TinyMCE only
45 | * });
46 | * ```
47 | *
48 | * [View the example using the CodeMirror editor](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts)
49 | *
50 | */
51 | export default class JoplinCommands {
52 | /**
53 | * desktop Executes the given
54 | * command.
55 | *
56 | * The command can take any number of arguments, and the supported
57 | * arguments will vary based on the command. For custom commands, this
58 | * is the `args` passed to the `execute()` function. For built-in
59 | * commands, you can find the supported arguments by checking the links
60 | * above.
61 | *
62 | * ```typescript
63 | * // Create a new note in the current notebook:
64 | * await joplin.commands.execute('newNote');
65 | *
66 | * // Create a new sub-notebook under the provided notebook
67 | * // Note: internally, notebooks are called "folders".
68 | * await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
69 | * ```
70 | */
71 | execute(commandName: string, ...args: any[]): Promise;
72 | /**
73 | * desktop Registers a new command.
74 | *
75 | * ```typescript
76 | * // Register a new commmand called "testCommand1"
77 | *
78 | * await joplin.commands.register({
79 | * name: 'testCommand1',
80 | * label: 'My Test Command 1',
81 | * iconName: 'fas fa-music',
82 | * execute: () => {
83 | * alert('Testing plugin command 1');
84 | * },
85 | * });
86 | * ```
87 | */
88 | register(command: Command): Promise;
89 | }
90 |
--------------------------------------------------------------------------------
/api/JoplinContentScripts.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | import { ContentScriptType } from './types';
3 | export default class JoplinContentScripts {
4 | private plugin;
5 | constructor(plugin: Plugin);
6 | /**
7 | * Registers a new content script. Unlike regular plugin code, which runs in
8 | * a separate process, content scripts run within the main process code and
9 | * thus allow improved performances and more customisations in specific
10 | * cases. It can be used for example to load a Markdown or editor plugin.
11 | *
12 | * Note that registering a content script in itself will do nothing - it
13 | * will only be loaded in specific cases by the relevant app modules (eg.
14 | * the Markdown renderer or the code editor). So it is not a way to inject
15 | * and run arbitrary code in the app, which for safety and performance
16 | * reasons is not supported.
17 | *
18 | * The plugin generator provides a way to build any content script you might
19 | * want to package as well as its dependencies. See the [Plugin Generator
20 | * doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
21 | * for more information.
22 | *
23 | * * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
24 | * * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
25 | *
26 | * See also the [postMessage demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)
27 | *
28 | * @param type Defines how the script will be used. See the type definition for more information about each supported type.
29 | * @param id A unique ID for the content script.
30 | * @param scriptPath Must be a path relative to the plugin main script. For example, if your file content_script.js is next to your index.ts file, you would set `scriptPath` to `"./content_script.js`.
31 | */
32 | register(type: ContentScriptType, id: string, scriptPath: string): Promise;
33 | /**
34 | * Listens to a messages sent from the content script using postMessage().
35 | * See {@link ContentScriptType} for more information as well as the
36 | * [postMessage
37 | * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)
38 | */
39 | onMessage(contentScriptId: string, callback: any): Promise;
40 | }
41 |
--------------------------------------------------------------------------------
/api/JoplinData.d.ts:
--------------------------------------------------------------------------------
1 | import { ModelType } from '../../../BaseModel';
2 | import Plugin from '../Plugin';
3 | import { Path } from './types';
4 | /**
5 | * This module provides access to the Joplin data API: https://joplinapp.org/help/api/references/rest_api
6 | * This is the main way to retrieve data, such as notes, notebooks, tags, etc.
7 | * or to update them or delete them.
8 | *
9 | * This is also what you would use to search notes, via the `search` endpoint.
10 | *
11 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/simple)
12 | *
13 | * In general you would use the methods in this class as if you were using a REST API. There are four methods that map to GET, POST, PUT and DELETE calls.
14 | * And each method takes these parameters:
15 | *
16 | * * `path`: This is an array that represents the path to the resource in the form `["resouceName", "resourceId", "resourceLink"]` (eg. ["tags", ":id", "notes"]). The "resources" segment is the name of the resources you want to access (eg. "notes", "folders", etc.). If not followed by anything, it will refer to all the resources in that collection. The optional "resourceId" points to a particular resources within the collection. Finally, an optional "link" can be present, which links the resource to a collection of resources. This can be used in the API for example to retrieve all the notes associated with a tag.
17 | * * `query`: (Optional) The query parameters. In a URL, this is the part after the question mark "?". In this case, it should be an object with key/value pairs.
18 | * * `data`: (Optional) Applies to PUT and POST calls only. The request body contains the data you want to create or modify, for example the content of a note or folder.
19 | * * `files`: (Optional) Used to create new resources and associate them with files.
20 | *
21 | * Please refer to the [Joplin API documentation](https://joplinapp.org/help/api/references/rest_api) for complete details about each call. As the plugin runs within the Joplin application **you do not need an authorisation token** to use this API.
22 | *
23 | * For example:
24 | *
25 | * ```typescript
26 | * // Get a note ID, title and body
27 | * const noteId = 'some_note_id';
28 | * const note = await joplin.data.get(['notes', noteId], { fields: ['id', 'title', 'body'] });
29 | *
30 | * // Get all folders
31 | * const folders = await joplin.data.get(['folders']);
32 | *
33 | * // Set the note body
34 | * await joplin.data.put(['notes', noteId], null, { body: "New note body" });
35 | *
36 | * // Create a new note under one of the folders
37 | * await joplin.data.post(['notes'], null, { body: "my new note", title: "some title", parent_id: folders[0].id });
38 | * ```
39 | */
40 | export default class JoplinData {
41 | private api_;
42 | private pathSegmentRegex_;
43 | private plugin;
44 | constructor(plugin: Plugin);
45 | private serializeApiBody;
46 | private pathToString;
47 | get(path: Path, query?: any): Promise;
48 | post(path: Path, query?: any, body?: any, files?: any[]): Promise;
49 | put(path: Path, query?: any, body?: any, files?: any[]): Promise;
50 | delete(path: Path, query?: any): Promise;
51 | itemType(itemId: string): Promise;
52 | resourcePath(resourceId: string): Promise;
53 | /**
54 | * Gets an item user data. User data are key/value pairs. The `key` can be any
55 | * arbitrary string, while the `value` can be of any type supported by
56 | * [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description)
57 | *
58 | * User data is synchronised across devices, and each value wil be merged based on their timestamp:
59 | *
60 | * - If value is modified by client 1, then modified by client 2, it will take the value from client 2
61 | * - If value is modified by client 1, then deleted by client 2, the value will be deleted after merge
62 | * - If value is deleted by client 1, then updated by client 2, the value will be restored and set to the value from client 2 after merge
63 | */
64 | userDataGet(itemType: ModelType, itemId: string, key: string): Promise;
65 | /**
66 | * Sets a note user data. See {@link JoplinData.userDataGet} for more details.
67 | */
68 | userDataSet(itemType: ModelType, itemId: string, key: string, value: T): Promise;
69 | /**
70 | * Deletes a note user data. See {@link JoplinData.userDataGet} for more details.
71 | */
72 | userDataDelete(itemType: ModelType, itemId: string, key: string): Promise;
73 | }
74 |
--------------------------------------------------------------------------------
/api/JoplinFilters.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | *
4 | * Not sure if it's the best way to hook into the app
5 | * so for now disable filters.
6 | */
7 | export default class JoplinFilters {
8 | on(name: string, callback: Function): Promise;
9 | off(name: string, callback: Function): Promise;
10 | }
11 |
--------------------------------------------------------------------------------
/api/JoplinImaging.d.ts:
--------------------------------------------------------------------------------
1 | import { Rectangle } from './types';
2 | export interface Implementation {
3 | nativeImage: any;
4 | }
5 | export interface CreateFromBufferOptions {
6 | width?: number;
7 | height?: number;
8 | scaleFactor?: number;
9 | }
10 | export interface ResizeOptions {
11 | width?: number;
12 | height?: number;
13 | quality?: 'good' | 'better' | 'best';
14 | }
15 | export type Handle = string;
16 | /**
17 | * Provides imaging functions to resize or process images. You create an image
18 | * using one of the `createFrom` functions, then use the other functions to
19 | * process the image.
20 | *
21 | * Images are associated with a handle which is what will be available to the
22 | * plugin. Once you are done with an image, free it using the `free()` function.
23 | *
24 | * [View the
25 | * example](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/imaging/src/index.ts)
26 | *
27 | */
28 | export default class JoplinImaging {
29 | private implementation_;
30 | private images_;
31 | constructor(implementation: Implementation);
32 | private createImageHandle;
33 | private imageByHandle;
34 | private cacheImage;
35 | createFromPath(filePath: string): Promise;
36 | createFromResource(resourceId: string): Promise;
37 | getSize(handle: Handle): Promise;
38 | resize(handle: Handle, options?: ResizeOptions): Promise;
39 | crop(handle: Handle, rectange: Rectangle): Promise;
40 | toPngFile(handle: Handle, filePath: string): Promise;
41 | /**
42 | * Quality is between 0 and 100
43 | */
44 | toJpgFile(handle: Handle, filePath: string, quality?: number): Promise;
45 | private tempFilePath;
46 | /**
47 | * Creates a new Joplin resource from the image data. The image will be
48 | * first converted to a JPEG.
49 | */
50 | toJpgResource(handle: Handle, resourceProps: any, quality?: number): Promise;
51 | /**
52 | * Creates a new Joplin resource from the image data. The image will be
53 | * first converted to a PNG.
54 | */
55 | toPngResource(handle: Handle, resourceProps: any): Promise;
56 | /**
57 | * Image data is not automatically deleted by Joplin so make sure you call
58 | * this method on the handle once you are done.
59 | */
60 | free(handle: Handle): Promise;
61 | }
62 |
--------------------------------------------------------------------------------
/api/JoplinInterop.d.ts:
--------------------------------------------------------------------------------
1 | import { ExportModule, ImportModule } from './types';
2 | /**
3 | * Provides a way to create modules to import external data into Joplin or to export notes into any arbitrary format.
4 | *
5 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export)
6 | *
7 | * To implement an import or export module, you would simply define an object with various event handlers that are called
8 | * by the application during the import/export process.
9 | *
10 | * See the documentation of the [[ExportModule]] and [[ImportModule]] for more information.
11 | *
12 | * You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/help/api/references/rest_api
13 | */
14 | export default class JoplinInterop {
15 | registerExportModule(module: ExportModule): Promise;
16 | registerImportModule(module: ImportModule): Promise;
17 | }
18 |
--------------------------------------------------------------------------------
/api/JoplinPlugins.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | import { ContentScriptType, Script } from './types';
3 | /**
4 | * This class provides access to plugin-related features.
5 | */
6 | export default class JoplinPlugins {
7 | private plugin;
8 | constructor(plugin: Plugin);
9 | /**
10 | * Registers a new plugin. This is the entry point when creating a plugin. You should pass a simple object with an `onStart` method to it.
11 | * That `onStart` method will be executed as soon as the plugin is loaded.
12 | *
13 | * ```typescript
14 | * joplin.plugins.register({
15 | * onStart: async function() {
16 | * // Run your plugin code here
17 | * }
18 | * });
19 | * ```
20 | */
21 | register(script: Script): Promise;
22 | /**
23 | * @deprecated Use joplin.contentScripts.register()
24 | */
25 | registerContentScript(type: ContentScriptType, id: string, scriptPath: string): Promise;
26 | /**
27 | * Gets the plugin own data directory path. Use this to store any
28 | * plugin-related data. Unlike [[installationDir]], any data stored here
29 | * will be persisted.
30 | */
31 | dataDir(): Promise;
32 | /**
33 | * Gets the plugin installation directory. This can be used to access any
34 | * asset that was packaged with the plugin. This directory should be
35 | * considered read-only because any data you store here might be deleted or
36 | * re-created at any time. To store new persistent data, use [[dataDir]].
37 | */
38 | installationDir(): Promise;
39 | /**
40 | * @deprecated Use joplin.require()
41 | */
42 | require(_path: string): any;
43 | }
44 |
--------------------------------------------------------------------------------
/api/JoplinSettings.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | import { SettingItem, SettingSection } from './types';
3 | export interface ChangeEvent {
4 | /**
5 | * Setting keys that have been changed
6 | */
7 | keys: string[];
8 | }
9 | export type ChangeHandler = (event: ChangeEvent) => void;
10 | export declare const namespacedKey: (pluginId: string, key: string) => string;
11 | /**
12 | * This API allows registering new settings and setting sections, as well as getting and setting settings. Once a setting has been registered it will appear in the config screen and be editable by the user.
13 | *
14 | * Settings are essentially key/value pairs.
15 | *
16 | * Note: Currently this API does **not** provide access to Joplin's built-in settings. This is by design as plugins that modify user settings could give unexpected results
17 | *
18 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/settings)
19 | */
20 | export default class JoplinSettings {
21 | private plugin_;
22 | constructor(plugin: Plugin);
23 | /**
24 | * Registers new settings.
25 | * Note that registering a setting item is dynamic and will be gone next time Joplin starts.
26 | * What it means is that you need to register the setting every time the plugin starts (for example in the onStart event).
27 | * The setting value however will be preserved from one launch to the next so there is no risk that it will be lost even if for some
28 | * reason the plugin fails to start at some point.
29 | */
30 | registerSettings(settings: Record): Promise;
31 | /**
32 | * @deprecated Use joplin.settings.registerSettings()
33 | *
34 | * Registers a new setting.
35 | */
36 | registerSetting(key: string, settingItem: SettingItem): Promise;
37 | /**
38 | * Registers a new setting section. Like for registerSetting, it is dynamic and needs to be done every time the plugin starts.
39 | */
40 | registerSection(name: string, section: SettingSection): Promise;
41 | /**
42 | * Gets a setting value (only applies to setting you registered from your plugin)
43 | */
44 | value(key: string): Promise;
45 | /**
46 | * Sets a setting value (only applies to setting you registered from your plugin)
47 | */
48 | setValue(key: string, value: any): Promise;
49 | /**
50 | * Gets a global setting value, including app-specific settings and those set by other plugins.
51 | *
52 | * The list of available settings is not documented yet, but can be found by looking at the source code:
53 | *
54 | * https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142
55 | */
56 | globalValue(key: string): Promise;
57 | /**
58 | * Called when one or multiple settings of your plugin have been changed.
59 | * - For performance reasons, this event is triggered with a delay.
60 | * - You will only get events for your own plugin settings.
61 | */
62 | onChange(handler: ChangeHandler): Promise;
63 | }
64 |
--------------------------------------------------------------------------------
/api/JoplinViews.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | import JoplinViewsDialogs from './JoplinViewsDialogs';
3 | import JoplinViewsMenuItems from './JoplinViewsMenuItems';
4 | import JoplinViewsMenus from './JoplinViewsMenus';
5 | import JoplinViewsToolbarButtons from './JoplinViewsToolbarButtons';
6 | import JoplinViewsPanels from './JoplinViewsPanels';
7 | import JoplinViewsNoteList from './JoplinViewsNoteList';
8 | /**
9 | * This namespace provides access to view-related services.
10 | *
11 | * All view services provide a `create()` method which you would use to create the view object, whether it's a dialog, a toolbar button or a menu item.
12 | * In some cases, the `create()` method will return a [[ViewHandle]], which you would use to act on the view, for example to set certain properties or call some methods.
13 | */
14 | export default class JoplinViews {
15 | private store;
16 | private plugin;
17 | private panels_;
18 | private menuItems_;
19 | private menus_;
20 | private toolbarButtons_;
21 | private dialogs_;
22 | private noteList_;
23 | private implementation_;
24 | constructor(implementation: any, plugin: Plugin, store: any);
25 | get dialogs(): JoplinViewsDialogs;
26 | get panels(): JoplinViewsPanels;
27 | get menuItems(): JoplinViewsMenuItems;
28 | get menus(): JoplinViewsMenus;
29 | get toolbarButtons(): JoplinViewsToolbarButtons;
30 | get noteList(): JoplinViewsNoteList;
31 | }
32 |
--------------------------------------------------------------------------------
/api/JoplinViewsDialogs.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | import { ButtonSpec, ViewHandle, DialogResult } from './types';
3 | /**
4 | * Allows creating and managing dialogs. A dialog is modal window that
5 | * contains a webview and a row of buttons. You can update the
6 | * webview using the `setHtml` method. Dialogs are hidden by default and
7 | * you need to call `open()` to open them. Once the user clicks on a
8 | * button, the `open` call will return an object indicating what button was
9 | * clicked on.
10 | *
11 | * ## Retrieving form values
12 | *
13 | * If your HTML content included one or more forms, a `formData` object
14 | * will also be included with the key/value for each form.
15 | *
16 | * ## Special button IDs
17 | *
18 | * The following buttons IDs have a special meaning:
19 | *
20 | * - `ok`, `yes`, `submit`, `confirm`: They are considered "submit" buttons
21 | * - `cancel`, `no`, `reject`: They are considered "dismiss" buttons
22 | *
23 | * This information is used by the application to determine what action
24 | * should be done when the user presses "Enter" or "Escape" within the
25 | * dialog. If they press "Enter", the first "submit" button will be
26 | * automatically clicked. If they press "Escape" the first "dismiss" button
27 | * will be automatically clicked.
28 | *
29 | * [View the demo
30 | * plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/dialog)
31 | */
32 | export default class JoplinViewsDialogs {
33 | private store;
34 | private plugin;
35 | private implementation_;
36 | constructor(implementation: any, plugin: Plugin, store: any);
37 | private controller;
38 | /**
39 | * Creates a new dialog
40 | */
41 | create(id: string): Promise;
42 | /**
43 | * Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
44 | */
45 | showMessageBox(message: string): Promise;
46 | /**
47 | * Displays a dialog to select a file or a directory. Same options and
48 | * output as
49 | * https://www.electronjs.org/docs/latest/api/dialog#dialogshowopendialogbrowserwindow-options
50 | */
51 | showOpenDialog(options: any): Promise;
52 | /**
53 | * Sets the dialog HTML content
54 | */
55 | setHtml(handle: ViewHandle, html: string): Promise;
56 | /**
57 | * Adds and loads a new JS or CSS files into the dialog.
58 | */
59 | addScript(handle: ViewHandle, scriptPath: string): Promise;
60 | /**
61 | * Sets the dialog buttons.
62 | */
63 | setButtons(handle: ViewHandle, buttons: ButtonSpec[]): Promise;
64 | /**
65 | * Opens the dialog
66 | */
67 | open(handle: ViewHandle): Promise;
68 | /**
69 | * Toggle on whether to fit the dialog size to the content or not.
70 | * When set to false, the dialog is set to 90vw and 80vh
71 | * @default true
72 | */
73 | setFitToContent(handle: ViewHandle, status: boolean): Promise;
74 | }
75 |
--------------------------------------------------------------------------------
/api/JoplinViewsMenuItems.d.ts:
--------------------------------------------------------------------------------
1 | import { CreateMenuItemOptions, MenuItemLocation } from './types';
2 | import Plugin from '../Plugin';
3 | /**
4 | * Allows creating and managing menu items.
5 | *
6 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
7 | */
8 | export default class JoplinViewsMenuItems {
9 | private store;
10 | private plugin;
11 | constructor(plugin: Plugin, store: any);
12 | /**
13 | * Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
14 | */
15 | create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise;
16 | }
17 |
--------------------------------------------------------------------------------
/api/JoplinViewsMenus.d.ts:
--------------------------------------------------------------------------------
1 | import { MenuItem, MenuItemLocation } from './types';
2 | import Plugin from '../Plugin';
3 | /**
4 | * Allows creating menus.
5 | *
6 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/menu)
7 | */
8 | export default class JoplinViewsMenus {
9 | private store;
10 | private plugin;
11 | constructor(plugin: Plugin, store: any);
12 | private registerCommandAccelerators;
13 | /**
14 | * Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
15 | * menu as a sub-menu of the application build-in menus.
16 | */
17 | create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise;
18 | }
19 |
--------------------------------------------------------------------------------
/api/JoplinViewsNoteList.d.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux';
2 | import Plugin from '../Plugin';
3 | import { ListRenderer } from './noteListType';
4 | /**
5 | * This API allows you to customise how each note in the note list is rendered.
6 | * The renderer you implement follows a unidirectional data flow.
7 | *
8 | * The app provides the required dependencies whenever a note is updated - you
9 | * process these dependencies, and return some props, which are then passed to
10 | * your template and rendered. See [[[ListRenderer]]] for a detailed description
11 | * of each property of the renderer.
12 | *
13 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/note_list_renderer)
14 | *
15 | * The default list renderer is implemented using the same API, so it worth checking it too:
16 | *
17 | * [Default list renderer](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/noteList/defaultListRenderer.ts)
18 | */
19 | export default class JoplinViewsNoteList {
20 | private plugin_;
21 | private store_;
22 | constructor(plugin: Plugin, store: Store);
23 | registerRenderer(renderer: ListRenderer): Promise;
24 | }
25 |
--------------------------------------------------------------------------------
/api/JoplinViewsPanels.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | import { ViewHandle } from './types';
3 | /**
4 | * Allows creating and managing view panels. View panels currently are
5 | * displayed at the right of the sidebar and allows displaying any HTML
6 | * content (within a webview) and update it in real-time. For example it
7 | * could be used to display a table of content for the active note, or
8 | * display various metadata or graph.
9 | *
10 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
11 | */
12 | export default class JoplinViewsPanels {
13 | private store;
14 | private plugin;
15 | constructor(plugin: Plugin, store: any);
16 | private controller;
17 | /**
18 | * Creates a new panel
19 | */
20 | create(id: string): Promise;
21 | /**
22 | * Sets the panel webview HTML
23 | */
24 | setHtml(handle: ViewHandle, html: string): Promise;
25 | /**
26 | * Adds and loads a new JS or CSS files into the panel.
27 | */
28 | addScript(handle: ViewHandle, scriptPath: string): Promise;
29 | /**
30 | * Called when a message is sent from the webview (using postMessage).
31 | *
32 | * To post a message from the webview to the plugin use:
33 | *
34 | * ```javascript
35 | * const response = await webviewApi.postMessage(message);
36 | * ```
37 | *
38 | * - `message` can be any JavaScript object, string or number
39 | * - `response` is whatever was returned by the `onMessage` handler
40 | *
41 | * Using this mechanism, you can have two-way communication between the
42 | * plugin and webview.
43 | *
44 | * See the [postMessage
45 | * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages) for more details.
46 | *
47 | */
48 | onMessage(handle: ViewHandle, callback: Function): Promise;
49 | /**
50 | * Sends a message to the webview.
51 | *
52 | * The webview must have registered a message handler prior, otherwise the message is ignored. Use;
53 | *
54 | * ```javascript
55 | * webviewApi.onMessage((message) => { ... });
56 | * ```
57 | *
58 | * - `message` can be any JavaScript object, string or number
59 | *
60 | * The view API may have only one onMessage handler defined.
61 | * This method is fire and forget so no response is returned.
62 | *
63 | * It is particularly useful when the webview needs to react to events emitted by the plugin or the joplin api.
64 | */
65 | postMessage(handle: ViewHandle, message: any): void;
66 | /**
67 | * Shows the panel
68 | */
69 | show(handle: ViewHandle, show?: boolean): Promise;
70 | /**
71 | * Hides the panel
72 | */
73 | hide(handle: ViewHandle): Promise;
74 | /**
75 | * Tells whether the panel is visible or not
76 | */
77 | visible(handle: ViewHandle): Promise;
78 | }
79 |
--------------------------------------------------------------------------------
/api/JoplinViewsToolbarButtons.d.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarButtonLocation } from './types';
2 | import Plugin from '../Plugin';
3 | /**
4 | * Allows creating and managing toolbar buttons.
5 | *
6 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
7 | */
8 | export default class JoplinViewsToolbarButtons {
9 | private store;
10 | private plugin;
11 | constructor(plugin: Plugin, store: any);
12 | /**
13 | * Creates a new toolbar button and associate it with the given command.
14 | */
15 | create(id: string, commandName: string, location: ToolbarButtonLocation): Promise;
16 | }
17 |
--------------------------------------------------------------------------------
/api/JoplinWindow.d.ts:
--------------------------------------------------------------------------------
1 | import Plugin from '../Plugin';
2 | export interface Implementation {
3 | injectCustomStyles(elementId: string, cssFilePath: string): Promise;
4 | }
5 | export default class JoplinWindow {
6 | private plugin_;
7 | private store_;
8 | private implementation_;
9 | constructor(implementation: Implementation, plugin: Plugin, store: any);
10 | /**
11 | * Loads a chrome CSS file. It will apply to the window UI elements, except
12 | * for the note viewer. It is the same as the "Custom stylesheet for
13 | * Joplin-wide app styles" setting. See the [Load CSS Demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/load_css)
14 | * for an example.
15 | */
16 | loadChromeCssFile(filePath: string): Promise;
17 | /**
18 | * Loads a note CSS file. It will apply to the note viewer, as well as any
19 | * exported or printed note. It is the same as the "Custom stylesheet for
20 | * rendered Markdown" setting. See the [Load CSS Demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/load_css)
21 | * for an example.
22 | */
23 | loadNoteCssFile(filePath: string): Promise;
24 | }
25 |
--------------------------------------------------------------------------------
/api/JoplinWorkspace.d.ts:
--------------------------------------------------------------------------------
1 | import { FolderEntity } from '../../database/types';
2 | import { Disposable, MenuItem } from './types';
3 | export interface EditContextMenuFilterObject {
4 | items: MenuItem[];
5 | }
6 | type FilterHandler = (object: T) => Promise;
7 | declare enum ItemChangeEventType {
8 | Create = 1,
9 | Update = 2,
10 | Delete = 3
11 | }
12 | interface ItemChangeEvent {
13 | id: string;
14 | event: ItemChangeEventType;
15 | }
16 | interface SyncStartEvent {
17 | withErrors: boolean;
18 | }
19 | interface ResourceChangeEvent {
20 | id: string;
21 | }
22 | type ItemChangeHandler = (event: ItemChangeEvent) => void;
23 | type SyncStartHandler = (event: SyncStartEvent) => void;
24 | type ResourceChangeHandler = (event: ResourceChangeEvent) => void;
25 | /**
26 | * The workspace service provides access to all the parts of Joplin that
27 | * are being worked on - i.e. the currently selected notes or notebooks as
28 | * well as various related events, such as when a new note is selected, or
29 | * when the note content changes.
30 | *
31 | * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins)
32 | */
33 | export default class JoplinWorkspace {
34 | private store;
35 | constructor(store: any);
36 | /**
37 | * Called when a new note or notes are selected.
38 | */
39 | onNoteSelectionChange(callback: Function): Promise;
40 | /**
41 | * Called when the content of a note changes.
42 | * @deprecated Use `onNoteChange()` instead, which is reliably triggered whenever the note content, or any note property changes.
43 | */
44 | onNoteContentChange(callback: Function): Promise;
45 | /**
46 | * Called when the content of the current note changes.
47 | */
48 | onNoteChange(handler: ItemChangeHandler): Promise;
49 | /**
50 | * Called when a resource is changed. Currently this handled will not be
51 | * called when a resource is added or deleted.
52 | */
53 | onResourceChange(handler: ResourceChangeHandler): Promise;
54 | /**
55 | * Called when an alarm associated with a to-do is triggered.
56 | */
57 | onNoteAlarmTrigger(handler: Function): Promise;
58 | /**
59 | * Called when the synchronisation process is starting.
60 | */
61 | onSyncStart(handler: SyncStartHandler): Promise;
62 | /**
63 | * Called when the synchronisation process has finished.
64 | */
65 | onSyncComplete(callback: Function): Promise;
66 | /**
67 | * Called just before the editor context menu is about to open. Allows
68 | * adding items to it.
69 | */
70 | filterEditorContextMenu(handler: FilterHandler): void;
71 | /**
72 | * Gets the currently selected note
73 | */
74 | selectedNote(): Promise;
75 | /**
76 | * Gets the currently selected folder. In some cases, for example during
77 | * search or when viewing a tag, no folder is actually selected in the user
78 | * interface. In that case, that function would return the last selected
79 | * folder.
80 | */
81 | selectedFolder(): Promise;
82 | /**
83 | * Gets the IDs of the selected notes (can be zero, one, or many). Use the data API to retrieve information about these notes.
84 | */
85 | selectedNoteIds(): Promise;
86 | }
87 | export {};
88 |
--------------------------------------------------------------------------------
/api/index.ts:
--------------------------------------------------------------------------------
1 | import type Joplin from './Joplin';
2 |
3 | declare const joplin: Joplin;
4 |
5 | export default joplin;
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "joplin-plugin-enhancement",
3 | "version": "1.3.1",
4 | "scripts": {
5 | "dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive",
6 | "prepare": "npm run dist",
7 | "update": "npm install -g generator-joplin && yo joplin --node-package-manager npm --update --force",
8 | "updateVersion": "webpack --env joplin-plugin-config=updateVersion"
9 | },
10 | "license": "MIT",
11 | "keywords": [
12 | "joplin-plugin",
13 | "live-preview"
14 | ],
15 | "files": [
16 | "publish"
17 | ],
18 | "devDependencies": {
19 | "@types/codemirror": "^5.60.5",
20 | "codemirror": "^5.60.5",
21 | "@codemirror/view": "6.24.1",
22 | "@codemirror/state": "6.4.1",
23 | "@codemirror/language": "6.10.1",
24 | "@types/node": "^18.7.13",
25 | "chalk": "^4.1.0",
26 | "copy-webpack-plugin": "^11.0.0",
27 | "fs-extra": "^10.1.0",
28 | "glob": "^8.0.3",
29 | "tar": "^6.1.11",
30 | "ts-loader": "^9.3.1",
31 | "ts-node": "^10.7.0",
32 | "typescript": "^4.8.2",
33 | "webpack": "^5.74.0",
34 | "webpack-cli": "^4.10.0",
35 | "yargs": "^16.2.0",
36 | "@joplin/lib": "~2.9"
37 | },
38 | "dependencies": {
39 | "dayjs": "^1.11.2",
40 | "highlight.js": "^11.6.0",
41 | "html-entities": "^2.3.3",
42 | "katex": "^0.16.0",
43 | "markdown-it": "^13.0.1",
44 | "markdown-it-mark": "^3.0.1",
45 | "mermaid": "10.6.1",
46 | "mime-types": "^2.1.35",
47 | "plantuml-encoder": "^1.4.0",
48 | "pseudocode": "^2.2.0",
49 | "showdown": "^2.1.0",
50 | "tippy.js": "^6.3.7",
51 | "ts-debounce": "^4.0.0"
52 | }
53 | }
--------------------------------------------------------------------------------
/plugin.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "extraScripts": [
3 | "driver/codemirror/tableFormatter/index.ts",
4 |
5 | "driver/markdownItRenderer/filePreview/index.ts",
6 | "driver/markdownItRenderer/image/index.ts",
7 | "driver/markdownItRenderer/pseudocode/index.ts",
8 | "driver/markdownItRenderer/quote/index.ts",
9 |
10 | "driver/markdownItRuler/image/index.ts",
11 | "driver/markdownItRuler/frontMatter/index.ts",
12 |
13 | "driver/codemirror/admonition/index.ts",
14 | "driver/codemirror/blockquote/index.ts",
15 | "driver/codemirror/searchReplace/index.ts",
16 | "driver/codemirror/focusMode/index.ts",
17 | "driver/codemirror/indentBorder/index.ts",
18 | "driver/codemirror/overlay/bulletList.ts",
19 |
20 | "driver/codemirror/index.ts"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/screenshot/admonition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/admonition.png
--------------------------------------------------------------------------------
/screenshot/codeblock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/codeblock.png
--------------------------------------------------------------------------------
/screenshot/colorfulBlockquote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/colorfulBlockquote.png
--------------------------------------------------------------------------------
/screenshot/enhancedQuote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/enhancedQuote.png
--------------------------------------------------------------------------------
/screenshot/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/example.png
--------------------------------------------------------------------------------
/screenshot/frontmatter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/frontmatter.png
--------------------------------------------------------------------------------
/screenshot/indentBorder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/indentBorder.png
--------------------------------------------------------------------------------
/screenshot/linkRender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/linkRender.png
--------------------------------------------------------------------------------
/screenshot/mathRender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/mathRender.png
--------------------------------------------------------------------------------
/screenshot/mermaidRender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/mermaidRender.png
--------------------------------------------------------------------------------
/screenshot/pseudocode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/pseudocode.png
--------------------------------------------------------------------------------
/screenshot/quoteRender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/quoteRender.png
--------------------------------------------------------------------------------
/screenshot/taskRender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/screenshot/taskRender.png
--------------------------------------------------------------------------------
/src/common.ts:
--------------------------------------------------------------------------------
1 | export const ENABLE_TABLE_FORMATTER = 'enableTableFormatter';
2 | export const ENABLE_LOCAL_PDF_PREVIEW = 'enableLocalPDFPreview';
3 | export const ENABLE_IMAGE_ENHANCEMENT = 'enableImageEnhancement';
4 | export const ENABLE_QUICK_COMMANDS = 'enableQuickCommands';
5 | export const ENABLE_PSEUDOCODE = 'enablePseudocode';
6 | export const ENABLE_ADMONITION_CM_RENDER = 'enableAdmonitionCmRender';
7 | export const ENABLE_FRONT_MATTER = 'enhancementEnableFrontMatter';
8 | export const ENABLE_COLORFUL_QUOTE = 'enhancementEnableColorfulQuote';
9 | export const ENABLE_LINK_FOLDER = 'enhancementEnableLinkFolder';
10 | export const ENABLE_BLOCK_LINK_FOLDER = 'enhancementEnableBlockLinkFolder';
11 | export const ENABLE_BLOCK_IMAGE_FOLDER = 'enhancementEnableBlockImageFolder';
12 | export const ENABLE_BLOCK_IMAGE_CAPTION = 'enhancementEnableBlockImageCaption';
13 | export const ENABLE_SEARCH_REPLACE = 'enhancementEnableSearchReplace';
14 | export const ENABLE_INLINE_MARKER = 'enhancementEnableInlineMarker';
15 | export const ENABLE_FOCUS_MODE = 'enhancementEnableFocusMode';
16 | export const ENABLE_INDENT_BORDER = 'enhancementEnableIndentBorder';
17 | export const ENABLE_TASK_RENDER = 'enhancementEnableTaskRender';
18 | export const ENABLE_HEADER_HASH_RENDER = 'enhancementEnableHeaderHashRender';
19 | export const ENABLE_TABLE_RENDER = 'enhancementEnableTableRender';
20 | export const ENABLE_MATH_RENDER = 'enhancementEnableMathRender';
21 | export const ENABLE_MERMAID_RENDER = 'enhancementEnableMermaidRender';
22 | export const ENABLE_PLANTUML_RENDER = 'enhancementEnablePlantUmlRenderer';
23 | export const ENABLE_CODEBLOCK_HL = 'enhancementEnableCodeBlockHighlight';
24 | export const ENABLE_FORMATTING_BAR = 'enhancementEnableFormattingBar';
25 | export const ENABLE_BULLET_LIST_RENDER = 'enhancementEnableBulletListRender';
26 | export const ENABLE_LIST_NUMBER_AUTO_CORRECT = 'enhancementEnableListNumberAutoCorrect';
27 | export const ENABLE_HORIZONTAL_LINE_RENDER = 'enhancementEnableHorizontalLineRender';
28 |
29 | export class EnhancementConfig {
30 | public tableFormatter: boolean;
31 | public localPdfPreview: boolean;
32 | public imageEnhancement: boolean;
33 | public quickCommands: boolean;
34 | public pseudocode: boolean;
35 | public admonitionCmRender: boolean;
36 | public frontMatterRender: boolean;
37 | public colorfulQuote: boolean;
38 | public linkFolder: boolean;
39 | public blockLinkFolder: boolean;
40 | public blockImageFolder: boolean;
41 | public blockImageCaption: boolean;
42 | public searchReplace: boolean;
43 | public inlineMarker: boolean;
44 | public focusMode: boolean;
45 | public indentBorder: boolean;
46 | public taskCmRender: boolean;
47 | public headerHashRender: boolean;
48 | public tableCmRender: boolean;
49 | public mathCmRender: boolean;
50 | public mermaidCmRender: boolean;
51 | public codeBlockHL: boolean;
52 | public formattingBar: boolean;
53 | public dateFormat: string;
54 | public plantumlCmRender: boolean;
55 | public bulletListCmRender: boolean;
56 | public listNumberAutoCorrect: boolean;
57 | public horizontalLineRender: boolean;
58 | }
59 |
60 |
61 | export enum ContextMsgType {
62 | GET_SETTINGS,
63 | OPEN_URL,
64 | RESOURCE_PATH,
65 | SHORTCUT
66 | }
67 |
68 |
69 | export class ContextMsg {
70 | type: ContextMsgType;
71 | content: any;
72 | }
73 |
--------------------------------------------------------------------------------
/src/driver/codemirror/admonition/admonition.css:
--------------------------------------------------------------------------------
1 | .CodeMirror-sizer {
2 | padding-left: 1em;
3 | }
4 |
5 | .CodeMirror-linebackground.admonition {
6 | border-left: 0.35em solid blue;
7 | background-color: transparent;
8 | margin-left: -0.8em;
9 | }
10 |
11 | .CodeMirror-linebackground.admonition.header {
12 | background-color: rgba(68,138,255,.1);
13 | }
14 |
15 | .cm-admonition-type, .cm-admonition-end {
16 | color: blue;
17 | }
18 |
19 | .cm-admonition-type.cm-warning, .cm-admonition-end.cm-warning {
20 | color: orange;
21 | }
22 |
23 | .admonition.header {
24 | border-radius: 0.25em 0.25em 0 0;
25 | }
26 |
27 | /* From joplin-plugin-admonition */
28 |
29 | .admonition.header.note {
30 | background-color: rgba(68,138,255,.1);
31 | }
32 |
33 | .admonition.header.abstract {
34 | background-color: rgba(0,176,255,.1);
35 | }
36 |
37 | .admonition.header.info {
38 | background-color: rgba(0,184,212,.1);
39 | }
40 |
41 | .admonition.header.tip {
42 | background-color: rgba(0,191,165,.1);
43 | }
44 |
45 | .admonition.header.success {
46 | background-color: rgba(0,200,83,.1);
47 | }
48 |
49 | .admonition.header.question {
50 | background-color: rgba(100,221,23,.1);
51 | }
52 |
53 | .admonition.header.warning {
54 | background-color: rgba(255,145,0,.1);
55 | }
56 |
57 | .admonition.header.failure {
58 | background-color: rgba(255,82,82,.1);
59 | }
60 |
61 | .admonition.header.danger {
62 | background-color: rgba(255,23,68,.1);
63 | }
64 |
65 | .admonition.header.bug {
66 | background-color: rgba(245,0,87,.1);
67 | }
68 |
69 | .admonition.header.example {
70 | background-color: rgba(124,77,255,.1);
71 | }
72 |
73 | .admonition.header.quote {
74 | background-color: hsla(0,0%,62%,.1);
75 | }
76 |
77 | .admonition.note {
78 | border-color: #448aff;
79 | box-shadow: 3px 0px 0px #eee;
80 | }
81 |
82 | .cm-admonition-type.cm-note, .cm-admonition-end.cm-note {
83 | color: #448aff;
84 | }
85 |
86 | .admonition.abstract {
87 | border-color: #00b0ff;
88 | box-shadow: 3px 0px 0px #eee;
89 | }
90 |
91 | .cm-admonition-type.cm-abstract, .cm-admonition-end.cm-abstract {
92 | color: #00b0ff;
93 | }
94 |
95 | .admonition.info {
96 | border-color: #00b8d4;
97 | box-shadow: 3px 0px 0px #eee;
98 | }
99 |
100 | .cm-admonition-type.cm-info, .cm-admonition-end.cm-info {
101 | color: #00b8d4;
102 | }
103 |
104 | .admonition.tip {
105 | border-color: #00bfa5;
106 | box-shadow: 3px 0px 0px #eee;
107 | }
108 |
109 | .cm-admonition-type.cm-tip, .cm-admonition-end.cm-tip {
110 | color: #00bfa5;
111 | }
112 |
113 | .admonition.success {
114 | border-color: #00c853;
115 | box-shadow: 3px 0px 0px #eee;
116 | }
117 |
118 | .cm-admonition-type.cm-success, .cm-admonition-end.cm-success {
119 | color: #00c853;
120 | }
121 |
122 | .admonition.question {
123 | border-color: #64dd17;
124 | box-shadow: 3px 0px 0px #eee;
125 | }
126 |
127 | .cm-admonition-type.cm-question, .cm-admonition-end.cm-question {
128 | color: #64dd17;
129 | }
130 |
131 | .admonition.warning {
132 | border-color: #ff9100;
133 | box-shadow: 3px 0px 0px #eee;
134 | }
135 |
136 | .cm-admonition-type.cm-warning, .cm-admonition-end.cm-warning {
137 | color: #ff9100;
138 | }
139 |
140 | .admonition.failure {
141 | border-color: #ff5252;
142 | box-shadow: 3px 0px 0px #eee;
143 | }
144 |
145 | .cm-admonition-type.cm-failure, .cm-admonition-end.cm-failure {
146 | color: #ff5252;
147 | }
148 |
149 | .admonition.danger {
150 | border-color: #ff1744;
151 | box-shadow: 3px 0px 0px #eee;
152 | }
153 |
154 | .cm-admonition-type.cm-danger, .cm-admonition-end.cm-danger {
155 | color: #ff1744;
156 | }
157 |
158 | .admonition.bug {
159 | border-color: #f50057;
160 | box-shadow: 3px 0px 0px #eee;
161 | }
162 |
163 | .cm-admonition-type.cm-bug, .cm-admonition-end.cm-bug {
164 | color: #f50057;
165 | }
166 |
167 | .admonition.example {
168 | border-color: #7c4dff;
169 | box-shadow: 3px 0px 0px #eee;
170 | }
171 |
172 | .cm-admonition-type.cm-example, .cm-admonition-end.cm-example {
173 | color: #7c4dff;
174 | }
175 |
176 | .admonition.quote {
177 | border-color: #9e9e9e;
178 | box-shadow: 3px 0px 0px #eee;
179 | }
180 |
181 | .cm-admonition-type.cm-quote, .cm-admonition-end.cm-quote {
182 | color: #9e9e9e;
183 | }
184 |
185 | span.cm-katex-marker-open, span.cm-katex-marker-close {
186 | color: burlywood;
187 | }
188 |
189 | span.cm-overlay.cm-enhancement-image-size {
190 | color: gray;
191 | }
192 |
193 | .cm-overlay.cm-enhancement-katex-inline-math {
194 | color: gray !important;
195 | }
196 |
197 | .CodeMirror-lines pre.footnote.CodeMirror-line {
198 | font-size: smaller;
199 | opacity: 0.75 !important;
200 | padding-left: 2em !important;
201 | padding-bottom: 5px;
202 | }
203 |
204 | .cm-admonition-type, .cm-admonition-end {
205 | opacity: 0.35 !important;
206 | }
207 |
--------------------------------------------------------------------------------
/src/driver/codemirror/blockquote/blockquote.css:
--------------------------------------------------------------------------------
1 | span.enhancement-folded-blockquotes-name, span.enhancement-folded-blockquotes-date {
2 | font-family: 'Font Awesome 5 Free' !important;
3 | color: var(--joplin-color-faded);
4 | }
5 |
--------------------------------------------------------------------------------
/src/driver/codemirror/blockquote/index.ts:
--------------------------------------------------------------------------------
1 | import CMInlineMarkerHelper from "../../../utils/CMInlineMarkerHelper";
2 | import {debounce} from "ts-debounce";
3 |
4 | const ENHANCED_QUOTE_MARKER = 'enhancement-folded-blockquotes';
5 | const ENHANCED_QUOTE_MARKER_NAME = 'enhancement-folded-blockquotes-name';
6 | const ENHANCED_QUOTE_MARKER_DATE = 'enhancement-folded-blockquotes-date';
7 |
8 | const regexList = [
9 | /(?<=\[)color=(.*?)(?=\])/g,
10 | /(?<=\[)name=(.*?)(?=\])/g,
11 | /(?<=\[)date=(.*?)(?=\])/g,
12 | ]
13 |
14 | module.exports = {
15 | default: function (_context) {
16 | return {
17 | plugin: function (CodeMirror) {
18 | CodeMirror.defineOption("quoteFolder", [], async function (cm, val, old) {
19 | const mathMarkerHelper = new CMInlineMarkerHelper(_context, cm, regexList, function (match, regIndex: number, from, to, innerDomEleCopy, lastMatchFrom, lastMatchTo) {
20 | const markEl = document.createElement('span');
21 | markEl.classList.add(ENHANCED_QUOTE_MARKER);
22 | switch (regIndex) {
23 | case 0:
24 | markEl.textContent = `■`;
25 | markEl.style.cssText = `color: ${match[1]}; font-size: large; vertical-align: middle;`
26 | break;
27 | case 1:
28 | markEl.classList.add('fas', 'fa-user', ENHANCED_QUOTE_MARKER_NAME);
29 | break;
30 | case 2:
31 | markEl.classList.add('fas', 'fa-clock', ENHANCED_QUOTE_MARKER_DATE);
32 | break;
33 | default:
34 | break;
35 | }
36 | if (regIndex !== 0) {
37 | markEl.style.cssText = 'color: darkgray;';
38 | }
39 | return markEl;
40 | }, [ENHANCED_QUOTE_MARKER, ENHANCED_QUOTE_MARKER_NAME, ENHANCED_QUOTE_MARKER_DATE], function (line) {
41 | return line.trim().startsWith('>')
42 | });
43 | });
44 | },
45 | codeMirrorOptions: {'quoteFolder': true},
46 | assets: function () {
47 | return [
48 | {
49 | name: "blockquote.css"
50 | }
51 | ];
52 | }
53 | }
54 | },
55 | }
56 |
--------------------------------------------------------------------------------
/src/driver/codemirror/commands/index.ts:
--------------------------------------------------------------------------------
1 | import { isReadOnly } from "../../../utils/cm-utils";
2 |
3 | export function initCommands(cm, CodeMirror) {
4 | const commandBridge = new CommandsBridge(cm);
5 |
6 | const addCommand = (name: string, command: ()=>void) => {
7 | // Work around a bug in CodeMirror.defineExtension on CodeMirror 6 --
8 | // closing and opening settings can leave such extensions only registered once.
9 | // See https://github.com/laurent22/joplin/issues/10023
10 | if (cm.cm6) {
11 | CodeMirror.registerCommand(name, command);
12 | } else {
13 | CodeMirror.defineExtension(name, command);
14 | }
15 | };
16 |
17 | addCommand('markdownHL1', commandBridge.hL1.bind(commandBridge));
18 | addCommand('markdownHL2', commandBridge.hL2.bind(commandBridge));
19 | addCommand('markdownHL3', commandBridge.hL3.bind(commandBridge));
20 | addCommand('markdownHL4', commandBridge.hL4.bind(commandBridge));
21 | addCommand('markdownHL5', commandBridge.hL5.bind(commandBridge));
22 | addCommand('markdownHL6', commandBridge.hL6.bind(commandBridge));
23 | addCommand('markdownHL7', commandBridge.hL7.bind(commandBridge));
24 | }
25 |
26 | class CommandsBridge {
27 | constructor(private readonly cm) {
28 | }
29 |
30 | hL1() {
31 | this.hlWithColor('#ffd400');
32 | }
33 |
34 | hL2() {
35 | this.hlWithColor('#ff6666');
36 | }
37 |
38 | hL3() {
39 | this.hlWithColor('#5fb236');
40 | }
41 |
42 | hL4() {
43 | this.hlWithColor('#2ea8e5');
44 | }
45 |
46 | hL5() {
47 | this.hlWithColor('#a28ae5');
48 | }
49 |
50 | hL6() {
51 | this.hlWithColor('#e56eee');
52 | }
53 |
54 | hL7() {
55 | this.hlWithColor('#f19837');
56 | }
57 |
58 | hlWithColor(color: string) {
59 | if (isReadOnly(this.cm)) {
60 | return;
61 | }
62 | markdownInline(this.cm, ``, '', 'mark')
63 | }
64 | }
65 |
66 |
67 | /**
68 | * Converts selection into a markdown inline element (or removes formatting)
69 | *
70 | * @param {CodeMirror.Editor} cm The CodeMirror instance
71 | * @param {string} pre The formatting mark before the element
72 | * @param {string} post The formatting mark behind the element
73 | */
74 | function markdownInline (cm: CodeMirror.Editor, pre: string, post: string, tokentype?: string): void {
75 | // Is something selected?
76 | if (!cm.somethingSelected()) {
77 | // TODO: Check token type state at the cursor position to leave the
78 | // mode if already in the mode.
79 | //
80 | // FIXME(cm6): cm.getTokenAt is unsupported in CodeMirror 6.
81 | let currentToken = cm.getTokenAt?.(cm.getCursor())?.type
82 | if (tokentype !== undefined && currentToken !== null && currentToken?.includes(tokentype)) { // -- the tokentypes can be multiple (spell-error, e.g.)
83 | // We are, indeed, currently in this token. So let's check *how*
84 | // we are going to leave the state.
85 | let to = { 'line': cm.getCursor().line, 'ch': cm.getCursor().ch + post.length }
86 | if (cm.getRange(cm.getCursor(), to) === post) {
87 | cm.setCursor(to)
88 | } else {
89 | // No sign in sight -> insert it. Cursor will automatically move forward
90 | cm.replaceSelection(post)
91 | }
92 | } else {
93 | // Not in the mode -> simply do the standard.
94 | cm.replaceSelection(pre + '' + post, 'start')
95 | // Move cursor forward (to the middle of the insertion)
96 | const cur = cm.getCursor()
97 | cur.ch = cur.ch + pre.length
98 | cm.setCursor(cur)
99 | }
100 | return
101 | }
102 |
103 | // Build the regular expression by first escaping problematic characters
104 | let preregex = pre.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
105 | let postregex = post.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
106 |
107 | let re = new RegExp('^' + preregex + '.+?' + postregex + '$', 'g')
108 |
109 | const replacements = []
110 | for (const selection of cm.getSelections()) {
111 | if (re.test(selection)) {
112 | // We got something so unformat.
113 | replacements.push(selection.substr(pre.length, selection.length - pre.length - post.length))
114 | } else {
115 | // TODO: Check whether the user just selected the text itself and
116 | // not the formatting marks!
117 |
118 | // NOTE: Since the user can triple-click a line, that selection will
119 | // extend beyond the line. So check if the last char of selection is
120 | // a newline, and, if so, pluck that and push it after post.
121 | if (selection[selection.length - 1] === '\n') {
122 | replacements.push(pre + String(selection).substr(0, selection.length - 1) + post + '\n')
123 | } else {
124 | replacements.push(pre + selection + post)
125 | }
126 | }
127 | }
128 |
129 | // Replace with changes selections
130 | cm.replaceSelections(replacements, 'around')
131 | }
132 |
--------------------------------------------------------------------------------
/src/driver/codemirror/common.ts:
--------------------------------------------------------------------------------
1 | export const enhancement_overlay_option = 'enhancement_overlay_option';
2 | export const enhancement_mermaid_render = 'enhancement_mermaid_render';
3 | export const enhancement_inline_marker = 'enhancement_inline_marker';
4 |
--------------------------------------------------------------------------------
/src/driver/codemirror/focusMode/focusModeStyle.css:
--------------------------------------------------------------------------------
1 | .CodeMirror-sizer {
2 | padding-left: 1em;
3 | }
4 |
--------------------------------------------------------------------------------
/src/driver/codemirror/focusMode/index.ts:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | default: function(_context) {
3 | return {
4 | plugin: function (CodeMirror) { },
5 | assets: function() {
6 | return [
7 | {
8 | name: 'focusModeStyle.css'
9 | }
10 | ];
11 | }
12 | }
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/src/driver/codemirror/formattingBar/formattingBar.css:
--------------------------------------------------------------------------------
1 | .editor-formatting-bar {
2 | cursor: default;
3 | display: flex;
4 | background-color: rgba(51, 51,51, 0.85);
5 | border-radius: 4px;
6 | color: white;
7 | text-align: center;
8 | }
9 |
10 | .editor-formatting-bar .button {
11 | display: inline-block;
12 | padding: 4px 8px;
13 | width: 35px;
14 | height: 35px;
15 | line-height: 25px;
16 | transition: 0.3s all ease;
17 | }
18 |
19 | .editor-formatting-bar .button:first-child {
20 | border-top-left-radius: 4px;
21 | border-bottom-left-radius: 4px;
22 | }
23 |
24 | .editor-formatting-bar .button:last-child {
25 | border-top-right-radius: 4px;
26 | border-bottom-right-radius: 4px;
27 | }
28 |
29 | .editor-formatting-bar .button:hover {
30 | background-color: rgb(120, 120, 120);
31 | }
32 |
33 | .editor-formatting-bar i.fas {
34 | font-family: 'Font Awesome 5 Free' !important;
35 | }
36 |
37 | .editor-formatting-bar i.fas.color1 {
38 | color: #ffd400;
39 | }
40 |
41 | .editor-formatting-bar i.fas.color2 {
42 | color: #ff6666;
43 | }
44 |
45 | .editor-formatting-bar i.fas.color3 {
46 | color: #5fb236;
47 | }
48 |
49 | .editor-formatting-bar i.fas.color4 {
50 | color: #2ea8e5;
51 | }
52 |
53 | .editor-formatting-bar i.fas.color5 {
54 | color: #a28ae5;
55 | }
56 |
57 | .editor-formatting-bar i.fas.color6 {
58 | color: #e56eee;
59 | }
60 |
61 | .editor-formatting-bar i.fas.color7 {
62 | color: #f19837;
63 | }
64 |
--------------------------------------------------------------------------------
/src/driver/codemirror/formattingBar/initFormattingBar.ts:
--------------------------------------------------------------------------------
1 | import { ContentScriptContext } from "api/types";
2 | import v6FormattingBar from "./v6/formattingBar"
3 | import v5FormattingBar from "./v5/formattingBar";
4 |
5 | const initFormattingBar = (context: ContentScriptContext, cm: any) => {
6 | if (cm.cm6) {
7 | cm.addExtension(v6FormattingBar(context));
8 | } else {
9 | v5FormattingBar(context, cm);
10 | }
11 | };
12 |
13 | export default initFormattingBar;
--------------------------------------------------------------------------------
/src/driver/codemirror/indentBorder/indentBorder.css:
--------------------------------------------------------------------------------
1 | /* from Obsidian */
2 |
3 | .cm-tab::before, .cm-null.cm-overlay.cm-rm-list-token::before {
4 | content: "\200B";
5 | width: 1px;
6 | border-right: 1px solid lightgray;
7 | color: transparent;
8 | position: absolute;
9 | top: 0;
10 | bottom: 0;
11 | }
12 |
13 | /* Add a vertical bar to the left side of quote blocks so they match the viewer */
14 | pre.cm-rm-blockquote.CodeMirror-line {
15 | border-left: 4px solid var(--joplin-code-border-color);
16 | opacity: 0.7;
17 | background-color: var(--joplin-background-color3);
18 |
19 | }
20 |
21 | pre.cm-rm-blockquote span.cm-quote + span.cm-quote {
22 | opacity: 1;
23 | }
24 |
25 | pre.cm-jn-code-block.CodeMirror-line {
26 | padding-left: 0.4em !important;
27 | line-height: initial !important;
28 | }
29 |
30 | /*.CodeMirror-selected {*/
31 | /* background-color: transparent !important;*/
32 | /*}*/
33 |
34 | /*.CodeMirror-selectedtext {*/
35 | /* background-color: rgba(179, 215, 255, 1) !important;*/
36 | /*}*/
37 |
38 |
39 | div.CodeMirror-activeline > pre > span > span.cm-rm-blockquote-token {
40 | opacity: 0.7 !important;
41 | }
42 |
43 | div:not(.CodeMirror-activeline) > pre > span > span.cm-rm-blockquote-token {
44 | opacity: 0 !important;
45 | }
46 |
--------------------------------------------------------------------------------
/src/driver/codemirror/indentBorder/index.ts:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | default: function(_context) {
3 | return {
4 | plugin: function (CodeMirror) { },
5 | codeMirrorResources: [
6 | 'addon/selection/mark-selection.js'
7 | ],
8 | codeMirrorOptions: {
9 | },
10 | assets: function() {
11 | return [
12 | {
13 | name: 'indentBorder.css'
14 | }
15 | ];
16 | }
17 | }
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/src/driver/codemirror/inlineMarker/inlineMarkerStyle.css:
--------------------------------------------------------------------------------
1 | .cm-editor-marker-colons,
2 | .cm-editor-marker-keyword {
3 | color: orangered !important;
4 | font-weight: bold;
5 | }
6 |
7 | .cm-editor-marker-left-open,
8 | .cm-editor-marker-keyword,
9 | .cm-editor-marker-colons {
10 | background-color: #f5f6f8;
11 | }
12 |
13 | .cm-editor-marker-left-open {
14 | border-radius: 1em 0 0 1em;
15 | color: transparent !important;
16 | }
17 |
18 | .cm-editor-marker-right-close {
19 | border-radius: 0 1em 1em 0;
20 | color: transparent !important;
21 | }
22 |
23 | .cm-editor-marker-right-close,
24 | .cm-editor-marker-text {
25 | background-color: deepskyblue;
26 | }
27 |
28 | .cm-editor-marker-text {
29 | color: white !important;
30 | }
31 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/codeMarker.ts:
--------------------------------------------------------------------------------
1 | import {CMBlockMarkerHelperV2} from "../../../utils/CMBlockMarkerHelperV2";
2 | import {CODE_BLOCK_END, CODE_BLOCK_START} from "./regexps";
3 |
4 | export const ENHANCED_CODE_BLOCK_MARKER = 'enhancement-code-block-marker';
5 | const NOT_RENDERED = 'paper|mermaid|pseudocode|plantuml';
6 |
7 | const hljs = require('highlight.js');
8 |
9 |
10 | export function createCodeBlockMarker(context, cm) {
11 | return new CMBlockMarkerHelperV2(cm, null, CODE_BLOCK_START, CODE_BLOCK_END, (beginMatch, endMatch, content, fromLine, toLine) => {
12 | const markEl = document.createElement('div');
13 |
14 | markEl.innerHTML = hljs.highlight(content, {language: beginMatch[1]}).value;
15 |
16 | return markEl;
17 | }, () => {
18 | const span = document.createElement('span');
19 | span.textContent = '===> Folded Code Block <===';
20 | span.style.cssText = 'color: lightgray; font-size: smaller; font-style: italic;';
21 | return span;
22 | }, ENHANCED_CODE_BLOCK_MARKER, true, false, (cm, line, match) => {
23 | if (match[1]) {
24 | return !NOT_RENDERED.includes(match[1].toLowerCase()) && hljs.getLanguage(match[1]);
25 | } else {
26 | return true;
27 | }
28 | }, (content, e) => {
29 |
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/default.min.css:
--------------------------------------------------------------------------------
1 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2 | Theme: Agate
3 | Author: (c) Taufik Nurrohman
4 | Maintainer: @taufik-nurrohman
5 | Updated: 2021-04-24
6 |
7 | #333
8 | #62c8f3
9 | #7bd694
10 | #888
11 | #a2fca2
12 | #ade5fc
13 | #b8d8a2
14 | #c6b4f0
15 | #d36363
16 | #fc9b9b
17 | #fcc28c
18 | #ffa
19 | #fff
20 | */.hljs{background:#333;color:#fff}.hljs-doctag,.hljs-meta-keyword,.hljs-name,.hljs-strong{font-weight:700}.hljs-code,.hljs-emphasis{font-style:italic}.hljs-section,.hljs-tag{color:#62c8f3}.hljs-selector-class,.hljs-selector-id,.hljs-template-variable,.hljs-variable{color:#ade5fc}.hljs-meta-string,.hljs-string{color:#a2fca2}.hljs-attr,.hljs-quote,.hljs-selector-attr{color:#7bd694}.hljs-tag .hljs-attr{color:inherit}.hljs-attribute,.hljs-title,.hljs-type{color:#ffa}.hljs-number,.hljs-symbol{color:#d36363}.hljs-bullet,.hljs-template-tag{color:#b8d8a2}.hljs-built_in,.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#fcc28c}.hljs-code,.hljs-comment,.hljs-formula{color:#888}.hljs-link,.hljs-regexp,.hljs-selector-pseudo{color:#c6b4f0}.hljs-meta{color:#fc9b9b}.hljs-deletion{background:#fc9b9b;color:#333}.hljs-addition{background:#a2fca2;color:#333}.hljs-subst{color:#fff}.hljs a{color:inherit}.hljs a:focus,.hljs a:hover{color:inherit;text-decoration:underline}.hljs mark{background:#555;color:inherit}
21 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_AMS-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_AMS-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_AMS-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_AMS-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_AMS-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_AMS-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Bold.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Bold.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Bold.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Caligraphic-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Bold.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Bold.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Bold.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Fraktur-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Bold.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Bold.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Bold.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-BoldItalic.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-BoldItalic.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Italic.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Italic.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Italic.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Main-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-BoldItalic.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-BoldItalic.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-Italic.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-Italic.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Math-Italic.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Bold.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Bold.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Bold.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Italic.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Italic.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Italic.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_SansSerif-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Script-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Script-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Script-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Script-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Script-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Script-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size1-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size1-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size1-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size1-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size1-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size1-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size2-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size2-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size2-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size2-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size2-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size2-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size3-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size3-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size3-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size3-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size3-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size3-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size4-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size4-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size4-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size4-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Size4-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Size4-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Typewriter-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Typewriter-Regular.ttf
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Typewriter-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Typewriter-Regular.woff
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/fonts/KaTeX_Typewriter-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeptemberHX/joplin-plugin-enhancement/69ddd42bf645feb982d2e1be7d0e33697a61b6ad/src/driver/codemirror/linkFolder/fonts/KaTeX_Typewriter-Regular.woff2
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/footnoteMarker.ts:
--------------------------------------------------------------------------------
1 | import CMInlineMarkerHelperV2 from "../../../utils/CMInlineMarkerHelperV2";
2 | import {INLINE_FOOTNOTE_REG} from "./regexps";
3 |
4 | const ENHANCED_FOOTNOTE_MARKER = 'enhancement-footnote-marker';
5 | const ENHANCED_FOOTNOTE_MARKER_TEXT = 'enhancement-footnote-marker-text';
6 |
7 |
8 | export function createInlineFootnoteMarker(context, cm) {
9 | return new CMInlineMarkerHelperV2(cm, INLINE_FOOTNOTE_REG, function (match, from, to) {
10 | const markEl = document.createElement('span');
11 | markEl.classList.add(ENHANCED_FOOTNOTE_MARKER);
12 | const textEl = document.createElement('span');
13 | textEl.classList.add(ENHANCED_FOOTNOTE_MARKER_TEXT);
14 | textEl.textContent = match[2];
15 | markEl.appendChild(textEl);
16 |
17 | const typesStr = cm.getTokenTypeAt(from);
18 | if (typesStr) {
19 | for (const typeStr of typesStr.split(' ')) {
20 | markEl.classList.add(`cm-${typeStr}`);
21 | }
22 | }
23 | return markEl;
24 | }, ENHANCED_FOOTNOTE_MARKER);
25 | }
26 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/horizontalLineMarker.ts:
--------------------------------------------------------------------------------
1 | import {CMBlockMarkerHelperV2} from "../../../utils/CMBlockMarkerHelperV2";
2 | import {HORIZONTAL_LINE_REG} from "./regexps";
3 |
4 | export const ENHANCED_HORIZONTAL_LINE_MARKER = 'enhancement-horizontal-line-marker';
5 |
6 |
7 | export function createHorizontalLineMarker(context, cm) {
8 | return new CMBlockMarkerHelperV2(cm, null, HORIZONTAL_LINE_REG, null, (beginMatch, endMatch, content, fromLine, toLine) => {
9 | const hrEl = document.createElement('hr');
10 | return hrEl;
11 | }, () => {
12 | const span = document.createElement('span');
13 | span.textContent = '===> Folded Horizontal Line <===';
14 | span.style.cssText = 'color: lightgray; font-size: smaller; font-style: italic;';
15 | return span;
16 | }, ENHANCED_HORIZONTAL_LINE_MARKER, true, false, null, null, true);
17 | }
18 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/htmlTagRender.ts:
--------------------------------------------------------------------------------
1 | import CMInlineMarkerHelperV2 from "../../../utils/CMInlineMarkerHelperV2";
2 | import {HTML_INS_REG, HTML_MARK_REG, HTML_TAG_STYLE_REG} from "./regexps";
3 | import {renderStrToDom} from "../../../utils/string-render";
4 |
5 |
6 | const ENHANCED_HTML_MARK_MARKER = 'enhancement-html-mark-marker';
7 | const ENHANCED_HTML_INS_MARKER = 'enhancement-html-ins-marker';
8 |
9 |
10 | export function createHtmlTagMarkRenderMarker(context, cm, renderBlock: boolean = false) {
11 | return new CMInlineMarkerHelperV2(cm, HTML_MARK_REG, function (match, from, to) {
12 | const markEl = document.createElement('mark');
13 | markEl.classList.add(ENHANCED_HTML_MARK_MARKER);
14 | markEl.innerHTML = renderStrToDom(match[1]);
15 |
16 | const styleMatch = HTML_TAG_STYLE_REG.exec(match[0]);
17 | if (styleMatch) {
18 | let cssText = '';
19 | for (const style of styleMatch[1].split(';')) {
20 | if (style.length > 0) {
21 | cssText += style + ' !important;';
22 | }
23 | }
24 |
25 | markEl.style.cssText = cssText;
26 | }
27 |
28 | return markEl;
29 | }, ENHANCED_HTML_MARK_MARKER);
30 | }
31 |
32 | export function createHtmlTagInsRenderMarker(context, cm, renderBlock: boolean = false) {
33 | return new CMInlineMarkerHelperV2(cm, HTML_INS_REG, function (match, from, to) {
34 | const markEl = document.createElement('ins');
35 | markEl.classList.add(ENHANCED_HTML_INS_MARKER);
36 | markEl.innerHTML = renderStrToDom(match[1]);
37 |
38 | const styleMatch = HTML_TAG_STYLE_REG.exec(match[0]);
39 | if (styleMatch) {
40 | let cssText = '';
41 | for (const style of styleMatch[1].split(';')) {
42 | if (style.length > 0) {
43 | cssText += style + ' !important;';
44 | }
45 | }
46 |
47 | markEl.style.cssText = cssText;
48 | }
49 |
50 | return markEl;
51 | }, ENHANCED_HTML_INS_MARKER);
52 | }
53 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/imageMarker.ts:
--------------------------------------------------------------------------------
1 | import CMInlineMarkerHelperV2 from "../../../utils/CMInlineMarkerHelperV2";
2 | import {CMBlockMarkerHelperV2} from "../../../utils/CMBlockMarkerHelperV2";
3 | import {BLOCK_IMAGE_REG, INLINE_IMAGE_REG} from "./regexps";
4 | import {findLineWidgetAtLine} from "../../../utils/cm-utils";
5 | import {ContextMsgType} from "../../../common";
6 | import {renderStrToDom} from "../../../utils/string-render";
7 |
8 |
9 | const ENHANCED_IMAGE_MARKER = 'enhancement-image-marker';
10 | export const ENHANCED_BLOCK_IMAGE_MARKER = 'enhancement-block-image-marker';
11 |
12 | const ENHANCED_IMAGE_MARKER_ICON = 'enhancement-image-marker-icon';
13 | const ENHANCED_IMAGE_MARKER_TEXT = 'enhancement-image-marker-text';
14 | const ENHANCED_IMAGE_SIZE_TEXT = 'enhancement-image-size-text';
15 |
16 | export function createInlineImageMarker(context, cm, enableBlockImageRendering: boolean) {
17 | return new CMInlineMarkerHelperV2(cm, INLINE_IMAGE_REG, function (match, from, to) {
18 | const markEl = document.createElement('span');
19 |
20 | markEl.classList.add(ENHANCED_IMAGE_MARKER);
21 | const iconEl = document.createElement('i');
22 | iconEl.classList.add(ENHANCED_IMAGE_MARKER_ICON, 'fas', 'fa-image');
23 | markEl.appendChild(iconEl);
24 |
25 | const textEl = document.createElement('span');
26 | textEl.classList.add(ENHANCED_IMAGE_MARKER_TEXT);
27 | textEl.innerHTML = renderStrToDom(match[1]);
28 | markEl.appendChild(textEl);
29 |
30 | if (match[3]) {
31 | const sizeEl = document.createElement('span');
32 | sizeEl.classList.add(ENHANCED_IMAGE_SIZE_TEXT);
33 | sizeEl.textContent = match[3].substr(1, match[3].length - 2);
34 | markEl.appendChild(sizeEl);
35 | }
36 |
37 | const typesStr = cm.getTokenTypeAt(from);
38 | if (typesStr) {
39 | for (const typeStr of typesStr.split(' ')) {
40 | markEl.classList.add(`cm-${typeStr}`);
41 | }
42 | }
43 | return markEl;
44 | }, ENHANCED_IMAGE_MARKER, function (line) {
45 | if (enableBlockImageRendering) {
46 | return !BLOCK_IMAGE_REG.test(line);
47 | } else {
48 | return true;
49 | }
50 | });
51 | }
52 |
53 | export function createBlockImageMarker(context, cm, enableCaption: boolean) {
54 | return new CMBlockMarkerHelperV2(cm, null, BLOCK_IMAGE_REG, null, (beginMatch, endMatch, content, fromLine, toLine) => {
55 | const markEl = document.createElement('figure');
56 | const imgEl = document.createElement('img');
57 |
58 | let imgUrl = beginMatch[2] ? beginMatch[2] : '';
59 | if (imgUrl.startsWith(':/')) {
60 | context.postMessage({
61 | type: ContextMsgType.RESOURCE_PATH,
62 | content: beginMatch[2]
63 | }).then((path) => {
64 | imgEl.src = path;
65 | const lineWidget = findLineWidgetAtLine(cm, fromLine, ENHANCED_BLOCK_IMAGE_MARKER + '-line-widget');
66 | if (lineWidget) {
67 | setTimeout(() => {lineWidget.changed()}, 50);
68 | }
69 | })
70 | } else {
71 | imgEl.src = imgUrl;
72 | }
73 | markEl.appendChild(imgEl);
74 |
75 | if (enableCaption && beginMatch[1]) {
76 | const captionEl = document.createElement('figcaption');
77 | captionEl.innerHTML = renderStrToDom(beginMatch[1]);
78 | markEl.appendChild(captionEl);
79 | }
80 |
81 | if (beginMatch[3]) {
82 | const widthMatch = /\{width=(\d+)(px|%|)\}/.exec(beginMatch[3]);
83 | if (widthMatch) {
84 | imgEl.style.width = widthMatch[1] + (widthMatch[2].length === 0 ? 'px' : widthMatch[2]);
85 | }
86 | }
87 | return markEl;
88 | }, () => {
89 | const span = document.createElement('span');
90 | span.textContent = '===> Folded Image Block <===';
91 | span.style.cssText = 'color: lightgray; font-size: smaller; font-style: italic;';
92 | return span;
93 | }, ENHANCED_BLOCK_IMAGE_MARKER, true, false, null, (content, e) => {
94 | const match = BLOCK_IMAGE_REG.exec(content);
95 | context.postMessage({
96 | type: ContextMsgType.OPEN_URL,
97 | content: match[2]
98 | });
99 | });
100 | }
101 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/mathMaker.ts:
--------------------------------------------------------------------------------
1 | import {CMBlockMarkerHelperV2} from "../../../utils/CMBlockMarkerHelperV2";
2 | import katex from 'katex'
3 | import CMInlineMarkerHelperV2 from "../../../utils/CMInlineMarkerHelperV2";
4 |
5 | export const ENHANCEMENT_MATH_BLOCK_SPAN_MARKER_CLASS = 'enhancement-math-block-marker';
6 |
7 | export function createInlineMathMarker(context, cm) {
8 | return new CMInlineMarkerHelperV2(cm, /(? {
9 | const markEl = document.createElement('span');
10 | katex.render(match[1], markEl, { throwOnError: false, displayMode: false, output: 'html' })
11 | return markEl;
12 | }, 'enhancement-inline-math-marker');
13 | }
14 |
15 | export function createBlockMathMarker(context, cm) {
16 | return new CMBlockMarkerHelperV2(cm, null, /^\s*\$\$\s*$/, /^\s*\$\$\s*$/, (beginMatch, endMatch, content: string, fromLine, toLine) => {
17 | let divElement = document.createElement("div");
18 | let spanElement = document.createElement('span');
19 | if (content.trim().length > 0) {
20 | let cCount = 0;
21 | for (let i = content.length - 1; i >= 0; i--) {
22 | if (content[i] === '\\') {
23 | cCount++;
24 | } else {
25 | break;
26 | }
27 | }
28 |
29 | katex.render(cCount % 2 !== 0 ? content.substring(0, content.length - 1) : content,
30 | spanElement, { throwOnError: false, strict: false, displayMode: true, output: 'html' })
31 | } else {
32 | spanElement.textContent = 'Empty Math Block';
33 | }
34 | divElement.appendChild(spanElement);
35 | divElement.style.cssText = 'text-align: center;'
36 | return divElement;
37 | }, () => {
38 | const span = document.createElement('span');
39 | span.textContent = '===> Folded Math Block <===';
40 | span.style.cssText = 'color: lightgray; font-size: smaller; font-style: italic;';
41 | return span;
42 | },ENHANCEMENT_MATH_BLOCK_SPAN_MARKER_CLASS, true, true, (editor, line, match) => {
43 | const token = editor.getTokenTypeAt({line: line, ch: match.index});
44 | return !token || !token.includes('jn-monospace');
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/plantumlMarker.ts:
--------------------------------------------------------------------------------
1 | import {CMBlockMarkerHelperV2} from "../../../utils/CMBlockMarkerHelperV2";
2 | var plantumlEncoder = require('plantuml-encoder')
3 |
4 | export const ENHANCEMENT_PLANTUML_SPAN_MARKER_CLASS = 'enhancement-plantuml-block-marker';
5 |
6 |
7 | export default function createPlantumlMarker(context, cm) {
8 | return new CMBlockMarkerHelperV2(cm, null, /^\s*```plantuml\s*$/, /^\s*```\s*$/, (beginMatch, endMatch, content: string, fromLine, toLine) => {
9 | let divElement = document.createElement("div");
10 | let imgElement = document.createElement('img');
11 | const encoded = plantumlEncoder.encode(content);
12 | imgElement.src = `http://www.plantuml.com/plantuml/svg/${encoded}`;
13 | divElement.appendChild(imgElement);
14 | divElement.style.cssText = 'text-align: center;'
15 | return divElement;
16 | }, () => {
17 | const span = document.createElement('span');
18 | span.textContent = '===> Folded Plantuml Block <===';
19 | span.style.cssText = 'color: lightgray; font-size: smaller; font-style: italic;';
20 | return span;
21 | }, ENHANCEMENT_PLANTUML_SPAN_MARKER_CLASS, true, false);
22 | }
23 |
--------------------------------------------------------------------------------
/src/driver/codemirror/linkFolder/regexps.ts:
--------------------------------------------------------------------------------
1 | export const INLINE_LINK_REG = /(??[ \t]*((['"])(.*?)\6[ \t]*)?\))$/;
9 |
10 | export const CODE_BLOCK_START = /^\s*```\s*(\S+)\s*$/;
11 | export const CODE_BLOCK_END = /^\s*```\s*$/;
12 |
13 |
14 | export const HTML_MARK_REG = /]*?>(.*?)<\/mark>/g;
15 | export const HTML_TAG_STYLE_REG = /style="(.*?)"/;
16 |
17 | export const HTML_INS_REG = /]*?>(.*?)<\/ins>/g;
18 |
19 | export const HORIZONTAL_LINE_REG = /^-{3,}$/g;
20 |
--------------------------------------------------------------------------------
/src/driver/codemirror/listNumber/index.ts:
--------------------------------------------------------------------------------
1 | import {debounce} from "ts-debounce";
2 |
3 | const listLineReg = /^\s*[\d|a-z]+\.\s/;
4 | const dotListLineReg = /^\s+[\*\-\+]\s/;
5 | const indentLineReg = /^\s+/;
6 |
7 |
8 | export function listNumberCorrector(context, cm: CodeMirror.Editor) {
9 | const fixListNumberDebounce = debounce((cm) => {
10 | fixListNumber(cm);
11 | }, 50);
12 | cm.on('cursorActivity', fixListNumberDebounce);
13 | }
14 |
15 | export function fixListNumber(cm: CodeMirror.Editor) {
16 | let currLineNumber = cm.getCursor().line;
17 | if (!currLineNumber) {
18 | return;
19 | }
20 |
21 | const currLine = cm.getLine(currLineNumber);
22 | if (!listLineReg.test(currLine) && !dotListLineReg.test(currLine)) {
23 | return;
24 | }
25 |
26 | // check list range starts
27 | let listFromLineN = currLineNumber;
28 | for (let lineN = currLineNumber - 1; lineN >= 0; --lineN) {
29 | const lineStr = cm.getLine(lineN);
30 | if (!lineStr) {
31 | break;
32 | }
33 |
34 | if (!listLineReg.test(lineStr) && !dotListLineReg.test(lineStr) && !indentLineReg.test(lineStr)) {
35 | break;
36 | }
37 |
38 | listFromLineN = lineN;
39 | }
40 |
41 | let listToLineN = currLineNumber;
42 | for (let lineN = currLineNumber + 1; lineN < cm.lineCount(); ++lineN) {
43 | const lineStr = cm.getLine(lineN);
44 | if (!lineStr) {
45 | break;
46 | }
47 |
48 | if (!listLineReg.test(lineStr) && !dotListLineReg.test(lineStr) && !indentLineReg.test(lineStr)) {
49 | break;
50 | }
51 |
52 | listToLineN = lineN;
53 | }
54 | // check list range ends
55 |
56 | let lastIndexMap = {0: 0};
57 | let listIndexType = 1; // 1: number; 2: a b c
58 | let listIndexTypeMap = {};
59 | let currIndent = 0;
60 | let currIndexN = 0;
61 | for (let lineN = listFromLineN; lineN <= listToLineN; ++lineN) {
62 | // ignore stuff in codeblocks
63 | const token = cm.getTokenTypeAt({line: lineN, ch: 0});
64 | if (token && token.includes('jn-monospace')) {
65 | continue;
66 | }
67 |
68 | const lineStr = cm.getLine(lineN);
69 | const indent = lineStr.length - lineStr.trimStart().length;
70 |
71 | if (indent > currIndent) {
72 | currIndexN = 1;
73 | } else if (indent == currIndent) {
74 | currIndexN += 1;
75 | } else if (indent < currIndent) {
76 | if (indent in lastIndexMap) {
77 | currIndexN = lastIndexMap[indent] + 1;
78 | } else {
79 | currIndexN = 1;
80 | }
81 |
82 | for (let child of Object.keys(lastIndexMap)) {
83 | if (Number(child) > indent) {
84 | delete lastIndexMap[child];
85 | }
86 | }
87 |
88 | for (let child of Object.keys(listIndexTypeMap)) {
89 | if (Number(child) > indent) {
90 | delete listIndexTypeMap[child];
91 | }
92 | }
93 | }
94 |
95 | currIndent = indent;
96 | lastIndexMap[indent] = currIndexN;
97 |
98 | const regResult = listLineReg.exec(lineStr);
99 |
100 | if (!regResult) {
101 | continue;
102 | }
103 |
104 | const numberStr = regResult[0].substr(indent, regResult[0].length - indent);
105 | if (!(indent in listIndexTypeMap)) {
106 | if (/\d+\.\s/.test(numberStr)) {
107 | listIndexTypeMap[indent] = 1;
108 | } else if (/[a-z]+\.\s/.test(numberStr)) {
109 | listIndexTypeMap[indent] = 2;
110 | } else {
111 | listIndexTypeMap[indent] = 1;
112 | }
113 | }
114 |
115 | listIndexType = listIndexTypeMap[indent];
116 | let replacement = '';
117 | switch (listIndexType) {
118 | case 1:
119 | replacement = `${currIndexN}. `;
120 | break;
121 | case 2:
122 | replacement = `${convertToNumberingScheme(currIndexN)}. `;
123 | break;
124 | default:
125 | break;
126 | }
127 |
128 | if (numberStr == replacement) {
129 | continue;
130 | }
131 |
132 | cm.replaceRange(replacement, {'line': lineN, 'ch': indent}, {'line': lineN, 'ch': regResult[0].length});
133 | }
134 | }
135 |
136 | function convertToNumberingScheme(number) {
137 | var baseChar = ("a").charCodeAt(0),
138 | letters = "";
139 |
140 | do {
141 | number -= 1;
142 | letters = String.fromCharCode(baseChar + (number % 26)) + letters;
143 | number = (number / 26) >> 0; // quick `floor`
144 | } while(number > 0);
145 |
146 | return letters;
147 | }
148 |
--------------------------------------------------------------------------------
/src/driver/codemirror/mermaidRender/index.ts:
--------------------------------------------------------------------------------
1 | import {CMBlockMarkerHelper} from "../../../utils/CMBlockMarkerHelper";
2 | import mermaid from 'mermaid'
3 | import {LineHandle} from "codemirror";
4 |
5 | const ENHANCEMENT_MERMAID_SPAN_MARKER_CLASS = 'enhancement-mermaid-block-marker';
6 | const ENHANCEMENT_MERMAID_SPAN_MARKER_LINE_CLASS = 'enhancement-mermaid-block-marker-line';
7 |
8 | // Initialise the mermaid API.
9 | mermaid.initialize({ startOnLoad: false })
10 |
11 | export default function mermaidRender(cm) {
12 | // Block Katex Math Render
13 | new CMBlockMarkerHelper(cm, null, /^\s*```mermaid\s*$/, /^\s*```\s*$/, (beginMatch, endMatch, content, fromLine, toLine) => {
14 | // code from zettlr
15 | let svg = document.createElement('span')
16 | svg.innerText = "..."
17 | svg.classList.add('mermaid-chart')
18 | mermaid.render(`graphDivL${fromLine}-L${toLine}${Date.now()}`, content).then((renderResult) => {
19 | svg.innerHTML = renderResult.svg
20 | }, (err) => {
21 | svg.classList.add('error')
22 | // TODO: Localise!
23 | svg.innerText = `Could not render Graph:\n\n${err.message as string}`
24 | })
25 | return svg;
26 | }, () => {
27 | const span = document.createElement('span');
28 | span.textContent = '===> Folded Mermaid Code Block <===';
29 | span.style.cssText = 'color: lightgray; font-size: smaller; font-style: italic;';
30 | return span;
31 | },ENHANCEMENT_MERMAID_SPAN_MARKER_CLASS, true);
32 |
33 | cm.on('renderLine', (editor, line: LineHandle, element: Element) => {
34 | if (element.getElementsByClassName(ENHANCEMENT_MERMAID_SPAN_MARKER_CLASS).length > 0) {
35 | element.classList.add(ENHANCEMENT_MERMAID_SPAN_MARKER_LINE_CLASS);
36 | }
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/src/driver/codemirror/mermaidRender/mermaid.css:
--------------------------------------------------------------------------------
1 | .CodeMirror-sizer .mermaid-chart div,
2 | .CodeMirror-sizer .mermaid-chart span {
3 | font-family: "trebuchet ms",verdana,arial,sans-serif !important;
4 | }
5 |
6 | .CodeMirror-sizer .mermaid-chart svg {
7 | max-height: 300px;
8 | }
9 |
10 | .enhancement-mermaid-block-marker-line {
11 | display: none;
12 | }
13 |
--------------------------------------------------------------------------------
/src/driver/codemirror/mode/index.ts:
--------------------------------------------------------------------------------
1 | export function initCodeMode(context, CodeMirror) {
2 | // FIXME(cm6): For now, not supported in CM6
3 | if (CodeMirror.cm6) {
4 | return;
5 | }
6 |
7 | CodeMirror.defineMode('pseudocode', function (config, modeConfig) {
8 | return CodeMirror.getMode(config, { name: 'stex', inMathMode: false });
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/src/driver/codemirror/overlay/bullet-list.css:
--------------------------------------------------------------------------------
1 | .cm-variable-2.cm-rm-single-list-token,
2 | .cm-variable-3.cm-rm-single-list-token,
3 | .cm-keyword.cm-rm-single-list-token {
4 | visibility: hidden;
5 | position: relative;
6 | }
7 |
8 | .cm-variable-2.cm-rm-single-list-token:after,
9 | .cm-variable-3.cm-rm-single-list-token:after,
10 | .cm-keyword.cm-rm-single-list-token:after {
11 | visibility: visible;
12 | content: '\2022';
13 | position: absolute;
14 | right: 0;
15 | }
16 |
--------------------------------------------------------------------------------
/src/driver/codemirror/overlay/bulletList.ts:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | default: function (_context) {
4 | return {
5 | plugin: function (CodeMirror) {
6 |
7 | },
8 | assets: function () {
9 | return [
10 | {
11 | name: "bullet-list.css"
12 | }
13 | ];
14 | }
15 | }
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/driver/codemirror/overlay/indent.ts:
--------------------------------------------------------------------------------
1 | // This module is modified from the CodeMirror indent wrap demo
2 | // https://codemirror.net/demo/indentwrap.html
3 |
4 | import {list_token_regex} from './index';
5 |
6 | // These variables are cached when the plugin is loaded
7 | // This stores the width of a space in the current font
8 | let spaceWidth = 0;
9 | // This stores the width of a monospace character using the current monospace font
10 | let monoSpaceWidth = 0;
11 | // This stores the width of the > character in the current font
12 | let blockCharWidth = 0;
13 |
14 | // Must be called when the editor is mounted
15 | export function calculateSpaceWidth(cm: any) {
16 | spaceWidth = charWidth(cm, ' ', '');
17 | monoSpaceWidth = charWidth(cm, ' ', 'cm-rm-monospace');
18 | blockCharWidth = charWidth(cm, '>', '');
19 | }
20 |
21 | // Adapted from codemirror/lib/codemirror.js
22 | function charWidth(cm: any, chr: string, cls: string) {
23 | let e = document.createElement('span');
24 | if (cls)
25 | e.classList.add(cls);
26 | e.style.whiteSpace = "pre-wrap";
27 | e.appendChild(document.createTextNode(chr.repeat(10)))
28 |
29 | const measure = cm.getWrapperElement().getElementsByClassName('CodeMirror-measure')[0];
30 | if (measure.firstChild)
31 | measure.removeChild(measure.firstChild);
32 |
33 | measure.appendChild(e);
34 |
35 | const rect = e.getBoundingClientRect()
36 | const width = (rect.right - rect.left) / 10;
37 |
38 | return width || cm.defaultCharWidth();
39 |
40 | }
41 |
42 | // Adapted from
43 | // https://github.com/codemirror/CodeMirror/blob/master/demo/indentwrap.html
44 | export function onRenderLine(cm: any, line: any, element: HTMLElement, CodeMirror: any) {
45 |
46 | const matches = line.text.match(list_token_regex);
47 | if (!matches) return;
48 |
49 | let off = CodeMirror.countColumn(line.text, matches[0].length, cm.getOption("tabSize")) * spaceWidth;
50 |
51 | // Special case handling for checkboxes with monospace enabled
52 | if (matches[0].indexOf('[') > 0) {
53 | // "- [ ] " is 6 characters
54 | off += monoSpaceWidth * 6 - spaceWidth * 6;
55 | } else if (matches[0].indexOf('>') >= 0) {
56 | off += blockCharWidth - spaceWidth;
57 | }
58 |
59 | element.style.textIndent = "-" + off + "px";
60 | element.style.paddingLeft = off + "px";
61 | }
62 |
--------------------------------------------------------------------------------
/src/driver/codemirror/overlay/overlay.css:
--------------------------------------------------------------------------------
1 | .cm-rm-monospace {
2 | font-family: monospace !important;
3 | }
4 | .cm-rm-ins {
5 | text-decoration: underline;
6 | }
7 | .cm-rm-sub {
8 | vertical-align: sub;
9 | font-size: smaller;
10 | }
11 | /*.cm-rm-sup {*/
12 | /* vertical-align: super;*/
13 | /* font-size: smaller;*/
14 | /*}*/
15 | .cm-rm-highlight {
16 | background-color: var(--joplin-search-marker-background-color);
17 | color: var(--joplin-search-marker-color);
18 | }
19 | .CodeMirror-selectedtext.cm-rm-highlight {
20 | background-color: #e5d3ce;
21 | }
22 | /* Needed for the renderLine indent hack to work */
23 | .CodeMirror pre > * { text-indent: 0px; }
24 |
25 | span.cm-overlay.cm-rm-todo-priority-1,
26 | span.cm-overlay.cm-rm-todo-priority-2,
27 | span.cm-overlay.cm-rm-todo-priority-3,
28 | span.cm-overlay.cm-rm-todo-priority-4 {
29 | padding: 1px 2px;
30 | border-radius: 4px;
31 | color: white !important;
32 | opacity: 0.75;
33 | }
34 |
35 | span.cm-overlay.cm-rm-todo-priority-1 {
36 | background: #e33e2f;
37 | }
38 |
39 | span.cm-overlay.cm-rm-todo-priority-2 {
40 | background: #f8d15f;
41 | }
42 |
43 | span.cm-overlay.cm-rm-todo-priority-3 {
44 | background: #549644;
45 | }
46 |
47 | span.cm-overlay.cm-rm-todo-priority-4 {
48 | background: #ececec;
49 | }
50 |
51 | span.cm-overlay.cm-rm-todo-date {
52 | background: #316cf4;
53 | padding: 1px 2px;
54 | border-radius: 4px;
55 | color: white !important;
56 | opacity: 0.75;
57 | }
58 |
59 | span.cm-overlay.cm-rm-todo-tag {
60 | background: #5dc7ec;
61 | padding: 1px 2px;
62 | border-radius: 4px;
63 | color: white !important;
64 | opacity: 0.75;
65 | }
66 |
67 | span.cm-overlay.cm-rm-todo-project {
68 | background: #f6c344;
69 | padding: 1px 2px;
70 | border-radius: 4px;
71 | color: white !important;
72 | opacity: 0.75;
73 | }
74 |
--------------------------------------------------------------------------------
/src/driver/codemirror/quickCommands/DateHints.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs';
2 |
3 |
4 | export function getDateHints(dateFormat: string) {
5 | return [
6 | {
7 | text: dayjs().format(dateFormat),
8 | displayText: '/today',
9 | description: 'Today',
10 | inline: true
11 | },
12 | {
13 | text: dayjs().format(`${dateFormat} HH:mm:ss`),
14 | displayText: '/now',
15 | description: 'Now',
16 | inline: true
17 | },
18 | {
19 | text: dayjs().add(1, 'day').format(dateFormat),
20 | displayText: '/tomorrow',
21 | description: 'Tomorrow',
22 | inline: true
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/driver/codemirror/quickCommands/MermaidHints.ts:
--------------------------------------------------------------------------------
1 | import {Hint} from "./quickCommands";
2 |
3 | const MermaidHints: Hint[] = [
4 | {
5 | text: '```mermaid\n' +
6 | 'graph\n' +
7 | '\n' +
8 | '```',
9 | displayText: '/flowchart',
10 | description: 'Mermaid flowchart',
11 | inline: false
12 | },
13 | {
14 | text: '```mermaid\n' +
15 | 'sequenceDiagram\n' +
16 | '\n' +
17 | '```',
18 | displayText: '/sequenceDiagram',
19 | description: 'Mermaid sequence diagram',
20 | inline: false
21 | },
22 | {
23 | text: '```mermaid\n' +
24 | 'gantt\n' +
25 | '\n' +
26 | '```',
27 | displayText: '/gantt',
28 | description: 'Mermaid gantt diagram',
29 | inline: false
30 | },
31 | {
32 | text: '```mermaid\n' +
33 | 'classDiagram\n' +
34 | '\n' +
35 | '```',
36 | displayText: '/classDiagram',
37 | description: 'Mermaid class diagram',
38 | inline: false
39 | },
40 | {
41 | text: '```mermaid\n' +
42 | 'erDiagram\n' +
43 | '\n' +
44 | '```',
45 | displayText: '/erDiagram',
46 | description: 'Mermaid entity relationship diagram',
47 | inline: false
48 | },
49 | {
50 | text: '```mermaid\n' +
51 | 'journey\n' +
52 | '\n' +
53 | '```',
54 | displayText: '/journey',
55 | description: 'Mermaid journey diagram',
56 | inline: false
57 | }
58 | ]
59 |
60 | export default MermaidHints;
61 |
--------------------------------------------------------------------------------
/src/driver/codemirror/quickCommands/PlantumlHints.ts:
--------------------------------------------------------------------------------
1 | import { Hint } from "./quickCommands";
2 |
3 | const sequanceDiagramText = () => {
4 | return '```plantuml\n' +
5 | '@startuml diagramName\n' +
6 | 'participant fullName as name\n' +
7 | 'participant another\n\n\n' +
8 |
9 | 'group group_name\n' +
10 | ' name -> another: some description\n' +
11 | ' another -> name: some description \n' +
12 | 'end\n' +
13 | '@enduml\n' +
14 | '```'
15 | };
16 |
17 |
18 | const classDiagramText = () => {
19 | return '```plantuml\n' +
20 | '@startuml\n' +
21 | 'interface Store {\n' +
22 | ' List getProducts\n' +
23 | '}\n' +
24 | 'Store *-- Product\n\n' +
25 |
26 | 'class LintaoStore implements Store\n' +
27 | 'interface Product\n' +
28 | 'class Computer implements Product\n' +
29 | 'class Phone implements Product\n' +
30 | '@enduml\n' +
31 | '```'
32 | }
33 |
34 | const PlantumlHints: Hint[] = [
35 | {
36 | text: sequanceDiagramText(),
37 | displayText: "/psequenceDiagram",
38 | description: 'Sequence diagram | plantuml',
39 | inline: false
40 | },
41 | {
42 | text: classDiagramText(),
43 | displayText: "/pclassDiagramText",
44 | description: 'Class diagram | plantuml',
45 | inline: false
46 | }
47 | ];
48 |
49 | export default PlantumlHints;
50 |
51 |
--------------------------------------------------------------------------------
/src/driver/codemirror/quickCommands/ShortTypeHints.ts:
--------------------------------------------------------------------------------
1 | import { Hint } from "./quickCommands";
2 |
3 | const ShortTypeHints: Hint[] = [
4 | {
5 | text: '- [ ] ',
6 | displayText: '/task',
7 | description: 'Checkbox',
8 | inline: true
9 | },
10 | {
11 | text: '``',
12 | displayText: '/inline',
13 | description: 'Inline Code',
14 | inline: true
15 | },
16 | {
17 | text: '```\n' +
18 | '\n' +
19 | '```',
20 | displayText: '/code',
21 | description: 'Codeblock',
22 | inline: false
23 | }
24 | ]
25 |
26 | export default ShortTypeHints;
27 |
--------------------------------------------------------------------------------
/src/driver/codemirror/quickCommands/quickCommands.css:
--------------------------------------------------------------------------------
1 | /* from https://github.com/ylc395/joplin-plugin-note-link-system/blob/main/src/driver/codeMirror/style.css */
2 |
3 | .CodeMirror-hints {
4 | position: absolute;
5 | z-index: 10;
6 | overflow: hidden;
7 | list-style: none;
8 |
9 | margin: 0;
10 | padding: 2px;
11 |
12 | -webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
13 | -moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
14 | box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
15 | border-radius: 3px;
16 | border: 1px solid silver;
17 |
18 | background: var(--joplin-background-color);
19 | font-size: 90%;
20 | font-family: monospace;
21 |
22 | max-height: 20em;
23 | overflow-y: auto;
24 | }
25 |
26 | .quick-commands-hint.CodeMirror-hint {
27 | margin: 0;
28 | padding: 0 4px;
29 | border-radius: 2px;
30 | white-space: pre;
31 | color: var(--joplin-color);
32 | cursor: pointer;
33 | }
34 |
35 | .quick-commands-hint.CodeMirror-hint-active {
36 | background: var(--joplin-raised-background-color);
37 | color: var(--joplin-raised-color);
38 | }
39 |
40 | .quick-commands-hint-path {
41 | margin-left: 1em;
42 | font-size: 0.8em;
43 | color: var(--joplin-color-faded);
44 | }
45 |
--------------------------------------------------------------------------------
/src/driver/codemirror/searchReplace/index.ts:
--------------------------------------------------------------------------------
1 | import {searchReplace} from "./search";
2 |
3 | module.exports = {
4 | default: function(_context) {
5 | return {
6 | plugin: function (CodeMirror) {
7 | searchReplace(CodeMirror);
8 | },
9 | codeMirrorOptions: { 'searchbox': true },
10 | codeMirrorResources: [
11 | 'addon/search/searchcursor',
12 | 'addon/scroll/annotatescrollbar',
13 | 'addon/search/matchesonscrollbar'
14 | ],
15 | assets: function() {
16 | return [];
17 | }
18 | }
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/src/driver/codemirror/tableFormatter/index.ts:
--------------------------------------------------------------------------------
1 | import {navigateCell, insertColumn, insertRow, deleteColume} from "./tableCommands";
2 | import TableFormatterBridge from "./tableFormatterBridge";
3 |
4 |
5 | module.exports = {
6 | default: function(_context) {
7 | return {
8 | plugin: function (CodeMirror) {
9 | CodeMirror.defineOption("tableFormatter", [], async function(cm, val, old) {
10 | if (old && old != CodeMirror.Init) {
11 | cm.off('keyup', tabPressed);
12 | }
13 | cm.on('keyup', tabPressed);
14 |
15 | const commandBridge = new TableFormatterBridge(cm);
16 | CodeMirror.defineExtension('insertColumnLeft', commandBridge.insertColumnLeft.bind(commandBridge));
17 | CodeMirror.defineExtension('insertColumnRight', commandBridge.insertColumnRight.bind(commandBridge));
18 | CodeMirror.defineExtension('insertRowAbove', commandBridge.insertRowAbove.bind(commandBridge));
19 | CodeMirror.defineExtension('insertRowBelow', commandBridge.insertRowBelow.bind(commandBridge));
20 | CodeMirror.defineExtension('deleteColumn', commandBridge.deleteColumn.bind(commandBridge));
21 | CodeMirror.defineExtension('alignColumns', commandBridge.alignColumnsCommand.bind(commandBridge));
22 | });
23 |
24 | function tabPressed(cm, event) {
25 | if (event.code === 'Tab' && !event.shiftKey) {
26 | navigateCell(cm, true, false);
27 | } else if (event.code === 'Tab' && event.shiftKey) {
28 | navigateCell(cm, true, true);
29 | } else if (event.shiftKey && event.ctrlKey && !event.metaKey) {
30 | switch (event.code) {
31 | case 'ArrowLeft':
32 | insertColumn(cm, true);
33 | break;
34 | case 'ArrowRight':
35 | insertColumn(cm, false);
36 | break;
37 | case 'ArrowUp':
38 | insertRow(cm, true);
39 | break;
40 | case 'ArrowDown':
41 | insertRow(cm, false);
42 | break;
43 | case 'Backspace':
44 | deleteColume(cm);
45 | break;
46 | default:break;
47 | }
48 | }
49 | }
50 | },
51 | codeMirrorOptions: { 'tableFormatter': true },
52 | assets: function() {
53 | return [ ];
54 | }
55 | }
56 | },
57 | }
58 |
--------------------------------------------------------------------------------
/src/driver/codemirror/tableFormatter/markdownTableData.ts:
--------------------------------------------------------------------------------
1 | // implemented by takumisoft68 at https://github.com/takumisoft68/vscode-markdown-table/blob/master/src/markdownTableData.ts
2 |
3 | export default class MarkdownTableData {
4 | public readonly originalText: string;
5 | public readonly aligns: [string, string][];
6 | public readonly columns: string[];
7 | public readonly cells: string[][];
8 | public readonly leftovers: string[];
9 | public readonly indent: string;
10 |
11 | constructor(_text: string, _aligns: [string, string][], _columns: string[], _cells: string[][], _leftovers: string[], _indent: string) {
12 | this.originalText = _text;
13 | this.aligns = _aligns;
14 | this.columns = _columns;
15 | this.cells = _cells;
16 | this.leftovers = _leftovers;
17 | this.indent = _indent;
18 | }
19 | };
--------------------------------------------------------------------------------
/src/driver/codemirror/tableFormatter/markdownTableUtility.ts:
--------------------------------------------------------------------------------
1 | // implemented by takumisoft68 at https://github.com/takumisoft68/vscode-markdown-table/blob/master/src/markdownTableUtility.ts
2 |
3 | /**
4 | * 1行分の文字列を列データ配列に分解する
5 | * 指定した列数に満たない行は 埋め文字 で埋める
6 | * @param linestr 1行分の文字列
7 | * @param columnNum 列数
8 | * @param fillstr 埋め文字
9 | */
10 | export function splitline(linestr: string, columnNum: number, fillstr: string = '') {
11 | // 先頭と末尾の|を削除
12 | linestr = linestr.trim();
13 | if (linestr.startsWith('|')) {
14 | linestr = linestr.slice(1);
15 | }
16 | if (linestr.endsWith('|')) {
17 | linestr = linestr.slice(0, -1);
18 | }
19 |
20 | // |で分割
21 | let linedatas: string[] = [];
22 | let startindex = 0;
23 | let endindex = 0;
24 | let isEscaping = false;
25 | let isInInlineCode = false;
26 | for (let i = 0; i < linestr.length; ++i) {
27 | if (isEscaping) {
28 | // エスケープ文字の次の文字は|かどうか判定しない
29 | isEscaping = false;
30 | endindex++;
31 | continue;
32 | }
33 |
34 | const chara = linestr.charAt(i);
35 | if (chara === '\`') {
36 | // `の間はインラインコード
37 | isInInlineCode = !isInInlineCode;
38 | endindex++;
39 | continue;
40 | }
41 | if (isInInlineCode) {
42 | // インラインコード中は|かどうか判定しない
43 | endindex++;
44 | continue;
45 | }
46 |
47 | if (chara === '\\') {
48 | // \はエスケープ文字
49 | isEscaping = true;
50 | endindex++;
51 | continue;
52 | }
53 |
54 | if (chara !== '|') {
55 | // | 以外だったら継続
56 | endindex++;
57 | continue;
58 | }
59 |
60 | // | だったら分割
61 | let cellstr = linestr.slice(startindex, endindex);
62 | linedatas.push(cellstr);
63 | startindex = i + 1;
64 | endindex = i + 1;
65 | }
66 | linedatas.push(linestr.slice(startindex));
67 |
68 | // データ数分を''で埋めておく
69 | let datas: string[] = new Array(columnNum).fill(fillstr);
70 | // 行文字列から取得したデータに置き換える
71 | for (let i = 0; i < linedatas.length; i++) {
72 | datas[i] = linedatas[i];
73 | }
74 | return datas;
75 | };
76 |
77 |
78 |
79 | // 半角文字は1文字、全角文字は2文字として文字数をカウントする
80 | export function getLen(str: string): number {
81 | let length = 0;
82 | for (let i = 0; i < str.length; i++) {
83 | let chp = str.codePointAt(i);
84 | if (chp === undefined) {
85 | continue;
86 | }
87 | let chr = chp as number;
88 | if (doesUse0Space(chr)) {
89 | length += 0;
90 | }
91 | else if (doesUse3Spaces(chr)) {
92 | length += 3;
93 | }
94 | else if (doesUse2Spaces(chr)) {
95 | // 全角文字の場合は2を加算
96 | length += 2;
97 | }
98 | else {
99 | //それ以外の文字の場合は1を加算
100 | length += 1;
101 | }
102 |
103 | let chc = str.charCodeAt(i);
104 | if (chc >= 0xD800 && chc <= 0xDBFF) {
105 | // サロゲートペアの時は1文字読み飛ばす
106 | i++;
107 | }
108 |
109 | // if( (chr >= 0x00 && chr <= 0x80) ||
110 | // (chr >= 0xa0 && chr <= 0xff) ||
111 | // (chr === 0xf8f0) ||
112 | // (chr >= 0xff61 && chr <= 0xff9f) ||
113 | // (chr >= 0xf8f1 && chr <= 0xf8f3)){
114 | // //半角文字の場合は1を加算
115 | // length += 1;
116 | // }else{
117 | // //それ以外の文字の場合は2を加算
118 | // length += 2;
119 | // }
120 | }
121 | //結果を返す
122 | return length;
123 | };
124 |
125 | function doesUse0Space(charCode: number): boolean {
126 | if ((charCode === 0x02DE) ||
127 | (charCode >= 0x0300 && charCode <= 0x036F) ||
128 | (charCode >= 0x0483 && charCode <= 0x0487) ||
129 | (charCode >= 0x0590 && charCode <= 0x05CF)) {
130 | return true;
131 | }
132 | return false;
133 | }
134 |
135 | function doesUse2Spaces(charCode: number): boolean {
136 | if ((charCode >= 0x2480 && charCode <= 0x24FF) ||
137 | (charCode >= 0x2600 && charCode <= 0x27FF) ||
138 | (charCode >= 0x2900 && charCode <= 0x2CFF) ||
139 | (charCode >= 0x2E00 && charCode <= 0xFF60) ||
140 | (charCode >= 0xFFA0)) {
141 | return true;
142 | }
143 | return false;
144 | }
145 |
146 | function doesUse3Spaces(charCode: number): boolean {
147 | if (charCode >= 0x1F300 && charCode <= 0x1FBFF) {
148 | return true;
149 | }
150 | return false;
151 | }
152 |
--------------------------------------------------------------------------------
/src/driver/codemirror/tableFormatter/tableFormatterBridge.ts:
--------------------------------------------------------------------------------
1 | import {alignColumns, deleteColume, insertColumn, insertRow} from "./tableCommands";
2 |
3 | export default class TableFormatterBridge {
4 | constructor(private readonly editor) {}
5 | private readonly doc = this.editor.getDoc();
6 | insertColumnLeft() {
7 | insertColumn(this.editor, true);
8 | }
9 |
10 | insertColumnRight() {
11 | insertColumn(this.editor, false);
12 | }
13 |
14 | insertRowAbove() {
15 | insertRow(this.editor, true);
16 | }
17 |
18 | insertRowBelow() {
19 | insertRow(this.editor, false);
20 | }
21 |
22 | deleteColumn() {
23 | deleteColume(this.editor);
24 | }
25 |
26 | alignColumnsCommand(options) {
27 | alignColumns(this.editor, options);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { taskAndHeaderRender as v5TaskAndHeaderRenderer } from './v5';
3 | import v6TaskAndHeaderRenderer from './v6';
4 |
5 | const taskAndHeaderRenderer = (cm: any) => {
6 | if (cm.cm6) {
7 | cm.addExtension(v6TaskAndHeaderRenderer());
8 | } else {
9 | v5TaskAndHeaderRenderer(cm);
10 | }
11 | };
12 |
13 | export default taskAndHeaderRenderer;
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/taskRender.css:
--------------------------------------------------------------------------------
1 | /* For CodeMirror 6 */
2 | input.cm-enhancement-checkbox {
3 | cursor: pointer;
4 | }
5 |
6 | /* from zettlr */
7 | .CodeMirror-sizer input[type="checkbox"],
8 | input.cm-enhancement-checkbox {
9 | vertical-align: middle;
10 | margin: 4px 0.6em 4px 0px;
11 | width: 1.4em;
12 | height: 1.4em;
13 | appearance: none;
14 | border: 1px solid #acacac;
15 | border-radius: 1.4em;
16 | }
17 |
18 | /* from obsidian */
19 | .CodeMirror-sizer input[type="checkbox"]:checked,
20 | input.cm-enhancement-checkbox:checked {
21 | background-color: dodgerblue;
22 | border: 1px solid transparent;
23 | background-position: 44% 55%;
24 | background-size: 70%;
25 | background-repeat: no-repeat;
26 | background-image: url('data:image/svg+xml; utf8, ');
27 | }
28 |
29 | .heading-tag {
30 | display: inline-block;
31 | cursor: pointer;
32 | border-radius: 3px;
33 | padding: 0 2px;
34 | transition: 0.2s all ease;
35 | color: var(--joplin-color);
36 | margin-left: -1.2em;
37 | }
38 |
39 | .heading-tag:hover {
40 | color: #787881;
41 | background-color: #dcdcdc;
42 | }
43 |
44 | div.application-menu {
45 | background-color: var(--joplin-background-color);
46 | border: 0.5px solid rgb(180, 180, 180);
47 | border-radius: 6px;
48 | max-width: 110px;
49 | position: fixed;
50 | overflow-y: auto;
51 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
52 | font-size: 14px;
53 | box-shadow: 5px 5px 15px 0px rgba(0, 0, 0, .3);
54 | min-width: 50px;
55 | }
56 |
57 | div.application-menu div.menu-item {
58 | height: 20px;
59 | line-height: 20px;
60 | overflow: hidden;
61 | margin: 5px;
62 | padding: 0 5px;
63 | display: grid;
64 | grid-template-columns: 20px auto 50px;
65 | grid-template-rows: 100%;
66 | grid-template-areas: "status label after";
67 | }
68 |
69 | div.application-menu div.menu-item:not(.separator):not(.disabled):hover {
70 | background-color: var(--joplin-selected-color);
71 | border-radius: 4px;
72 | }
73 |
74 | div.application-menu div.menu-item div.status {
75 | grid-area: status;
76 | text-align: center;
77 | }
78 |
79 | div.application-menu div.menu-item div.after-element {
80 | grid-area: after;
81 | text-align: right;
82 | }
83 |
84 | div.application-menu div.menu-item div.label {
85 | grid-area: label;
86 | text-align: left;
87 | }
88 |
89 | div.application-menu div.menu-item.separator {
90 | height: 1px;
91 | border-bottom: 1px solid var(--joplin-divider-color);
92 | }
93 |
94 | .cm-enhancement-finished-task {
95 | text-decoration: line-through;
96 | opacity: 0.85;
97 | }
98 |
99 | .table-helper {
100 | border: 1px solid #e6e6e6;
101 | }
102 |
103 | .table-helper tr:nth-child(odd) {
104 | background-color: #f4f5f5;
105 | }
106 |
107 | table.table-helper td {
108 | padding: 6px;
109 | border: 1px solid #E6E6E6;
110 | }
111 |
112 | table.table-helper tr p {
113 | margin: 0;
114 | }
115 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/index.ts:
--------------------------------------------------------------------------------
1 | import {markdownRenderTasks} from "./taskRender";
2 | import {markdownRenderHTags} from "./render-h-tags";
3 | import {markdownRenderTables} from "./render-tables";
4 | import {debounce} from "ts-debounce";
5 |
6 |
7 | export function taskAndHeaderRender(cm) {
8 | // While taskHandle is undefined, there's no task scheduled. Else, there is.
9 | let taskHandle: number|undefined
10 |
11 | const callback = function (cm: CodeMirror.Editor): void {
12 | if (taskHandle !== undefined) {
13 | return // Already a task registered
14 | }
15 |
16 | debounce(() => {
17 | renderElements(cm, true);
18 | }, 500)();
19 | }
20 |
21 | cm.on('change', async function (cm, changeObjs) {
22 | if (changeObjs.origin === 'setValue') {
23 | renderElements(cm, false);
24 | }
25 | });
26 |
27 | cm.on('cursorActivity', callback)
28 | cm.on('viewportChange', callback) // renderElements)
29 | cm.on('optionChange', callback)
30 |
31 | callback(cm);
32 | }
33 |
34 | function renderElements (cm: CodeMirror.Editor, viewPort: boolean): void {
35 | if (cm.state.enhancement) {
36 | if (cm.state.enhancement.settings.taskCmRender) {
37 | markdownRenderTasks(cm, viewPort);
38 | }
39 |
40 | if (cm.state.enhancement.settings.headerHashRender) {
41 | markdownRenderHTags(cm, viewPort);
42 | }
43 |
44 | if (cm.state.enhancement.settings.tableCmRender) {
45 | markdownRenderTables(cm);
46 | }
47 | } else {
48 | markdownRenderTasks(cm, viewPort);
49 | markdownRenderHTags(cm, viewPort);
50 | markdownRenderTables(cm);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/render-h-tags.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: Heading tags Plugin
6 | * CVM-Role: CodeMirror Plugin
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This plugin renders Bear-style heading indicators.
11 | *
12 | * END HEADER
13 | */
14 |
15 | import CodeMirror, { commands } from 'codemirror'
16 | import { getHeadRE } from '../../../../utils/regular-expressions'
17 | import showPopupMenu from './window-register/application-menu-helper'
18 | import canRenderElement from '../../../../utils/can-render-element'
19 |
20 | const headRE = getHeadRE()
21 |
22 | export function markdownRenderHTags (cm: CodeMirror.Editor, viewPort: boolean): void {
23 | let match
24 |
25 | let fromLine = 0;
26 | let toLine = Math.min(60, cm.lineCount());
27 |
28 | if (viewPort) {
29 | // We'll only renderer the viewport
30 | const viewport = cm.getViewport()
31 | fromLine = viewport.from;
32 | toLine = viewport.to;
33 | }
34 | for (let i = fromLine; i < toLine; i++) {
35 | if (cm.getModeAt({ 'line': i, 'ch': 0 }).name !== 'markdown') continue
36 | // Always reset lastIndex property, because test()-ing on regular
37 | // expressions advances it.
38 | headRE.lastIndex = 0
39 |
40 | // First get the line and test if the contents contain a link
41 | let line = cm.getLine(i)
42 | if ((match = headRE.exec(line)) == null) {
43 | continue
44 | }
45 |
46 | const headingLevel = match[1].length
47 |
48 | const curFrom = { line: i, ch: 0 }
49 | const curTo = { line: i, ch: headingLevel }
50 |
51 | if (!canRenderElement(cm, curFrom, curTo, !viewPort)) {
52 | continue
53 | }
54 |
55 | const hTagWrapper = document.createElement('div')
56 | hTagWrapper.className = 'heading-tag'
57 |
58 | const hTag = document.createElement('span')
59 | hTag.textContent = `h${headingLevel}`
60 | hTagWrapper.appendChild(hTag)
61 |
62 | const textMarker = cm.markText(
63 | curFrom, curTo,
64 | {
65 | 'clearOnEnter': true,
66 | 'replacedWith': hTagWrapper,
67 | 'inclusiveLeft': false,
68 | 'inclusiveRight': false
69 | }
70 | )
71 |
72 | // Display a small menu to change the heading level on click
73 | hTagWrapper.onclick = (e) => {
74 | // Prevent bubbling because otherwise the context menu will be hidden
75 | // again immediately due to the event bubbling upwards to the window.
76 | e.stopPropagation()
77 |
78 | const items = [
79 | {
80 | id: '1',
81 | label: '#',
82 | type: 'checkbox',
83 | enabled: !cm.isReadOnly(),
84 | checked: headingLevel === 1
85 | },
86 | {
87 | id: '2',
88 | label: '##',
89 | type: 'checkbox',
90 | enabled: !cm.isReadOnly(),
91 | checked: headingLevel === 2
92 | },
93 | {
94 | id: '3',
95 | label: '###',
96 | type: 'checkbox',
97 | enabled: !cm.isReadOnly(),
98 | checked: headingLevel === 3
99 | },
100 | {
101 | id: '4',
102 | label: '####',
103 | type: 'checkbox',
104 | enabled: !cm.isReadOnly(),
105 | checked: headingLevel === 4
106 | },
107 | {
108 | id: '5',
109 | label: '#####',
110 | type: 'checkbox',
111 | enabled: !cm.isReadOnly(),
112 | checked: headingLevel === 5
113 | },
114 | {
115 | id: '6',
116 | label: '######',
117 | type: 'checkbox',
118 | enabled: !cm.isReadOnly(),
119 | checked: headingLevel === 6
120 | }
121 | ]
122 |
123 | const point = { x: e.clientX, y: e.clientY }
124 | showPopupMenu(point, items as any, (id) => {
125 | const newLevel = parseInt(id, 10)
126 |
127 | const markerRange = textMarker.find()
128 | if (markerRange === undefined) {
129 | return
130 | }
131 |
132 | // The heading might have changed position in the meantime
133 | const { from, to } = markerRange
134 | cm.replaceRange('#'.repeat(newLevel), from, to)
135 | textMarker.clear()
136 | // Programmatically trigger a cursor movement ...
137 | cm.setCursor({ line: from.line, ch: newLevel + 1 })
138 | // ... and re-focus the editor
139 | cm.focus()
140 | })
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/renderer/context.ts:
--------------------------------------------------------------------------------
1 | export interface CheckboxRadioItem {
2 | id: string
3 | label: string
4 | accelerator?: string
5 | type: 'checkbox'|'radio'
6 | enabled: boolean
7 | checked: boolean
8 | }
9 |
10 | export interface SeparatorItem {
11 | type: 'separator'
12 | }
13 |
14 | export interface SubmenuItem {
15 | id: string
16 | label: string
17 | type: 'submenu'
18 | enabled: boolean
19 | submenu: Array
20 | }
21 |
22 | export interface NormalItem {
23 | id: string
24 | label: string
25 | accelerator?: string
26 | type: 'normal'
27 | enabled: boolean
28 | }
29 |
30 | export type AnyMenuItem = CheckboxRadioItem | SeparatorItem | SubmenuItem | NormalItem
31 |
32 | // Any menu item w/o separators
33 | export type InteractiveMenuItem = CheckboxRadioItem | SubmenuItem | NormalItem
34 |
35 | export interface Rect {
36 | top: number
37 | left: number
38 | width: number
39 | height: number
40 | }
41 |
42 | export interface Point {
43 | x: number
44 | y: number
45 | }
46 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/build-grid.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: TableHelper utility function
6 | * CVM-Role: Utility
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: Transforms an AST to a grid table.
11 | * Cf. https://pandoc.org/MANUAL.html#tables
12 | *
13 | * END HEADER
14 | */
15 |
16 | import calculateColSizes from './calculate-col-sizes'
17 | import { ColAlignment } from './types'
18 |
19 | export default function buildGridTable (ast: string[][], colAlignment: ColAlignment[]): string {
20 | const colSizes = calculateColSizes(ast)
21 | let separatorRow = colSizes.map(elem => '-'.repeat(elem + 2)).join('+')
22 | separatorRow = '+' + separatorRow + '+\n'
23 |
24 | // First add a beginning row
25 | let markdownTable = separatorRow
26 |
27 | for (let i = 0; i < ast.length; i++) {
28 | for (let j = 0; j < ast[i].length; j++) {
29 | if (j === 0) markdownTable += '|'
30 | // Pad the text contents to fill up to the maximum chars
31 | switch (colAlignment[j]) {
32 | case 'left':
33 | case 'center':
34 | markdownTable += ` ${ast[i][j].padEnd(colSizes[j], ' ')} |`
35 | break
36 | case 'right':
37 | markdownTable += ` ${ast[i][j].padStart(colSizes[j], ' ')} |`
38 | break
39 | }
40 | }
41 |
42 | markdownTable += '\n'
43 |
44 | // First row is the header, so add a secondary row.
45 | if (i === 0) {
46 | markdownTable += '+'
47 | for (let k = 0; k < colSizes.length; k++) {
48 | // Respect the spaces left and right and account for alignment
49 | switch (colAlignment[k]) {
50 | case 'left':
51 | markdownTable += '='.repeat(colSizes[k] + 2) + '+'
52 | break
53 | case 'center':
54 | markdownTable += ':' + '='.repeat(colSizes[k]) + ':+'
55 | break
56 | case 'right':
57 | markdownTable += '='.repeat(colSizes[k] + 1) + ':+'
58 | break
59 | }
60 | }
61 | markdownTable += '\n'
62 | } else {
63 | // Add separators after each line
64 | markdownTable += separatorRow
65 | }
66 | }
67 |
68 | return markdownTable
69 | }
70 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/build-pipe.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: TableHelper utility function
6 | * CVM-Role: Utility
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This parser transforms pipe tables as specified
11 | * in the Pandoc manual into an AST and returns both
12 | * that and the column alignments.
13 | * Cf. https://pandoc.org/MANUAL.html#tables
14 | *
15 | * END HEADER
16 | */
17 |
18 | import calculateColSizes from './calculate-col-sizes'
19 | import { ColAlignment } from './types'
20 |
21 | export default function buildPipeTable (ast: string[][], colAlignment: ColAlignment[]): string {
22 | const colSizes = calculateColSizes(ast)
23 | let markdownTable = ''
24 | // Now build from AST
25 | for (let i = 0; i < ast.length; i++) {
26 | for (let j = 0; j < ast[i].length; j++) {
27 | if (j === 0) markdownTable += '|'
28 | // Pad the text contents to fill up to the maximum chars
29 | switch (colAlignment[j]) {
30 | case 'left':
31 | case 'center':
32 | markdownTable += ` ${ast[i][j].padEnd(colSizes[j], ' ')} |`
33 | break
34 | case 'right':
35 | markdownTable += ` ${ast[i][j].padStart(colSizes[j], ' ')} |`
36 | break
37 | }
38 | }
39 |
40 | markdownTable += '\n'
41 |
42 | // First row is the header, so add a secondary row.
43 | if (i === 0) {
44 | markdownTable += '|'
45 | for (let k = 0; k < colSizes.length; k++) {
46 | // Respect the spaces left and right and account for alignment
47 | switch (colAlignment[k]) {
48 | case 'left':
49 | markdownTable += '-'.repeat(colSizes[k] + 2) + '|'
50 | break
51 | case 'center':
52 | markdownTable += ':' + '-'.repeat(colSizes[k]) + ':|'
53 | break
54 | case 'right':
55 | markdownTable += '-'.repeat(colSizes[k] + 1) + ':|'
56 | break
57 | }
58 | }
59 | markdownTable += '\n'
60 | }
61 | }
62 |
63 | return markdownTable
64 | }
65 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/build-simple.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: TableHelper utility function
6 | * CVM-Role: Utility
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This parser transforms an AST into a simple Table.
11 | * Cf. https://pandoc.org/MANUAL.html#tables
12 | *
13 | * END HEADER
14 | */
15 |
16 | import calculateColSizes from './calculate-col-sizes'
17 | import { ColAlignment } from './types'
18 |
19 | export default function buildSimpleTable (ast: string[][], colAlignment: ColAlignment[]): string {
20 | const colSizes = calculateColSizes(ast)
21 | let markdownTable = ''
22 | // Now build from AST
23 | for (let i = 0; i < ast.length; i++) {
24 | for (let j = 0; j < ast[i].length; j++) {
25 | // Pad the text contents to fill up to the maximum chars
26 | let toFill = colSizes[j] - ast[i][j].length
27 | let start = Math.round(toFill / 2)
28 | let end = toFill - start
29 | start += 1
30 | switch (colAlignment[j]) {
31 | case 'left':
32 | markdownTable += ast[i][j].padEnd(colSizes[j] + 2, ' ')
33 | break
34 | case 'center':
35 | markdownTable += ' '.repeat(start) + ast[i][j] + ' '.repeat(end + 1)
36 | break
37 | case 'right':
38 | markdownTable += ast[i][j].padStart(colSizes[j] + 2, ' ')
39 | break
40 | }
41 |
42 | // Pad with one space
43 | if (j < ast[i].length - 1) markdownTable += ' '
44 | }
45 |
46 | markdownTable += '\n'
47 |
48 | // First row is the header, so add a secondary row.
49 | if (i === 0) {
50 | let r = colSizes.map(elem => '-'.repeat(elem + 2))
51 | markdownTable += r.join(' ') + '\n'
52 | }
53 | }
54 |
55 | return markdownTable
56 | }
57 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/calculate-col-sizes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: Utility function
6 | * CVM-Role: Utility
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This function takes a table AST and calculates, for each
11 | * column, the maximum amount of characters.
12 | *
13 | * END HEADER
14 | */
15 |
16 | /**
17 | * Calculates the maximum size (characters) of each column of a table AST
18 | *
19 | * @param {string[][]} ast The AST
20 | *
21 | * @return {number[]} The maximum sizes for all columns
22 | */
23 | export default function calculateColSizes (ast: string[][]): number[] {
24 | const sizes = []
25 | for (let col = 0; col < ast[0].length; col++) {
26 | let colSize = 0
27 | for (let row = 0; row < ast.length; row++) {
28 | if (ast[row][col].length > colSize) {
29 | colSize = ast[row][col].length
30 | }
31 | }
32 | sizes.push(colSize)
33 | }
34 | return sizes
35 | }
36 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: TableEditor
6 | * CVM-Role: Model
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This class models Markdown tables using an internal AST and
11 | * enables easy WYSIWYG-style editing of tables.
12 | *
13 | * END HEADER
14 | */
15 |
16 | import TableEditor from './table-editor'
17 | import parsePipeTable from './parse-pipe'
18 | import parseSimpleTable from './parse-simple'
19 | import parseGridTable from './parse-grid'
20 | import { TableEditorOptions } from './types'
21 |
22 | export default function fromMarkdown (markdownTable: string, potentialType: 'pipe'|'simple'|'grid' = 'pipe', hooks: TableEditorOptions = {}): TableEditor {
23 | let parsed
24 | switch (potentialType) {
25 | case 'simple':
26 | parsed = parseSimpleTable(markdownTable)
27 | break
28 | case 'grid':
29 | parsed = parseGridTable(markdownTable)
30 | break
31 | default:
32 | parsed = parsePipeTable(markdownTable)
33 | break
34 | }
35 |
36 | // Now parse the whole thing into the table editor.
37 | const editor = new TableEditor(parsed.ast, parsed.colAlignments, potentialType, hooks)
38 |
39 | return editor
40 | }
41 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/parse-grid.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: TableHelper utility function
6 | * CVM-Role: Utility
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This parser transforms grid tables as specified
11 | * in the Pandoc manual into an AST and returns both
12 | * that and the column alignments.
13 | * Cf. https://pandoc.org/MANUAL.html#tables
14 | *
15 | * END HEADER
16 | */
17 |
18 | import { ParsedTable, ColAlignment } from './types'
19 |
20 | /**
21 | * Parses a grid table
22 | * @param {String|Array} A markdown table, either as line array or string
23 | * @returns {Object} An object with properties ast and colAlignments
24 | */
25 | export default function parseGridTable (markdownTable: string|string[]): ParsedTable {
26 | // First remove whitespace from both sides of the table, e.g. in case
27 | // a trailing newline is present
28 | if (typeof markdownTable === 'string') markdownTable = markdownTable.trim()
29 | if (!Array.isArray(markdownTable)) markdownTable = markdownTable.split('\n')
30 |
31 | if (markdownTable.length === 0 || (markdownTable.length === 1 && markdownTable[0].trim() === '')) {
32 | throw new Error('MarkdownTable was empty!')
33 | }
34 |
35 | const ast: string[][] = []
36 | const colAlignments: ColAlignment[] = [] // One-dimensional column alignments
37 | let numColumns = 0 // If there is an uneven number of columns, throw an error.
38 |
39 | // The speciality of grid tables is that they can contain the alignment info
40 | // either in the first row, or in the second separator row. So we have to find
41 | // the alignment prior to iterating over the rows.
42 | let headerRow = 0
43 | for (let i = 0; i < markdownTable.length; i++) {
44 | if (/^[:+= ]+$/.test(markdownTable[i])) { // "Real" headers have = instead of -
45 | headerRow = i
46 | break
47 | }
48 | }
49 |
50 | let headerContent: string|string[] = markdownTable[headerRow]
51 |
52 | headerContent = headerContent.split('+').slice(1, -1)
53 | for (let i = 0; i < headerContent.length; i++) {
54 | const col = headerContent[i].trim()
55 | if (col.startsWith(':') && col.endsWith(':')) {
56 | colAlignments[i] = 'center'
57 | } else if (col.endsWith(':')) {
58 | colAlignments[i] = 'right'
59 | } else {
60 | colAlignments[i] = 'left'
61 | }
62 | }
63 |
64 | // Now iterate over all table rows
65 | for (let i = 0; i < markdownTable.length; i++) {
66 | // There should not be empty lines in the table.
67 | // If so, this indicates an error in the render tables plugin!
68 | if (markdownTable[i].trim() === '') {
69 | throw new Error(`Line ${i} in the table was empty!`)
70 | }
71 |
72 | const row: string|string[] = markdownTable[i].trim() // Clean up whitespace
73 | if (row.startsWith('+')) {
74 | continue // Skip separation lines
75 | }
76 |
77 | if (!row[0].startsWith('|')) {
78 | throw new Error(`Malformed Markdown Table! Row ${i} did not start with + or |!`)
79 | }
80 |
81 | // Split to columns
82 | const cols = row.split('|').map(elem => elem.trim()).slice(1, -1)
83 |
84 | // First row determines the amount of columns expected
85 | if (numColumns === 0) {
86 | numColumns = cols.length // Basically: First row determines column count ...
87 | } else if (numColumns !== cols.length) { // ... subsequent rows check against this.
88 | throw new Error(`Malformed Markdown Table! Found ${row.length} columns on line ${i} (should be ${numColumns}).`)
89 | }
90 |
91 | // Add the whole row to the AST
92 | ast.push(cols)
93 | }
94 |
95 | if (ast.length === 0 || ast[0].length === 0) {
96 | // This AST does not look as it's supposed to -> abort
97 | throw new Error('Malformed Markdown Table! The parsed AST was empty.')
98 | }
99 |
100 | // Return the AST
101 | return { ast, colAlignments }
102 | }
103 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/parse-pipe.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: TableHelper utility function
6 | * CVM-Role: Utility
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This parser transforms pipe tables as specified
11 | * in the Pandoc manual into an AST and returns both
12 | * that and the column alignments.
13 | * Cf. https://pandoc.org/MANUAL.html#tables
14 | *
15 | * END HEADER
16 | */
17 |
18 | import { ParsedTable, ColAlignment } from './types'
19 |
20 | /**
21 | * Parses a pipe table
22 | * @param {String|Array} A markdown table, either as line array or string
23 | * @returns {Object} An object with properties ast and colAlignments
24 | */
25 | export default function parsePipeTable (markdownTable: string|string[]): ParsedTable {
26 | // First remove whitespace from both sides of the table, e.g. in case
27 | // a trailing newline is present
28 | if (typeof markdownTable === 'string') markdownTable = markdownTable.trim()
29 | if (!Array.isArray(markdownTable)) markdownTable = markdownTable.split('\n')
30 |
31 | if (markdownTable.length === 0) throw new Error('MarkdownTable was empty!')
32 | if (markdownTable.length === 1 && markdownTable[0].trim() === '') throw new Error('MarkdownTable was empty!')
33 |
34 | let ast = [] // Two-dimensional array
35 | let colAlignments: ColAlignment[] = [] // One-dimensional column alignments
36 | let numColumns = -1 // If there is an uneven number of columns, throw an error.
37 |
38 | // Now iterate over all table rows
39 | for (let i = 0; i < markdownTable.length; i++) {
40 | // There should not be empty lines in the table.
41 | // If so, this indicates an error in the render tables plugin!
42 | if (markdownTable[i].trim() === '') throw new Error(`Line ${i} in the table was empty!`)
43 | let row: string[]|string = markdownTable[i].trim() // Clean up whitespace
44 | if (/^[- :+|]+$/.test(row)) {
45 | // We have an alternative pipetable, separated with + instead of |,
46 | // so we have to replace all instances of + with |
47 | row = row.replace(/\+/g, '|')
48 | }
49 |
50 | // Split to columns
51 | row = row.split('|')
52 |
53 | // Now, expect that the first and last "column" are empty and remove them.
54 | // If they are not empty, we probably have a pipe table without surrounding
55 | // pipes.
56 | if (row[0].trim() === '') row.shift()
57 | if (row[row.length - 1].trim() === '') row.pop()
58 |
59 | // First row determines the amount of columns expected
60 | if (numColumns === -1) {
61 | numColumns = row.length // First row determines column count ...
62 | }
63 |
64 | if (numColumns !== row.length) { // ... subsequent rows check that length.
65 | throw new Error(`Malformed Markdown Table! Found ${row.length} columns on line ${i} (should be ${numColumns}).`)
66 | }
67 |
68 | // First test if we've got a header row. A header row is defined of
69 | // consisting of columns only containing dashes, colons and spaces. The
70 | // first column to break this rule means we don't have a valid header.
71 | let isHeader = true
72 | for (let col of row) {
73 | if (!/^[-: ]+$/.test(col) || col.trim() === '') {
74 | // Note we have to check for completely blank lines
75 | isHeader = false
76 | break
77 | }
78 | }
79 |
80 | // Parse the header
81 | if (isHeader) {
82 | for (let j = 0; j < row.length; j++) {
83 | let col = row[j].trim()
84 | if (col[0] === ':' && col[col.length - 1] === ':') {
85 | colAlignments[j] = 'center'
86 | } else if (col[col.length - 1] === ':') {
87 | colAlignments[j] = 'right'
88 | } else {
89 | colAlignments[j] = 'left'
90 | }
91 | }
92 | continue // We're done here -- don't add it to the AST
93 | }
94 |
95 | // We have a normal table row, so parse all columns
96 | let cols = []
97 | for (let j = 0; j < row.length; j++) {
98 | cols.push(row[j].trim()) // Trim whitespaces
99 | }
100 |
101 | // Add the whole row to the AST
102 | ast.push(cols)
103 | }
104 |
105 | // If we have reached this stage, but no header row was found,
106 | // preset all column alignments with left
107 | if (colAlignments.length === 0) {
108 | for (let i = 0; i < numColumns; i++) {
109 | colAlignments.push('left')
110 | }
111 | }
112 |
113 | if (ast.length === 0 || ast[0].length === 0) {
114 | // This AST does not look as it's supposed to -> abort
115 | throw new Error('Malformed Markdown Table! The parsed AST was empty.')
116 | }
117 |
118 | // Return the AST
119 | return {
120 | 'ast': ast,
121 | 'colAlignments': colAlignments
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/parse-simple.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: TableHelper utility function
6 | * CVM-Role: Utility
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This parser transforms simple tables as specified
11 | * in the Pandoc manual into an AST and returns both
12 | * that and the column alignments.
13 | * Cf. https://pandoc.org/MANUAL.html#tables
14 | *
15 | * END HEADER
16 | */
17 |
18 | import { ParsedTable, ColAlignment } from './types'
19 |
20 | /**
21 | * Parses a "simple" table ("simple", because it's not that simple)
22 | * @param {String|Array} A markdown table, either as line array or string
23 | * @returns {Object} An object with properties ast and colAlignments
24 | */
25 | export default function parseSimpleTable (markdownTable: string|string[]): ParsedTable {
26 | // Let's begin. First we need a line array. Make sure to strip potential
27 | // newlines from beginning and end, if applicable. Also perform some
28 | // initial sanity checks on the table.
29 | if (!Array.isArray(markdownTable)) {
30 | markdownTable = markdownTable.split('\n')
31 | }
32 |
33 | if (markdownTable.length === 0 || (markdownTable.length === 1 && markdownTable[0].trim() === '')) {
34 | throw new Error('MarkdownTable was empty!')
35 | }
36 |
37 | const ast: string[][] = []
38 | const colAlignments: ColAlignment[] = []
39 | const colSizes: number[] = [] // Yes, here we need the lengths of the columns
40 | const colPadding: number[] = [] // Also yes: We need to know the padding between cols.
41 |
42 | // First, we need to determine the headers and the column alignments. As the
43 | // columns can be separated by an arbitrary amount of spaces, we need to
44 | // gauge the length AND the indentation by looking either at the first line
45 | // OR at the second line, depending on which one consists solely of dashes
46 | // and spaces.
47 | let determineAlignWith = markdownTable[0]
48 | let determineLengthsWith = markdownTable[1]
49 | if (/^[- ]+$/.test(markdownTable[0])) {
50 | // We got a headless table, so we need to swap the lines.
51 | determineLengthsWith = markdownTable[0]
52 | determineAlignWith = markdownTable[1]
53 |
54 | // Bonus round: According to the manual (TM) a headless table needs to have
55 | // a solely dashed line at the bottom.
56 | if (!/^[- ]+$/.test(markdownTable[markdownTable.length - 1])) {
57 | throw new Error('Malformed Markdown Table: Did not find a finishing dashed line for the table.')
58 | }
59 | }
60 |
61 | // Additional sanity check: The table is not allowed to be padded.
62 | if (determineLengthsWith.startsWith(' ') || determineLengthsWith.endsWith(' ')) {
63 | throw new Error('The table is padded.')
64 | }
65 |
66 | // Now we first need the indent and column sizes
67 | // @ts-ignore
68 | for (const match of determineLengthsWith.matchAll(/-+| +/g)) {
69 | // This RegExp is able to extract single-character-substrings
70 | if (match[0].startsWith('-')) {
71 | colSizes.push(match[0].length)
72 | } else if (match[0].startsWith(' ')) {
73 | colPadding.push(match[0].length)
74 | }
75 | }
76 |
77 | // Now, determine the alignments. Do so by first splitting the respective
78 | // line according to the lengths and see where the whitespace is.
79 | let index = 0
80 | for (let i = 0; i < colSizes.length; i++) {
81 | colAlignments.push(determineAlignWith.substring(index, index + colSizes[i]) as any)
82 | index += colSizes[i] + colPadding[i]
83 | }
84 |
85 | // Transform the colAlignments containing real text into alignments
86 | for (let i = 0; i < colAlignments.length; i++) {
87 | let a = colAlignments[i]
88 | if (a.startsWith(' ') && a.endsWith(' ')) {
89 | colAlignments[i] = 'center'
90 | } else if (a.startsWith(' ')) {
91 | colAlignments[i] = 'right'
92 | } else {
93 | colAlignments[i] = 'left'
94 | }
95 | }
96 |
97 | // Now we should have column sizes and indentation sizes.
98 | // Next: Parse the full table according to these rules.
99 | for (const line of markdownTable) {
100 | if (/^[- ]+$/.test(line)) {
101 | continue // Heading row
102 | }
103 |
104 | const columns: string[] = []
105 | let index = 0
106 | for (let i = 0; i < colSizes.length; i++) {
107 | columns.push(line.substring(index, index + colSizes[i]).trim())
108 | index += colSizes[i] + colPadding[i]
109 | }
110 | ast.push(columns)
111 | }
112 |
113 | if (ast.length === 0 || ast[0].length === 0) {
114 | // This AST does not look as it's supposed to -> abort
115 | throw new Error('Malformed Markdown Table! The parsed AST was empty.')
116 | }
117 |
118 | // Return the AST
119 | return { ast, colAlignments }
120 | }
121 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/table-editor/types.ts:
--------------------------------------------------------------------------------
1 | import TableEditor from './index'
2 |
3 | export type ColAlignment = 'center'|'left'|'right'
4 |
5 | export interface ParsedTable {
6 | ast: string[][]
7 | colAlignments: ColAlignment[]
8 | }
9 |
10 | export interface TableEditorOptions {
11 | /**
12 | * Describes the container for the Table element (either an Element or a querySelector)
13 | */
14 | container?: HTMLElement|string
15 | /**
16 | * A callback that is fired whenever the TableEditor is unfocused
17 | *
18 | * @param {TableEditor} instance The TableEditor instance
19 | */
20 | onBlur?: (instance: ReturnType) => void
21 | /**
22 | * A callback that is fired whenever the TableEditor's contents change
23 | *
24 | * @param {TableEditor} instance The TableEditor instance
25 | */
26 | onChange?: (instance: ReturnType) => void
27 | /**
28 | * A callback that is fired whenever the user switches the cell of the table
29 | *
30 | * @param {TableEditor} instance The TableEditor instance
31 | */
32 | onCellChange?: (instance: ReturnType) => void
33 | }
34 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v5/taskRender.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: Task rendering Plugin
6 | * CVM-Role: CodeMirror Plugin
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This plugin renders task items and makes them checkable.
11 | *
12 | * END HEADER
13 | */
14 |
15 | import CodeMirror from 'codemirror'
16 | import { getTaskRE } from '../../../../utils/regular-expressions'
17 | import canRenderElement from "../../../../utils/can-render-element";
18 |
19 | const taskRE = getTaskRE() // Matches `- [ ]` and `- [x]`
20 |
21 | /**
22 | * Renders task list items
23 | *
24 | * @param {CodeMirror.Editor} cm The CodeMirror instance
25 | */
26 | export function markdownRenderTasks(cm: CodeMirror.Editor, viewPort: boolean) {
27 | let match
28 |
29 | let fromLine = 0;
30 | let toLine = Math.min(60, cm.lineCount());
31 |
32 | if (viewPort) {
33 | // We'll only renderer the viewport
34 | const viewport = cm.getViewport()
35 | fromLine = viewport.from;
36 | toLine = viewport.to;
37 | }
38 | for (let i = fromLine; i <= toLine; i++) {
39 | if (cm.getModeAt({ line: i, ch: 0 }).name !== 'markdown') continue
40 | // Always reset lastIndex property, because test()-ing on regular
41 | // expressions advances it.
42 | taskRE.lastIndex = 0
43 |
44 | // First get the line and test if the contents contain a link
45 | const line = cm.getLine(i)
46 | if ((match = taskRE.exec(line)) == null) {
47 | continue
48 | }
49 |
50 | const leadingSpaces = match[1].length ?? 0
51 | const curFrom = { line: i, ch: 0 + leadingSpaces }
52 | const curTo = { line: i, ch: 5 + leadingSpaces }
53 |
54 | if (!canRenderElement(cm, curFrom, curTo, !viewPort)) {
55 | continue
56 | }
57 |
58 | // Now we can renderer it finally.
59 | const checked = (match[3] === 'x')
60 | const listSign = match[2] // Save the sign +, -, or * for later
61 |
62 | const cbox = document.createElement('input')
63 | cbox.type = 'checkbox'
64 | if (checked) {
65 | cbox.checked = true
66 | }
67 |
68 | // If the CodeMirror instance is readOnly, disable the checkbox
69 | cbox.disabled = cm.isReadOnly()
70 |
71 | let textMarker = cm.markText(
72 | curFrom, curTo,
73 | {
74 | 'clearOnEnter': true,
75 | 'replacedWith': cbox,
76 | 'inclusiveLeft': false,
77 | 'inclusiveRight': false
78 | }
79 | )
80 |
81 | // Clear the textmarker once it's hidden b/c we'd rather
82 | // re-renderer than having a wrong state associated with the marker
83 | textMarker.on('hide', () => { textMarker.clear() })
84 |
85 | cbox.onclick = (e) => {
86 | const markerPosition = textMarker.find()
87 | if (cm.isReadOnly() || markerPosition === undefined) {
88 | return // Don't do anything
89 | }
90 |
91 | const { from, to } = markerPosition
92 |
93 | // Check or uncheck it (NOTE that cbox will already represent the NEW state)
94 | const newMark = (cbox.checked) ? 'x' : ' '
95 | cm.replaceRange(`${listSign} [${newMark}]`, from, to)
96 |
97 | // ReplaceRange removes the marker, so we have to re-initiate it
98 | textMarker = cm.markText(
99 | from, to,
100 | {
101 | 'clearOnEnter': true,
102 | 'replacedWith': cbox,
103 | 'inclusiveLeft': false,
104 | 'inclusiveRight': false
105 | }
106 | )
107 | } // END onclick
108 |
109 | // We need to listen to readOnly state changes to enable/disable checkboxes
110 | const updateHandler = (cm: CodeMirror.Editor, option: any): void => {
111 | if (!document.body.contains(cbox)) {
112 | // Remove the event listener again
113 | cm.off('optionChange', updateHandler)
114 | }
115 |
116 | cbox.disabled = cm.isReadOnly()
117 | }
118 |
119 | // Listen to option changes
120 | cm.on('optionChange', updateHandler)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v6/checklistPlugin.ts:
--------------------------------------------------------------------------------
1 |
2 | import { requireCodeMirrorLanguage, requireCodeMirrorView } from '../../../../utils/cm-dynamic-require';
3 | import type { Range } from '@codemirror/state';
4 | import { Decoration, DecorationSet, EditorView, ViewUpdate, WidgetType } from '@codemirror/view';
5 |
6 | // See https://codemirror.net/examples/decoration/
7 |
8 | const checklistPlugin = () => {
9 | const { ViewPlugin, WidgetType, Decoration } = requireCodeMirrorView();
10 | const { syntaxTree } = requireCodeMirrorLanguage();
11 |
12 | const checkboxClassName = 'cm-enhancement-checkbox';
13 |
14 | class CheckboxWidget extends WidgetType {
15 | public constructor(private checked: boolean) {
16 | super();
17 | }
18 |
19 | public override eq(other: WidgetType) {
20 | return (other instanceof CheckboxWidget) && this.checked === other.checked;
21 | }
22 |
23 | public override toDOM() {
24 | const container = document.createElement('span');
25 | container.setAttribute('aria-hidden', 'true');
26 |
27 | const input = document.createElement('input');
28 | input.type = 'checkbox';
29 | input.checked = this.checked;
30 | input.classList.add(checkboxClassName);
31 |
32 | container.appendChild(input);
33 | return container;
34 | }
35 |
36 | public override ignoreEvent(_event: Event) {
37 | // Allows toggling the checkbox on click.
38 | return false;
39 | }
40 | }
41 |
42 | const checkboxDecorationChecked = Decoration.replace({
43 | widget: new CheckboxWidget(true),
44 | });
45 | const checkboxDecorationUnchecked = Decoration.replace({
46 | widget: new CheckboxWidget(false),
47 | });
48 |
49 | const buildCheckboxDecorations = (view: EditorView) => {
50 | const decorations: Range[] = [];
51 | const cursor = view.state.selection.main.from;
52 |
53 | for (const { from, to } of view.visibleRanges) {
54 | syntaxTree(view.state).iterate({
55 | from, to,
56 | enter: node => {
57 | if (node.name === 'TaskMarker') {
58 | // Generally, task markers are defined like this:
59 | // - [x]
60 | // CodeMirror doesn't include the "- " part in the TaskMarker syntax tree
61 | // node. As such, we increase the concealed size by 2 to include it:
62 | const beforeCheckbox = view.state.sliceDoc(node.from - 2, node.from);
63 | const from = beforeCheckbox === '- ' ? node.from - 2 : node.from;
64 |
65 | const containsCursor = cursor >= from && cursor <= node.to;
66 | if (!containsCursor) {
67 | const checkboxContent = view.state.sliceDoc(node.from, node.to);
68 | const isChecked = checkboxContent.toLowerCase().includes('x');
69 | const decoration = isChecked ? checkboxDecorationChecked : checkboxDecorationUnchecked;
70 |
71 | decorations.push(decoration.range(from, node.to));
72 | }
73 | }
74 | },
75 | });
76 | }
77 | return Decoration.set(decorations);
78 | };
79 |
80 | // Toggles the checkbox just before `pos`.
81 | // `pos` should point to just after the checkbox.
82 | const toggleChecbox = (view: EditorView, pos: number) => {
83 | const before = view.state.sliceDoc(pos - 3, pos);
84 | let newContent = '[x]';
85 | if (before.toLowerCase() === '[x]') {
86 | newContent = '[ ]';
87 | }
88 | view.dispatch({ changes: { from: pos - 3, to: pos, insert: newContent }});
89 | };
90 |
91 | return ViewPlugin.fromClass(class {
92 | public decorations: DecorationSet;
93 |
94 | public constructor(view: EditorView) {
95 | this.decorations = buildCheckboxDecorations(view);
96 | }
97 |
98 | public update(update: ViewUpdate) {
99 | if (update.docChanged || update.viewportChanged || update.selectionSet) {
100 | this.decorations = buildCheckboxDecorations(update.view);
101 | }
102 | }
103 | }, {
104 | decorations: plugin => plugin.decorations,
105 | eventHandlers: {
106 | mousedown: (event, view) => {
107 | const target = event.target as HTMLElement;
108 | if (target.nodeName === 'INPUT' && target.classList.contains(checkboxClassName)) {
109 | toggleChecbox(view, view.posAtDOM((target)));
110 | return true;
111 | }
112 | },
113 | }
114 | });
115 | };
116 |
117 | export default checklistPlugin;
--------------------------------------------------------------------------------
/src/driver/codemirror/taskAndHeaderRenderer/v6/index.ts:
--------------------------------------------------------------------------------
1 | import checklistPlugin from './checklistPlugin';
2 |
3 | const taskAndHeaderRenderer = () => {
4 | return [
5 | checklistPlugin(),
6 | ];
7 | };
8 |
9 | export default taskAndHeaderRenderer;
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/filePreview/filePreviewRenderer.ts:
--------------------------------------------------------------------------------
1 | import path = require("path");
2 | var mime = require('mime-types')
3 |
4 |
5 | export function filePreviewRenderer(markdownIt, _options) {
6 | const defaultRender = markdownIt.renderer.rules.link_close || function (tokens, idx, options, env, self) {
7 | return self.renderToken(tokens, idx, options, env, self);
8 | };
9 |
10 | markdownIt.renderer.rules.link_close = function (tokens, idx, options, env, self) {
11 | let result = defaultRender(tokens, idx, options, env, self);
12 | if (tokens.length < 2) {
13 | return result;
14 | }
15 |
16 | const token = tokens[0];
17 | if (token.type !== 'link_open') {
18 | return result;
19 | }
20 |
21 | let link_path;
22 | if (!token.attrs) {
23 | return result;
24 | }
25 |
26 | for (let arr of token.attrs) {
27 | if (arr[0] === 'href') {
28 | link_path = arr[1];
29 | }
30 | }
31 |
32 | if (link_path.startsWith('file:///')) {
33 | let file_extension = path.extname(link_path).toUpperCase();
34 | switch (file_extension) {
35 | case '.PDF':
36 | return result + ``;
37 | }
38 | }
39 | return result
40 | };
41 | }
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/filePreview/index.ts:
--------------------------------------------------------------------------------
1 | import {filePreviewRenderer} from "./filePreviewRenderer";
2 |
3 | export default function (context) {
4 | return {
5 | plugin: function (markdownIt, _options) {
6 | const pluginId = context.pluginId;
7 |
8 | filePreviewRenderer(markdownIt, _options);
9 | },
10 | assets: function() {
11 | return [
12 | ];
13 | },
14 | }
15 | }
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/image/imageRenderer.ts:
--------------------------------------------------------------------------------
1 | export function imageRenderer(markdownIt, _options) {
2 | const defaultRender = markdownIt.renderer.rules.image || function (tokens, idx, options, env, self) {
3 | return self.renderToken(tokens, idx, options, env, self);
4 | };
5 |
6 | markdownIt.renderer.rules.image = function (tokens, idx, options, env, self) {
7 | // console.log(tokens, idx);
8 | const token = tokens[idx];
9 | let result = defaultRender(tokens, idx, options, env, self);
10 |
11 | let styleStr = "";
12 | for (let attr of token.attrs) {
13 | if (attr[0] == 'alt' && attr[1].length > 0) {
14 | result = `
15 | ${result}
16 | ${attr[1]}
17 | `
18 | } else if (attr[0] == 'width') {
19 | styleStr += ` width="${attr[1]}" `;
20 | } else if (attr[0] == 'height') {
21 | styleStr += ` height="${attr[1]}" `;
22 | }
23 | }
24 |
25 | let imgTagRex = /
30 | ${result}
31 |
32 | `;
33 | return result
34 | }
35 | }
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/image/index.ts:
--------------------------------------------------------------------------------
1 | import {imageRenderer} from "./imageRenderer";
2 |
3 | export default function (context) {
4 | return {
5 | plugin: function (markdownIt, _options) {
6 | const pluginId = context.pluginId;
7 |
8 | imageRenderer(markdownIt, _options);
9 | },
10 | assets: function() {
11 | return [
12 | { name: 'markdownItPlugin.css' }
13 | ];
14 | },
15 | }
16 | }
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/image/markdownItPlugin.css:
--------------------------------------------------------------------------------
1 | body {
2 | counter-set: figNo;
3 | }
4 |
5 | figure {
6 | text-align: center;
7 | counter-increment: figNo;
8 | }
9 |
10 | img {
11 | text-align: center;
12 | }
13 |
14 | figcaption {
15 | text-align: center;
16 | font-weight: bold;
17 | }
18 |
19 | figcaption::before {
20 | content: 'Fig. ' counter(figNo) ': ';
21 | }
22 |
23 | .mermaid {
24 | text-align: center;
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/pseudocode/index.ts:
--------------------------------------------------------------------------------
1 | import {pseudocodeFenceRenderer} from "./pseudocode";
2 |
3 | var mime = require('mime-types')
4 |
5 | export default function (context) {
6 | return {
7 | plugin: function (markdownIt, _options) {
8 | const pluginId = context.pluginId;
9 |
10 | pseudocodeFenceRenderer(markdownIt, _options);
11 | },
12 | assets: function() {
13 | return [
14 | { name: 'pseudocode.min.css' }
15 | ];
16 | },
17 | }
18 | }
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/pseudocode/pseudocode.min.css:
--------------------------------------------------------------------------------
1 | .ps-root{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-size:1em;font-weight:100;-webkit-font-smoothing:antialiased!important}.ps-root .ps-algorithm{margin:.8em 0;border-top:3px solid #000;border-bottom:2px solid #000}.ps-root .ps-algorithm.with-caption>.ps-line:first-child{border-bottom:2px solid #000}.ps-root .katex{text-indent:0;font-size:1em}.ps-root .MathJax,.ps-root .MathJax_CHTML{text-indent:0;font-size:1em!important}.ps-root .ps-line{margin:0;padding:0;line-height:1.2}.ps-root .ps-funcname{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:400;font-variant:small-caps;font-style:normal;text-transform:none}.ps-root .ps-keyword{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:700;font-variant:normal;font-style:normal;text-transform:none}.ps-root .ps-comment{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:400;font-variant:normal;font-style:normal;text-transform:none}.ps-root .ps-linenum{font-size:.8em;line-height:1em;width:1.6em;text-align:right;display:inline-block;position:relative;padding-right:.3em}.ps-root .ps-algorithmic.with-linenum .ps-line.ps-code{text-indent:-1.6em}.ps-root .ps-algorithmic.with-linenum .ps-line.ps-code>span{text-indent:0}
2 |
3 | p.ps-line {
4 | margin: 0.25em !important;
5 | }
6 |
7 | .ps-algorithm {
8 | width: 400px;
9 | }
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/pseudocode/pseudocode.ts:
--------------------------------------------------------------------------------
1 | import { renderToString } from 'pseudocode';
2 |
3 | export function pseudocodeFenceRenderer(markdownIt, _options) {
4 | const defaultRender = markdownIt.renderer.rules.fence || function (tokens, idx, options, env, self) {
5 | return self.renderToken(tokens, idx, options, env, self);
6 | };
7 |
8 | markdownIt.renderer.rules.fence = function (tokens, idx, options, env, self) {
9 | // console.log(tokens, idx);
10 | const token = tokens[idx];
11 | if (token.info !== 'pseudocode') {
12 | return defaultRender(tokens, idx, options, env, self);
13 | }
14 |
15 | try {
16 | let result = renderToString(token.content, {lineNumber: true, captionCount: 1});
17 | return result.replace(/Algorithm \d/, 'Algorithm');
18 | } catch (e) {
19 |
20 | } finally {
21 | }
22 |
23 | return ' Errors in pseudocode
';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/quote/index.ts:
--------------------------------------------------------------------------------
1 | import {quoteRenderer} from "./quoteRender";
2 |
3 | export default function (context) {
4 | return {
5 | plugin: function (markdownIt, _options) {
6 | const pluginId = context.pluginId;
7 |
8 | quoteRenderer(markdownIt, _options);
9 | },
10 | assets: function() {
11 | return [
12 | {
13 | name: 'quoteRender.css'
14 | }
15 | ];
16 | },
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/quote/quoteRender.css:
--------------------------------------------------------------------------------
1 | span.blockquote-name.blockquote-enhancement::before {
2 | content: '🖇 ';
3 | }
4 |
5 | span.blockquote-date.blockquote-enhancement::before {
6 | content: '🕰 ';
7 | }
8 |
9 | span.blockquote-name.blockquote-enhancement {
10 | padding-right: 1em;
11 | }
12 |
13 | small.blockquote-enhancement-wrap::before {
14 | content: '\2014 \00A0';
15 | }
16 |
17 | small.blockquote-enhancement-wrap {
18 | background: lavender;
19 | padding: 2px 6px 2px 4px;
20 | border-radius: 6px;
21 | }
22 |
23 | blockquote > p {
24 | margin-top: 0;
25 | }
26 |
--------------------------------------------------------------------------------
/src/driver/markdownItRenderer/quote/quoteRender.ts:
--------------------------------------------------------------------------------
1 | const colorRegex = /\[color=(.*?)\]/;
2 | const nameRegex = /\[name=(.*?)\]/;
3 | const dateRegex = /\[date=(.*?)\]/;
4 |
5 |
6 | export function quoteRenderer(markdownIt, _options) {
7 | const defaultRender = markdownIt.renderer.rules.blockquote_open || function (tokens, idx, options, env, self) {
8 | return self.renderToken(tokens, idx, options, env, self);
9 | };
10 |
11 | markdownIt.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) {
12 | const token = tokens[idx];
13 | let name = null, date = null;
14 | for (let i = idx + 1; i < tokens.length; i++) {
15 | if (tokens[i].type === 'blockquote_close') {
16 | break;
17 | }
18 |
19 | const colorMatch = colorRegex.exec(tokens[i].content);
20 | const nameMatch = nameRegex.exec(tokens[i].content);
21 | const dateMatch = dateRegex.exec(tokens[i].content);
22 |
23 | if (colorMatch || nameMatch || dateMatch) {
24 | for (const child of tokens[i].children) {
25 | if (child.type !== 'text') {
26 | continue;
27 | }
28 |
29 | let realColorMatch = colorRegex.exec(child.content);
30 | if (realColorMatch) {
31 | child.content = child.content.replace(colorRegex, '');
32 | if (!token.attrs) {
33 | token.attrs = [];
34 | }
35 | token.attrs.push(['style', `border-color:${realColorMatch[1]}`]);
36 | }
37 |
38 | let realNameMatch = nameRegex.exec(child.content);
39 | if (realNameMatch) {
40 | child.content = child.content.replace(nameRegex, '');
41 | name = realNameMatch[1];
42 | }
43 |
44 | let realDateMatch = dateRegex.exec(child.content);
45 | if (realDateMatch) {
46 | child.content = child.content.replace(dateRegex, '');
47 | date = realDateMatch[1];
48 | }
49 | }
50 | }
51 | }
52 |
53 | let result = defaultRender(tokens, idx, options, env, self);
54 | let appendix = '';
55 | if (name) {
56 | appendix += `${name}`;
57 | }
58 | if (date) {
59 | appendix += `${date}`;
60 | }
61 |
62 | if (appendix.length > 0) {
63 | result += `${appendix}`;
64 | }
65 |
66 | return result;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/driver/markdownItRuler/image/utils.ts:
--------------------------------------------------------------------------------
1 | // Utilities
2 | //
3 | 'use strict';
4 |
5 | // Hepler to unify [reference labels].
6 | //
7 | function normalizeReference(str) {
8 | // Trim and collapse whitespace
9 | //
10 | str = str.trim().replace(/\s+/g, ' ');
11 |
12 | // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug
13 | // fixed in v12 (couldn't find any details).
14 | //
15 | // So treat this one as a special case
16 | // (remove this when node v10 is no longer supported).
17 | //
18 | if ('ẞ'.toLowerCase() === 'Ṿ') {
19 | str = str.replace(/ẞ/g, 'ß');
20 | }
21 |
22 | // .toLowerCase().toUpperCase() should get rid of all differences
23 | // between letter variants.
24 | //
25 | // Simple .toLowerCase() doesn't normalize 125 code points correctly,
26 | // and .toUpperCase doesn't normalize 6 of them (list of exceptions:
27 | // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently
28 | // uppercased versions).
29 | //
30 | // Here's an example showing how it happens. Lets take greek letter omega:
31 | // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ)
32 | //
33 | // Unicode entries:
34 | // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8;
35 | // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398
36 | // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398
37 | // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8;
38 | //
39 | // Case-insensitive comparison should treat all of them as equivalent.
40 | //
41 | // But .toLowerCase() doesn't change ϑ (it's already lowercase),
42 | // and .toUpperCase() doesn't change ϴ (already uppercase).
43 | //
44 | // Applying first lower then upper case normalizes any character:
45 | // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398'
46 | //
47 | // Note: this is equivalent to unicode case folding; unicode normalization
48 | // is a different step that is not required here.
49 | //
50 | // Final result should be uppercased, because it's later stored in an object
51 | // (this avoid a conflict with Object.prototype members,
52 | // most notably, `__proto__`)
53 | //
54 | return str.toLowerCase().toUpperCase();
55 | }
56 |
57 | function isSpace(code) {
58 | switch (code) {
59 | case 0x09:
60 | case 0x20:
61 | return true;
62 | }
63 | return false;
64 | }
65 |
66 | exports.normalizeReference = normalizeReference;
67 | exports.isSpace = isSpace;
68 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 1,
3 | "id": "com.septemberhx.Joplin.Enhancement",
4 | "app_min_version": "2.7",
5 | "version": "1.3.1",
6 | "name": "Enhancement",
7 | "description": "Enhance the markdown editor with live preview for math, mermaid, link, image, and more. It also includes other features for markdown rendering.",
8 | "author": "SeptemberHX",
9 | "homepage_url": "https://github.com/SeptemberHX/joplin-plugin-enhancement.git",
10 | "repository_url": "https://github.com/SeptemberHX/joplin-plugin-enhancement.git",
11 | "keywords": ["Live Preview"],
12 | "categories": []
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/CMInlineMarkerHelperV2.ts:
--------------------------------------------------------------------------------
1 | // implemented according to https://github.com/ylc395/joplin-plugin-note-link-system/blob/main/src/driver/codeMirror/UrlFolder.ts
2 |
3 | import { debounce } from "ts-debounce";
4 | import clickAndClear from "./click-and-clear";
5 | import {isRangeSelected} from "./cm-utils";
6 |
7 | interface MarkerMatch {
8 | match;
9 | }
10 |
11 | export default class CMInlineMarkerHelperV2 {
12 | renderer: (match, from, to) => any;
13 | lineFilter: (line: string) => boolean;
14 | clicked: (match, e: MouseEvent) => void;
15 | regex;
16 | MARKER_CLASS_NAME: string;
17 |
18 | constructor(private readonly editor, regex, renderer, MARKER_CLASS_NAME, lineFilter?, clicked?) {
19 | this.regex = regex;
20 | this.renderer = renderer;
21 | this.lineFilter = lineFilter;
22 | this.clicked = clicked;
23 | this.MARKER_CLASS_NAME = MARKER_CLASS_NAME;
24 | }
25 |
26 | /**
27 | * This function should be in editor.operation()
28 | */
29 | public process(afterSetValue: boolean = false) {
30 | const viewport = this.editor.getViewport()
31 | const doc = this.editor.getDoc();
32 | const currentCursor = this.editor.getCursor();
33 |
34 | let fromLine = viewport.from;
35 | let toLine = viewport.to;
36 |
37 | if (afterSetValue) {
38 | fromLine = 0;
39 |
40 | // improve user experience;
41 | // todo: decide whether use smaller number or not
42 | toLine = this.editor.lineCount();
43 | }
44 |
45 | for (let lineNo = fromLine; lineNo < toLine; ++lineNo) {
46 | const line = doc.getLine(lineNo);
47 | if (!line) {
48 | continue;
49 | }
50 |
51 | if (this.lineFilter) {
52 | if (!this.lineFilter(line)) {
53 | continue;
54 | }
55 | }
56 |
57 | // to process the situations like ==Hello **World**== correctly
58 | // we need to get all match and process with specific orders
59 | let lineMatches: MarkerMatch[] = [];
60 | for (const match of line.matchAll(this.regex)) {
61 | // by default, we ignore inline code between ` and ```
62 | const tokenType = this.editor.getTokenTypeAt({line: lineNo, ch: match.index});
63 | if (tokenType && tokenType.includes('jn-monospace')) {
64 | continue;
65 | }
66 | lineMatches.push({
67 | match: match
68 | });
69 | }
70 |
71 | // we need to process from inside out, which means we need to process them in reverse order
72 | for (let currIndex = lineMatches.length - 1; currIndex >= 0; --currIndex) {
73 | const markerMatch = lineMatches[currIndex];
74 | this.foldByMatch(doc, lineNo, markerMatch.match);
75 | }
76 | }
77 | }
78 |
79 | private foldByMatch(doc, lineNo, match) {
80 | if (match) {
81 | const cursor = this.editor.getCursor();
82 | // not fold when it is folded ?
83 | this.editor.findMarksAt({line: lineNo, ch: match.index}).find((marker) => {
84 | if (marker.className === this.MARKER_CLASS_NAME) {
85 | marker.clear();
86 | }
87 | });
88 |
89 | const from = {line: lineNo, ch: match.index};
90 | const to = {line: lineNo, ch: match.index + match[0].length};
91 |
92 | let selected = isRangeSelected(from, to, this.editor);
93 |
94 | if (!selected) {
95 | // not fold when the cursor is in the block
96 | if (!(cursor.line === lineNo && cursor.ch >= from.ch && cursor.ch <= to.ch)) {
97 | const element = this.renderer(match, from, to);
98 | const textMarker = doc.markText(
99 | from,
100 | to,
101 | {
102 | replacedWith: element,
103 | className: this.MARKER_CLASS_NAME, // class name is not renderer in DOM
104 | clearOnEnter: true,
105 | inclusiveLeft: false,
106 | inclusiveRight: false
107 | },
108 | );
109 |
110 | element.onclick = (e) => {
111 | if (e.ctrlKey || e.metaKey) {
112 | e.preventDefault();
113 | if (this.clicked) {
114 | this.clicked(match, e);
115 | }
116 | } else {
117 | clickAndClear(textMarker, this.editor)(e);
118 | }
119 | };
120 | }
121 | }
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/utils/can-render-element.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: canRenderElement
6 | * CVM-Role: Utility Function
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: This function can check if you can renderer a preview element
11 | * at the provided position
12 | *
13 | * END HEADER
14 | */
15 |
16 | import { Editor, Position } from 'codemirror'
17 |
18 | /**
19 | * Given the provided positions, this function returns true if you are free to
20 | * renderer an element there
21 | *
22 | * @param {Editor} cm The editor instance
23 | * @param {Position} from The beginning position
24 | * @param {Position} to The ending position
25 | *
26 | * @param {Boolean} ignoreCursor Whether we need to check the cursor position
27 | * @return {boolean} Returns true if you can renderer an element there
28 | */
29 | export default function canRenderElement (cm: Editor, from: Position, to: Position, ignoreCursor: boolean = false): boolean {
30 | // Check if the cursor is within the range
31 | if (!ignoreCursor) {
32 | const cursor = cm.getCursor('head')
33 | if (cursor.line === from.line && cursor.ch >= from.ch && cursor.ch <= to.ch) {
34 | return false
35 | }
36 | }
37 |
38 | // We can only have one marker at any given position at any given time
39 | if (cm.findMarks(from, to).length > 0) {
40 | return false
41 | }
42 |
43 | // We cannot renderer an element within a comment. This is the final check since
44 | // it is quite expensive to compute
45 | const tokenTypeBegin = cm.getTokenTypeAt(from)
46 | const tokenTypeEnd = cm.getTokenTypeAt(to)
47 | if (tokenTypeBegin?.includes('comment') || tokenTypeEnd?.includes('comment')) {
48 | return false
49 | }
50 |
51 | return true
52 | }
53 |
--------------------------------------------------------------------------------
/src/utils/citeproc.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: Citeproc Typings
6 | * CVM-Role: Types
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: Contains the types for Citeproc.js, since these are not (yet)
11 | * added to the types repository.
12 | *
13 | * END HEADER
14 | */
15 |
16 | /**
17 | * Cite-items describe a specific reference to a bibliographic item. The fields
18 | * that a cite-item may contain depend on its context. In a citation, cite-items
19 | * listed as part of the citationItems array provide only pinpoint, descriptive,
20 | * and text-suppression fields.
21 | */
22 | interface CiteItem {
23 | /**
24 | * The only required field: Contains a citation key with which the engine can
25 | * request the full bibliographic information from the registry.
26 | */
27 | id: string
28 | /**
29 | * A string identifying a page number or other pinpoint location or range
30 | * within the resource.
31 | */
32 | locator?: string
33 | /**
34 | * A label type, indicating whether the locator is to a page, a chapter, or
35 | * other subdivision of the target resource. Valid labels are defined in the
36 | * CSL specification.
37 | */
38 | label?: string
39 | /**
40 | * If true, author names will not be included in the citation output for this
41 | * cite.
42 | */
43 | 'suppress-author'?: boolean
44 | /**
45 | * If true, only the author name will be included in the citation output for
46 | * this cite – this optional parameter provides a means for certain demanding
47 | * styles that require the processor output to be divided between the main
48 | * text and a footnote. (See the section Partial suppression of citation
49 | * content under Running the Processor :: Dirty Tricks for more details.)
50 | */
51 | 'author-only'?: boolean
52 | /**
53 | * A string to print before this cite item.
54 | */
55 | prefix?: string
56 | /**
57 | * A string to print after this cite item.
58 | */
59 | suffix?: string
60 | }
61 |
--------------------------------------------------------------------------------
/src/utils/click-and-clear.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: clickAndClear
6 | * CVM-Role: Utility function
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: A small helper function that is intended to be used with
11 | * CodeMirror rendering plugins.
12 | *
13 | * END HEADER
14 | */
15 |
16 | import CodeMirror, { TextMarker } from 'codemirror'
17 |
18 | /**
19 | * Returns a callback that performs a "click and clear" operation on a rendered
20 | * textmarker, i.e. remove the marker and place the cursor precisely where the
21 | * user clicked
22 | *
23 | * @param {TextMarker} marker The text marker
24 | * @param {CodeMirror} cm The CodeMirror instance
25 | *
26 | * @return {Function} The callback
27 | */
28 | export default function clickAndClear (
29 | marker: TextMarker,
30 | cm: CodeMirror.Editor
31 | ): (e: MouseEvent) => void {
32 | return (e: MouseEvent) => {
33 | marker.clear()
34 | cm.setCursor(cm.coordsChar({ left: e.clientX, top: e.clientY }))
35 | cm.focus()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/utils/cm-dynamic-require.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Because this plugin supports both CodeMirror 5 and CodeMirror 6,
4 | * we need to be able to dynamic-require some packages that are only available
5 | * in CodeMirror 6.
6 | *
7 | * Normal imports fail in older versions of Joplin where these libraries aren't
8 | * available and perhaps in some cases while using CodeMirror 5.
9 | */
10 |
11 | import type * as CodeMirrorView from '@codemirror/view';
12 | import type * as CodeMirrorState from '@codemirror/state';
13 | import type * as CodeMirrorLanguage from '@codemirror/language';
14 |
15 | export function requireCodeMirrorView() {
16 | return require('@codemirror/view') as typeof CodeMirrorView;
17 | }
18 |
19 | export function requireCodeMirrorState() {
20 | return require('@codemirror/state') as typeof CodeMirrorState;
21 | }
22 |
23 | export function requireCodeMirrorLanguage() {
24 | return require('@codemirror/language') as typeof CodeMirrorLanguage;
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils/cm-utils.ts:
--------------------------------------------------------------------------------
1 | import type { EditorView } from "@codemirror/view";
2 | import {Editor, Position} from "codemirror";
3 |
4 | export function isRangeSelected(from: Position, to: Position, cm: Editor): boolean {
5 | let selected = false;
6 | for (let selection of cm.getDoc().listSelections()) {
7 | if (from.line < selection.from().line || (from.line === selection.from().line && from.ch < selection.from().ch)
8 | || to.line > selection.to().line || (to.line === selection.to().line && to.ch > selection.to().ch)) {
9 | ;
10 | } else {
11 | selected = true;
12 | break;
13 | }
14 | }
15 | return selected;
16 | }
17 |
18 | export function isCursorOutRange(cursorPos: Position, from: Position, to: Position) {
19 | return cursorPos.line < from.line || cursorPos.line > to.line
20 | || (cursorPos.line === from.line && cursorPos.ch < from.ch)
21 | || (cursorPos.line === to.line && cursorPos.ch > to.ch);
22 | }
23 |
24 | export function findLineWidgetAtLine(editor: Editor, lineNumber: number, className: string) {
25 | // check whether there exists rendered line widget
26 | const line = editor.lineInfo(lineNumber);
27 | if (line.widgets) {
28 | for (const wid of line.widgets) {
29 | if (wid.className === className) {
30 | return wid;
31 | }
32 | }
33 | }
34 | return null;
35 | }
36 |
37 | export function isReadOnly(editor: any) {
38 | if (editor.cm6) {
39 | return (editor.editor as EditorView).state.readOnly;
40 | } else {
41 | return (editor as Editor).isReadOnly();
42 | }
43 | }
--------------------------------------------------------------------------------
/src/utils/md-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | * BEGIN HEADER
4 | *
5 | * Contains: md2html function
6 | * CVM-Role: Utility function
7 | * Maintainer: Hendrik Erz
8 | * License: GNU GPL v3
9 | *
10 | * Description: md2html converts a Markdown string to valid HTML
11 | *
12 | * END HEADER
13 | */
14 |
15 | import { Converter, ShowdownExtension } from 'showdown'
16 | import extractCitations from './extract-citations'
17 |
18 | type CitationCallback = (items: CiteItem[], composite: boolean) => string|undefined
19 |
20 | /**
21 | * md2html converts the given Markdown to HTML, optionally making any link
22 | * "renderer" safe, which means that those links will be opened using
23 | * electron shell to prevent them overriding the content of the window.
24 | *
25 | * @param {string} markdown The input Markdown text
26 | *
27 | * @return {string} The final HTML string
28 | */
29 | export function getConverter (citationCallback?: CitationCallback): (markdown: string) => string {
30 | // If the caller did not provide a citation callback we'll create a polyfill
31 | // that will simply return an empty string, effectively stripping any citations
32 | if (citationCallback === undefined) {
33 | citationCallback = function (items, composite) { return '' }
34 | }
35 |
36 | // Spin up a showdown converter
37 | const showdownConverter = new Converter({
38 | strikethrough: true,
39 | tables: true,
40 | omitExtraWLInCodeBlocks: true,
41 | tasklists: true,
42 | requireSpaceBeforeHeadingText: true,
43 | ghMentions: false,
44 | extensions: [makeCitationPlugin(citationCallback)]
45 | })
46 |
47 | showdownConverter.setFlavor('github')
48 |
49 | return function (markdown: string): string {
50 | return showdownConverter.makeHtml(markdown)
51 | }
52 | }
53 |
54 | /**
55 | * Extension for showdown.js which parses citations using our citeproc provider.
56 | *
57 | * @return {any} The showdown extension
58 | */
59 | function makeCitationPlugin (citationCallback: CitationCallback): () => ShowdownExtension {
60 | return function (): ShowdownExtension {
61 | return {
62 | type: 'lang',
63 | filter: function (text, converter, options) {
64 | // First, extract all citations ...
65 | const allCitations = extractCitations(text)
66 | // ... and retrieve the rendered ones from the citeproc provider
67 | const finalCitations = allCitations.map((elem) => {
68 | return citationCallback(elem.citations, elem.composite) ?? text
69 | })
70 |
71 | // Now get the citations to be replaced
72 | const toBeReplaced = allCitations.map(citation => {
73 | return text.substring(citation.from, citation.to - citation.from)
74 | })
75 |
76 | // Finally, replace every citation with its designated replacement
77 | for (let i = 0; i < allCitations.length; i++) {
78 | text = text.replace(toBeReplaced[i], finalCitations[i])
79 | }
80 |
81 | // Now return the text
82 | return text
83 | }
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/src/utils/reg.ts:
--------------------------------------------------------------------------------
1 | export function exec(query: RegExp, stream: any) {
2 | query.lastIndex = stream.pos;
3 | return query.exec(stream.string);
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/string-render.ts:
--------------------------------------------------------------------------------
1 | var md = require('markdown-it')()
2 | .use(require('markdown-it-mark'));
3 |
4 | /*
5 | * Use markdown-it until joplin provides APIs for string rendering
6 | */
7 | export function renderStrToDom(str: string) {
8 | return md.renderInline(str);
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "module": "commonjs",
5 | "target": "es2015",
6 | "jsx": "react",
7 | "allowJs": true,
8 | "baseUrl": ".",
9 | "esModuleInterop": true,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------