Skip to content

Commit 098f27e

Browse files
committed
增加一键折叠按钮,优化大文档加载性能
修改复制标题功能为复制TOC目录 优化大文档目录生成效率 当目录pin的时候编辑器缩进会参考浮动目录实际宽度。
1 parent 42c6d42 commit 098f27e

File tree

5 files changed

+552
-417
lines changed

5 files changed

+552
-417
lines changed

build/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "floating-toc",
33
"name": "floating toc",
4-
"version": "2.5.2",
4+
"version": "2.5.3",
55
"minAppVersion": "0.14.0",
66
"description": "This is a floating Toc plugin that hovers a table of content containing a header level on the notes sidebar.",
77
"author": "Cuman",

src/components/floatingtocUI.ts

Lines changed: 151 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
hasChildHeading,
1616
collapseHeading,
1717
expandHeading,
18+
toggleAllHeadings,
1819
} from "./toggleCollapse";
1920

2021
export async function renderHeader(
@@ -135,60 +136,60 @@ export async function renderHeader(
135136
export async function createLi(
136137
plugin: FloatingToc,
137138
view: MarkdownView,
138-
ul_dom: HTMLElement,
139+
ul_dom: HTMLElement |DocumentFragment,
139140
heading: HeadingCache,
140141
index: number
141142
) {
142143
// 检查是否是大型文档
143-
const isLargeDocument = plugin.headingdata && plugin.headingdata.length > 100;
144+
// const isLargeDocument = plugin.headingdata && plugin.headingdata.length > 100;
144145
let li_dom = ul_dom.createEl("li");
145146
li_dom.addClass("heading-list-item");
146-
li_dom.setAttribute("data-level", heading.level.toString());
147+
li_dom.setAttribute("data-level", heading?.level?.toString() ?? "");
147148
li_dom.setAttribute("data-id", index.toString());
148-
li_dom.setAttribute("data-line", heading.position.start.line.toString());
149+
li_dom.setAttribute("data-line", heading?.position?.start?.line?.toString() ?? "");
149150
let text_dom = li_dom.createEl("div");
150151
text_dom.addClass("text-wrap");
151152

152-
if (isLargeDocument) {
153+
// if (isLargeDocument) {
153154

154-
// 对于大型文档,使用简化的渲染方式以提高性能
155-
// 直接创建文本元素而不使用Markdown渲染
156-
const textEl = text_dom.createEl("a");
157-
textEl.addClass("text");
158-
textEl.textContent = heading.heading;
155+
// // 对于大型文档,使用简化的渲染方式以提高性能
156+
// // 直接创建文本元素而不使用Markdown渲染
157+
// const textEl = text_dom.createEl("a");
158+
// textEl.addClass("text");
159+
// textEl.textContent = heading.heading;
159160

160-
// 添加点击事件
161-
textEl.onclick = function(event: MouseEvent) {
162-
event.stopPropagation();
163-
const startline = heading.position.start.line;
161+
// // 添加点击事件
162+
// textEl.onclick = function(event: MouseEvent) {
163+
// event.stopPropagation();
164+
// const startline = heading.position.start.line;
164165

165-
if (event.ctrlKey || event.metaKey) {
166-
foldHeader(view, startline);
167-
} else {
168-
openFiletoline(view, startline);
166+
// if (event.ctrlKey || event.metaKey) {
167+
// foldHeader(view, startline);
168+
// } else {
169+
// openFiletoline(view, startline);
169170

170-
const prevLocation = ul_dom.querySelector(".text-wrap.located");
171-
if (prevLocation) {
172-
prevLocation.removeClass("located");
173-
}
174-
text_dom.addClass("located");
175-
}
176-
};
171+
// const prevLocation = ul_dom.querySelector(".text-wrap.located");
172+
// if (prevLocation) {
173+
// prevLocation.removeClass("located");
174+
// }
175+
// text_dom.addClass("located");
176+
// }
177+
// };
177178

178-
// 检查是否有子标题
179-
if (hasChildHeading(index, plugin.headingdata)) {
180-
li_dom.setAttribute("isCollapsed", "false");
179+
// // 检查是否有子标题
180+
// if (hasChildHeading(index, plugin.headingdata)) {
181+
// li_dom.setAttribute("isCollapsed", "false");
181182

182-
// 添加折叠/展开点击事件
183-
li_dom.addEventListener("click", (e: MouseEvent) => {
184-
e.stopPropagation();
185-
toggleCollapse(e, li_dom, plugin.settings.expandAllSubheadings);
186-
});
187-
}
188-
} else {
183+
// // 添加折叠/展开点击事件
184+
// li_dom.addEventListener("click", (e: MouseEvent) => {
185+
// e.stopPropagation();
186+
// toggleCollapse(e, li_dom, plugin.settings.expandAllSubheadings);
187+
// });
188+
// }
189+
// } else {
189190
// 对于标题数量较少的情况,使用完整的Markdown渲染
190-
await renderHeader(plugin, view, heading.heading, text_dom, view.file.path, null);
191-
}
191+
await renderHeader(plugin, view, heading.heading, text_dom, view.file.path, null);
192+
192193

193194
let line_dom = li_dom.createEl("div");
194195
line_dom.addClass("line-wrap");
@@ -265,79 +266,15 @@ export function creatToc(app: App, plugin: FloatingToc): void {
265266
else floatingTocWrapper.removeClass("alignLeft");
266267
let ul_dom = floatingTocWrapper.createEl("ul");
267268
ul_dom.addClass("floating-toc");
268-
let toolbar = ul_dom.createEl("div");
269-
toolbar.addClass("toolbar");
270-
toolbar.addClass("pin");
271-
toolbar.addClass("hide");
272-
let pinButton = new ButtonComponent(toolbar);
273-
pinButton
274-
.setIcon("pin")
275-
.setTooltip("pin")
276-
.onClick(() => {
277-
if (floatingTocWrapper.classList.contains("pin"))
278-
floatingTocWrapper.removeClass("pin");
279-
else floatingTocWrapper.addClass("pin");
280-
});
281-
ul_dom.onmouseenter = function () {
282-
//移入事件
283-
toolbar.removeClass("hide");
284-
floatingTocWrapper.addClass("hover");
285-
};
286-
ul_dom.onmouseleave = function () {
287-
//移出事件
288-
toolbar.addClass("hide");
289-
floatingTocWrapper.removeClass("hover");
290-
};
291-
let topBuuton = new ButtonComponent(toolbar);
292-
topBuuton
293-
.setIcon("double-up-arrow-glyph")
294-
.setTooltip("Scroll to Top")
295-
.setClass("top")
296-
.onClick(() => {
297-
const view =
298-
this.app.workspace.getActiveViewOfType(MarkdownView);
299-
if (view) {
300-
view.setEphemeralState({ scroll: 0 });
301-
}
302-
});
303-
let bottomBuuton = new ButtonComponent(toolbar);
304-
bottomBuuton
305-
.setIcon("double-down-arrow-glyph")
306-
.setTooltip("Scroll to Bottom")
307-
.setClass("bottom")
308-
.onClick(async () => {
309-
const view =
310-
this.app.workspace.getActiveViewOfType(MarkdownView);
311-
if (view) {
312-
313-
314-
const file = this.app.workspace.getActiveFile()
315-
const content = await (this.app as any).vault.cachedRead(file);
316-
const lines = content.split('\n');
317-
let numberOfLines = lines.length;
318-
//in preview mode don't count empty lines at the end
319-
if (view.getMode() === 'preview') {
320-
while (numberOfLines > 0 && lines[numberOfLines - 1].trim() === '') {
321-
numberOfLines--;
322-
}
323-
}
324-
view.currentMode.applyScroll((numberOfLines - 1))
325-
326-
327-
}
328-
});
329-
let CopyBuuton = new ButtonComponent(toolbar);
330-
CopyBuuton.setIcon("copy")
331-
.setTooltip("copy to clipboard")
332-
.setClass("copy")
333-
.onClick(async () => {
334-
let headers = plugin.headingdata.map((h: HeadingCache) => {
335-
return " ".repeat(h.level - 1) + h.heading;
336-
});
337-
await navigator.clipboard.writeText(headers.join("\n"));
338-
new Notice("Copied");
339-
});
340-
269+
270+
271+
let toolbar = floatingTocWrapper.createEl("div");
272+
273+
// 创建 toolbar 的内容移到这里
274+
createToolbar(plugin, toolbar as HTMLElement, floatingTocWrapper as HTMLElement);
275+
276+
277+
341278
if (plugin.settings.ignoreHeaders) {
342279
let levelsToFilter = plugin.settings.ignoreHeaders.split("\n");
343280
plugin.headingdata = app.metadataCache
@@ -371,6 +308,7 @@ export function creatToc(app: App, plugin: FloatingToc): void {
371308
loadingIndicator.style.top = "45px";
372309
// 渲染初始批次
373310
initialBatch.forEach((heading: HeadingCache, index: number) => {
311+
374312
createLi(plugin, view, ul_dom, heading, index);
375313
});
376314

@@ -382,9 +320,10 @@ export function creatToc(app: App, plugin: FloatingToc): void {
382320
const batchSize = 20; // 每批处理的标题数量
383321

384322
const renderNextBatch = () => {
323+
385324
// 确定这一批次要渲染的数量
386325
const batchEndIndex = Math.min(nextIndex + batchSize, totalHeadings);
387-
326+
388327
// 更新加载指示器
389328
loadingIndicator.textContent = `加载中... (${batchEndIndex}/${totalHeadings})`;
390329

@@ -421,7 +360,7 @@ export function creatToc(app: App, plugin: FloatingToc): void {
421360

422361
// 执行渲染
423362
renderHeadings();
424-
363+
425364
currentleaf
426365
?.querySelector(".markdown-source-view")
427366
?.insertAdjacentElement("beforebegin", floatingTocWrapper) ||
@@ -444,7 +383,107 @@ export function creatToc(app: App, plugin: FloatingToc): void {
444383
if (plugin.settings.isDefaultPin)
445384
floatingTocWrapper.addClass("pin");
446385
genToc(view.contentEl, floatingTocWrapper);
386+
plugin.updateTocWidth(floatingTocWrapper as HTMLElement, plugin.headingdata);
447387
} else return;
448388
}
449389
}
450390
}
391+
392+
export function createToolbar(plugin: FloatingToc, toolbar: HTMLElement, float_toc_dom: HTMLElement) {
393+
toolbar.addClass("toolbar");
394+
395+
toolbar.addClass("hide");
396+
397+
let pinButton = new ButtonComponent(toolbar);
398+
pinButton
399+
.setIcon("pin")
400+
.setTooltip("pin")
401+
.onClick(() => {
402+
if (float_toc_dom.classList.contains("pin"))
403+
float_toc_dom.removeClass("pin");
404+
else float_toc_dom.addClass("pin");
405+
});
406+
407+
let topBuuton = new ButtonComponent(toolbar);
408+
topBuuton
409+
.setIcon("double-up-arrow-glyph")
410+
.setTooltip("Scroll to Top")
411+
.setClass("top")
412+
.onClick(() => {
413+
const view =
414+
this.app.workspace.getActiveViewOfType(MarkdownView);
415+
if (view) {
416+
view.setEphemeralState({ scroll: 0 });
417+
}
418+
});
419+
let bottomBuuton = new ButtonComponent(toolbar);
420+
bottomBuuton
421+
.setIcon("double-down-arrow-glyph")
422+
.setTooltip("Scroll to Bottom")
423+
.setClass("bottom")
424+
.onClick(async () => {
425+
const view =
426+
this.app.workspace.getActiveViewOfType(MarkdownView);
427+
if (view) {
428+
429+
430+
const file = this.app.workspace.getActiveFile()
431+
const content = await (this.app as any).vault.cachedRead(file);
432+
const lines = content.split('\n');
433+
let numberOfLines = lines.length;
434+
//in preview mode don't count empty lines at the end
435+
if (view.getMode() === 'preview') {
436+
while (numberOfLines > 0 && lines[numberOfLines - 1].trim() === '') {
437+
numberOfLines--;
438+
}
439+
}
440+
view.currentMode.applyScroll((numberOfLines - 1))
441+
442+
443+
}
444+
});
445+
let CopyBuuton = new ButtonComponent(toolbar);
446+
CopyBuuton.setIcon("copy")
447+
.setTooltip("copy TOC to clipboard")
448+
.setClass("copy")
449+
.onClick(async () => {
450+
let headers = plugin.headingdata.map((h: HeadingCache) => {
451+
const indent = " ".repeat(h.level - 1);
452+
// 创建 markdown 列表项和双链格式
453+
return `${indent}- [[#${h.heading}]]`;
454+
});
455+
await navigator.clipboard.writeText(headers.join("\n"));
456+
new Notice("TOC Copied");
457+
});
458+
const toggleButton = new ButtonComponent(toolbar)
459+
.setIcon("chevron-down")
460+
.setTooltip("Collapse/Expand all headings")
461+
.setClass("toggle-all")
462+
.onClick(() => {
463+
464+
465+
const isAllExpanded = float_toc_dom.getAttribute("data-all-expanded") === "true";
466+
toggleButton.setIcon(isAllExpanded ? "chevron-right" : "chevron-down");
467+
// 使用新的 toggleAllHeadings 函数
468+
toggleAllHeadings(float_toc_dom as HTMLElement, !isAllExpanded);
469+
470+
// 更新全局状态
471+
float_toc_dom.setAttribute("data-all-expanded", (!isAllExpanded).toString());
472+
473+
474+
475+
});
476+
477+
if (!float_toc_dom.hasAttribute("has-events")) {
478+
float_toc_dom.onmouseenter = function () {
479+
toolbar?.removeClass("hide");
480+
float_toc_dom.addClass("hover");
481+
};
482+
float_toc_dom.onmouseleave = function () {
483+
toolbar?.addClass("hide");
484+
float_toc_dom.removeClass("hover");
485+
};
486+
float_toc_dom.setAttribute("has-events", "true");
487+
}
488+
489+
}

0 commit comments

Comments
 (0)