");
$ols[$ols.length - 1].append($ol);
$ols.push($ol);
}
} else if (levelDelta < 0) {
// Close as many lists as curLevel - newLevel
// be careful not to empty $ols completely, the root element should stay (could happen with a rogue h1 element)
for (let i = 0; i < -levelDelta && $ols.length > 1; ++i) {
$ols.pop();
}
}
curLevel = newLevel;
//
// Create the list item and set up the click callback
//
const headingText = $("").html(m[2]).text();
const $li = $('
- ').text(headingText);
$li.on("click", () => this.jumpToHeading(headingIndex));
$ols[$ols.length - 1].append($li);
headingCount = headingIndex;
}
return {
$toc,
headingCount
};
}
async jumpToHeading(headingIndex) {
// 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 isReadOnly = await this.noteContext.isReadOnly();
if (isReadOnly) {
const $container = await this.noteContext.getContentElement();
const headingElement = $container.find(":header")[headingIndex];
if (headingElement != null) {
headingElement.scrollIntoView({ behavior: "smooth" });
}
} else {
const textEditor = await this.noteContext.getTextEditor();
const model = textEditor.model;
const doc = model.document;
const root = doc.getRoot();
const headingNode = findHeadingNodeByIndex(root, headingIndex);
// headingNode could be null if the html was malformed or
// with headings inside elements, just ignore and don't
// navigate (note that the TOC rendering and other TOC
// entries' navigation could be wrong too)
if (headingNode != null) {
$(textEditor.editing.view.domRoots.values().next().value).find(':header')[headingIndex].scrollIntoView({
behavior: 'smooth'
});
}
}
}
async closeTocCommand() {
this.noteContext.viewScope.tocTemporarilyHidden = true;
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
}
async entitiesReloadedEvent({loadResults}) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributes().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'toc')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}
}
/**
* Find a heading node in the parent's children given its index.
*
* @param {Element} parent Parent node to find a headingIndex'th in.
* @param {uint} headingIndex Index for the heading
* @returns {Element|null} Heading node with the given index, null couldn't be
* found (ie malformed like nested headings, etc.)
*/
function findHeadingNodeByIndex(parent, headingIndex) {
let headingNode = null;
for (let i = 0; i < parent.childCount; ++i) {
let child = parent.getChild(i);
// Headings appear as flattened top level children in the CKEditor
// document named as "heading" plus the level, eg "heading2",
// "heading3", "heading2", etc. and not nested wrt the heading level. If
// a heading node is found, decrement the headingIndex until zero is
// reached
if (child.name.startsWith("heading")) {
if (headingIndex === 0) {
headingNode = child;
break;
}
headingIndex--;
}
}
return headingNode;
}
class CloseTocButton extends OnClickButtonWidget {
constructor() {
super();
this.icon("bx-x")
.title("Close TOC")
.titlePlacement("bottom")
.onClick((widget, e) => {
e.stopPropagation();
widget.triggerCommand("closeToc");
})
.class("icon-action close-toc");
}
}