Using toc-list macro to show a "flat" list of a toc

Here’s my TiddlyTools <<toc-list>> macro (see https://tiddlytools.com/#TiddlyTools%2FTOC), which walks a TOC tree structure in the manner you have described (“depth-first traversal”) and produces a “flat list” output of links to all tiddlers in the TOC:

\define toc-list(here,max,exclude,level:"1",field:"tags")
<!-- SHOW FLAT LIST -->
<$list filter="""[contains:$field$[$here$]sortby{$here$!!list}] -[subfilter<__exclude__>] -[[$here$]]""">
   <$text text="[["/><<currentTiddler>><$text text="]]"/><br>
   <$reveal default="$level$" type="nomatch" text="$max$">
      <$macrocall $name="toc-list" here=<<currentTiddler>> max="$max$" exclude="""$exclude$ [[$here$]]""" level={{{ [[$level$]add[1]] }}} field=<<__field__>>/>
   </$reveal>
</$list>
\end

Notes:

  • Only the here parameter is required, where here is the top-level starting tag.
    Example: <<toc-list "TableOfContents">>
  • max (optional) allows you to limit the depth of traversal.
    Example: <<toc-list here:"TableOfContents" max:3>>
  • exclude (optional) can be used to provide a filter that ignores specified tiddlers. It is also used internally to prevent infinite recursive loops by ignoring any tiddlers that have already been visited in the current TOC tree branch.
  • level is used internally (along with max) to track the current depth.
  • field:fieldname (optional, default=“tags”) allows you to define the tree structure using a field other than “tags” (e.g., field:parent)
  • You can use <$wikify name="flatlist" text="<<toc-list TableOfContents>>">...</$wikify> to capture the output into a variable that can then be used via [enlist<flatlist>] to perform operations on the resultant tiddler list (e.g., previous/next navigation via <$link to={{{ [enlist<flatlist>before<currentTiddler>] }}}>previous</$link> and <$link to={{{ [enlist<flatlist>after<currentTiddler>] }}}>next</$link>

The macro can also be simplified a bit by removing the handling for max, level and field parameters, like this:

\define toc-list(here,exclude)
<$list filter="""[tag[$here$]sortby{$here$!!list}] -[subfilter<__exclude__>] -[[$here$]]""">
   <$text text="[["/><<currentTiddler>><$text text="]]"/><br>
   <$macrocall $name="toc-list" here=<<currentTiddler>> exclude="""$exclude$ [[$here$]]"""/>
</$list>
\end

enjoy,
-e

2 Likes

Thanks @EricShulman you have documented how to achieve what I was asking for, however I was aware of this approach, and was wondering if the taggingtree filter operator could return the flat list, making it a lot simpler. It is so close to doing this already and with 5.3.0 it could be used in a function.

  • Until taggingtree has this output order, the way you show is the only way to achive this so I am sure it will help others finding this thread by search.

Hi Eric, I like the new version of toc-list as it has no textual substitution of parameter and support other fields alongside with tags.

\define toc-list(here,max,exclude,level:"0",field:"tags")
<$list filter="[<__level__>!match<__max__>]">
<$let here={{{ [<__here__>!match[]else<currentTiddler>] }}} sortby={{{ [<here>get[list]] }}}>
<$set name="excluded" filter="[subfilter<__exclude__>] [<here>]">
<$set name="items" filter="[contains:$field$<here>!has[draft.of]sortby<sortby>] -[enlist<excluded>]">
<$list filter="[enlist<items>] :filter[list<here>] [enlist<items>!list<here>]" variable="item">
	<$text text="[["/><<currentTiddler>><$text text="]]"/><br>
	<$macrocall $name="toc-list" here=<<currentTiddler>> max=<<__max__>> exclude=<<excluded>> level={{{ [<__level__>add[1]] }}} field=<<__field__>>/>
</$list>
\end

I have tried it on tiddlywiki.com using

<<toc-list TableOfContents>>

but it retuens many [[0]]. Would you please have a look?

@Mohammad

Good catch! IT’S A BUG IN MY CODE!!! There were TWO problems:

The first line of the toc-list() macro:

<$list filter="[<__level__>!match<__max__>]">

checks for “maximum tree depth”, but had the side-effect of setting “currentTiddler” to the value of <__level__> (which is initially “0”). I’ve changed this to:

<$list filter="[<__level__>!match<__max__>]" variable=none>

and…

These two lines

<$text text="[["/><<currentTiddler>><$text text="]]"/><br>
<$macrocall $name="toc-list" here=<<currentTiddler>> max=<<__max__>> exclude=<<excluded>> level={{{ [<__level__>add[1]] }}} field=<<__field__>>/>

were using <<currentTiddler>> when then SHOULD have been using <<item>>. I’ve changed them to:

<$text text="[["/><<item>><$text text="]]"/><br>
<$macrocall $name="toc-list" here=<<item>> max=<<__max__>> exclude=<<excluded>> level={{{ [<__level__>add[1]] }}} field=<<__field__>>/>

The result of these two errors was that instead of recursively outputting the tiddler titles, it was outputting the level number (as [[0]]) for each item at the first level of the “TableOfContents” tree (for which there are 13 tiddlers, and thus 13 [[0]] were displayed.

I’ve fixed the code as shown above and uploaded it to https://tiddlytools.com/#TiddlyTools%2FTOC.

much thanks for your help in finding this bug!

enjoy,
-e

2 Likes

Hi Eric,
Much appreciated for the prompt reply and the quick fixes. The toc-list works now as expected.

TOC is really powerful, specially for some of its unique features:

  • Drag-and-drop handling for re-organizing the tree hierarchy
  • Auto-open tree branches
  • Auto-scrolling tree view
  • Sequential previous/next navigation through all tiddlers within a Table of Contents tree

Best wishes
Mohammad

What? Does this mean TOC can work like VSCode’s sidebar folder menu, auto open active branch? Discussed in Possible to auto open a title in TOC? · Issue #4 · kookma/TW-TOC · GitHub

Well, when you open a tiddler by the next/previous button the TOC in the sidebar automatically opens the branch and highlights the nodes!
I did not try the new code to see if a node (tiddler) is opened by other means it will open the branch or not!

TiddlyTools/TOC <<toc-tree>>'s “auto-open” feature relies upon the value in $:/HistoryList!!current-tiddler, so it should work as long opening a tiddler updates the $:/HistoryList!!current-tiddler value. This should be the case for any action that uses tm-navigate, including clicking on links directly embedded in tiddler content.

Note, however, that if some custom code directly changes the title list contained in $:/StoryList!!list but does not also update the $:/HistoryList!!current-tiddler value, then the <<toc-tree>> display won’t auto-open nor highlight the “current” tiddler.

Here’s some code you can try on https://TiddlyWiki.com to illustrate the different handling:

Direct link to [[Other Tiddler]] (missing)

<$button> tm-navigate open of "Other Tiddler"
<$action-navigate $to="Other Tiddler"/>
</$button>

<$button> test custom open of "Other Tiddler"
<$action-setfield $tiddler="$:/StoryList" $field="list" $value={{{ [[Other Tiddler]] [list[$:/StoryList]] +[format:titlelist[]join[ ]] }}}/>
</$button>

<$button> close "Other Tiddler"
<$action-sendmessage $message="tm-close-tiddler" $param="Other Tiddler"/>
</$button>

$:/StoryList!!list = <blockquote>{{$:/StoryList!!list}}</blockquote>
$:/HistoryList!!current-tiddler = <blockquote>{{$:/HistoryList!!current-tiddler}}</blockquote>

enjoy,
-e

Thank you for the clarification Eric.