2020-10-09 19:35:46 +02:00
|
|
|
import joplin from 'api';
|
|
|
|
|
2020-10-16 23:55:48 +02:00
|
|
|
const uslug = require('uslug');
|
2020-10-09 19:35:46 +02:00
|
|
|
|
|
|
|
// From https://stackoverflow.com/a/6234804/561309
|
|
|
|
function escapeHtml(unsafe:string) {
|
|
|
|
return unsafe
|
|
|
|
.replace(/&/g, "&")
|
|
|
|
.replace(/</g, "<")
|
|
|
|
.replace(/>/g, ">")
|
|
|
|
.replace(/"/g, """)
|
|
|
|
.replace(/'/g, "'");
|
|
|
|
}
|
|
|
|
|
|
|
|
function noteHeaders(noteBody:string) {
|
|
|
|
const headers = [];
|
|
|
|
const lines = noteBody.split('\n');
|
|
|
|
for (const line of lines) {
|
|
|
|
const match = line.match(/^(#+)\s(.*)*/);
|
|
|
|
if (!match) continue;
|
|
|
|
headers.push({
|
|
|
|
level: match[1].length,
|
|
|
|
text: match[2],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return headers;
|
|
|
|
}
|
|
|
|
|
|
|
|
let slugs:any = {};
|
|
|
|
|
|
|
|
function headerSlug(headerText:string) {
|
2020-10-16 23:55:48 +02:00
|
|
|
const s = uslug(headerText);
|
2020-10-09 19:35:46 +02:00
|
|
|
let num = slugs[s] ? slugs[s] : 1;
|
|
|
|
const output = [s];
|
|
|
|
if (num > 1) output.push(num);
|
|
|
|
slugs[s] = num + 1;
|
|
|
|
return output.join('-');
|
|
|
|
}
|
|
|
|
|
|
|
|
joplin.plugins.register({
|
|
|
|
onStart: async function() {
|
|
|
|
const panels = joplin.views.panels;
|
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
const view = await (panels as any).create();
|
2020-10-09 19:35:46 +02:00
|
|
|
|
|
|
|
await panels.setHtml(view, 'Loading...');
|
|
|
|
await panels.addScript(view, './webview.js');
|
|
|
|
await panels.addScript(view, './webview.css');
|
|
|
|
|
|
|
|
panels.onMessage(view, (message:any) => {
|
|
|
|
if (message.name === 'scrollToHash') {
|
2020-10-18 22:52:10 +02:00
|
|
|
joplin.commands.execute('scrollToHash', message.hash)
|
2020-10-09 19:35:46 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
async function updateTocView() {
|
|
|
|
const note = await joplin.workspace.selectedNote();
|
|
|
|
slugs = {};
|
|
|
|
|
|
|
|
if (note) {
|
|
|
|
const headers = noteHeaders(note.body);
|
|
|
|
|
|
|
|
const itemHtml = [];
|
|
|
|
for (const header of headers) {
|
|
|
|
const slug = headerSlug(header.text);
|
|
|
|
|
|
|
|
itemHtml.push(`
|
|
|
|
<p class="toc-item" style="padding-left:${(header.level - 1) * 15}px">
|
|
|
|
<a class="toc-item-link" href="#" data-slug="${escapeHtml(slug)}">
|
|
|
|
${escapeHtml(header.text)}
|
|
|
|
</a>
|
|
|
|
</p>
|
|
|
|
`);
|
|
|
|
}
|
|
|
|
|
|
|
|
await panels.setHtml(view, `
|
|
|
|
<div class="container">
|
|
|
|
${itemHtml.join('\n')}
|
|
|
|
</div>
|
|
|
|
`);
|
|
|
|
} else {
|
|
|
|
await panels.setHtml(view, 'Please select a note to view the table of content');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
joplin.workspace.onNoteSelectionChange(() => {
|
|
|
|
updateTocView();
|
|
|
|
});
|
|
|
|
|
|
|
|
joplin.workspace.onNoteContentChange(() => {
|
|
|
|
updateTocView();
|
|
|
|
});
|
|
|
|
|
|
|
|
updateTocView();
|
|
|
|
},
|
|
|
|
});
|