Streams plugin: how to add a custom new node template?

@vuk, you shouldn’t allow yourself to be intimidated with this particular modification :slight_smile: if you make a mistake you can always delete and get back to the original!

I’d be happy to post more of my version of the code later if it would be helpful for your context, @etardiff will probably note how convoluted it is and recommend refinements :stuck_out_tongue_winking_eye:

In my case I set the fieldname object-type=stream-item and avoid poluting the tag space.

I also set the streams config item “Template wikified for node title” to $:/s/<<now "[UTC]YY-0MM-0DD 0hh:0mm:0ss 0XXX">> this places all streams items behind the system prefix.

I am currently building a view template tiddler, that on detecting the parent field, locates the root parent and provides a link to that. In the case of streams this will be the streams root.

  • I am now looking at displaying an icon when the parent is a system tiddler (The titles are all but unreadable) and idealy with a mouse over preview each stream item in the tree.

So far I only managed to create an edit toolbar button that inserts the wikitext template into the stream node. It looks like this:

<$action-sendmessage
	$message="tm-edit-text-operation"
	$param="replace-all"
	text={{Stream node template!!text}}
/>

Here Stream node template is the tiddler that contains the actual template. Unfortunately I can only use it if I enable the editor toolbar for stream node editor in the plugin. This eats a lot of screen space, which is especially painful on smartphone, but at least it works.

I would probably create a new action macro that could be triggered from the context menu

I had a look at the first FAQ entry and the example tiddler $:/streams/contextmenu/flatten, I can make a new context menu appear by cloning this tiddler, but I’ve yet to figure how to implement its event handler. I don’t know how to reference the stream node tiddler from there. Simply copying the edit toolbar button code from above did not work.

@vuk, I’m happy to help you with this some, as possible :slight_smile: Could I perhaps see your Stream node template, so I can understand what you are trying to achieve?

I can tell you that you will not be able to trigger an action-sendmessage in this way. $:/plugins/sq/streams/templates/stream-row-body is the tiddler you would want to modify in order to apply conditions to the streams view.

For example, in my $:/plugins/sq/streams/templates/stream-row-body I have the following statement:

<$list filter="[all[current]tag[concept]] [all[current]tag[element]] [all[current]tag[reaction]] [all[current]tag[setting]] [all[current]tag[description]] [all[current]tag[character]] [all[current]tag[Notes]]">
  {{||$:/config.template}}
</$list>

Roughly translated, for all tiddlers that have the tags concept, element, reaction, setting, description, character, or Notes, transclude these contents through the $:/config.template tiddler.

The contents of that template are:

  \define structure.title()
  [[$(structure)$|$(parent)$]]
  \end 

  \define title.title()
  [[$(title)$|$(cover)$]]
  \end 

  \define editable()
  $(turtle)$.editable
  \end


  <$set name= structure value={{!!structure}}>
  <$set name= title value={{!!caption1}}>
  <$set name= turtle value={{!!title}}>
  <$set name= cover value={{!!cover}}>
  <$set name= parent value={{!!parent}}>

<$list filter="[all[current]![search[$:/config.template]]">

  <!-- This line transcludes the tiddler content:  -->
  <$transclude tiddler=<<currentTiddler>> mode="block"/>

  <$reveal type="nomatch" state=<<editable>> text="edit">
    <$button set=<<editable>> setTo="edit" class="tc-btn-invisible">
      {{!!pagenum}}
    </$button>
  </$reveal>
  <$reveal type="match" state=<<editable>> text="edit">
    <$edit-text tiddler=<<currentTiddler>> field="pagenum" tag="input" default="" placeholder="Enter page number" class="input-small"/>
    <$button set=<<editable>> setTo="" class="tc-btn-invisible">
      {{$:/core/images/done-button}}
    </$button>
  </$reveal>, //<<structure.title>>,// <<title.title>>. <$set name="tempStateTiddler" value=<<qualify "$:/temp/showtags/"+<<currentTiddler>>> 
    <$button>
      {{$:/core/images/tag-button}}
      <$action-setfield $tiddler=<<tempStateTiddler>> text="yes"/>
    </$button>

    <$reveal type="match" state=<<tempStateTiddler>> text="yes">
      <$edit-text tiddler=<<currentTiddler>> field="tags" tag="input"/>
      <$button>
        Done 
        <$action-setfield $tiddler=<<tempStateTiddler>> text="no"/>
      </$button>
    </$reveal>
  </$set>
  <br><br>
