mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-28 18:42:28 +08:00
chore(client/ts): port toc
This commit is contained in:
parent
d901a0f787
commit
d0317f4bb6
@ -77,6 +77,7 @@ export type CommandMappings = {
|
|||||||
searchString?: string;
|
searchString?: string;
|
||||||
ancestorNoteId?: string | null;
|
ancestorNoteId?: string | null;
|
||||||
};
|
};
|
||||||
|
closeTocCommand: CommandData;
|
||||||
showLaunchBarSubtree: CommandData;
|
showLaunchBarSubtree: CommandData;
|
||||||
showOptions: CommandData & {
|
showOptions: CommandData & {
|
||||||
section: string;
|
section: string;
|
||||||
@ -206,6 +207,8 @@ export type CommandMappings = {
|
|||||||
zoomFactor: string;
|
zoomFactor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reEvaluateRightPaneVisibility: CommandData;
|
||||||
|
|
||||||
// Geomap
|
// Geomap
|
||||||
deleteFromMap: { noteId: string },
|
deleteFromMap: { noteId: string },
|
||||||
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
|
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
|
||||||
@ -266,6 +269,9 @@ type EventMappings = {
|
|||||||
reEvaluateHighlightsListWidgetVisibility: {
|
reEvaluateHighlightsListWidgetVisibility: {
|
||||||
noteId: string | undefined;
|
noteId: string | undefined;
|
||||||
};
|
};
|
||||||
|
reEvaluateTocWidgetVisibility: {
|
||||||
|
noteId: string | undefined;
|
||||||
|
};
|
||||||
showHighlightsListWidget: {
|
showHighlightsListWidget: {
|
||||||
noteId: string;
|
noteId: string;
|
||||||
};
|
};
|
||||||
@ -301,7 +307,10 @@ type EventMappings = {
|
|||||||
};
|
};
|
||||||
refreshNoteList: {
|
refreshNoteList: {
|
||||||
noteId: string;
|
noteId: string;
|
||||||
}
|
};
|
||||||
|
showToc: {
|
||||||
|
noteId: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EventListener<T extends EventNames> = {
|
export type EventListener<T extends EventNames> = {
|
||||||
|
@ -33,6 +33,14 @@ export interface ViewScope {
|
|||||||
readOnlyTemporarilyDisabled?: boolean;
|
readOnlyTemporarilyDisabled?: boolean;
|
||||||
highlightsListPreviousVisible?: boolean;
|
highlightsListPreviousVisible?: boolean;
|
||||||
highlightsListTemporarilyHidden?: boolean;
|
highlightsListTemporarilyHidden?: boolean;
|
||||||
|
tocTemporarilyHidden?: boolean;
|
||||||
|
/*
|
||||||
|
* The reason for adding tocPreviousVisible is to record whether the previous state of the toc is hidden or displayed,
|
||||||
|
* and then let it be displayed/hidden at the initial time. If there is no such value,
|
||||||
|
* when the right panel needs to display highlighttext but not toc, every time the note content is changed,
|
||||||
|
* toc will appear and then close immediately, because getToc(html) function will consume time
|
||||||
|
*/
|
||||||
|
tocPreviousVisible?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateLinkOptions {
|
interface CreateLinkOptions {
|
||||||
|
1
src/public/app/types.d.ts
vendored
1
src/public/app/types.d.ts
vendored
@ -239,6 +239,7 @@ declare global {
|
|||||||
},
|
},
|
||||||
getData(): string;
|
getData(): string;
|
||||||
setData(data: string): void;
|
setData(data: string): void;
|
||||||
|
sourceElement: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MentionItem {
|
interface MentionItem {
|
||||||
|
@ -18,8 +18,9 @@ import attributeService from "../services/attributes.js";
|
|||||||
import RightPanelWidget from "./right_panel_widget.js";
|
import RightPanelWidget from "./right_panel_widget.js";
|
||||||
import options from "../services/options.js";
|
import options from "../services/options.js";
|
||||||
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext, { type EventData } from "../components/app_context.js";
|
||||||
import libraryLoader from "../services/library_loader.js";
|
import libraryLoader from "../services/library_loader.js";
|
||||||
|
import type FNote from "../entities/fnote.js";
|
||||||
|
|
||||||
const TPL = `<div class="toc-widget">
|
const TPL = `<div class="toc-widget">
|
||||||
<style>
|
<style>
|
||||||
@ -54,6 +55,9 @@ const TPL = `<div class="toc-widget">
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
export default class TocWidget extends RightPanelWidget {
|
export default class TocWidget extends RightPanelWidget {
|
||||||
|
|
||||||
|
private $toc!: JQuery<HTMLElement>;
|
||||||
|
|
||||||
get widgetTitle() {
|
get widgetTitle() {
|
||||||
return t("toc.table_of_contents");
|
return t("toc.table_of_contents");
|
||||||
}
|
}
|
||||||
@ -75,7 +79,7 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
if (!super.isEnabled()) {
|
if (!super.isEnabled() || !this.note) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +87,9 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
const isTextNote = (this.note.type === "text");
|
const isTextNote = (this.note.type === "text");
|
||||||
const isNoteSupported = isTextNote || isHelpNote;
|
const isNoteSupported = isTextNote || isHelpNote;
|
||||||
|
|
||||||
return isNoteSupported && !this.noteContext.viewScope.tocTemporarilyHidden && this.noteContext.viewScope.viewMode === "default";
|
return isNoteSupported
|
||||||
|
&& !this.noteContext?.viewScope?.tocTemporarilyHidden
|
||||||
|
&& this.noteContext?.viewScope?.viewMode === "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRenderBody() {
|
async doRenderBody() {
|
||||||
@ -91,12 +97,9 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
this.$toc = this.$body.find(".toc");
|
this.$toc = this.$body.find(".toc");
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote(note) {
|
async refreshWithNote(note: FNote) {
|
||||||
/*The reason for adding tocPreviousVisible is to record whether the previous state of the toc is hidden or displayed,
|
|
||||||
* and then let it be displayed/hidden at the initial time. If there is no such value,
|
this.toggleInt(!!this.noteContext?.viewScope?.tocPreviousVisible);
|
||||||
* when the right panel needs to display highlighttext but not toc, every time the note content is changed,
|
|
||||||
* toc will appear and then close immediately, because getToc(html) function will consume time*/
|
|
||||||
this.toggleInt(!!this.noteContext.viewScope.tocPreviousVisible);
|
|
||||||
|
|
||||||
const tocLabel = note.getLabel("toc");
|
const tocLabel = note.getLabel("toc");
|
||||||
|
|
||||||
@ -106,12 +109,19 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let $toc = "",
|
if (!this.note || !this.noteContext?.viewScope) {
|
||||||
headingCount = 0;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let $toc: JQuery<HTMLElement> | null = null;
|
||||||
|
let headingCount = 0;
|
||||||
|
|
||||||
// Check for type text unconditionally in case alwaysShowWidget is set
|
// Check for type text unconditionally in case alwaysShowWidget is set
|
||||||
if (this.note.type === "text") {
|
if (this.note.type === "text") {
|
||||||
const { content } = await note.getBlob();
|
const blob = await note.getBlob();
|
||||||
({ $toc, headingCount } = await this.getToc(content));
|
if (blob) {
|
||||||
|
({ $toc, headingCount } = await this.getToc(blob.content));
|
||||||
|
}
|
||||||
} else if (this.note.type === "doc") {
|
} else if (this.note.type === "doc") {
|
||||||
const $contentEl = await this.noteContext.getContentElement();
|
const $contentEl = await this.noteContext.getContentElement();
|
||||||
if ($contentEl) {
|
if ($contentEl) {
|
||||||
@ -122,8 +132,13 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$toc.html($toc);
|
if ($toc) {
|
||||||
if (["", "show"].includes(tocLabel?.value) || headingCount >= options.getInt("minTocHeadings")) {
|
this.$toc.append($toc);
|
||||||
|
} else {
|
||||||
|
this.$toc.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (["", "show"].includes(tocLabel?.value ?? "") || headingCount >= (options.getInt("minTocHeadings") ?? 0)) {
|
||||||
this.toggleInt(true);
|
this.toggleInt(true);
|
||||||
this.noteContext.viewScope.tocPreviousVisible = true;
|
this.noteContext.viewScope.tocPreviousVisible = true;
|
||||||
} else {
|
} else {
|
||||||
@ -137,10 +152,10 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
/**
|
/**
|
||||||
* Rendering formulas in strings using katex
|
* Rendering formulas in strings using katex
|
||||||
*
|
*
|
||||||
* @param {string} html Note's html content
|
* @param html Note's html content
|
||||||
* @returns {string} The HTML content with mathematical formulas rendered by KaTeX.
|
* @returns The HTML content with mathematical formulas rendered by KaTeX.
|
||||||
*/
|
*/
|
||||||
async replaceMathTextWithKatax(html) {
|
async replaceMathTextWithKatax(html: string) {
|
||||||
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
|
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
|
||||||
var matches = [...html.matchAll(mathTextRegex)];
|
var matches = [...html.matchAll(mathTextRegex)];
|
||||||
let modifiedText = html;
|
let modifiedText = html;
|
||||||
@ -183,12 +198,12 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
/**
|
/**
|
||||||
* Builds a jquery table of contents.
|
* Builds a jquery table of contents.
|
||||||
*
|
*
|
||||||
* @param {string} html Note's html content
|
* @param html Note's html content
|
||||||
* @returns {$toc: jQuery, headingCount: integer} ordered list table of headings, nested by heading level
|
* @returns ordered list table of headings, nested by heading level
|
||||||
* with an onclick event that will cause the document to scroll to
|
* with an onclick event that will cause the document to scroll to
|
||||||
* the desired position.
|
* the desired position.
|
||||||
*/
|
*/
|
||||||
async getToc(html) {
|
async getToc(html: string) {
|
||||||
// Regular expression for headings <h1>...</h1> using non-greedy
|
// Regular expression for headings <h1>...</h1> using non-greedy
|
||||||
// matching and backreferences
|
// matching and backreferences
|
||||||
const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi;
|
const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi;
|
||||||
@ -200,12 +215,12 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
// Note heading 2 is the first level Trilium makes available to the note
|
// Note heading 2 is the first level Trilium makes available to the note
|
||||||
let curLevel = 2;
|
let curLevel = 2;
|
||||||
const $ols = [$toc];
|
const $ols = [$toc];
|
||||||
let headingCount;
|
let headingCount = 0;
|
||||||
for (let m = null, headingIndex = 0; (m = headingTagsRegex.exec(html)) !== null; headingIndex++) {
|
for (let m = null, headingIndex = 0; (m = headingTagsRegex.exec(html)) !== null; headingIndex++) {
|
||||||
//
|
//
|
||||||
// Nest/unnest whatever necessary number of ordered lists
|
// Nest/unnest whatever necessary number of ordered lists
|
||||||
//
|
//
|
||||||
const newLevel = m[1];
|
const newLevel = parseInt(m[1]);
|
||||||
const levelDelta = newLevel - curLevel;
|
const levelDelta = newLevel - curLevel;
|
||||||
if (levelDelta > 0) {
|
if (levelDelta > 0) {
|
||||||
// Open as many lists as newLevel - curLevel
|
// Open as many lists as newLevel - curLevel
|
||||||
@ -237,7 +252,7 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
$toc = this.pullLeft($toc);
|
$toc = this.pullLeft($toc);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
$toc,
|
$toc: $toc,
|
||||||
headingCount
|
headingCount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -245,7 +260,7 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
/**
|
/**
|
||||||
* Reduce indent if a larger headings are not being used: https://github.com/zadam/trilium/issues/4363
|
* Reduce indent if a larger headings are not being used: https://github.com/zadam/trilium/issues/4363
|
||||||
*/
|
*/
|
||||||
pullLeft($toc) {
|
pullLeft($toc: JQuery<HTMLElement>) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const $children = $toc.children();
|
const $children = $toc.children();
|
||||||
|
|
||||||
@ -264,7 +279,11 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
return $toc;
|
return $toc;
|
||||||
}
|
}
|
||||||
|
|
||||||
async jumpToHeading(headingIndex) {
|
async jumpToHeading(headingIndex: number) {
|
||||||
|
if (!this.note || !this.noteContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// A readonly note can change state to "readonly disabled
|
// A readonly note can change state to "readonly disabled
|
||||||
// temporarily" (ie "edit this note" button) without any
|
// temporarily" (ie "edit this note" button) without any
|
||||||
// intervening events, do the readonly calculation at navigation
|
// intervening events, do the readonly calculation at navigation
|
||||||
@ -286,26 +305,28 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async closeTocCommand() {
|
async closeTocCommand() {
|
||||||
this.noteContext.viewScope.tocTemporarilyHidden = true;
|
if (this.noteContext?.viewScope) {
|
||||||
|
this.noteContext.viewScope.tocTemporarilyHidden = true;
|
||||||
|
}
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||||
appContext.triggerEvent("reEvaluateTocWidgetVisibility", { noteId: this.noteId });
|
appContext.triggerEvent("reEvaluateTocWidgetVisibility", { noteId: this.noteId });
|
||||||
}
|
}
|
||||||
|
|
||||||
async showTocWidgetEvent({ noteId }) {
|
async showTocWidgetEvent({ noteId }: EventData<"showToc">) {
|
||||||
if (this.noteId === noteId) {
|
if (this.noteId === noteId) {
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async entitiesReloadedEvent({ loadResults }) {
|
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||||
if (loadResults.isNoteContentReloaded(this.noteId)) {
|
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) {
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
} else if (
|
} else if (
|
||||||
loadResults
|
loadResults
|
||||||
.getAttributeRows()
|
.getAttributeRows()
|
||||||
.find((attr) => attr.type === "label" && (attr.name.toLowerCase().includes("readonly") || attr.name === "toc") && attributeService.isAffecting(attr, this.note))
|
.find((attr) => attr.type === "label" && ((attr.name ?? "").toLowerCase().includes("readonly") || attr.name === "toc") && attributeService.isAffecting(attr, this.note))
|
||||||
) {
|
) {
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user