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.

Hello,

First of all, thank you so much for sharing this useful tool.

I noticed two confusing behaviours of toc-list when using customized field as toc field:

  1. When the indexed tiddler have blankspace in tittle, the macro would return a link named [[title]], which leads to a missing tiddler
  2. It would return all tiddlers listed in the list field, even though list is not the customized field.

For reference, I randomly added reply-to field to some tiddlers, and tested with the following code:

<<toc-all here:"FieldsWidget" field:"reply-to">>

<<toc-list here:"FieldsWidget" field:"reply-to">>

and the result is:

As you can see, the links have different behaviour with toc-all and toc-list.

I’m not sure if these are intended behaviour. I would like you to be aware of them nevertheless.

TiddlyTools <<toc-all>> (which is a special case of <<toc-tree>>) automatically shows the caption field of each tiddler but links to the actual tiddler title values. This is consistent with the handling in the TWCore’s <<toc-*>> macros, which also show the captions but link to the titles.

The <<toc-caption>> macro, defined in the $:/core/macros/toc shadow tiddler, looks like this:

\define toc-caption()
\whitespace trim
<span class="tc-toc-caption tc-tiny-gap-left">
<$set name="tv-wikilinks" value="no">
  <$transclude field="caption">
    <$view field="title"/>
  </$transclude>
</$set>
</span>
\end

If you want to always show the actual tiddler title, you can define a modified version of <<toc-caption>> that precedes your use of <<toc-all>> (or <<toc-tree>>) and replaces

  <$transclude field="caption">
    <$view field="title"/>
  </$transclude>

with just

    <$view field="title"/>

In contrast, <<toc-list>> always outputs the actual title value of each tiddler. It is left up to the calling wikitext context to decide how to handle that list of titles, either by showing/linking to them “as-is”, or by showing the caption text but linking to the title value.

So, while it might not be “expected”, it is the “intended” behavior.

Thank you for your explanation. I know understand why the links in toc-all and toc-list are different.

However, I still don’t understand why there are extra [[]] add to the outside of title by toc-link where there are blankspace in the title. As a result of this behaviour, those links are not pointing to the actual tiddler with title title. Instead they are pointing to [[title]], which is different from the tiddler indexed.

The output of <<toc-list>> uses the TWCore standard “titlelist” format, which automatically surrounds titles that contain spaces inside [[ and ]]. This allows you to use the enlist filter operator to process the list of titles. If the square brackets were not present, then each WORD of a title that contains spaces would be processed as a separate tiddler title.

For example:

<$wikify name=titles text="<<toc-list roottag>>">
<$list filter="[enlist<titles>]"><$link/><br></$list>
</$wikify>

The $wikify widget “captures” the <<toc-list>> output in a variable, which is then used as a parameter for the enlist filter operator to iterate over the titles and output a link to each.

-e

1 Like

I see. Thank you for your patient explanation!

Regarding the second “confusing behavior”:

Both the TiddlyTools TOC macros (<<toc-tree>> and <<toc-list>>) and the standard TWCore TOC macros use the list field to apply a manually defined sort order in which child tiddlers are shown in the tree (in contrast to the default alpha-sorted order that is used for “unlisted” child tiddlers).

The list field is also used by the TiddlyTools <<toc-tree>> macro to handle drag-and-drop re-arrangment of tree items, which allows interactive redefinition of parent-child relationships as well as “sibling re-ordering”. Dropping an item on a parent makes it a child of the item it is dropped on. This drag-and-drop behavior is also affected by “modifier” keys, where alt-drop creates a child copy of an item, and ctrl-drop or shift-drop moves an item before or after the item it is dropped on (i.e., as a sibling of the item it is dropped on).

The list field WAS also being used to allow the creation of “top down” tree definitions, where each parent could contain an explicit list of their respective children, as compared to usual “bottom up” tree definitions, where each child has a field (default tags) that contains a list of one or more parent tiddlers.

However… as you’ve correctly pointed out… this produced unexpected results by conflating the top down list field handling and the “sort order” list field handling.

Although top down tree definitions are still interesting, the implementation (which was essentially an undocumented experiment) was, at best, flawed… and been temporarily REMOVED from the TiddlyTools/TOC code until such time as I can come up with a better approach to adding this functionality.

You can get the latest updated code here: TiddlyTools/TOC

-e

1 Like