How to open the tiddler just before the current one in the history?

I am using a simpler solution to display the last tiddler above the page controls in the sidebar,

last-tiddler.json (378 Bytes)

\define title-string() "title": "
\define end-title() "
<$link to={{{ [{$:/HistoryList}split<title-string>splitbefore<end-title>removesuffix<end-title>reverse[]is[tiddler]nth[2]] }}}/>

But it has the same “Problem” you are experiencing, using the control panel settings button is not triggering an update of the history.

This has lead me to realised that this is because buttons that open tiddlers do so outside the list or navigator widget that normally happens.

I think the answer may lie in

  • using the MessageCatcherWidget to trap navigation events triggered by page controls or wrapping the page controls in $:/core/ui/PageTemplate/pagecontrols
    • A quick addition of story="$:/StoryList" history="$:/HistoryList" to that list did not work.

Further research needed but this almost deserves an issue raised in GitHub for enhancement in future versions of tiddlywiki.

  • I observe lists inside the sidebar tabs do make use of the history (unlike buttons), this is using the tabs macro ## $:/core/macros/tabs
  • I also note the use of history=<<tv-history-list>> storyview="pop" inside the side bar open tab $:/core/ui/SideBar/Open should we somehow do this with the page controls or must we do it in each button that opens a tiddler?

I think that I saw that $:/HistoryList does get updated. For me the behavior happens even if I have the control panel tiddler open and I click on the tab and then the tab of another tiddler. Then my shortcut navigates to a random tiddler instead of the control panel.
Regarding raising issues in github, I’m afraid I’ll be out of my depth if I try to explain what you just wrote…

Are you sure that your target variable is being correctly assigned? Parsing JSON with string operators is rather brittle. My guess is that the is[tiddler] filter step is excluding $:/ControlPanel since it is a shadow tiddler.

Try this:

\define title-string() "title": "
\define end-title() "

<$navigator story="$:/StoryList" history="$:/HistoryList">
	<$let target={{{ [{$:/HistoryList}split<title-string>splitbefore<end-title>removesuffix<end-title>reverse[]nth[2]] }}}>
		<$log target=<<target>> />
		<$action-navigate $to=<<target>> />
	</$let>
</$navigator>

Also, use $action-log instead of $log inside action strings to ensure that the logging only happens when the button is clicked.

@saqimtiaz as I elucidated in my earlier post, as far as I can see the control panel button simple does not use the standard story or history list. This is the same for Tag manager and Tiddler Manager which are Navigating buttons.

That is our code is working and any tiddler opened within a SideBar tab is detected and the listory list updated. So too are new tiddlers (only once saved) and links from tiddlers in the story.

A quick way to demonstrate is to install my history tab, and keep it open in the sidebar, or return to it after following a link in another sidebar tab. history-sidebar.json (1.3 KB)

Also my last tiddler that displays the $:/HistoryList!!currentTiddler field if clicked to returns to the last focused tiddler using the link widget also gets added to the history. last-tiddler.json (378 Bytes)

So it appears that the $:/core/ui/Buttons/control-panel tiddler using the following;
<$button to="$:/ControlPanel" is just not updating the History.

Seems like an oversight or bug!

can raise an issue

@TW_Tones I am trying to assist @Ittayd in debugging his code as he has requested. I don’t see what relevance your comment about control panel buttons has here. Please post a separate thread if you believe there is some underlying issue.

The use of the is[tiddler] filter step in the code being used for that shortcut makes it impossible for that filter expression to return $:/ControlPanel or any other shadow tiddler as a result unless it has been overridden.

OK but I have detected the same fault outside his code. Debug if you wish but I am not just bullshitting or wasting my time.

Thank you. So is there a way to select either a tiddler, a system tiddler or a shadow tiddler?

Have you tried this updated code that I posted?

\define title-string() "title": "
\define end-title() "

<$navigator story="$:/StoryList" history="$:/HistoryList">
	<$let target={{{ [{$:/HistoryList}split<title-string>splitbefore<end-title>removesuffix<end-title>reverse[]nth[2]] }}}>
		<$log target=<<target>> />
		<$action-navigate $to=<<target>> />
	</$let>
</$navigator>

I am not sure that you need to check if the tiddler exists as a shadow or real tiddler, the only situation in which not doing so could be problematic is where the previous tiddler has been deleted.