</$list>

And the result is:

which renders some of the field values of those stream-list tiddlers as a reference at the bottom of the text, which you can see here:

If you are trying to make a viewtemplate that applies to all streams, I would recommend using a field that all streams (created conventionally, you’ll note that my tiddler does not have that field because it was processed by AI) have such as [all[current]field:stream-type[default]]

I have never played with $:/streams/contextmenu/flatten

I have rather made extensive modifications to $:/plugins/sq/streams/contextmenu/contextmenu-template

This will be embarrassing for me, because I’m sure there is some more-artful way to add items to the context-menu that @saqimtiaz might enlighten us both on :wink: However, this has been my approach to adding new macro triggers to the context menu and it has worked well:


Within $:/plugins/sq/streams/contextmenu/contextmenu-template, there is a definition for common-menu-items. These are items that you want to appear on every streams context menu, and you see that I have added several of my own, including the yadda-yadda macro for the WikiSage plugin:



\define common-menu-items()
            <$macrocall $name="menu-item" title="Open" actions="""<$action-navigate/>"""/>
            <$macrocall $name="menu-item" title="Rename" subtitle="((streams-rename))" actions=<<rename-node-actions>>/>
            <$macrocall $name="menu-item" title="Add Description" subtitle="((add-description-actions))" actions=<<add-description-actions>>/>
						<$macrocall $name="menu-item" title="Mark as Stub" subtitle="((mark-stub))" actions=<<addStubTag>>/>
						<$macrocall $name="menu-item" title="Copy Title" subtitle="((copy-link))" actions="""<<copy-link>>/>"""/>
						<$macrocall $name="menu-item" title="Create Reference" subtitle="((create-link))" actions="""<<add-new-link>>/>"""/>
						<$macrocall $name="menu-item" title="Transclude as Note" subtitle="((transclude-config))" actions="""<<transcludeAsNote>>/>"""/>
            <$macrocall $name="menu-item" title="Delete" subtitle="((streams-delete))" actions=<<delete-node-actions>>/>
						<$macrocall $name="menu-item" title="Add to Daily Notes" subtitle="((transclude-tiddler))" actions=<<create-transclusion-and-append-to-daily-journal>>/>
						<$macrocall $name="menu-item" title="Edit Content" subtitle="" actions="""<<clarify-content-streams>>"""/>
						<$macrocall $name="menu-item" title="Yadda-yadda" subtitle="((yadda-yadda))" actions="""<<yadda-yadda-streams>>"""/>
						<$macrocall $name="menu-item" title="send to calendar" subtitle="" actions="""<<.addToCalendarActions>>"""/>
						\end

Now I’m absolutely sure that there is a more artful way of doing this, however, in my break-shit-fast methodology I have directly modified the $:/plugins/sq/streams/action-macros tiddler to add all action macros that I may need:

\define mark-idea-actions()
<$action-listops $tiddler=<<currentTiddler>> $field="tags" $subfilter="Idea"/>
<$action-setfield $tiddler=<<currentTiddler>> description="{{!!text}}"/>
\end

A fun note about modifying the $:/plugins/sq/streams/contextmenu/contextmenu-template is that you can create different context menus depeneding on different conditions. For example, I have:

        <$list filter="[<currentTiddler>tag[Idea]]" variable="ideaCheck">
            <$macrocall $name="menu-item" title="Create Topic" subtitle="((create-topic))" actions=<<create-topic-actions>>/>
            <$macrocall $name="menu-item" title="Remove Idea Tag" subtitle="((remove-idea-tag))" actions=<<removeIdeaTag>>/>
            <$macrocall $name="menu-item" title="Adopt Index" subtitle="((adopt-index))" actions=<<adopt-index-actions>>/>
            <$macrocall $name="menu-item" title="Add Description" subtitle="((add-description))" actions=<<add-description-actions>>/>
            <$macrocall $name="menu-item" title="Recaption" subtitle="((recaption))" actions=<<recaption-actions>>/>
            <$macrocall $name="menu-item" title="Add Alias" subtitle="((add-alias))" actions=<<add-alias-actions>>/>
						<$macrocall $name="menu-item" title="Upgrade to Index" subtitle="((mark-index))" actions=<<mark-index-actions>>/>
            <<common-menu-items>>
        </$list>

What this says is that for all tiddlers with the Tag Idea, the context menu will include Idea-tiddler-specific items in addition to the common-menu-items seen above.

