').html(headingText);
const $li = $("
").append($itemContent)
.on("click", () => this.jumpToHeading(headingIndex));
$ols[$ols.length - 1].append($li);
headingCount = headingIndex;
$previousLi = $li;
}
// Clean up unused entries in tocCollapsedHeadings
for (const key of tocCollapsedHeadings) {
if (!validHeadingKeys.has(key)) {
tocCollapsedHeadings.delete(key);
}
}
$toc = this.pullLeft($toc);
return {
$toc,
headingCount
};
}
/**
* Reduce indent if a larger headings are not being used: https://github.com/zadam/trilium/issues/4363
*/
pullLeft($toc: JQuery) {
while (true) {
const $children = $toc.children();
if ($children.length !== 1) {
break;
}
const $first = $toc.children(":first");
if ($first[0].tagName.toLowerCase() !== "ol") {
break;
}
$toc = $first;
}
return $toc;
}
async jumpToHeading(headingIndex: number) {
if (!this.note || !this.noteContext) {
return;
}
// A readonly note can change state to "readonly disabled
// temporarily" (ie "edit this note" button) without any
// intervening events, do the readonly calculation at navigation
// time and not at outline creation time
// See https://github.com/zadam/trilium/issues/2828
const isDocNote = this.note.type === "doc";
const isReadOnly = await this.noteContext.isReadOnly();
let $container;
if (isReadOnly || isDocNote) {
$container = await this.noteContext.getContentElement();
} else {
const textEditor = await this.noteContext.getTextEditor();
$container = $(textEditor.sourceElement);
}
const headingElement = $container?.find(":header:not(section.include-note :header)")?.[headingIndex];
headingElement?.scrollIntoView({ behavior: "smooth" });
}
async setupCollapsibleHeading($ol: JQuery, $previousLi: JQuery, headingKey: string, tocCollapsedHeadings: Set, validHeadingKeys: Set) {
if ($previousLi && $previousLi.find(".collapse-button").length === 0) {
const $collapseButton = $('');
$previousLi.prepend($collapseButton);
// Restore the previous collapsed state
if (tocCollapsedHeadings?.has(headingKey)) {
$previousLi.addClass("collapsed");
validHeadingKeys.add(headingKey);
} else {
$previousLi.removeClass("collapsed");
}
$collapseButton.on("click", (event) => {
event.stopPropagation();
if ($previousLi.hasClass("animating")) return;
const willCollapse = !$previousLi.hasClass("collapsed");
$previousLi.addClass("animating");
if (willCollapse) { // Collapse
$ol.css("maxHeight", `${$ol.prop("scrollHeight")}px`);
requestAnimationFrame(() => {
requestAnimationFrame(() => {
$ol.css("maxHeight", "0px");
$collapseButton.css("transform", "rotate(-90deg)");
});
});
setTimeout(() => {
$ol.css("maxHeight", "");
$previousLi.addClass("collapsed");
$previousLi.removeClass("animating");
}, 300);
} else { // Expand
$previousLi.removeClass("collapsed");
$ol.css("maxHeight", "0px");
requestAnimationFrame(() => {
$ol.css("maxHeight", `${$ol.prop("scrollHeight")}px`);
$collapseButton.css("transform", "");
});
setTimeout(() => {
$ol.css("maxHeight", "");
$previousLi.removeClass("animating");
}, 300);
}
if (willCollapse) { // Store collapsed headings
tocCollapsedHeadings!.add(headingKey);
} else {
tocCollapsedHeadings!.delete(headingKey);
}
});
}
}
async closeTocCommand() {
if (this.noteContext?.viewScope) {
this.noteContext.viewScope.tocTemporarilyHidden = true;
}
await this.refresh();
this.triggerCommand("reEvaluateRightPaneVisibility");
appContext.triggerEvent("reEvaluateTocWidgetVisibility", { noteId: this.noteId });
}
async showTocWidgetEvent({ noteId }: EventData<"showTocWidget">) {
if (this.noteId === noteId) {
await this.refresh();
this.triggerCommand("reEvaluateRightPaneVisibility");
}
}
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (
loadResults
.getAttributeRows()
.find((attr) => attr.type === "label" && ((attr.name ?? "").toLowerCase().includes("readonly") || attr.name === "toc") && attributeService.isAffecting(attr, this.note))
) {
await this.refresh();
}
}
}