1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-13 00:10:37 +02:00

Desktop: Add support for multiple columns note list (#9924)

This commit is contained in:
Laurent Cozic
2024-03-02 15:29:18 +00:00
committed by GitHub
parent f19b1c5364
commit 53d5cf55bc
50 changed files with 1677 additions and 115 deletions

View File

@ -18,7 +18,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:
*

View File

@ -19,38 +19,52 @@ export interface OnChangeEvent {
noteId: string;
}
export interface OnClickEvent {
elementId: string;
}
export type OnRenderNoteHandler = (props: any)=> Promise<RenderNoteView>;
export type OnChangeHandler = (event: OnChangeEvent)=> Promise<void>;
export type OnClickHandler = (event: OnClickEvent)=> Promise<void>;
/**
* 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<string, string>;
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 +79,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 +117,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
* `<span>{{value}}</span>` 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
* `
* <div>
* Raw timestamp: {{updatedTimeMs}} <!-- This is **not** auto-rendered ->
* Formatted time: {{note.user_updated_time}} <!-- This is -->
* </div>
* `,
*
* ```
*
* 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 `<span>{{value}}</span>`, the final template will be
* `<span>{{formattedDate}}</span>`.
*
* 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 +254,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 +272,46 @@ export interface ListRenderer {
* </div>
* ```
*
* 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:
*
* `<input data-id="todo-checkbox" type="checkbox" {{#note.todo_completed}}checked="checked"{{/note.todo_completed}}>`
*/
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;
};