I can, from the context menu, tag a thing Idea, and then open the context menu again and see Idea-specific actions

Could I perhaps see your Stream node template, so I can understand what you are trying to achieve?

So far it only contains “foo”, it’s a placeholder. I don’t want a custom viewtemplate, if I understand you correctly. As I wrote initially, all I want is to have a custom tiddler - Stream node template in this example - that contains a snippet of wikitext. Static wikitext, not parametrized. And every time I create a new stream node - I want that text prefilled into the stream node text field edit, automatically (now I do it manually, via the edit toolbar button, as explained above).

Why I need this? Typing “foo” in every new node isn’t hard, but I want to use a more complex template of course. Something collapsible like DetailsWidget Plugins — Utilities for TiddlyWiki . I want every new stream node to be prefilled with something like

<$details summary="Node head">
Node body
</$details>

This way I could not only collapse nodes in the tree (functionality provided by streams), but also collapse node content(text) and display only the first line of it (the headline, or title, as one prefers to name it), thus saving more screen space and achieving a visual look closer to a classic outline, which holds node heads in a visual tree structure and displays node bodies in another pane.

I don’t know the DetailsWidget, but yes, you could achieve that with the viewtemplate method. If your viewtemplate were

<$details summary={{!!description}} open="yes">

Content will be immediately visible if open is set to "yes".

</$details>

 <$transclude tiddler=<<currentTiddler>> mode="block"/>

For example, then any time you create a node that meets the conditions, its text would appear, in view mode, with the details widget above it with a unique summary based on that tiddler’s description field (it could be any field though).

In this case I would add an action macro to the context menu that would allow me to input a description from the stream.

I actually do that with my recaption macro

\define recaption-actions()
<$action-withinput message="Enter new caption for this tiddler:" default={{!!caption}} actions="""
    <$action-setfield $tiddler=<<currentTiddler>> caption=<<userInput>>/>
"""/>
\end

I realize on reflection that prior to the current method I am using (which I still highly recommend, speaking from experience :slight_smile: ) I actually did my templates in the way I understand you are trying to achieve, and, if I am understanding you correct, I do not understand why you are having trouble.



In my wiki, whatever tiddler is in this edit text box becomes the text of any new stream node that is generated by clicking Enter to create a new stream (but not if you click the new node).

It’s very possible that this is something I’ve modified along the way, as I notice in my instance that Tiddler to use as new node template appears twice in the plugin menu.

Let me look into that.

EDIT – Nope, looks to me like $:/config/sq/streams/new-node-tiddler-template is controlled the same as in the original plugin. Add the tiddler title (try as a string with no spaces) to that tiddler and whatever text or wikitext is in that tiddler will be the basis of new tiddlers when you create them using enter rather than pushing the button

Add the tiddler title (try as a string with no spaces) to that tiddler and whatever text or wikitext is in that tiddler will be the basis of new tiddlers when you create them using enter rather than pushing the button

I can reproduce this. A tiddler title with spaces works as well.

I wanted the edit field prefilled in either scenario. With an empty tiddler, there’s only that (+) at the bottom, which creates the edit for the new stream node. Until now I kept trying that and getting an text area that is empty. While typing inside it and hitting Enter to create the next edit, that edit indeed isn’t empty, it contains the text of my custom template tiddler. I don’t know if this is better or worse - a broken toy is a broken toy - at least the things are clear. A half-broken toy is even more confusing - while if I hit Enter things work as expected, I’m still confused about how to get the same result when I use the (+) button (without having the edit toolbar enabled and pushing the custom button). What you tried to explain above about templates is too complex for me - I’ve never wrote a template yet, this is more complex than the basic things I’m currently learning. It may take a while until I will attempt to dive into the source code of streams to understand how it works.

I know you said you didn’t want a ViewTemplate, but IMO the absolute easiest and most efficient way to do this is by editing $:/plugins/sq/streams/templates/stream-row-body, which is the template used to render nodes when they’re not being edited. Here’s a demo — note the details marker.

Unfolded:

The summary is automatically generated from the first line of text; everything after the first linebreak becomes the body of the details element. Single-line nodes (like the first node under Filters) will be normally displayed.

And here you can see there’s no special code needed in the node text.

Here’s the code I used: $__plugins_sq_streams_templates_stream-row-body.json (537 Bytes)

<$let
	summary={{{ [{!!text}splitregexp[\n]!is[blank]] }}}
	text={{{ [{!!text}removeprefix<summary>trim[]] }}}
