Scroll after tiddler has been rendered

I’m trying to adapt a macro I found that allows to link to anchors. The critical part is this:

<$action-navigate $to=<<tiddler>> $scroll="yes"/>
<$action-sendmessage $message="tm-scroll" selector=<<selector>>

The first action is to navigate to the tiddler with the anchor link and then the second action is to scroll the anchor link to view.

The problem is that action-navigate doesn’t end when the tiddler has been loaded and added to the dom (rather, it just adds the tiddler to $:/StoryList so when the wiki is rendered, it will be rendered, that happens later). So when searching for the anchor link (through documet#querySelector), nothing is found and so nothing is scrolled.

Is there a way to run the second action only after the story list has been rendered?

Sort of, kinda…

I’d use @EricShulman’s $action-timeout action widget with a suitable delay. It’s not ideal, it’s not synchronous and may “miss” depending on prevailing network conditions. But you can get pretty close to a 99% satisfactory solution.

Search Search results for '$action-timeout' - Talk TW

1 Like

Hmmm, maybe I’ll create an $action-hook and listen to the post render hook

Hi @Ittayd you may be interested to have a look at TWPub which I worked on last year for @xcazin. It includes an implementation of multiple story rivers, and navigating to anchors within tiddlers.

It implements most of the functionality of the navigator widget entirely in wikitext. To make the scrolling work, I prototyped a new <$historytracker> widget (see the source). After rendering its children, and when the $:/History tiddler changes, it scrolls to the current tiddler.

There’s a lot of code there. I’m happy to answer questions about it, it sounds like you are exploring similar territory

Intriguing. Certainly sounds like a proper solution to OP.

Thanks. Is the functionality in the twpub-tools plugin? If so, how can install only it?

I’ve implemented action-hook and here it is FWIW:

/*\
title: action-hook.js
type: application/javascript
module-type: widget
 
$action-hook invokes actions after a hook has been triggered
Can be persistent or one time
\*/
 
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
	
let autoId = 0;
	
var Widget = require("$:/core/modules/widgets/widget.js").widget;
	
var HookWidget= function(parseTreeNode,options) {
	this.initialise(parseTreeNode,options);
};
 
HookWidget.prototype = new Widget();
 
HookWidget.prototype.render = function(parent,nextSibling) {
	this.computeAttributes();
	this.execute();
};
	
HookWidget.prototype.execute = function() {
	this.id       = this.getAttribute("id", autoId++);
	this.clear     = this.getAttribute("clear",   "");
	this.persistent     = this.getAttribute("persistent",   "");
	this.name = this.getAttribute("name", "");
	this.actions   = this.getAttribute("actions", "");
	if (this.getAttribute("autostart")) this.invokeAction();
};
	
HookWidget.prototype.refresh = function(changedTiddlers) {
	var changedAttributes = this.computeAttributes();
	
	if(Object.keys(changedAttributes).length > 0) { 
		this.refreshSelf(); return true;
	}
	
	return this.refreshChildren(changedTiddlers);
};
	
HookWidget.prototype.allowActionPropagation = function() { 	
	return false; 
};
 
HookWidget.prototype.functionDefinition = function(event) {
	const func = new Function('widget', 'actions', 'event', 
     `return function hook_${this.id}(){widget.invokeActionString(actions, widget, event)}`
  )(this, this.actions, event);
	func.id = this.id;
	return func;
}
 
HookWidget.prototype.removeHook = function() {
	const self = this
	const p = $tw.hooks.names[this.name].findIndex(f => f.id == self.id)
	if (p != -1) {
		$tw.hooks.names[this.name].splice(p, 1);
	}
}
	
HookWidget.prototype.invokeAction = function(triggeringWidget,event) {
	const self = this;
	const func = function() {
		self.invokeActionString(self.actions, self, event)
		if(!self.persistent) {
			self.removeHook();
		}
	}
	func.id = this.id
	
	if (this.clear) {
		this.removeHook()
	} else {
		$tw.hooks.addHook(this.name, func)
	}
		
	return true; // Action was invoked
};
	
exports["action-hook"] = HookWidget;
})();

TWPub includes quite a few core customisations that are highly likely to break other plugins and customisations. As you’ve discovered, trying to do navigation in wikitext cannot be done satisfactorily without extensions to the core, so I used TWPub as a testbed for exploring possible approaches. It also includes a lot of code to handle the display of EPUBs that have been converted using a separate tool provided as a Node.js app. So I think you’d be better off picking some techniques from TWPub. I’m happy to discuss the core modifications we need for doing this.