// ================================================================= // Command API types // ================================================================= export interface Command { /** * Name of command - must be globally unique */ name: string; /** * Label to be displayed on menu items or keyboard shortcut editor for example. * If it is missing, it's assumed it's a private command, to be called programmatically only. * In that case the command will not appear in the shortcut editor or command panel, and logically * should not be used as a menu item. */ label?: string; /** * Icon to be used on toolbar buttons for example */ iconName?: string; /** * Code to be ran when the command is executed. It may return a result. */ execute(...args: any[]): Promise; /** * Defines whether the command should be enabled or disabled, which in turns * affects the enabled state of any associated button or menu item. * * The condition should be expressed as a "when-clause" (as in Visual Studio * Code). It's a simple boolean expression that evaluates to `true` or * `false`. It supports the following operators: * * Operator | Symbol | Example * -- | -- | -- * Equality | == | "editorType == markdown" * Inequality | != | "currentScreen != config" * Or | \|\| | "noteIsTodo \|\| noteTodoCompleted" * And | && | "oneNoteSelected && !inConflictFolder" * * Joplin, unlike VSCode, also supports parenthesis, which allows creating * more complex expressions such as `cond1 || (cond2 && cond3)`. Only one * level of parenthesis is possible (nested ones aren't supported). * * Currently the supported context variables aren't documented, but you can * find the list below: * * - [Global When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts) * - [Desktop app When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts) * * Note: Commands are enabled by default unless you use this property. */ enabledCondition?: string; } // ================================================================= // Interop API types // ================================================================= export enum FileSystemItem { File = 'file', Directory = 'directory', } export enum ImportModuleOutputFormat { Markdown = 'md', Html = 'html', } /** * 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. * * 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. * * To get a better sense of what it will contain it can be useful to print it using `console.info(context)`. */ export interface ExportModule { /** * The format to be exported, eg "enex", "jex", "json", etc. */ format: string; /** * The description that will appear in the UI, for example in the menu item. */ description: string; /** * 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. */ target: FileSystemItem; /** * Only applies to single file exporters or importers * It tells whether the format can package multiple notes into one file. * For example JEX or ENEX can, but HTML cannot. */ isNoteArchive: boolean; /** * 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. */ fileExtensions?: string[]; /** * Called when the export process starts. */ onInit(context: ExportContext): Promise; /** * Called when an item needs to be processed. An "item" can be any Joplin object, such as a note, a folder, a notebook, etc. */ onProcessItem(context: ExportContext, itemType: number, item: any): Promise; /** * Called when a resource file needs to be exported. */ onProcessResource(context: ExportContext, resource: any, filePath: string): Promise; /** * Called when the export process is done. */ onClose(context: ExportContext): Promise; } export interface ImportModule { /** * The format to be exported, eg "enex", "jex", "json", etc. */ format: string; /** * The description that will appear in the UI, for example in the menu item. */ description: string; /** * Only applies to single file exporters or importers * It tells whether the format can package multiple notes into one file. * For example JEX or ENEX can, but HTML cannot. */ isNoteArchive: boolean; /** * The type of sources that are supported by the module. Tells whether the module can import files or directories or both. */ sources: FileSystemItem[]; /** * Tells the file extensions of the exported files. */ fileExtensions?: string[]; /** * Tells the type of notes that will be generated, either HTML or Markdown (default). */ outputFormat?: ImportModuleOutputFormat; /** * Called when the import process starts. There is only one event handler within which you should import the complete data. */ onExec(context: ImportContext): Promise; } export interface ExportOptions { format?: string; path?: string; sourceFolderIds?: string[]; sourceNoteIds?: string[]; // modulePath?: string; target?: FileSystemItem; } export interface ExportContext { destPath: string; options: ExportOptions; /** * You can attach your own custom data using this propery - it will then be passed to each event handler, allowing you to keep state from one event to the next. */ userData?: any; } export interface ImportContext { sourcePath: string; options: any; warnings: string[]; } // ================================================================= // Misc types // ================================================================= export interface Script { onStart?(event: any): Promise; } export interface Disposable { // dispose():void; } export enum ModelType { Note = 1, Folder = 2, Setting = 3, Resource = 4, Tag = 5, NoteTag = 6, Search = 7, Alarm = 8, MasterKey = 9, ItemChange = 10, NoteResource = 11, ResourceLocalState = 12, Revision = 13, Migration = 14, SmartFilter = 15, Command = 16, } // ================================================================= // Menu types // ================================================================= export interface CreateMenuItemOptions { accelerator: string; } export enum MenuItemLocation { File = 'file', Edit = 'edit', View = 'view', Note = 'note', Tools = 'tools', Help = 'help', /** * @deprecated Do not use - same as NoteListContextMenu */ Context = 'context', // If adding an item here, don't forget to update isContextMenuItemLocation() /** * When a command is called from the note list context menu, the * command will receive the following arguments: * * - `noteIds:string[]`: IDs of the notes that were right-clicked on. */ NoteListContextMenu = 'noteListContextMenu', EditorContextMenu = 'editorContextMenu', /** * When a command is called from a folder context menu, the * command will receive the following arguments: * * - `folderId:string`: ID of the folder that was right-clicked on */ FolderContextMenu = 'folderContextMenu', /** * When a command is called from a tag context menu, the * command will receive the following arguments: * * - `tagId:string`: ID of the tag that was right-clicked on */ TagContextMenu = 'tagContextMenu', } export function isContextMenuItemLocation(location: MenuItemLocation): boolean { return [ MenuItemLocation.Context, MenuItemLocation.NoteListContextMenu, MenuItemLocation.EditorContextMenu, MenuItemLocation.FolderContextMenu, MenuItemLocation.TagContextMenu, ].includes(location); } export interface MenuItem { /** * Command that should be associated with the menu item. All menu item should * have a command associated with them unless they are a sub-menu. */ commandName?: string; /** * Arguments that should be passed to the command. They will be as rest * parameters. */ commandArgs?: any[]; /** * Set to "separator" to create a divider line */ type?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio'); /** * Accelerator associated with the menu item */ accelerator?: string; /** * Menu items that should appear below this menu item. Allows creating a menu tree. */ submenu?: MenuItem[]; /** * Menu item label. If not specified, the command label will be used instead. */ label?: string; } // ================================================================= // View API types // ================================================================= export interface ButtonSpec { id: ButtonId; title?: string; onClick?(): void; } export type ButtonId = string; export enum ToolbarButtonLocation { /** * This toolbar in the top right corner of the application. It applies to the note as a whole, including its metadata. */ NoteToolbar = 'noteToolbar', /** * This toolbar is right above the text editor. It applies to the note body only. */ EditorToolbar = 'editorToolbar', } export type ViewHandle = string; export interface EditorCommand { name: string; value?: any; } export interface DialogResult { id: ButtonId; formData?: any; } // ================================================================= // Settings types // ================================================================= export enum SettingItemType { Int = 1, String = 2, Bool = 3, Array = 4, Object = 5, Button = 6, } export enum SettingItemSubType { FilePathAndArgs = 'file_path_and_args', FilePath = 'file_path', // Not supported on mobile! DirectoryPath = 'directory_path', // Not supported on mobile! } export enum AppType { Desktop = 'desktop', Mobile = 'mobile', Cli = 'cli', } export enum SettingStorage { Database = 1, File = 2, } // Redefine a simplified interface to mask internal details // and to remove function calls as they would have to be async. export interface SettingItem { value: any; type: SettingItemType; /** * Currently only used to display a file or directory selector. Always set * `type` to `SettingItemType.String` when using this property. */ subType?: SettingItemSubType; label: string; description?: string; /** * A public setting will appear in the Configuration screen and will be * modifiable by the user. A private setting however will not appear there, * and can only be changed programmatically. You may use this to store some * values that you do not want to directly expose. */ public: boolean; /** * You would usually set this to a section you would have created * specifically for the plugin. */ section?: string; /** * To create a setting with multiple options, set this property to `true`. * That setting will render as a dropdown list in the configuration screen. */ isEnum?: boolean; /** * This property is required when `isEnum` is `true`. In which case, it * should contain a map of value => label. */ options?: Record; /** * Reserved property. Not used at the moment. */ appTypes?: AppType[]; /** * Set this to `true` to store secure data, such as passwords. Any such * setting will be stored in the system keychain if one is available. */ secure?: boolean; /** * An advanced setting will be moved under the "Advanced" button in the * config screen. */ advanced?: boolean; /** * Set the min, max and step values if you want to restrict an int setting * to a particular range. */ minimum?: number; maximum?: number; step?: number; /** * Either store the setting in the database or in settings.json. Defaults to database. */ storage?: SettingStorage; } export interface SettingSection { label: string; iconName?: string; description?: string; name?: string; } // ================================================================= // Data API types // ================================================================= /** * An array of at least one element and at most three elements. * * - **[0]**: Resource name (eg. "notes", "folders", "tags", etc.) * - **[1]**: (Optional) Resource ID. * - **[2]**: (Optional) Resource link. */ export type Path = string[]; // ================================================================= // Content Script types // ================================================================= export type PostMessageHandler = (message: any)=> Promise; /** * When a content script is initialised, it receives a `context` object. */ export interface ContentScriptContext { /** * The plugin ID that registered this content script */ pluginId: string; /** * The content script ID, which may be necessary to post messages */ contentScriptId: string; /** * Can be used by CodeMirror content scripts to post a message to the plugin */ postMessage: PostMessageHandler; } export enum ContentScriptType { /** * Registers a new Markdown-It plugin, which should follow the template * below. * * ```javascript * module.exports = { * default: function(context) { * return { * plugin: function(markdownIt, options) { * // ... * }, * assets: { * // ... * }, * } * } * } * ``` * See [the * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script) * for a simple Markdown-it plugin example. * * ## Exported members * * - The `context` argument is currently unused but could be used later on * to provide access to your own plugin so that the content script and * plugin can communicate. * * - The **required** `plugin` key is the actual Markdown-It plugin - check * the [official doc](https://github.com/markdown-it/markdown-it) for more * information. The `options` parameter is of type * [RuleOptions](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml.ts), * which contains a number of options, mostly useful for Joplin's internal * code. * * - Using the **optional** `assets` key you may specify assets such as JS * or CSS that should be loaded in the rendered HTML document. Check for * example the Joplin [Mermaid * plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts) * to see how the data should be structured. * * ## Posting messages from the content script to your plugin * * The application provides the following function to allow executing * commands from the rendered HTML code: * * ```javascript * const response = await webviewApi.postMessage(contentScriptId, message); * ``` * * - `contentScriptId` is the ID you've defined when you registered the * content script. You can retrieve it from the * {@link ContentScriptContext | context}. * - `message` can be any basic JavaScript type (number, string, plain * object), but it cannot be a function or class instance. * * When you post a message, the plugin can send back a `response` thus * allowing two-way communication: * * ```javascript * await joplin.contentScripts.onMessage(contentScriptId, (message) => { * // Process message * return response; // Can be any object, string or number * }); * ``` * * See {@link JoplinContentScripts.onMessage} for more details, as well as * the [postMessage * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages). * * ## Registering an existing Markdown-it plugin * * To include a regular Markdown-It plugin, that doesn't make use of any * Joplin-specific features, you would simply create a file such as this: * * ```javascript * module.exports = { * default: function(context) { * return { * plugin: require('markdown-it-toc-done-right'); * } * } * } * ``` */ MarkdownItPlugin = 'markdownItPlugin', /** * Registers a new CodeMirror plugin, which should follow the template * below. * * ```javascript * module.exports = { * default: function(context) { * return { * plugin: function(CodeMirror) { * // ... * }, * codeMirrorResources: [], * codeMirrorOptions: { * // ... * }, * assets: { * // ... * }, * } * } * } * ``` * * - The `context` argument is currently unused but could be used later on * to provide access to your own plugin so that the content script and * plugin can communicate. * * - The `plugin` key is your CodeMirror plugin. This is where you can * register new commands with CodeMirror or interact with the CodeMirror * instance as needed. * * - The `codeMirrorResources` key is an array of CodeMirror resources that * will be loaded and attached to the CodeMirror module. These are made up * of addons, keymaps, and modes. For example, for a plugin that want's to * enable clojure highlighting in code blocks. `codeMirrorResources` would * be set to `['mode/clojure/clojure']`. * * - The `codeMirrorOptions` key contains all the * [CodeMirror](https://codemirror.net/doc/manual.html#config) options * that will be set or changed by this plugin. New options can alse be * declared via * [`CodeMirror.defineOption`](https://codemirror.net/doc/manual.html#defineOption), * and then have their value set here. For example, a plugin that enables * line numbers would set `codeMirrorOptions` to `{'lineNumbers': true}`. * * - Using the **optional** `assets` key you may specify **only** CSS assets * that should be loaded in the rendered HTML document. Check for example * the Joplin [Mermaid * plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts) * to see how the data should be structured. * * One of the `plugin`, `codeMirrorResources`, or `codeMirrorOptions` keys * must be provided for the plugin to be valid. Having multiple or all * provided is also okay. * * See also the [demo * plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script) * for an example of all these keys being used in one plugin. * * ## Posting messages from the content script to your plugin * * In order to post messages to the plugin, you can use the postMessage * function passed to the {@link ContentScriptContext | context}. * * ```javascript * const response = await context.postMessage('messageFromCodeMirrorContentScript'); * ``` * * When you post a message, the plugin can send back a `response` thus * allowing two-way communication: * * ```javascript * await joplin.contentScripts.onMessage(contentScriptId, (message) => { * // Process message * return response; // Can be any object, string or number * }); * ``` * * See {@link JoplinContentScripts.onMessage} for more details, as well as * the [postMessage * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages). * */ CodeMirrorPlugin = 'codeMirrorPlugin', }