>
<$list filter="[<text>!match[]]"
	variable="NULL"
	emptyMessage="<$transclude mode=block />"
>
	<details>
		<summary><<summary>></summary>
		<$list filter="[<text>]"><$transclude field=title mode=block /></$list>
	</details>
</$list>
</$let>

I highly recommend using a template like this over hard-coding a details widget into every streams node you create: it will avoid a lot of redundant code and make it much easier to update if you should decide you want a different format in the future. Happy to help you modify the template if you have other display needs.

1 Like

I know you said you didn’t want a ViewTemplate

Don’t get me wrong, I did’t try to avoid the template approach out of just being stubborn, I was trying to avoid using a technique I’m not yet familiar with. But apparently my practical needs are pushing me towards that direction.

However, I spent a couple more hours reading through the streams code and ended in the $:/plugins/sq/streams/action-macros hacking on the add-node-actions macro. I’ve got to the

<$action-createtiddler $basetitle=<<new-node>> text=<<__startText__>> $template={{$:/config/sq/streams/new-node-tiddler-template}} parent=<<parent>> stream-type="default" stream-list=<<__streamList__>>/>

part and changed the startText reference to my custom template code. This gave me that template code in the node edit textarea, no matter how I would create new streams nodes. However, then I realized that the add-node-actions is called many times in that tiddler by other macros, is called with differed arguments, which means that I broke its functionality. So all those wasted hours at least brought me the confidence that I was solving an X-Y problem.

Now with me morally ready to go the template way, I wouldn’t want to turn this into “just copy/paste and forget it”.

It took me a while to understand the

summary={{{ [{!!text}splitregexp[\n]!is[blank]] }}}

part, as in why a filter produces only one item instead of a whole list, without explicitly using something like
limit[]. I think I found the answer here Those Pesky Brackets — ...and other wikitext punctuation in example 2 and ultimayely here https://tiddlywiki.com/#Filtered%20Attribute%20Values

But I’ve yet to understand the point of NULL variable, which is never used explicitly.

Don’t worry, no one understands null, even its inventor. :smiling_imp:

$list widgets are generally used to iterate over a set of items that are identified by the syntax contained in the filter="..." parameter. For each item, the $list widget sets the “currentTiddler” variable to contain the current item value so that the wikitext within the body of the $list widget can to refer to <<currentTiddler>> in order to display or act upon each list item.

However, this can sometimes be a problem if the wikitext needs to also refer to the title of the tiddler in which the $list widget itself occurs. To avoid this, the $list widget allows you to declare a different variable name to be used to hold the current list item value, which leaves the value of <<currentTiddler>> unchanged.

But… in addition to iterating over a set of items, the $list widget syntax can also be used as a kind of conditional test where it results in either 0 or 1 items being returned by the filter. If no items are returned, then the wikitext within the $list body is not processed, but if an item IS returned then the $list body is processed. In this situation, it usually doesn’t matter what variable the $list widget uses, so the default “currentTiddler” variable is typically set, but NOT referenced.

However, as noted above, it is sometimes still necessary to reference the title of the tiddler in which the $list widget itself occurs, so we don’t want to change the value of <<currentTiddler>> and need to add the variable="..." syntax to avoid this. But that variable isn’t really needed within the $list body, so any name will do. Some people use variable="null" for this purpose. Within my own code, I personally prefer using variable=none. Also note that I omit the quotes around the variable name, as they are not required for the syntax to be parsed correctly.

Hope this explanation is clear…

enjoy,
-e

1 Like

This is a very reasonable stance, and I don’t want to undermine it; I took a pretty similar trajectory myself. That’s part of the reason why I do spend so much time encouraging people to consider a template; the tiddlers I made early on contained a lot of repetitive HTML and other styling/templating content, and it was tedious to remove it all once I had learned more efficient techniques.

I’d alluded to this issue with changing startText in my first response, but in retrospect I wasn’t particularly clear about the specific pitfalls. I’m glad you were able to work through the problem to your satisfaction; I’m sorry you had to spend those hours investigating!

You’re spot-on in your conclusions about filtered attribute values. If you were using {{{ [{!!text}splitregexp[\n]!is[blank]] }}} as stand-alone wikitext, you would need to add first[] or nth[1] to the end of the filter in order to restrict the results to the first line only.

