diff --git a/.eslintignore b/.eslintignore index 2ecef6ba8..eda5726a1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -440,6 +440,7 @@ packages/app-desktop/gui/ToolbarButton/ToolbarButton.js packages/app-desktop/gui/ToolbarButton/styles/index.js packages/app-desktop/gui/ToolbarSpace.js packages/app-desktop/gui/TrashNotification/TrashNotification.js +packages/app-desktop/gui/UpdateNotification/UpdateNotification.js packages/app-desktop/gui/dialogs.js packages/app-desktop/gui/hooks/useEffectDebugger.js packages/app-desktop/gui/hooks/useElementHeight.js diff --git a/.gitignore b/.gitignore index 8bf8aa28f..70c7e8eb3 100644 --- a/.gitignore +++ b/.gitignore @@ -417,6 +417,7 @@ packages/app-desktop/gui/ToolbarButton/ToolbarButton.js packages/app-desktop/gui/ToolbarButton/styles/index.js packages/app-desktop/gui/ToolbarSpace.js packages/app-desktop/gui/TrashNotification/TrashNotification.js +packages/app-desktop/gui/UpdateNotification/UpdateNotification.js packages/app-desktop/gui/dialogs.js packages/app-desktop/gui/hooks/useEffectDebugger.js packages/app-desktop/gui/hooks/useElementHeight.js diff --git a/packages/generator-joplin/generators/app/templates/api/Joplin.d.ts b/packages/generator-joplin/generators/app/templates/api/Joplin.d.ts index ab121ff92..4dae70d4a 100644 --- a/packages/generator-joplin/generators/app/templates/api/Joplin.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/Joplin.d.ts @@ -68,6 +68,8 @@ export default class Joplin { * - [fs-extra](https://www.npmjs.com/package/fs-extra) * * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/nativeModule) + * + * desktop */ require(_path: string): any; versionInfo(): Promise; diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinClipboard.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinClipboard.d.ts index 3abc4e9df..26fdf42c2 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinClipboard.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinClipboard.d.ts @@ -4,14 +4,20 @@ export default class JoplinClipboard { constructor(electronClipboard: any, electronNativeImage: any); readText(): Promise; writeText(text: string): Promise; + /** desktop */ readHtml(): Promise; + /** desktop */ writeHtml(html: string): Promise; /** * Returns the image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format. + * + * desktop */ readImage(): Promise; /** * Takes an image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format. + * + * desktop */ writeImage(dataUrl: string): Promise; /** diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinCommands.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinCommands.d.ts index babb249c5..0b7043185 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinCommands.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinCommands.d.ts @@ -1,4 +1,5 @@ import { Command } from './types'; +import Plugin from '../Plugin'; /** * This class allows executing or registering new Joplin commands. Commands * can be executed or associated with @@ -20,6 +21,12 @@ import { Command } from './types'; * To view what arguments are supported, you can open any of these files * and look at the `execute()` command. * + * Note that many of these commands only work on desktop. The more limited list of mobile + * commands can be found in these places: + * + * * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-mobile/commands) + * * [Editor commands](https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/components/NoteEditor/commandDeclarations.ts) + * * ## Executing editor commands * * There might be a situation where you want to invoke editor commands @@ -49,9 +56,10 @@ import { Command } from './types'; * */ export default class JoplinCommands { + private plugin_; + constructor(plugin_: Plugin); /** - * desktop Executes the given - * command. + * Executes the given command. * * The command can take any number of arguments, and the supported * arguments will vary based on the command. For custom commands, this @@ -70,7 +78,7 @@ export default class JoplinCommands { */ execute(commandName: string, ...args: any[]): Promise; /** - * desktop Registers a new command. + * Registers a new command. * * ```typescript * // Register a new commmand called "testCommand1" diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinContentScripts.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinContentScripts.d.ts index 145e9d145..adf4e8de0 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinContentScripts.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinContentScripts.d.ts @@ -21,7 +21,8 @@ export default class JoplinContentScripts { * for more information. * * * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script) - * * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script) + * * [View the editor plugin tutorial](https://joplinapp.org/help/api/tutorials/cm6_plugin) + * * [View the legacy editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script) * * See also the [postMessage demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages) * diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinFilters.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinFilters.d.ts index 43bc1b2b7..6a50479ca 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinFilters.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinFilters.d.ts @@ -1,3 +1,4 @@ +import { FilterHandler } from '../../../eventManager'; /** * @ignore * @@ -5,6 +6,6 @@ * so for now disable filters. */ export default class JoplinFilters { - on(name: string, callback: Function): Promise; - off(name: string, callback: Function): Promise; + on(name: string, callback: FilterHandler): Promise; + off(name: string, callback: FilterHandler): Promise; } diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinImaging.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinImaging.d.ts index 3706b311b..8d878b5db 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinImaging.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinImaging.d.ts @@ -24,10 +24,8 @@ export interface PdfInfo { pageCount: number; } export interface Implementation { - nativeImage: { - createFromPath: (path: string) => Promise; - createFromPdf: (path: string, options: CreateFromPdfOptions) => Promise; - }; + createFromPath: (path: string) => Promise; + createFromPdf: (path: string, options: CreateFromPdfOptions) => Promise; getPdfInfo: (path: string) => Promise; } export interface ResizeOptions { @@ -47,6 +45,7 @@ export type Handle = string; * [View the * example](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/imaging/src/index.ts) * + * desktop */ export default class JoplinImaging { private implementation_; @@ -55,6 +54,11 @@ export default class JoplinImaging { private createImageHandle; private imageByHandle; private cacheImage; + /** + * Creates an image from the provided path. Note that images and PDFs are supported. If you + * provide a URL instead of a local path, the file will be downloaded first then converted to an + * image. + */ createFromPath(filePath: string): Promise; createFromResource(resourceId: string): Promise; createFromPdfPath(path: string, options?: CreateFromPdfOptions): Promise; diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinInterop.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinInterop.d.ts index 60cf1f498..349edd289 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinInterop.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinInterop.d.ts @@ -10,6 +10,9 @@ import { ExportModule, ImportModule } from './types'; * See the documentation of the [[ExportModule]] and [[ImportModule]] for more information. * * 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 + * + * desktop: While it is possible to register import and export + * modules on mobile, there is no GUI to activate them. */ export default class JoplinInterop { registerExportModule(module: ExportModule): Promise; diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinSettings.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinSettings.d.ts index 2d04aa2a9..40f9fba53 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinSettings.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinSettings.d.ts @@ -7,7 +7,6 @@ export interface ChangeEvent { keys: string[]; } export type ChangeHandler = (event: ChangeEvent) => void; -export declare const namespacedKey: (pluginId: string, key: string) => string; /** * 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. * diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinViewsDialogs.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinViewsDialogs.d.ts index 493d76f9f..d4cf7e50f 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinViewsDialogs.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinViewsDialogs.d.ts @@ -47,6 +47,8 @@ export default class JoplinViewsDialogs { * Displays a dialog to select a file or a directory. Same options and * output as * https://www.electronjs.org/docs/latest/api/dialog#dialogshowopendialogbrowserwindow-options + * + * desktop */ showOpenDialog(options: any): Promise; /** diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinViewsMenuItems.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinViewsMenuItems.d.ts index 69e2a8f18..5e236b1e6 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinViewsMenuItems.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinViewsMenuItems.d.ts @@ -4,6 +4,8 @@ import Plugin from '../Plugin'; * Allows creating and managing menu items. * * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command) + * + * desktop */ export default class JoplinViewsMenuItems { private store; diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinViewsMenus.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinViewsMenus.d.ts index f5f803cb1..474830d75 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinViewsMenus.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinViewsMenus.d.ts @@ -4,6 +4,8 @@ import Plugin from '../Plugin'; * Allows creating menus. * * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/menu) + * + * desktop */ export default class JoplinViewsMenus { private store; diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinViewsNoteList.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinViewsNoteList.d.ts index 1afb284c8..004a22c6c 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinViewsNoteList.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinViewsNoteList.d.ts @@ -14,7 +14,9 @@ import { ListRenderer } from './noteListType'; * * * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/note_list_renderer) * - * * [Default list renderer](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/noteList/defaultListRenderer.ts) + * * [Default simple renderer](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/noteList/defaultListRenderer.ts) + * + * * [Default detailed renderer](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/noteList/defaultMultiColumnsRenderer.ts) * * ## Screenshots: * @@ -30,6 +32,7 @@ import { ListRenderer } from './noteListType'; * * * + * desktop */ export default class JoplinViewsNoteList { private plugin_; diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinViewsPanels.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinViewsPanels.d.ts index 4d4c52701..57e1f789f 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinViewsPanels.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinViewsPanels.d.ts @@ -1,12 +1,17 @@ import Plugin from '../Plugin'; import { ViewHandle } from './types'; /** - * Allows creating and managing view panels. View panels currently are - * displayed at the right of the sidebar and allows displaying any HTML - * content (within a webview) and update it in real-time. For example it + * Allows creating and managing view panels. View panels allow displaying any HTML + * content (within a webview) and updating it in real-time. For example it * could be used to display a table of content for the active note, or * display various metadata or graph. * + * On desktop, view panels currently are displayed at the right of the sidebar, though can + * be moved with "View" > "Change application layout". + * + * On mobile, view panels are shown in a tabbed dialog that can be opened using a + * toolbar button. + * * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc) */ export default class JoplinViewsPanels { diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinWindow.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinWindow.d.ts index 231c55c3d..e16a8b4c7 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinWindow.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinWindow.d.ts @@ -12,6 +12,8 @@ export default class JoplinWindow { * for the note viewer. It is the same as the "Custom stylesheet for * 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) * for an example. + * + * desktop */ loadChromeCssFile(filePath: string): Promise; /** @@ -19,6 +21,8 @@ export default class JoplinWindow { * exported or printed note. It is the same as the "Custom stylesheet for * rendered Markdown" setting. See the [Load CSS Demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/load_css) * for an example. + * + * desktop */ loadNoteCssFile(filePath: string): Promise; } diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinWorkspace.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinWorkspace.d.ts index 626d6b6a8..47613390f 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinWorkspace.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinWorkspace.d.ts @@ -1,3 +1,4 @@ +import Plugin from '../Plugin'; import { FolderEntity } from '../../database/types'; import { Disposable, MenuItem } from './types'; export interface EditContextMenuFilterObject { @@ -13,15 +14,25 @@ interface ItemChangeEvent { id: string; event: ItemChangeEventType; } -interface SyncStartEvent { - withErrors: boolean; -} interface ResourceChangeEvent { id: string; } -type ItemChangeHandler = (event: ItemChangeEvent) => void; -type SyncStartHandler = (event: SyncStartEvent) => void; -type ResourceChangeHandler = (event: ResourceChangeEvent) => void; +interface NoteContentChangeEvent { + note: any; +} +interface NoteSelectionChangeEvent { + value: string[]; +} +interface NoteAlarmTriggerEvent { + noteId: string; +} +interface SyncCompleteEvent { + withErrors: boolean; +} +type WorkspaceEventHandler = (event: EventType) => void; +type ItemChangeHandler = WorkspaceEventHandler; +type SyncStartHandler = () => void; +type ResourceChangeHandler = WorkspaceEventHandler; /** * The workspace service provides access to all the parts of Joplin that * are being worked on - i.e. the currently selected notes or notebooks as @@ -32,16 +43,17 @@ type ResourceChangeHandler = (event: ResourceChangeEvent) => void; */ export default class JoplinWorkspace { private store; - constructor(store: any); + private plugin; + constructor(plugin: Plugin, store: any); /** * Called when a new note or notes are selected. */ - onNoteSelectionChange(callback: Function): Promise; + onNoteSelectionChange(callback: WorkspaceEventHandler): Promise; /** * Called when the content of a note changes. * @deprecated Use `onNoteChange()` instead, which is reliably triggered whenever the note content, or any note property changes. */ - onNoteContentChange(callback: Function): Promise; + onNoteContentChange(callback: WorkspaceEventHandler): Promise; /** * Called when the content of the current note changes. */ @@ -54,7 +66,7 @@ export default class JoplinWorkspace { /** * Called when an alarm associated with a to-do is triggered. */ - onNoteAlarmTrigger(handler: Function): Promise; + onNoteAlarmTrigger(handler: WorkspaceEventHandler): Promise; /** * Called when the synchronisation process is starting. */ @@ -62,14 +74,16 @@ export default class JoplinWorkspace { /** * Called when the synchronisation process has finished. */ - onSyncComplete(callback: Function): Promise; + onSyncComplete(callback: WorkspaceEventHandler): Promise; /** * Called just before the editor context menu is about to open. Allows * adding items to it. + * + * desktop */ filterEditorContextMenu(handler: FilterHandler): void; /** - * Gets the currently selected note + * Gets the currently selected note. Will be `null` if no note is selected. */ selectedNote(): Promise; /** diff --git a/packages/generator-joplin/generators/app/templates/api/noteListType.d.ts b/packages/generator-joplin/generators/app/templates/api/noteListType.d.ts index 3c60081d5..7125976e0 100644 --- a/packages/generator-joplin/generators/app/templates/api/noteListType.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/noteListType.d.ts @@ -10,29 +10,26 @@ export interface OnChangeEvent { value: any; noteId: string; } +export interface OnClickEvent { + elementId: string; +} export type OnRenderNoteHandler = (props: any) => Promise; export type OnChangeHandler = (event: OnChangeEvent) => Promise; +export type OnClickHandler = (event: OnClickEvent) => Promise; /** - * Most of these are the built-in note properties, such as `note.title`, - * `note.todo_completed`, etc. + * Most of these are the built-in note properties, such as `note.title`, `note.todo_completed`, etc. + * complemented with special properties such as `note.isWatched`, to know if a note is currently + * opened in the external editor, and `note.tags` to get the list tags associated with the note. * - * Additionally, the `item.*` properties are specific to the rendered item. The - * most important being `item.selected`, which you can use to display the - * selected note in a different way. + * ## Item properties * - * Finally some special properties are provided to make it easier to render - * notes. In particular, if possible prefer `note.titleHtml` to `note.title` - * since some important processing has already been done on the string, such as - * handling the search highlighter and escaping. Since it's HTML and already - * escaped you would insert it using `{{{titleHtml}}}` (triple-mustache syntax, - * which disables escaping). - * - * `notes.tag` gives you the list of tags associated with the note. - * - * `note.isWatched` tells you if the note is currently opened in an external - * editor. In which case you would generally display some indicator. + * The `item.*` properties are specific to the rendered item. The most important being + * `item.selected`, which you can use to display the selected note in a different way. */ -export type ListRendererDependency = ListRendererDatabaseDependency | 'item.index' | 'item.size.width' | 'item.size.height' | 'item.selected' | 'note.titleHtml' | 'note.isWatched' | 'note.tags'; +export type ListRendererDependency = ListRendererDatabaseDependency | 'item.index' | 'item.selected' | 'item.size.height' | 'item.size.width' | 'note.folder.title' | 'note.isWatched' | 'note.tags' | 'note.titleHtml'; +export type ListRendererItemValueTemplates = Record; +export 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"]; +export type ColumnName = typeof columnNames[number]; export interface ListRenderer { /** * It must be unique to your plugin. @@ -44,6 +41,11 @@ export interface ListRenderer { * height. */ flow: ItemFlow; + /** + * Whether the renderer supports multiple columns. Applies only when `flow` + * is `topToBottom`. Defaults to `false`. + */ + multiColumns?: boolean; /** * The size of each item must be specified in advance for performance * reasons, and cannot be changed afterwards. If the item flow is top to @@ -74,20 +76,97 @@ export interface ListRenderer { * that you do not add more than what you need since there is a performance * penalty for each property. */ - dependencies: ListRendererDependency[]; + dependencies?: ListRendererDependency[]; + headerTemplate?: string; + headerHeight?: number; + onHeaderClick?: OnClickHandler; /** - * This is the HTML template that will be used to render the note list item. - * This is a [Mustache template](https://github.com/janl/mustache.js) and it - * will receive the variable you return from `onRenderNote` as tags. For - * example, if you return a property named `formattedDate` from - * `onRenderNote`, you can insert it in the template using `Created date: - * {{formattedDate}}`. + * This property is set differently depending on the `multiColumns` property. * - * In order to get syntax highlighting working here, it's recommended - * installing an editor extension such as [es6-string-html VSCode + * ## If `multiColumns` is `false` + * + * There is only one column and the template is used to render the entire row. + * + * This is the HTML template that will be used to render the note list item. This is a [Mustache + * template](https://github.com/janl/mustache.js) and it will receive the variable you return + * from `onRenderNote` as tags. For example, if you return a property named `formattedDate` from + * `onRenderNote`, you can insert it in the template using `Created date: {{formattedDate}}` + * + * ## If `multiColumns` is `true` + * + * Since there is multiple columns, this template will be used to render each note property + * within the row. For example if the current columns are the Updated and Title properties, this + * template will be called once to render the updated time and a second time to render the + * title. To display the current property, the generic `value` property is provided - it will be + * replaced at runtime by the actual note property. To render something different depending on + * the note property, use `itemValueTemplate`. A minimal example would be + * `{{value}}` which will simply render the current property inside a span tag. + * + * In order to get syntax highlighting working here, it's recommended installing an editor + * extension such as [es6-string-html VSCode * extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html) + * + * ## Default property rendering + * + * Certain properties are automatically rendered once inserted in the Mustache template. Those + * are in particular all the date-related fields, such as `note.user_updated_time` or + * `note.todo_completed`. Internally, those are timestamps in milliseconds, however when + * rendered we display them as date/time strings using the user's preferred time format. Another + * notable auto-rendered property is `note.title` which is going to include additional HTML, + * such as the search markers. + * + * If you do not want this default rendering behaviour, for example if you want to display the + * raw timestamps in milliseconds, you can simply return custom properties from + * `onRenderNote()`. For example: + * + * ```typescript + * onRenderNote: async (props: any) => { + * return { + * ...props, + * // Return the property under a different name + * updatedTimeMs: props.note.user_updated_time, + * } + * }, + * + * itemTemplate: // html + * ` + *
+ * Raw timestamp: {{updatedTimeMs}} + *
+ * `, + * + * ``` + * + * See + * `[https://github.com/laurent22/joplin/blob/dev/packages/lib/services/noteList/renderViewProps.ts](renderViewProps.ts)` + * for the list of properties that have a default rendering. */ itemTemplate: string; + /** + * This property applies only when `multiColumns` is `true`. It is used to render something + * different for each note property. + * + * This is a map of actual dependencies to templates - you only need to return something if the + * default, as specified in `template`, is not enough. + * + * Again you need to return a Mustache template and it will be combined with the `template` + * property to create the final template. For example if you return a property named + * `formattedDate` from `onRenderNote`, you can insert it in the template using + * `{{formattedDate}}`. This string will replace `{{value}}` in the `template` property. + * + * So if the template property is set to `{{value}}`, the final template will be + * `{{formattedDate}}`. + * + * The property would be set as so: + * + * ```javascript + * itemValueTemplates: { + * 'note.user_updated_time': '{{formattedDate}}', + * } + * ``` + */ + itemValueTemplates?: ListRendererItemValueTemplates; /** * This user-facing text is used for example in the View menu, so that your * renderer can be selected. @@ -128,17 +207,15 @@ export interface ListRenderer { */ onRenderNote: OnRenderNoteHandler; /** - * This handler allows adding some interactivity to the note renderer - - * whenever an input element within the item is changed (for example, when a - * checkbox is clicked, or a text input is changed), this `onChange` handler - * is going to be called. + * This handler allows adding some interactivity to the note renderer - whenever an input element + * within the item is changed (for example, when a checkbox is clicked, or a text input is + * changed), this `onChange` handler is going to be called. * - * You can inspect `event.elementId` to know which element had some changes, - * and `event.value` to know the new value. `event.noteId` also tells you - * what note is affected, so that you can potentially apply changes to it. + * You can inspect `event.elementId` to know which element had some changes, and `event.value` + * to know the new value. `event.noteId` also tells you what note is affected, so that you can + * potentially apply changes to it. * - * You specify the element ID, by setting a `data-id` attribute on the - * input. + * You specify the element ID, by setting a `data-id` attribute on the input. * * For example, if you have such a template: * @@ -148,9 +225,26 @@ export interface ListRenderer { * * ``` * - * The event handler will receive an event with `elementId` set to - * `noteTitleInput`. + * The event handler will receive an event with `elementId` set to `noteTitleInput`. + * + * ## Default event handlers + * + * Currently one click event is automatically handled: + * + * If there is a checkbox with a `data-id="todo-checkbox"` attribute is present, it is going to + * automatically toggle the note to-do "completed" status. + * + * For example this is what is used in the default list renderer: + * + * `` */ onChange?: OnChangeHandler; } +export interface NoteListColumn { + name: ColumnName; + width: number; +} +export type NoteListColumns = NoteListColumn[]; +export declare const defaultWidth = 100; +export declare const defaultListColumns: () => NoteListColumns; export {}; diff --git a/packages/generator-joplin/generators/app/templates/api/noteListType.ts b/packages/generator-joplin/generators/app/templates/api/noteListType.ts index 30aeb8351..a2657020e 100644 --- a/packages/generator-joplin/generators/app/templates/api/noteListType.ts +++ b/packages/generator-joplin/generators/app/templates/api/noteListType.ts @@ -11,46 +11,63 @@ export enum ItemFlow { LeftToRight = 'leftToRight', } +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied export type RenderNoteView = Record; export interface OnChangeEvent { elementId: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied value: any; noteId: string; } +export interface OnClickEvent { + elementId: string; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied export type OnRenderNoteHandler = (props: any)=> Promise; export type OnChangeHandler = (event: OnChangeEvent)=> Promise; +export type OnClickHandler = (event: OnClickEvent)=> Promise; /** - * Most of these are the built-in note properties, such as `note.title`, - * `note.todo_completed`, etc. + * Most of these are the built-in note properties, such as `note.title`, `note.todo_completed`, etc. + * complemented with special properties such as `note.isWatched`, to know if a note is currently + * opened in the external editor, and `note.tags` to get the list tags associated with the note. * - * Additionally, the `item.*` properties are specific to the rendered item. The - * most important being `item.selected`, which you can use to display the - * selected note in a different way. + * ## Item properties * - * Finally some special properties are provided to make it easier to render - * notes. In particular, if possible prefer `note.titleHtml` to `note.title` - * since some important processing has already been done on the string, such as - * handling the search highlighter and escaping. Since it's HTML and already - * escaped you would insert it using `{{{titleHtml}}}` (triple-mustache syntax, - * which disables escaping). - * - * `notes.tag` gives you the list of tags associated with the note. - * - * `note.isWatched` tells you if the note is currently opened in an external - * editor. In which case you would generally display some indicator. + * The `item.*` properties are specific to the rendered item. The most important being + * `item.selected`, which you can use to display the selected note in a different way. */ export type ListRendererDependency = ListRendererDatabaseDependency | 'item.index' | - 'item.size.width' | - 'item.size.height' | 'item.selected' | - 'note.titleHtml' | + 'item.size.height' | + 'item.size.width' | + 'note.folder.title' | 'note.isWatched' | - 'note.tags'; + 'note.tags' | + 'note.titleHtml'; + +export type ListRendererItemValueTemplates = Record; + +export const columnNames = [ + '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', +] as const; + +export type ColumnName = typeof columnNames[number]; export interface ListRenderer { /** @@ -65,6 +82,12 @@ export interface ListRenderer { */ flow: ItemFlow; + /** + * Whether the renderer supports multiple columns. Applies only when `flow` + * is `topToBottom`. Defaults to `false`. + */ + multiColumns?: boolean; + /** * The size of each item must be specified in advance for performance * reasons, and cannot be changed afterwards. If the item flow is top to @@ -97,22 +120,101 @@ export interface ListRenderer { * that you do not add more than what you need since there is a performance * penalty for each property. */ - dependencies: ListRendererDependency[]; + dependencies?: ListRendererDependency[]; + + headerTemplate?: string; + headerHeight?: number; + onHeaderClick?: OnClickHandler; /** - * This is the HTML template that will be used to render the note list item. - * This is a [Mustache template](https://github.com/janl/mustache.js) and it - * will receive the variable you return from `onRenderNote` as tags. For - * example, if you return a property named `formattedDate` from - * `onRenderNote`, you can insert it in the template using `Created date: - * {{formattedDate}}`. + * This property is set differently depending on the `multiColumns` property. * - * In order to get syntax highlighting working here, it's recommended - * installing an editor extension such as [es6-string-html VSCode + * ## If `multiColumns` is `false` + * + * There is only one column and the template is used to render the entire row. + * + * This is the HTML template that will be used to render the note list item. This is a [Mustache + * template](https://github.com/janl/mustache.js) and it will receive the variable you return + * from `onRenderNote` as tags. For example, if you return a property named `formattedDate` from + * `onRenderNote`, you can insert it in the template using `Created date: {{formattedDate}}` + * + * ## If `multiColumns` is `true` + * + * Since there is multiple columns, this template will be used to render each note property + * within the row. For example if the current columns are the Updated and Title properties, this + * template will be called once to render the updated time and a second time to render the + * title. To display the current property, the generic `value` property is provided - it will be + * replaced at runtime by the actual note property. To render something different depending on + * the note property, use `itemValueTemplate`. A minimal example would be + * `{{value}}` which will simply render the current property inside a span tag. + * + * In order to get syntax highlighting working here, it's recommended installing an editor + * extension such as [es6-string-html VSCode * extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html) + * + * ## Default property rendering + * + * Certain properties are automatically rendered once inserted in the Mustache template. Those + * are in particular all the date-related fields, such as `note.user_updated_time` or + * `note.todo_completed`. Internally, those are timestamps in milliseconds, however when + * rendered we display them as date/time strings using the user's preferred time format. Another + * notable auto-rendered property is `note.title` which is going to include additional HTML, + * such as the search markers. + * + * If you do not want this default rendering behaviour, for example if you want to display the + * raw timestamps in milliseconds, you can simply return custom properties from + * `onRenderNote()`. For example: + * + * ```typescript + * onRenderNote: async (props: any) => { + * return { + * ...props, + * // Return the property under a different name + * updatedTimeMs: props.note.user_updated_time, + * } + * }, + * + * itemTemplate: // html + * ` + *
+ * Raw timestamp: {{updatedTimeMs}} + *
+ * `, + * + * ``` + * + * See + * `[https://github.com/laurent22/joplin/blob/dev/packages/lib/services/noteList/renderViewProps.ts](renderViewProps.ts)` + * for the list of properties that have a default rendering. */ itemTemplate: string; + /** + * This property applies only when `multiColumns` is `true`. It is used to render something + * different for each note property. + * + * This is a map of actual dependencies to templates - you only need to return something if the + * default, as specified in `template`, is not enough. + * + * Again you need to return a Mustache template and it will be combined with the `template` + * property to create the final template. For example if you return a property named + * `formattedDate` from `onRenderNote`, you can insert it in the template using + * `{{formattedDate}}`. This string will replace `{{value}}` in the `template` property. + * + * So if the template property is set to `{{value}}`, the final template will be + * `{{formattedDate}}`. + * + * The property would be set as so: + * + * ```javascript + * itemValueTemplates: { + * 'note.user_updated_time': '{{formattedDate}}', + * } + * ``` + */ + itemValueTemplates?: ListRendererItemValueTemplates; + /** * This user-facing text is used for example in the View menu, so that your * renderer can be selected. @@ -155,17 +257,15 @@ export interface ListRenderer { onRenderNote: OnRenderNoteHandler; /** - * This handler allows adding some interactivity to the note renderer - - * whenever an input element within the item is changed (for example, when a - * checkbox is clicked, or a text input is changed), this `onChange` handler - * is going to be called. + * This handler allows adding some interactivity to the note renderer - whenever an input element + * within the item is changed (for example, when a checkbox is clicked, or a text input is + * changed), this `onChange` handler is going to be called. * - * You can inspect `event.elementId` to know which element had some changes, - * and `event.value` to know the new value. `event.noteId` also tells you - * what note is affected, so that you can potentially apply changes to it. + * You can inspect `event.elementId` to know which element had some changes, and `event.value` + * to know the new value. `event.noteId` also tells you what note is affected, so that you can + * potentially apply changes to it. * - * You specify the element ID, by setting a `data-id` attribute on the - * input. + * You specify the element ID, by setting a `data-id` attribute on the input. * * For example, if you have such a template: * @@ -175,8 +275,46 @@ export interface ListRenderer { * * ``` * - * The event handler will receive an event with `elementId` set to - * `noteTitleInput`. + * The event handler will receive an event with `elementId` set to `noteTitleInput`. + * + * ## Default event handlers + * + * Currently one click event is automatically handled: + * + * If there is a checkbox with a `data-id="todo-checkbox"` attribute is present, it is going to + * automatically toggle the note to-do "completed" status. + * + * For example this is what is used in the default list renderer: + * + * `` */ onChange?: OnChangeHandler; } + +export interface NoteListColumn { + name: ColumnName; + width: number; +} + +export type NoteListColumns = NoteListColumn[]; + +export const defaultWidth = 100; + +export const defaultListColumns = () => { + const columns: NoteListColumns = [ + { + name: 'note.is_todo', + width: 30, + }, + { + name: 'note.user_updated_time', + width: defaultWidth, + }, + { + name: 'note.title', + width: 0, + }, + ]; + + return columns; +}; diff --git a/packages/generator-joplin/generators/app/templates/api/types.ts b/packages/generator-joplin/generators/app/templates/api/types.ts index 07c1de94a..a51b7fad1 100644 --- a/packages/generator-joplin/generators/app/templates/api/types.ts +++ b/packages/generator-joplin/generators/app/templates/api/types.ts @@ -26,6 +26,7 @@ export interface Command { /** * Code to be ran when the command is executed. It may return a result. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied execute(...args: any[]): Promise; /** @@ -115,11 +116,13 @@ export interface ExportModule { /** * Called when an item needs to be processed. An "item" can be any Joplin object, such as a note, a folder, a notebook, etc. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied onProcessItem(context: ExportContext, itemType: number, item: any): Promise; /** * Called when a resource file needs to be exported. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied onProcessResource(context: ExportContext, resource: any, filePath: string): Promise; /** @@ -183,11 +186,13 @@ export interface ExportContext { /** * 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. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied userData?: any; } export interface ImportContext { sourcePath: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied options: any; warnings: string[]; } @@ -197,6 +202,7 @@ export interface ImportContext { // ================================================================= export interface Script { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied onStart?(event: any): Promise; } @@ -227,6 +233,8 @@ export interface VersionInfo { version: string; profileVersion: number; syncVersion: number; + + platform: 'desktop'|'mobile'; } // ================================================================= @@ -300,6 +308,7 @@ export interface MenuItem { * Arguments that should be passed to the command. They will be as rest * parameters. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied commandArgs?: any[]; /** @@ -338,6 +347,8 @@ 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. + * + * desktop */ NoteToolbar = 'noteToolbar', @@ -351,11 +362,13 @@ export type ViewHandle = string; export interface EditorCommand { name: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied value?: any; } export interface DialogResult { id: ButtonId; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied formData?: any; } @@ -404,6 +417,7 @@ export enum SettingStorage { // Redefine a simplified interface to mask internal details // and to remove function calls as they would have to be async. export interface SettingItem { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied value: any; type: SettingItemType; @@ -440,6 +454,7 @@ export interface SettingItem { * This property is required when `isEnum` is `true`. In which case, it * should contain a map of value => label. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied options?: Record; /** @@ -497,6 +512,7 @@ export type Path = string[]; // Content Script types // ================================================================= +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied export type PostMessageHandler = (message: any)=> Promise; /** @@ -520,30 +536,38 @@ export interface ContentScriptContext { } export interface ContentScriptModuleLoadedEvent { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied userData?: any; } export interface ContentScriptModule { onLoaded?: (event: ContentScriptModuleLoadedEvent)=> void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied plugin: ()=> any; assets?: ()=> void; } export interface MarkdownItContentScriptModule extends Omit { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied plugin: (markdownIt: any, options: any)=> any; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied type EditorCommandCallback = (...args: any[])=> any; export interface CodeMirrorControl { /** Points to a CodeMirror 6 EditorView instance. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied editor: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied cm6: any; /** `extension` should be a [CodeMirror 6 extension](https://codemirror.net/docs/ref/#state.Extension). */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied addExtension(extension: any|any[]): void; supportsCommand(name: string): boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied execCommand(name: string, ...args: any[]): any; registerCommand(name: string, callback: EditorCommandCallback): void; @@ -557,17 +581,19 @@ export interface CodeMirrorControl { * * Using `autocompletion({ override: [ ... ]})` causes errors when done by multiple plugins. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied completionSource(completionSource: any): any; /** * Creates an extension that enables or disables [`languageData`-based autocompletion](https://codemirror.net/docs/ref/#autocomplete.autocompletion^config.override). */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied enableLanguageDataAutocomplete: { of: (enabled: boolean)=> any }; }; } -export interface CodeMirrorContentScriptModule extends Omit { - plugin: (codeMirrorControl: CodeMirrorControl)=> void; +export interface MarkdownEditorContentScriptModule extends Omit { + plugin: (editorControl: CodeMirrorControl)=> void; } export enum ContentScriptType { @@ -610,6 +636,45 @@ export enum ContentScriptType { * plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts) * to see how the data should be structured. * + * ## Supporting the Rich Text Editor + * + * Joplin's Rich Text Editor works with rendered HTML, which is converted back + * to markdown when saving. To prevent the original markdown for your plugin from + * being lost, Joplin needs additional metadata. + * + * To provide this, + * 1. Wrap the HTML generated by your plugin in an element with class `joplin-editable`. + * For example, + * ```html + *
+ * ...your html... + *
+ * ``` + * 2. Add a child with class `joplin-source` that contains the original markdown that + * was rendered by your plugin. Include `data-joplin-source-open`, `data-joplin-source-close`, + * and `data-joplin-language` attributes. + * For example, if your plugin rendered the following code block, + * ```` + * ```foo + * ... original source here ... + * ``` + * ```` + * then it should render to + * ```html + *
+ *
 ... original source here ... 
+ * ... rendered HTML here ... + *
+ * ``` + * + * See [the demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script) + * for a complete example. + * * ## Getting the settings from the renderer * * You can access your plugin settings from the renderer by calling @@ -684,21 +749,21 @@ export enum ContentScriptType { * } * ``` * - * - 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 `context` argument allows communicating with other parts of + * your plugin (see below). * * - 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 + * - **CodeMirror 5 only**: 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']`. + * This field is ignored on mobile and when the desktop beta editor is enabled. * - * - The `codeMirrorOptions` key contains all the + * - **CodeMirror 5 only**: 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 @@ -716,9 +781,11 @@ export enum ContentScriptType { * 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. + * 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. + * - See [the editor plugin tutorial](https://joplinapp.org/help/api/tutorials/cm6_plugin) + * for how to develop a plugin for the mobile editor and the desktop beta markdown editor. * * ## Posting messages from the content script to your plugin *