Ahh, sorry, yes I did. I added a ‘!is[missing]’ because otherwise it would navigate to draft tiddlers if my last action was an edit. It doesn’t work. Note that I do see the $:/ControlPanel in $:/HistoryList but the log message shows it was ignored.

Try adding !is[draft] instead of !is[missing]
Also use $action-log instead of $log to only log when the actions are invoked.

\define title-string() "title": "
\define end-title() "

<$navigator story="$:/StoryList" history="$:/HistoryList">
	<$let target={{{ [{$:/HistoryList}split<title-string>splitbefore<end-title>removesuffix<end-title>!is[draft]reverse[]nth[2]] }}}>
		<$action-log target=<<target>> />
		<$action-navigate $to=<<target>> />
	</$let>
</$navigator>

Perfect! Thank you very much

To early to declare victory. If I edit a tiddler, finish and then use the shortcut, the draft tiddler is opened (because it’s now a missing tiddler). And, thinking about it, I would like to be able to navigate to a draft tiddler (e.g. editing it, want to lookup information in another, then come back to editing)

Is there an option to do an ‘or’ between operators? Then I’d do or[is[tiddler] is[draft] is[shadow]]

I ended up creating a ‘tabbed’ filter operator. Works OK, except for the use case I mentioned. That is, if I’m on a ‘foo’ tiddler and start editing, then the tiddler before the current one is ‘foo’ (current one is ‘draft of foo’. Need to figure out how to filter out tiddlers that have an open draft… I guess the best is if the operator can use the story list to find only tiddlers that are open.

Ended up creating an ‘instory’ filter operator. Here it is:

/*\
title: $:/customization/tabbed.js
type: application/javascript
module-type: filteroperator

Filter function for [instory[]]

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
	
var DEFAULT_STORY_TITLE = "$:/StoryList";


/*
Export our filter function
*/
exports.instory = function(source,operator,options) {
	var currStoryList = $tw.wiki.getTiddlerList(DEFAULT_STORY_TITLE);
	var storyFilter = $tw.utils.stringifyList(currStoryList);	
	var storyList = $tw.wiki.filterTiddlers(storyFilter)
	
	var results = [];
	if(operator.prefix === "!") {
		source(function(tiddler,title) {
			if (!storyList.includes(title)) {
				results.push(title);
			}
		});
	} else {
		source(function(tiddler,title) {
			if (storyList.includes(title)) {
				results.push(title);
			}
			
		});
	}
	return results;
};

})();

And while I’m at it, here’s a filter to get the history titles directly

/*\
title: $:/customization/history.js
type: application/javascript
module-type: filteroperator

Filter function for [history[]]

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
	
var DEFAULT_HISTORY_TITLE = "$:/HistoryList";


/*
Export our filter function
*/
exports.history = function(source,operator,options) {
	var historyList = options.wiki.getTiddlerDataCached(DEFAULT_HISTORY_TITLE,[])
	
	return historyList.map(item => item.title);
};

})();
2 Likes

The filter to get the history titles directly is a very good idea.

For the rest, below are some ideas that might be helpful:

Only retain titles that are real tiddlers or shadow tiddlers:

\define removeNonExistentTiddlers() [<currentTiddler>is[tiddler]then[yes]] [<currentTiddler>is[shadow]then[yes]]

[<history>filter<removeNonExistentTiddlers>]

Extending the above to only keep the titles that are in the story list:

\define removeNonExistentTiddlers() [<currentTiddler>is[tiddler]then[yes]] [<currentTiddler>is[shadow]then[yes]]

[<history>filter<removeNonExistentTiddlers>] :intersection[list[$:/StoryList]]

Note that it might be possible to replace the hardcoded $:/StoryList with the variable tv-story-list. Also you may not need to check if the title is a real or shadow tiddler if you only want titles that are in the story.

1 Like

UPDATE: Turns out that there are a few quirks to work around, so the code below will not work reliably. See my next post for a better solution.

Another approach that I have used successfully ist to order the Story List by the (reverse) History List – this way you’ll get a list of the open tiddlers, but sorted by their last viewed order.
I use it in a modified “Close” button in zoomin view (closing a tiddler shows the previously viewed open tiddler).
The code is rather straightforward:

\define reverse-history-list-filter() [<tv-history-list>get[text]splitregexp[\n]reverse[]unique[]trim[]removeprefix["title": "]removesuffix["]search-replace:g[\"],["]is[tiddler]]

