mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-01-10 09:20:26 +00:00
docs: about why history change will reflect on storyview
This commit is contained in:
parent
0d18b25b26
commit
dfa060024e
@ -1,15 +1,15 @@
|
||||
/*\
|
||||
title: $:/core/modules/parsers/wikiparser/rules/blockidentifier.js
|
||||
title: $:/core/modules/parsers/wikiparser/rules/anchor.js
|
||||
type: application/javascript
|
||||
module-type: wikirule
|
||||
|
||||
Use hash as a tag for paragraph, we call it block identifier.
|
||||
Use hash as a tag for paragraph, we call it anchor.
|
||||
|
||||
1. Hash won't change, it can be written by hand or be generated, and it is a ` \^\S+$` string after line: `text ^cb9d485` or `text ^1`, so it can be human readable (while without space), here are the parse rule for this.
|
||||
2. When creating widgets for rendering, omit this hash, so it's invisible in view mode. But this widget will create an anchor to jump to.
|
||||
|
||||
\*/
|
||||
exports.name = "blockid";
|
||||
exports.name = "anchor";
|
||||
exports.types = {inline: true};
|
||||
|
||||
/*
|
||||
@ -17,7 +17,7 @@ Instantiate parse rule
|
||||
*/
|
||||
exports.init = function(parser) {
|
||||
this.parser = parser;
|
||||
// Regexp to match the block identifier
|
||||
// Regexp to match the anchor.
|
||||
// 1. located on the end of the line, with a space before it, means it's the id of the current block.
|
||||
// 2. located at start of the line, no space, means it's the id of the previous block. Because some block can't have id suffix, otherwise id break the block mode parser like codeblock.
|
||||
this.matchRegExp = /[ ]\^(\S+)$|^\^(\S+)$/mg;
|
||||
@ -30,16 +30,16 @@ exports.parse = function() {
|
||||
// Move past the match
|
||||
this.parser.pos = this.matchRegExp.lastIndex;
|
||||
// will be one of following case, another will be undefined
|
||||
var blockId = this.match[1];
|
||||
var blockBeforeId = this.match[2];
|
||||
var anchorId = this.match[1];
|
||||
var anchorBeforeId = this.match[2];
|
||||
// Parse tree nodes to return
|
||||
return [{
|
||||
type: "blockid",
|
||||
type: "anchor",
|
||||
attributes: {
|
||||
id: {type: "string", value: blockId || blockBeforeId},
|
||||
// `yes` means the block is before this node, in parent node's children list.
|
||||
id: {type: "string", value: anchorId || anchorBeforeId},
|
||||
// `yes` means the block that this anchor pointing to, is before this node, both anchor and the block, is in a same parent node's children list.
|
||||
// empty means the block is this node's direct parent node.
|
||||
previousSibling: {type: "string", value: Boolean(blockBeforeId) ? "yes" : ""},
|
||||
previousSibling: {type: "string", value: Boolean(anchorBeforeId) ? "yes" : ""},
|
||||
},
|
||||
children: []
|
||||
}];
|
@ -33,11 +33,11 @@ exports.parse = function() {
|
||||
// Process the link
|
||||
var text = this.match[1],
|
||||
link = this.match[2] || text,
|
||||
blockId = this.match[3] || "";
|
||||
anchor = this.match[3] || "";
|
||||
if($tw.utils.isLinkExternal(link)) {
|
||||
// add back the part after `^` to the ext link, if it happen to has one.
|
||||
if(blockId) {
|
||||
link = link + "^" + blockId;
|
||||
if(anchor) {
|
||||
link = link + "^" + anchor;
|
||||
}
|
||||
return [{
|
||||
type: "element",
|
||||
@ -57,7 +57,7 @@ exports.parse = function() {
|
||||
type: "link",
|
||||
attributes: {
|
||||
to: {type: "string", value: link},
|
||||
toBlockId: {type: "string", value: blockId},
|
||||
toAnchor: {type: "string", value: anchor},
|
||||
},
|
||||
children: [{
|
||||
type: "text", text: text
|
||||
|
59
core/modules/widgets/anchor.js
Normal file
59
core/modules/widgets/anchor.js
Normal file
@ -0,0 +1,59 @@
|
||||
/*\
|
||||
title: $:/core/modules/widgets/anchor.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
An invisible element with anchor id metadata.
|
||||
\*/
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
var AnchorWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
// only this widget knows target info (if the block is before this node or not), so we need to hook the focus event, and process it here, instead of in the root widget.
|
||||
};
|
||||
AnchorWidget.prototype = new Widget();
|
||||
|
||||
AnchorWidget.prototype.render = function(parent,nextSibling) {
|
||||
// Save the parent dom node
|
||||
this.parentDomNode = parent;
|
||||
// Compute our attributes
|
||||
this.computeAttributes();
|
||||
// Execute our logic
|
||||
this.execute();
|
||||
// Create an invisible DOM element with data that can be accessed from JS or CSS
|
||||
this.idNode = this.document.createElement("span");
|
||||
this.idNode.setAttribute("data-anchor-id",this.id);
|
||||
this.idNode.setAttribute("data-anchor-title",this.tiddlerTitle);
|
||||
if(this.before) {
|
||||
this.idNode.setAttribute("data-before","true");
|
||||
}
|
||||
this.idNode.className = "tc-anchor";
|
||||
parent.insertBefore(this.idNode,nextSibling);
|
||||
this.domNodes.push(this.idNode);
|
||||
};
|
||||
|
||||
/*
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
AnchorWidget.prototype.execute = function() {
|
||||
// Get the id from the parse tree node or manually assigned attributes
|
||||
this.id = this.getAttribute("id");
|
||||
this.tiddlerTitle = this.getVariable("currentTiddler");
|
||||
this.previousSibling = this.getAttribute("previousSibling") === "yes";
|
||||
// Make the child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
|
||||
/*
|
||||
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
||||
*/
|
||||
AnchorWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(($tw.utils.count(changedAttributes) > 0)) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
} else {
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
}
|
||||
};
|
||||
|
||||
exports.anchor = AnchorWidget;
|
@ -1,131 +0,0 @@
|
||||
/*\
|
||||
title: $:/core/modules/widgets/blockid.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
An invisible element with block id metadata.
|
||||
\*/
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
var BlockIdWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
// only this widget knows target info (if the block is before this node or not), so we need to hook the focus event, and process it here, instead of in the root widget.
|
||||
this.hookNavigationAddHistoryEvent = this.hookNavigationAddHistoryEvent.bind(this);
|
||||
this.hookNavigatedEvent = this.hookNavigatedEvent.bind(this);
|
||||
$tw.hooks.addHook("th-navigating-add-history",this.hookNavigationAddHistoryEvent);
|
||||
$tw.hooks.addHook("th-navigated",this.hookNavigatedEvent);
|
||||
};
|
||||
BlockIdWidget.prototype = new Widget();
|
||||
|
||||
BlockIdWidget.prototype.removeChildDomNodes = function() {
|
||||
$tw.hooks.removeHook("th-navigating-add-history",this.hookNavigationAddHistoryEvent);
|
||||
$tw.hooks.removeHook("th-navigated",this.hookNavigatedEvent);
|
||||
};
|
||||
|
||||
BlockIdWidget.prototype.render = function(parent,nextSibling) {
|
||||
// Save the parent dom node
|
||||
this.parentDomNode = parent;
|
||||
// Compute our attributes
|
||||
this.computeAttributes();
|
||||
// Execute our logic
|
||||
this.execute();
|
||||
// Create an invisible DOM element with data that can be accessed from JS or CSS
|
||||
this.idNode = this.document.createElement("span");
|
||||
this.idNode.setAttribute("data-block-id",this.id);
|
||||
this.idNode.setAttribute("data-block-title",this.tiddlerTitle);
|
||||
if(this.before) {
|
||||
this.idNode.setAttribute("data-before","true");
|
||||
}
|
||||
this.idNode.className = "tc-block-id";
|
||||
parent.insertBefore(this.idNode,nextSibling);
|
||||
this.domNodes.push(this.idNode);
|
||||
};
|
||||
|
||||
BlockIdWidget.prototype._isNavigateToHere = function(event) {
|
||||
if(!event || !event.toBlockId) return false;
|
||||
if(event.toBlockId !== this.id) return false;
|
||||
if(this.tiddlerTitle && event.navigateTo !== this.tiddlerTitle) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockIdWidget.prototype.hookNavigatedEvent = function(event) {
|
||||
if(!this._isNavigateToHere(event)) return event;
|
||||
var baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document;
|
||||
var element = this._getTargetElement(baseElement);
|
||||
if(element) {
|
||||
// if tiddler is already in the story view, just move to it.
|
||||
this._scrollToBlockAndHighlight(element);
|
||||
} else {
|
||||
var self = this;
|
||||
// Here we still need to wait for extra time after `duration`, so tiddler dom is actually added to the story view.
|
||||
var duration = $tw.utils.getAnimationDuration() + 50;
|
||||
setTimeout(function() {
|
||||
element = self._getTargetElement(baseElement);
|
||||
self._scrollToBlockAndHighlight(element);
|
||||
}, duration);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
BlockIdWidget.prototype.hookNavigationAddHistoryEvent = function(event) {
|
||||
if(!this._isNavigateToHere(event)) return event;
|
||||
event.navigateSuppressNavigation = true;
|
||||
return event;
|
||||
};
|
||||
|
||||
BlockIdWidget.prototype._getTargetElement = function(baseElement) {
|
||||
var selector = "span[data-block-id='"+this.id+"']";
|
||||
if(this.tiddlerTitle) {
|
||||
// allow different tiddler have same block id in the text, and only jump to the one with a same tiddler title.
|
||||
selector += "[data-block-title='"+this.tiddlerTitle+"']";
|
||||
}
|
||||
// re-query the dom node, because `this.idNode.parentNode` might already be removed from document
|
||||
var element = $tw.utils.querySelectorSafe(selector,baseElement);
|
||||
if(!element || !element.parentNode) return;
|
||||
// the actual block is always at the parent level
|
||||
element = element.parentNode;
|
||||
// need to check if the block is before this node
|
||||
if(this.previousSibling && element.previousSibling) {
|
||||
element = element.previousSibling;
|
||||
}
|
||||
return element;
|
||||
};
|
||||
|
||||
BlockIdWidget.prototype._scrollToBlockAndHighlight = function(element) {
|
||||
if(!element) return;
|
||||
// toggle class to trigger highlight animation
|
||||
$tw.utils.removeClass(element,"tc-focus-highlight");
|
||||
// We enable the `navigateSuppressNavigation` in LinkWidget when sending `tm-navigate`, otherwise `tm-navigate` will force move to the title
|
||||
element.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" });
|
||||
element.focus({ focusVisible: true });
|
||||
// Using setTimeout to ensure the removal takes effect before adding the class again.
|
||||
setTimeout(function() {
|
||||
$tw.utils.addClass(element,"tc-focus-highlight");
|
||||
}, 50);
|
||||
};
|
||||
|
||||
/*
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
BlockIdWidget.prototype.execute = function() {
|
||||
// Get the id from the parse tree node or manually assigned attributes
|
||||
this.id = this.getAttribute("id");
|
||||
this.tiddlerTitle = this.getVariable("currentTiddler");
|
||||
this.previousSibling = this.getAttribute("previousSibling") === "yes";
|
||||
// Make the child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
|
||||
/*
|
||||
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
||||
*/
|
||||
BlockIdWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(($tw.utils.count(changedAttributes) > 0)) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
} else {
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
}
|
||||
};
|
||||
|
||||
exports.blockid = BlockIdWidget;
|
@ -150,7 +150,7 @@ LinkWidget.prototype.handleClickEvent = function(event) {
|
||||
this.dispatchEvent({
|
||||
type: "tm-navigate",
|
||||
navigateTo: this.to,
|
||||
toBlockId: this.toBlockId,
|
||||
toAnchor: this.toAnchor,
|
||||
navigateFromTitle: this.getVariable("storyTiddler"),
|
||||
navigateFromNode: this,
|
||||
navigateFromClientRect: { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height
|
||||
@ -181,7 +181,7 @@ Compute the internal state of the widget
|
||||
LinkWidget.prototype.execute = function() {
|
||||
// Pick up our attributes
|
||||
this.to = this.getAttribute("to",this.getVariable("currentTiddler"));
|
||||
this.toBlockId = this.getAttribute("toBlockId");
|
||||
this.toAnchor = this.getAttribute("toAnchor");
|
||||
this.tooltip = this.getAttribute("tooltip");
|
||||
this["aria-label"] = this.getAttribute("aria-label");
|
||||
this.linkClasses = this.getAttribute("class");
|
||||
|
@ -1,8 +1,8 @@
|
||||
caption: block id
|
||||
created: 20230916061829840
|
||||
modified: 20230917121007649
|
||||
modified: 20230922150245402
|
||||
tags: Widgets
|
||||
title: BlockIdWidget
|
||||
title: AnchorWidget
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
! Introduction
|
||||
@ -11,7 +11,7 @@ The block id widget make an anchor that can be focused and jump to.
|
||||
|
||||
! Content and Attributes
|
||||
|
||||
The content of the `<$blockid>` widget is ignored.
|
||||
The content of the `<$anchor>` widget is ignored.
|
||||
|
||||
|!Attribute |!Description |
|
||||
|id |The unique id for the block |
|
||||
@ -21,14 +21,14 @@ See [[Block Level Links in WikiText^🤗→AddingIDforblock]] for WikiText synta
|
||||
|
||||
! Example
|
||||
|
||||
<<wikitext-example-without-html """The block id widget is invisible, and is usually located at the end of the line. ID is here:<$blockid id="BlockLevelLinksID1"/>
|
||||
<<wikitext-example-without-html """The block id widget is invisible, and is usually located at the end of the line. ID is here:<$anchor id="BlockLevelLinksID1"/>
|
||||
|
||||
[[Link to BlockLevelLinksID1|BlockIdWidget^BlockLevelLinksID1]]
|
||||
[[Link to BlockLevelLinksID1|AnchorWidget^BlockLevelLinksID1]]
|
||||
""">>
|
||||
|
||||
<<wikitext-example """You can refer to the block that is a line before the block id widget. Make sure block id widget itself is in a block (paragraph).
|
||||
|
||||
ID is here:<$blockid id="BlockLevelLinksID2" previousSibling="yes"/>
|
||||
ID is here:<$anchor id="BlockLevelLinksID2" previousSibling="yes"/>
|
||||
|
||||
[[Link to BlockLevelLinksID2|BlockIdWidget^BlockLevelLinksID2]]
|
||||
[[Link to BlockLevelLinksID2|AnchorWidget^BlockLevelLinksID2]]
|
||||
""">>
|
@ -1,11 +1,11 @@
|
||||
caption: Block Level Links
|
||||
created: 20230916061138153
|
||||
modified: 20230917122221226
|
||||
modified: 20230922150740619
|
||||
tags: WikiText
|
||||
title: Block Level Links in WikiText
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
! Adding ID for block ^🤗→AddingIDforblock
|
||||
<<wikitext-example-without-html src:"! Adding ID for block ^🤗→AddingIDforblock">>
|
||||
|
||||
The basic syntax for block id is:
|
||||
|
||||
@ -43,4 +43,6 @@ Adding `^blockID` after the title in the link, will make this link highlight the
|
||||
|
||||
<<wikitext-example-without-html src:"[[Link to BlockLevelLinksID2|Block Level Links in WikiText^BlockLevelLinksID2]]">>
|
||||
|
||||
<<wikitext-example src:"[[Link to Title|Block Level Links in WikiText^🤗→AddingIDforblock]]">>
|
||||
<<wikitext-example-without-html src:"[[Link to Title|Block Level Links in WikiText^🤗→AddingIDforblock]]">>
|
||||
|
||||
<<wikitext-example-without-html src:"[[Link to Non-existing anchor|Block Level Links in WikiText^😂❎]]">>
|
Loading…
Reference in New Issue
Block a user