Table of Contents (TOC) for Free

name

Generate TOC

icon

list_alt

description

Unofficial implementation of {toc}, Table of Contents.

instructions



Adds TOC (Table of Contents) features like the official one.

This plugin has two features:
1. Inline TOC: Type "{otoc}" (ordered) or "{utoc}" (unordered) to insert the TOC. Similar to the official {toc} feature.
2. Note-level TOC: Select "Generate TOC" from note options to add TOC at the beginning of the note. As long as it's there, a new TOC overwrites the old one whenever it runs again.

Settings (can be configured in "Account Settings > Plugins > Generate TOC (Gear icon)"):
- Ordered TOC Expression Name: Expression name for {otoc}. Defaults to "otoc".
- Unordered TOC Expression Name: Expression name for {utoc}. Defaults to "utoc".
- Enable Note Level TOC: Set this to "false" to turn off note-level TOC feature. Defaults to true.
- Use Ordered TOC in Note Level TOC: Set this to "false" to make Nove-level TOCs unordered (bullets). Defaults to true.

Disclaimer: I haven't thoroughly tested if this successfully runs in every situation. If you find bugs, please feel free to let me know in comments.

setting

Ordered TOC Expression Name

setting

Unordered TOC Expression Name

setting

Enable Note Level TOC

setting

Use Ordered TOC in Note Level TOC



Updates:

May 31st, 2024

Changed icon to "list_alt" (same as the official {toc})

May 30th, 2024

Deprecated {tocf} and added {otoc} and {utoc}. These names can be changed via the Settings.

Changed setting keys

May 26th, 2024

First release


{
_getTOCFromSections(sections, usesOrderedTOC) {
let ret = "";
for (const section of sections) {
if (section.heading) {
const components = [
" ".repeat(section.heading.level - 1),
usesOrderedTOC ? "1." : "-",
" [",
section.heading.text,
"](#",
section.heading.anchor,
") \n\n",
];
ret += components.join("");
}
}
return ret;
},
 
_isTOCish(noteContent, usesOrderedTOC) {
const re = RegExp("^(| {4,8})" + (usesOrderedTOC ? "1\\." : "-") + " \\[.+\\]\\(#.+\\) $");
const lines = noteContent.split("\n");
for (let i = 0; i < lines.length; i++) {
if (lines[i] === "---") return true;
if (! re.test(lines[i])) return false;
i++;
if (i >= lines.length || lines[i] !== "") return false;
}
return false;
},
 
noteOption: {
check(app) {
return app.settings["Enable Note Level TOC"] !== "false";
},
 
async run(app, noteUUID) {
const usesOrderedTOC = app.settings["Use Ordered TOC in Note Level TOC"] !== "false";
const noteContent = await app.getNoteContent({uuid: noteUUID});
const sections = await app.getNoteSections({uuid: noteUUID});
const toc = this._getTOCFromSections(sections, usesOrderedTOC);
if (this._isTOCish(noteContent, usesOrderedTOC)) {
app.replaceNoteContent({uuid: noteUUID}, toc, {section: {}});
} else {
app.insertNoteContent({uuid: noteUUID}, toc + "---\n\n");
}
},
},
 
async _insertTextRun(app, usesOrderedTOC) {
const sections = await app.getNoteSections({uuid: app.context.noteUUID});
const toc = this._getTOCFromSections(sections, usesOrderedTOC);
const replacedSelection = await app.context.replaceSelection("\n" + toc);
return null;
},
 
insertText: {
"Ordered": {
check(app) {
return app.settings["Ordered TOC Expression Name"] || "otoc";
},
run(app) {
return this._insertTextRun(app, true);
},
},
 
"Unordered": {
check(app) {
return app.settings["Unordered TOC Expression Name"] || "utoc";
},
run(app) {
return this._insertTextRun(app, false);
},
},
}
}