<$set name="reverseHistoryList" filter=<<reverse-history-list-filter>> >
	<$let previousItem={{{ [list<tv-story-list>sortby<reverseHistoryList>nth[2]] }}}>
		<$action-navigate $to=<<previousItem>> />
	</$let>
</$set>

The parsing is a bit different from above solutions. It’s important to have reverse[] precede unique[], because unique[] will keep the first instance it finds and ignores all subsequent ones.
The search-replace[] is for cases where there’s a quotation mark in the title, which will get escaped (") in the history list.
If you wanted to suppress drafts, you’d ideally do it in the $let filter.
Have a nice day
Yaisog

PS: The is[tiddler] check is actually unnecessary here, because the history is only used for sorting a list of open and existing tiddlers. However, I use the same filter in a “History” button showing the last 30 opened tiddlers, where I do have to check, because of renames and deletions:

<$button popup=<<qualify "$:/state/popup/history">> class="tc-btn-invisible mwi-history-button" tooltip="Show tiddler history">
	{{$:/mwi/images/history}}
</$button>
<$reveal type="popup" state=<<qualify "$:/state/popup/history">> position="belowleft">
  <div class="tc-drop-down" >
	<h2 class="mwi-history-title">Tiddler-History</h2>
	<ol class="tc-toc">
	  <$list filter="[subfilter<reverse-history-list-filter>!is[draft]butfirst[]limit[30]]">
		<li>
		  <$link>
			<$let fieldName={{{ [all[current]is[system]then[title]] ~[all[current]has[caption]then[caption]else[title]] }}}>
			  <$view field=<<fieldName>> />
			</$let>
		  </$link>
		</li>
	  </$list>
	</ol>
  </div>
</$reveal>

PPS: With @Ittayd’s history[] filter, I guess this could be shortened to:

[history[]reverse[]unique[]search-replace:g[\"],["]]

so I’ll likely adopt that filter into my TW. For maximum flexibility I reckon it should evaluate <tv-history-list> instead of hard-coding the history tiddler title, though.
Not sure about the search-replace still being necessary, need to check.

2 Likes

I discovered some quirks that made the code in the previous post unreliable. Also, don’t parse the $:/HistoryList with WikiText filters, but use @Ittayd’s history[] filter above. I did change a line in the filter code to be able to pass an alternative history tiddler:

var historyTiddler = (operator.operand || "$:/HistoryList");
var historyList = options.wiki.getTiddlerDataCached(historyTiddler,[])

The code segment for the Close button becomes

<$set name="historyList" filter="[history<tv-history-list>reverse[]unique[]reverse[]]" >
	<$let previousItem={{{ [list<tv-story-list>sortby<historyList>reverse[]nth[2]] }}}>
		<$action-navigate $to=<<previousItem>> />
	</$let>
</$set>

The filter for historyList contains two reverse[] operators, because unique[] keeps only the first occurence, but we need each last one. Also, the sort-list needs to be in forward order, because sortby[] puts items that are not on this list up front. So after the then following reverse[] in the previousItem filter, they will be at the back AND the list will be sorted in reverse chronological order as we want it.

The list for the history button is also more straightforward with the history[] filter:

<$list filter="[history[]reverse[]unique[]is[tiddler]!is[draft]butfirst[]limit[30]]">

And there is no longer a need for the reverse-history-list-filter() definition.
Have a nice day
Yaisog

@Yaisog thanks for the follow up post.

I discovered

also, in my own wikitext parsing, but some of them may affect the history operator as well, but I have not looked closely at it yet. However I have uncovered the following;

The history list is added to at navigation or editing of a tiddler, so without targeted filtering, it will not only list both the tiddler AND its draft, but also the titles of now deleted or titles prior to a rename, ie the original title.

This does confound the use of history in this way but it also promises the possibility of some interesting functionality that may arise from this, especially if the history record was modified to include additional information. I could see this being done to reduce the potential problems I identified however once done, could then go one to do other interesting things such as I speculate now;

  • Ability to enable a rename to be reversed.
  • gather activity statistics from or in addition to the history

I have also speculated about keeping the history list across wiki loads, even a separate “Full History”, or in a single session declaring a new “historical Epoch”. For example one history for each project and more.

TiddlyWIki is always stimulating our imaginations.