In fact, you could also use in it summary={{{ [{!!text}splitregexp[\n]!is[blank]first[]] }}} without affecting the results. It’s not necessary from a functional perspective, but I encourage you to do so if it makes the code easier for you to parse at a glance; it shouldn’t add any meaningful lag to the rendering process.

@EricShulman’s already provided an excellent explanation of the theory behind variable="NULL", so I’ll just add that it’s not strictly necessary in the code I suggested. Since I defined the <<summary>> and <<text>> variables outside the $list, those values won’t change even if the value of <<currentTiddler>> changes.

However, if you did want to include a field transclusion (let’s say {{!!modified}}) in your $list template, you’d want to ensure that you were seeing the value of that node’s modified field, not the modified field of the (likely nonexistent) tiddler whose title corresponds to the value of <<text>>. And in that case, you’d want <<currentTiddler>> to retain its original value (i.e., the title of the node tiddler). So I included variable="NULL" as a bit of future-proofing, to minimize potential issues if you decided to further expand on the template.

2 Likes

However, this can sometimes be a problem if the wikitext needs to also refer to the title of the tiddler in which the $list widget itself occurs.

Based on the template snippet above, is it correct to think that this usually happens in context of nested lists, because there’s a clash between items from either in the <<currentTiddler>> variable, and the workaround is the outer list defining another variable, to isolate the contexts? Or is there the presence of a nested list not necessary, and there still are situations when this custom variable might be needed?

As a bit of a side note… You’ll see a lot of similar uses of $list as a conditional test in older TW content, but if you’re using TW 5.3.2+, the <% if %> conditional shortcut syntax may be a more semantic alternative. Here’s an alternate version of the same template using conditional shortcuts instead:

<$let
	summary={{{ [{!!text}splitregexp[\n]!is[blank]] }}}
	text={{{ [{!!text}removeprefix<summary>trim[]] }}}
>
<% if [<text>!match[]] %>
	<details>
		<summary><<summary>></summary>
		<$list filter="[<text>]"><$transclude field=title mode=block /></$list>
	</details>
<% else %>
	<$transclude mode=block />
<% endif %>
</$let>

You can see that I’m not setting a “placeholder” variable here, and that’s because <% if %> does so automatically: it assigns the first result of the filter to the variable <<condition>>, which you can use inside the conditional as needed (or ignore if it’s unnecessary in your template code). This means that <<currentTiddler>> will retain whatever value it had outside the conditional without any special effort on your part.

In some ways, this is a more elegant solution, and I did consider it. But in testing my code on the Streams site, I realized that the Streams wiki itself is still using v. 5.2.2, so I opted for the more “old-fashioned” approach. Digging a little further, I now realize that the plugin itself requires >=5.1.23, which predates the introduction of the $let widget… so I suppose if I really wanted to cover my bases, I should have used the older $vars widget instead.

You’re right that we generally assign $list variables to avoid <<currentTiddler>> clashes, though they don’t always happen in the context of nested lists at the tiddler-text level: you might want to retain your ability to transclude field values from the current tiddler inside a single $list. Of course, once you start digging around in the $:/core, $lists are responsible for a great deal of the TiddlyWiki UI (including each tiddler in the story river), so in a way we’re always dealing with nested lists.

I also find it’s sometimes worth it to assign a variable to a list even in circumstances where I don’t mind <<currentTiddler>> changing simply because it lets me use that <<variableName>> to reference the current list-item inside the $list template — even when <<currentTiddler>> or its synonym {{!!title}} would technically do the job.

For instance, the following…

<$list filter="[[Lewis Carroll]] [[Terry Pratchett]] [[Isaac Asimov]]" variable=author>

!! <<author>>
<<list-links "[tag[Book]author<author>]">>
</$list>

… may be clearer at a glance than this simpler code, which produces the same results:

<$list filter="[[Lewis Carroll]] [[Terry Pratchett]] [[Isaac Asimov]]">

!! {{!!title}}
<<list-links "[tag[Book]author{!!title}]">>
</$list>
1 Like

In the core we use variable="ignore" for these type of variable names. It is not perfect but consistently used that way.

In the core we generally use quotes around variable values for two reasons:

A) Consistency and
B) It is less error-prone if users do copy paste core code for their own use-cases

Sometimes users need values, that contain spaces. If the core code does not contain quotes they think we can handle “values with spaces” without the quotes – Which is an error.

1 Like

The future proof way to do this in terms of making future upgrades easier is to use a separate tiddler with the tag $:/tags/streams/user-actionmacros, see Streams — on TiddlyWiki 5.2.2

1 Like