[
  {
    "path": ".gitignore",
    "content": "dist/\nnode_modules/\npublish/*.json\npublish/\n"
  },
  {
    "path": ".npmignore",
    "content": "*.md\n!README.md\n/*.jpl\n/api\n/src\n/dist\ntsconfig.json\nwebpack.config.js\n"
  },
  {
    "path": "GENERATOR_DOC.md",
    "content": "# Plugin development\n\nThis documentation describes how to create a plugin, and how to work with the plugin builder framework and API.\n\n## Installation\n\nFirst, 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/)).\n\n```bash\nnpm install -g yo@4.3.1\nnpm install -g generator-joplin\n```\n\nThen generate your new project:\n\n```bash\nyo --node-package-manager npm joplin\n```\n\n## Structure\n\nThe main two files you will want to look at are:\n\n- `/src/index.ts`, which contains the entry point for the plugin source code.\n- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.\n\nThe 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.\n\n## Building the plugin\n\nThe 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.\n\nTo build the plugin, simply run `npm run dist`.\n\nThe project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.\n\n## Updating the manifest version number\n\nYou 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.\n\n## Publishing the plugin\n\nTo 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:\n\n- In `package.json`, the name starts with \"joplin-plugin-\". For example, \"joplin-plugin-toc\".\n- In `package.json`, the keywords include \"joplin-plugin\".\n- In the `publish/` directory, there should be a .jpl and .json file (which are built by `npm run dist`)\n\nIn 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.\n\n## Updating the plugin framework\n\nTo update the plugin framework, run `npm run update`.\n\nIn 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.\n\nThe 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.\n\n## External script files\n\nBy 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:\n\n- The script is a TypeScript file - in which case it has to be compiled to JavaScript.\n\n- 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.\n\nTo 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.\n\n## More information\n\n- [Joplin Plugin API](https://joplinapp.org/api/references/plugin_api/classes/joplin.html)\n- [Joplin Data API](https://joplinapp.org/help/api/references/rest_api)\n- [Joplin Plugin Manifest](https://joplinapp.org/api/references/plugin_manifest/)\n- Ask for help on the [forum](https://discourse.joplinapp.org/) or our [Discord channel](https://discord.gg/VSj7AFHvpq)\n\n## License\n\nMIT © Laurent Cozic\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Caleb John\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Rich Markdown\n\nA plugin that will finally allow you to ditch the markdown viewer, saving space and making your life easier.\n\nRich Markdown comes as a collection of multiple features that can be toggled in settings. The main goal is to add better interaction features to the editor, namely [image](#images-on-hover) [viewing](#images-in-editor), [checkbox interaction](#checkbox), and [links](#links). It also adds a number of CSS classes that make it possible to add extra customization to the markdown editor. Checkout the image below to see how far this can go!\n\n![Welcome Notebook Screenshot](https://github.com/CalebJohn/joplin-rich-markdown/blob/main/examples/welcome.png)\n\nThe above theme can be applied in the plugin settings menu (this one is called \"Stylish\"). Thanks to [@uxamanda](https://discourse.joplinapp.org/t/plugin-rich-markdown/15053/105) for the awesome theme!\n\n# Features\nThe Rich Markdown plugin supports a number of optional features. Unless otherwise stated, these features can be toggled under `Tools -> Options -> Rich Markdown -> Add additional CSS classes for enhanced customization`.\n\n## Images on Hover\nWhen hovering over a markdown image tag with Ctrl (or Opt) pressed, a preview of the image will pop up below the mouse cursor.\n\nThis is enabled by default.\n[Example of an image on hover](https://github.com/CalebJohn/joplin-rich-markdown/blob/main/examples/hover_image.png)\n\n## Images in Editor\nAny image that is contained on it's own line of the markdown source will render directly on the line directly below. This works for both internal resources and generic links to images\n\nThis is not enabled by default, but can be quick toggled in the View menu.\n[Example of images being rendered in markdown editor](https://github.com/CalebJohn/joplin-rich-markdown/blob/main/examples/inline_image.png)\n\n## Checkbox\nCheckboxes can be toggled in the markdown source by Ctrl (or Opt) + Clicking or with the  Perform action command in the context menu.\n\nThis is enabled by default.\n\n## Links\nLinks can be followed by Ctrl (or Opt) + Clicking or with the Perform action command in the context menu.\n\nThis is enabled by default.\n\n## Highlighting\nText surrounded by == (e.g. ==mark==) will now be highlighted in the editor.\n\nInsert syntax (++insert++), sub (~sub~), and sup (^sup^) syntaxes are also available and enabled in the same way.\n\nThis can be enabled under `Tools -> Options -> Markdown -> Enable ==mark== syntax`.\n\n## Align Lists\nLists that get wrapped will have the wrap match the indentation of the list element.\nThis includes checkboxes and block quotes.\n\nThis is enabled by default.\n\n## Extra CSS\nSome additional CSS classes have been added to further enable customization through [userchrome.css](https://joplinapp.org/help/apps/custom_css). Available classes are detailed below.\n\n`.cm-header.cm-rm-header-token`: The grouping of hashtags (#) at the start of a header\n\n`.cm-em.cm-rm-em-token`: The \\* or \\_ used to begin italics\n\n`.cm-strong.cm-rm-strong-token`: The \\*\\* or \\_\\_ used to begin bold\n\n`.cm-rm-highlight`: The highlighted text (including the ==)\n\n`.cm-rm-highlight.cm-rm-highlight-token`: The == used to begin highlighting\n\n`.cm-strikethrough.cm-rm-strike-token`: The \\~\\~ used to begin a strike through\n\n`.cm-rm-ins.cm-rm-ins-token`: The ++ used to begin the insert syntax\n\n`.cm-rm-sub.cm-rm-sub-token`: The ~ used to begin the sub syntax\n\n`.cm-rm-sup.cm-rm-sup-token`: The ^ used to begin the sup syntax\n\n`pre.cm-rm-hr.CodeMirror-line`: Used to support drawing a horizontal rule [see here](https://github.com/CalebJohn/joplin-rich-markdown/blob/main/TIPS.md#horizontal-rule)\n\n`pre.cm-rm-blockquote.CodeMirror-line`: The line that contains a block quote\n\nIf you have a suggestion for something you'd like to be able to customize. Just let me know and I'll see whats possible.\n\nThis is disabled by default.\n\n## Image External Edits\nIf an image is edited, the change will be reflected in the editor within 3s (by default) or immediately after any change to the note. The 3s period can be changed in the settings (under the advanced tab). Setting the value to 0s will prevent the periodic image changes, but image changes will still be picked up when the note is edited.\n\n\n# Installation\nThe typical way to install plugins is through the built-in Joplin plugin manager.\n\n- Go to `Tools -> Options -> Plugins`(macOS: Joplin -> Preferences -> Plugins)\n- Search for \"Rich Markdown\" in the search box\n- Click Install and restart Joplin\n\n#### Or\n- Download the [plugin jpl](https://github.com/joplin/plugins/raw/master/plugins/plugin.calebjohn.rich-markdown/plugin.jpl)\n- Go to `Tools -> Options -> Plugins`\n- Click on the gear icon and select \"Install from file\"\n- Select the downloaded jpl file\n- Restart Joplin\n\n\n# Feature Requests\nFeature Requests are appreciated and encouraged. Not all feature requests will be technically feasible, so please be patient. Feature requests that align with the projects philosophy (below) are more likely to be implemented. \n\nThe aim of this project is to ditch the markdown viewer without trying to turn a markdown document into some kind of unholy WYSIAWYG (what you see is almost what you get). To that end, feature requests that hide markdown or otherwise introduce invisible formatting will be treated skeptically. The intention of this project is not to build the worlds best (most performant, beautiful, etc.) markdown editor, but rather to build something that can make my (and others!) life a little bit nicer.\n\nPlease make feature requests (please prepend the title with Feature Request) [on the github page](https://github.com/CalebJohn/joplin-rich-markdown/issues), or on the [rich markdown forum topic](https://discourse.joplinapp.org/t/plugin-rich-markdown/15053).\n\n# Tips\n\nGo to [Tips](https://github.com/CalebJohn/joplin-rich-markdown/blob/main/TIPS.md) to see a collection of CSS styling tips to get the most out of the Rich Markdown plugin.\n\n\n# Known Issues\n- file:// links only work with markdown link syntax (\\[\\]\\(\\) \\<\\>)\n- When hovering over an image on the bottom line, the image will be cut off\n\t- This can be fixed by scrolling the editor down enough to display the image\n- Links opened on Windows systems will be opened in the background (i.e. the browser won't jump to the front)\n- Images with a newline in the title won't be rendered in the editor\n  e.g.\n  ```\n  ![joplin\n  is cool](some_image_path)\n  ```\n"
  },
  {
    "path": "TIPS.md",
    "content": "# Tips\n\nNote: Most of these have been wrapped up in to the themes (available in the plugin settings). So make sure to check there before spending too much time with custom css.\n\nThe below is a collection of [userchrome.css](https://joplinapp.org/help/#custom-css) customizations that might be handy. If you have one of your own that you'd like added here, please make a [PR](https://github.com/CalebJohn/joplin-rich-markdown/pulls) or let me know [on the forum](https://discourse.joplinapp.org/t/plugin-rich-markdown/15053).\n\n## Horizontal Rule \n\n```css\n/* Render horizontal lines (made with \\-\\-\\- or \\*\\*\\*) as an actual line across the editor. */\ndiv.CodeMirror .cm-hr {\n  border-top: 1px solid #777;\n  display: block;\n  line-height: 0px;\n}\n```\n\n## Subtle Headers\n\n```css\n/* Reduce the size and visibility of the header hash tags. */\n/* The additional CSS option must be enabled */\ndiv.cm-editor .cm-header > .cm-rm-header-token {\n\tfont-size: 0.9em;\n\tcolor: grey;\n}\n```\n\n```css\n/* Additionally, this CSS unindent the \"#\" characters to align */\n/* the header text with the rest of the text */\ndiv.cm-editor .cm-header > .cm-rm-header-token {\n\tcolor: #cccccc;\n\tfont-size: 0.9em;\n\tmargin-left: -50px;\n\tmax-width: 50px;\n\twidth: 50px;\n\toverflow: hidden;\n\tdisplay: inline-block;\n\ttext-align: right;\n\topacity: 0.5;\n}\n```\n\nThanks to [uxamanda](https://discourse.joplinapp.org/t/plugin-rich-markdown/15053/105) for the code.\n\n## Subtle Tokens\n\n```css\n/* Reduces the intensity of the markdown tokens\ndiv.cm-editor .tok-meta {\n\topacity: 0.5;\n}\n\n/* This is also available for highlight and strikethrough, but it doesn't look very good */\n/*\n.cm-rm-highlight .cm-rm-highlight-token,\n.cm-strikethrough .cm-rm-strike-token,\n*/\n\n```\n\n## Strikeout Checkboxes\n```css\n/* strikeout and dim a checked checkbox */\ndiv.CodeMirror span.cm-rm-checkboxed {\n\ttext-decoration: line-through;\n\topacity: 0.7;\n}\n```\n\n## Highlight the Active Line\n```css\n/* Requires the \"highlight background of current line option to be enabled */\ndiv.CodeMirror .CodeMirror-activeline.CodeMirror-activeline-background {\n  background: grey !important;\n}\n```\n\n## List Spacing\n\n```css\n/* Match list spacing to the viewer */\ndiv.CodeMirror .cm-rm-list-token {\n  line-height: 2em;\n}\n```\n\n## Code Blocks\n\nCode block highlighting is implemented by the main Joplin app as of v2.3.4 and was removed from Rich Markdown  versions 0.8.0 onwards.\n\n\n## Colour Schemes\n\nEach Joplin theme uses a different CodeMirror colour scheme, it's useful to know what these colour schemes are because they can be used to support customizations that differ across Joplin themes (see [Coloured List Tokens](#coloured-list-tokens) for an example).\n\n```\nLight: .cm-s-default\nDark: .cm-s-material-darker\nSolarized Light: .cm-s-solarized\nSolarized Dark: .cm-s-solarized and .cm-s-solarized.cm-s-dark\nDracula: .cm-s-dracula\nNord: .cm-s-nord\nAritim Dark: .cm-s-monokai\nOLED Dark: .cm-s-material-darker\n```\n\n## General\n\nThe Joplin forum has [a collection](https://discourse.joplinapp.org/t/joplin-customization/11195) of useful CSS snippets for customizations that aren't specific to this plugin.\n\n---\n\n## Warning\n#### Checkmark Checkboxes\nMuch thanks to [ambrt](https://discourse.joplinapp.org/u/ambrt/) for the initial implementation.\n```css\n/* Requires the additional Css option to be enabled */\ndiv.CodeMirror .cm-rm-checkbox .cm-rm-checkbox-checked {\n  display: none;\n}\ndiv.CodeMirror .cm-rm-checkbox .cm-rm-checkbox-checked + .cm-taskMarker:before {\n    content: \"✓\";\n}\n```\n\n## Notes on legacy editor (pre 3.1.1)\nSome tokens had the class `.cm-overlay` pre-pended to them. This class is removed in the new editor, any styles that involved can simply have it removed. For example\n\n```diff\n- div.CodeMirror .cm-overlay.cm-rm-list-token {\n+ div.CodeMirror .cm-rm-list-token {\n```\nIf still using the legacy editor, please view the older [TIPS](https://github.com/CalebJohn/joplin-rich-markdown/blob/7d6bd9c984176a1a0d6fdc5683c34f03669967b5/TIPS.md)\n"
  },
  {
    "path": "api/Global.d.ts",
    "content": "import Plugin from '../Plugin';\nimport Joplin from './Joplin';\n/**\n * @ignore\n */\n/**\n * @ignore\n */\nexport default class Global {\n    private joplin_;\n    constructor(implementation: any, plugin: Plugin, store: any);\n    get joplin(): Joplin;\n    get process(): any;\n}\n"
  },
  {
    "path": "api/Joplin.d.ts",
    "content": "import Plugin from '../Plugin';\nimport JoplinData from './JoplinData';\nimport JoplinPlugins from './JoplinPlugins';\nimport JoplinWorkspace from './JoplinWorkspace';\nimport JoplinFilters from './JoplinFilters';\nimport JoplinCommands from './JoplinCommands';\nimport JoplinViews from './JoplinViews';\nimport JoplinInterop from './JoplinInterop';\nimport JoplinSettings from './JoplinSettings';\nimport JoplinContentScripts from './JoplinContentScripts';\nimport JoplinClipboard from './JoplinClipboard';\nimport JoplinWindow from './JoplinWindow';\nimport BasePlatformImplementation from '../BasePlatformImplementation';\nimport JoplinImaging from './JoplinImaging';\n/**\n * This is the main entry point to the Joplin API. You can access various services using the provided accessors.\n *\n * The API is now relatively stable and in general maintaining backward compatibility is a top priority, so you shouldn't except much breakages.\n *\n * If a breaking change ever becomes needed, best effort will be done to:\n *\n * - Deprecate features instead of removing them, so as to give you time to fix the issue;\n * - Document breaking changes in the changelog;\n *\n * 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.\n */\nexport default class Joplin {\n    private data_;\n    private plugins_;\n    private imaging_;\n    private workspace_;\n    private filters_;\n    private commands_;\n    private views_;\n    private interop_;\n    private settings_;\n    private contentScripts_;\n    private clipboard_;\n    private window_;\n    private implementation_;\n    constructor(implementation: BasePlatformImplementation, plugin: Plugin, store: any);\n    get data(): JoplinData;\n    get clipboard(): JoplinClipboard;\n    get imaging(): JoplinImaging;\n    get window(): JoplinWindow;\n    get plugins(): JoplinPlugins;\n    get workspace(): JoplinWorkspace;\n    get contentScripts(): JoplinContentScripts;\n    /**\n     * @ignore\n     *\n     * Not sure if it's the best way to hook into the app\n     * so for now disable filters.\n     */\n    get filters(): JoplinFilters;\n    get commands(): JoplinCommands;\n    get views(): JoplinViews;\n    get interop(): JoplinInterop;\n    get settings(): JoplinSettings;\n    /**\n     * It is not possible to bundle native packages with a plugin, because they\n     * need to work cross-platforms. Instead access to certain useful native\n     * packages is provided using this function.\n     *\n     * Currently these packages are available:\n     *\n     * - [sqlite3](https://www.npmjs.com/package/sqlite3)\n     * - [fs-extra](https://www.npmjs.com/package/fs-extra)\n     *\n     * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/nativeModule)\n     *\n     * <span class=\"platform-desktop\">desktop</span>\n     */\n    require(_path: string): any;\n    versionInfo(): Promise<import(\"./types\").VersionInfo>;\n}\n"
  },
  {
    "path": "api/JoplinClipboard.d.ts",
    "content": "export default class JoplinClipboard {\n    private electronClipboard_;\n    private electronNativeImage_;\n    constructor(electronClipboard: any, electronNativeImage: any);\n    readText(): Promise<string>;\n    writeText(text: string): Promise<void>;\n    /** <span class=\"platform-desktop\">desktop</span> */\n    readHtml(): Promise<string>;\n    /** <span class=\"platform-desktop\">desktop</span> */\n    writeHtml(html: string): Promise<void>;\n    /**\n     * Returns the image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.\n     *\n     * <span class=\"platform-desktop\">desktop</span>\n     */\n    readImage(): Promise<string>;\n    /**\n     * Takes an image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.\n     *\n     * <span class=\"platform-desktop\">desktop</span>\n     */\n    writeImage(dataUrl: string): Promise<void>;\n    /**\n     * Returns the list available formats (mime types).\n     *\n     * For example [ 'text/plain', 'text/html' ]\n     */\n    availableFormats(): Promise<string[]>;\n}\n"
  },
  {
    "path": "api/JoplinCommands.d.ts",
    "content": "import { Command } from './types';\nimport Plugin from '../Plugin';\n/**\n * This class allows executing or registering new Joplin commands. Commands\n * can be executed or associated with\n * {@link JoplinViewsToolbarButtons | toolbar buttons} or\n * {@link JoplinViewsMenuItems | menu items}.\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)\n *\n * ## Executing Joplin's internal commands\n *\n * It is also possible to execute internal Joplin's commands which, as of\n * now, are not well documented. You can find the list directly on GitHub\n * though at the following locations:\n *\n * * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)\n * * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)\n * * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts)\n *\n * To view what arguments are supported, you can open any of these files\n * and look at the `execute()` command.\n *\n * Note that many of these commands only work on desktop. The more limited list of mobile\n * commands can be found in these places:\n *\n * * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-mobile/commands)\n * * [Editor commands](https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/components/NoteEditor/commandDeclarations.ts)\n *\n * ## Executing editor commands\n *\n * There might be a situation where you want to invoke editor commands\n * without using a {@link JoplinContentScripts | contentScript}. For this\n * reason Joplin provides the built in `editor.execCommand` command.\n *\n * `editor.execCommand`  should work with any core command in both the\n * [CodeMirror](https://codemirror.net/doc/manual.html#execCommand) and\n * [TinyMCE](https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand) editors,\n * as well as most functions calls directly on a CodeMirror editor object (extensions).\n *\n * * [CodeMirror commands](https://codemirror.net/doc/manual.html#commands)\n * * [TinyMCE core editor commands](https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands)\n *\n * `editor.execCommand` supports adding arguments for the commands.\n *\n * ```typescript\n * await joplin.commands.execute('editor.execCommand', {\n *     name: 'madeUpCommand', // CodeMirror and TinyMCE\n *     args: [], // CodeMirror and TinyMCE\n *     ui: false, // TinyMCE only\n *     value: '', // TinyMCE only\n * });\n * ```\n *\n * [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)\n *\n */\nexport default class JoplinCommands {\n    private plugin_;\n    constructor(plugin_: Plugin);\n    /**\n     * Executes the given command.\n     *\n     * The command can take any number of arguments, and the supported\n     * arguments will vary based on the command. For custom commands, this\n     * is the `args` passed to the `execute()` function. For built-in\n     * commands, you can find the supported arguments by checking the links\n     * above.\n     *\n     * ```typescript\n     * // Create a new note in the current notebook:\n     * await joplin.commands.execute('newNote');\n     *\n     * // Create a new sub-notebook under the provided notebook\n     * // Note: internally, notebooks are called \"folders\".\n     * await joplin.commands.execute('newFolder', \"SOME_FOLDER_ID\");\n     * ```\n     */\n    execute(commandName: string, ...args: any[]): Promise<any | void>;\n    /**\n     * Registers a new command.\n     *\n     * ```typescript\n     * // Register a new commmand called \"testCommand1\"\n     *\n     * await joplin.commands.register({\n     *     name: 'testCommand1',\n     *     label: 'My Test Command 1',\n     *     iconName: 'fas fa-music',\n     *     execute: () => {\n     *         alert('Testing plugin command 1');\n     *     },\n     * });\n     * ```\n     */\n    register(command: Command): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinContentScripts.d.ts",
    "content": "import Plugin from '../Plugin';\nimport { ContentScriptType } from './types';\nexport default class JoplinContentScripts {\n    private plugin;\n    constructor(plugin: Plugin);\n    /**\n     * Registers a new content script. Unlike regular plugin code, which runs in\n     * a separate process, content scripts run within the main process code and\n     * thus allow improved performances and more customisations in specific\n     * cases. It can be used for example to load a Markdown or editor plugin.\n     *\n     * Note that registering a content script in itself will do nothing - it\n     * will only be loaded in specific cases by the relevant app modules (eg.\n     * the Markdown renderer or the code editor). So it is not a way to inject\n     * and run arbitrary code in the app, which for safety and performance\n     * reasons is not supported.\n     *\n     * The plugin generator provides a way to build any content script you might\n     * want to package as well as its dependencies. See the [Plugin Generator\n     * doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)\n     * for more information.\n     *\n     * * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)\n     * * [View the editor plugin tutorial](https://joplinapp.org/help/api/tutorials/cm6_plugin)\n     * * [View the legacy editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)\n     *\n     * See also the [postMessage demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)\n     *\n     * @param type Defines how the script will be used. See the type definition for more information about each supported type.\n     * @param id A unique ID for the content script.\n     * @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`.\n     */\n    register(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;\n    /**\n     * Listens to a messages sent from the content script using postMessage().\n     * See {@link ContentScriptType} for more information as well as the\n     * [postMessage\n     * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)\n     */\n    onMessage(contentScriptId: string, callback: any): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinData.d.ts",
    "content": "import { ModelType } from '../../../BaseModel';\nimport Plugin from '../Plugin';\nimport { Path } from './types';\n/**\n * This module provides access to the Joplin data API: https://joplinapp.org/help/api/references/rest_api\n * This is the main way to retrieve data, such as notes, notebooks, tags, etc.\n * or to update them or delete them.\n *\n * This is also what you would use to search notes, via the `search` endpoint.\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/simple)\n *\n * 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.\n * And each method takes these parameters:\n *\n * * `path`: This is an array that represents the path to the resource in the form `[\"resourceName\", \"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.\n * * `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.\n * * `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.\n * * `files`: (Optional) Used to create new resources and associate them with files.\n *\n * 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.\n *\n * For example:\n *\n * ```typescript\n * // Get a note ID, title and body\n * const noteId = 'some_note_id';\n * const note = await joplin.data.get(['notes', noteId], { fields: ['id', 'title', 'body'] });\n *\n * // Get all folders\n * const folders = await joplin.data.get(['folders']);\n *\n * // Set the note body\n * await joplin.data.put(['notes', noteId], null, { body: \"New note body\" });\n *\n * // Create a new note under one of the folders\n * await joplin.data.post(['notes'], null, { body: \"my new note\", title: \"some title\", parent_id: folders[0].id });\n * ```\n */\nexport default class JoplinData {\n    private api_;\n    private pathSegmentRegex_;\n    private plugin;\n    constructor(plugin: Plugin);\n    private serializeApiBody;\n    private pathToString;\n    get(path: Path, query?: any): Promise<any>;\n    post(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;\n    put(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;\n    delete(path: Path, query?: any): Promise<any>;\n    itemType(itemId: string): Promise<ModelType>;\n    resourcePath(resourceId: string): Promise<string>;\n    /**\n     * Gets an item user data. User data are key/value pairs. The `key` can be any\n     * arbitrary string, while the `value` can be of any type supported by\n     * [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description)\n     *\n     * User data is synchronised across devices, and each value wil be merged based on their timestamp:\n     *\n     * - If value is modified by client 1, then modified by client 2, it will take the value from client 2\n     * - If value is modified by client 1, then deleted by client 2, the value will be deleted after merge\n     * - 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\n     */\n    userDataGet<T>(itemType: ModelType, itemId: string, key: string): Promise<T>;\n    /**\n     * Sets a note user data. See {@link JoplinData.userDataGet} for more details.\n     */\n    userDataSet<T>(itemType: ModelType, itemId: string, key: string, value: T): Promise<void>;\n    /**\n     * Deletes a note user data. See {@link JoplinData.userDataGet} for more details.\n     */\n    userDataDelete(itemType: ModelType, itemId: string, key: string): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinFilters.d.ts",
    "content": "import { FilterHandler } from '../../../eventManager';\n/**\n * @ignore\n *\n * Not sure if it's the best way to hook into the app\n * so for now disable filters.\n */\nexport default class JoplinFilters {\n    on(name: string, callback: FilterHandler): Promise<void>;\n    off(name: string, callback: FilterHandler): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinImaging.d.ts",
    "content": "import { Rectangle } from './types';\nexport interface CreateFromBufferOptions {\n    width?: number;\n    height?: number;\n    scaleFactor?: number;\n}\nexport interface CreateFromPdfOptions {\n    /**\n     * The first page to export. Defaults to `1`, the first page in\n     * the document.\n     */\n    minPage?: number;\n    /**\n     * The number of the last page to convert. Defaults to the last page\n     * if not given.\n     *\n     * If `maxPage` is greater than the number of pages in the PDF, all pages\n     * in the PDF will be converted to images.\n     */\n    maxPage?: number;\n    scaleFactor?: number;\n}\nexport interface PdfInfo {\n    pageCount: number;\n}\nexport interface Implementation {\n    createFromPath: (path: string) => Promise<unknown>;\n    createFromPdf: (path: string, options: CreateFromPdfOptions) => Promise<unknown[]>;\n    getPdfInfo: (path: string) => Promise<PdfInfo>;\n}\nexport interface ResizeOptions {\n    width?: number;\n    height?: number;\n    quality?: 'good' | 'better' | 'best';\n}\nexport type Handle = string;\n/**\n * Provides imaging functions to resize or process images. You create an image\n * using one of the `createFrom` functions, then use the other functions to\n * process the image.\n *\n * Images are associated with a handle which is what will be available to the\n * plugin. Once you are done with an image, free it using the `free()` function.\n *\n * [View the\n * example](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/imaging/src/index.ts)\n *\n * <span class=\"platform-desktop\">desktop</span>\n */\nexport default class JoplinImaging {\n    private implementation_;\n    private images_;\n    constructor(implementation: Implementation);\n    private createImageHandle;\n    private imageByHandle;\n    private cacheImage;\n    /**\n     * Creates an image from the provided path. Note that images and PDFs are supported. If you\n     * provide a URL instead of a local path, the file will be downloaded first then converted to an\n     * image.\n     */\n    createFromPath(filePath: string): Promise<Handle>;\n    createFromResource(resourceId: string): Promise<Handle>;\n    createFromPdfPath(path: string, options?: CreateFromPdfOptions): Promise<Handle[]>;\n    createFromPdfResource(resourceId: string, options?: CreateFromPdfOptions): Promise<Handle[]>;\n    getPdfInfoFromPath(path: string): Promise<PdfInfo>;\n    getPdfInfoFromResource(resourceId: string): Promise<PdfInfo>;\n    getSize(handle: Handle): Promise<any>;\n    resize(handle: Handle, options?: ResizeOptions): Promise<string>;\n    crop(handle: Handle, rectangle: Rectangle): Promise<string>;\n    toPngFile(handle: Handle, filePath: string): Promise<void>;\n    /**\n     * Quality is between 0 and 100\n     */\n    toJpgFile(handle: Handle, filePath: string, quality?: number): Promise<void>;\n    private tempFilePath;\n    /**\n     * Creates a new Joplin resource from the image data. The image will be\n     * first converted to a JPEG.\n     */\n    toJpgResource(handle: Handle, resourceProps: any, quality?: number): Promise<import(\"../../database/types\").ResourceEntity>;\n    /**\n     * Creates a new Joplin resource from the image data. The image will be\n     * first converted to a PNG.\n     */\n    toPngResource(handle: Handle, resourceProps: any): Promise<import(\"../../database/types\").ResourceEntity>;\n    /**\n     * Image data is not automatically deleted by Joplin so make sure you call\n     * this method on the handle once you are done.\n     */\n    free(handles: Handle[] | Handle): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinInterop.d.ts",
    "content": "import { ExportModule, ImportModule } from './types';\n/**\n * Provides a way to create modules to import external data into Joplin or to export notes into any arbitrary format.\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export)\n *\n * To implement an import or export module, you would simply define an object with various event handlers that are called\n * by the application during the import/export process.\n *\n * See the documentation of the [[ExportModule]] and [[ImportModule]] for more information.\n *\n * 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\n *\n * <span class=\"platform-desktop\">desktop</span>: While it is possible to register import and export\n * modules on mobile, there is no GUI to activate them.\n */\nexport default class JoplinInterop {\n    registerExportModule(module: ExportModule): Promise<void>;\n    registerImportModule(module: ImportModule): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinPlugins.d.ts",
    "content": "import Plugin from '../Plugin';\nimport { ContentScriptType, Script } from './types';\n/**\n * This class provides access to plugin-related features.\n */\nexport default class JoplinPlugins {\n    private plugin;\n    constructor(plugin: Plugin);\n    /**\n     * 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.\n     * That `onStart` method will be executed as soon as the plugin is loaded.\n     *\n     * ```typescript\n     * joplin.plugins.register({\n     *     onStart: async function() {\n     *         // Run your plugin code here\n     *     }\n     * });\n     * ```\n     */\n    register(script: Script): Promise<void>;\n    /**\n     * @deprecated Use joplin.contentScripts.register()\n     */\n    registerContentScript(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;\n    /**\n     * Gets the plugin own data directory path. Use this to store any\n     * plugin-related data. Unlike [[installationDir]], any data stored here\n     * will be persisted.\n     */\n    dataDir(): Promise<string>;\n    /**\n     * Gets the plugin installation directory. This can be used to access any\n     * asset that was packaged with the plugin. This directory should be\n     * considered read-only because any data you store here might be deleted or\n     * re-created at any time. To store new persistent data, use [[dataDir]].\n     */\n    installationDir(): Promise<string>;\n    /**\n     * @deprecated Use joplin.require()\n     */\n    require(_path: string): any;\n}\n"
  },
  {
    "path": "api/JoplinSettings.d.ts",
    "content": "import Plugin from '../Plugin';\nimport { SettingItem, SettingSection } from './types';\nexport interface ChangeEvent {\n    /**\n     * Setting keys that have been changed\n     */\n    keys: string[];\n}\nexport type ChangeHandler = (event: ChangeEvent) => void;\n/**\n * 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.\n *\n * Settings are essentially key/value pairs.\n *\n * 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\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/settings)\n */\nexport default class JoplinSettings {\n    private plugin_;\n    constructor(plugin: Plugin);\n    /**\n     * Registers new settings.\n     * Note that registering a setting item is dynamic and will be gone next time Joplin starts.\n     * What it means is that you need to register the setting every time the plugin starts (for example in the onStart event).\n     * 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\n     * reason the plugin fails to start at some point.\n     */\n    registerSettings(settings: Record<string, SettingItem>): Promise<void>;\n    /**\n     * @deprecated Use joplin.settings.registerSettings()\n     *\n     * Registers a new setting.\n     */\n    registerSetting(key: string, settingItem: SettingItem): Promise<void>;\n    /**\n     * Registers a new setting section. Like for registerSetting, it is dynamic and needs to be done every time the plugin starts.\n     */\n    registerSection(name: string, section: SettingSection): Promise<void>;\n    /**\n     * Gets setting values (only applies to setting you registered from your plugin)\n     */\n    values(keys: string[] | string): Promise<Record<string, unknown>>;\n    /**\n     * @deprecated Use joplin.settings.values()\n     *\n     * Gets a setting value (only applies to setting you registered from your plugin)\n     */\n    value(key: string): Promise<any>;\n    /**\n     * Sets a setting value (only applies to setting you registered from your plugin)\n     */\n    setValue(key: string, value: any): Promise<void>;\n    /**\n     * Gets a global setting value, including app-specific settings and those set by other plugins.\n     *\n     * The list of available settings is not documented yet, but can be found by looking at the source code:\n     *\n     * https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142\n     */\n    globalValue(key: string): Promise<any>;\n    /**\n     * Called when one or multiple settings of your plugin have been changed.\n     * - For performance reasons, this event is triggered with a delay.\n     * - You will only get events for your own plugin settings.\n     */\n    onChange(handler: ChangeHandler): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinViews.d.ts",
    "content": "import Plugin from '../Plugin';\nimport JoplinViewsDialogs from './JoplinViewsDialogs';\nimport JoplinViewsMenuItems from './JoplinViewsMenuItems';\nimport JoplinViewsMenus from './JoplinViewsMenus';\nimport JoplinViewsToolbarButtons from './JoplinViewsToolbarButtons';\nimport JoplinViewsPanels from './JoplinViewsPanels';\nimport JoplinViewsNoteList from './JoplinViewsNoteList';\nimport JoplinViewsEditors from './JoplinViewsEditor';\n/**\n * This namespace provides access to view-related services.\n *\n * 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.\n * 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.\n */\nexport default class JoplinViews {\n    private store;\n    private plugin;\n    private panels_;\n    private menuItems_;\n    private menus_;\n    private toolbarButtons_;\n    private dialogs_;\n    private editors_;\n    private noteList_;\n    private implementation_;\n    constructor(implementation: any, plugin: Plugin, store: any);\n    get dialogs(): JoplinViewsDialogs;\n    get panels(): JoplinViewsPanels;\n    get editors(): JoplinViewsEditors;\n    get menuItems(): JoplinViewsMenuItems;\n    get menus(): JoplinViewsMenus;\n    get toolbarButtons(): JoplinViewsToolbarButtons;\n    get noteList(): JoplinViewsNoteList;\n}\n"
  },
  {
    "path": "api/JoplinViewsDialogs.d.ts",
    "content": "import Plugin from '../Plugin';\nimport { ButtonSpec, ViewHandle, DialogResult } from './types';\n/**\n * Allows creating and managing dialogs. A dialog is modal window that\n * contains a webview and a row of buttons. You can update the\n * webview using the `setHtml` method. Dialogs are hidden by default and\n * you need to call `open()` to open them. Once the user clicks on a\n * button, the `open` call will return an object indicating what button was\n * clicked on.\n *\n * ## Retrieving form values\n *\n * If your HTML content included one or more forms, a `formData` object\n * will also be included with the key/value for each form.\n *\n * ## Special button IDs\n *\n * The following buttons IDs have a special meaning:\n *\n * - `ok`, `yes`, `submit`, `confirm`: They are considered \"submit\" buttons\n * - `cancel`, `no`, `reject`: They are considered \"dismiss\" buttons\n *\n * This information is used by the application to determine what action\n * should be done when the user presses \"Enter\" or \"Escape\" within the\n * dialog. If they press \"Enter\", the first \"submit\" button will be\n * automatically clicked. If they press \"Escape\" the first \"dismiss\" button\n * will be automatically clicked.\n *\n * [View the demo\n * plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/dialog)\n */\nexport default class JoplinViewsDialogs {\n    private store;\n    private plugin;\n    private implementation_;\n    constructor(implementation: any, plugin: Plugin, store: any);\n    private controller;\n    /**\n     * Creates a new dialog\n     */\n    create(id: string): Promise<ViewHandle>;\n    /**\n     * Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - \"0\" for OK and \"1\" for \"Cancel\"\n     */\n    showMessageBox(message: string): Promise<number>;\n    /**\n     * Displays a dialog to select a file or a directory. Same options and\n     * output as\n     * https://www.electronjs.org/docs/latest/api/dialog#dialogshowopendialogbrowserwindow-options\n     *\n     * <span class=\"platform-desktop\">desktop</span>\n     */\n    showOpenDialog(options: any): Promise<any>;\n    /**\n     * Sets the dialog HTML content\n     */\n    setHtml(handle: ViewHandle, html: string): Promise<string>;\n    /**\n     * Adds and loads a new JS or CSS files into the dialog.\n     */\n    addScript(handle: ViewHandle, scriptPath: string): Promise<void>;\n    /**\n     * Sets the dialog buttons.\n     */\n    setButtons(handle: ViewHandle, buttons: ButtonSpec[]): Promise<ButtonSpec[]>;\n    /**\n     * Opens the dialog.\n     *\n     * On desktop, this closes any copies of the dialog open in different windows.\n     */\n    open(handle: ViewHandle): Promise<DialogResult>;\n    /**\n     * Toggle on whether to fit the dialog size to the content or not.\n     * When set to false, the dialog is set to 90vw and 80vh\n     * @default true\n     */\n    setFitToContent(handle: ViewHandle, status: boolean): Promise<boolean>;\n}\n"
  },
  {
    "path": "api/JoplinViewsEditor.d.ts",
    "content": "import Plugin from '../Plugin';\nimport { ActivationCheckCallback, ViewHandle, UpdateCallback } from './types';\n/**\n * Allows creating alternative note editors. You can create a view to handle loading and saving the\n * note, and do your own rendering.\n *\n * Although it may be used to implement an alternative text editor, the more common use case may be\n * to render the note in a different, graphical way - for example displaying a graph, and\n * saving/loading the graph data in the associated note. In that case, you would detect whether the\n * current note contains graph data and, in this case, you'd display your viewer.\n *\n * Terminology: An editor is **active** when it can be used to edit the current note. Note that it\n * doesn't necessarily mean that your editor is visible - it just means that the user has the option\n * to switch to it (via the \"toggle editor\" button). A **visible** editor is active and is currently\n * being displayed.\n *\n * To implement an editor you need to listen to two events:\n *\n * - `onActivationCheck`: This is a way for the app to know whether your editor should be active or\n *   not. Return `true` from this handler to activate your editor.\n *\n * - `onUpdate`: When this is called you should update your editor based on the current note\n *   content. Call `joplin.workspace.selectedNote()` to get the current note.\n *\n * - `showEditorPlugin` and `toggleEditorPlugin` commands. Additionally you can use these commands\n *   to display your editor via `joplin.commands.execute('showEditorPlugin')`. This is not always\n *   necessary since the user can switch to your editor using the \"toggle editor\" button, however\n *   you may want to programmatically display the editor in some cases - for example when creating a\n *   new note specific to your editor.\n *\n * Note that only one editor view can be active at a time. This is why it is important not to\n * activate your view if it's not relevant to the current note. If more than one is active, it is\n * undefined which editor is going to be used to display the note.\n *\n * For an example of editor plugin, see the [YesYouKan\n * plugin](https://github.com/joplin/plugin-yesyoukan/blob/master/src/index.ts). In particular,\n * check the logic around `onActivationCheck` and `onUpdate` since this is the entry points for\n * using this API.\n */\nexport default class JoplinViewsEditors {\n    private store;\n    private plugin;\n    private activationCheckHandlers_;\n    constructor(plugin: Plugin, store: any);\n    private controller;\n    /**\n     * Creates a new editor view\n     */\n    create(id: string): Promise<ViewHandle>;\n    /**\n     * Sets the editor HTML content\n     */\n    setHtml(handle: ViewHandle, html: string): Promise<string>;\n    /**\n     * Adds and loads a new JS or CSS file into the panel.\n     */\n    addScript(handle: ViewHandle, scriptPath: string): Promise<void>;\n    /**\n     * See [[JoplinViewPanels]]\n     */\n    onMessage(handle: ViewHandle, callback: Function): Promise<void>;\n    /**\n     * Emitted when the editor can potentially be activated - this for example when the current note\n     * is changed, or when the application is opened. At that point should can check the current\n     * note and decide whether your editor should be activated or not. If it should return `true`,\n     * otherwise return `false`.\n     */\n    onActivationCheck(handle: ViewHandle, callback: ActivationCheckCallback): Promise<void>;\n    /**\n     * Emitted when the editor content should be updated. This for example when the currently\n     * selected note changes, or when the user makes the editor visible.\n     */\n    onUpdate(handle: ViewHandle, callback: UpdateCallback): Promise<void>;\n    /**\n     * See [[JoplinViewPanels]]\n     */\n    postMessage(handle: ViewHandle, message: any): void;\n    /**\n     * Tells whether the editor is active or not.\n     */\n    isActive(handle: ViewHandle): Promise<boolean>;\n    /**\n     * Tells whether the editor is effectively visible or not. If the editor is inactive, this will\n     * return `false`. If the editor is active and the user has switched to it, it will return\n     * `true`. Otherwise it will return `false`.\n     */\n    isVisible(handle: ViewHandle): Promise<boolean>;\n}\n"
  },
  {
    "path": "api/JoplinViewsMenuItems.d.ts",
    "content": "import { CreateMenuItemOptions, MenuItemLocation } from './types';\nimport Plugin from '../Plugin';\n/**\n * Allows creating and managing menu items.\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)\n *\n * <span class=\"platform-desktop\">desktop</span>\n */\nexport default class JoplinViewsMenuItems {\n    private store;\n    private plugin;\n    constructor(plugin: Plugin, store: any);\n    /**\n     * 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.\n     */\n    create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinViewsMenus.d.ts",
    "content": "import { MenuItem, MenuItemLocation } from './types';\nimport Plugin from '../Plugin';\n/**\n * Allows creating menus.\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/menu)\n *\n * <span class=\"platform-desktop\">desktop</span>\n */\nexport default class JoplinViewsMenus {\n    private store;\n    private plugin;\n    constructor(plugin: Plugin, store: any);\n    private registerCommandAccelerators;\n    /**\n     * 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\n     * menu as a sub-menu of the application build-in menus.\n     */\n    create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinViewsNoteList.d.ts",
    "content": "import { Store } from 'redux';\nimport Plugin from '../Plugin';\nimport { ListRenderer } from './noteListType';\n/**\n * This API allows you to customise how each note in the note list is rendered.\n * The renderer you implement follows a unidirectional data flow.\n *\n * The app provides the required dependencies whenever a note is updated - you\n * process these dependencies, and return some props, which are then passed to\n * your template and rendered. See [[ListRenderer]] for a detailed description\n * of each property of the renderer.\n *\n * ## Reference\n *\n * * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/note_list_renderer)\n *\n * * [Default simple renderer](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/noteList/defaultListRenderer.ts)\n *\n * * [Default detailed renderer](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/noteList/defaultMultiColumnsRenderer.ts)\n *\n * ## Screenshots:\n *\n * ### Top to bottom with title, date and body\n *\n * <img width=\"250px\" src=\"https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/note_list/TopToBottom.png\"/>\n *\n * ### Left to right with thumbnails\n *\n * <img width=\"250px\" src=\"https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/note_list/LeftToRight_Thumbnails.png\"/>\n *\n * ### Top to bottom with editable title\n *\n * <img width=\"250px\" src=\"https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/note_list/TopToBottom_Editable.png\"/>\n *\n * <span class=\"platform-desktop\">desktop</span>\n */\nexport default class JoplinViewsNoteList {\n    private plugin_;\n    private store_;\n    constructor(plugin: Plugin, store: Store);\n    registerRenderer(renderer: ListRenderer): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinViewsPanels.d.ts",
    "content": "import Plugin from '../Plugin';\nimport { ViewHandle } from './types';\n/**\n * Allows creating and managing view panels. View panels allow displaying any HTML\n * content (within a webview) and updating it in real-time. For example it\n * could be used to display a table of content for the active note, or\n * display various metadata or graph.\n *\n * On desktop, view panels currently are displayed at the right of the sidebar, though can\n * be moved with \"View\" > \"Change application layout\".\n *\n * On mobile, view panels are shown in a tabbed dialog that can be opened using a\n * toolbar button.\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)\n */\nexport default class JoplinViewsPanels {\n    private store;\n    private plugin;\n    constructor(plugin: Plugin, store: any);\n    private controller;\n    /**\n     * Creates a new panel\n     */\n    create(id: string): Promise<ViewHandle>;\n    /**\n     * Sets the panel webview HTML\n     */\n    setHtml(handle: ViewHandle, html: string): Promise<string>;\n    /**\n     * Adds and loads a new JS or CSS files into the panel.\n     */\n    addScript(handle: ViewHandle, scriptPath: string): Promise<void>;\n    /**\n     * Called when a message is sent from the webview (using postMessage).\n     *\n     * To post a message from the webview to the plugin use:\n     *\n     * ```javascript\n     * const response = await webviewApi.postMessage(message);\n     * ```\n     *\n     * - `message` can be any JavaScript object, string or number\n     * - `response` is whatever was returned by the `onMessage` handler\n     *\n     * Using this mechanism, you can have two-way communication between the\n     * plugin and webview.\n     *\n     * See the [postMessage\n     * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages) for more details.\n     *\n     */\n    onMessage(handle: ViewHandle, callback: Function): Promise<void>;\n    /**\n     * Sends a message to the webview.\n     *\n     * The webview must have registered a message handler prior, otherwise the message is ignored. Use;\n     *\n     * ```javascript\n     * webviewApi.onMessage((message) => { ... });\n     * ```\n     *\n     *  - `message` can be any JavaScript object, string or number\n     *\n     * The view API may have only one onMessage handler defined.\n     * This method is fire and forget so no response is returned.\n     *\n     * It is particularly useful when the webview needs to react to events emitted by the plugin or the joplin api.\n     */\n    postMessage(handle: ViewHandle, message: any): void;\n    /**\n     * Shows the panel\n     */\n    show(handle: ViewHandle, show?: boolean): Promise<void>;\n    /**\n     * Hides the panel\n     */\n    hide(handle: ViewHandle): Promise<void>;\n    /**\n     * Tells whether the panel is visible or not\n     */\n    visible(handle: ViewHandle): Promise<boolean>;\n    isActive(handle: ViewHandle): Promise<boolean>;\n}\n"
  },
  {
    "path": "api/JoplinViewsToolbarButtons.d.ts",
    "content": "import { ToolbarButtonLocation } from './types';\nimport Plugin from '../Plugin';\n/**\n * Allows creating and managing toolbar buttons.\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)\n */\nexport default class JoplinViewsToolbarButtons {\n    private store;\n    private plugin;\n    constructor(plugin: Plugin, store: any);\n    /**\n     * Creates a new toolbar button and associate it with the given command.\n     */\n    create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinWindow.d.ts",
    "content": "import Plugin from '../Plugin';\nexport default class JoplinWindow {\n    private store_;\n    constructor(_plugin: Plugin, store: any);\n    /**\n     * Loads a chrome CSS file. It will apply to the window UI elements, except\n     * for the note viewer. It is the same as the \"Custom stylesheet for\n     * 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)\n     * for an example.\n     *\n     * <span class=\"platform-desktop\">desktop</span>\n     */\n    loadChromeCssFile(filePath: string): Promise<void>;\n    /**\n     * Loads a note CSS file. It will apply to the note viewer, as well as any\n     * exported or printed note. It is the same as the \"Custom stylesheet for\n     * rendered Markdown\" setting. See the [Load CSS Demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/load_css)\n     * for an example.\n     *\n     * <span class=\"platform-desktop\">desktop</span>\n     */\n    loadNoteCssFile(filePath: string): Promise<void>;\n}\n"
  },
  {
    "path": "api/JoplinWorkspace.d.ts",
    "content": "import Plugin from '../Plugin';\nimport { FolderEntity } from '../../database/types';\nimport { Disposable, EditContextMenuFilterObject, FilterHandler } from './types';\ndeclare enum ItemChangeEventType {\n    Create = 1,\n    Update = 2,\n    Delete = 3\n}\ninterface ItemChangeEvent {\n    id: string;\n    event: ItemChangeEventType;\n}\ninterface ResourceChangeEvent {\n    id: string;\n}\ninterface NoteContentChangeEvent {\n    note: any;\n}\ninterface NoteSelectionChangeEvent {\n    value: string[];\n}\ninterface NoteAlarmTriggerEvent {\n    noteId: string;\n}\ninterface SyncCompleteEvent {\n    withErrors: boolean;\n}\ntype WorkspaceEventHandler<EventType> = (event: EventType) => void;\ntype ItemChangeHandler = WorkspaceEventHandler<ItemChangeEvent>;\ntype SyncStartHandler = () => void;\ntype ResourceChangeHandler = WorkspaceEventHandler<ResourceChangeEvent>;\n/**\n * The workspace service provides access to all the parts of Joplin that\n * are being worked on - i.e. the currently selected notes or notebooks as\n * well as various related events, such as when a new note is selected, or\n * when the note content changes.\n *\n * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins)\n */\nexport default class JoplinWorkspace {\n    private store;\n    private plugin;\n    constructor(plugin: Plugin, store: any);\n    /**\n     * Called when a new note or notes are selected.\n     */\n    onNoteSelectionChange(callback: WorkspaceEventHandler<NoteSelectionChangeEvent>): Promise<Disposable>;\n    /**\n     * Called when the content of a note changes.\n     * @deprecated Use `onNoteChange()` instead, which is reliably triggered whenever the note content, or any note property changes.\n     */\n    onNoteContentChange(callback: WorkspaceEventHandler<NoteContentChangeEvent>): Promise<void>;\n    /**\n     * Called when the content of the current note changes.\n     */\n    onNoteChange(handler: ItemChangeHandler): Promise<Disposable>;\n    /**\n     * Called when a resource is changed. Currently this handled will not be\n     * called when a resource is added or deleted.\n     */\n    onResourceChange(handler: ResourceChangeHandler): Promise<void>;\n    /**\n     * Called when an alarm associated with a to-do is triggered.\n     */\n    onNoteAlarmTrigger(handler: WorkspaceEventHandler<NoteAlarmTriggerEvent>): Promise<Disposable>;\n    /**\n     * Called when the synchronisation process is starting.\n     */\n    onSyncStart(handler: SyncStartHandler): Promise<Disposable>;\n    /**\n     * Called when the synchronisation process has finished.\n     */\n    onSyncComplete(callback: WorkspaceEventHandler<SyncCompleteEvent>): Promise<Disposable>;\n    /**\n     * Called just before the editor context menu is about to open. Allows\n     * adding items to it.\n     *\n     * <span class=\"platform-desktop\">desktop</span>\n     */\n    filterEditorContextMenu(handler: FilterHandler<EditContextMenuFilterObject>): void;\n    /**\n     * Gets the currently selected note. Will be `null` if no note is selected.\n     */\n    selectedNote(): Promise<any>;\n    /**\n     * Gets the currently selected folder. In some cases, for example during\n     * search or when viewing a tag, no folder is actually selected in the user\n     * interface. In that case, that function would return the last selected\n     * folder.\n     */\n    selectedFolder(): Promise<FolderEntity>;\n    /**\n     * Gets the IDs of the selected notes (can be zero, one, or many). Use the data API to retrieve information about these notes.\n     */\n    selectedNoteIds(): Promise<string[]>;\n}\nexport {};\n"
  },
  {
    "path": "api/index.ts",
    "content": "import type Joplin from './Joplin';\n\ndeclare const joplin: Joplin;\n\nexport default joplin;\n"
  },
  {
    "path": "api/noteListType.d.ts",
    "content": "import { Size } from './types';\ntype ListRendererDatabaseDependency = 'folder.created_time' | 'folder.deleted_time' | 'folder.encryption_applied' | 'folder.encryption_cipher_text' | 'folder.icon' | 'folder.id' | 'folder.is_shared' | 'folder.master_key_id' | 'folder.parent_id' | 'folder.share_id' | 'folder.title' | 'folder.updated_time' | 'folder.user_created_time' | 'folder.user_data' | 'folder.user_updated_time' | 'folder.type_' | 'note.altitude' | 'note.application_data' | 'note.author' | 'note.body' | 'note.conflict_original_id' | 'note.created_time' | 'note.deleted_time' | 'note.encryption_applied' | 'note.encryption_cipher_text' | 'note.id' | 'note.is_conflict' | 'note.is_shared' | 'note.is_todo' | 'note.latitude' | 'note.longitude' | 'note.markup_language' | 'note.master_key_id' | 'note.order' | 'note.parent_id' | 'note.share_id' | 'note.source' | 'note.source_application' | 'note.source_url' | 'note.title' | 'note.todo_completed' | 'note.todo_due' | 'note.updated_time' | 'note.user_created_time' | 'note.user_data' | 'note.user_updated_time' | 'note.type_';\nexport declare enum ItemFlow {\n    TopToBottom = \"topToBottom\",\n    LeftToRight = \"leftToRight\"\n}\nexport type RenderNoteView = Record<string, any>;\nexport interface OnChangeEvent {\n    elementId: string;\n    value: any;\n    noteId: string;\n}\nexport interface OnClickEvent {\n    elementId: string;\n}\nexport type OnRenderNoteHandler = (props: any) => Promise<RenderNoteView>;\nexport type OnChangeHandler = (event: OnChangeEvent) => Promise<void>;\nexport type OnClickHandler = (event: OnClickEvent) => Promise<void>;\n/**\n * Most of these are the built-in note properties, such as `note.title`, `note.todo_completed`, etc.\n * complemented with special properties such as `note.isWatched`, to know if a note is currently\n * opened in the external editor, and `note.tags` to get the list tags associated with the note.\n *\n * The `note.todoStatusText` property is a localised description of the to-do status (e.g.\n * \"to-do, incomplete\"). If you include an `<input type='checkbox' ... />` for to-do items that would\n * otherwise be unlabelled, consider adding `note.todoStatusText` as the checkbox's `aria-label`.\n *\n * ## Item properties\n *\n * The `item.*` properties are specific to the rendered item. The most important being\n * `item.selected`, which you can use to display the selected note in a different way.\n */\nexport type ListRendererDependency = ListRendererDatabaseDependency | 'item.index' | 'item.selected' | 'item.size.height' | 'item.size.width' | 'note.folder.title' | 'note.isWatched' | 'note.tags' | 'note.todoStatusText' | 'note.titleHtml';\nexport type ListRendererItemValueTemplates = Record<string, string>;\nexport declare const columnNames: readonly [\"note.folder.title\", \"note.is_todo\", \"note.latitude\", \"note.longitude\", \"note.source_url\", \"note.tags\", \"note.title\", \"note.todo_completed\", \"note.todo_due\", \"note.user_created_time\", \"note.user_updated_time\"];\nexport type ColumnName = typeof columnNames[number];\nexport interface ListRenderer {\n    /**\n     * It must be unique to your plugin.\n     */\n    id: string;\n    /**\n     * Can be top to bottom or left to right. Left to right gives you more\n     * option to set the size of the items since you set both its width and\n     * height.\n     */\n    flow: ItemFlow;\n    /**\n     * Whether the renderer supports multiple columns. Applies only when `flow`\n     * is `topToBottom`. Defaults to `false`.\n     */\n    multiColumns?: boolean;\n    /**\n     * The size of each item must be specified in advance for performance\n     * reasons, and cannot be changed afterwards. If the item flow is top to\n     * bottom, you only need to specify the item height (the width will be\n     * ignored).\n     */\n    itemSize: Size;\n    /**\n     * The CSS is relative to the list item container. What will appear in the\n     * page is essentially `.note-list-item { YOUR_CSS; }`. It means you can use\n     * child combinator with guarantee it will only apply to your own items. In\n     * this example, the styling will apply to `.note-list-item > .content`:\n     *\n     * ```css\n     * > .content {\n     *     padding: 10px;\n     * }\n     * ```\n     *\n     * In order to get syntax highlighting working here, it's recommended\n     * installing an editor extension such as [es6-string-html VSCode\n     * extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)\n     */\n    itemCss?: string;\n    /**\n     * List the dependencies that your plugin needs to render the note list\n     * items. Only these will be passed to your `onRenderNote` handler. Ensure\n     * that you do not add more than what you need since there is a performance\n     * penalty for each property.\n     */\n    dependencies?: ListRendererDependency[];\n    headerTemplate?: string;\n    headerHeight?: number;\n    onHeaderClick?: OnClickHandler;\n    /**\n     * This property is set differently depending on the `multiColumns` property.\n     *\n     * ## If `multiColumns` is `false`\n     *\n     * There is only one column and the template is used to render the entire row.\n     *\n     * This is the HTML template that will be used to render the note list item. This is a [Mustache\n     * template](https://github.com/janl/mustache.js) and it will receive the variable you return\n     * from `onRenderNote` as tags. For example, if you return a property named `formattedDate` from\n     * `onRenderNote`, you can insert it in the template using `Created date: {{formattedDate}}`\n     *\n     * ## If `multiColumns` is `true`\n     *\n     * Since there is multiple columns, this template will be used to render each note property\n     * within the row. For example if the current columns are the Updated and Title properties, this\n     * template will be called once to render the updated time and a second time to render the\n     * title. To display the current property, the generic `value` property is provided - it will be\n     * replaced at runtime by the actual note property. To render something different depending on\n     * the note property, use `itemValueTemplate`. A minimal example would be\n     * `<span>{{value}}</span>` which will simply render the current property inside a span tag.\n     *\n     * In order to get syntax highlighting working here, it's recommended installing an editor\n     * extension such as [es6-string-html VSCode\n     * extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)\n     *\n     * ## Default property rendering\n     *\n     * Certain properties are automatically rendered once inserted in the Mustache template. Those\n     * are in particular all the date-related fields, such as `note.user_updated_time` or\n     * `note.todo_completed`. Internally, those are timestamps in milliseconds, however when\n     * rendered we display them as date/time strings using the user's preferred time format. Another\n     * notable auto-rendered property is `note.title` which is going to include additional HTML,\n     * such as the search markers.\n     *\n     * If you do not want this default rendering behaviour, for example if you want to display the\n     * raw timestamps in milliseconds, you can simply return custom properties from\n     * `onRenderNote()`. For example:\n     *\n     * ```typescript\n     * onRenderNote: async (props: any) => {\n     *     return {\n     *         ...props,\n     *         // Return the property under a different name\n     *         updatedTimeMs: props.note.user_updated_time,\n     *     }\n     * },\n     *\n     * itemTemplate: // html\n     *     `\n     *     <div>\n     *         Raw timestamp: {{updatedTimeMs}} <!-- This is **not** auto-rendered ->\n     *         Formatted time: {{note.user_updated_time}} <!-- This is -->\n     *     </div>\n     * `,\n     *\n     * ```\n     *\n     * See\n     * `[https://github.com/laurent22/joplin/blob/dev/packages/lib/services/noteList/renderViewProps.ts](renderViewProps.ts)`\n     * for the list of properties that have a default rendering.\n     */\n    itemTemplate: string;\n    /**\n     * This property applies only when `multiColumns` is `true`. It is used to render something\n     * different for each note property.\n     *\n     * This is a map of actual dependencies to templates - you only need to return something if the\n     * default, as specified in `template`, is not enough.\n     *\n     * Again you need to return a Mustache template and it will be combined with the `template`\n     * property to create the final template. For example if you return a property named\n     * `formattedDate` from `onRenderNote`, you can insert it in the template using\n     * `{{formattedDate}}`. This string will replace `{{value}}` in the `template` property.\n     *\n     * So if the template property is set to `<span>{{value}}</span>`, the final template will be\n     * `<span>{{formattedDate}}</span>`.\n     *\n     * The property would be set as so:\n     *\n     * ```javascript\n     * itemValueTemplates: {\n     *     'note.user_updated_time': '{{formattedDate}}',\n     * }\n     * ```\n     */\n    itemValueTemplates?: ListRendererItemValueTemplates;\n    /**\n     * This user-facing text is used for example in the View menu, so that your\n     * renderer can be selected.\n     */\n    label: () => Promise<string>;\n    /**\n     * This is where most of the real-time processing will happen. When a note\n     * is rendered for the first time and every time it changes, this handler\n     * receives the properties specified in the `dependencies` property. You can\n     * then process them, load any additional data you need, and once done you\n     * need to return the properties that are needed in the `itemTemplate` HTML.\n     * Again, to use the formatted date example, you could have such a renderer:\n     *\n     * ```typescript\n     * dependencies: [\n     *     'note.title',\n     *     'note.created_time',\n     * ],\n     *\n     * itemTemplate: // html\n     *     `\n     *     <div>\n     *         Title: {{note.title}}<br/>\n     *         Date: {{formattedDate}}\n     *     </div>\n     * `,\n     *\n     * onRenderNote: async (props: any) => {\n     *     const formattedDate = dayjs(props.note.created_time).format();\n     *     return {\n     *         // Also return the props, so that note.title is available from the\n     *         // template\n     *         ...props,\n     *         formattedDate,\n     *     }\n     * },\n     * ```\n     */\n    onRenderNote: OnRenderNoteHandler;\n    /**\n     * This handler allows adding some interactivity to the note renderer - whenever an input element\n     * within the item is changed (for example, when a checkbox is clicked, or a text input is\n     * changed), this `onChange` handler is going to be called.\n     *\n     * You can inspect `event.elementId` to know which element had some changes, and `event.value`\n     * to know the new value. `event.noteId` also tells you what note is affected, so that you can\n     * potentially apply changes to it.\n     *\n     * You specify the element ID, by setting a `data-id` attribute on the input.\n     *\n     * For example, if you have such a template:\n     *\n     * ```html\n     * <div>\n     *     <input type=\"text\" value=\"{{note.title}}\" data-id=\"noteTitleInput\"/>\n     * </div>\n     * ```\n     *\n     * The event handler will receive an event with `elementId` set to `noteTitleInput`.\n     *\n     * ## Default event handlers\n     *\n     * Currently one click event is automatically handled:\n     *\n     * If there is a checkbox with a `data-id=\"todo-checkbox\"` attribute is present, it is going to\n     * automatically toggle the note to-do \"completed\" status.\n     *\n     * For example this is what is used in the default list renderer:\n     *\n     * `<input data-id=\"todo-checkbox\" type=\"checkbox\" {{#note.todo_completed}}checked=\"checked\"{{/note.todo_completed}}>`\n     */\n    onChange?: OnChangeHandler;\n}\nexport interface NoteListColumn {\n    name: ColumnName;\n    width: number;\n}\nexport type NoteListColumns = NoteListColumn[];\nexport declare const defaultWidth = 100;\nexport declare const defaultListColumns: () => NoteListColumns;\nexport {};\n"
  },
  {
    "path": "api/noteListType.ts",
    "content": "/* eslint-disable multiline-comment-style */\n\nimport { Size } from './types';\n\n// AUTO-GENERATED by generate-database-type\ntype ListRendererDatabaseDependency = 'folder.created_time' | 'folder.deleted_time' | 'folder.encryption_applied' | 'folder.encryption_cipher_text' | 'folder.icon' | 'folder.id' | 'folder.is_shared' | 'folder.master_key_id' | 'folder.parent_id' | 'folder.share_id' | 'folder.title' | 'folder.updated_time' | 'folder.user_created_time' | 'folder.user_data' | 'folder.user_updated_time' | 'folder.type_' | 'note.altitude' | 'note.application_data' | 'note.author' | 'note.body' | 'note.conflict_original_id' | 'note.created_time' | 'note.deleted_time' | 'note.encryption_applied' | 'note.encryption_cipher_text' | 'note.id' | 'note.is_conflict' | 'note.is_shared' | 'note.is_todo' | 'note.latitude' | 'note.longitude' | 'note.markup_language' | 'note.master_key_id' | 'note.order' | 'note.parent_id' | 'note.share_id' | 'note.source' | 'note.source_application' | 'note.source_url' | 'note.title' | 'note.todo_completed' | 'note.todo_due' | 'note.updated_time' | 'note.user_created_time' | 'note.user_data' | 'note.user_updated_time' | 'note.type_';\n// AUTO-GENERATED by generate-database-type\n\nexport enum ItemFlow {\n\tTopToBottom = 'topToBottom',\n\tLeftToRight = 'leftToRight',\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\nexport type RenderNoteView = Record<string, any>;\n\nexport interface OnChangeEvent {\n\telementId: string;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tvalue: any;\n\tnoteId: string;\n}\n\nexport interface OnClickEvent {\n\telementId: string;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\nexport type OnRenderNoteHandler = (props: any)=> Promise<RenderNoteView>;\nexport type OnChangeHandler = (event: OnChangeEvent)=> Promise<void>;\nexport type OnClickHandler = (event: OnClickEvent)=> Promise<void>;\n\n/**\n * Most of these are the built-in note properties, such as `note.title`, `note.todo_completed`, etc.\n * complemented with special properties such as `note.isWatched`, to know if a note is currently\n * opened in the external editor, and `note.tags` to get the list tags associated with the note.\n *\n * The `note.todoStatusText` property is a localised description of the to-do status (e.g.\n * \"to-do, incomplete\"). If you include an `<input type='checkbox' ... />` for to-do items that would\n * otherwise be unlabelled, consider adding `note.todoStatusText` as the checkbox's `aria-label`.\n *\n * ## Item properties\n *\n * The `item.*` properties are specific to the rendered item. The most important being\n * `item.selected`, which you can use to display the selected note in a different way.\n */\nexport type ListRendererDependency =\n\tListRendererDatabaseDependency |\n\t'item.index' |\n\t'item.selected' |\n\t'item.size.height' |\n\t'item.size.width' |\n\t'note.folder.title' |\n\t'note.isWatched' |\n\t'note.tags' |\n\t'note.todoStatusText' |\n\t'note.titleHtml';\n\nexport type ListRendererItemValueTemplates = Record<string, string>;\n\nexport const columnNames = [\n\t'note.folder.title',\n\t'note.is_todo',\n\t'note.latitude',\n\t'note.longitude',\n\t'note.source_url',\n\t'note.tags',\n\t'note.title',\n\t'note.todo_completed',\n\t'note.todo_due',\n\t'note.user_created_time',\n\t'note.user_updated_time',\n] as const;\n\nexport type ColumnName = typeof columnNames[number];\n\nexport interface ListRenderer {\n\t/**\n\t * It must be unique to your plugin.\n\t */\n\tid: string;\n\n\t/**\n\t * Can be top to bottom or left to right. Left to right gives you more\n\t * option to set the size of the items since you set both its width and\n\t * height.\n\t */\n\tflow: ItemFlow;\n\n\t/**\n\t * Whether the renderer supports multiple columns. Applies only when `flow`\n\t * is `topToBottom`. Defaults to `false`.\n\t */\n\tmultiColumns?: boolean;\n\n\t/**\n\t * The size of each item must be specified in advance for performance\n\t * reasons, and cannot be changed afterwards. If the item flow is top to\n\t * bottom, you only need to specify the item height (the width will be\n\t * ignored).\n\t */\n\titemSize: Size;\n\n\t/**\n\t * The CSS is relative to the list item container. What will appear in the\n\t * page is essentially `.note-list-item { YOUR_CSS; }`. It means you can use\n\t * child combinator with guarantee it will only apply to your own items. In\n\t * this example, the styling will apply to `.note-list-item > .content`:\n\t *\n\t * ```css\n\t * > .content {\n\t *     padding: 10px;\n\t * }\n\t * ```\n\t *\n\t * In order to get syntax highlighting working here, it's recommended\n\t * installing an editor extension such as [es6-string-html VSCode\n\t * extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)\n\t */\n\titemCss?: string;\n\n\t/**\n\t * List the dependencies that your plugin needs to render the note list\n\t * items. Only these will be passed to your `onRenderNote` handler. Ensure\n\t * that you do not add more than what you need since there is a performance\n\t * penalty for each property.\n\t */\n\tdependencies?: ListRendererDependency[];\n\n\theaderTemplate?: string;\n\theaderHeight?: number;\n\tonHeaderClick?: OnClickHandler;\n\n\t/**\n\t * This property is set differently depending on the `multiColumns` property.\n\t *\n\t * ## If `multiColumns` is `false`\n\t *\n\t * There is only one column and the template is used to render the entire row.\n\t *\n\t * This is the HTML template that will be used to render the note list item. This is a [Mustache\n\t * template](https://github.com/janl/mustache.js) and it will receive the variable you return\n\t * from `onRenderNote` as tags. For example, if you return a property named `formattedDate` from\n\t * `onRenderNote`, you can insert it in the template using `Created date: {{formattedDate}}`\n\t *\n\t * ## If `multiColumns` is `true`\n\t *\n\t * Since there is multiple columns, this template will be used to render each note property\n\t * within the row. For example if the current columns are the Updated and Title properties, this\n\t * template will be called once to render the updated time and a second time to render the\n\t * title. To display the current property, the generic `value` property is provided - it will be\n\t * replaced at runtime by the actual note property. To render something different depending on\n\t * the note property, use `itemValueTemplate`. A minimal example would be\n\t * `<span>{{value}}</span>` which will simply render the current property inside a span tag.\n\t *\n\t * In order to get syntax highlighting working here, it's recommended installing an editor\n\t * extension such as [es6-string-html VSCode\n\t * extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)\n\t *\n\t * ## Default property rendering\n\t *\n\t * Certain properties are automatically rendered once inserted in the Mustache template. Those\n\t * are in particular all the date-related fields, such as `note.user_updated_time` or\n\t * `note.todo_completed`. Internally, those are timestamps in milliseconds, however when\n\t * rendered we display them as date/time strings using the user's preferred time format. Another\n\t * notable auto-rendered property is `note.title` which is going to include additional HTML,\n\t * such as the search markers.\n\t *\n\t * If you do not want this default rendering behaviour, for example if you want to display the\n\t * raw timestamps in milliseconds, you can simply return custom properties from\n\t * `onRenderNote()`. For example:\n\t *\n\t * ```typescript\n\t * onRenderNote: async (props: any) => {\n\t *     return {\n\t *         ...props,\n\t *         // Return the property under a different name\n\t *         updatedTimeMs: props.note.user_updated_time,\n\t *     }\n\t * },\n\t *\n\t * itemTemplate: // html\n\t *     `\n\t *     <div>\n\t *         Raw timestamp: {{updatedTimeMs}} <!-- This is **not** auto-rendered ->\n\t *         Formatted time: {{note.user_updated_time}} <!-- This is -->\n\t *     </div>\n\t * `,\n\t *\n\t * ```\n\t *\n\t * See\n\t * `[https://github.com/laurent22/joplin/blob/dev/packages/lib/services/noteList/renderViewProps.ts](renderViewProps.ts)`\n\t * for the list of properties that have a default rendering.\n\t */\n\titemTemplate: string;\n\n\t/**\n\t * This property applies only when `multiColumns` is `true`. It is used to render something\n\t * different for each note property.\n\t *\n\t * This is a map of actual dependencies to templates - you only need to return something if the\n\t * default, as specified in `template`, is not enough.\n\t *\n\t * Again you need to return a Mustache template and it will be combined with the `template`\n\t * property to create the final template. For example if you return a property named\n\t * `formattedDate` from `onRenderNote`, you can insert it in the template using\n\t * `{{formattedDate}}`. This string will replace `{{value}}` in the `template` property.\n\t *\n\t * So if the template property is set to `<span>{{value}}</span>`, the final template will be\n\t * `<span>{{formattedDate}}</span>`.\n\t *\n\t * The property would be set as so:\n\t *\n\t * ```javascript\n\t * itemValueTemplates: {\n\t *     'note.user_updated_time': '{{formattedDate}}',\n\t * }\n\t * ```\n\t */\n\titemValueTemplates?: ListRendererItemValueTemplates;\n\n\t/**\n\t * This user-facing text is used for example in the View menu, so that your\n\t * renderer can be selected.\n\t */\n\tlabel: ()=> Promise<string>;\n\n\t/**\n\t * This is where most of the real-time processing will happen. When a note\n\t * is rendered for the first time and every time it changes, this handler\n\t * receives the properties specified in the `dependencies` property. You can\n\t * then process them, load any additional data you need, and once done you\n\t * need to return the properties that are needed in the `itemTemplate` HTML.\n\t * Again, to use the formatted date example, you could have such a renderer:\n\t *\n\t * ```typescript\n\t * dependencies: [\n\t *     'note.title',\n\t *     'note.created_time',\n\t * ],\n\t *\n\t * itemTemplate: // html\n\t *     `\n\t *     <div>\n\t *         Title: {{note.title}}<br/>\n\t *         Date: {{formattedDate}}\n\t *     </div>\n\t * `,\n\t *\n\t * onRenderNote: async (props: any) => {\n\t *     const formattedDate = dayjs(props.note.created_time).format();\n\t *     return {\n\t *         // Also return the props, so that note.title is available from the\n\t *         // template\n\t *         ...props,\n\t *         formattedDate,\n\t *     }\n\t * },\n\t * ```\n\t */\n\tonRenderNote: OnRenderNoteHandler;\n\n\t/**\n\t * This handler allows adding some interactivity to the note renderer - whenever an input element\n\t * within the item is changed (for example, when a checkbox is clicked, or a text input is\n\t * changed), this `onChange` handler is going to be called.\n\t *\n\t * You can inspect `event.elementId` to know which element had some changes, and `event.value`\n\t * to know the new value. `event.noteId` also tells you what note is affected, so that you can\n\t * potentially apply changes to it.\n\t *\n\t * You specify the element ID, by setting a `data-id` attribute on the input.\n\t *\n\t * For example, if you have such a template:\n\t *\n\t * ```html\n\t * <div>\n\t *     <input type=\"text\" value=\"{{note.title}}\" data-id=\"noteTitleInput\"/>\n\t * </div>\n\t * ```\n\t *\n\t * The event handler will receive an event with `elementId` set to `noteTitleInput`.\n\t *\n\t * ## Default event handlers\n\t *\n\t * Currently one click event is automatically handled:\n\t *\n\t * If there is a checkbox with a `data-id=\"todo-checkbox\"` attribute is present, it is going to\n\t * automatically toggle the note to-do \"completed\" status.\n\t *\n\t * For example this is what is used in the default list renderer:\n\t *\n\t * `<input data-id=\"todo-checkbox\" type=\"checkbox\" {{#note.todo_completed}}checked=\"checked\"{{/note.todo_completed}}>`\n\t */\n\tonChange?: OnChangeHandler;\n}\n\nexport interface NoteListColumn {\n\tname: ColumnName;\n\twidth: number;\n}\n\nexport type NoteListColumns = NoteListColumn[];\n\nexport const defaultWidth = 100;\n\nexport const defaultListColumns = () => {\n\tconst columns: NoteListColumns = [\n\t\t{\n\t\t\tname: 'note.is_todo',\n\t\t\twidth: 30,\n\t\t},\n\t\t{\n\t\t\tname: 'note.user_updated_time',\n\t\t\twidth: defaultWidth,\n\t\t},\n\t\t{\n\t\t\tname: 'note.title',\n\t\t\twidth: 0,\n\t\t},\n\t];\n\n\treturn columns;\n};\n"
  },
  {
    "path": "api/types.ts",
    "content": "/* eslint-disable multiline-comment-style */\n\n// =================================================================\n// Command API types\n// =================================================================\n\nexport interface Command {\n\t/**\n\t * Name of command - must be globally unique\n\t */\n\tname: string;\n\n\t/**\n\t * Label to be displayed on menu items or keyboard shortcut editor for example.\n\t * If it is missing, it's assumed it's a private command, to be called programmatically only.\n\t * In that case the command will not appear in the shortcut editor or command panel, and logically\n\t * should not be used as a menu item.\n\t */\n\tlabel?: string;\n\n\t/**\n\t * Icon to be used on toolbar buttons for example\n\t */\n\ticonName?: string;\n\n\t/**\n\t * Code to be ran when the command is executed. It may return a result.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\texecute(...args: any[]): Promise<any | void>;\n\n\t/**\n\t * Defines whether the command should be enabled or disabled, which in turns\n\t * affects the enabled state of any associated button or menu item.\n\t *\n\t * The condition should be expressed as a \"when-clause\" (as in Visual Studio\n\t * Code). It's a simple boolean expression that evaluates to `true` or\n\t * `false`. It supports the following operators:\n\t *\n\t * Operator | Symbol | Example\n\t * -- | -- | --\n\t * Equality | == | \"editorType == markdown\"\n\t * Inequality | != | \"currentScreen != config\"\n\t * Or | \\|\\| | \"noteIsTodo \\|\\| noteTodoCompleted\"\n\t * And | && | \"oneNoteSelected && !inConflictFolder\"\n\t *\n\t * Joplin, unlike VSCode, also supports parentheses, which allows creating\n\t * more complex expressions such as `cond1 || (cond2 && cond3)`. Only one\n\t * level of parentheses is possible (nested ones aren't supported).\n\t *\n\t * Currently the supported context variables aren't documented, but you can\n\t * find the list below:\n\t *\n\t * - [Global When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts)\n\t * - [Desktop app When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts)\n\t *\n\t * Note: Commands are enabled by default unless you use this property.\n\t */\n\tenabledCondition?: string;\n}\n\n// =================================================================\n// Interop API types\n// =================================================================\n\nexport enum FileSystemItem {\n\tFile = 'file',\n\tDirectory = 'directory',\n}\n\nexport enum ImportModuleOutputFormat {\n\tMarkdown = 'md',\n\tHtml = 'html',\n}\n\n/**\n * Used to implement a module to export data from Joplin. [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export) for an example.\n *\n * In general, all the event handlers you'll need to implement take a `context` object as a first argument. This object will contain the export or import path as well as various optional properties, such as which notes or notebooks need to be exported.\n *\n * To get a better sense of what it will contain it can be useful to print it using `console.info(context)`.\n */\nexport interface ExportModule {\n\t/**\n\t * The format to be exported, eg \"enex\", \"jex\", \"json\", etc.\n\t */\n\tformat: string;\n\n\t/**\n\t * The description that will appear in the UI, for example in the menu item.\n\t */\n\tdescription: string;\n\n\t/**\n\t * Whether the module will export a single file or multiple files in a directory. It affects the open dialog that will be presented to the user when using your exporter.\n\t */\n\ttarget: FileSystemItem;\n\n\t/**\n\t * Only applies to single file exporters or importers\n\t * It tells whether the format can package multiple notes into one file.\n\t * For example JEX or ENEX can, but HTML cannot.\n\t */\n\tisNoteArchive: boolean;\n\n\t/**\n\t * The extensions of the files exported by your module. For example, it is `[\"htm\", \"html\"]` for the HTML module, and just `[\"jex\"]` for the JEX module.\n\t */\n\tfileExtensions?: string[];\n\n\t/**\n\t * Called when the export process starts.\n\t */\n\tonInit(context: ExportContext): Promise<void>;\n\n\t/**\n\t * Called when an item needs to be processed. An \"item\" can be any Joplin object, such as a note, a folder, a notebook, etc.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tonProcessItem(context: ExportContext, itemType: number, item: any): Promise<void>;\n\n\t/**\n\t * Called when a resource file needs to be exported.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tonProcessResource(context: ExportContext, resource: any, filePath: string): Promise<void>;\n\n\t/**\n\t * Called when the export process is done.\n\t */\n\tonClose(context: ExportContext): Promise<void>;\n}\n\nexport interface ImportModule {\n\t/**\n\t * The format to be exported, eg \"enex\", \"jex\", \"json\", etc.\n\t */\n\tformat: string;\n\n\t/**\n\t * The description that will appear in the UI, for example in the menu item.\n\t */\n\tdescription: string;\n\n\t/**\n\t * Only applies to single file exporters or importers\n\t * It tells whether the format can package multiple notes into one file.\n\t * For example JEX or ENEX can, but HTML cannot.\n\t */\n\tisNoteArchive: boolean;\n\n\t/**\n\t * The type of sources that are supported by the module. Tells whether the module can import files or directories or both.\n\t */\n\tsources: FileSystemItem[];\n\n\t/**\n\t * Tells the file extensions of the exported files.\n\t */\n\tfileExtensions?: string[];\n\n\t/**\n\t * Tells the type of notes that will be generated, either HTML or Markdown (default).\n\t */\n\toutputFormat?: ImportModuleOutputFormat;\n\n\t/**\n\t * Called when the import process starts. There is only one event handler within which you should import the complete data.\n\t */\n\tonExec(context: ImportContext): Promise<void>;\n}\n\nexport interface ExportOptions {\n\tformat?: string;\n\tpath?: string;\n\tsourceFolderIds?: string[];\n\tsourceNoteIds?: string[];\n\t// modulePath?: string;\n\ttarget?: FileSystemItem;\n}\n\nexport interface ExportContext {\n\tdestPath: string;\n\toptions: ExportOptions;\n\n\t/**\n\t * You can attach your own custom data using this property - it will then be passed to each event handler, allowing you to keep state from one event to the next.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tuserData?: any;\n}\n\nexport interface ImportContext {\n\tsourcePath: string;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\toptions: any;\n\twarnings: string[];\n}\n\n// =================================================================\n// Misc types\n// =================================================================\n\nexport interface Script {\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tonStart?(event: any): Promise<void>;\n}\n\nexport interface Disposable {\n\t// dispose():void;\n}\n\nexport enum ModelType {\n\tNote = 1,\n\tFolder = 2,\n\tSetting = 3,\n\tResource = 4,\n\tTag = 5,\n\tNoteTag = 6,\n\tSearch = 7,\n\tAlarm = 8,\n\tMasterKey = 9,\n\tItemChange = 10,\n\tNoteResource = 11,\n\tResourceLocalState = 12,\n\tRevision = 13,\n\tMigration = 14,\n\tSmartFilter = 15,\n\tCommand = 16,\n}\n\nexport interface VersionInfo {\n\tversion: string;\n\tprofileVersion: number;\n\tsyncVersion: number;\n\n\tplatform: 'desktop'|'mobile';\n}\n\n// =================================================================\n// Menu types\n// =================================================================\n\nexport interface CreateMenuItemOptions {\n\taccelerator: string;\n}\n\nexport enum MenuItemLocation {\n\tFile = 'file',\n\tEdit = 'edit',\n\tView = 'view',\n\tNote = 'note',\n\tTools = 'tools',\n\tHelp = 'help',\n\n\t/**\n\t * @deprecated Do not use - same as NoteListContextMenu\n\t */\n\tContext = 'context',\n\n\t// If adding an item here, don't forget to update isContextMenuItemLocation()\n\n\t/**\n\t * When a command is called from the note list context menu, the\n\t * command will receive the following arguments:\n\t *\n\t * - `noteIds:string[]`: IDs of the notes that were right-clicked on.\n\t */\n\tNoteListContextMenu = 'noteListContextMenu',\n\n\tEditorContextMenu = 'editorContextMenu',\n\n\t/**\n\t * When a command is called from a folder context menu, the\n\t * command will receive the following arguments:\n\t *\n\t * - `folderId:string`: ID of the folder that was right-clicked on\n\t */\n\tFolderContextMenu = 'folderContextMenu',\n\n\t/**\n\t * When a command is called from a tag context menu, the\n\t * command will receive the following arguments:\n\t *\n\t * - `tagId:string`: ID of the tag that was right-clicked on\n\t */\n\tTagContextMenu = 'tagContextMenu',\n}\n\nexport function isContextMenuItemLocation(location: MenuItemLocation): boolean {\n\treturn [\n\t\tMenuItemLocation.Context,\n\t\tMenuItemLocation.NoteListContextMenu,\n\t\tMenuItemLocation.EditorContextMenu,\n\t\tMenuItemLocation.FolderContextMenu,\n\t\tMenuItemLocation.TagContextMenu,\n\t].includes(location);\n}\n\nexport interface MenuItem {\n\t/**\n\t * Command that should be associated with the menu item. All menu item should\n\t * have a command associated with them unless they are a sub-menu.\n\t */\n\tcommandName?: string;\n\n\t/**\n\t * Arguments that should be passed to the command. They will be as rest\n\t * parameters.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tcommandArgs?: any[];\n\n\t/**\n\t * Set to \"separator\" to create a divider line\n\t */\n\ttype?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio');\n\n\t/**\n\t * Accelerator associated with the menu item\n\t */\n\taccelerator?: string;\n\n\t/**\n\t * Menu items that should appear below this menu item. Allows creating a menu tree.\n\t */\n\tsubmenu?: MenuItem[];\n\n\t/**\n\t * Menu item label. If not specified, the command label will be used instead.\n\t */\n\tlabel?: string;\n}\n\n// =================================================================\n// View API types\n// =================================================================\n\nexport interface ButtonSpec {\n\tid: ButtonId;\n\ttitle?: string;\n\tonClick?(): void;\n}\n\nexport type ButtonId = string;\n\nexport enum ToolbarButtonLocation {\n\t/**\n\t * This toolbar in the top right corner of the application. It applies to the note as a whole, including its metadata.\n\t *\n\t * <span class=\"platform-desktop\">desktop</span>\n\t */\n\tNoteToolbar = 'noteToolbar',\n\n\t/**\n\t * This toolbar is right above the text editor. It applies to the note body only.\n\t */\n\tEditorToolbar = 'editorToolbar',\n}\n\nexport type ViewHandle = string;\n\nexport interface EditorCommand {\n\tname: string;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tvalue?: any;\n}\n\nexport interface DialogResult {\n\tid: ButtonId;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tformData?: any;\n}\n\nexport interface Size {\n\twidth?: number;\n\theight?: number;\n}\n\nexport interface Rectangle {\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n}\n\nexport type ActivationCheckCallback = ()=> Promise<boolean>;\n\nexport type UpdateCallback = ()=> Promise<void>;\n\nexport type VisibleHandler = ()=> Promise<void>;\n\nexport interface EditContextMenuFilterObject {\n\titems: MenuItem[];\n}\n\nexport interface EditorActivationCheckFilterObject {\n\tactivatedEditors: {\n\t\tpluginId: string;\n\t\tviewId: string;\n\t\tisActive: boolean;\n\t}[];\n}\n\nexport type FilterHandler<T> = (object: T)=> Promise<T>;\n\n// =================================================================\n// Settings types\n// =================================================================\n\nexport enum SettingItemType {\n\tInt = 1,\n\tString = 2,\n\tBool = 3,\n\tArray = 4,\n\tObject = 5,\n\tButton = 6,\n}\n\nexport enum SettingItemSubType {\n\tFilePathAndArgs = 'file_path_and_args',\n\tFilePath = 'file_path', // Not supported on mobile!\n\tDirectoryPath = 'directory_path', // Not supported on mobile!\n}\n\nexport enum AppType {\n\tDesktop = 'desktop',\n\tMobile = 'mobile',\n\tCli = 'cli',\n}\n\nexport enum SettingStorage {\n\tDatabase = 1,\n\tFile = 2,\n}\n\n// Redefine a simplified interface to mask internal details\n// and to remove function calls as they would have to be async.\nexport interface SettingItem {\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tvalue: any;\n\ttype: SettingItemType;\n\n\t/**\n\t * Currently only used to display a file or directory selector. Always set\n\t * `type` to `SettingItemType.String` when using this property.\n\t */\n\tsubType?: SettingItemSubType;\n\n\tlabel: string;\n\tdescription?: string;\n\n\t/**\n\t * A public setting will appear in the Configuration screen and will be\n\t * modifiable by the user. A private setting however will not appear there,\n\t * and can only be changed programmatically. You may use this to store some\n\t * values that you do not want to directly expose.\n\t */\n\tpublic: boolean;\n\n\t/**\n\t * You would usually set this to a section you would have created\n\t * specifically for the plugin.\n\t */\n\tsection?: string;\n\n\t/**\n\t * To create a setting with multiple options, set this property to `true`.\n\t * That setting will render as a dropdown list in the configuration screen.\n\t */\n\tisEnum?: boolean;\n\n\t/**\n\t * This property is required when `isEnum` is `true`. In which case, it\n\t * should contain a map of value => label.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\toptions?: Record<any, any>;\n\n\t/**\n\t * Reserved property. Not used at the moment.\n\t */\n\tappTypes?: AppType[];\n\n\t/**\n\t * Set this to `true` to store secure data, such as passwords. Any such\n\t * setting will be stored in the system keychain if one is available.\n\t */\n\tsecure?: boolean;\n\n\t/**\n\t * An advanced setting will be moved under the \"Advanced\" button in the\n\t * config screen.\n\t */\n\tadvanced?: boolean;\n\n\t/**\n\t * Set the min, max and step values if you want to restrict an int setting\n\t * to a particular range.\n\t */\n\tminimum?: number;\n\tmaximum?: number;\n\tstep?: number;\n\n\t/**\n\t * Either store the setting in the database or in settings.json. Defaults to database.\n\t */\n\tstorage?: SettingStorage;\n}\n\nexport interface SettingSection {\n\tlabel: string;\n\ticonName?: string;\n\tdescription?: string;\n\tname?: string;\n}\n\n// =================================================================\n// Data API types\n// =================================================================\n\n/**\n * An array of at least one element and at most three elements.\n *\n * - **[0]**: Resource name (eg. \"notes\", \"folders\", \"tags\", etc.)\n * - **[1]**: (Optional) Resource ID.\n * - **[2]**: (Optional) Resource link.\n */\nexport type Path = string[];\n\n// =================================================================\n// Content Script types\n// =================================================================\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\nexport type PostMessageHandler = (message: any)=> Promise<any>;\n\n/**\n * When a content script is initialised, it receives a `context` object.\n */\nexport interface ContentScriptContext {\n\t/**\n\t * The plugin ID that registered this content script\n\t */\n\tpluginId: string;\n\n\t/**\n\t * The content script ID, which may be necessary to post messages\n\t */\n\tcontentScriptId: string;\n\n\t/**\n\t * Can be used by CodeMirror content scripts to post a message to the plugin\n\t */\n\tpostMessage: PostMessageHandler;\n}\n\nexport interface ContentScriptModuleLoadedEvent {\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tuserData?: any;\n}\n\nexport interface ContentScriptModule {\n\tonLoaded?: (event: ContentScriptModuleLoadedEvent)=> void;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tplugin: ()=> any;\n\tassets?: ()=> void;\n}\n\nexport interface MarkdownItContentScriptModule extends Omit<ContentScriptModule, 'plugin'> {\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tplugin: (markdownIt: any, options: any)=> any;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\ntype EditorCommandCallback = (...args: any[])=> any;\n\nexport interface CodeMirrorControl {\n\t/** Points to a CodeMirror 6 EditorView instance. */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\teditor: any;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\tcm6: any;\n\n\t/** `extension` should be a [CodeMirror 6 extension](https://codemirror.net/docs/ref/#state.Extension). */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\taddExtension(extension: any|any[]): void;\n\n\tsupportsCommand(name: string): boolean;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\texecCommand(name: string, ...args: any[]): any;\n\tregisterCommand(name: string, callback: EditorCommandCallback): void;\n\n\tjoplinExtensions: {\n\t\t/**\n\t\t * Returns a [CodeMirror 6 extension](https://codemirror.net/docs/ref/#state.Extension) that\n\t\t * registers the given [CompletionSource](https://codemirror.net/docs/ref/#autocomplete.CompletionSource).\n\t\t *\n\t\t * Use this extension rather than the built-in CodeMirror [`autocompletion`](https://codemirror.net/docs/ref/#autocomplete.autocompletion)\n\t\t * if you don't want to use [languageData-based autocompletion](https://codemirror.net/docs/ref/#autocomplete.autocompletion^config.override).\n\t\t *\n\t\t * Using `autocompletion({ override: [ ... ]})` causes errors when done by multiple plugins.\n\t\t */\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\t\tcompletionSource(completionSource: any): any;\n\n\t\t/**\n\t\t * Creates an extension that enables or disables [`languageData`-based autocompletion](https://codemirror.net/docs/ref/#autocomplete.autocompletion^config.override).\n\t\t */\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied\n\t\tenableLanguageDataAutocomplete: { of: (enabled: boolean)=> any };\n\t};\n}\n\nexport interface MarkdownEditorContentScriptModule extends Omit<ContentScriptModule, 'plugin'> {\n\tplugin: (editorControl: CodeMirrorControl)=> void;\n}\n\nexport enum ContentScriptType {\n\t/**\n\t * Registers a new Markdown-It plugin, which should follow the template\n\t * below.\n\t *\n\t * ```javascript\n\t * module.exports = {\n\t *     default: function(context) {\n\t *         return {\n\t *             plugin: function(markdownIt, pluginOptions) {\n\t *                 // ...\n\t *             },\n\t *             assets: {\n\t *                 // ...\n\t *             },\n\t *         }\n\t *     }\n\t * }\n\t * ```\n\t *\n\t * See [the\n\t * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)\n\t * for a simple Markdown-it plugin example.\n\t *\n\t * ## Exported members\n\t *\n\t * - The `context` argument is currently unused but could be used later on\n\t *   to provide access to your own plugin so that the content script and\n\t *   plugin can communicate.\n\t *\n\t * - The **required** `plugin` key is the actual Markdown-It plugin - check\n\t *   the [official doc](https://github.com/markdown-it/markdown-it) for more\n\t *   information.\n\t *\n\t * - Using the **optional** `assets` key you may specify assets such as JS\n\t *   or CSS that should be loaded in the rendered HTML document. Check for\n\t *   example the Joplin [Mermaid\n\t *   plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)\n\t *   to see how the data should be structured.\n\t *\n\t * ## Supporting the Rich Text Editor\n\t *\n\t * Joplin's Rich Text Editor works with rendered HTML, which is converted back\n\t * to markdown when saving. To prevent the original markdown for your plugin from\n\t * being lost, Joplin needs additional metadata.\n\t *\n\t * To provide this,\n\t * 1. Wrap the HTML generated by your plugin in an element with class `joplin-editable`.\n\t *    For example,\n\t *    ```html\n\t *    <div class=\"joplin-editable\">\n\t *        ...your html...\n\t *    </div>\n\t *    ```\n\t * 2. Add a child with class `joplin-source` that contains the original markdown that\n\t *    was rendered by your plugin. Include `data-joplin-source-open`, `data-joplin-source-close`,\n\t *    and `data-joplin-language` attributes.\n\t *    For example, if your plugin rendered the following code block,\n\t *    ````\n\t *    ```foo\n\t *    ... original source here ...\n\t *    ```\n\t *    ````\n\t *    then it should render to\n\t *    ```html\n\t *    <div class=\"joplin-editable\">\n\t *        <pre\n\t *            class=\"joplin-source\"\n\t *            data-joplin-language=\"foo\"\n\t *            data-joplin-source-open=\"```foo&NewLine;\"\n\t *            data-joplin-source-close=\"```\"\n\t *        > ... original source here ... </pre>\n\t *        ... rendered HTML here ...\n\t *    </div>\n\t *    ```\n\t *\n\t * See [the demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)\n\t * for a complete example.\n\t *\n\t * ## Getting the settings from the renderer\n\t *\n\t * You can access your plugin settings from the renderer by calling\n\t * `pluginOptions.settingValue(\"your-setting-key')`.\n\t *\n\t * ## Posting messages from the content script to your plugin\n\t *\n\t * The application provides the following function to allow executing\n\t * commands from the rendered HTML code:\n\t *\n\t * ```javascript\n\t * const response = await webviewApi.postMessage(contentScriptId, message);\n\t * ```\n\t *\n\t * - `contentScriptId` is the ID you've defined when you registered the\n\t *   content script. You can retrieve it from the\n\t *   {@link ContentScriptContext | context}.\n\t * - `message` can be any basic JavaScript type (number, string, plain\n\t *   object), but it cannot be a function or class instance.\n\t *\n\t * When you post a message, the plugin can send back a `response` thus\n\t * allowing two-way communication:\n\t *\n\t * ```javascript\n\t * await joplin.contentScripts.onMessage(contentScriptId, (message) => {\n\t *     // Process message\n\t *     return response; // Can be any object, string or number\n\t * });\n\t * ```\n\t *\n\t * See {@link JoplinContentScripts.onMessage} for more details, as well as\n\t * the [postMessage\n\t * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).\n\t *\n\t * ## Registering an existing Markdown-it plugin\n\t *\n\t * To include a regular Markdown-It plugin, that doesn't make use of any\n\t * Joplin-specific features, you would simply create a file such as this:\n\t *\n\t * ```javascript\n\t * module.exports = {\n\t *     default: function(context) {\n\t *         return {\n\t *             plugin: require('markdown-it-toc-done-right');\n\t *         }\n\t *     }\n\t * }\n\t * ```\n\t */\n\tMarkdownItPlugin = 'markdownItPlugin',\n\n\t/**\n\t * Registers a new CodeMirror plugin, which should follow the template\n\t * below.\n\t *\n\t * ```javascript\n\t * module.exports = {\n\t *     default: function(context) {\n\t *         return {\n\t *             plugin: function(CodeMirror) {\n\t *                 // ...\n\t *             },\n\t *             codeMirrorResources: [],\n\t *             codeMirrorOptions: {\n\t *                                  // ...\n\t *                       },\n\t *             assets: {\n\t *                 // ...\n\t *             },\n\t *         }\n\t *     }\n\t * }\n\t * ```\n\t *\n\t * - The `context` argument allows communicating with other parts of\n\t *   your plugin (see below).\n\t *\n\t * - The `plugin` key is your CodeMirror plugin. This is where you can\n\t *   register new commands with CodeMirror or interact with the CodeMirror\n\t *   instance as needed.\n\t *\n\t * - **CodeMirror 5 only**: The `codeMirrorResources` key is an array of CodeMirror resources that\n\t *   will be loaded and attached to the CodeMirror module. These are made up\n\t *   of addons, keymaps, and modes. For example, for a plugin that want's to\n\t *   enable clojure highlighting in code blocks. `codeMirrorResources` would\n\t *   be set to `['mode/clojure/clojure']`.\n\t *   This field is ignored on mobile and when the desktop beta editor is enabled.\n\t *\n\t * - **CodeMirror 5 only**: The `codeMirrorOptions` key contains all the\n\t *   [CodeMirror](https://codemirror.net/doc/manual.html#config) options\n\t *   that will be set or changed by this plugin. New options can alse be\n\t *   declared via\n\t *   [`CodeMirror.defineOption`](https://codemirror.net/doc/manual.html#defineOption),\n\t *   and then have their value set here. For example, a plugin that enables\n\t *   line numbers would set `codeMirrorOptions` to `{'lineNumbers': true}`.\n\t *\n\t * - Using the **optional** `assets` key you may specify **only** CSS assets\n\t *   that should be loaded in the rendered HTML document. Check for example\n\t *   the Joplin [Mermaid\n\t *   plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)\n\t *   to see how the data should be structured.\n\t *\n\t * One of the `plugin`, `codeMirrorResources`, or `codeMirrorOptions` keys\n\t * must be provided for the plugin to be valid. Having multiple or all\n\t * provided is also okay.\n\t *\n\t * See also:\n\t * - The [demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)\n\t *   for an example of all these keys being used in one plugin.\n\t * - See [the editor plugin tutorial](https://joplinapp.org/help/api/tutorials/cm6_plugin)\n\t *   for how to develop a plugin for the mobile editor and the desktop beta markdown editor.\n\t *\n\t * ## Posting messages from the content script to your plugin\n\t *\n\t * In order to post messages to the plugin, you can use the postMessage\n\t * function passed to the {@link ContentScriptContext | context}.\n\t *\n\t * ```javascript\n\t * const response = await context.postMessage('messageFromCodeMirrorContentScript');\n\t * ```\n\t *\n\t * When you post a message, the plugin can send back a `response` thus\n\t * allowing two-way communication:\n\t *\n\t * ```javascript\n\t * await joplin.contentScripts.onMessage(contentScriptId, (message) => {\n\t *     // Process message\n\t *     return response; // Can be any object, string or number\n\t * });\n\t * ```\n\t *\n\t * See {@link JoplinContentScripts.onMessage} for more details, as well as\n\t * the [postMessage\n\t * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).\n\t *\n\t */\n\tCodeMirrorPlugin = 'codeMirrorPlugin',\n}\n"
  },
  {
    "path": "jest.config.js",
    "content": "/** @type {import('jest').Config} */\nconst config = {\n\tpreset: 'ts-jest',\n\ttestEnvironment: 'node',\n};\n\nmodule.exports = config;"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"joplin-plugin-rich-markdown\",\n  \"version\": \"0.17.1\",\n  \"scripts\": {\n    \"dist\": \"webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive\",\n    \"prepare\": \"npm run dist\",\n    \"update\": \"npm install -g generator-joplin && yo joplin --node-package-manager npm --update --force\",\n    \"preversion\": \"npm run test\",\n    \"updatetags\": \"LOGS=$(git log $(git describe --tags --abbrev=0 HEAD~1)..HEAD~1 --oneline) && git tag -fam \\\"v$npm_package_version\\n\\n$LOGS\\\" v$npm_package_version && git tag -fa v$npm_package_version\",\n    \"postversion\": \"npm run updatetags && git push origin main --tags\",\n    \"version\": \"sed -i '/\\\\\\\"version\\\\\\\": \\\\\\\"/s/[^\\\\\\\"]*\\\\\\\",/'\\\"$npm_package_version\\\\\\\",/\\\" src/manifest.json && git add src/manifest.json\",\n    \"test\": \"jest\",\n    \"updateVersion\": \"webpack --env joplin-plugin-config=updateVersion\"\n  },\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"joplin-plugin\"\n  ],\n  \"browser\": {\n    \"child_process\": false,\n    \"fs\": false\n  },\n  \"devDependencies\": {\n    \"@codemirror/language\": \"^6.11.2\",\n    \"@types/jest\": \"^27.5.2\",\n    \"@types/node\": \"^18.7.13\",\n    \"chalk\": \"^4.1.0\",\n    \"copy-webpack-plugin\": \"^11.0.0\",\n    \"fs-extra\": \"^10.1.0\",\n    \"glob\": \"^8.0.3\",\n    \"jest\": \"^30.0.5\",\n    \"jest-util\": \"^30.0.5\",\n    \"on-build-webpack\": \"^0.1.0\",\n    \"tar\": \"^6.1.11\",\n    \"ts-jest\": \"^29.4.0\",\n    \"ts-loader\": \"^9.3.1\",\n    \"typescript\": \"^4.8.2\",\n    \"webpack\": \"^5.74.0\",\n    \"webpack-cli\": \"^4.10.0\",\n    \"yargs\": \"^16.2.0\"\n  },\n  \"dependencies\": {\n    \"@joplin/lib\": \"^2.6.3\"\n  },\n  \"files\": [\n    \"publish\"\n  ]\n}\n"
  },
  {
    "path": "plugin.config.json",
    "content": "{\n\t\"extraScripts\": [\n\t\t\"richMarkdown.ts\",\n\t\t\"images.ts\",\n\t\t\"indent.ts\",\n\t\t\"clickHandlers.ts\",\n\t\t\"stylesheets.ts\",\n\t\t\"overlay.ts\"\n\t]\n}\n"
  },
  {
    "path": "shell.nix",
    "content": "{ pkgs ? import <nixpkgs> {} }:\npkgs.mkShell rec {\n  name = \"nodejs\";\n\n  NODE_OPTIONS=\"--openssl-legacy-provider\";\n\n  buildInputs = with pkgs; [\n    nodejs nodePackages.webpack-cli\n  ];\n}\n"
  },
  {
    "path": "src/clickHandlers.test.ts",
    "content": "import * as ClickHandlers from './clickHandlers';\nimport * as Overlay from './overlay';\n\nconst test_text = `\nsoemthing\nthis is a notes\n\nthis note has [many](https://calebjohn.ca) links\nit also contains other links like <joplinapp.org> this\nbut it doesn't forget plain old https://joplinapp.org links\nand all the rest!\n(In this [page](https://joplinapp.org) you can see a test)\nit also has [reference links] which are neat!\nLinks with () should not drop the trailing )\nhttps://joplinapp.org/www(x=y)\n[even this](https://joplinapp.org/www(x=y))\n\n[reference links]: https://joplinapp.org\n`\n\ntest(\"getMatchAt works for markdown links\", () => {\n\tlet match = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 45);\n\texpect(match[0]).toBe(\"[many](https://calebjohn.ca)\");\n\texpect(match[1]).toBe(\"https://calebjohn.ca\");\n\texpect(match[2]).toBeUndefined();\n\texpect(match[3]).toBeUndefined();\n\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 115)\n\texpect(match[0]).toBe(\"<joplinapp.org>\");\n\texpect(match[1]).toBeUndefined();\n\texpect(match[2]).toBe(\"joplinapp.org\");\n\texpect(match[3]).toBeUndefined();\n\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 165)\n\texpect(match[0]).toBe(\"https://joplinapp.org\");\n\texpect(match[1]).toBeUndefined();\n\texpect(match[2]).toBeUndefined();\n\texpect(match[3]).toBe(\"https://joplinapp.org\");\n\t\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 220)\n\texpect(match[0]).toBe(\"[page](https://joplinapp.org)\");\n\texpect(match[1]).toBe(\"https://joplinapp.org\");\n\texpect(match[2]).toBeUndefined();\n\texpect(match[3]).toBeUndefined();\n\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 380)\n\texpect(match[0]).toBe(\"https://joplinapp.org/www(x=y)\");\n\texpect(match[1]).toBeUndefined();\n\texpect(match[2]).toBeUndefined();\n\texpect(match[3]).toBe(\"https://joplinapp.org/www(x=y)\");\n\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 403)\n\texpect(match[0]).toBe(\"[even this](https://joplinapp.org/www(x=y))\");\n\texpect(match[1]).toBe(\"https://joplinapp.org/www(x=y)\");\n\texpect(match[2]).toBeUndefined();\n\texpect(match[3]).toBeUndefined();\n});\n\ntest(\"getMatchAt works on the boundary of a match (but not over)\", () => {\n\tlet match = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 42);\n\texpect(match).not.toBeNull();\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 41);\n\texpect(match).toBeNull();\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 70);\n\texpect(match).not.toBeNull();\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_regex, 71);\n\texpect(match).toBeNull();\n});\n\ntest(\"reference link regexes are working okay\", () => {\n\tlet match = ClickHandlers.getMatchAt(test_text, Overlay.link_reference_regex, 290);\n\texpect(match).not.toBeNull();\n\tmatch = ClickHandlers.getMatchAt(test_text, Overlay.link_label_regex, 290);\n\texpect(match).not.toBeNull();\n});\n"
  },
  {
    "path": "src/clickHandlers.ts",
    "content": "import * as Overlay from './overlay';\n\nfunction normalizeCoord(coord: any) {\n\tif (coord.sticky && coord.sticky === \"before\")\n\t\treturn { ch: coord.ch - 1, line: coord.line };\n\n\treturn coord;\n}\n\nexport function isLink(event: MouseEvent) {\n\tif (!event.target) return false;\n\tconst target = event.target as HTMLElement;\n\n\treturn target.matches('.cm-rm-link *, .cm-rm-link') || target.matches('.cm-rm-link-label *, .cm-rm-link-label');\n}\n\nexport function isCheckbox(event: MouseEvent) {\n\tif (!event.target) return false;\n\tconst target = event.target as HTMLElement;\n\n\treturn target.matches('.cm-rm-checkbox *, .cm-rm-checkbox');\n}\n\n// Joplin uses es2015 so we don't have matchAll\nexport function getMatchAt(lineText: string, regex: RegExp, ch: number) {\n\tlet match = null;\n\tregex.lastIndex = 0;\n\n\tif (!regex.global) {\n\t\tconsole.error(\"getMatchAt requires a global regex; Consider adding a `g` after ${regex}\");\n\t\treturn null;\n\t}\n\n\tdo {\n\t\tmatch = regex.exec(lineText);\n\n\t\tif (!match) break;\n\n\t\tconst start = match.index;\n\t\tconst end = start + match[0].length;\n\n\t\tif (start <= ch && ch <= end)\n\t\t\treturn match;\n\n\t} while (match);\n\n\treturn null;\n}\n\nexport function getClickCoord(cm: any, event: MouseEvent) {\n\treturn normalizeCoord(cm.coordsChar({left: event.clientX, top: event.clientY}));\n}\n\nexport function clickAt(cm: any, coord: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\tconst settings = cm.state.richMarkdown.settings;\n\n\tif (settings.links) {\n\t\tconst url = getLinkAt(cm, coord);\n\t\tif (url)\n\t\t\treturn {name: 'followLink', url };\n\t}\n\n\tif (settings.checkbox) {\n\t\tif (toggleCheckbox(cm, coord, ''))\n\t\t\treturn null;\n\t}\n\n\treturn null;\n}\n\nexport enum TextItemType {\n\tLink = 'link',\n\tCheckbox = 'checkbox',\n\tImage = 'image',\n}\n\nexport interface TextItem {\n\ttype: TextItemType;\n\tcoord: any;\n\turl?: string;\n}\n\nexport function getItemsAt(cm:any, coord:any):TextItem[] {\n\tif (!cm.state.richMarkdown) return null;\n\n\tlet items: TextItem[] = [];\n\n\tconst url = getLinkAt(cm, coord);\n\tif (url) {\n\t\titems.push({ type: TextItemType.Link, url, coord });\n\t}\n\n\tconst checkboxInfo = getCheckboxInfo(cm, coord);\n\tif (checkboxInfo) {\n\t\titems.push({ type: TextItemType.Checkbox, coord });\n\t}\n\n\tconst imageUrl = getRegexAt(cm, coord, Overlay.html_full_image_regex, 2);\n\tif (imageUrl) {\n\t\titems.push({ type: TextItemType.Image, url: imageUrl, coord });\n\t}\n\n\treturn items;\n}\n\nfunction getLinkAt(cm: any, coord: any) {\n\tlet { line, ch } = coord;\n\n\tconst lineText = cm.getLine(line);\n\n\tconst match = getMatchAt(lineText, Overlay.link_regex, ch);\n\n\tlet url = '';\n\tif (match) {\n\t\tfor (let i = 1; i <= 4; i++) {\n\t\t\turl = url || match[i];\n\t\t}\n\t}\n\telse { // This might be a reference link, check for that\n\t\tconst reference_match = getMatchAt(lineText, Overlay.link_reference_regex, ch)\n\n\t\tif (!reference_match) return;\n\n\t\tconst reference = reference_match[1] || reference_match[2];\n\t\tconst trimmedReference = reference.trim();\n\n\t\tif (trimmedReference === '' || trimmedReference.toLowerCase() === 'x') return; // This is a checkbox\n\n\t\t// Skip Joplin directives or wiki style links such as [[toc]] that surface nested brackets\n\t\tif (trimmedReference.includes('[') || trimmedReference.includes(']')) return;\n\n\t\tconst escapedReference = trimmedReference.replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&');\n\t\tconst link_definition_regex = new RegExp(`\\\\[${escapedReference}\\\\]:\\\\s([^\\\\n]+)`, 'g');\n\n\t\tfor (let i = 0; i < cm.lineCount(); i++) {\n\t\t\t// a link reference definition can only be preceded by up to 3\n\t\t\t// spaces, so we will be sure to find a match (if it exists) that\n\t\t\t// contains character 4\n\t\t\tconst definition_match = getMatchAt(cm.getLine(i), link_definition_regex, 4);\n\n\t\t\tif (definition_match) {\n\t\t\t\turl = definition_match[1];\n\t\t\t\tbreak;\n\t\t\t};\n\t\t}\n\n\t\t// No match found, just exit\n\t\tif (url === '') {\n\t\t\talert(`No link defintion for [${trimmedReference}]. Press Esc to dismiss.`);\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// Special case, if this matches a link inside of an image, we will need to strip\n\t// the trailing )\n\tif (url && url.endsWith(')')) {\n\t\t// this is not safe based on RFC 1738 (the ) character is allowd in URLS)\n\t\t// so we'll need to do an additional check\n\t\tif (getMatchAt(lineText, Overlay.image_regex, ch))\n\t\t\turl = url.slice(0, url.length - 1);\n\t}\n\t// URLs inside html elements have a trailing quote character\n\telse if (url && (url.endsWith('\"') || url.endsWith(\"'\"))) {\n\t\t// Quotes are not allowed in URLs as per RFC 1738\n\t\t// https://www.ietf.org/rfc/rfc1738.txt\n\t\t// Page 2 includes a list of unsafe characters\n\t\turl = url.slice(0, url.length - 1);\n\t}\n\n\t// Take the first element in case a title has been provided\n\t// [](https://link.ca \"title\")\n\t// spaces are not allowed in urls (RFC 1738) so this is safe\n\turl = url.split(' ')[0];\n\n\treturn url;\n}\n\nfunction getRegexAt(cm: any, coord: any, regex: RegExp, groupNum: number) {\n\tlet { line, ch } = coord;\n\n\tconst lineText = cm.getLine(line);\n\n\tconst match = getMatchAt(lineText, regex, ch);\n\n\tif (match) {\n\t\treturn match[groupNum];\n\t}\n\n\treturn '';\n}\n\nfunction getCheckboxInfo(cm:any, coord:any) {\n\tconst { line, ch } = coord;\n\tconst lineText = cm.getLine(line);\n\n\tconst match = getMatchAt(lineText, Overlay.checkbox_regex, ch);\n\n\tif (!match || !match[3]) return null;\n\n\treturn { match, lineText, line };\n}\n\nfunction toggleCheckboxInner(cm: any, coord: any, replacement: string) {\n\tconst cursor = cm.getCursor();\n\n\tconst info = getCheckboxInfo(cm, coord);\n\n\tif (!info) return false;\n\n\tconst { match, line, lineText } = info;\n\n\tlet from = lineText.indexOf(match[3])\n\tlet to = from + match[3].length;\n\n\tlet replace = replacement;\n\tif (replace === '') {\n\t\treplace = match[3][1] === ' ' ? '[x]' : '[ ]';\n\t}\n\n\tcm.replaceRange(replace, { ch: from, line }, { ch: to, line }, '+input');\n\t// This isn't exactly needed, but the replaceRange does move the cursor\n\t// to the end of the range if the cursor is withing the section that changes\n\tcm.setCursor(cursor, null, { scroll: false });\n\n\treturn true;\n}\n\nexport function toggleCheckbox(cm: any, coord: any, replacement: string) {\n\tif (!cm.somethingSelected()) {\n\t\treturn toggleCheckboxInner(cm, coord, replacement);\n\t}\n\n\tfor (let selection of cm.listSelections()) {\n\t\tlet start, end;\n\t\tif (selection.anchor.line > selection.head.line) {\n\t\t\tstart = selection.head;\n\t\t\tend = selection.anchor;\n\t\t} else {\n\t\t\tstart = selection.anchor;\n\t\t\tend = selection.head;\n\t\t}\n\n\t\tfor (let i = start.line; i <= end.line; i++) {\n\t\t\ttoggleCheckboxInner(cm, {line: i, ch: 0}, replacement);\n\t\t}\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "src/cm6ListIndent.ts",
    "content": "import type { Decoration, DecorationSet, PluginValue } from '@codemirror/view';\nimport { require_codemirror_view, require_codemirror_state, require_codemirror_language } from \"./cm6Requires\";\n\nfunction calculateIndent(indentStr, tabSize) {\n\tlet width = 0;\n\tlet hasTab = false;\n\tfor (let i = 0; i < indentStr.length; i++) {\n\t\tif (indentStr[i] === '\\t') {\n\t\t\t// Round up to next tab stop\n\t\t\twidth = Math.ceil((width + 1) / tabSize) * tabSize;\n\t\t\thasTab = true;\n\t\t} else {\n\t\t\twidth += 1;\n\t\t}\n\t}\n\treturn { width, hasTab };\n}\n\nfunction createListIndentPlugin() {\n\tconst { RangeSetBuilder } = require_codemirror_state();\n\tconst { Decoration, ViewPlugin } = require_codemirror_view();\n\tconst { getIndentUnit, syntaxTree } = require_codemirror_language();\n\n\tconst wrapIndent = (indent, hasTab) => Decoration.line({\n\t\tattributes: { style: `text-indent: -${indent}ch; padding-left: ${indent}ch;` }\n\t});\n\n\tconst listMarkerRegex = /^(\\s*)([-*+>](?:\\s\\[[Xx ]\\])?|\\d+[.)]|) /;\n\n\treturn ViewPlugin.fromClass(class implements PluginValue {\n\t\tdecorations!: DecorationSet;\n\n\t\tconstructor(view) {\n\t\t\tthis.decorations = this.buildDecorations(view);\n\t\t}\n\t\t\n\t\tupdate(update) {\n\t\t\tif (update.docChanged || update.viewportChanged) {\n\t\t\t\tthis.decorations = this.buildDecorations(update.view);\n\t\t\t}\n\t\t}\n\n\t\tbuildDecorations(view) {\n\t\t\tconst builder = new RangeSetBuilder<Decoration>();\n\t\t\tconst tree = syntaxTree(view.state);\n\t\t\t\n\t\t\tconst tabSize = view.state.tabSize || 6;\n\t\t\t\n\t\t\ttree.iterate({\n\t\t\t\tenter: (node) => {\n\t\t\t\t\tif (node.name === \"ListItem\" || node.name == \"Blockquote\") {\n\t\t\t\t\t\tconst line = view.state.doc.lineAt(node.from);\n\t\t\t\t\t\tconst lineText = line.text;\n\t\t\t\t\t\tconst match = listMarkerRegex.exec(lineText);\n\t\t\t\t\t\t\n\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\tconst indentStr = match[1];\n\t\t\t\t\t\t\tconst marker = match[2];\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// Calculate visual width of indentation\n\t\t\t\t\t\t\tconst { width, hasTab } = calculateIndent(indentStr, tabSize);\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// +1 for the space after the marker\n\t\t\t\t\t\t\tconst markerWidth = marker.length + 1;\n\t\t\t\t\t\t\t// Chrome insists on using tab stops so line indentation needs to be a multiple\n\t\t\t\t\t\t\t// of the tab stop, which also means that we can't align the indented lines with\n\t\t\t\t\t\t\t// the marker :'(\n\t\t\t\t\t\t\tconst totalIndent = width + (hasTab ? 0 : markerWidth);\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tbuilder.add(line.from, line.from, wrapIndent(totalIndent, hasTab));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\t\n\t\t\treturn builder.finish();\n\t\t}\n\t}, {\n\t\tdecorations: v => v.decorations,\n\t})\n}\n\nexport const listIndent = () => {\n\tconst { EditorView } = require_codemirror_view();\n\treturn [\n\t\tEditorView.lineWrapping,\n\t\tcreateListIndentPlugin()\n\t];\n}\n"
  },
  {
    "path": "src/cm6Requires.ts",
    "content": "import type * as CodeMirrorView from '@codemirror/view';\nimport type * as CodeMirrorState from '@codemirror/state';\nimport type * as CodeMirrorLanguage from '@codemirror/language';\n\n// Dynamically imports a CodeMirror 6 library. This is done\n// to allow the plugin to start in both CodeMirror 5 and CodeMirror 6\n// without import failure errors.\nexport function require_codemirror_view(): typeof CodeMirrorView {\n\treturn require('@codemirror/view');\n}\n\nexport function require_codemirror_state(): typeof CodeMirrorState {\n\treturn require('@codemirror/state');\n}\n\nexport function require_codemirror_language(): typeof CodeMirrorLanguage {\n\treturn require('@codemirror/language');\n}\n"
  },
  {
    "path": "src/imageData.ts",
    "content": "import joplin from 'api';\n\nexport async function imageToDataURL(filePath:string, mimeType:string) {\n\tconst fs = joplin.require('fs-extra');\n\tconst fileBuffer = await fs.readFile(filePath);\n\tconst base64String = fileBuffer.toString('base64');\n\treturn `data:${mimeType};base64,${base64String}`;\n}\n"
  },
  {
    "path": "src/images.test.ts",
    "content": "import * as ImageHandlers from './images';\n\nconst test_text = `\n![space-fish.png](:/40530199c558430d8ea01363748d9657){width=100%}\n![おはいよ](https://www.inmoth.ca/images/envelope.png)\n![red.png](file:///home/joplinuser/Pictures/red.png)\n![](:/40530199c558430d8ea)\n![おはいよ](https://www.inmoth.ca/images/envelope.png \"title\"){width=78px}\n![name](https://url.com \"title\" this is all bad)\nsome paragr ![i1.png](:/d9e191134dad42dda2d94ab3e98d3517) something![](:/f190a79a355e4bbb86990cb3b55bedb6)som\n\n[space-fish.png](:/40530199c558430d8ea01363748d9657)\n[おはいよ](https://www.inmoth.ca/images/envelope.png)\n[red.png](file:///home/joplinuser/Pictures/red.png)\nsome paragr [i1.png](:/d9e191134dad42dda2d94ab3e98d3517) something! [](:/f190a79a355e4bbb86990cb3b55bedb6)som\n<img>\n<p src=\":/5d1ac6e676094f4f908c1d0a65694eff\" alt=\"69f775caf86ae2a8e86c3416fee6060d.png\" width=\"163\" height=\"161\" class=\"jop-noMdConv\" style=\"zoom: 50%;\">\n<img src=\":/5d1ac6e676094f4f908c1d0a65694eff\" alt=\"69f775caf86ae2a8e86c3416fee6060d.png\" width=\"135\" height=\"135\" class=\"jop-noMdConv\" style=\"zoom: 0.5;\"><img src=\":/5d1ac6e676094f4f908c1d0a65694eff\" alt=\"69f775caf86ae2a8e86c3416fee6060d.png\" width=\"135\" height=\"135\" class=\"jop-noMdConv\" style=\"transform: scale(0.5);\">\nsomething in a paragraphs <img src=\":/5d1ac6e676094f4f908c1d0a65694eff\" alt=\"69f775caf86ae2a8e86c3416fee6060d.png\" width=\"163\" height=\"161\" class=\"jop-noMdConv\" style=\"zoom: 50%;\"> which is supported by an image\n`\nconst test_html = `<img src=\":/5d1ac6e676094f4f908c1d0a65694eff\" alt=\"69f775caf86ae2a8e86c3416fee6060d.png\" width=\"163\" height=\"161\" class=\"jop-noMdConv\" style=\"zoom: 50%;\">\n<img src=\"https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/LinuxIcons/256x256.png\" alt=\"Joplin Icon\" width=\"331\" height=\"331\" class=\"jop-noMdConv\">\n<img src=\"test\" onerror=\"alert('123')\"/>`\n\n\nconst line_cases = [\n\t[1, \"space-fish.png\", \":/40530199c558430d8ea01363748d9657\", null, \"100%\", \"%\"],\n\t[2, \"おはいよ\", \"https://www.inmoth.ca/images/envelope.png\", null, undefined, undefined],\n\t[3, \"red.png\", \"file:///home/joplinuser/Pictures/red.png\", null, undefined, undefined],\n\t[4, \"\", \":/40530199c558430d8ea\", null, undefined, undefined],\n\t[5, \"おはいよ\", \"https://www.inmoth.ca/images/envelope.png\", \" \\\"title\\\"\", \"78px\", \"px\"],\n\t// The current regex correctly grabs the title, but it doesn't guard against extra bits\n\t[6, \"name\", \"https://url.com\", \" \\\"title\\\" this is all bad\", undefined, undefined],\n]\n\ndescribe(\"Test image line regex matches images that own a line\", () => {\n\tconst lines = test_text.split(\"\\n\");\n\n\ttest.each(line_cases)(\n\t\t\"Line %p matches ![%p](%p)\",\n\t\t(line, name, url, title, width, unit) => {\n\t\t\tlet match = ImageHandlers.image_line_regex.exec(lines[line]);\n\t\t\texpect(match).not.toBeNull();\n\t\t\tconst widthString = width ? `{width=${match[4]}}` : '';\n\t\t\texpect(match[0]).toBe(`![${name}](${url}${title ? title : ''})${widthString}`);\n\t\t\texpect(match[1]).toBe(name);\n\t\t\texpect(match[2]).toBe(url);\n\t\t\t// match[3] is the full match of the {width=?} I won't bother checking it\n\t\t\texpect(match[4]).toBe(width);\n\t\t\texpect(match[5]).toBe(unit);\n\t\t}\n\t);\n});\n\ndescribe(\"Test image line regex does not match anything else\", () => {\n\tconst lines = test_text.split(\"\\n\").concat(test_html.split(\"\\n\"));\n\tconst cases = Array.from({length: lines.length - line_cases.length}, (_, i) => i+line_cases.length+1)\n\ttest.each(cases)(\n\t\t\"Line %p is not a line image\",\n\t\t(line: number) => {\n\t\t\tlet match = ImageHandlers.image_line_regex.exec(lines[line]);\n\t\t\texpect(match).toBeNull();\n\t\t}\n\t);\n});\n\ndescribe(\"Test image inline regex matches all images\", () => {\n\tconst cases = [\n\t\t[\"space-fish.png\", \":/40530199c558430d8ea01363748d9657\", null, \"100%\", \"%\"],\n\t\t[\"おはいよ\", \"https://www.inmoth.ca/images/envelope.png\", null, undefined, undefined],\n\t\t[\"red.png\", \"file:///home/joplinuser/Pictures/red.png\", null, undefined\t, undefined],\n\t\t[\"\", \":/40530199c558430d8ea\", null, undefined, undefined],\n\t\t[\"おはいよ\", \"https://www.inmoth.ca/images/envelope.png\", \" \\\"title\\\"\", \"78px\", \"px\"],\n\t\t// The current regex correctly grabs the title, but it doesn't guard against extra bits\n\t\t[\"name\", \"https://url.com\", \" \\\"title\\\" this is all bad\", undefined, undefined],\n\t\t[\"i1.png\", \":/d9e191134dad42dda2d94ab3e98d3517\", null, undefined, undefined],\n\t\t[\"\", \":/f190a79a355e4bbb86990cb3b55bedb6\", null, undefined, undefined],\n\t];\n\n\ttest.each(cases)(\n\t\t\"Matches from ![%p](%p)\",\n\t\t(name, url, title, width, unit) => {\n\t\t\tlet match = ImageHandlers.image_inline_regex.exec(test_text);\n\t\t\texpect(match).not.toBeNull();\n\t\t\tconst widthString = width ? `{width=${match[4]}}` : '';\n\t\t\texpect(match[0]).toBe(`![${name}](${url}${title ? title : ''})${widthString}`);\n\t\t\texpect(match[1]).toBe(name);\n\t\t\texpect(match[2]).toBe(url);\n\t\t\texpect(match[4]).toBe(width);\n\t\t\texpect(match[5]).toBe(unit);\n\t\t}\n\t);\n\n\ttest(\"There are no more matches\", () => {\n\t\tlet match = ImageHandlers.image_inline_regex.exec(test_text);\n\t\texpect(match).toBeNull();\n\t});\n});\n\ndescribe(\"Test image html line regex only matches html images that own a line\", () => {\n\tconst text_lines = test_text.split(\"\\n\")\n\ttest.each(text_lines)(\n\t\t\"%p is not an html image on it's own line\",\n\t\t(line: string) => {\n\t\t\tlet match = ImageHandlers.html_image_line_regex.exec(line);\n\t\t\texpect(match).toBeNull();\n\t\t}\n\t);\n\n\tconst html_lines = test_html.split(\"\\n\")\n\ttest.each(html_lines)(\n\t\t\"%p is an html image on it's own line\",\n\t\t(line: string) => {\n\t\t\tlet match = ImageHandlers.html_image_line_regex.exec(line);\n\t\t\texpect(match).not.toBeNull();\n\t\t\texpect(match[0]).toBe(line);\n\t\t\t// This is less important because the entire tag is used to generate an image\n\t\t\t// So the rest of the match statement is ignored\n\t\t\t// In the future this might be changed\n\t\t\t// expect(match[1]).toBe('');\n\t\t}\n\t);\n});\n\nconst test_link_text = `\n![space-fish.png](:/40530199c558430d8ea01363748d9657){width=100%}\n[![おはいよ](https://www.inmoth.ca/images/envelope.png)](https://www.inmoth.ca)\n![red.png](file:///home/joplinuser/Pictures/red.png)\n[![](:/40530199c558430d8ea)](https://joplinapp.org)\n[![おはいよ](https://www.inmoth.ca/images/envelope.png \"title\"){width=78px}](https://www.inmoth.ca)\n![name](https://url.com \"title\" this is all bad)\nsome paragr ![i1.png](:/d9e191134dad42dda2d94ab3e98d3517) something![](:/f190a79a355e4bbb86990cb3b55bedb6)som\n\n[space-fish.png](:/40530199c558430d8ea01363748d9657)\n[おはいよ](https://www.inmoth.ca/images/envelope.png)\n[red.png](file:///home/joplinuser/Pictures/red.png)\nsome paragr [i1.png](:/d9e191134dad42dda2d94ab3e98d3517) something! [](:/f190a79a355e4bbb86990cb3b55bedb6)som\n`\n\nconst line_link_cases = [\n\t[1, null],\n\t[2, \"![おはいよ](https://www.inmoth.ca/images/envelope.png)\"],\n\t[3, null],\n\t[4, \"![](:/40530199c558430d8ea)\"],\n\t[5, \"![おはいよ](https://www.inmoth.ca/images/envelope.png \\\"title\\\"){width=78px}\"],\n\t[6, null],\n\t[7, null],\n\t[8, null],\n\t[9, null],\n\t[10, null],\n\t[11, null],\n\t[12, null],\n]\n\ndescribe(\"Test image line inside link regex matches only lines with images inside links\", () => {\n\tconst lines = test_link_text.split(\"\\n\");\n\n\ttest.each(line_link_cases)(\n\t\t\"Line %p matches %p\",\n\t\t(line, image) => {\n\t\t\tlet match = ImageHandlers.image_line_link_regex.exec(lines[line]);\n\t\t\tif (image) {\n\t\t\t\texpect(match).not.toBeNull();\n\t\t\t\texpect(match[1]).toBe(image);\n\t\t\t} else {\n\t\t\t\texpect(match).toBeNull();\n\t\t\t}\n\t\t}\n\t);\n});\n"
  },
  {
    "path": "src/images.ts",
    "content": "import * as ClickHandlers from './clickHandlers';\nimport * as Overlay from './overlay';\n\nimport { require_codemirror_language } from \"./cm6Requires\";\n\nexport const image_line_regex = /^\\s*!\\[([^\\]]*)\\]\\((<[^\\)]+>|[^)\\s]+)[^)]*\\)({width=(\\d+(px|%)?)})?\\s*$/;\nexport const image_line_link_regex = /^\\[(!\\[.*)\\]\\((.*)\\)$/;\nexport const image_inline_regex = /!\\[([^\\]]*)\\]\\((<[^\\)]+>|[^)\\s]+)[^)]*\\)({width=(\\d+(px|%)?)})?/g;\nexport const html_image_line_regex = /^\\s*<img([^>]+?)\\/?>\\s*$/;\n\n// Used to quickly index widgets that will get updated\nlet allWidgets = {};\n\nexport function isSupportedImageMimeType(mime:string) {\n\treturn ['image/png', 'image/jpg', 'image/jpeg'].includes(mime.toLowerCase());\n}\n\nexport function isSupportedOcrMimeType(mime:string) {\n\treturn ['application/pdf'].includes(mime.toLowerCase()) || isSupportedImageMimeType(mime);\n}\n\nexport function onSourceChanged(cm: any, from: number, to: number, context: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\tif (cm.state.richMarkdown.settings.inlineImages) {\n\t\tcheck_lines(cm, from, to, context);\n\t}\n}\n\nexport function afterSourceChanges(cm: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\tif (cm.state.richMarkdown.settings.imageHover)\n\t\tupdate_hover_widgets(cm);\n}\n\nfunction isLineInCodeBlock(editor, syntaxTree, lineNumber) {\n\tconst doc = editor.state.doc\n\tconst line = doc.line(lineNumber)\n\tconst tree = syntaxTree(editor.state)\n\n\tlet node = tree.resolveInner(line.from)\n\n\t// Walk up the tree to find if we're inside a code block\n\twhile (node) {\n\t\tif (node.name === \"FencedCode\" || node.name === \"CodeBlock\") {\n\t\t\treturn true\n\t\t}\n\t\tnode = node.parent\n\t}\n\n\treturn false\n}\n\nasync function getImageData(cm: any, coord: any) {\n\tlet { line, ch } = coord;\n\n\tconst lineText = cm.getLine(line);\n\n\tconst match = ClickHandlers.getMatchAt(lineText, image_inline_regex, ch);\n\tlet img = null;\n\n\tif (match) {\n\t\timg = await createImage(match[2], match[1], cm.state.richMarkdown.path_from_id, match[4], match[5]);\n\t}\n\telse {\n\t\tconst imgMatch = ClickHandlers.getMatchAt(lineText, Overlay.html_image_regex, ch);\n\n\t\tif (imgMatch) {\n\t\t\timg = await createImageFromImg(imgMatch[0], cm.state.richMarkdown.path_from_id);\n\t\t}\n\t}\n\n\treturn img;\n}\n\nfunction open_widget(cm: any) {\n\treturn async function(event: MouseEvent) {\n\t\tif (!event.target) return;\n\t\tif (!cm.state.richMarkdown) return;\n\t\tif (!cm.state.richMarkdown.settings.imageHover) return;\n\t\t// This shortcut is only enabled for the ctrl case because in the default case I would\n\t\t// prefer to accidentally display 2 images rather than not dislpay anything\n\t\tif (!cm.state.richMarkdown.settings.imageHoverCtrl &&\n\t\t\t\t(!(event.ctrlKey || event.altKey) || cm.state.richMarkdown.isMouseHovering)) return;\n\n\t\tcm.state.richMarkdown.isMouseHovering = true;\n\n\t\tconst target = event.target as HTMLElement;\n\t\tif (!target.offsetParent) return;\n\n\t\t// This already has the image rendered inline\n\t\tif (cm.state.richMarkdown.settings.inlineImages && target.parentNode.childNodes.length <= 3)\n\t\t\treturn;\n\n\t\tconst img = await getImageData(cm, ClickHandlers.getClickCoord(cm, event));\n\t\tif (!img) return;\n\n\t\t// manual zoom\n\t\tif (img.style.zoom) {\n\t\t\tlet zoom = parseFloat(img.style.zoom);\n\t\t\tif (img.style.zoom.endsWith('%')){\n\t\t\t\tzoom /= 100\n\t\t\t}\n\t\t\tif (zoom) {\n\t\t\t\timg.width *= zoom\n\t\t\t\timg.height *= zoom\n\t\t\t\timg.style.zoom = '100%';\n\t\t\t}\n\t\t}\n\n\t\timg.style.visibility = 'hidden';\n\t\ttarget.offsetParent.appendChild(img);\n\t\timg.style.position = 'absolute';\n\t\timg.style.zIndex = '1000';\n\t\timg.classList.add('rich-markdown-hover-image');\n\t\timg.onload = function() {\n\t\t\tif (!cm.state.richMarkdown) return;\n\t\t\tif (!cm.state.richMarkdown.isMouseHovering) {\n\t\t\t\timg.remove();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet im = this as HTMLElement;\n\t\t\tconst par = target.offsetParent as HTMLElement;\n\t\t\tconst { right, width } = par.getBoundingClientRect();\n\n\t\t\tlet x = 0;\n\t\t\tif (im.clientWidth < width) {\n\t\t\t\tx = Math.min(event.clientX, right - im.clientWidth);\n\t\t\t}\n\n\t\t\tconst coords = cm.coordsChar({left: x, top: event.clientY}, 'page');\n\n\t\t\tim.style.visibility = 'visible';\n\t\t\tcm.addWidget(coords, img, false);\n\t\t}\n\t}\n}\n\nfunction clearHoverImages(cm: any) {\n\tconst widgets = cm.getWrapperElement().getElementsByClassName('rich-markdown-hover-image');\n\n\t// Removed widgets are simultaneously removed from this array\n\t// we need to iterate backwards to prevent the array from changing on us\n\tfor (let i = widgets.length -1; i >= 0; i--) {\n\t\twidgets[i].remove();\n\t}\n}\n\nfunction close_widget(cm: any) {\n\treturn function(event: MouseEvent) {\n\t\tif (cm.state.richMarkdown)\n\t\t\tcm.state.richMarkdown.isMouseHovering = false;\n\n\t\tclearHoverImages(cm);\n\t}\n}\n\nfunction update_hover_widgets(cm: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\t// If the image source is removed, this update funciton won't catch it\n\t// and the image will be stuck around forever joplin-rich-markdown/issues/69\n\t// this prevents this from happening (by pre-emptively clearing the image)\n\t// but causes a flicker while editing and hovering.\n\tclearHoverImages(cm);\n\n\tconst images = cm.getWrapperElement().getElementsByClassName(\"cm-rm-image\");\n\n\tfor (let image of images) {\n\t\t\timage.onmouseenter = open_widget(cm);\n\t\t\timage.onmouseleave = close_widget(cm);\n\t\t\tif (!cm.state.richMarkdown.settings.imageHoverCtrl) {\n\t\t\t\timage.onmousemove = open_widget(cm);\n\t\t\t}\n\t}\n}\n\nasync function check_lines(cm: any, from: number, to: number, context: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\tconst path_from_id = cm.state.richMarkdown.path_from_id;\n\tlet needsRefresh = false;\n\n\tfor (let i = from; i <= to; i++) {\n\t\tconst line = cm.lineInfo(i);\n\n\t\tif (line.widgets) {\n\t\t\tfor (const wid of line.widgets) {\n\t\t\t\tif (wid.className === 'rich-markdown-resource')\n\t\t\t\t\twid.clear();\n\t\t\t\t\tdelete allWidgets[wid.node.id];\n\t\t\t}\n\t\t}\n\n\t\tif (!line) { continue; }\n\n\t\tif (cm.cm6) {\n\t\t\tif (!cm.state.richMarkdown.language) {\n\t\t\t\tcm.state.richMarkdown.language = require_codemirror_language();\n\t\t\t}\n\t\t\tconst syntaxTree = cm.state.richMarkdown.language.syntaxTree;\n\t\t\t// cm6 uses 1 based indexing for line numbers, but cm5 uses 0 based\n\t\t\t// the line object we have here is emulated cm5, so it uses 0 based\n\t\t\t// but the checking function is cm6, so we need to adjust\n\t\t\tif (isLineInCodeBlock(cm.editor, syntaxTree, line.line + 1)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else {\n\t\t\tconst state = cm.getStateAfter(i, true);\n\n\t\t\t// Don't render inline images inside of code blocks (not for cm5/legacy editor only)\n\t\t\tif (state?.outer && (state?.outer?.code || (state?.outer?.thisLine?.fencedCodeEnd))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t// Special Case\n\t\t// If the line only contains a link wrapped around an image, we should match against that\n\t\tconst line_link_match = line.text.match(image_line_link_regex);\n\t\tlet lineText = line.text;\n\t\tlet lineLink = '';\n\t\tif (line_link_match) {\n\t\t\tlineText = line_link_match[1];\n\t\t\tlineLink = line_link_match[2];\n\t\t}\n\n\t\tconst match = lineText.match(image_line_regex);\n\t\tlet img = null;\n\n\t\tif (match) {\n\t\t\timg = await createImage(match[2], match[1], path_from_id, match[4], match[5], context, lineLink);\n\t\t}\n\t\telse {\n\t\t\tconst imgMatch = line.text.match(html_image_line_regex);\n\n\t\t\tif (imgMatch) {\n\t\t\t\timg = await createImageFromImg(imgMatch[0], path_from_id);\n\t\t\t}\n\t\t}\n\n\t\tif (img) {\n\t\t\tconst wid = cm.addLineWidget(i, img, { className: 'rich-markdown-resource' });\n\t\t\tallWidgets[img.id] = wid;\n\t\t\tneedsRefresh = true;\n\t\t}\n\t}\n\tif (needsRefresh) {\n\t\tcm.refresh();\n\t}\n}\n\nasync function createImageFromImg(imgTag: string, path_from_id: any) {\n\tconst par = new DOMParser().parseFromString(imgTag, \"text/html\");\n\tconst img = par.body.firstChild as HTMLImageElement;\n\timg.style.height = img.style.height || 'auto';\n\timg.style.maxWidth = img.style.maxWidth || '100%';\n\n\t// Tags taken from\n\t// https://github.com/laurent22/joplin/blob/80b16dd17e227e3f538aa221d7b6cc2d81688e72/packages/renderer/htmlUtils.ts\n\tconst disallowedTags = ['script', 'noscript', 'iframe', 'frameset', 'frame', 'object', 'base', 'embed', 'link', 'meta'];\n\tfor (let i = 0; i < img.attributes.length; i++) {\n\t\tconst name = img.attributes[i].name;\n\t\tif (disallowedTags.includes(name) || name.startsWith('on')) {\n\t\t\timg.attributes[i].value = '';\n\t\t}\n\t}\n\n\t// Joplin resource paths get added on to the end of the local path for some reason\n\tif (img.src.length >= 34) {\n\t\tconst id = img.src.substring(img.src.length - 34);\n\t\tif (id.startsWith(':/')) {\n\t\t\timg.src = await path_from_id(id.substring(2));\n\t\t\timg.id = id.substring(2);\n\t\t}\n\t}\n\n\treturn img;\n}\n\nasync function createImage(path: string, alt: string, path_from_id: any, width?: string, unit?: string, context?: any, link?: string) {\n\tlet id = path.substring(2)\n\tif (path.startsWith(':/') && path.length == 34) {\n\t\tpath = await path_from_id(id);\n\t}\n\tif (path.startsWith('<') && path.endsWith('>')) {\n\t\t// <> quotes are not allowed in URLs as per RFC 1738\n\t\t// https://www.ietf.org/rfc/rfc1738.txt\n\t\t// Page 2 includes a list of unsafe characters\n\t\tpath = path.substring(1, path.length - 1);\n\t}\n\n\tconst img = document.createElement('img');\n\timg.src = path;\n\timg.alt = alt;\n\timg.style.maxWidth = '100%';\n\timg.style.height = 'auto';\n\tif (link && context) {\n\t\timg.onclick = () => {\n\t\t\tcontext.postMessage({ name: 'followLink', url: link });\n\t\t};\n\t}\n\tif (width) {\n\t\timg.style.width = width + (unit ? '' : 'px');\n\t}\n\n\t// This will either contain the resource id or some gibberish path\n\timg.id = id;\n\n\treturn img;\n}\n\n// Reload the specified resource on disk, this will be in response\n// to changes made by the user\nexport function refreshResource(cm: any, id: string) {\n\tconst timestamp = new Date().getTime();\n\tlet wid = allWidgets[id];\n\n\tconst path = wid.node.src.split(\"?t=\")[0];\n\n\tconst height = wid.node.height;\n\twid.node.onload = function() {\n\t\tlet im = this as HTMLImageElement;\n\t\t// If the image is scrolled out of view (no need to refresh), it won't have a clientRect\n\t\tif (im.getClientRects().length == 0) { return; }\n\n\t\tif (im.height != height) {\n\t\t\tcm.refresh();\n\t\t}\n\t};\n\twid.node.src = `${path}?t=${timestamp}`;\n}\n\n// Used on cleanup\nexport function clearAllWidgets(cm: any) {\n\tclearHoverImages(cm);\n\n\tfor (let id in allWidgets) {\n\t\tallWidgets[id].clear();\n\t}\n\tallWidgets = {};\n\n\t// Refresh codemirror to make sure everything is sized correctly\n\tcm.refresh();\n}\n\n"
  },
  {
    "path": "src/indent.ts",
    "content": "// This module is modified from the CodeMirror indent wrap demo\n// https://codemirror.net/demo/indentwrap.html\n\nimport { list_token_regex } from './overlay';\n\n// These variables are cached when the plugin is loaded\n// This stores the width of a space in the current font\nlet spaceWidth = 0;\n// This stores the width of a monospace character using the current monospace font\nlet monoSpaceWidth = 0;\n// This stores the width of the > character in the current font\nlet blockCharWidth = 0;\n\n// Must be called when the editor is mounted\nexport function calculateSpaceWidth(cm: any) {\n\tspaceWidth = charWidth(cm, ' ', '');\n\tmonoSpaceWidth = charWidth(cm, ' ', 'cm-rm-monospace');\n\tblockCharWidth = charWidth(cm, '>', '');\n}\n\n// Adapted from codemirror/lib/codemirror.js\nfunction charWidth(cm: any, chr: string, cls: string) {\n\tlet e = document.createElement('span');\n\tif (cls)\n\t\te.classList.add(cls);\n\te.style.whiteSpace = \"pre-wrap\";\n\te.appendChild(document.createTextNode(chr.repeat(10)))\n\n\tconst measure = cm.getWrapperElement().getElementsByClassName('CodeMirror-measure')[0];\n\tif (measure.firstChild)\n\t\tmeasure.removeChild(measure.firstChild);\n\n\tmeasure.appendChild(e);\n\n  const rect = e.getBoundingClientRect()\n\tconst width = (rect.right - rect.left) / 10;\n\n  return width || cm.defaultCharWidth();\n\n}\n\n// Adapted from\n// https://github.com/codemirror/CodeMirror/blob/master/demo/indentwrap.html\nexport function onRenderLine(cm: any, line: any, element: HTMLElement, CodeMirror: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\tif (cm.state.richMarkdown.settings.alignIndent) {\n\t\tconst matches = line.text.match(list_token_regex);\n\t\tif (!matches) return;\n\n\t\tlet off = CodeMirror.countColumn(line.text, matches[0].length, cm.getOption(\"tabSize\")) * spaceWidth;\n\n\t\t// Special case handling for checkboxes with monospace enabled\n\t\tif (cm.state.richMarkdown.settings.enforceMono && matches[0].indexOf('[') > 0) {\n\t\t\t// \"- [ ] \" is 6 characters\n\t\t\toff += monoSpaceWidth * 6 - spaceWidth * 6;\n\t\t}\n\t\telse if (cm.state.richMarkdown.settings.enforceMono && matches[0].indexOf('>') >= 0) {\n\t\t\toff += blockCharWidth - spaceWidth;\n\t\t}\n\n\t\telement.style.textIndent = \"-\" + off + \"px\";\n    element.style.paddingLeft = off + \"px\";\n\t}\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import joplin from 'api';\nimport { ContentScriptType, MenuItem, MenuItemLocation, ModelType } from 'api/types';\nimport { getAllSettings, registerAllSettings } from './settings';\n\n// TODO: Waiting for https://github.com/laurent22/joplin/pull/4509\n// import prettier = require('prettier/standalone');\n// import markdown = require('prettier/parser-markdown');\nimport { TextItem, TextItemType } from './clickHandlers';\nimport { isSupportedImageMimeType, isSupportedOcrMimeType } from './images';\nimport { imageToDataURL } from './imageData';\n\nconst fs = joplin.require('fs-extra');\nconst { parseResourceUrl } = require('@joplin/lib/urlUtils');\n\nconst contentScriptId = 'richMarkdownEditor';\n\njoplin.plugins.register({\n\tonStart: async function() {\n\t\t// There is a bug (race condition?) where the perform action command\n\t\t// doesn't always work when first opening the app. Opening the keyboard\n\t\t// shortcuts will properly bind it and make it work.\n\t\t// Placing the command before registering settings also seems to fix it\n\t\tawait joplin.commands.register({\n\t\t\tname: 'editor.richMarkdown.clickAtCursor',\n\t\t\tlabel: 'Perform action',\n\t\t\ticonName: 'fas fa-link',\n\t\t\texecute: async () => {\n\t\t\t\tawait joplin.commands.execute('editor.execCommand', {\n\t\t\t\t\tname: 'clickUnderCursor',\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t\tawait joplin.views.menuItems.create('richMarkdownClickAtCursor', 'editor.richMarkdown.clickAtCursor', MenuItemLocation.Note, { accelerator: 'Ctrl+Enter' });\n\n\t\t// TODO: See about getting this same behaviour into the openItem function\n\t\tawait joplin.commands.register({\n\t\t\tname: 'app.richMarkdown.openItem',\n\t\t\texecute: async (url: string) => {\n\t\t\t\t// From RFC 1738 Page 1 a url is <scheme>:<scheme specific part>\n\t\t\t\t// the below regex implements matching for the scheme (with support for uppercase)\n\t\t\t\t// urls without a scheme will be assumed http\n\t\t\t\tif (!url.startsWith(':/') && !url.match(/^(?:[a-zA-Z0-9\\+\\.\\-])+:/)) {\n\t\t\t\t\turl = 'http://' + url;\n\t\t\t\t}\n\t\t\t\tawait joplin.commands.execute('openItem', url);\n\t\t\t},\n\t\t});\n\n\t\tawait joplin.commands.register({\n\t\t\tname: 'editor.richMarkdown.toggleCheckbox',\n\t\t\texecute: async (coord: any) => {\n\t\t\t\tawait joplin.commands.execute('editor.execCommand', {\n\t\t\t\t\tname: 'toggleCheckbox',\n\t\t\t\t\targs: [coord],\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t\tawait joplin.commands.register({\n\t\t\tname: 'editor.richMarkdown.checkCheckbox',\n\t\t\texecute: async (coord: any) => {\n\t\t\t\tawait joplin.commands.execute('editor.execCommand', {\n\t\t\t\t\tname: 'checkCheckbox',\n\t\t\t\t\targs: [coord],\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t\tawait joplin.commands.register({\n\t\t\tname: 'editor.richMarkdown.uncheckCheckbox',\n\t\t\texecute: async (coord: any) => {\n\t\t\t\tawait joplin.commands.execute('editor.execCommand', {\n\t\t\t\t\tname: 'uncheckCheckbox',\n\t\t\t\t\targs: [coord],\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\tawait joplin.commands.register({\n\t\t\tname: 'editor.richMarkdown.copyImage',\n\t\t\texecute: async (itemId: string) => {\n\t\t\t\tconst resource = await joplin.data.get(['resources', itemId], { fields: ['mime'] });\n\t\t\t\tconst resourcePath = await joplin.data.resourcePath(itemId);\n\t\t\t\tconst dataUrl = await imageToDataURL(resourcePath, resource.mime);\n\t\t\t\tawait joplin.clipboard.writeImage(dataUrl);\n\t\t\t},\n\t\t});\n\n\t\tawait joplin.commands.register({\n\t\t\tname: 'editor.richMarkdown.viewOcrText',\n\t\t\texecute: async (itemId: string) => {\n\t\t\t\tconst resource = await joplin.data.get(['resources', itemId], { fields: ['id', 'mime', 'ocr_text', 'ocr_status'] });\n\t\t\t\tif (resource.ocr_status === 2) { // ResourceOcrStatus.Done\n\t\t\t\t\tconst tempFilePath = `${await joplin.plugins.dataDir()}/${resource.id}_ocr.txt`;\n\t\t\t\t\tawait fs.writeFile(tempFilePath, resource.ocr_text, 'utf8');\n\t\t\t\t\tconst fileUrl = `file://${tempFilePath.replace(/\\\\/g, '/')}`;\n\t\t\t\t\tawait joplin.commands.execute('openItem', fileUrl);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.info(`OCR of resource ${itemId} is not ready yet ${resource.ocr_status}`);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\n\t\tawait joplin.commands.register({\n\t\t\tname: 'editor.richMarkdown.copyPathToClipboard',\n\t\t\texecute: async (path: string) => {\n\t\t\t\tawait joplin.clipboard.writeText(path);\n\t\t\t},\n\t\t});\n\n\t\t// Helper to build menu items for a resource (image or other attachment)\n\t\tconst buildResourceMenuItems = async (resourceId: string, openUrl: string): Promise<MenuItem[]> => {\n\t\t\tconst items: MenuItem[] = [];\n\t\t\tconst resource = await joplin.data.get(['resources', resourceId], { fields: ['mime'] });\n\n\t\t\titems.push({\n\t\t\t\tlabel: 'Open link',\n\t\t\t\tcommandName: 'app.richMarkdown.openItem',\n\t\t\t\tcommandArgs: [openUrl],\n\t\t\t});\n\n\t\t\titems.push({\n\t\t\t\tlabel: 'Reveal file in folder',\n\t\t\t\tcommandName: 'revealResourceFile',\n\t\t\t\tcommandArgs: [resourceId],\n\t\t\t});\n\n\t\t\tif (isSupportedOcrMimeType(resource.mime)) {\n\t\t\t\titems.push({\n\t\t\t\t\tlabel: 'View OCR text',\n\t\t\t\t\tcommandName: 'editor.richMarkdown.viewOcrText',\n\t\t\t\t\tcommandArgs: [resourceId],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (isSupportedImageMimeType(resource.mime)) {\n\t\t\t\titems.push({\n\t\t\t\t\tlabel: 'Copy image',\n\t\t\t\t\tcommandName: 'editor.richMarkdown.copyImage',\n\t\t\t\t\tcommandArgs: [resourceId],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst resourcePath = await joplin.data.resourcePath(resourceId);\n\t\t\titems.push({\n\t\t\t\tlabel: 'Copy path to clipboard',\n\t\t\t\tcommandName: 'editor.richMarkdown.copyPathToClipboard',\n\t\t\t\tcommandArgs: [resourcePath],\n\t\t\t});\n\n\t\t\treturn items;\n\t\t};\n\n\t\t// Helper to build menu items for a non-resource link\n\t\tconst buildLinkMenuItems = (url: string): MenuItem[] => {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Open link',\n\t\t\t\t\tcommandName: 'app.richMarkdown.openItem',\n\t\t\t\t\tcommandArgs: [url],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Copy link to clipboard',\n\t\t\t\t\tcommandName: 'editor.richMarkdown.copyPathToClipboard',\n\t\t\t\t\tcommandArgs: [url],\n\t\t\t\t},\n\t\t\t];\n\t\t};\n\n\t\tawait joplin.workspace.filterEditorContextMenu(async (object: any) => {\n\t\t\t// Use context passed by Joplin if available (preferred method).\n\t\t\t// This correctly identifies what was right-clicked even when the cursor\n\t\t\t// is elsewhere (e.g., after switching from markdown editor to viewer).\n\t\t\tconst context = object.context || {};\n\t\t\tconst contextResourceId = context.resourceId;\n\t\t\tconst contextItemType = context.itemType;\n\n\t\t\t// Fall back to cursor-based detection for backward compatibility\n\t\t\t// and for items not covered by context (e.g., checkboxes)\n\t\t\tconst textItems: TextItem[] = await joplin.commands.execute('editor.execCommand', {\n\t\t\t\tname: 'getItemsUnderCursor',\n\t\t\t});\n\t\t\tconst selection = await joplin.commands.execute('selectedText');\n\n\t\t\tconst newItems: MenuItem[] = [];\n\n\t\t\t// If context indicates an image/resource was right-clicked, use that\n\t\t\tif (contextResourceId && (contextItemType === 'image' || contextItemType === 'resource')) {\n\t\t\t\ttry {\n\t\t\t\t\tconst resourceItems = await buildResourceMenuItems(contextResourceId, `:/${contextResourceId}`);\n\t\t\t\t\tnewItems.push(...resourceItems);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.warn('Rich Markdown: Failed to get resource info from context', error);\n\t\t\t\t}\n\t\t\t} else if (textItems.length) {\n\t\t\t\t// Fall back to cursor-based detection\n\t\t\t\tfor (const textItem of textItems) {\n\t\t\t\t\tif (textItem.type === TextItemType.Link || textItem.type === TextItemType.Image) {\n\t\t\t\t\t\tconst info = parseResourceUrl(textItem.url);\n\t\t\t\t\t\tconst itemId = info ? info.itemId : null;\n\t\t\t\t\t\tconst itemType = itemId ? await joplin.data.itemType(itemId) : null;\n\n\t\t\t\t\t\tif (itemType === ModelType.Resource) {\n\t\t\t\t\t\t\tconst resourceItems = await buildResourceMenuItems(itemId, textItem.url);\n\t\t\t\t\t\t\tnewItems.push(...resourceItems);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst linkItems = buildLinkMenuItems(textItem.url);\n\t\t\t\t\t\t\tnewItems.push(...linkItems);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (textItem.type === TextItemType.Checkbox) {\n\t\t\t\t\t\tconst newlineRegex = /[\\r\\n]/;\n\t\t\t\t\t\tif (newlineRegex.test(selection)) {\n\t\t\t\t\t\t\tnewItems.push({\n\t\t\t\t\t\t\t\tlabel: 'Toggle all',\n\t\t\t\t\t\t\t\tcommandName: 'editor.richMarkdown.toggleCheckbox',\n\t\t\t\t\t\t\t\tcommandArgs: [textItem.coord],\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tnewItems.push({\n\t\t\t\t\t\t\t\tlabel: 'Uncheck all',\n\t\t\t\t\t\t\t\tcommandName: 'editor.richMarkdown.uncheckCheckbox',\n\t\t\t\t\t\t\t\tcommandArgs: [textItem.coord],\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tnewItems.push({\n\t\t\t\t\t\t\t\tlabel: 'Check all',\n\t\t\t\t\t\t\t\tcommandName: 'editor.richMarkdown.checkCheckbox',\n\t\t\t\t\t\t\t\tcommandArgs: [textItem.coord],\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnewItems.push({\n\t\t\t\t\t\t\t\tlabel: 'Toggle checkbox',\n\t\t\t\t\t\t\t\tcommandName: 'editor.richMarkdown.toggleCheckbox',\n\t\t\t\t\t\t\t\tcommandArgs: [textItem.coord],\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (newItems.length) {\n\t\t\t\tnewItems.splice(0, 0, {\n\t\t\t\t\ttype: 'separator',\n\t\t\t\t});\n\n\t\t\t\tobject.items = object.items.concat(newItems);\n\t\t\t}\n\n\t\t\treturn object;\n\t\t});\n\n\t\tawait registerAllSettings();\n\n\t\tawait joplin.contentScripts.register(\n\t\t\tContentScriptType.CodeMirrorPlugin,\n\t\t\tcontentScriptId,\n\t\t\t'./richMarkdown.js'\n\t\t);\n\n\t\tawait joplin.contentScripts.onMessage(contentScriptId, async (message: any) => {\n\t\t\tif (message.name === 'getResourcePath') {\n\t\t\t\treturn await joplin.data.resourcePath(message.id);\n\t\t\t}\n\t\t\telse if (message.name === 'getSettings') {\n\t\t\t\treturn await getAllSettings();\n\t\t\t}\n\t\t\telse if (message.name === 'followLink') {\n\t\t\t\tawait joplin.commands.execute('app.richMarkdown.openItem', message.url);\n\t\t\t}\n\n\t\t\treturn \"Error: \" + message + \" is not a valid message\";\n\t\t});\n\n\t\tawait (joplin.workspace as any).onResourceChange(async (event: any) => {\n\t\t\tawait joplin.commands.execute('editor.execCommand', {\n\t\t\t\tname: 'refreshResource',\n\t\t\t\targs: [event.id],\n\t\t\t});\n\t\t});\n\n\t\t// TODO: Waiting for https://github.com/laurent22/joplin/pull/4509\n\t\t// await joplin.commands.register({\n\t\t//     name: 'editor.richMarkdown.prettifySelection',\n\t\t//     label: 'Prettify Selection',\n\t\t//     iconName: 'fas fa-rocket',\n\t\t//     execute: async () => {\n\t\t// \t\t\tconst text =  await joplin.commands.execute('selectedText');\n\t\t// \t\t\tconst formatted = prettier.format(text, { parser: 'markdown', plugins: [markdown] });\n\n\t\t// \t\t\tawait joplin.commands.execute('replaceSelection', formatted);\n\t\t//     },\n\t\t// });\n\t\t// await joplin.views.menuItems.create('prettifySelectionContext', 'editor.richMarkdown.prettifySelection', MenuItemLocation.EditorContextMenu);\n\t\t// await joplin.views.menuItems.create('prettifySelectionEdit', 'editor.richMarkdown.prettifySelection', MenuItemLocation.Edit);\n\t\t// await joplin.views.toolbarButtons.create('prettifySelectionToolbar', 'editor.richMarkdown.prettifySelection', ToolbarButtonLocation.EditorToolbar);\n\t},\n});\n"
  },
  {
    "path": "src/manifest.json",
    "content": "{\n\t\"manifest_version\": 1,\n\t\"id\": \"plugin.calebjohn.rich-markdown\",\n\t\"app_min_version\": \"3.5.9\",\n\t\"version\": \"0.17.1\",\n\t\"name\": \"Rich Markdown\",\n\t\"description\": \"Helping you ditch the markdown viewer for good.\",\n\t\"author\": \"Caleb John\",\n\t\"homepage_url\": \"https://github.com/CalebJohn/joplin-rich-markdown#readme\",\n\t\"repository_url\": \"https://github.com/CalebJohn/joplin-rich-markdown\",\n\t\"keywords\": [\n\t\t\"editor\",\n\t\t\"visual\"\n\t],\n\t\"categories\": [\n\t\t\"appearance\",\n\t\t\"editor\"\n\t],\n\t\"screenshots\": [\n\t\t{\n\t\t\t\"src\": \"examples/welcome.png\",\n\t\t\t\"label\": \"A demonstration of what a note can look like using this plugin.\"\n\t\t}\n\t],\n\t\"icons\": {\n\t\t\"16\": \"icons/16.png\",\n\t\t\"32\": \"icons/32.png\",\n\t\t\"48\": \"icons/48.png\",\n\t\t\"128\": \"icons/128.png\"\n\t}\n}"
  },
  {
    "path": "src/overlay.test.ts",
    "content": "import * as Overlay from './overlay';\n\n\ndescribe('link regex', () => {\n\ttest('valid urls', () => {\n\t\texpect('[many](https://calebjohn.ca)').toMatch(Overlay.link_regex);\n\t\texpect('<joplinapp.org>').toMatch(Overlay.link_regex);\n\t\texpect('https://joplinapp.org').toMatch(Overlay.link_regex);\n\t\texpect('https://joplinapp.org/www(x=y)').toMatch(Overlay.link_regex);\n\t\texpect('[even this](https://joplinapp.org/www(x=y))').toMatch(Overlay.link_regex);\n\t\texpect('[even this](https://joplinapp.org/www(x=y)skmfnsm)').toMatch(Overlay.link_regex);\n\t\texpect('[](test)').toMatch(Overlay.link_regex);\n\t\texpect('[ev]()').toMatch(Overlay.link_regex);\n\t\texpect('[](www.google.ca \"soe\")').toMatch(Overlay.link_regex);\n\t});\n\n\ttest('match groups', () => {\n\t\tlet str = '[many](https://calebjohn.ca)';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tlet match = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe(str);\n\t\texpect(match[1]).toBe('https://calebjohn.ca');\n\t\texpect(match[2]).toBeUndefined();\n\t\texpect(match[3]).toBeUndefined();\n\n\t\tstr = '<joplinapp.org>';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tmatch = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe(str);\n\t\texpect(match[1]).toBeUndefined();\n\t\texpect(match[2]).toBe('joplinapp.org');\n\t\texpect(match[3]).toBeUndefined();\n\n\t\tstr = 'https://joplinapp.org';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tmatch = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe(str);\n\t\texpect(match[1]).toBeUndefined();\n\t\texpect(match[2]).toBeUndefined();\n\t\texpect(match[3]).toBe('https://joplinapp.org');\n\n\t\tstr = 'https://joplinapp.org/www(x=y)';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tmatch = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe(str);\n\t\texpect(match[1]).toBeUndefined();\n\t\texpect(match[2]).toBeUndefined();\n\t\texpect(match[3]).toBe('https://joplinapp.org/www(x=y)');\n\n\t\tstr = '[even this](https://joplinapp.org/www(x=y))';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tmatch = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe(str);\n\t\texpect(match[1]).toBe('https://joplinapp.org/www(x=y)');\n\t\texpect(match[2]).toBeUndefined();\n\t\texpect(match[3]).toBeUndefined();\n\n\t\tstr = '[even this](https://joplinapp.org/www(x=y)skmfnsm)';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tmatch = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe(str);\n\t\texpect(match[1]).toBe('https://joplinapp.org/www(x=y)skmfnsm');\n\t\texpect(match[2]).toBeUndefined();\n\t\texpect(match[3]).toBeUndefined();\n\n\t\t// It's too difficult to support nested parens using regexes\n\t\t// let's hope we never face such a cursed url\n\t\tstr = '[even this](https://joplinapp.org/www(x(=)y)skmfnsm)';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tmatch = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe('[even this](https://joplinapp.org/www(x(=)y)');\n\t\texpect(match[1]).toBe('https://joplinapp.org/www(x(=)y');\n\t\texpect(match[2]).toBeUndefined();\n\t\texpect(match[3]).toBeUndefined();\n\n\t\tstr = '[](test)';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tmatch = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe(str);\n\t\texpect(match[1]).toBe('test');\n\t\texpect(match[2]).toBeUndefined();\n\t\texpect(match[3]).toBeUndefined();\n\n\t\tstr = '[](www.google.ca \"soe\")';\n\t\tOverlay.link_regex.lastIndex = 0;\n\t\tmatch = Overlay.link_regex.exec(str);\n\t\texpect(match[0]).toBe(str);\n\t\texpect(match[1]).toBe('www.google.ca \"soe\"');\n\t\texpect(match[2]).toBeUndefined();\n\t\texpect(match[3]).toBeUndefined();\n\n\t});\n});\n\ndescribe('highlight regex', () => {\n\ttest('valid highlight', () => {\n\t\texpect('==highlight==').toMatch(Overlay.highlight_regex);\n\t\texpect('==high light==').toMatch(Overlay.highlight_regex);\n\t\texpect('==highlight=me==').toMatch(Overlay.highlight_regex);\n\t\texpect('==highlight=me=please==').toMatch(Overlay.highlight_regex);\n\t});\n\ttest('invalid highlight', () => {\n\t\texpect('\\\\==lowlight==').not.toMatch(Overlay.highlight_regex);\n\t\texpect('==lowlight\\\\==').not.toMatch(Overlay.highlight_regex);\n\t\texpect('== lowlight==').not.toMatch(Overlay.highlight_regex);\n\t\texpect('==lowlight ==').not.toMatch(Overlay.highlight_regex);\n\t\texpect('=lowlight=').not.toMatch(Overlay.highlight_regex);\n\t\texpect('lowlight== lowlight==').not.toMatch(Overlay.highlight_regex);\n\t});\n});\n\ndescribe('html full image regex', () => {\n\ttest('valid html matches', () => {\n\t\texpect('<img src=\":/8be52e7f659b4cd29f1fe962e9e42b1d\" alt=\"\" width=\"547\" height=\"420\" class=\"jop-noMdConv\">').toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<img    class=\"fit-picture\" src=\"/shared-assets/images/examples/grapefruit-slice.jpg\" alt=\"Grapefruit slice atop a pile of other slices\" />').toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<img src=\"image-file.png\" alt=\"My image file description\" attributionsrc=\"https://a.example/register-source https://b.example/register-source\" />').toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<img alt=\"A Penguin on a beach.\" src=\"penguin.jpg\" />').toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<a href=\"https://developer.mozilla.org\"><img   src=\"/shared-assets/images/examples/favicon144.png\"  alt=\"Visit the MDN site\" /></a>').toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<img src=\"/shared-assets/images/examples/favicon72.png\" alt=\"MDN\" srcset=\"/shared-assets/images/examples/favicon144.png 2x\" />').toMatch(Overlay.html_full_image_regex);\n\t});\n\n\ttest('invalid html matches', () => {\n\t\texpect('<imgsrc=\":/8be52e7f659b4cd29f1fe962e9e42b1d\" alt=\"\" width=\"547\" height=\"420\" class=\"jop-noMdConv\">').not.toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<img >').not.toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<img    class=\"fit-picture\" alt=\"Grapefruit slice atop a pile of other slices\" />').not.toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<img alt=\"A Penguin on a beach.\" src=\"penguin.jpg\" /').not.toMatch(Overlay.html_full_image_regex);\n\n\t\texpect('<img alt=\"MDN\" srcset=\"/shared-assets/images/examples/favicon144.png 2x\" />').not.toMatch(Overlay.html_full_image_regex);\n\t});\n});\n"
  },
  {
    "path": "src/overlay.ts",
    "content": "\n// Taken from codemirror/addon/edit/continuelist.js\nexport const checkbox_regex = /^(\\s*)((?:[\\*\\+\\-\\#]|[\\#]+) )(\\[[Xx ]\\])\\s.*$/g;\nexport const checkboxed_regex = /^(\\s*)((?:[\\*\\+\\-\\#]|[\\#]+) )(\\[[Xx]\\])\\s.*$/g;\nexport const checkbox_inner_regex = /(?<=\\[)[Xx ](?=\\])/g;\nexport const checkbox_inner_checked_regex = /(?<=\\[)[Xx](?=\\])/g;\n// Last part of regex taken from https://stackoverflow.com/a/17773849/12245502\n// This regex will match html tags tht somehow include a . in them\n// I've decided that this is an acceptable level of functionality\nexport const link_regex = /(?<![\\\\])\\[[^\\]]*\\]\\(([^()]*(?:\\([^)]*\\)[^)]*)*)\\)|<([^>\\s]+\\.[^>\\s]+)>|((?:[a-zA-Z0-9\\+\\.\\-])+:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|(?:[a-zA-Z0-9\\+\\.\\-])+:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})|joplin:\\/\\/x-callback-url\\/.*/g;\nexport const link_reference_regex = /(?<![\\\\])\\[([^\\]]*)\\](?!\\()|\\]\\[([^\\]]*)\\]\\s/g;\nexport const link_label_regex = /((?<![\\\\])\\[(?:[^\\sxX]|(?:[^\\]][^\\]]+))\\])/g;\nexport const image_regex = /!\\[[^\\]]*\\]\\([^\\(]+\\)/g;\n// Modified from https://stackoverflow.com/a/18665138/12245502\nexport const html_image_regex = /<img([^>]+?)\\/?>/g;\n// Used for clicking on images in editor, note: 2 extra capture groups to align with link regex\nexport const html_full_image_regex = /<img\\s([^>]*?src\\s*=\\s*['\"]([^'\"]*)['\"][^>]*?()())\\/?>/g;\nexport const highlight_regex = /(?<!\\\\)==(?=[^\\s])(?:[^=]=?)*[^=\\s\\\\]==/g;\nexport const insert_regex = /(?<!\\\\)\\+\\+(?=[^\\s])[^\\+]*[^\\+\\s\\\\]\\+\\+/g;\nexport const sub_regex = /(?<![\\\\~])~(?=[^\\s])[^~]*[^~\\s\\\\]~/g;\nexport const sup_regex = /(?<![\\\\[])\\^(?=[^\\s])[^\\^]*[^\\^\\s\\\\[]\\^/g;\nexport const emph_star_regex = /(?<![\\\\\\*])\\*(?!\\*)/g;\nexport const emph_underline_regex = /(?<![\\\\\\_])\\_(?!\\_)/g;\nexport const strong_star_regex = /(?<![\\\\\\*])\\*\\*(?!\\*)/g;\nexport const strong_underline_regex = /(?<![\\\\\\_])\\_\\_(?!\\_)/g;\nexport const highlight_token_regex = /(?<![\\\\=])==(?!=)/g;\nexport const insert_token_regex = /(?<![\\\\\\+])\\+\\+(?!\\+)/g;\nexport const sub_token_regex = /(?<![\\\\~])~(?!~)/g;\nexport const sup_token_regex = /(?<![\\\\\\^])\\^(?!\\^)/g;\nexport const strike_token_regex = /(?<![\\\\~])~~(?!~~)/g;\nexport const backtick_token_regex = /(?<![\\\\`])`(?!`)/g;\nexport const backtick_block_token_regex = /^```\\S*$/g;\nexport const admonition_token_regex = /^!!!/g;\nexport const admonition_line_regex = /^!!!.*$/g;\nexport const header_regex = /^\\s*#+\\s/g;\n// Taken from codemirror/addon/edit/continuelist.js\nexport const list_token_regex = /^(\\s*)([*+-] \\[[Xx ]\\]\\s|[*+->]\\s|(\\d+)([.)]\\s))(\\s*)/g;\n// Taken from codemirror/mode/markdown/markdown.js\nexport const hr_regex = /^([*\\-_])(?:\\s*\\1){2,}\\s*$/;\nexport const blockquote_regex = /^\\s*\\>+\\s/g;\nexport const nbsp_regex = /&nbsp;/g;\n\n// TODO: Extend this to get better table highlighting\nexport const table_regex = /^\\|[^\\n]+\\|/g;\n\nfunction exec(query: RegExp, stream: any) {\n\tquery.lastIndex = stream.pos;\n\treturn query.exec(stream.string);\n}\n\nexport function regexOverlay(name: string, regex: RegExp, requiredSettings: string[]) {\n\t// Hack to allow the backtick regexes in code blocks\n\tconst allowedInCodeblock = name.indexOf(\"tick\") > 0;\n\treturn {\n\t\tname: \"RichMarkdownOverlay-\" + name,\n\t\trequiredSettings: requiredSettings,\n\t\ttoken: function(stream: any) {\n\t\t\tconst match = exec(regex, stream);\n\n\t\t\tconst baseToken = stream.baseToken();\n\t\t\tif (baseToken?.type && (\n\t\t\t\t// This baseToken stuff doesn't actually work in Joplin code blocks, but it does\n\t\t\t\t// work to prevent highlighting of the backtick tokens, so I need to add a special\n\t\t\t\t// case to allow them. It also works inside comments which I think is handy\n\t\t\t\t!allowedInCodeblock && (\n\t\t\t\t\tbaseToken.type.includes(\"jn-inline-code\") ||\n\t\t\t\t\tbaseToken.type.includes(\"comment\")\n\t\t\t\t) ||\n\t\t\t\tbaseToken.type.includes(\"katex\"))) {\n\t\t\t\tstream.pos += baseToken.size;\n\t\t\t}\n\t\t\telse if (match && match.index === stream.pos) {\n\t\t\t\t// advance\n\t\t\t\tstream.pos += match[0].length || 1;\n\t\t\t\treturn name;\n\t\t\t}\n\t\t\telse if (match) {\n\t\t\t\t// jump to the next match\n\t\t\t\tstream.pos = match.index;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstream.skipToEnd();\n\t\t\t}\n\t\n\t\t\treturn null;\n\t\t},\n\t};\n}\n\nexport const overlays = [\n\tregexOverlay('rm-checkbox', checkbox_regex, []),\n\tregexOverlay('rm-checkboxed', checkboxed_regex, ['extraCSS']),\n\tregexOverlay('rm-checkbox-check', checkbox_inner_regex, ['extraCSS']),\n\tregexOverlay('rm-checkbox-checked', checkbox_inner_checked_regex, ['extraCSS']),\n\tregexOverlay('rm-link', link_regex, []),\n\tregexOverlay('rm-link-label', link_label_regex, []),\n\tregexOverlay('rm-image', image_regex, []),\n\tregexOverlay('rm-image', html_image_regex, []),\n\tregexOverlay('rm-list-token', list_token_regex, []),\n\tregexOverlay('rm-ins', insert_regex, ['insertHighlight']),\n\tregexOverlay('rm-sub', sub_regex, ['subHighlight']),\n\tregexOverlay('rm-sup', sup_regex, ['supHighlight']),\n\tregexOverlay('rm-header-token', header_regex, ['extraCSS']),\n\tregexOverlay('line-cm-rm-blockquote', blockquote_regex, ['extraCSS']),\n\tregexOverlay('rm-em-token', emph_star_regex, ['extraCSS']),\n\tregexOverlay('rm-em-token', emph_underline_regex, ['extraCSS']),\n\tregexOverlay('rm-strong-token', strong_star_regex, ['extraCSS']),\n\tregexOverlay('rm-strong-token', strong_underline_regex, ['extraCSS']),\n\tregexOverlay('rm-highlight', highlight_regex, ['markHighlight']),\n\tregexOverlay('rm-highlight-token', highlight_token_regex, ['extraCSS', 'markHighlight']),\n\tregexOverlay('rm-ins-token', insert_token_regex, ['extraCSS', 'insertHighlight']),\n\tregexOverlay('rm-sub-token', sub_token_regex, ['extraCSS', 'subHighlight']),\n\tregexOverlay('rm-sup-token', sup_token_regex, ['extraCSS', 'supHighlight']),\n\tregexOverlay('rm-strike-token', strike_token_regex, ['extraCSS']),\n\tregexOverlay('rm-backtick-token', backtick_token_regex, ['extraCSS']),\n\tregexOverlay('rm-triptick-token', backtick_block_token_regex, ['extraCSS']),\n\tregexOverlay('rm-hr line-cm-rm-hr', hr_regex, ['extraCSS']),\n\tregexOverlay('rm-admonition-token line-cm-rm-admonition line-background-cm-rm-admonition', admonition_token_regex, ['extraCSS']),\n\tregexOverlay('rm-admonition', admonition_line_regex, ['extraCSS']),\n\tregexOverlay('rm-nbsp', nbsp_regex, ['extraCSS']),\n];\n\nfunction validate(settings: any, values: string[]): boolean {\n\tfor (let value of values) {\n\t\tif (!settings[value] && !(value === \"extraCSS\" && (settings.theme !== \"none\" || settings.extraFancy)))\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport function add(cm: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\tfor (let overlay of overlays)\n\t\tif (validate(cm.state.richMarkdown.settings, overlay.requiredSettings))\n\t\t\tcm.addOverlay(overlay);\n}\n\nexport function remove(cm: any) {\n\tfor (let overlay of overlays)\n\t\tcm.removeOverlay(overlay);\n}\n\n"
  },
  {
    "path": "src/richMarkdown.ts",
    "content": "import * as ImageHandlers from './images';\nimport * as ClickHandlers from './clickHandlers';\nimport * as Overlay from './overlay';\nimport * as IndentHandlers from './indent';\nimport * as Stylesheets from './stylesheets';\nimport { RichMarkdownSettings } from './settings';\nimport { listIndent } from './cm6ListIndent';\n\nmodule.exports = {\n\tdefault: function(context) { \n\t\treturn {\n\t\t\tplugin: function(CodeMirror) {\n\t\t\t\tasync function path_from_id(id: string) {\n\t\t\t\t\treturn await context.postMessage({name:'getResourcePath', id: id});\n\t\t\t\t}\n\t\t\t\tasync function get_settings() {\n\t\t\t\t\treturn await context.postMessage({name: 'getSettings'});\n\t\t\t\t}\n\n\t\t\t\tfunction is_click_allowed(cm:any, event: MouseEvent) {\n\t\t\t\t\tconst settings = cm.state.richMarkdown.settings;\n\t\t\t\t\tif (!settings.clickCtrl) return true;\n\n\t\t\t\t\tlet allowed = false;\n\t\t\t\t\tif (settings.clickAlt) {\n\t\t\t\t\t\tallowed = allowed || event.altKey;\n\t\t\t\t\t}\n\t\t\t\t\tif (navigator.platform.toUpperCase().indexOf('MAC') >= 0) {\n\t\t\t\t\t\tallowed = allowed || event.metaKey;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tallowed = allowed || event.ctrlKey;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn allowed;\n\t\t\t\t}\n\n\t\t\t\tCodeMirror.defineExtension('initializeRichMarkdown', function(settings: RichMarkdownSettings) {\n\t\t\t\t\tthis.state.richMarkdown = {\n\t\t\t\t\t\tsettings,\n\t\t\t\t\t\tpath_from_id,\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.on('change', on_change);\n\t\t\t\t\tthis.on('update', on_update);\n\t\t\t\t\tthis.on('mousedown', on_mousedown);\n\t\t\t\t\tthis.on('renderLine', on_renderLine);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tJSON.parse(settings.regexOverlays).forEach((overlay: any) => {\n\t\t\t\t\t\t\tOverlay.overlays.push(\n\t\t\t\t\t\t\t\tOverlay.regexOverlay(overlay.name, new RegExp(overlay.regex, 'g'), ['extraCSS'])\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tconsole.error('Error parsing regexOverlays', e);\n\t\t\t\t\t}\n\n\t\t\t\t\tOverlay.add(this);\n\t\t\t\t\tIndentHandlers.calculateSpaceWidth(this);\n\n\t\t\t\t\tthis.updateRichMarkdownSettings(settings);\n\n\t\t\t\t\tif (this.cm6 && this.state.richMarkdown.settings.alignIndent) {\n\t\t\t\t\t\tthis.addExtension(listIndent());\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tCodeMirror.defineExtension('updateRichMarkdownSettings', function(newSettings: RichMarkdownSettings) {\n\t\t\t\t\tif (!this.state.richMarkdown) return;\n\n\t\t\t\t\tthis.state.richMarkdown.settings = newSettings;\n\t\t\t\t\tImageHandlers.clearAllWidgets(this);\n\t\t\t\t\tImageHandlers.onSourceChanged(this, this.firstLine(), this.lastLine(), context);\n\t\t\t\t\tImageHandlers.afterSourceChanges(this);\n\t\t\t\t\tif (newSettings.activeLine) {\n\t\t\t\t\t\tthis.setOption('styleActiveLine', { nonEmpty: true });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.setOption('styleActiveLine', false);\n\t\t\t\t\t}\n\t\t\t\t\tthis.getWrapperElement().onmousemove = on_mousemove(this, newSettings);\n\t\t\t\t\tthis.getWrapperElement().onmouseup = on_mouseup(this, newSettings);\n\t\t\t\t\tStylesheets.refreshStylesheets(this);\n\t\t\t\t});\n\n\t\t\t\tCodeMirror.defineExtension('clickUnderCursor', function() {\n\t\t\t\t\tconst coord = this.getCursor('head');\n\t\t\t\t\tconst mes = ClickHandlers.clickAt(this, coord);\n\t\t\t\t\tif (mes)\n\t\t\t\t\t\tcontext.postMessage(mes);\n\t\t\t\t});\n\n\t\t\t\tCodeMirror.defineExtension('getItemsUnderCursor', function(responsePromiseId:string) {\n\t\t\t\t\tconst coord = this.getCursor('head');\n\t\t\t\t\treturn ClickHandlers.getItemsAt(this, coord);\n\t\t\t\t});\n\n\t\t\t\tCodeMirror.defineExtension('toggleCheckbox', function(coord:any) {\n\t\t\t\t\tClickHandlers.toggleCheckbox(this, coord, '');\n\t\t\t\t});\n\n\t\t\t\tCodeMirror.defineExtension('checkCheckbox', function(coord:any) {\n\t\t\t\t\tClickHandlers.toggleCheckbox(this, coord, '[x]');\n\t\t\t\t});\n\n\t\t\t\tCodeMirror.defineExtension('uncheckCheckbox', function(coord:any) {\n\t\t\t\t\tClickHandlers.toggleCheckbox(this, coord, '[ ]');\n\t\t\t\t});\n\n\t\t\t\tCodeMirror.defineExtension('refreshResource', function(resourceId:string) {\n\t\t\t\t\tImageHandlers.refreshResource(this, resourceId);\n\t\t\t\t});\n\n\t\t\t\tfunction on_renderLine(cm: any, line: any, element: HTMLElement) {\n\t\t\t\t\tIndentHandlers.onRenderLine(cm, line, element, CodeMirror);\n\t\t\t\t}\n\n\t\t\t\tfunction get_change_lines(change: any) {\n\t\t\t\t\t// change.from and change.to reflect the change location *before* the edit took place\n\t\t\t\t\t// when a larger scale edit happens they don't necessarily reflect the true scope of changes\n\t\t\t\t\tconst from = change.from.line;\n\t\t\t\t\tlet to = change.to.line;\n\t\t\t\t\tto -= change.removed.length;\n\t\t\t\t\tto += change.text.length;\n\n\t\t\t\t\treturn { from, to };\n\t\t\t\t}\n\n\t\t\t\tfunction on_change(cm: any, change: any) {\n\t\t\t\t\tconst { from, to } = get_change_lines(change);\n\t\t\t\t\t\n\t\t\t\t\tImageHandlers.onSourceChanged(cm, from, to, context);\n\t\t\t\t}\n\t\t\t\tfunction on_update(cm: any) {\n\t\t\t\t\tImageHandlers.afterSourceChanges(cm);\n\t\t\t\t}\n\n\t\t\t\tasync function on_mousedown(cm: any, event: MouseEvent) {\n\t\t\t\t\tif (!cm.state.richMarkdown) return;\n\n\t\t\t\t\tconst clickAllowed = is_click_allowed(cm, event);\n\n\t\t\t\t\tif (clickAllowed &&\n\t\t\t\t\t\t (ClickHandlers.isLink(event) ||\n\t\t\t\t\t\t\tClickHandlers.isCheckbox(event))) {\n\t\t\t\t\t\tconst coord = ClickHandlers.getClickCoord(cm, event);\n\t\t\t\t\t\tconst mes = ClickHandlers.clickAt(cm, coord);\n\t\t\t\t\t\tif (mes)\n\t\t\t\t\t\t\tcontext.postMessage(mes);\n\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfunction update_cursor(cm:any, settings: RichMarkdownSettings, event: MouseEvent) {\n\t\t\t\t\tif (!event.target) return;\n\n\t\t\t\t\tlet cursor = '';\n\n\t\t\t\t\tif ((settings.links && ClickHandlers.isLink(event)) ||\n\t\t\t\t\t\t  (settings.checkbox && ClickHandlers.isCheckbox(event))) {\n\t\t\t\t\t\tcursor = is_click_allowed(cm, event) ? 'pointer' : cursor;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst target = event.target as HTMLElement;\n\t\t\t\t\ttarget.style.cursor = cursor;\n\t\t\t\t}\n\n\t\t\t\tfunction on_mousemove(cm:any, settings: RichMarkdownSettings) {\n\t\t\t\t\treturn function(event: MouseEvent) {\n\t\t\t\t\t\tupdate_cursor(cm, settings, event);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfunction on_mouseup(cm:any, settings: RichMarkdownSettings) {\n\t\t\t\t\treturn function(event: MouseEvent) {\n\t\t\t\t\t\tupdate_cursor(cm, settings, event);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tCodeMirror.defineOption('enable-rich-mode', false, async function(cm, val, old) {\n\t\t\t\t\t// Cleanup\n\t\t\t\t\tif (old && old != CodeMirror.Init) {\n\t\t\t\t\t\tcm.off('change', on_change);\n\t\t\t\t\t\tcm.off('update', on_update);\n\t\t\t\t\t\tcm.off('mousedown', on_mousedown);\n\t\t\t\t\t\tcm.off('renderLine', on_renderLine);\n\n\t\t\t\t\t\tOverlay.remove(cm);\n\t\t\t\t\t\tcm.state.richMarkdown = null;\n\t\t\t\t\t\tImageHandlers.clearAllWidgets(cm);\n\t\t\t\t\t}\n\t\t\t\t\t// setup\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\t// There is a race condition in the Joplin initialization code\n\t\t\t\t\t\t// Sometimes the settings aren't ready yet and will return `undefined`\n\t\t\t\t\t\t// This code will perform an exponential backoff and poll settings\n\t\t\t\t\t\t// until something is returned\n\t\t\t\t\t\tasync function backoff(timeout: number) {\n\t\t\t\t\t\t\tconst settings = await get_settings();\n\n\t\t\t\t\t\t\tif (!settings) {\n\t\t\t\t\t\t\t\tsetTimeout(backoff, timeout * 2, timeout * 2);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tcm.initializeRichMarkdown(settings);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\t// Set the first timeout to 50 because settings are usually ready immediately\n\t\t\t\t\t\t// Set the first backoff to (100*2) to give a little extra time\n\t\t\t\t\t\tsetTimeout(backoff, 50, 100);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t\tcodeMirrorResources: ['addon/selection/active-line', 'addon/selection/mark-selection'],\n\t\t\tcodeMirrorOptions: { 'enable-rich-mode': true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t 'styleSelectedText': true },\n\t\t\tassets: function() {\n\t\t\t\treturn [\n\t\t\t\t\t{ mime: 'text/css',\n\t\t\t\t\t\tinline: true,\n\t\t\t\t\t\ttext: `.cm-rm-monospace {\n\t\t\t\t\t\t\t\t\t\t\tfont-family: monospace !important;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.cm-rm-ins {\n\t\t\t\t\t\t\t\t\t\t\ttext-decoration: underline;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.cm-jn-code-block .cm-rm-ins {\n\t\t\t\t\t\t\t\t\t\t\ttext-decoration: revert;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.cm-rm-sub {\n\t\t\t\t\t\t\t\t\t\t\tvertical-align: sub;\n\t\t\t\t\t\t\t\t\t\t\tfont-size: smaller;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.cm-rm-sup {\n\t\t\t\t\t\t\t\t\t\t\tvertical-align: super;\n\t\t\t\t\t\t\t\t\t\t\tfont-size: smaller;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.cm-jn-code-block .cm-rm-sub, .cm-jn-code-block .cm-rm-sup {\n\t\t\t\t\t\t\t\t\t\t\tvertical-align: revert;\n\t\t\t\t\t\t\t\t\t\t\tfont-size: revert;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tdiv.CodeMirror span.cm-overlay.cm-rm-highlight {\n\t\t\t\t\t\t\t\t\t\t\tbackground-color: var(--joplin-search-marker-background-color);\n\t\t\t\t\t\t\t\t\t\t\tcolor: var(--joplin-search-marker-color);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tdiv.CodeMirror span.cm-rm-highlight {\n\t\t\t\t\t\t\t\t\t\t\tbackground-color: var(--joplin-search-marker-background-color);\n\t\t\t\t\t\t\t\t\t\t\tcolor: var(--joplin-search-marker-color);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.cm-jn-code-block .cm-rm-highlight, .cm-jn-code-block .cm-rm-highlight-token {\n\t\t\t\t\t\t\t\t\t\t    color: revert;\n\t\t\t\t\t\t\t\t\t\t    background-color: revert;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tdiv.CodeMirror span.cm-comment.cm-jn-inline-code.cm-overlay.cm-rm-backtick-token:not(.cm-search-marker):not(.cm-fat-cursor-mark):not(.cm-search-marker-selected):not(.CodeMirror-selectedtext) {\n\t\t\t\t\t\t\t\t\t\t\tbackground-color: transparent;\n\t\t\t\t\t\t\t\t\t\t\tborder: none;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tdiv.CodeMirror span.cm-comment.cm-jn-inline-code.cm-rm-backtick-token:not(.cm-search-marker):not(.cm-fat-cursor-mark):not(.cm-search-marker-selected):not(.CodeMirror-selectedtext) {\n\t\t\t\t\t\t\t\t\t\t\tbackground-color: transparent;\n\t\t\t\t\t\t\t\t\t\t\tborder: none;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.CodeMirror-selectedtext.cm-rm-highlight {\n\t\t\t\t\t\t\t\t\t\t\tbackground-color: #e5d3ce;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t/* Needed for the renderLine indent hack to work */\n\t\t\t\t\t\t\t\t\t\t.CodeMirror pre > * { text-indent: 0px; }\n\t\t\t\t\t\t\t\t\t\t.cm-rm-list-token .cm-ext-checkbox-toggle .content { text-indent: 0px; }\n\t\t\t\t\t\t\t`\n\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t},\n\t\t}\n\t},\n}\n"
  },
  {
    "path": "src/settings.ts",
    "content": "import joplin from 'api';\nimport { MenuItemLocation, SettingItemType } from 'api/types';\n\nexport interface RichMarkdownSettings {\n\tinlineImages: boolean;\n\timageHover: boolean;\n\timageHoverCtrl: boolean;\n\tmarkHighlight: boolean;\n\tinsertHighlight: boolean;\n\tsubHighlight: boolean;\n\tsupHighlight: boolean;\n\textraCSS: boolean;\n\tactiveLine: boolean;\n\talignIndent: boolean;\n\tcheckbox: boolean;\n\tlinks: boolean;\n\tclickCtrl: boolean;\n\tclickAlt: boolean;\n\tfocusMode: boolean;\n\ttheme: string;\n\textraFancy: string;\n\tcssPath: string;\n\tregexOverlays: string;\n}\n\nexport async function getAllSettings(): Promise<RichMarkdownSettings> {\n\treturn {\n\t\tinlineImages: await joplin.settings.value('inlineImages'),\n\t\timageHover: await joplin.settings.value('imageHover'),\n\t\timageHoverCtrl: await joplin.settings.value('imageHoverCtrl'),\n\t\tmarkHighlight: await joplin.settings.globalValue('markdown.plugin.mark'),\n\t\tinsertHighlight: await joplin.settings.globalValue('markdown.plugin.insert'),\n\t\tsubHighlight: await joplin.settings.globalValue('markdown.plugin.sub'),\n\t\tsupHighlight: await joplin.settings.globalValue('markdown.plugin.sup'),\n\t\textraCSS: await joplin.settings.value('extraCSS'),\n\t\tactiveLine: await joplin.settings.value('activeLine'),\n\t\talignIndent: await joplin.settings.value('alignIndent'),\n\t\tcheckbox: await joplin.settings.value('checkbox'),\n\t\tlinks: await joplin.settings.value('links'),\n\t\tclickCtrl: await joplin.settings.value('clickCtrl'),\n\t\tclickAlt: await joplin.settings.value('clickAlt'),\n\t\tfocusMode: await joplin.settings.value('focusMode'),\n\t\ttheme: await joplin.settings.value('theme'),\n\t\textraFancy: await joplin.settings.value('extraFancy'),\n\t\tcssPath: await joplin.plugins.installationDir(),\n\t\tregexOverlays: await joplin.settings.value('regexOverlays'),\n\t}\n}\n\nexport async function registerAllSettings() {\n\tawait joplin.settings.registerSection('settings.calebjohn.richmarkdown', {\n\t\tlabel: 'Rich Markdown',\n\t\ticonName: 'fas fa-rocket'\n\t});\n\n\tawait joplin.settings.registerSettings({\n\t\t'inlineImages': {\n\t\t\tvalue: false,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Render images below their markdown source (only for images on their own line)'\n\t\t},\n\n\t\t'imageHover': {\n\t\t\tvalue: true,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Show an image popup when hovering over the image source with Ctrl (or Opt) pressed'\n\t\t},\n\t\t'imageHoverCtrl': {\n\t\t\tvalue: false,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Enable image popup even when Ctrl (or Opt) is not pressed'\n\t\t},\n\n\t\t'alignIndent': {\n\t\t\tvalue: true,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Align wrapped list items to the indent level',\n\t\t},\n\n\t\t'extraCSS': {\n\t\t\tvalue: false,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Add additional CSS classes for enhanced customization',\n\t\t\tdescription: 'See https://github.com/CalebJohn/joplin-rich-markdown#extra-css for options',\n\t\t},\n\n\t\t'activeLine': {\n\t\t\tvalue: false,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Highlight the background of the current line',\n\t\t},\n\n\t\t'checkbox': {\n\t\t\tvalue: true,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Toggle checkboxes with Ctrl (or Cmd)+Click'\n\t\t},\n\n\t\t'links': {\n\t\t\tvalue: true,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Follow note links with Ctrl (or Cmd)+Click'\n\t\t},\n\n\t\t'clickCtrl': {\n\t\t\tvalue: true,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tadvanced: true,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Require Ctrl (or Cmd) when clicking on elements (links and checkboxes)',\n\t\t\tdescription: 'It\\'s recommended not to change this',\n\t\t},\n\n\t\t'clickAlt': {\n\t\t\tvalue: false,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tadvanced: true,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Allow Alt (or Opt) in addition to Ctrl/Cmd  when clicking on elements (links and checkboxes)',\n\t\t\tdescription: 'It\\'s recommended not to change this',\n\t\t},\n\n\t\t'focusMode': {\n\t\t\tvalue: false,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Focus Mode',\n\t\t\tdescription: 'Fade everything that isn\\'t the current paragraph.',\n\t\t},\n\t\t'theme': {\n\t\t\tlabel: 'Theme',\n\t\t\tvalue: 'none',\n\t\t\ttype: SettingItemType.String,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tisEnum: true,\n\t\t\tpublic: true,\n\t\t\toptions: {\n\t\t\t\t'none': 'none',\n\t\t\t\t'stylish': 'Stylish',\n\t\t\t},\n\t\t\tdescription: 'Warning: Changing theme can change the settings above.',\n\t\t},\n\t\t'extraFancy': {\n\t\t\tvalue: false,\n\t\t\ttype: SettingItemType.Bool,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tlabel: 'Hide Markdown Elements',\n\t\t\tdescription: 'Fades the markdown characters on other lines',\n\t\t},\n\t\t'regexOverlays': {\n\t\t\tvalue: '[]',\n\t\t\ttype: SettingItemType.String,\n\t\t\tsection: 'settings.calebjohn.richmarkdown',\n\t\t\tpublic: true,\n\t\t\tadvanced: true,\n\t\t\tlabel: 'Custom classes JSON',\n\t\t\tdescription: 'Add custom classes in the format of [{\"name\": \"className\", \"regex\": string}] to create custom overlays referenced as \"cm-className\".',\n\t\t},\n\t});\n\tregisterToggle('inlineImages',\n\t\t'Toggle images in the markdown editor',\n\t\t'fas fa-image');\n\tregisterToggle('focusMode',\n\t\t'Toggle focus mode in the markdown editor',\n\t\t'fas fa-eye');\n}\n\nasync function registerToggle(name: string, label: string, icon: string) {\n\tawait joplin.commands.register({\n\t\tname: `richMarkdown.${name}`,\n\t\tlabel: label,\n\t\ticonName: icon,\n\t\texecute: async () => {\n\t\t\tconst enabled = await joplin.settings.value(name);\n\t\t\tjoplin.settings.setValue(name, !enabled);\n\t\t\tconst settings = await getAllSettings();\n\n\t\t\tawait joplin.commands.execute('editor.execCommand', {\n\t\t\t\tname: 'updateRichMarkdownSettings',\n\t\t\t\targs: [settings]\n\t\t\t});\n\t\t},\n\t});\n\tawait joplin.views.menuItems.create(`richMarkdown${name}`, `richMarkdown.${name}`, MenuItemLocation.View);\n}\n"
  },
  {
    "path": "src/style/extra_css_fixes.css",
    "content": "div.cm-editor .cm-inlineCode {\n  background-color: var(--joplin-code-background-color);\n  border-style: none;\n}\n"
  },
  {
    "path": "src/style/extra_fancy.css",
    "content": "/* Hide everything */\n.cm-header.cm-rm-header-token, .cm-em.cm-rm-em-token, .cm-strong.cm-rm-strong-token, div.CodeMirror span.cm-overlay.cm-rm-highlight.cm-rm-highlight-token, div.CodeMirror span.cm-rm-highlight .cm-rm-highlight-token, .cm-strikethrough.cm-rm-strike-token, .cm-rm-ins.cm-rm-ins-token, .cm-rm-sub.cm-rm-sub-token, .cm-rm-sup.cm-rm-sup-token, .cm-quote.cm-rm-list-token, .cm-katex-marker-open, .cm-katex-marker-close, .cm-rm-backtick-token, .cm-rm-triptick-token, .cm-rm-nbsp,\ndiv.cm-editor .tok-meta {\n\topacity: 0.2 !important;\n\tfont-size: 0.9em;\n\tbackground: transparent;\n\tcolor: inherit;\n\ttransition: opacity 0.3s;\n}\n\n/* Restore font-size inside of markdown tables because it messes up alignment */\ndiv.cm-editor .cm-tableDelimiter .tok-meta {\n\tfont-size: 1em;\n}\n\n/* The url string looks not great when it's faded all the way to 0.2*/\ndiv.CodeMirror .cm-string.cm-url,\ndiv.cm-editor .cm-url {\n\topacity: 0.5 !important;\n\tfont-size: 0.9em;\n\ttransition: opacity 0.3s;\n}\n\ndiv.CodeMirror .CodeMirror-activeline .cm-string.cm-url, .CodeMirror-activeline .cm-header.cm-rm-header-token, .CodeMirror-activeline .cm-em.cm-rm-em-token, .CodeMirror-activeline .cm-strong.cm-rm-strong-token, div.CodeMirror .CodeMirror-activeline span.cm-overlay.cm-rm-highlight.cm-rm-highlight-token, div.CodeMirror .CodeMirror-activeline span.cm-rm-highlight .cm-rm-highlight-token, .CodeMirror-activeline .cm-strikethrough.cm-rm-strike-token, .CodeMirror-activeline .cm-rm-ins.cm-rm-ins-token, .CodeMirror-activeline .cm-rm-sub.cm-rm-sub-token, .CodeMirror-activeline .cm-rm-sup.cm-rm-sup-token, .CodeMirror-activeline .cm-quote.cm-rm-list-token, .CodeMirror-activeline .cm-katex-marker-open, .CodeMirror-activeline .cm-katex-marker-close, .CodeMirror-activeline .cm-rm-backtick-token, .CodeMirror-activeline .cm-rm-triptick-token, .CodeMirror-activeline .cm-rm-nbsp,\ndiv.cm-editor .CodeMirror-activeline .tok-meta, div.cm-editor .cm-url {\n\topacity: 0.7 !important;\n\tbackground: transparent;\n\tcolor: inherit;\n\ttransition: opacity 0.5s;\n}\n\n/* Don't apply the fading in code blocks */\ndiv.CodeMirror .cm-jn-code-block .cm-string.cm-url,\n.cm-jn-code-block .cm-header.cm-rm-header-token,\n.cm-jn-code-block .cm-em.cm-rm-em-token,\n.cm-jn-code-block .cm-strong.cm-rm-strong-token,\n.cm-jn-code-block .cm-rm-highlight.cm-rm-highlight-token,\n.cm-jn-code-block .cm-strikethrough.cm-rm-strike-token,\n.cm-jn-code-block .cm-rm-ins.cm-rm-ins-token,\n.cm-jn-code-block .cm-rm-sub.cm-rm-sub-token,\n.cm-jn-code-block .cm-rm-sup.cm-rm-sup-token,\n.cm-jn-code-block .cm-quote.cm-rm-list-token,\n.cm-jn-code-block .cm-katex-marker-open,\n.cm-jn-code-block .cm-katex-marker-close,\n.cm-jn-code-block .cm-rm-nbsp {\n\topacity: revert !important;\n\tfont-size: revert;\n}\n\n/* Revert background colour change on the activeline */\n.CodeMirror .CodeMirror-activeline\n.CodeMirror-activeline-background:not(.cm-jn-code-block):not(.cm-jn-code-block-background),\n.cm-editor .CodeMirror-activeline-background,\n.cm-s-solarized.cm-s-light div.CodeMirror-activeline-background,\n.cm-s-solarized.cm-s-dark div.CodeMirror-activeline-background {\n  background: inherit;\n}\n/* END Hide everything */\n\n"
  },
  {
    "path": "src/style/focus.css",
    "content": "div.CodeMirror .CodeMirror-line,\ndiv.cm-editor .cm-line,\ndiv.CodeMirror img {\n  opacity: 0.4;\n  color: var(--joplin-color) !important;\n\ttransition: opacity 0.2s;\n}\n\ndiv.CodeMirror .CodeMirror-activeline .CodeMirror-line,\ndiv.CodeMirror .CodeMirror-activeline img,\ndiv.CodeMirror .CodeMirror-activeline.cm-line,\ndiv.cm-editor .CodeMirror-activeline + .rich-markdown-resource.cm-line > img,\ndiv.cm-editor .CodeMirror-activeline + .rich-markdown-resource.cm-line {\n  opacity: 1.0;\n\ttransition: opacity 0.4s;\n}\n\n/* Revert background colour change on the activeline */\n.CodeMirror .CodeMirror-activeline\n.CodeMirror-activeline-background:not(.cm-jn-code-block):not(.cm-jn-code-block-background),\n.cm-editor .CodeMirror-activeline-background:not(.cm-jn-code-block):not(.cm-jn-code-block-background),\n.cm-s-solarized.cm-s-light div.CodeMirror-activeline-background,\n.cm-s-solarized.cm-s-dark div.CodeMirror-activeline-background {\n  background: inherit;\n}\n\n"
  },
  {
    "path": "src/style/stylish.css",
    "content": "/*\n * Adapted from the theme that uxamanda shared on the Joplin forum\n * https://discourse.joplinapp.org/t/plugin-rich-markdown/15053/108\n * */ \n\n/* Horizontal Rule*/\ndiv.CodeMirror span.cm-hr {\n\tborder-top: 2px solid var(--joplin-divider-color);\n\tdisplay: block;\n\tline-height: 1px;\n\twidth: 100%;\n}\n/* END Horizontal Rule*/\n\n/* Blockquotes */\npre.cm-rm-blockquote.CodeMirror-line,\npre.cm-rm-blockquote.cm-line {\n\tborder-left: 4px solid var(--joplin-code-border-color);\n\topacity: 0.7;\n}\n\npre.cm-rm-blockquote span.cm-quote + span.cm-quote {\n\topacity: 1;\n}\n/* END Blockquotes */\n\n/* Fade tokens*/\ndiv.CodeMirror .cm-string.cm-url,\ndiv.CodeMirror .cm-header.cm-rm-header-token,\ndiv.CodeMirror .cm-overlay.cm-rm-highlight.cm-rm-highlight-token,\ndiv.CodeMirror .cm-rm-highlight .cm-rm-highlight-token,\ndiv.CodeMirror .cm-strikethrough.cm-rm-strike-token,\ndiv.CodeMirror .cm-rm-ins.cm-rm-ins-token,\ndiv.CodeMirror .cm-rm-sub.cm-rm-sub-token,\ndiv.CodeMirror .cm-rm-sup.cm-rm-sup-token,\ndiv.CodeMirror .cm-quote.cm-rm-list-token,\ndiv.CodeMirror .cm-rm-backtick-token,\ndiv.CodeMirror .cm-rm-triptick-token,\ndiv.CodeMirror .cm-em.cm-rm-em-token,\ndiv.CodeMirror .cm-katex-marker-open,\ndiv.CodeMirror .cm-katex-marker-close,\ndiv.CodeMirror .cm-strong.cm-rm-strong-token,\ndiv.CodeMirror .cm-rm-nbsp,\ndiv.cm-editor .tok-meta {\n\topacity: 0.6;\n\tbackground: transparent;\n\tcolor: inherit;\n}\n/* Don't apply the fading in code blocks */\ndiv.CodeMirror .cm-jn-code-block .cm-string.cm-url,\ndiv.CodeMirror .cm-jn-code-block .cm-header.cm-rm-header-token,\ndiv.CodeMirror .cm-jn-code-block .cm-em.cm-rm-em-token,\ndiv.CodeMirror .cm-jn-code-block .cm-strong.cm-rm-strong-token,\ndiv.CodeMirror .cm-jn-code-block .cm-rm-highlight.cm-rm-highlight-token,\ndiv.CodeMirror .cm-jn-code-block .cm-rm-highlight .cm-rm-highlight-token,\ndiv.CodeMirror .cm-jn-code-block .cm-strikethrough.cm-rm-strike-token,\ndiv.CodeMirror .cm-jn-code-block .cm-rm-ins.cm-rm-ins-token,\ndiv.CodeMirror .cm-jn-code-block .cm-rm-sub.cm-rm-sub-token,\ndiv.CodeMirror .cm-jn-code-block .cm-rm-sup.cm-rm-sup-token,\ndiv.CodeMirror .cm-jn-code-block .cm-quote.cm-rm-list-token,\ndiv.CodeMirror .cm-jn-code-block .cm-katex-marker-open,\ndiv.CodeMirror .cm-jn-code-block .cm-katex-marker-close,\ndiv.CodeMirror .cm-rm-nbsp,\ndiv.cm-editor .cm-jn-code-block .tok-meta {\n\topacity: revert;\n\tcolor: inherit;\n}\n/* END Fade tokens*/\n\n/* Headers */\ndiv.CodeMirror .cm-header.cm-rm-header-token:not(.cm-whitespace-a, .cm-whitespace-b),\ndiv.cm-editor .cm-header > .cm-rm-header-token {\n\tfont-family: monospace;\n\tfont-size: 0.9em;\n\t/* Add some spacing for breathing room */\n\tmargin-right: 0.3ex;\n}\ndiv.CodeMirror .cm-rm-header-token.cm-header-1:not(.cm-whitespace-a, .cm-whitespace-b),\ndiv.cm-editor .cm-header.cm-h1 > .cm-rm-header-token {\n\t/* 1ex is the height of a lowercase x, it's an approximation for width */\n\t/* We need 2ex to account for the space character */\n\tmargin-left: -2ex;\n}\n\ndiv.CodeMirror .cm-rm-header-token.cm-header-2:not(.cm-whitespace-a, .cm-whitespace-b),\ndiv.cm-editor .cm-header.cm-h2 > .cm-rm-header-token {\n\tmargin-left: -3ex;\n}\n\ndiv.CodeMirror .cm-rm-header-token.cm-header-3:not(.cm-whitespace-a, .cm-whitespace-b),\ndiv.cm-editor .cm-header.cm-h3 > .cm-rm-header-token {\n\tmargin-left: -4ex;\n}\n\ndiv.CodeMirror .cm-rm-header-token.cm-header-4:not(.cm-whitespace-a, .cm-whitespace-b),\ndiv.cm-editor .cm-header.cm-h4 > .cm-rm-header-token {\n\tmargin-left: -5ex;\n}\n\ndiv.CodeMirror .cm-rm-header-token.cm-header-5:not(.cm-whitespace-a, .cm-whitespace-b),\ndiv.cm-editor .cm-header.cm-h5 > .cm-rm-header-token {\n\tmargin-left: -6ex;\n}\n\ndiv.CodeMirror .cm-rm-header-token.cm-header-6:not(.cm-whitespace-a, .cm-whitespace-b),\ndiv.cm-editor .cm-header.cm-h6 > .cm-rm-header-token {\n\tmargin-left: -7ex;\n}\n/* END Headers */\n\n/* Checkbox */\n/* strikeout and dim the text of a checked checkbox */\n.cm-property + span.cm-rm-checkbox + span.cm-rm-checkbox ~ span.cm-rm-checkbox {\n\ttext-decoration: line-through;\n\topacity: 0.7;\n}\n/* Remove the strikethrough from the right edge of the checkbox */\ndiv.CodeMirror span.cm-property.cm-rm-checkbox {\n\ttext-decoration: none;\n\topacity: 1;\n}\n/* END Checkbox */\n\n"
  },
  {
    "path": "src/stylesheets.ts",
    "content": "\n// Hack to store created elements so that the can easily be removed (and re-added)\nconst elements = [\n\t'focus',\n\t'stylish',\n\t'extra_fancy',\n\t'extra_css_fixes',\n];\nconst idPrefix = \"content-script-richMarkdown-link-\";\n\nfunction createElement(name: string, path: string) {\n\tconst element = document.createElement('link');\n\telement.setAttribute('id', idPrefix + name);\n\telement.setAttribute('rel', 'stylesheet');\n\telement.setAttribute('href', path + \"/style/\" + name + \".css\");\n\n\treturn element;\n}\n\nexport function refreshStylesheets(cm: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\tcleanup(cm);\n\tconst settings = cm.state.richMarkdown.settings;\n\n\tif (settings.focusMode) {\n\t\tcm.setOption('styleActiveLine', { nonEmpty: true });\n\t\tconst element = createElement('focus', settings.cssPath);\n\t\tdocument.head.appendChild(element);\n\t}\n\tif (settings.theme == \"stylish\") {\n\t\tconst element = createElement('stylish', settings.cssPath);\n\t\tdocument.head.appendChild(element);\n\t}\n\tif (settings.extraFancy) {\n\t\tcm.setOption('styleActiveLine', { nonEmpty: true });\n\t\tconst element = createElement('extra_fancy', settings.cssPath);\n\t\tdocument.head.appendChild(element);\n\t}\n\tif (settings.extraCSS || settings.extraFancy || settings.theme == \"stylish\") {\n\t\tconst element = createElement('extra_css_fixes', settings.cssPath);\n\t\tdocument.head.appendChild(element);\n\t}\n\tcm.refresh();\n}\n\nfunction cleanup(cm: any) {\n\tif (!cm.state.richMarkdown) return;\n\n\tfor (let element of elements) {\n\t\tconst el = document.getElementById(idPrefix + element);\n\t\tif (el) {\n\t\t\tel.remove();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"./dist/\",\n\t\t\"module\": \"commonjs\",\n\t\t\"target\": \"es2015\",\n\t\t\"jsx\": \"react\",\n\t\t\"allowJs\": true,\n\t\t\"baseUrl\": \".\"\n\t}\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "// -----------------------------------------------------------------------------\n// This file is used to build the plugin file (.jpl) and plugin info (.json). It\n// is recommended not to edit this file as it would be overwritten when updating\n// the plugin framework. If you do make some changes, consider using an external\n// JS file and requiring it here to minimize the changes. That way when you\n// update, you can easily restore the functionality you've added.\n// -----------------------------------------------------------------------------\n\n/* eslint-disable no-console */\n\nconst path = require('path');\nconst crypto = require('crypto');\nconst fs = require('fs-extra');\nconst chalk = require('chalk');\nconst CopyPlugin = require('copy-webpack-plugin');\nconst tar = require('tar');\nconst glob = require('glob');\nconst execSync = require('child_process').execSync;\n\n// AUTO-GENERATED by updateCategories\nconst allPossibleCategories = [{ 'name': 'appearance' }, { 'name': 'developer tools' }, { 'name': 'productivity' }, { 'name': 'themes' }, { 'name': 'integrations' }, { 'name': 'viewer' }, { 'name': 'search' }, { 'name': 'tags' }, { 'name': 'editor' }, { 'name': 'files' }, { 'name': 'personal knowledge management' }];\n// AUTO-GENERATED by updateCategories\n\nconst rootDir = path.resolve(__dirname);\nconst userConfigFilename = './plugin.config.json';\nconst userConfigPath = path.resolve(rootDir, userConfigFilename);\nconst distDir = path.resolve(rootDir, 'dist');\nconst srcDir = path.resolve(rootDir, 'src');\nconst publishDir = path.resolve(rootDir, 'publish');\n\nconst userConfig = {\n\textraScripts: [],\n\t...(fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {}),\n};\n\nconst manifestPath = `${srcDir}/manifest.json`;\nconst packageJsonPath = `${rootDir}/package.json`;\nconst allPossibleScreenshotsType = ['jpg', 'jpeg', 'png', 'gif', 'webp'];\nconst manifest = readManifest(manifestPath);\nconst pluginArchiveFilePath = path.resolve(publishDir, `${manifest.id}.jpl`);\nconst pluginInfoFilePath = path.resolve(publishDir, `${manifest.id}.json`);\n\nconst { builtinModules } = require('node:module');\n\n// Webpack5 doesn't polyfill by default and displays a warning when attempting to require() built-in\n// node modules. Set these to false to prevent Webpack from warning about not polyfilling these modules.\n// We don't need to polyfill because the plugins run in Electron's Node environment.\nconst moduleFallback = {};\nfor (const moduleName of builtinModules) {\n\tmoduleFallback[moduleName] = false;\n}\n\nconst getPackageJson = () => {\n\treturn JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));\n};\n\nfunction validatePackageJson() {\n\tconst content = getPackageJson();\n\tif (!content.name || content.name.indexOf('joplin-plugin-') !== 0) {\n\t\tconsole.warn(chalk.yellow(`WARNING: To publish the plugin, the package name should start with \"joplin-plugin-\" (found \"${content.name}\") in ${packageJsonPath}`));\n\t}\n\n\tif (!content.keywords || content.keywords.indexOf('joplin-plugin') < 0) {\n\t\tconsole.warn(chalk.yellow(`WARNING: To publish the plugin, the package keywords should include \"joplin-plugin\" (found \"${JSON.stringify(content.keywords)}\") in ${packageJsonPath}`));\n\t}\n\n\tif (content.scripts && content.scripts.postinstall) {\n\t\tconsole.warn(chalk.yellow(`WARNING: package.json contains a \"postinstall\" script. It is recommended to use a \"prepare\" script instead so that it is executed before publish. In ${packageJsonPath}`));\n\t}\n}\n\nfunction fileSha256(filePath) {\n\tconst content = fs.readFileSync(filePath);\n\treturn crypto.createHash('sha256').update(content).digest('hex');\n}\n\nfunction currentGitInfo() {\n\ttry {\n\t\tlet branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();\n\t\tconst commit = execSync('git rev-parse HEAD', { stdio: 'pipe' }).toString().trim();\n\t\tif (branch === 'HEAD') branch = 'master';\n\t\treturn `${branch}:${commit}`;\n\t} catch (error) {\n\t\tconst messages = error.message ? error.message.split('\\n') : [''];\n\t\tconsole.info(chalk.cyan('Could not get git commit (not a git repo?):', messages[0].trim()));\n\t\tconsole.info(chalk.cyan('Git information will not be stored in plugin info file'));\n\t\treturn '';\n\t}\n}\n\nfunction validateCategories(categories) {\n\tif (!categories) return null;\n\tif ((categories.length !== new Set(categories).size)) throw new Error('Repeated categories are not allowed');\n\t// eslint-disable-next-line github/array-foreach -- Old code before rule was applied\n\tcategories.forEach(category => {\n\t\tif (!allPossibleCategories.map(category => { return category.name; }).includes(category)) throw new Error(`${category} is not a valid category. Please make sure that the category name is lowercase. Valid categories are: \\n${allPossibleCategories.map(category => { return category.name; })}\\n`);\n\t});\n}\n\nfunction validateScreenshots(screenshots) {\n\tif (!screenshots) return null;\n\tfor (const screenshot of screenshots) {\n\t\tif (!screenshot.src) throw new Error('You must specify a src for each screenshot');\n\n\t\t// Avoid attempting to download and verify URL screenshots.\n\t\tif (screenshot.src.startsWith('https://') || screenshot.src.startsWith('http://')) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst screenshotType = screenshot.src.split('.').pop();\n\t\tif (!allPossibleScreenshotsType.includes(screenshotType)) throw new Error(`${screenshotType} is not a valid screenshot type. Valid types are: \\n${allPossibleScreenshotsType}\\n`);\n\n\t\tconst screenshotPath = path.resolve(rootDir, screenshot.src);\n\n\t\t// Max file size is 1MB\n\t\tconst fileMaxSize = 1024;\n\t\tconst fileSize = fs.statSync(screenshotPath).size / 1024;\n\t\tif (fileSize > fileMaxSize) throw new Error(`Max screenshot file size is ${fileMaxSize}KB. ${screenshotPath} is ${fileSize}KB`);\n\t}\n}\n\nfunction readManifest(manifestPath) {\n\tconst content = fs.readFileSync(manifestPath, 'utf8');\n\tconst output = JSON.parse(content);\n\tif (!output.id) throw new Error(`Manifest plugin ID is not set in ${manifestPath}`);\n\tvalidateCategories(output.categories);\n\tvalidateScreenshots(output.screenshots);\n\treturn output;\n}\n\nfunction createPluginArchive(sourceDir, destPath) {\n\tconst distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true, windowsPathsNoEscape: true })\n\t\t.map(f => f.substr(sourceDir.length + 1));\n\n\tif (!distFiles.length) throw new Error('Plugin archive was not created because the \"dist\" directory is empty');\n\tfs.removeSync(destPath);\n\n\ttar.create(\n\t\t{\n\t\t\tstrict: true,\n\t\t\tportable: true,\n\t\t\tfile: destPath,\n\t\t\tcwd: sourceDir,\n\t\t\tsync: true,\n\t\t},\n\t\tdistFiles,\n\t);\n\n\tconsole.info(chalk.cyan(`Plugin archive has been created in ${destPath}`));\n}\n\nconst writeManifest = (manifestPath, content) => {\n\tfs.writeFileSync(manifestPath, JSON.stringify(content, null, '\\t'), 'utf8');\n};\n\nfunction createPluginInfo(manifestPath, destPath, jplFilePath) {\n\tconst contentText = fs.readFileSync(manifestPath, 'utf8');\n\tconst content = JSON.parse(contentText);\n\tcontent._publish_hash = `sha256:${fileSha256(jplFilePath)}`;\n\tcontent._publish_commit = currentGitInfo();\n\twriteManifest(destPath, content);\n}\n\nfunction onBuildCompleted() {\n\ttry {\n\t\tfs.removeSync(path.resolve(publishDir, 'index.js'));\n\t\tcreatePluginArchive(distDir, pluginArchiveFilePath);\n\t\tcreatePluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);\n\t\tvalidatePackageJson();\n\t} catch (error) {\n\t\tconsole.error(chalk.red(error.message));\n\t}\n}\n\nconst baseConfig = {\n\tmode: 'production',\n\ttarget: 'node',\n\tstats: 'errors-only',\n\tmodule: {\n\t\trules: [\n\t\t\t{\n\t\t\t\ttest: /\\.tsx?$/,\n\t\t\t\tuse: 'ts-loader',\n\t\t\t\texclude: /node_modules/,\n\t\t\t},\n\t\t],\n\t},\n\t...userConfig.webpackOverrides,\n};\n\nconst pluginConfig = { ...baseConfig, entry: './src/index.ts',\n\tresolve: {\n\t\talias: {\n\t\t\tapi: path.resolve(__dirname, 'api'),\n\t\t},\n\t\tfallback: moduleFallback,\n\t\t// JSON files can also be required from scripts so we include this.\n\t\t// https://github.com/joplin/plugin-bibtex/pull/2\n\t\textensions: ['.js', '.tsx', '.ts', '.json'],\n\t},\n\toutput: {\n\t\tfilename: 'index.js',\n\t\tpath: distDir,\n\t},\n\tplugins: [\n\t\tnew CopyPlugin({\n\t\t\tpatterns: [\n\t\t\t\t{\n\t\t\t\t\tfrom: '**/*',\n\t\t\t\t\tcontext: path.resolve(__dirname, 'src'),\n\t\t\t\t\tto: path.resolve(__dirname, 'dist'),\n\t\t\t\t\tglobOptions: {\n\t\t\t\t\t\tignore: [\n\t\t\t\t\t\t\t// All TypeScript files are compiled to JS and\n\t\t\t\t\t\t\t// already copied into /dist so we don't copy them.\n\t\t\t\t\t\t\t'**/*.ts',\n\t\t\t\t\t\t\t'**/*.tsx',\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t] };\n\n\n// These libraries can be included with require(...) or\n// joplin.require(...) from content scripts.\nconst externalContentScriptLibraries = [\n\t'@codemirror/view',\n\t'@codemirror/state',\n\t'@codemirror/search',\n\t'@codemirror/language',\n\t'@codemirror/autocomplete',\n\t'@codemirror/commands',\n\t'@codemirror/highlight',\n\t'@codemirror/lint',\n\t'@codemirror/lang-html',\n\t'@codemirror/lang-markdown',\n\t'@codemirror/language-data',\n\t'@lezer/common',\n\t'@lezer/markdown',\n\t'@lezer/highlight',\n];\n\nconst extraScriptExternals = {};\nfor (const library of externalContentScriptLibraries) {\n\textraScriptExternals[library] = { commonjs: library };\n}\n\nconst extraScriptConfig = {\n\t...baseConfig,\n\tresolve: {\n\t\talias: {\n\t\t\tapi: path.resolve(__dirname, 'api'),\n\t\t},\n\t\tfallback: moduleFallback,\n\t\textensions: ['.js', '.tsx', '.ts', '.json'],\n\t},\n\n\t// We support requiring @codemirror/... libraries through require('@codemirror/...')\n\texternalsType: 'commonjs',\n\texternals: extraScriptExternals,\n};\n\nconst createArchiveConfig = {\n\tstats: 'errors-only',\n\tentry: './dist/index.js',\n\tresolve: {\n\t\tfallback: moduleFallback,\n\t},\n\toutput: {\n\t\tfilename: 'index.js',\n\t\tpath: publishDir,\n\t},\n\tplugins: [{\n\t\tapply(compiler) {\n\t\t\tcompiler.hooks.done.tap('archiveOnBuildListener', onBuildCompleted);\n\t\t},\n\t}],\n};\n\nfunction resolveExtraScriptPath(name) {\n\tconst relativePath = `./src/${name}`;\n\n\tconst fullPath = path.resolve(`${rootDir}/${relativePath}`);\n\tif (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: \"${name}\" at \"${fullPath}\"`);\n\n\tconst s = name.split('.');\n\ts.pop();\n\tconst nameNoExt = s.join('.');\n\n\treturn {\n\t\tentry: relativePath,\n\t\toutput: {\n\t\t\tfilename: `${nameNoExt}.js`,\n\t\t\tpath: distDir,\n\t\t\tlibrary: 'default',\n\t\t\tlibraryTarget: 'commonjs',\n\t\t\tlibraryExport: 'default',\n\t\t},\n\t};\n}\n\nfunction buildExtraScriptConfigs(userConfig) {\n\tif (!userConfig.extraScripts.length) return [];\n\n\tconst output = [];\n\n\tfor (const scriptName of userConfig.extraScripts) {\n\t\tconst scriptPaths = resolveExtraScriptPath(scriptName);\n\t\toutput.push({ ...extraScriptConfig, entry: scriptPaths.entry,\n\t\t\toutput: scriptPaths.output });\n\t}\n\n\treturn output;\n}\n\nconst increaseVersion = version => {\n\ttry {\n\t\tconst s = version.split('.');\n\t\tconst d = Number(s[s.length - 1]) + 1;\n\t\ts[s.length - 1] = `${d}`;\n\t\treturn s.join('.');\n\t} catch (error) {\n\t\terror.message = `Could not parse version number: ${version}: ${error.message}`;\n\t\tthrow error;\n\t}\n};\n\nconst updateVersion = () => {\n\tconst packageJson = getPackageJson();\n\tpackageJson.version = increaseVersion(packageJson.version);\n\tfs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\\n`, 'utf8');\n\n\tconst manifest = readManifest(manifestPath);\n\tmanifest.version = increaseVersion(manifest.version);\n\twriteManifest(manifestPath, manifest);\n\n\tif (packageJson.version !== manifest.version) {\n\t\tconsole.warn(chalk.yellow(`Version numbers have been updated but they do not match: package.json (${packageJson.version}), manifest.json (${manifest.version}). Set them to the required values to get them in sync.`));\n\t} else {\n\t\tconsole.info(packageJson.version);\n\t}\n};\n\nfunction main(environ) {\n\tconst configName = environ['joplin-plugin-config'];\n\tif (!configName) throw new Error('A config file must be specified via the --joplin-plugin-config flag');\n\n\t// Webpack configurations run in parallel, while we need them to run in\n\t// sequence, and to do that it seems the only way is to run webpack multiple\n\t// times, with different config each time.\n\n\tconst configs = {\n\t\t// Builds the main src/index.ts and copy the extra content from /src to\n\t\t// /dist including scripts, CSS and any other asset.\n\t\tbuildMain: [pluginConfig],\n\n\t\t// Builds the extra scripts as defined in plugin.config.json. When doing\n\t\t// so, some JavaScript files that were copied in the previous might be\n\t\t// overwritten here by the compiled version. This is by design. The\n\t\t// result is that JS files that don't need compilation, are simply\n\t\t// copied to /dist, while those that do need it are correctly compiled.\n\t\tbuildExtraScripts: buildExtraScriptConfigs(userConfig),\n\n\t\t// Ths config is for creating the .jpl, which is done via the plugin, so\n\t\t// it doesn't actually need an entry and output, however webpack won't\n\t\t// run without this. So we give it an entry that we know is going to\n\t\t// exist and output in the publish dir. Then the plugin will delete this\n\t\t// temporary file before packaging the plugin.\n\t\tcreateArchive: [createArchiveConfig],\n\t};\n\n\t// If we are running the first config step, we clean up and create the build\n\t// directories.\n\tif (configName === 'buildMain') {\n\t\tfs.removeSync(distDir);\n\t\tfs.removeSync(publishDir);\n\t\tfs.mkdirpSync(publishDir);\n\t}\n\n\tif (configName === 'updateVersion') {\n\t\tupdateVersion();\n\t\treturn [];\n\t}\n\n\treturn configs[configName];\n}\n\n\nmodule.exports = (env) => {\n\tlet exportedConfigs = [];\n\n\ttry {\n\t\texportedConfigs = main(env);\n\t} catch (error) {\n\t\tconsole.error(error.message);\n\t\tprocess.exit(1);\n\t}\n\n\tif (!exportedConfigs.length) {\n\t\t// Nothing to do - for example where there are no external scripts to\n\t\t// compile.\n\t\tprocess.exit(0);\n\t}\n\n\treturn exportedConfigs;\n};\n\n"
  }
]