I'm having a look again at this feature:
opened 02:40PM - 27 Aug 21 UTC
spec
plugins
The goal of this feature is to allow plugins to customise the note list and in p… articular the way each note is rendered. Then for example this kind of features could be done as plugins:
- Display the note as a thumbnail
- Display an excerpt of the note body below the title
- Display tags
- Display various buttons that could be clicked on
As part of this feature, it should also be set the note list flow:
- Top to bottom (as now)
- Left to right (would be useful to display thumbnails for example)
All that should be done in a backward compatible way, so that the note list by default still looks like it does now.
## Implementation
The app would still be responsible for displaying the note list and its items. This is necessary because it needs to be optimised so that only the visible notes are being rendered. That allows having thousands of notes inside a notebooks without any slow down.
The part that will be customisable is the the note list item. Currently it is shows the note title and, optionally, a checkbox on the left:
<img src="https://user-images.githubusercontent.com/1285584/131144633-3d1a9506-9d34-408d-90fe-9df09aea5bde.png" width=300/>
For maximum flexibility, the API should let plugins define the note list items as HTML. This could be done like so:
- The plugin registers a note list item renderer, which provides some HTML based on the note to be rendered
- There should be some way for the plugin to specify the width and height of the item, so that the app knows how many fit within the note list panel.
- The app gets the HTML for all the notes currently visible, and display them.
## Interaction
Allowing the plugin to listen to mouse clicks or keyboard events on the items might be a bit tricky, due to the process boundaries between plugin and app. Perhaps it will require a custom solution - for example, the app could listen to event, then call an event handler on the plugin when something has been clicked.
## Plugin conflicts
The above solution would allow only one plugin at a time to modify the note list items. It's not ideal but it's not clear how it could be done otherwise.
## Example 1
```typescript
interface NoteView {
html: string;
}
await joplin.views.noteList.registerNoteRenderer('my-id', {
flow: 'leftToRight',
itemSize: {
width: 500,
height: 300,
},
onRenderNotes: async (notes:any[]) => {
const output:Record<string, NoteView> = {};
for (const note of notes) {
const noteTags = await joplin.data.get('notes/' + note.id + '/tags');
output[note.id] = {
html: `<div>${note.title}<br/>${note.created_time}<br/><a id="tag-0" href="#">${noteTags[0]}</a></div>`,
};
}
return output;
},
onItemClick = async (item:any, elementId:string) => {
// For example, if the tag is clicked above, "elementId"
// would be "tag-0".
console.info('Item element was clicked: ' + elementId);
},
})
```
## Example 2
This other version uses a different approach which may offer better performances:
- The plugin developer specifies what dependencies are needed for rendering
- If they haven't changed between render, we just use the cached version we have
- A Mustache template is also provided - that's the HTML used to render the list item
- It contains placeholder that are filled at render time
- The `onRenderNote` method returns the values for the template placeholders.
The advantage is that we no longer have a method to render the whole, but only each individual items. The inconvenient is that when the list is created we have to call `onRenderNote` multiple times, which may be slow over IPC. Should investigate if it can be called multiple times plugin-side, then the complete result (all the rendered items) is sent back to the host in one call. If that can be done, this approach should be better.
```typescript
await joplin.views.noteList.registerNoteRenderer('my-id', {
flow: 'leftToRight',
itemSize: {
width: 500,
height: 300,
},
dependencies: [
'noteTitle',
'noteCreatedTime',
'noteTags',
],
itemTemplate: `
<div>
<div>{{title}}</div>
<div>Created: {{date}}</div>
<div>{#tagTitles}}<span class="tag">{{.}}</span>{{/tagTitles}}</div>
</div>
`,
onRenderNote: async (context:Context, noteTitle:string, noteCreatedTime:number, noteTags:TagEntity[]) => {
return {
title: noteTitle,
date: dayjs(noteCreatedTime).format('DD/MM/YYYY'),
tagTitles: noteTags.map(t => t.title),
};
},
})
```
## Other ideas
### Render list item content via filters
- In order to allow multiple plugins to modify the note item content, we could make the plugins act as filters, which modify the HTML content. For example it starts with `<div class="title">Note title</div>`, then plugin 1 change this by adding the date below: `<div class="title">Note title</div><div class="date">21/08/21</div>`, then another plugin removes the title tag and replace it by a thumbnail `<div class="title"><img src="..."/></div><div class="date">21/08/21</div>`, etc.
- Advantage is that multiple plugins can modify the content
- Disadvantage is that each plugin will have to be careful about how they change it - for example they shouldn't expect that a particular is going to be present, since it might have been removed by another plugin. So in a way conflicts are still possible.
### Plugin defines a view instead of a renderer
Perhaps plugins could define a view instead of a renderer, then it's up to the user what view they choose. The chosen view will be fully responsible for rendering the note list content, and so no conflicts are possible.
The goal is to allow plugins to manage and render the note list. For example, we could imagine a plugin that renders thumbnails of the notes, or one that display the notes in a tabular format.
Those are just examples but I was wondering, if you are a plugin developer or user, what list-based plugin would you like to see or develop?
You can already see some ideas in the GitHub issue under "Example 1" and "Example 2"
It would be very useful to gather as much feedback a possible, so that we can get the implementation right from the start. Any suggestions or ideas are welcome!
2 Likes
Thanks for the suggestions, I'm adding this to the list of features we'd like to support.
Assuming it doesn't already exist and I missed it... Could sticky/pinned notes per notebook be a thing? I like MRU note ordering, so having at least one note able to skirt around that and stay at the top would be nice. Or is that out of scope for this thread because this is purely about rendering the list?
4 Likes
Great idea. For me
Size, Updated, Created, Sync status (just a tick would be great), and tags.
I really miss these from my Evernote view.
2 Likes
Yeah, for me too the main use case is having additional columns in the note list to see certain attributes at a glance, and also to easily sort. Personally I would like to have a customisable set of columns as @pjsmith commented, with the ability to sort the note list by these.
I guess the fulll list of columns I would like to be able to display would be:
title
notebook
tag list
created
updated
sync status
So mostly as above, but with notebook included - for me this is really important when searching globally to have an idea of where the notes in my results sit.
2 Likes
Something similar to the Evernote Snippets view would be amazing - this is one of my main impediments to migrating to Joplin i.e. thumbnail with a short section of text from the note.
Custom/programmable content and formatting/colours would be superb. Custom icons/tags? Custom sorting orders, filters, display toggles?