Compare commits

...

30 Commits

Author SHA1 Message Date
lin onetwo 02e9c3e07a
Merge 13d0c5cbd5 into a081e58273 2024-04-25 23:52:03 +08:00
Matt Lauber a081e58273
HTTP Client: Return success calls for all 2XX response codes (#8150)
APIs especially use 2XX response codes outside of 200, 201, 204 for responding to responses.  Treat all "Successful" response codes (i.e. anything between 200-299) as successes, and pass the responseText.
2024-04-16 16:24:53 +01:00
Joshua Fontany 5f74f4c2fa
Fix bug 7878: Save command (#8140)
* first pass at fixing bug 7878, needs testing

* clarify default behaviour in comment

* fix property typo, tested and works as intended

* remove debugger
2024-04-11 21:54:46 +01:00
Joshua Fontany 9167b190d2
Fix bug 8138: server cache-control (#8141)
* cache-control no-store by default

* clarify comment spec reference

* comment typo

* fix else formatting

* Update server.js

allow route definitions to set their own cache-control
2024-04-11 19:23:32 +01:00
linonetwo 13d0c5cbd5 docs: about toAnchor added in 5.3.2 2023-09-23 00:05:50 +08:00
linonetwo 13f6bd84e9 refactor: remove unusned hook 2023-09-23 00:05:36 +08:00
linonetwo 48d2eff33c fix: no need for setTimeout 2023-09-22 23:59:47 +08:00
linonetwo 507d004a57 feat: adapt for other story views 2023-09-22 23:57:14 +08:00
linonetwo ebf84b59e8 docs: about HistoryMechanism in dev doc 2023-09-22 23:14:30 +08:00
linonetwo 436343ce1b refactor: use history mechanism for block level navigation 2023-09-22 23:14:22 +08:00
linonetwo dfa060024e docs: about why history change will reflect on storyview 2023-09-22 23:10:06 +08:00
linonetwo 0d18b25b26 Update blockid.js 2023-09-18 11:17:32 +08:00
linonetwo fef444cb1c fix: when id not exist, still navigate to the tiddler 2023-09-17 20:25:40 +08:00
linonetwo cff0240ac8 feat: allow using any char in id 2023-09-17 20:11:59 +08:00
linonetwo a5c2f8558e feat: allow different tiddler have same block id in the text, and only jump to the one with a same tiddler title. 2023-09-17 20:03:15 +08:00
linonetwo 07130c2ad0 fix: code style and types 2023-09-16 15:37:46 +08:00
linonetwo 3bcd822c97 fix: scroll too slow if tiddler already appear 2023-09-16 15:33:03 +08:00
linonetwo c0b6b7988a fix: element not exist 2023-09-16 15:22:42 +08:00
linonetwo 7200f73cdc refactor: use th-navigated to simplify the code 2023-09-16 15:12:56 +08:00
linonetwo db83401a69 docs: about usage 2023-09-16 14:26:37 +08:00
linonetwo e6445b79c5 docs: why add hook 2023-09-16 13:40:55 +08:00
linonetwo 0863916f9b fix: wait until animation finish and dom show 2023-09-16 05:13:40 +08:00
linonetwo d8bdd09eb0 fix: ensure hightlight is visible 2023-09-16 05:00:10 +08:00
linonetwo 6b6124369c fix: param maybe null 2023-09-16 04:49:50 +08:00
linonetwo 3d8ade3ebe feat: redirect tm-focus-selector event to check parent or sibling 2023-09-16 04:45:07 +08:00
linonetwo b956e72536 fix: properly match blockId and pass it to ast 2023-09-16 04:09:21 +08:00
linonetwo d5e9d2a71b feat: allow wiki pretty link to have id 2023-09-16 02:49:58 +08:00
linonetwo 18236b547f feat: allow add id for code block 2023-09-16 02:38:06 +08:00
linonetwo 4c407c28e4 refactor: use blockid for shorter name 2023-09-15 22:43:23 +08:00
linonetwo 5b6f5b2704 feat: parse and show ^id 2023-09-15 22:17:19 +08:00
22 changed files with 345 additions and 27 deletions

View File

@ -43,7 +43,9 @@ Saves individual tiddlers in their raw text or binary format to the specified fi
directory: path.resolve(self.commander.outputPath),
pathFilters: [filenameFilter],
wiki: wiki,
fileInfo: {}
fileInfo: {
overwrite: true
}
});
if(self.commander.verbose) {
console.log("Saving \"" + title + "\" to \"" + fileInfo.filepath + "\"");

View File

@ -0,0 +1,46 @@
/*\
title: $:/core/modules/parsers/wikiparser/rules/anchor.js
type: application/javascript
module-type: wikirule
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 = "anchor";
exports.types = {inline: true};
/*
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.
this.matchRegExp = /[ ]\^(\S+)$|^\^(\S+)$/mg;
};
/*
Parse the most recent match
*/
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];
// Parse tree nodes to return
return [{
type: "anchor",
attributes: {
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(anchorBeforeId) ? "yes" : ""},
},
children: []
}];
};

