@@ -15,6 +15,7 @@ import {
1515 hasChildHeading ,
1616 collapseHeading ,
1717 expandHeading ,
18+ toggleAllHeadings ,
1819} from "./toggleCollapse" ;
1920
2021export async function renderHeader (
@@ -135,60 +136,60 @@ export async function renderHeader(
135136export 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