From 5b6f5b270471b53e310b70418297cb797523b9a8 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Fri, 15 Sep 2023 22:17:19 +0800 Subject: [PATCH 01/30] feat: parse and show ^id --- .../wikiparser/rules/blockidentifier.js | 39 ++++++++++++++ core/modules/widgets/blockidentifier.js | 52 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 core/modules/parsers/wikiparser/rules/blockidentifier.js create mode 100644 core/modules/widgets/blockidentifier.js diff --git a/core/modules/parsers/wikiparser/rules/blockidentifier.js b/core/modules/parsers/wikiparser/rules/blockidentifier.js new file mode 100644 index 00000000000..5cc659600d1 --- /dev/null +++ b/core/modules/parsers/wikiparser/rules/blockidentifier.js @@ -0,0 +1,39 @@ +/*\ +title: $:/core/modules/parsers/wikiparser/rules/blockidentifier.js +type: application/javascript +module-type: wikirule + +Use hash as a tag for paragraph, we call it block identifier. + +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 = "blockidentifier"; +exports.types = {inline: true}; + +/* +Instantiate parse rule +*/ +exports.init = function(parser) { + this.parser = parser; + // Regexp to match the block identifier located on the end of the line. + this.matchRegExp = /[ ]\^\S+$/mg; +}; + +/* +Parse the most recent match +*/ +exports.parse = function() { + // Move past the match + this.parser.pos = this.matchRegExp.lastIndex; + var id = this.match[0].slice(2); + // Parse tree nodes to return + return [{ + type: "blockidentifier", + attributes: { + id: {type: "string", value: id} + }, + children: [] + }]; +}; diff --git a/core/modules/widgets/blockidentifier.js b/core/modules/widgets/blockidentifier.js new file mode 100644 index 00000000000..caad458f241 --- /dev/null +++ b/core/modules/widgets/blockidentifier.js @@ -0,0 +1,52 @@ +/*\ +title: $:/core/modules/widgets/blockidentifier.js +type: application/javascript +module-type: widget + +An invisible element with block id metadata. +\*/ +var Widget = require("$:/core/modules/widgets/widget.js").widget; +var BlockIdentifierWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; +BlockIdentifierWidget.prototype = new Widget(); + +BlockIdentifierWidget.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.spanDomNode = this.document.createElement("span"); + this.spanDomNode.setAttribute("data-id",this.id); + this.spanDomNode.className = "tc-block-id"; + parent.insertBefore(this.spanDomNode,nextSibling); + this.domNodes.push(this.spanDomNode); +}; + +/* +Compute the internal state of the widget +*/ +BlockIdentifierWidget.prototype.execute = function() { + // Get the id from the parse tree node or manually assigned attributes + this.id = this.getAttribute("id"); + // 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 +*/ +BlockIdentifierWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(($tw.utils.count(changedAttributes) > 0)) { + this.refreshSelf(); + return true; + } else { + return this.refreshChildren(changedTiddlers); + } +}; + +exports.blockidentifier = BlockIdentifierWidget; From 4c407c28e45c1ad4a5a2b8b9b25cf6157d105df2 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Fri, 15 Sep 2023 22:43:23 +0800 Subject: [PATCH 02/30] refactor: use blockid for shorter name --- .../rules/{blockidentifier.js => blockid.js} | 4 ++-- .../widgets/{blockidentifier.js => blockid.js} | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) rename core/modules/parsers/wikiparser/rules/{blockidentifier.js => blockid.js} (94%) rename core/modules/widgets/{blockidentifier.js => blockid.js} (75%) diff --git a/core/modules/parsers/wikiparser/rules/blockidentifier.js b/core/modules/parsers/wikiparser/rules/blockid.js similarity index 94% rename from core/modules/parsers/wikiparser/rules/blockidentifier.js rename to core/modules/parsers/wikiparser/rules/blockid.js index 5cc659600d1..ecc4e2393a1 100644 --- a/core/modules/parsers/wikiparser/rules/blockidentifier.js +++ b/core/modules/parsers/wikiparser/rules/blockid.js @@ -9,7 +9,7 @@ Use hash as a tag for paragraph, we call it block identifier. 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 = "blockidentifier"; +exports.name = "blockid"; exports.types = {inline: true}; /* @@ -30,7 +30,7 @@ exports.parse = function() { var id = this.match[0].slice(2); // Parse tree nodes to return return [{ - type: "blockidentifier", + type: "blockid", attributes: { id: {type: "string", value: id} }, diff --git a/core/modules/widgets/blockidentifier.js b/core/modules/widgets/blockid.js similarity index 75% rename from core/modules/widgets/blockidentifier.js rename to core/modules/widgets/blockid.js index caad458f241..0f33c1adb89 100644 --- a/core/modules/widgets/blockidentifier.js +++ b/core/modules/widgets/blockid.js @@ -1,17 +1,17 @@ /*\ -title: $:/core/modules/widgets/blockidentifier.js +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 BlockIdentifierWidget = function(parseTreeNode,options) { +var BlockIdWidget = function(parseTreeNode,options) { this.initialise(parseTreeNode,options); }; -BlockIdentifierWidget.prototype = new Widget(); +BlockIdWidget.prototype = new Widget(); -BlockIdentifierWidget.prototype.render = function(parent,nextSibling) { +BlockIdWidget.prototype.render = function(parent,nextSibling) { // Save the parent dom node this.parentDomNode = parent; // Compute our attributes @@ -29,7 +29,7 @@ BlockIdentifierWidget.prototype.render = function(parent,nextSibling) { /* Compute the internal state of the widget */ -BlockIdentifierWidget.prototype.execute = function() { +BlockIdWidget.prototype.execute = function() { // Get the id from the parse tree node or manually assigned attributes this.id = this.getAttribute("id"); // Make the child widgets @@ -39,7 +39,7 @@ BlockIdentifierWidget.prototype.execute = function() { /* Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ -BlockIdentifierWidget.prototype.refresh = function(changedTiddlers) { +BlockIdWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); if(($tw.utils.count(changedAttributes) > 0)) { this.refreshSelf(); @@ -49,4 +49,4 @@ BlockIdentifierWidget.prototype.refresh = function(changedTiddlers) { } }; -exports.blockidentifier = BlockIdentifierWidget; +exports.blockid = BlockIdWidget; From 18236b547f206d57266f327c76fbc29a404514fd Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 02:38:06 +0800 Subject: [PATCH 03/30] feat: allow add id for code block --- core/modules/parsers/wikiparser/rules/blockid.js | 15 +++++++++++---- core/modules/widgets/blockid.js | 4 ++++ core/ui/EditTemplate/title.tid | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/core/modules/parsers/wikiparser/rules/blockid.js b/core/modules/parsers/wikiparser/rules/blockid.js index ecc4e2393a1..84a0be41ad9 100644 --- a/core/modules/parsers/wikiparser/rules/blockid.js +++ b/core/modules/parsers/wikiparser/rules/blockid.js @@ -17,8 +17,10 @@ Instantiate parse rule */ exports.init = function(parser) { this.parser = parser; - // Regexp to match the block identifier located on the end of the line. - this.matchRegExp = /[ ]\^\S+$/mg; + // Regexp to match the block identifier + // 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; }; /* @@ -27,12 +29,17 @@ Parse the most recent match exports.parse = function() { // Move past the match this.parser.pos = this.matchRegExp.lastIndex; - var id = this.match[0].slice(2); + // will be one of following case, another will be undefined + var blockId = this.match[1]; + var blockBeforeId = this.match[2]; // Parse tree nodes to return return [{ type: "blockid", attributes: { - id: {type: "string", value: id} + id: {type: "string", value: blockId || blockBeforeId}, + // `true` means the block is before this node, in parent node's children list. + // `false` means the block is this node's parent node. + before: {type: "boolean", value: Boolean(blockBeforeId)}, }, children: [] }]; diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 0f33c1adb89..e5902ee35ac 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -21,6 +21,9 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { // Create an invisible DOM element with data that can be accessed from JS or CSS this.spanDomNode = this.document.createElement("span"); this.spanDomNode.setAttribute("data-id",this.id); + if(this.before) { + this.spanDomNode.setAttribute("data-before","true"); + } this.spanDomNode.className = "tc-block-id"; parent.insertBefore(this.spanDomNode,nextSibling); this.domNodes.push(this.spanDomNode); @@ -32,6 +35,7 @@ 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.before = this.getAttribute("before"); // Make the child widgets this.makeChildWidgets(); }; diff --git a/core/ui/EditTemplate/title.tid b/core/ui/EditTemplate/title.tid index 5228ad7c064..38e75997a30 100644 --- a/core/ui/EditTemplate/title.tid +++ b/core/ui/EditTemplate/title.tid @@ -4,7 +4,7 @@ tags: $:/tags/EditTemplate \whitespace trim <$edit-text field="draft.title" class="tc-titlebar tc-edit-texteditor" focus={{{ [{$:/config/AutoFocus}match[title]then[true]] ~[[false]] }}} tabindex={{$:/config/EditTabIndex}} cancelPopups="yes"/> -<$vars pattern="""[\|\[\]{}]""" bad-chars="""`| [ ] { }`"""> +<$vars pattern="""[\^\|\[\]{}]""" bad-chars="""`| ^ [ ] { }`"""> <$list filter="[all[current]regexp:draft.title]" variable="listItem"> From d5e9d2a71bf6127fa673efcb3c8aab2ee8aa6696 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 02:49:58 +0800 Subject: [PATCH 04/30] feat: allow wiki pretty link to have id --- .../modules/parsers/wikiparser/rules/prettylink.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js index 56a2850a39b..4d7839979a7 100644 --- a/core/modules/parsers/wikiparser/rules/prettylink.js +++ b/core/modules/parsers/wikiparser/rules/prettylink.js @@ -23,8 +23,8 @@ exports.types = {inline: true}; exports.init = function(parser) { this.parser = parser; - // Regexp to match - this.matchRegExp = /\[\[(.*?)(?:\|(.*?))?\]\]/mg; + // Regexp to match `[[Title^blockId|Alias]]`, the `^blockId` and `|Alias` are optional. + this.matchRegExp = /\[\[(.*?)(?:\^([^|\s^]+))?(?:\|(.*?))?\]\]/mg; }; exports.parse = function() { @@ -32,8 +32,13 @@ exports.parse = function() { this.parser.pos = this.matchRegExp.lastIndex; // Process the link var text = this.match[1], - link = this.match[2] || text; + blockId = this.match[2] || "", + link = this.match[3] || text; 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; + } return [{ type: "element", tag: "a", @@ -51,7 +56,8 @@ exports.parse = function() { return [{ type: "link", attributes: { - to: {type: "string", value: link} + to: {type: "string", value: link}, + id: {type: "string", value: blockId}, }, children: [{ type: "text", text: text From b956e72536389c1a722d26f0668c70600f0ff7d4 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 04:09:21 +0800 Subject: [PATCH 05/30] fix: properly match blockId and pass it to ast --- core/modules/parsers/wikiparser/rules/prettylink.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js index 4d7839979a7..bfca074f1b7 100644 --- a/core/modules/parsers/wikiparser/rules/prettylink.js +++ b/core/modules/parsers/wikiparser/rules/prettylink.js @@ -23,8 +23,8 @@ exports.types = {inline: true}; exports.init = function(parser) { this.parser = parser; - // Regexp to match `[[Title^blockId|Alias]]`, the `^blockId` and `|Alias` are optional. - this.matchRegExp = /\[\[(.*?)(?:\^([^|\s^]+))?(?:\|(.*?))?\]\]/mg; + // Regexp to match `[[Alias|Title^blockId]]`, the `Alias|` and `^blockId` are optional. + this.matchRegExp = /\[\[(.*?)(?:\|(.*?)?)?(?:\^([^|\s^]+)?)?\]\]/mg; }; exports.parse = function() { @@ -32,8 +32,8 @@ exports.parse = function() { this.parser.pos = this.matchRegExp.lastIndex; // Process the link var text = this.match[1], - blockId = this.match[2] || "", - link = this.match[3] || text; + link = this.match[2] || text, + blockId = 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) { @@ -57,7 +57,7 @@ exports.parse = function() { type: "link", attributes: { to: {type: "string", value: link}, - id: {type: "string", value: blockId}, + toBlockId: {type: "string", value: blockId}, }, children: [{ type: "text", text: text From 3d8ade3ebe6bf29a739f5a478594bec463a50a64 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 04:45:07 +0800 Subject: [PATCH 06/30] feat: redirect tm-focus-selector event to check parent or sibling --- boot/boot.js | 14 +++++++++ .../parsers/wikiparser/rules/blockid.js | 2 +- core/modules/startup/rootwidget.js | 2 ++ core/modules/widgets/blockid.js | 29 +++++++++++++++++-- core/modules/widgets/link.js | 8 +++++ themes/tiddlywiki/vanilla/base.tid | 13 +++++++++ 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/boot/boot.js b/boot/boot.js index 06d4628c046..2636cb8f4a2 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -2674,6 +2674,20 @@ $tw.hooks.addHook = function(hookName,definition) { } }; +/* +Delete hooks from the hashmap +*/ +$tw.hooks.removeHook = function(hookName,definition) { + if($tw.utils.hop($tw.hooks.names,hookName)) { + var index = $tw.hooks.names[hookName].findIndex(function(hook) { + return hook === definition; + }); + if(index !== -1) { + $tw.hooks.names[hookName].splice(index, 1); + } + } +}; + /* Invoke the hook by key */ diff --git a/core/modules/parsers/wikiparser/rules/blockid.js b/core/modules/parsers/wikiparser/rules/blockid.js index 84a0be41ad9..61438981b1d 100644 --- a/core/modules/parsers/wikiparser/rules/blockid.js +++ b/core/modules/parsers/wikiparser/rules/blockid.js @@ -39,7 +39,7 @@ exports.parse = function() { id: {type: "string", value: blockId || blockBeforeId}, // `true` means the block is before this node, in parent node's children list. // `false` means the block is this node's parent node. - before: {type: "boolean", value: Boolean(blockBeforeId)}, + previousSibling: {type: "boolean", value: Boolean(blockBeforeId)}, }, children: [] }]; diff --git a/core/modules/startup/rootwidget.js b/core/modules/startup/rootwidget.js index 716275cda09..580c0e94da8 100644 --- a/core/modules/startup/rootwidget.js +++ b/core/modules/startup/rootwidget.js @@ -72,6 +72,8 @@ exports.startup = function() { }); // Install the tm-focus-selector message $tw.rootWidget.addEventListener("tm-focus-selector",function(event) { + event = $tw.hooks.invokeHook("th-focus-selector",event); + if (!event) return; var selector = event.param || "", element, baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index e5902ee35ac..0d915ab7ab2 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -18,9 +18,13 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); // Execute our logic this.execute(); + $tw.hooks.removeHook("th-focus-selector",this.hookFocusElementEvent); + this.hookFocusElementEvent = this.hookFocusElementEvent.bind(this); + $tw.hooks.addHook("th-focus-selector",this.hookFocusElementEvent); // Create an invisible DOM element with data that can be accessed from JS or CSS this.spanDomNode = this.document.createElement("span"); - this.spanDomNode.setAttribute("data-id",this.id); + this.spanDomNode.id = this.id; + this.spanDomNode.setAttribute("data-block-id",this.id); if(this.before) { this.spanDomNode.setAttribute("data-before","true"); } @@ -29,13 +33,34 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { this.domNodes.push(this.spanDomNode); }; +BlockIdWidget.prototype.hookFocusElementEvent = function(event) { + var id = event.param.replace('#',''); + if(id !== this.id) { + return event; + } + var element = this.parentDomNode; + // need to check if the block is before this node + if(this.previousSibling) { + element = element.previousSibling; + } + element.focus({ focusVisible: true }); + // toggle class to trigger highlight animation + $tw.utils.removeClass(element,"tc-focus-highlight"); + $tw.utils.addClass(element,"tc-focus-highlight"); + return false; +}; + +BlockIdWidget.prototype.removeChildDomNodes = function() { + $tw.hooks.removeHook("th-focus-selector",this.hookFocusElementEvent); +}; + /* 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.before = this.getAttribute("before"); + this.previousSibling = this.getAttribute("previousSibling"); // Make the child widgets this.makeChildWidgets(); }; diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index 6f199d395b3..12fc2da23ae 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -167,6 +167,13 @@ LinkWidget.prototype.handleClickEvent = function(event) { shiftKey: event.shiftKey, event: event }); + if(this.toBlockId) { + this.dispatchEvent({ + type: "tm-focus-selector", + param: "#" + this.toBlockId, + event: event, + }); + } if(this.domNodes[0].hasAttribute("href")) { event.preventDefault(); } @@ -180,6 +187,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.tooltip = this.getAttribute("tooltip"); this["aria-label"] = this.getAttribute("aria-label"); this.linkClasses = this.getAttribute("class"); diff --git a/themes/tiddlywiki/vanilla/base.tid b/themes/tiddlywiki/vanilla/base.tid index 4603589ae09..b7cb1a3fc6d 100644 --- a/themes/tiddlywiki/vanilla/base.tid +++ b/themes/tiddlywiki/vanilla/base.tid @@ -2413,6 +2413,19 @@ html body.tc-body.tc-single-tiddler-window { color: <>; } +@keyframes fadeHighlight { + 0% { + background-color: <>; + } + 100% { + background-color: transparent; + } +} + +.tc-focus-highlight { + animation: fadeHighlight 2s forwards; +} + @media (min-width: <>) { .tc-static-alert { From 6b6124369cab6249b8be15bb5bb33fec3ad51cd6 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 04:49:50 +0800 Subject: [PATCH 07/30] fix: param maybe null --- core/modules/widgets/blockid.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 0d915ab7ab2..959b5324335 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -34,10 +34,9 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { }; BlockIdWidget.prototype.hookFocusElementEvent = function(event) { + if(!event.param) return event; var id = event.param.replace('#',''); - if(id !== this.id) { - return event; - } + if(id !== this.id) return event; var element = this.parentDomNode; // need to check if the block is before this node if(this.previousSibling) { From d8bdd09eb0f21d0e30aa42164aecd8462e7e9bc9 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 05:00:10 +0800 Subject: [PATCH 08/30] fix: ensure hightlight is visible --- core/modules/widgets/blockid.js | 5 ++++- themes/tiddlywiki/vanilla/base.tid | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 959b5324335..eaec7a537c4 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -45,7 +45,10 @@ BlockIdWidget.prototype.hookFocusElementEvent = function(event) { element.focus({ focusVisible: true }); // toggle class to trigger highlight animation $tw.utils.removeClass(element,"tc-focus-highlight"); - $tw.utils.addClass(element,"tc-focus-highlight"); + // Using setTimeout to ensure the removal takes effect before adding the class again. + setTimeout(function() { + $tw.utils.addClass(element,"tc-focus-highlight"); + }, 50); return false; }; diff --git a/themes/tiddlywiki/vanilla/base.tid b/themes/tiddlywiki/vanilla/base.tid index b7cb1a3fc6d..e957ea0f902 100644 --- a/themes/tiddlywiki/vanilla/base.tid +++ b/themes/tiddlywiki/vanilla/base.tid @@ -2416,9 +2416,11 @@ html body.tc-body.tc-single-tiddler-window { @keyframes fadeHighlight { 0% { background-color: <>; + border-color: <>; } 100% { background-color: transparent; + border-color: transparent; } } From 0863916f9b0cf654b64b4ba7907ef0372e40862e Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 05:13:40 +0800 Subject: [PATCH 09/30] fix: wait until animation finish and dom show --- core/modules/widgets/blockid.js | 9 +++++++-- core/modules/widgets/link.js | 14 +++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index eaec7a537c4..a7d361bff31 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -37,9 +37,14 @@ BlockIdWidget.prototype.hookFocusElementEvent = function(event) { if(!event.param) return event; var id = event.param.replace('#',''); if(id !== this.id) return event; - var element = this.parentDomNode; + var selector = event.param || "", + element, + baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; + element = $tw.utils.querySelectorSafe(selector,baseElement) || this.spanDomNode; + if(!element.parentNode) return; + element = element.parentNode; // need to check if the block is before this node - if(this.previousSibling) { + if(this.previousSibling && element.previousSibling) { element = element.previousSibling; } element.focus({ focusVisible: true }); diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index 12fc2da23ae..6bb3aa4d215 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -168,11 +168,15 @@ LinkWidget.prototype.handleClickEvent = function(event) { event: event }); if(this.toBlockId) { - this.dispatchEvent({ - type: "tm-focus-selector", - param: "#" + this.toBlockId, - event: event, - }); + var duration = $tw.utils.getAnimationDuration(); + var self = this; + setTimeout(function() { + self.dispatchEvent({ + type: "tm-focus-selector", + param: "#" + self.toBlockId, + event: event, + }); + },duration); } if(this.domNodes[0].hasAttribute("href")) { event.preventDefault(); From e6445b79c50afa5f327478cbd4d2ab728ed8ea3d Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 13:40:55 +0800 Subject: [PATCH 10/30] docs: why add hook --- core/modules/widgets/blockid.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index a7d361bff31..15db5c095b3 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -8,6 +8,9 @@ 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.hookFocusElementEvent = this.hookFocusElementEvent.bind(this); + $tw.hooks.addHook("th-focus-selector",this.hookFocusElementEvent); }; BlockIdWidget.prototype = new Widget(); @@ -18,9 +21,6 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); // Execute our logic this.execute(); - $tw.hooks.removeHook("th-focus-selector",this.hookFocusElementEvent); - this.hookFocusElementEvent = this.hookFocusElementEvent.bind(this); - $tw.hooks.addHook("th-focus-selector",this.hookFocusElementEvent); // Create an invisible DOM element with data that can be accessed from JS or CSS this.spanDomNode = this.document.createElement("span"); this.spanDomNode.id = this.id; @@ -40,7 +40,7 @@ BlockIdWidget.prototype.hookFocusElementEvent = function(event) { var selector = event.param || "", element, baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; - element = $tw.utils.querySelectorSafe(selector,baseElement) || this.spanDomNode; + element = $tw.utils.querySelectorSafe(selector,baseElement); if(!element.parentNode) return; element = element.parentNode; // need to check if the block is before this node From db83401a69a6a72e69dd73c0f954280745910da6 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 14:26:37 +0800 Subject: [PATCH 11/30] docs: about usage --- .../parsers/wikiparser/rules/blockid.js | 6 +-- core/modules/widgets/blockid.js | 6 +-- .../tiddlers/widgets/BlockIdWidget.tid | 39 ++++++++++++++++ .../Block Level Links in WikiText.tid | 44 +++++++++++++++++++ .../tiddlers/wikitext/Linking in WikiText.tid | 6 +++ 5 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid create mode 100644 editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid diff --git a/core/modules/parsers/wikiparser/rules/blockid.js b/core/modules/parsers/wikiparser/rules/blockid.js index 61438981b1d..57da17d7988 100644 --- a/core/modules/parsers/wikiparser/rules/blockid.js +++ b/core/modules/parsers/wikiparser/rules/blockid.js @@ -37,9 +37,9 @@ exports.parse = function() { type: "blockid", attributes: { id: {type: "string", value: blockId || blockBeforeId}, - // `true` means the block is before this node, in parent node's children list. - // `false` means the block is this node's parent node. - previousSibling: {type: "boolean", value: Boolean(blockBeforeId)}, + // `yes` means the block is before this node, in parent node's children list. + // empty means the block is this node's direct parent node. + previousSibling: {type: "string", value: Boolean(blockBeforeId) ? "yes" : ""}, }, children: [] }]; diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 15db5c095b3..47ce11722ec 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -34,14 +34,14 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { }; BlockIdWidget.prototype.hookFocusElementEvent = function(event) { - if(!event.param) return event; + if(!event || !event.param) return event; var id = event.param.replace('#',''); if(id !== this.id) return event; var selector = event.param || "", element, baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; element = $tw.utils.querySelectorSafe(selector,baseElement); - if(!element.parentNode) return; + if(!element || !element.parentNode) return; element = element.parentNode; // need to check if the block is before this node if(this.previousSibling && element.previousSibling) { @@ -67,7 +67,7 @@ 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.previousSibling = this.getAttribute("previousSibling"); + this.previousSibling = this.getAttribute("previousSibling") === "yes"; // Make the child widgets this.makeChildWidgets(); }; diff --git a/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid b/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid new file mode 100644 index 00000000000..ab8a5e230a1 --- /dev/null +++ b/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid @@ -0,0 +1,39 @@ +caption: block id +created: 20230916061829840 +modified: 20230916062346854 +tags: Widgets +title: BlockIdWidget +type: text/vnd.tiddlywiki + +! Introduction + +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. + +|!Attribute |!Description | +|id |The unique id for the block | +|previousSibling |`yes` means the block is before this node, in parent node's children list, else it means the block is this node's direct parent node. | + +See [[Block Level Links in WikiText^block091606]] for WikiText syntax of block ID. + +! Example + +< + +[[exampleid1|BlockIdWidget^exampleid1]] +""">> + +<<$blockid id="exampleid2" previousSibling="yes"/>

+ +[[exampleid2|BlockIdWidget^exampleid2]] +""">> + +< + +[[exampleid1|BlockIdWidget^exampleid1]] +""">> \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid new file mode 100644 index 00000000000..f460d1c9b55 --- /dev/null +++ b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid @@ -0,0 +1,44 @@ +caption: Block Level Links +created: 20230916061138153 +modified: 20230916062623280 +tags: WikiText +title: Block Level Links in WikiText +type: text/vnd.tiddlywiki + +! Adding ID for block ^block091606 + +The basic syntax for block id is: + +<> + +# Don't forget the space between the end of the line and the `^`. +# And there is no space between `^` and the id. +# ID should be a [ext[valid HTML element ID|https://developer.mozilla.org/docs/Web/HTML/Global_attributes/id]], to avoid inadvertent errors, only ASCII letters, digits, '_', and '-' should be used, and the value for an id attribute should start with a letter. + +And this block id widget will be rendered as an invisible element: + +```html + +``` + +!! Adding id to previous block + +Some block, for example, code block, can't be suffixed by `^id`, but we can add the id in the next line, with no space prefix to it. + +<> + +! Link to the block ID + +Adding `^blockID` after the title in the link, will make this link highlight the block with that ID. + +<> + +<> \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid index b64d0806a76..6d401aad681 100644 --- a/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid @@ -123,3 +123,9 @@ See also another example of [[constructing dynamic links|Concatenating text and In TiddlyWiki anchor links can help us link to target points and distinct sections within rendered tiddlers. They can help the reader navigate longer tiddler content. See [[Anchor Links using HTML]] for more information. + +! Linking within tiddlers - Link to block + +You can link to a specific block within a tiddler using `^blockId` syntax. You will also get block level backlinks with this technique. Some examples are in [[BlockIdWidget^exampleid1]]. + +See [[Block Level Links in WikiText]] for more information. From 7200f73cdc0f1bd99e46b9333dc84ec2b4371366 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 15:12:56 +0800 Subject: [PATCH 12/30] refactor: use th-navigated to simplify the code --- core/modules/widgets/blockid.js | 28 ++++++++++--------- core/modules/widgets/link.js | 14 ++-------- core/modules/widgets/navigator.js | 1 + .../dev/tiddlers/new/Hook__th-navigated.tid | 11 ++++++++ .../Block Level Links in WikiText.tid | 4 +-- .../tiddlers/wikitext/Linking in WikiText.tid | 4 +-- 6 files changed, 33 insertions(+), 29 deletions(-) create mode 100644 editions/dev/tiddlers/new/Hook__th-navigated.tid diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 47ce11722ec..9aef2a3a56e 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -9,8 +9,8 @@ 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.hookFocusElementEvent = this.hookFocusElementEvent.bind(this); - $tw.hooks.addHook("th-focus-selector",this.hookFocusElementEvent); + this.hookNavigatedEvent = this.hookNavigatedEvent.bind(this); + $tw.hooks.addHook("th-navigated",this.hookNavigatedEvent); }; BlockIdWidget.prototype = new Widget(); @@ -33,32 +33,34 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { this.domNodes.push(this.spanDomNode); }; -BlockIdWidget.prototype.hookFocusElementEvent = function(event) { - if(!event || !event.param) return event; - var id = event.param.replace('#',''); - if(id !== this.id) return event; - var selector = event.param || "", - element, - baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; - element = $tw.utils.querySelectorSafe(selector,baseElement); +BlockIdWidget.prototype.hookNavigatedEvent = function(event) { + if(!event || !event.toBlockId) return event; + if(event.toBlockId !== this.id) return event; + var selector = "#"+event.toBlockId; + var baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; + // re-query the dom node, because `this.spanDomNode.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; } - element.focus({ focusVisible: true }); // toggle class to trigger highlight animation $tw.utils.removeClass(element,"tc-focus-highlight"); - // Using setTimeout to ensure the removal takes effect before adding the class again. + // we have enabled `navigateSuppressNavigation` to avoid collision with scroll effect of `tm-navigate` + element.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" }); + element.focus({ focusVisible: true }); setTimeout(function() { + // Using setTimeout to ensure the removal takes effect before adding the class again. $tw.utils.addClass(element,"tc-focus-highlight"); }, 50); return false; }; BlockIdWidget.prototype.removeChildDomNodes = function() { - $tw.hooks.removeHook("th-focus-selector",this.hookFocusElementEvent); + $tw.hooks.removeHook("th-focus-selector",this.hookNavigatedEvent); }; /* diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index 6bb3aa4d215..372a45bebd2 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -150,6 +150,7 @@ LinkWidget.prototype.handleClickEvent = function(event) { this.dispatchEvent({ type: "tm-navigate", navigateTo: this.to, + toBlockId: this.toBlockId, 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 @@ -160,24 +161,13 @@ LinkWidget.prototype.handleClickEvent = function(event) { navigateFromClientRight: bounds.right, navigateFromClientBottom: bounds.bottom, navigateFromClientHeight: bounds.height, - navigateSuppressNavigation: event.metaKey || event.ctrlKey || (event.button === 1), + navigateSuppressNavigation: this.toBlockId || event.metaKey || event.ctrlKey || (event.button === 1), metaKey: event.metaKey, ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, event: event }); - if(this.toBlockId) { - var duration = $tw.utils.getAnimationDuration(); - var self = this; - setTimeout(function() { - self.dispatchEvent({ - type: "tm-focus-selector", - param: "#" + self.toBlockId, - event: event, - }); - },duration); - } if(this.domNodes[0].hasAttribute("href")) { event.preventDefault(); } diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index efdbba83f9e..1ce51d3022b 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -154,6 +154,7 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) { this.addToHistory(event.navigateTo,event.navigateFromClientRect); } } + $tw.hooks.invokeHook("th-navigated",event); return false; }; diff --git a/editions/dev/tiddlers/new/Hook__th-navigated.tid b/editions/dev/tiddlers/new/Hook__th-navigated.tid new file mode 100644 index 00000000000..0315529baf8 --- /dev/null +++ b/editions/dev/tiddlers/new/Hook__th-navigated.tid @@ -0,0 +1,11 @@ +tags: HookMechanism +title: Hook: th-navigated +type: text/vnd.tiddlywiki + +This hook allows plugins to do things after navigation takes effect. + +Hook function parameters are same as [[Hook: th-navigating]]: + +Return value: + +* possibly modified event object diff --git a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid index f460d1c9b55..6b553fc4937 100644 --- a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid @@ -1,6 +1,6 @@ caption: Block Level Links created: 20230916061138153 -modified: 20230916062623280 +modified: 20230916070710392 tags: WikiText title: Block Level Links in WikiText type: text/vnd.tiddlywiki @@ -35,7 +35,7 @@ Some block, for example, code block, can't be suffixed by `^id`, but we can add ">> -! Link to the block ID +! Link to the block ID ^linkto091607 Adding `^blockID` after the title in the link, will make this link highlight the block with that ID. diff --git a/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid index 6d401aad681..b7aa53faba8 100644 --- a/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid @@ -1,6 +1,6 @@ caption: Linking created: 20131205155230596 -modified: 20211230145939554 +modified: 20230916070722108 tags: WikiText title: Linking in WikiText type: text/vnd.tiddlywiki @@ -128,4 +128,4 @@ See [[Anchor Links using HTML]] for more information. You can link to a specific block within a tiddler using `^blockId` syntax. You will also get block level backlinks with this technique. Some examples are in [[BlockIdWidget^exampleid1]]. -See [[Block Level Links in WikiText]] for more information. +See [[Block Level Links in WikiText^linkto091607]] for more information. From c0b6b7988ab05246f25cfc5a347e29be36a86cbd Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 15:22:42 +0800 Subject: [PATCH 13/30] fix: element not exist --- core/modules/widgets/blockid.js | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 9aef2a3a56e..0118a7e841c 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -36,8 +36,27 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { BlockIdWidget.prototype.hookNavigatedEvent = function(event) { if(!event || !event.toBlockId) return event; if(event.toBlockId !== this.id) return event; - var selector = "#"+event.toBlockId; var baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; + var duration = $tw.utils.getAnimationDuration(); + var self = this; + // we have enabled `navigateSuppressNavigation` to avoid collision with scroll effect of `tm-navigate`, but need to wait for tiddler dom stably added to the story view. + setTimeout(function() { + var element = self._getTargetElement(baseElement); + if(!element) return; + // toggle class to trigger highlight animation + $tw.utils.removeClass(element,"tc-focus-highlight"); + 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); + }, duration); + return false; +}; + +BlockIdWidget.prototype._getTargetElement = function(baseElement) { + var selector = "#"+this.id; // re-query the dom node, because `this.spanDomNode.parentNode` might already be removed from document var element = $tw.utils.querySelectorSafe(selector,baseElement); if(!element || !element.parentNode) return; @@ -47,16 +66,7 @@ BlockIdWidget.prototype.hookNavigatedEvent = function(event) { if(this.previousSibling && element.previousSibling) { element = element.previousSibling; } - // toggle class to trigger highlight animation - $tw.utils.removeClass(element,"tc-focus-highlight"); - // we have enabled `navigateSuppressNavigation` to avoid collision with scroll effect of `tm-navigate` - element.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" }); - element.focus({ focusVisible: true }); - setTimeout(function() { - // Using setTimeout to ensure the removal takes effect before adding the class again. - $tw.utils.addClass(element,"tc-focus-highlight"); - }, 50); - return false; + return element; }; BlockIdWidget.prototype.removeChildDomNodes = function() { From 3bcd822c97da73845ff19450e46ec5da6c956306 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 15:33:03 +0800 Subject: [PATCH 14/30] fix: scroll too slow if tiddler already appear --- core/modules/widgets/blockid.js | 39 +++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 0118a7e841c..1138fdd03b7 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -37,21 +37,19 @@ BlockIdWidget.prototype.hookNavigatedEvent = function(event) { if(!event || !event.toBlockId) return event; if(event.toBlockId !== this.id) return event; var baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; - var duration = $tw.utils.getAnimationDuration(); - var self = this; - // we have enabled `navigateSuppressNavigation` to avoid collision with scroll effect of `tm-navigate`, but need to wait for tiddler dom stably added to the story view. - setTimeout(function() { - var element = self._getTargetElement(baseElement); - if(!element) return; - // toggle class to trigger highlight animation - $tw.utils.removeClass(element,"tc-focus-highlight"); - 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. + 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() { - $tw.utils.addClass(element,"tc-focus-highlight"); - }, 50); - }, duration); + element = self._getTargetElement(baseElement); + self._scrollToBlockAndHighlight(element); + }, duration); + } return false; }; @@ -69,6 +67,19 @@ BlockIdWidget.prototype._getTargetElement = function(baseElement) { 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); +}; + BlockIdWidget.prototype.removeChildDomNodes = function() { $tw.hooks.removeHook("th-focus-selector",this.hookNavigatedEvent); }; From 07130c2ad094dcb04c65fb8ce8496a40afb8a2ac Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 16 Sep 2023 15:37:46 +0800 Subject: [PATCH 15/30] fix: code style and types --- core/modules/startup/rootwidget.js | 2 -- core/modules/widgets/blockid.js | 2 +- themes/tiddlywiki/vanilla/base.tid | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/modules/startup/rootwidget.js b/core/modules/startup/rootwidget.js index 580c0e94da8..716275cda09 100644 --- a/core/modules/startup/rootwidget.js +++ b/core/modules/startup/rootwidget.js @@ -72,8 +72,6 @@ exports.startup = function() { }); // Install the tm-focus-selector message $tw.rootWidget.addEventListener("tm-focus-selector",function(event) { - event = $tw.hooks.invokeHook("th-focus-selector",event); - if (!event) return; var selector = event.param || "", element, baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 1138fdd03b7..916793ba5a8 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -81,7 +81,7 @@ BlockIdWidget.prototype._scrollToBlockAndHighlight = function(element) { }; BlockIdWidget.prototype.removeChildDomNodes = function() { - $tw.hooks.removeHook("th-focus-selector",this.hookNavigatedEvent); + $tw.hooks.removeHook("th-navigated",this.hookNavigatedEvent); }; /* diff --git a/themes/tiddlywiki/vanilla/base.tid b/themes/tiddlywiki/vanilla/base.tid index e957ea0f902..f6de39f2a3c 100644 --- a/themes/tiddlywiki/vanilla/base.tid +++ b/themes/tiddlywiki/vanilla/base.tid @@ -2413,7 +2413,7 @@ html body.tc-body.tc-single-tiddler-window { color: <>; } -@keyframes fadeHighlight { +@keyframes fade-highlight { 0% { background-color: <>; border-color: <>; @@ -2425,7 +2425,7 @@ html body.tc-body.tc-single-tiddler-window { } .tc-focus-highlight { - animation: fadeHighlight 2s forwards; + animation: fade-highlight 2s forwards; } @media (min-width: <>) { From a5c2f8558e3f9b64b7a48181d3b9df7ec83bf373 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sun, 17 Sep 2023 20:03:15 +0800 Subject: [PATCH 16/30] feat: allow different tiddler have same block id in the text, and only jump to the one with a same tiddler title. --- core/modules/widgets/blockid.js | 23 ++++++++++++------- .../tiddlers/widgets/BlockIdWidget.tid | 17 +++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 916793ba5a8..42f096f4ad4 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -22,20 +22,22 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { // Execute our logic this.execute(); // Create an invisible DOM element with data that can be accessed from JS or CSS - this.spanDomNode = this.document.createElement("span"); - this.spanDomNode.id = this.id; - this.spanDomNode.setAttribute("data-block-id",this.id); + this.idNode = this.document.createElement("span"); + this.idNode.id = this.id; + this.idNode.setAttribute("data-block-id",this.id); + this.idNode.setAttribute("data-block-title",this.tiddlerTitle); if(this.before) { - this.spanDomNode.setAttribute("data-before","true"); + this.idNode.setAttribute("data-before","true"); } - this.spanDomNode.className = "tc-block-id"; - parent.insertBefore(this.spanDomNode,nextSibling); - this.domNodes.push(this.spanDomNode); + this.idNode.className = "tc-block-id"; + parent.insertBefore(this.idNode,nextSibling); + this.domNodes.push(this.idNode); }; BlockIdWidget.prototype.hookNavigatedEvent = function(event) { if(!event || !event.toBlockId) return event; if(event.toBlockId !== this.id) return event; + if(this.tiddlerTitle && event.navigateTo !== this.tiddlerTitle) return event; var baseElement = event.event && event.event.target ? event.event.target.ownerDocument : document; var element = this._getTargetElement(baseElement); if(element) { @@ -55,7 +57,11 @@ BlockIdWidget.prototype.hookNavigatedEvent = function(event) { BlockIdWidget.prototype._getTargetElement = function(baseElement) { var selector = "#"+this.id; - // re-query the dom node, because `this.spanDomNode.parentNode` might already be removed from document + 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 @@ -90,6 +96,7 @@ 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(); diff --git a/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid b/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid index ab8a5e230a1..47d0cb1779d 100644 --- a/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid @@ -1,6 +1,6 @@ caption: block id created: 20230916061829840 -modified: 20230916062346854 +modified: 20230917120133519 tags: Widgets title: BlockIdWidget type: text/vnd.tiddlywiki @@ -21,19 +21,14 @@ See [[Block Level Links in WikiText^block091606]] for WikiText syntax of block I ! Example -< +< -[[exampleid1|BlockIdWidget^exampleid1]] +[[Link to BlockLevelLinksID1|BlockIdWidget^BlockLevelLinksID1]] """>> -<<$blockid id="exampleid2" previousSibling="yes"/>

+ID is here:<$blockid id="BlockLevelLinksID2" previousSibling="yes"/> -[[exampleid2|BlockIdWidget^exampleid2]] +[[Link to BlockLevelLinksID2|BlockIdWidget^BlockLevelLinksID2]] """>> - -< - -[[exampleid1|BlockIdWidget^exampleid1]] -""">> \ No newline at end of file From cff0240ac8e5adcff8b3321bd270f30f4e29ee91 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sun, 17 Sep 2023 20:11:59 +0800 Subject: [PATCH 17/30] feat: allow using any char in id --- core/modules/widgets/blockid.js | 3 +-- editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid | 4 ++-- .../wikitext/Block Level Links in WikiText.tid | 12 +++++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index 42f096f4ad4..ea27b4d0ae9 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -23,7 +23,6 @@ BlockIdWidget.prototype.render = function(parent,nextSibling) { 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.id = this.id; this.idNode.setAttribute("data-block-id",this.id); this.idNode.setAttribute("data-block-title",this.tiddlerTitle); if(this.before) { @@ -56,7 +55,7 @@ BlockIdWidget.prototype.hookNavigatedEvent = function(event) { }; BlockIdWidget.prototype._getTargetElement = function(baseElement) { - var selector = "#"+this.id; + 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+"']"; diff --git a/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid b/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid index 47d0cb1779d..6f7d6a55695 100644 --- a/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid @@ -1,6 +1,6 @@ caption: block id created: 20230916061829840 -modified: 20230917120133519 +modified: 20230917121007649 tags: Widgets title: BlockIdWidget type: text/vnd.tiddlywiki @@ -17,7 +17,7 @@ The content of the `<$blockid>` widget is ignored. |id |The unique id for the block | |previousSibling |`yes` means the block is before this node, in parent node's children list, else it means the block is this node's direct parent node. | -See [[Block Level Links in WikiText^block091606]] for WikiText syntax of block ID. +See [[Block Level Links in WikiText^🀗→AddingIDforblock]] for WikiText syntax of block ID. ! Example diff --git a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid index 6b553fc4937..3e0b3296470 100644 --- a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid @@ -1,11 +1,11 @@ caption: Block Level Links created: 20230916061138153 -modified: 20230916070710392 +modified: 20230917121127276 tags: WikiText title: Block Level Links in WikiText type: text/vnd.tiddlywiki -! Adding ID for block ^block091606 +! Adding ID for block ^🀗→AddingIDforblock The basic syntax for block id is: @@ -13,12 +13,12 @@ The basic syntax for block id is: # Don't forget the space between the end of the line and the `^`. # And there is no space between `^` and the id. -# ID should be a [ext[valid HTML element ID|https://developer.mozilla.org/docs/Web/HTML/Global_attributes/id]], to avoid inadvertent errors, only ASCII letters, digits, '_', and '-' should be used, and the value for an id attribute should start with a letter. +# ID can contain any char other than `^` and space ` `. And this block id widget will be rendered as an invisible element: ```html - + ``` !! Adding id to previous block @@ -41,4 +41,6 @@ Adding `^blockID` after the title in the link, will make this link highlight the <> -<> \ No newline at end of file +<> + +<> \ No newline at end of file From fef444cb1ce3e6ef62aeddfb53c62349c6343805 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sun, 17 Sep 2023 20:25:40 +0800 Subject: [PATCH 18/30] fix: when id not exist, still navigate to the tiddler --- core/modules/widgets/blockid.js | 30 ++++++++++++++----- core/modules/widgets/link.js | 2 +- core/modules/widgets/navigator.js | 1 + .../Block Level Links in WikiText.tid | 4 +-- .../tiddlers/wikitext/Linking in WikiText.tid | 4 +-- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index ea27b4d0ae9..aa04deee577 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -9,11 +9,18 @@ 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; @@ -33,10 +40,15 @@ BlockIdWidget.prototype.render = function(parent,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(!event || !event.toBlockId) return event; - if(event.toBlockId !== this.id) return event; - if(this.tiddlerTitle && event.navigateTo !== this.tiddlerTitle) return 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) { @@ -54,6 +66,14 @@ BlockIdWidget.prototype.hookNavigatedEvent = function(event) { return false; }; +BlockIdWidget.prototype.hookNavigationAddHistoryEvent = function(event) { + // DEBUG: console this._isNavigateToHere(event) + console.log(`this._isNavigateToHere(event)`, this._isNavigateToHere(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) { @@ -85,10 +105,6 @@ BlockIdWidget.prototype._scrollToBlockAndHighlight = function(element) { }, 50); }; -BlockIdWidget.prototype.removeChildDomNodes = function() { - $tw.hooks.removeHook("th-navigated",this.hookNavigatedEvent); -}; - /* Compute the internal state of the widget */ diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index 372a45bebd2..f336f646343 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -161,7 +161,7 @@ LinkWidget.prototype.handleClickEvent = function(event) { navigateFromClientRight: bounds.right, navigateFromClientBottom: bounds.bottom, navigateFromClientHeight: bounds.height, - navigateSuppressNavigation: this.toBlockId || event.metaKey || event.ctrlKey || (event.button === 1), + navigateSuppressNavigation: event.metaKey || event.ctrlKey || (event.button === 1), metaKey: event.metaKey, ctrlKey: event.ctrlKey, altKey: event.altKey, diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 1ce51d3022b..84d69dd6b23 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -150,6 +150,7 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) { event = $tw.hooks.invokeHook("th-navigating",event); if(event.navigateTo) { this.addToStory(event.navigateTo,event.navigateFromTitle); + event = $tw.hooks.invokeHook("th-navigating-add-history",event); if(!event.navigateSuppressNavigation) { this.addToHistory(event.navigateTo,event.navigateFromClientRect); } diff --git a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid index 3e0b3296470..6b0dfaf5b6a 100644 --- a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid @@ -1,6 +1,6 @@ caption: Block Level Links created: 20230916061138153 -modified: 20230917121127276 +modified: 20230917122221226 tags: WikiText title: Block Level Links in WikiText type: text/vnd.tiddlywiki @@ -35,7 +35,7 @@ Some block, for example, code block, can't be suffixed by `^id`, but we can add ">> -! Link to the block ID ^linkto091607 +! Link to the block ID ^091607 Adding `^blockID` after the title in the link, will make this link highlight the block with that ID. diff --git a/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid index b7aa53faba8..ae80c708893 100644 --- a/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid @@ -1,6 +1,6 @@ caption: Linking created: 20131205155230596 -modified: 20230916070722108 +modified: 20230917121659927 tags: WikiText title: Linking in WikiText type: text/vnd.tiddlywiki @@ -128,4 +128,4 @@ See [[Anchor Links using HTML]] for more information. You can link to a specific block within a tiddler using `^blockId` syntax. You will also get block level backlinks with this technique. Some examples are in [[BlockIdWidget^exampleid1]]. -See [[Block Level Links in WikiText^linkto091607]] for more information. +See [[Block Level Links in WikiText^091607]] for more information. From 0d18b25b26a77f4a7b3bf0ede984792610514d42 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Mon, 18 Sep 2023 11:17:32 +0800 Subject: [PATCH 19/30] Update blockid.js --- core/modules/widgets/blockid.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js index aa04deee577..e2152c88fd2 100644 --- a/core/modules/widgets/blockid.js +++ b/core/modules/widgets/blockid.js @@ -67,8 +67,6 @@ BlockIdWidget.prototype.hookNavigatedEvent = function(event) { }; BlockIdWidget.prototype.hookNavigationAddHistoryEvent = function(event) { - // DEBUG: console this._isNavigateToHere(event) - console.log(`this._isNavigateToHere(event)`, this._isNavigateToHere(event)); if(!this._isNavigateToHere(event)) return event; event.navigateSuppressNavigation = true; return event; From dfa060024e5b15ab55d73937d117b9821815ff01 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Fri, 22 Sep 2023 23:10:06 +0800 Subject: [PATCH 20/30] docs: about why history change will reflect on storyview --- .../rules/{blockid.js => anchor.js} | 20 +-- .../parsers/wikiparser/rules/prettylink.js | 8 +- core/modules/widgets/anchor.js | 59 ++++++++ core/modules/widgets/blockid.js | 131 ------------------ core/modules/widgets/link.js | 4 +- .../BlockIdWidget.tid => AnchorWidget.tid} | 14 +- .../Block Level Links in WikiText.tid | 8 +- 7 files changed, 87 insertions(+), 157 deletions(-) rename core/modules/parsers/wikiparser/rules/{blockid.js => anchor.js} (69%) create mode 100644 core/modules/widgets/anchor.js delete mode 100644 core/modules/widgets/blockid.js rename editions/tw5.com/tiddlers/{widgets/BlockIdWidget.tid => AnchorWidget.tid} (71%) diff --git a/core/modules/parsers/wikiparser/rules/blockid.js b/core/modules/parsers/wikiparser/rules/anchor.js similarity index 69% rename from core/modules/parsers/wikiparser/rules/blockid.js rename to core/modules/parsers/wikiparser/rules/anchor.js index 57da17d7988..cede453cbdd 100644 --- a/core/modules/parsers/wikiparser/rules/blockid.js +++ b/core/modules/parsers/wikiparser/rules/anchor.js @@ -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: [] }]; diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js index bfca074f1b7..5eb4edde839 100644 --- a/core/modules/parsers/wikiparser/rules/prettylink.js +++ b/core/modules/parsers/wikiparser/rules/prettylink.js @@ -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 diff --git a/core/modules/widgets/anchor.js b/core/modules/widgets/anchor.js new file mode 100644 index 00000000000..201caa22285 --- /dev/null +++ b/core/modules/widgets/anchor.js @@ -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; diff --git a/core/modules/widgets/blockid.js b/core/modules/widgets/blockid.js deleted file mode 100644 index e2152c88fd2..00000000000 --- a/core/modules/widgets/blockid.js +++ /dev/null @@ -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; diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index f336f646343..e588903e4ed 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -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"); diff --git a/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid b/editions/tw5.com/tiddlers/AnchorWidget.tid similarity index 71% rename from editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid rename to editions/tw5.com/tiddlers/AnchorWidget.tid index 6f7d6a55695..d587753d375 100644 --- a/editions/tw5.com/tiddlers/widgets/BlockIdWidget.tid +++ b/editions/tw5.com/tiddlers/AnchorWidget.tid @@ -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 -< +< -[[Link to BlockLevelLinksID1|BlockIdWidget^BlockLevelLinksID1]] +[[Link to BlockLevelLinksID1|AnchorWidget^BlockLevelLinksID1]] """>> < +ID is here:<$anchor id="BlockLevelLinksID2" previousSibling="yes"/> -[[Link to BlockLevelLinksID2|BlockIdWidget^BlockLevelLinksID2]] +[[Link to BlockLevelLinksID2|AnchorWidget^BlockLevelLinksID2]] """>> diff --git a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid index 6b0dfaf5b6a..16384b18e16 100644 --- a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid @@ -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 +<> 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 <> -<> \ No newline at end of file +<> + +<> \ No newline at end of file From 436343ce1b9e84a1359fc27d52770d28e41dc3ab Mon Sep 17 00:00:00 2001 From: linonetwo Date: Fri, 22 Sep 2023 23:14:22 +0800 Subject: [PATCH 21/30] refactor: use history mechanism for block level navigation --- core/modules/story.js | 4 ++-- core/modules/storyviews/classic.js | 15 ++++++++++++++- core/modules/utils/dom/scroller.js | 20 +++++++++++++++++++- core/modules/widgets/anchor.js | 5 +++-- core/modules/widgets/navigator.js | 8 ++++---- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/core/modules/story.js b/core/modules/story.js index 16ff5a74a42..c7b09ed5d38 100644 --- a/core/modules/story.js +++ b/core/modules/story.js @@ -90,12 +90,12 @@ Story.prototype.saveStoryList = function(storyList) { )); }; -Story.prototype.addToHistory = function(navigateTo,navigateFromClientRect) { +Story.prototype.addToHistory = function(navigateTo,fromPageRect,anchor) { var titles = $tw.utils.isArray(navigateTo) ? navigateTo : [navigateTo]; // Add a new record to the top of the history stack var historyList = this.wiki.getTiddlerData(this.historyTitle,[]); $tw.utils.each(titles,function(title) { - historyList.push({title: title, fromPageRect: navigateFromClientRect}); + historyList.push({title: title, fromPageRect: fromPageRect, anchor: anchor}); }); this.wiki.setTiddlerData(this.historyTitle,historyList,{"current-tiddler": titles[titles.length-1]}); }; diff --git a/core/modules/storyviews/classic.js b/core/modules/storyviews/classic.js index c2848c4359a..0b9d554c064 100644 --- a/core/modules/storyviews/classic.js +++ b/core/modules/storyviews/classic.js @@ -26,13 +26,26 @@ ClassicStoryView.prototype.navigateTo = function(historyInfo) { } var listItemWidget = this.listWidget.children[listElementIndex], targetElement = listItemWidget.findFirstDomNode(); + // If anchor is provided, find the element the anchor pointing to + var foundAnchor = false; + if(targetElement && historyInfo.anchor) { + var anchorElement = targetElement.querySelector("[data-anchor-id='" + historyInfo.anchor + "']"); + if(anchorElement) { + targetElement = anchorElement.parentNode; + var isBefore = anchorElement.dataset.anchorPreviousSibling === "true"; + if(isBefore) { + targetElement = targetElement.previousSibling; + } + foundAnchor = true; + } + } // Abandon if the list entry isn't a DOM element (it might be a text node) if(!targetElement || targetElement.nodeType === Node.TEXT_NODE) { return; } if(duration) { // Scroll the node into view - this.listWidget.dispatchEvent({type: "tm-scroll", target: targetElement}); + this.listWidget.dispatchEvent({type: "tm-scroll", target: targetElement, highlight: foundAnchor}); } else { targetElement.scrollIntoView(); } diff --git a/core/modules/utils/dom/scroller.js b/core/modules/utils/dom/scroller.js index 905bb275073..4166507d68d 100644 --- a/core/modules/utils/dom/scroller.js +++ b/core/modules/utils/dom/scroller.js @@ -49,7 +49,9 @@ Handle an event */ PageScroller.prototype.handleEvent = function(event) { if(event.type === "tm-scroll") { - var options = {}; + var options = { + highlight: event.highlight, + }; if($tw.utils.hop(event.paramObject,"animationDuration")) { options.animationDuration = event.paramObject.animationDuration; } @@ -65,14 +67,21 @@ PageScroller.prototype.handleEvent = function(event) { /* Handle a scroll event hitting the page document + +options: +- animationDuration: total time of scroll animation +- highlight: highlight the element after scrolling, to make it evident. Usually to focus an anchor in the middle of the tiddler. */ PageScroller.prototype.scrollIntoView = function(element,callback,options) { var self = this, duration = $tw.utils.hop(options,"animationDuration") ? parseInt(options.animationDuration) : $tw.utils.getAnimationDuration(), + highlight = options.highlight || false, srcWindow = element ? element.ownerDocument.defaultView : window; // Now get ready to scroll the body this.cancelScroll(srcWindow); this.startTime = Date.now(); + // toggle class to allow trigger the highlight animation + $tw.utils.removeClass(element,"tc-focus-highlight"); // Get the height of any position:fixed toolbars var toolbar = srcWindow.document.querySelector(".tc-adjust-top-of-scroll"), offset = 0; @@ -121,6 +130,15 @@ PageScroller.prototype.scrollIntoView = function(element,callback,options) { srcWindow.scrollTo(scrollPosition.x + (endX - scrollPosition.x) * t,scrollPosition.y + (endY - scrollPosition.y) * t); if(t < 1) { self.idRequestFrame = self.requestAnimationFrame.call(srcWindow,drawFrame); + } else { + // the animation is end. + if(highlight) { + 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); + } } }; drawFrame(); diff --git a/core/modules/widgets/anchor.js b/core/modules/widgets/anchor.js index 201caa22285..c9c6af091d5 100644 --- a/core/modules/widgets/anchor.js +++ b/core/modules/widgets/anchor.js @@ -23,8 +23,9 @@ AnchorWidget.prototype.render = function(parent,nextSibling) { 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"); + // if the actual block is before this node, we need to add a flag to the node + if(this.previousSibling) { + this.idNode.setAttribute("data-anchor-previous-sibling","true"); } this.idNode.className = "tc-anchor"; parent.insertBefore(this.idNode,nextSibling); diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 84d69dd6b23..423f6545929 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -138,9 +138,10 @@ NavigatorWidget.prototype.addToStory = function(title,fromTitle) { Add a new record to the top of the history stack title: a title string or an array of title strings fromPageRect: page coordinates of the origin of the navigation +anchor:optional anchor id in this tiddler */ -NavigatorWidget.prototype.addToHistory = function(title,fromPageRect) { - this.story.addToHistory(title,fromPageRect,this.historyTitle); +NavigatorWidget.prototype.addToHistory = function(title,fromPageRect,anchor) { + this.story.addToHistory(title,fromPageRect,anchor); }; /* @@ -150,9 +151,8 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) { event = $tw.hooks.invokeHook("th-navigating",event); if(event.navigateTo) { this.addToStory(event.navigateTo,event.navigateFromTitle); - event = $tw.hooks.invokeHook("th-navigating-add-history",event); if(!event.navigateSuppressNavigation) { - this.addToHistory(event.navigateTo,event.navigateFromClientRect); + this.addToHistory(event.navigateTo,event.navigateFromClientRect,event.toAnchor); } } $tw.hooks.invokeHook("th-navigated",event); From ebf84b59e865f5e0a35e992b6b4fb0e1c7abe5d9 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Fri, 22 Sep 2023 23:14:30 +0800 Subject: [PATCH 22/30] docs: about HistoryMechanism in dev doc --- editions/dev/tiddlers/mechanism/HistoryMechanism.tid | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 editions/dev/tiddlers/mechanism/HistoryMechanism.tid diff --git a/editions/dev/tiddlers/mechanism/HistoryMechanism.tid b/editions/dev/tiddlers/mechanism/HistoryMechanism.tid new file mode 100644 index 00000000000..81379f4fc6c --- /dev/null +++ b/editions/dev/tiddlers/mechanism/HistoryMechanism.tid @@ -0,0 +1,8 @@ +created: 20230922141042399 +modified: 20230922141423022 +tags: +title: HistoryMechanism + +The story view is created by [[$:/core/ui/PageTemplate/story]] core page template, which uses list widget to render tiddlers. In this way, page template will reflect to history's change. + +List widget has a `history="$:/HistoryList"` parameter, that will be used in list widget's `handleHistoryChanges` method, and pass to the `this.storyview.navigateTo`, you can read [[storyview module]] for how storyview use the changed history. \ No newline at end of file From 507d004a57b926a8cd7dd8bbdd66dc7357c3be21 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Fri, 22 Sep 2023 23:57:14 +0800 Subject: [PATCH 23/30] feat: adapt for other story views --- core/modules/storyviews/classic.js | 14 ++++++-------- core/modules/storyviews/pop.js | 13 ++++++++++++- core/modules/storyviews/zoomin.js | 15 ++++++++++++++- core/modules/utils/parsetree.js | 15 +++++++++++++++ core/modules/widgets/anchor.js | 20 ++++++++++++++++++-- 5 files changed, 65 insertions(+), 12 deletions(-) diff --git a/core/modules/storyviews/classic.js b/core/modules/storyviews/classic.js index 0b9d554c064..3470f7be2f7 100644 --- a/core/modules/storyviews/classic.js +++ b/core/modules/storyviews/classic.js @@ -28,14 +28,12 @@ ClassicStoryView.prototype.navigateTo = function(historyInfo) { targetElement = listItemWidget.findFirstDomNode(); // If anchor is provided, find the element the anchor pointing to var foundAnchor = false; - if(targetElement && historyInfo.anchor) { - var anchorElement = targetElement.querySelector("[data-anchor-id='" + historyInfo.anchor + "']"); - if(anchorElement) { - targetElement = anchorElement.parentNode; - var isBefore = anchorElement.dataset.anchorPreviousSibling === "true"; - if(isBefore) { - targetElement = targetElement.previousSibling; - } + if(listItemWidget && historyInfo.anchor) { + var anchorWidget = $tw.utils.findChildNodeInTree(listItemWidget, function(widget) { + return widget.anchorId === historyInfo.anchor; + }); + if(anchorWidget) { + targetElement = anchorWidget.findAnchorTargetDomNode() foundAnchor = true; } } diff --git a/core/modules/storyviews/pop.js b/core/modules/storyviews/pop.js index e2634e3d5b1..3b9f9e39cd3 100644 --- a/core/modules/storyviews/pop.js +++ b/core/modules/storyviews/pop.js @@ -23,12 +23,23 @@ PopStoryView.prototype.navigateTo = function(historyInfo) { } var listItemWidget = this.listWidget.children[listElementIndex], targetElement = listItemWidget.findFirstDomNode(); + // If anchor is provided, find the element the anchor pointing to + var foundAnchor = false; + if(listItemWidget && historyInfo.anchor) { + var anchorWidget = $tw.utils.findChildNodeInTree(listItemWidget, function(widget) { + return widget.anchorId === historyInfo.anchor; + }); + if(anchorWidget) { + targetElement = anchorWidget.findAnchorTargetDomNode() + foundAnchor = true; + } + } // Abandon if the list entry isn't a DOM element (it might be a text node) if(!targetElement || targetElement.nodeType === Node.TEXT_NODE) { return; } // Scroll the node into view - this.listWidget.dispatchEvent({type: "tm-scroll", target: targetElement}); + this.listWidget.dispatchEvent({type: "tm-scroll", target: targetElement, highlight: foundAnchor}); }; PopStoryView.prototype.insert = function(widget) { diff --git a/core/modules/storyviews/zoomin.js b/core/modules/storyviews/zoomin.js index d02f705e73e..0390473720a 100644 --- a/core/modules/storyviews/zoomin.js +++ b/core/modules/storyviews/zoomin.js @@ -51,6 +51,16 @@ ZoominListView.prototype.navigateTo = function(historyInfo) { } var listItemWidget = this.listWidget.children[listElementIndex], targetElement = listItemWidget.findFirstDomNode(); + // If anchor is provided, find the element the anchor pointing to + var anchorElement = null; + if(listItemWidget && historyInfo.anchor) { + var anchorWidget = $tw.utils.findChildNodeInTree(listItemWidget, function(widget) { + return widget.anchorId === historyInfo.anchor; + }); + if(anchorWidget) { + anchorElement = anchorWidget.findAnchorTargetDomNode() + } + } // Abandon if the list entry isn't a DOM element (it might be a text node) if(!targetElement) { return; @@ -119,7 +129,10 @@ ZoominListView.prototype.navigateTo = function(historyInfo) { },duration); } // Scroll the target into view -// $tw.pageScroller.scrollIntoView(targetElement); + if(anchorElement) { + this.listWidget.dispatchEvent({type: "tm-scroll", target: anchorElement, highlight: true}); + } + // $tw.pageScroller.scrollIntoView(targetElement); }; /* diff --git a/core/modules/utils/parsetree.js b/core/modules/utils/parsetree.js index a74b8f3f81e..7ee2de09e35 100644 --- a/core/modules/utils/parsetree.js +++ b/core/modules/utils/parsetree.js @@ -103,6 +103,21 @@ exports.findParseTreeNode = function(nodeArray,search) { return undefined; }; +exports.findChildNodeInTree = function(root,searchFn) { + if(searchFn(root)) { + return root; + } + if(root.children && root.children.length > 0) { + for(var i=0; i Date: Fri, 22 Sep 2023 23:59:47 +0800 Subject: [PATCH 24/30] fix: no need for setTimeout --- core/modules/utils/dom/scroller.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/modules/utils/dom/scroller.js b/core/modules/utils/dom/scroller.js index 4166507d68d..f9cc8c9d9cd 100644 --- a/core/modules/utils/dom/scroller.js +++ b/core/modules/utils/dom/scroller.js @@ -134,10 +134,7 @@ PageScroller.prototype.scrollIntoView = function(element,callback,options) { // the animation is end. if(highlight) { 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); + $tw.utils.addClass(element,"tc-focus-highlight"); } } }; From 13f6bd84e97f4aa5a1c87a4de5b5ed1b4bf217e8 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 23 Sep 2023 00:05:36 +0800 Subject: [PATCH 25/30] refactor: remove unusned hook --- boot/boot.js | 14 -------------- core/modules/widgets/navigator.js | 1 - editions/dev/tiddlers/new/Hook__th-navigated.tid | 11 ----------- 3 files changed, 26 deletions(-) delete mode 100644 editions/dev/tiddlers/new/Hook__th-navigated.tid diff --git a/boot/boot.js b/boot/boot.js index 2636cb8f4a2..06d4628c046 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -2674,20 +2674,6 @@ $tw.hooks.addHook = function(hookName,definition) { } }; -/* -Delete hooks from the hashmap -*/ -$tw.hooks.removeHook = function(hookName,definition) { - if($tw.utils.hop($tw.hooks.names,hookName)) { - var index = $tw.hooks.names[hookName].findIndex(function(hook) { - return hook === definition; - }); - if(index !== -1) { - $tw.hooks.names[hookName].splice(index, 1); - } - } -}; - /* Invoke the hook by key */ diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 423f6545929..ed5d1929ec7 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -155,7 +155,6 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) { this.addToHistory(event.navigateTo,event.navigateFromClientRect,event.toAnchor); } } - $tw.hooks.invokeHook("th-navigated",event); return false; }; diff --git a/editions/dev/tiddlers/new/Hook__th-navigated.tid b/editions/dev/tiddlers/new/Hook__th-navigated.tid deleted file mode 100644 index 0315529baf8..00000000000 --- a/editions/dev/tiddlers/new/Hook__th-navigated.tid +++ /dev/null @@ -1,11 +0,0 @@ -tags: HookMechanism -title: Hook: th-navigated -type: text/vnd.tiddlywiki - -This hook allows plugins to do things after navigation takes effect. - -Hook function parameters are same as [[Hook: th-navigating]]: - -Return value: - -* possibly modified event object From 13d0c5cbd53d2fbd709a5d53f4fa02ff6ade6cc2 Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sat, 23 Sep 2023 00:05:50 +0800 Subject: [PATCH 26/30] docs: about toAnchor added in 5.3.2 --- editions/tw5.com/tiddlers/{ => widgets}/AnchorWidget.tid | 0 editions/tw5.com/tiddlers/widgets/LinkWidget.tid | 1 + 2 files changed, 1 insertion(+) rename editions/tw5.com/tiddlers/{ => widgets}/AnchorWidget.tid (100%) diff --git a/editions/tw5.com/tiddlers/AnchorWidget.tid b/editions/tw5.com/tiddlers/widgets/AnchorWidget.tid similarity index 100% rename from editions/tw5.com/tiddlers/AnchorWidget.tid rename to editions/tw5.com/tiddlers/widgets/AnchorWidget.tid diff --git a/editions/tw5.com/tiddlers/widgets/LinkWidget.tid b/editions/tw5.com/tiddlers/widgets/LinkWidget.tid index 26e78c422e6..9386273228f 100644 --- a/editions/tw5.com/tiddlers/widgets/LinkWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/LinkWidget.tid @@ -16,6 +16,7 @@ The content of the link widget is rendered within the `` tag representing the |!Attribute |!Description | |to |The title of the target tiddler for the link (defaults to the [[current tiddler|Current Tiddler]]) | +|toAnchor |<<.from-version "5.3.2">> Optional id of the anchor for [[Block Level Links in WikiText]] | |aria-label |Optional accessibility label | |tooltip |Optional tooltip WikiText | |tabindex |Optional numeric [[tabindex|https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex]] | From 9c2c6d724ea9cfa7a0ac93371699681404d53222 Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Sun, 9 Jun 2024 23:01:07 +0800 Subject: [PATCH 27/30] fix: link end position should add ^id 's length --- core/modules/parsers/wikiparser/rules/prettylink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js index b1172ac4ae3..f577dba8c57 100644 --- a/core/modules/parsers/wikiparser/rules/prettylink.js +++ b/core/modules/parsers/wikiparser/rules/prettylink.js @@ -40,7 +40,7 @@ exports.parse = function() { textEndPos = this.matchRegExp.lastIndex - 2; } var linkStart = this.match[2] ? (start + this.match[1].length + 1) : start; - var linkEnd = linkStart + link.length; + var linkEnd = anchor ? (linkStart + link.length + 1 + anchor.length) : (linkStart + link.length); if($tw.utils.isLinkExternal(link)) { // add back the part after `^` to the ext link, if it happen to has one. if(anchor) { From 7e9cadf6b0071eeedaef6d0648f37b54881d91b5 Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Sun, 9 Jun 2024 23:05:37 +0800 Subject: [PATCH 28/30] Revert "fix: link end position should add ^id 's length" This reverts commit 9c2c6d724ea9cfa7a0ac93371699681404d53222. --- core/modules/parsers/wikiparser/rules/prettylink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js index f577dba8c57..b1172ac4ae3 100644 --- a/core/modules/parsers/wikiparser/rules/prettylink.js +++ b/core/modules/parsers/wikiparser/rules/prettylink.js @@ -40,7 +40,7 @@ exports.parse = function() { textEndPos = this.matchRegExp.lastIndex - 2; } var linkStart = this.match[2] ? (start + this.match[1].length + 1) : start; - var linkEnd = anchor ? (linkStart + link.length + 1 + anchor.length) : (linkStart + link.length); + var linkEnd = linkStart + link.length; if($tw.utils.isLinkExternal(link)) { // add back the part after `^` to the ext link, if it happen to has one. if(anchor) { From 92ca17a08bbcfbbc15388b7147613f379f4005a8 Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Sun, 9 Jun 2024 23:15:41 +0800 Subject: [PATCH 29/30] fix: correct anchor start end --- core/modules/parsers/wikiparser/rules/anchor.js | 15 +++++++++------ .../parsers/wikiparser/rules/prettylink.js | 6 ++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/core/modules/parsers/wikiparser/rules/anchor.js b/core/modules/parsers/wikiparser/rules/anchor.js index cede453cbdd..831ec105c68 100644 --- a/core/modules/parsers/wikiparser/rules/anchor.js +++ b/core/modules/parsers/wikiparser/rules/anchor.js @@ -18,8 +18,8 @@ Instantiate parse rule exports.init = function(parser) { this.parser = parser; // 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. + // 1. inlineId: located on the end of the line, with a space before it, means it's the id of the current block. + // 2. blockId: 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,19 @@ exports.parse = function() { // Move past the match this.parser.pos = this.matchRegExp.lastIndex; // will be one of following case, another will be undefined - var anchorId = this.match[1]; - var anchorBeforeId = this.match[2]; + var inlineId = this.match[1]; + var blockId = this.match[2]; + var id = inlineId || blockId || ''; + var anchorStart = this.parser.pos; + var anchorEnd = anchorStart + id.length; // Parse tree nodes to return return [{ type: "anchor", attributes: { - id: {type: "string", value: anchorId || anchorBeforeId}, + id: {type: "string", value: id, start: anchorStart, end: anchorEnd}, // `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(anchorBeforeId) ? "yes" : ""}, + previousSibling: {type: "string", value: Boolean(blockId) ? "yes" : ""}, }, children: [] }]; diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js index b1172ac4ae3..bed0365d3f0 100644 --- a/core/modules/parsers/wikiparser/rules/prettylink.js +++ b/core/modules/parsers/wikiparser/rules/prettylink.js @@ -42,7 +42,7 @@ exports.parse = function() { var linkStart = this.match[2] ? (start + this.match[1].length + 1) : start; var linkEnd = linkStart + link.length; if($tw.utils.isLinkExternal(link)) { - // add back the part after `^` to the ext link, if it happen to has one. + // add back the part after `^` to the ext link, if it happen to has one. Here is is not an anchor, but a part of the external URL. if(anchor) { link = link + "^" + anchor; } @@ -60,11 +60,13 @@ exports.parse = function() { }] }]; } else { + var anchorStart = anchor ? (linkEnd + 1) : linkEnd; + var anchorEnd = anchorStart + anchor.length; return [{ type: "link", attributes: { to: {type: "string", value: link, start: linkStart, end: linkEnd}, - toAnchor: {type: "string", value: anchor}, + toAnchor: {type: "string", value: anchor, start: anchorStart, end: anchorEnd}, }, children: [{ type: "text", text: text, start: start, end: textEndPos From cc12af5ef60b75197c5b00806899a255a80ec3d6 Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Sun, 9 Jun 2024 23:41:00 +0800 Subject: [PATCH 30/30] docs: fix BlockIdWidget --- .../tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid | 2 +- editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid index 16384b18e16..71b3dabbdb8 100644 --- a/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Block Level Links in WikiText.tid @@ -11,7 +11,7 @@ The basic syntax for block id is: <> -# Don't forget the space between the end of the line and the `^`. +# You should add a space between the end of your text and the `^`. # And there is no space between `^` and the id. # ID can contain any char other than `^` and space ` `. diff --git a/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid index ae80c708893..e99e76e060b 100644 --- a/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Linking in WikiText.tid @@ -126,6 +126,6 @@ See [[Anchor Links using HTML]] for more information. ! Linking within tiddlers - Link to block -You can link to a specific block within a tiddler using `^blockId` syntax. You will also get block level backlinks with this technique. Some examples are in [[BlockIdWidget^exampleid1]]. +You can link to a specific block within a tiddler using `^blockId` syntax. You will also get block level backlinks with this technique. See [[Block Level Links in WikiText^091607]] for more information.