View File

@ -23,8 +23,8 @@ exports.types = {inline: true};
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
this.matchRegExp = /\[\[(.*?)(?:\|(.*?))?\]\]/mg;
// Regexp to match `[[Alias|Title^blockId]]`, the `Alias|` and `^blockId` 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;
link = this.match[2] || text,
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(anchor) {
link = link + "^" + anchor;
}
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},
toAnchor: {type: "string", value: anchor},
},
children: [{
type: "text", text: text

View File

@ -140,6 +140,11 @@ function sendResponse(request,response,statusCode,headers,data,encoding) {
return;
}
}
} else {
// RFC 7231, 6.1. Overview of Status Codes:
// Browser clients may cache 200, 203, 204, 206, 300, 301,
// 404, 405, 410, 414, and 501 unless given explicit cache controls
headers["Cache-Control"] = headers["Cache-Control"] || "no-store";
}
/*
If the gzip=yes is set, check if the user agent permits compression. If so,

View File

@ -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]});
};

View File

@ -26,13 +26,24 @@ 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(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;
}
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();
}

View File

@ -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) {

View File

@ -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);
};
/*

View File

@ -283,7 +283,7 @@ exports.httpRequest = function(options) {
// Set up the state change handler
request.onreadystatechange = function() {
if(this.readyState === 4) {
if(this.status === 200 || this.status === 201 || this.status === 204) {
if(this.status >= 200 && this.status < 300) {
// Success!
options.callback(null,this[returnProp],this);
return;

View File

@ -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,12 @@ 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 });
$tw.utils.addClass(element,"tc-focus-highlight");
}
}
};
drawFrame();

View File

@ -316,11 +316,13 @@ Options include:
pathFilters: optional array of filters to be used to generate the base path
wiki: optional wiki for evaluating the pathFilters
fileInfo: an existing fileInfo object to check against
fileInfo.overwrite: if true, turns off filename clash numbers (defaults to false)
*/
exports.generateTiddlerFilepath = function(title,options) {
var directory = options.directory || "",
extension = options.extension || "",
originalpath = (options.fileInfo && options.fileInfo.originalpath) ? options.fileInfo.originalpath : "",
overwrite = options.fileInfo && options.fileInfo.overwrite || false,
filepath;
// Check if any of the pathFilters applies
if(options.pathFilters && options.wiki) {
@ -381,19 +383,20 @@ exports.generateTiddlerFilepath = function(title,options) {
filepath += char.charCodeAt(0).toString();
});
}
// Add a uniquifier if the file already exists
var fullPath, oldPath = (options.fileInfo) ? options.fileInfo.filepath : undefined,
// Add a uniquifier if the file already exists (default)
var fullPath = path.resolve(directory, filepath + extension);
if (!overwrite) {
var oldPath = (options.fileInfo) ? options.fileInfo.filepath : undefined,
count = 0;
do {
fullPath = path.resolve(directory,filepath + (count ? "_" + count : "") + extension);
if(oldPath && oldPath == fullPath) {
break;
}
count++;
} while(fs.existsSync(fullPath));
do {
fullPath = path.resolve(directory,filepath + (count ? "_" + count : "") + extension);
if(oldPath && oldPath == fullPath) break;
count++;
} while(fs.existsSync(fullPath));
}
// If the last write failed with an error, or if path does not start with:
// the resolved options.directory, the resolved wikiPath directory, the wikiTiddlersPath directory,
// or the 'originalpath' directory, then $tw.utils.encodeURIComponentExtended() and resolve to tiddler directory.
// or the 'originalpath' directory, then $tw.utils.encodeURIComponentExtended() and resolve to options.directory.
var writePath = $tw.hooks.invokeHook("th-make-tiddler-path",fullPath,fullPath),
encode = (options.fileInfo || {writeError: false}).writeError == true;
if(!encode) {

View File

@ -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<root.children.length; i++) {
var result = exports.findChildNodeInTree(root.children[i], searchFn);
if(result) {
return result;
}
}
}
return undefined;
};
/*
Helper to get the text of a parse tree node or array of nodes
*/

View File

@ -0,0 +1,76 @@
/*\
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.anchorId);
this.idNode.setAttribute("data-anchor-title",this.tiddlerTitle);
// 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);
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.anchorId = this.getAttribute("id");
this.tiddlerTitle = this.getVariable("currentTiddler");
this.previousSibling = this.getAttribute("previousSibling") === "yes";
// Make the child widgets
this.makeChildWidgets();
};
/*
Find the DOM node pointed by this anchor
*/
Widget.prototype.findAnchorTargetDomNode = function() {
if(!this.idNode) {
return null;
}
// the actual block is always at the parent level
targetElement = this.idNode.parentNode;
// need to check if the block is before this node
if(this.previousSibling) {
targetElement = targetElement.previousSibling;
}
return targetElement;
};
/*
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;

View File

@ -160,6 +160,7 @@ LinkWidget.prototype.handleClickEvent = function(event) {
this.dispatchEvent({
type: "tm-navigate",
navigateTo: this.to,
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
@ -190,6 +191,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.toAnchor = this.getAttribute("toAnchor");
this.tooltip = this.getAttribute("tooltip");
this["aria-label"] = this.getAttribute("aria-label");
this.linkClasses = this.getAttribute("class");

View File

@ -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);
};
/*
@ -151,7 +152,7 @@ NavigatorWidget.prototype.handleNavigateEvent = function(event) {
if(event.navigateTo) {
this.addToStory(event.navigateTo,event.navigateFromTitle);
if(!event.navigateSuppressNavigation) {
this.addToHistory(event.navigateTo,event.navigateFromClientRect);
this.addToHistory(event.navigateTo,event.navigateFromClientRect,event.toAnchor);
}
}
return false;

View File

@ -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<pattern>]" variable="listItem">

View File

@ -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.

View File

@ -0,0 +1,34 @@
caption: block id
created: 20230916061829840
modified: 20230922150245402
tags: Widgets
title: AnchorWidget
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 `<$anchor>` 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^🤗→AddingIDforblock]] for WikiText syntax of block ID.
! Example
<<wikitext-example-without-html """The block id widget is invisible, and is usually located at the end of the line. ID is here:<$anchor id="BlockLevelLinksID1"/>
[[Link to BlockLevelLinksID1|AnchorWidget^BlockLevelLinksID1]]
""">>
<<wikitext-example """You can refer to the block that is a line before the block id widget. Make sure block id widget itself is in a block (paragraph).
ID is here:<$anchor id="BlockLevelLinksID2" previousSibling="yes"/>
[[Link to BlockLevelLinksID2|AnchorWidget^BlockLevelLinksID2]]
""">>

View File

@ -16,6 +16,7 @@ The content of the link widget is rendered within the `<a>` 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]] |

View File

@ -0,0 +1,48 @@
caption: Block Level Links
created: 20230916061138153
modified: 20230922150740619
tags: WikiText
title: Block Level Links in WikiText
type: text/vnd.tiddlywiki
<<wikitext-example-without-html src:"! Adding ID for block ^🤗→AddingIDforblock">>
The basic syntax for block id is:
<<wikitext-example src:"There is a block id that is invisible, but you can find it using developer tool's inspect element feature. ^BlockLevelLinksID1">>
# Don't forget the space between the end of the line and the `^`.
# And there is no space between `^` and the id.
# ID can contain any char other than `^` and space ` `.
And this block id widget will be rendered as an invisible element:
```html
<span class="tc-block-id" data-block-id="BlockLevelLinksID1" data-block-title="Block Level Links in WikiText"></span>
```
!! 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.
<<wikitext-example src:"```css
.main {
display: none;
}
```
^BlockLevelLinksID2
">>
! Link to the block ID ^091607
Adding `^blockID` after the title in the link, will make this link highlight the block with that ID.
<<wikitext-example-without-html src:"[[Link to BlockLevelLinksID1|Block Level Links in WikiText^BlockLevelLinksID1]]">>
<<wikitext-example-without-html src:"[[Link to BlockLevelLinksID2|Block Level Links in WikiText^BlockLevelLinksID2]]">>
<<wikitext-example-without-html src:"[[Link to Title|Block Level Links in WikiText^🤗→AddingIDforblock]]">>
<<wikitext-example-without-html src:"[[Link to Non-existing anchor|Block Level Links in WikiText^😂❎]]">>

View File

@ -1,6 +1,6 @@
caption: Linking
created: 20131205155230596
modified: 20211230145939554
modified: 20230917121659927
tags: WikiText
title: Linking in WikiText
type: text/vnd.tiddlywiki
@ -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^091607]] for more information.

View File

@ -2446,6 +2446,21 @@ html body.tc-body.tc-single-tiddler-window {
color: <<colour alert-highlight>>;
}
@keyframes fade-highlight {
0% {
background-color: <<colour highlight-background>>;
border-color: <<colour highlight-background>>;
}
100% {
background-color: transparent;
border-color: transparent;
}
}
.tc-focus-highlight {
animation: fade-highlight 2s forwards;
}
@media (min-width: <<sidebarbreakpoint>>) {
.tc-static-alert {