1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-02-07 02:30:22 +00:00

Compare commits

...

11 Commits

Author SHA1 Message Date
Jeremy Ruston
a83855329a Minor docs improvement 2026-02-06 16:29:50 +00:00
Jeremy Ruston
364e2f2cb5 Merge branch 'master' into background-actions 2026-02-06 16:25:04 +00:00
Jeremy Ruston
67f13c585d Fix #9055 docs issue identified in https://github.com/TiddlyWiki/TiddlyWiki5/pull/9055#pullrequestreview-3732792103 2026-02-06 16:24:14 +00:00
Jeremy Ruston
6e85d99ed5 Add changenote 2026-02-06 14:54:26 +00:00
Jeremy Ruston
5791627a50 Initial docs from #8702
...which need to also be turned into a changenote
2026-02-06 14:18:51 +00:00
Jeremy Ruston
32405c3a71 Initial commit cherry picked from #8702 2026-02-06 14:11:05 +00:00
Mario Pietsch
1bbb7fd53b Add start- and endactions to link-widget, list-links-draggable and list-tagged-draggable macros (#9621)
* Add start- endactions to link-widget + documentation

* Add start- endactions to list-links-draggable and list-tagged-draggable macros

* Add changenote

* Update docs with actionTiddler info
2026-02-06 14:23:42 +01:00
Saq Imtiaz
599933c34d Remove 'tiddlywiki/dom-to-image' dependency
Removed 'tiddlywiki/dom-to-image' from the tiddlywiki.info
2026-02-06 12:01:22 +01:00
Simon Huber
b1ccb82e0a Update body-toolbar-button - use value={{{ [subfilter{!!button-classes}] :and[join[ ]] }}} (#9585)
* Update body-toolbar-button - use `filter={{!!button-classes}}`

* Update body-toolbar-button.tid

* Create #9566 - button-classes.tid

* Update and rename #9566 - button-classes.tid to #9585 - button-classes.tid

* :and[join[ ]]

* Update how-to guide to mention button-classes
2026-02-06 11:50:43 +01:00
Mohammad Rahmani
ea648c7d15 Update StoryList.tid to Work in Different Layouts (#9094)
* Update StoryList.tid to Work in Different Layouts

See https://talk.tiddlywiki.org/t/stay-organized-by-using-multiple-desktops-in-tiddlywiki/12752/7

All PageTemplate elements are enclosed within that `$navigator` widget, so it should be possible to use those two variables in filters instead of hard-coding the values.

* Apply suggestion from @saqimtiaz

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2026-02-06 11:46:18 +01:00
Jeremy Ruston
a3a4e91751 New dom-to-image plugin for saving DOM nodes as an image (#8810)
* Add tm-save-dom-to-image message

* Temporarily include the geospatial plugin in the Netlify previews

* Scale should default to 1x

* Fix saving SVG images

* Add example of saving in SVG format

* Add library version number

* Document peculiarities of JPEG quality parameter

* Allow format="jpg" as well as the more technically correct "jpeg"

* Document what happens if the selector returns multiple DOM nodes

* Refactor image-to-dom to be a separate plugin

* Add support for oncompletion handler

* Remove ELS marker

Thanks @ericshulman
2026-02-06 10:42:40 +00:00
31 changed files with 760 additions and 64 deletions

View File

@@ -0,0 +1,116 @@
/*\
title: $:/core/modules/background-actions.js
type: application/javascript
module-type: global
Class to dispatch actions when filters change
\*/
"use strict";
class BackgroundActionDispatcher {
constructor(filterTracker, wiki) {
this.filterTracker = filterTracker;
this.wiki = wiki;
this.nextTrackedFilterId = 1;
this.trackedFilters = new Map(); // Use Map for better key management
// Track the filter for the background actions
this.filterTracker.track({
filterString: "[all[tiddlers+shadows]tag[$:/tags/BackgroundAction]!is[draft]]",
fnEnter: title => this.trackFilter(title),
fnLeave: (title, enterValue) => this.untrackFilter(enterValue),
fnChange: (title, enterValue) => {
this.untrackFilter(enterValue);
return this.trackFilter(title);
},
fnProcess: changes => this.process(changes)
});
}
trackFilter(title) {
const tiddler = this.wiki.getTiddler(title);
const id = this.nextTrackedFilterId++;
const tracker = new BackgroundActionTracker({
wiki: this.wiki,
title,
trackFilter: tiddler.fields["track-filter"],
actions: tiddler.fields.text
});
this.trackedFilters.set(id, tracker);
return id;
}
untrackFilter(enterValue) {
const tracker = this.trackedFilters.get(enterValue);
if(tracker) {
tracker.destroy();
}
this.trackedFilters.delete(enterValue);
}
process(changes) {
for(const tracker of this.trackedFilters.values()) {
tracker.process(changes);
}
}
}
/*
Represents an individual tracked filter. Options include:
wiki: wiki to use
title: title of the tiddler being tracked
trackFilter: filter string to track changes
actions: actions to be executed when the filter changes
*/
class BackgroundActionTracker {
constructor({wiki, title, trackFilter, actions}) {
this.wiki = wiki;
this.title = title;
this.trackFilter = trackFilter;
this.actions = actions;
this.filterTracker = new $tw.FilterTracker(this.wiki);
this.hasChanged = false;
this.trackerID = this.filterTracker.track({
filterString: this.trackFilter,
fnEnter: () => { this.hasChanged = true; },
fnLeave: () => { this.hasChanged = true; },
fnProcess: changes => {
if(this.hasChanged) {
this.hasChanged = false;
console.log("Processing background action", this.title);
const tiddler = this.wiki.getTiddler(this.title);
let doActions = true;
if(tiddler && tiddler.fields.platforms) {
doActions = false;
const platforms = $tw.utils.parseStringArray(tiddler.fields.platforms);
if(($tw.browser && platforms.includes("browser")) || ($tw.node && platforms.includes("node"))) {
doActions = true;
}
}
if(doActions) {
this.wiki.invokeActionString(
this.actions,
null,
{
currentTiddler: this.title
},{
parentWidget: $tw.rootWidget
}
);
}
}
}
});
}
process(changes) {
this.filterTracker.handleChangeEvent(changes);
}
destroy() {
this.filterTracker.untrack(this.trackerID);
}
}
exports.BackgroundActionDispatcher = BackgroundActionDispatcher;

View File

@@ -0,0 +1,106 @@
/*\
title: $:/core/modules/filter-tracker.js
type: application/javascript
module-type: global
Class to track the results of a filter string
\*/
"use strict";
class FilterTracker {
constructor(wiki) {
this.wiki = wiki;
this.trackers = new Map();
this.nextTrackerId = 1;
}
handleChangeEvent(changes) {
this.processTrackers();
this.processChanges(changes);
}
/*
Add a tracker to the filter tracker. Returns null if any of the parameters are invalid, or a tracker id if the tracker was added successfully. Options include:
filterString: the filter string to track
fnEnter: function to call when a title enters the filter results. Called even if the tiddler does not actually exist. Called as (title), and should return a truthy value that is stored in the tracker as the "enterValue"
fnLeave: function to call when a title leaves the filter results. Called as (title,enterValue)
fnChange: function to call when a tiddler changes in the filter results. Only called for filter results that identify a tiddler or shadow tiddler. Called as (title,enterValue), and may optionally return a replacement enterValue
fnProcess: function to call each time the tracker is processed, after any enter, leave or change functions are called. Called as (changes)
*/
track(options = {}) {
const {
filterString,
fnEnter,
fnLeave,
fnChange,
fnProcess
} = options;
const id = this.nextTrackerId++;
const tracker = {
id,
filterString,
fnEnter,
fnLeave,
fnChange,
fnProcess,
previousResults: [],
resultValues: {}
};
this.trackers.set(id, tracker);
// Process the tracker
this.processTracker(id);
return id;
}
untrack(id) {
this.trackers.delete(id);
}
processTrackers() {
for(const id of this.trackers.keys()) {
this.processTracker(id);
}
}
processTracker(id) {
const tracker = this.trackers.get(id);
if(!tracker) return;
const results = [];
// Evaluate the filter and remove duplicate results
$tw.utils.each(this.wiki.filterTiddlers(tracker.filterString), title => {
$tw.utils.pushTop(results, title);
});
// Process the newly entered results
results.forEach(title => {
if(!tracker.previousResults.includes(title) && !tracker.resultValues[title] && tracker.fnEnter) {
tracker.resultValues[title] = tracker.fnEnter(title) || true;
}
});
// Process the results that have just left
tracker.previousResults.forEach(title => {
if(!results.includes(title) && tracker.resultValues[title] && tracker.fnLeave) {
tracker.fnLeave(title, tracker.resultValues[title]);
delete tracker.resultValues[title];
}
});
// Update the previous results
tracker.previousResults = results;
}
processChanges(changes) {
for(const tracker of this.trackers.values()) {
Object.keys(changes).forEach(title => {
if(title && tracker.previousResults.includes(title) && tracker.fnChange) {
tracker.resultValues[title] = tracker.fnChange(title, tracker.resultValues[title]) || tracker.resultValues[title];
}
});
if(tracker.fnProcess) {
tracker.fnProcess(changes);
}
}
}
}
exports.FilterTracker = FilterTracker;

View File

@@ -0,0 +1,67 @@
/*\
title: $:/core/modules/info/mediaquerytracker.js
type: application/javascript
module-type: info
Initialise $:/info/ tiddlers derived from media queries via
\*/
"use strict";
exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
if($tw.browser) {
// Functions to start and stop tracking a particular media query tracker tiddler
function track(title) {
var result = {},
tiddler = $tw.wiki.getTiddler(title);
if(tiddler) {
var mediaQuery = tiddler.fields["media-query"],
infoTiddler = tiddler.fields["info-tiddler"],
infoTiddlerAlt = tiddler.fields["info-tiddler-alt"];
if(mediaQuery && infoTiddler) {
// Evaluate and track the media query
result.mqList = window.matchMedia(mediaQuery);
function getResultTiddlers() {
var value = result.mqList.matches ? "yes" : "no",
tiddlers = [];
tiddlers.push({title: infoTiddler, text: value});
if(infoTiddlerAlt) {
tiddlers.push({title: infoTiddlerAlt, text: value});
}
return tiddlers;
};
updateInfoTiddlersCallback(getResultTiddlers());
result.handler = function(event) {
updateInfoTiddlersCallback(getResultTiddlers());
};
result.mqList.addEventListener("change",result.handler);
}
}
return result;
}
function untrack(enterValue) {
if(enterValue.mqList && enterValue.handler) {
enterValue.mqList.removeEventListener("change",enterValue.handler);
}
}
// Track media query tracker tiddlers
function fnEnter(title) {
return track(title);
}
function fnLeave(title,enterValue) {
untrack(enterValue);
}
function fnChange(title,enterValue) {
untrack(enterValue);
return track(title);
}
$tw.filterTracker.track({
filterString: "[all[tiddlers+shadows]tag[$:/tags/MediaQueryTracker]!is[draft]]",
fnEnter: fnEnter,
fnLeave: fnLeave,
fnChange: fnChange
});
}
return [];
};

View File

@@ -33,13 +33,6 @@ exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
// Screen size
infoTiddlerFields.push({title: "$:/info/browser/screen/width", text: window.screen.width.toString()});
infoTiddlerFields.push({title: "$:/info/browser/screen/height", text: window.screen.height.toString()});
// Dark mode through event listener on MediaQueryList
var mqList = window.matchMedia("(prefers-color-scheme: dark)"),
getDarkModeTiddler = function() {return {title: "$:/info/darkmode", text: mqList.matches ? "yes" : "no"};};
infoTiddlerFields.push(getDarkModeTiddler());
mqList.addListener(function(event) {
updateInfoTiddlersCallback([getDarkModeTiddler()]);
});
// Language
infoTiddlerFields.push({title: "$:/info/browser/language", text: navigator.language || ""});
}

View File

@@ -13,6 +13,11 @@ Load core modules
exports.name = "load-modules";
exports.synchronous = true;
// Set to `true` to enable performance instrumentation
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";
var widget = require("$:/core/modules/widgets/widget.js");
exports.startup = function() {
// Load modules
$tw.modules.applyMethods("utils",$tw.utils);
@@ -31,6 +36,27 @@ exports.startup = function() {
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
$tw.macros = $tw.modules.getModulesByTypeAsHashmap("macro");
$tw.wiki.initParsers();
// --------------------------
// The rest of the startup process here is not strictly to do with loading modules, but are needed before other startup
// modules are executed. It is easier to put them here than to introduce a new startup module
// --------------------------
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
$tw.rootWidget = new widget.widget({
type: "widget",
children: []
},{
wiki: $tw.wiki,
document: $tw.browser ? document : $tw.fakeDocument
});
// Set up the performance framework
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
// Kick off the filter tracker
$tw.filterTracker = new $tw.FilterTracker($tw.wiki);
$tw.wiki.addEventListener("change",function(changes) {
$tw.filterTracker.handleChangeEvent(changes);
});
// Kick off the background action dispatcher
$tw.backgroundActionDispatcher = new $tw.BackgroundActionDispatcher($tw.filterTracker,$tw.wiki);
if($tw.node) {
$tw.Commander.initCommands();
}

View File

@@ -14,11 +14,6 @@ exports.name = "startup";
exports.after = ["load-modules"];
exports.synchronous = true;
// Set to `true` to enable performance instrumentation
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";
var widget = require("$:/core/modules/widgets/widget.js");
exports.startup = function() {
// Minimal browser detection
if($tw.browser) {
@@ -54,16 +49,6 @@ exports.startup = function() {
}
// Initialise version
$tw.version = $tw.utils.extractVersionInfo();
// Set up the performance framework
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
$tw.rootWidget = new widget.widget({
type: "widget",
children: []
},{
wiki: $tw.wiki,
document: $tw.browser ? document : $tw.fakeDocument
});
// Kick off the language manager and switcher
$tw.language = new $tw.Language();
$tw.languageSwitcher = new $tw.PluginSwitcher({

View File

@@ -86,7 +86,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
classes.push(this.linkClasses);
}
} else if(this.overrideClasses !== "") {
classes.push(this.overrideClasses)
classes.push(this.overrideClasses);
}
if(classes.length > 0) {
domNode.setAttribute("class",classes.join(" "));
@@ -97,7 +97,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
if(wikilinkTransformFilter) {
// Use the filter to construct the href
wikiLinkText = this.wiki.filterTiddlers(wikilinkTransformFilter,this,function(iterator) {
iterator(self.wiki.getTiddler(self.to),self.to)
iterator(self.wiki.getTiddler(self.to),self.to);
})[0];
} else {
// Expand the tv-wikilink-template variable to construct the href
@@ -121,12 +121,12 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
var tooltipWikiText = this.tooltip || this.getVariable("tv-wikilink-tooltip");
if(tooltipWikiText) {
var tooltipText = this.wiki.renderText("text/plain","text/vnd.tiddlywiki",tooltipWikiText,{
parseAsInline: true,
variables: {
currentTiddler: this.to
},
parentWidget: this
});
parseAsInline: true,
variables: {
currentTiddler: this.to
},
parentWidget: this
});
domNode.setAttribute("title",tooltipText);
}
if(this.role) {
@@ -135,7 +135,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
this.assignAttributes(domNode,{
sourcePrefix: "aria-",
destPrefix: "aria-"
})
});
// Add a click event handler
$tw.utils.addEventListeners(domNode,[
{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"},
@@ -145,6 +145,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
$tw.utils.makeDraggable({
domNode: domNode,
dragTiddlerFn: function() {return self.to;},
startActions: self.startActions,
endActions: self.endActions,
widget: this
});
} else if(this.draggable === "no") {
@@ -203,6 +205,8 @@ LinkWidget.prototype.execute = function() {
this.overrideClasses = this.getAttribute("overrideClass");
this.tabIndex = this.getAttribute("tabindex");
this.draggable = this.getAttribute("draggable","yes");
this.startActions = this.getAttribute("startactions");
this.endActions = this.getAttribute("endactions");
this.linkTag = this.getAttribute("tag","a");
// Determine the link characteristics
this.isMissing = !this.wiki.tiddlerExists(this.to);

View File

@@ -103,9 +103,9 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
<$set
name="buttonClasses"
value={{!!button-classes}}
value={{{ [subfilter{!!button-classes}] :and[join[ ]] }}}
><<toolbar-button>></$set>
\end
<<toolbar-button-outer>>
<<toolbar-button-outer>>

View File

@@ -1,5 +1,5 @@
title: $:/core/Filters/StoryList
tags: $:/tags/Filter
filter: [list[$:/StoryList]] -$:/AdvancedSearch
filter: [<tv-story-list>is[variable]then<tv-story-list>else[$:/StoryList]] =>storylist [list<storylist>] -$:/AdvancedSearch
description: {{$:/language/Filters/StoryList}}

View File

@@ -0,0 +1,5 @@
title: $:/core/wiki/config/MediaQueryTrackers/DarkLightPreferred
tags: $:/tags/MediaQueryTracker
media-query: (prefers-color-scheme: dark)
info-tiddler: $:/info/browser/darkmode
info-tiddler-alt: $:/info/darkmode

View File

@@ -22,7 +22,7 @@ tags: $:/tags/Macro
<$action-listops $tiddler=<<targetTiddler>> $field=<<targetField>> $subfilter="+[insertbefore<actionTiddler>,<currentTiddler>]"/>
\end
\define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate, displayField:"caption")
\define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate,displayField:"caption",startactions,endactions)
\whitespace trim
<$set name="_tiddler" value="""$tiddler$""" emptyValue=<<currentTiddler>> >
<$let field-reference={{{ [<_tiddler>] "!!" [[$field$]] +[join[]] }}}
@@ -39,8 +39,11 @@ tags: $:/tags/Macro
>
<div class="tc-droppable-placeholder"/>
<div>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$transclude tiddler=<<__itemTemplate__>>>
<$link to={{!!title}}
startactions=<<__startactions__>>
endactions=<<__endactions__>>
>
<$let tv-wikilinks="no">
<$transclude field=<<__displayField__>>>
<$view field="title"/>
@@ -92,7 +95,7 @@ tags: $:/tags/Macro
</$set>
\end
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"",displayField:"title")
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"",displayField:"title",startactions,endactions)
\whitespace trim
<span class="tc-tagged-draggable-list">
<$set name="tag" value=<<__tag__>>>
@@ -108,8 +111,11 @@ tags: $:/tags/Macro
>
<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/>
<$genesis $type=<<__elementTag__>>>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$transclude tiddler=<<__itemTemplate__>>>
<$link to={{!!title}}
startactions=<<__startactions__>>
endactions=<<__endactions__>>
>
<$let tv-wikilinks="no">
<$transclude field=<<__displayField__>>>
<$view field="title"/>

View File

@@ -1,5 +1,5 @@
created: 20201216182347597
modified: 20211018102328148
modified: 20260206104319886
tags:
title: How to create dynamic editor toolbar buttons
type: text/vnd.tiddlywiki
@@ -91,6 +91,9 @@ This tiddler contains all the necessary elements that are important for toolbar
; shortcuts
: This is the [[Keyboard Shortcut Descriptor]] eg: `((temp-bold))`
; button-classes <<.from-version "5.4.0">>
: Additional CSS classes applied to the created button, definable as a list or filter expression
<<<
!! Disabled State

View File

@@ -1,6 +1,6 @@
caption: list-links-draggable
created: 20170328204925306
modified: 20211214141650488
modified: 20260206131823536
tags: Macros [[Core Macros]]
title: list-links-draggable Macro
type: text/vnd.tiddlywiki
@@ -31,9 +31,16 @@ The <<.def list-links-draggable>> [[macro|Macros]] renders the ListField of a ti
; itemTemplate
: Optional title of a tiddler to use as the template for rendering list items
;displayField
: <<.from-version 5.4.0>> Optional name of the field to display when the list is rendered. Defaults to the "caption" field; if "caption" is not present, the "title" field is used instead
; displayField <<.from-version 5.4.0>>
: Optional name of the field to display when the list is rendered. Defaults to the "caption" field; if "caption" is not present, the "title" field is used instead
; startactions <<.from-version "5.4.0">>
: Optional action string that gets invoked when link ''dragging starts''
: <<.var actionTiddler>> variable is available in this action
; endactions <<.from-version "5.4.0">>
: Optional action string that gets invoked when link ''dragging ends''
: <<.var actionTiddler>> variable is available in this action
If the `itemTemplate` parameter is not provided then the list items are rendered as simple links. Within the `itemTemplate`, the [[currentTiddler Variable]] refers to the current list item.

View File

@@ -1,6 +1,6 @@
caption: list-tagged-draggable
created: 20170329092723939
modified: 20180109171254045
modified: 20260206131803841
tags: Macros [[Core Macros]]
title: list-tagged-draggable Macro
type: text/vnd.tiddlywiki
@@ -9,19 +9,30 @@ The <<.def list-tagged-draggable>> [[macro|Macros]] renders the tiddlers with a
!! Parameters
;tag
; tag
: The title of the tag
;subFilter
; subFilter
: An optional subfilter to filter out unwanted items (eg `!tag[done]`)
;itemTemplate
; itemTemplate
: Optional title of a tiddler to use as the template for rendering list items
;emptyMessage
; emptyMessage
: Optional wikitext to display if there are no tiddlers with the specified tag
;displayField
: <<.from-version 5.4.0>> Optional name of the field to display when the list is rendered. Defaults to `title` field.
; displayField <<.from-version 5.4.0>>
: Optional name of the field to display when the list is rendered. Defaults to `title` field
Note that the [[ordering|Order of Tagged Tiddlers]] is accomplished by assigning a new list to the `list` field of the tag tiddler. Any `list-before` or `list-after` fields on any of the other tiddlers carrying the tag are also removed to ensure the `list` field is respected.
; startactions <<.from-version "5.4.0">>
: Optional action string that gets invoked when link ''dragging starts''
: <<.var actionTiddler>> variable is available in this action.
; endactions <<.from-version "5.4.0">>
: Optional action string that gets invoked when link ''dragging ends''
: <<.var actionTiddler>> variable is available in this action
Note that the [[ordering|Order of Tagged Tiddlers]] is accomplished by assigning a new list to the `list` field of the tag tiddler. Any `list-before` or `list-after` fields on any of the other tiddlers carrying the tag are also removed to ensure the `list` field is respected
If the `itemTemplate` parameter is not provided then the list items are rendered as simple links. Within the `itemTemplate`, the [[currentTiddler Variable]] refers to the current list item.

View File

@@ -0,0 +1,28 @@
title: Background Actions
created: 20250212154426403
modified: 20250212154426403
tags: Mechanisms
Background actions are performed whenever there are changes to the results of a filter.
They can be useful for hooking into existing functionality by tracking changes to the tiddler store.
The following example tracks changes to the story list, reusing itself as the text of a notification at the same time:
<<.demo-tiddler """
title: SampleBackgroundAction: Story Change
tags: $:/tags/BackgroundAction
track-filter: [list[$:/StoryList]]
<$action-sendmessage $message="tm-notify" $param="SampleBackgroundAction: Story Change" list={{$:/StoryList!!list}}/>
Story List:
<ol>
<$list filter="[enlist<list>]">
<li>
<$text text=<<currentTiddler>>/>
</li>
</$list>
</ol>
""">>

View File

@@ -4,8 +4,8 @@ tags: Mechanisms
title: InfoMechanism
type: text/vnd.tiddlywiki
\define example(name)
<$transclude tiddler="""$:/info/url/$name$""" mode="inline"/>
\procedure example(name)
<$text text={{{ [[$:/info/url/]addsuffix<name>get[text]] }}} />
\end
System tiddlers in the namespace `$:/info/` are used to expose information about the system (including the current browser) so that WikiText applications can adapt themselves to available features.
@@ -19,6 +19,8 @@ System tiddlers in the namespace `$:/info/` are used to expose information about
|[[$:/info/browser/language]] |<<.from-version "5.1.20">> Language as reported by browser (note that some browsers report two character codes such as `en` while others report full codes such as `en-GB`) |
|[[$:/info/browser/screen/width]] |Screen width in pixels |
|[[$:/info/browser/screen/height]] |Screen height in pixels |
|[[$:/info/browser/darkmode]] |<<.from-version "5.4.0">> Is dark mode preferred? ("yes" or "no") |
|[[$:/info/darkmode]] |<<.deprecated-since "5.4.0">> Alias for $:/info/browser/darkmode |
|`$:/info/browser/window/*` |<<.from-version "5.4.0">> Tiddlers reporting window dimensions, updated when the windows are resized |
|[[$:/info/node]] |Running under [[Node.js]]? ("yes" or "no") |
|[[$:/info/url/full]] |<<.from-version "5.1.14">> Full URL of wiki (eg, ''<<example full>>'') |

View File

@@ -0,0 +1,13 @@
title: Media Query Tracker Mechanism
tags: Mechanisms
created: 20250212154426403
modified: 20250212154426403
<<.from-version "5.4.0">> The media query tracker mechanism allows you to define [[custom CSS media queries|https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries]] to be bound to a specified [[info|InfoMechanism]] tiddler. The info tiddler will be dynamically update to reflect the current state of the media query.
Adding or modifying a tiddler tagged $:/tags/MediaQueryTracker takes effect immediately.
The media queries are always applied against the main window. This is relevant for viewport related media queries such as `min-width` which will always respect the main window and ignore the sizes of any external windows.
The core includes a media query tracker that is used for tracking the operating system dark/light setting. See $:/core/wiki/config/MediaQueryTrackers/DarkLightPreferred for details.

View File

@@ -0,0 +1,13 @@
title: $:/changenotes/5.4.0/#9585
created: 20260106174849522
modified: 20260106174849522
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: usability
description: The button classes of Editor toolbar buttons can now be evaluated as filter expressions
release: 5.4.0
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9585
github-contributors: BurningTreeC
type: text/vnd.tiddlywiki
This change lets you define the button-classes of Editor toolbar buttons as filters for flexible styling

View File

@@ -0,0 +1,12 @@
change-category: usability
change-type: enhancement
created: 20260126125300
description: Add start- and endactions to link-widget, list-links-draggable and list-tagged-draggable macros
github-contributors: pmario
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9621
modified: 20260126125300
release: 5.4.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.0/#9621
type: text/vnd.tiddlywiki

View File

@@ -0,0 +1,14 @@
title: $:/changenotes/5.4.0/#9641
description: Background actions and media query tracking
tags: $:/tags/ChangeNote
release: 5.4.0
change-type: enhancement
change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9641
github-contributors: Jermolene
Introduces[[Background Actions]] that are triggered whenever there is a change to the results of a specified filter.
Building on that, it also introduces a new [[Media Query Tracker Mechanism]] that can track the results of any CSS media query (not just dark mode), storing the results in a shadow `$:/info/...` tiddler
These improvements were cherrypicked from [[#8702 - Colour Handling Improvements|https://github.com/TiddlyWiki/TiddlyWiki5/pull/8702]] when it was deferred until v5.5.0.

View File

@@ -1,6 +0,0 @@
created: 20230726145210484
modified: 20260130210336084
title: Behaviour of called variables depends on how the variable was declared
type: text/vnd.tiddlywiki
See [[Behaviour of called variables depends on how the variable was declared]].

View File

@@ -1,6 +1,6 @@
caption: link
created: 20131024141900000
modified: 20250720025154474
modified: 20260206131722044
tags: Widgets
title: LinkWidget
type: text/vnd.tiddlywiki
@@ -27,6 +27,8 @@ The content of the link widget is rendered within the `<a>` tag representing the
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element |
|aria-* |<<.from-version "5.4.0">> Optional [[ARIA attributes|https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes]] to be assigned to the HTML element |
|startactions |<<.from-version "5.4.0">> Optional action string that gets invoked when ''dragging starts''. The <<.var actionTiddler>> variable is available in this action.|
|endactions |<<.from-version "5.4.0">> Optional action string that gets invoked when ''dragging ends''. The <<.var actionTiddler>> variable is available in this action. |
The draggable functionality is equivalent to using the DraggableWidget with the ''tiddler'' attribute set to the link target.
@@ -67,6 +69,19 @@ A useful convention is to set the tooltip like this:
This causes the tooltip to be the ''tooltip'' field of the target tiddler. If the field isn't present, then the title is used instead.
! Action Variables
<<.from-version "5.4.0">> The <<.var actionTiddler>> variable is available to <<.param startaction>> and <<.param endaction>>.
To see all variables available to an action you can use this code and see the variables in the browser dev-console (F12)
<<wikitext-example-without-html """\procedure endActions()
<$action-log/>
\end
<$link to=HelloThere endactions=<<endActions>>>drag me!</$link>
""">>
! CSS Classes
The link widget automatically determines and applies the following classes to links:
@@ -95,7 +110,7 @@ The following process is used to generate the `href` attribute of the generated
# If <<.vlink tv-wikilink-template>> is defined it is treated as a specialised macro body that can perform limited conversion of the target tiddler title to the `href` value
# Otherwise, the target tiddler title is URI encoded to create the `href`
! Configuration variables
! Configuration Variables
* <<.vlink tv-wikilinks>>
* <<.vlink tv-filter-export-link>>

View File

@@ -4,10 +4,10 @@
"tiddlywiki/browser-sniff",
"tiddlywiki/confetti",
"tiddlywiki/dynannotate",
"tiddlywiki/tour",
"tiddlywiki/internals",
"tiddlywiki/menubar",
"tiddlywiki/railroad",
"tiddlywiki/tour"
"tiddlywiki/railroad"
],
"themes": [
"tiddlywiki/vanilla",

View File

@@ -0,0 +1,66 @@
title: $:/plugins/tiddlywiki/dom-to-image/docs
caption: tm-save-dom-to-image message
!! `tm-save-dom-to-image` message
The `tm-save-dom-to-image` message uses a [[third-party library|https://github.com/1904labs/dom-to-image-more]] to save a DOM node as an image. The image can be saved to a file or to a tiddler.
Note that there are some limitations to saving content loaded from external domains due to the same-origin policy. See the documentation for more details.
The following parameters are supported:
|!Parameters |!Description |
|''selector'' |CSS selector identifying the DOM node to be saved as an image (defaults to `body`). If multiple DOM nodes are returned by the selector then the first one is used |
|''format'' |Save format: ''png'', ''jpeg'' or ''svg'' (defaults to ''png'') |
|''quality'' |Optional quality 0 to 1 for JPEG images (note that the default quality is 1, and this default is applied even if the quality is explicitly specified as `0`. To force a low quality JPEG image it is therefore necessary to specify a small non-zero value such as `0.001`) |
|''scale'' |Optional scale factor for the image (defaults to 1) |
|''width'' |Optional width of the image in pixels |
|''height'' |Optional height of the image in pixels |
|''save-file'' |Optional filename to save the image to |
|''save-title'' |Optional title of tiddler to save the image to |
|''oncompletion'' |Optional action string to be invoked when the save operation has completed |
|''var-*'' |Variables to be passed to the completion handler (without the "var-" prefix) |
!! Examples
<$button>
<$action-sendmessage
$message="tm-save-dom-to-image"
selector="body.tc-body"
scale="2"
save-file="screenshot.png"
/>
Save the screen as an image file in PNG format
</$button>
<$button>
<$action-sendmessage
$message="tm-save-dom-to-image"
selector="body.tc-body"
scale="2"
save-title="$:/temp/screenshot.png"
/>
Save the screen as an image tiddler in PNG format
</$button> [[$:/temp/screenshot.png]]
<$button>
<$action-sendmessage
$message="tm-save-dom-to-image"
selector="body.tc-body"
format="jpeg"
scale="1"
quality="0.01"
save-title="$:/temp/screenshot.jpeg"
/>
Save the screen as an image tiddler in low quality JPEG format
</$button> [[$:/temp/screenshot.jpeg]]
<$button>
<$action-sendmessage
$message="tm-save-dom-to-image"
selector="body.tc-body"
format="svg"
save-title="$:/temp/screenshot.svg"
/>
Save the screen as an image tiddler in SVG format
</$button> [[$:/temp/screenshot.svg]]

View File

@@ -0,0 +1,29 @@
The MIT License (MIT)
Copyright 2018 Marc Brooks
https://about.me/idisposable
Copyright 2015 Anatolii Saienko
https://github.com/tsayen
Copyright 2012 Paul Bakaus
http://paulbakaus.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
{
"tiddlers": [
{
"file": "dom-to-image-more.min.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/dom-to-image/dom-to-image-more.js",
"module-type": "library",
"url": "https://github.com/1904labs/dom-to-image-more",
"version": "3.5.0"
}
},
{
"file": "LICENSE",
"fields": {
"type": "text/plain",
"title": "$:/plugins/tiddlywiki/dom-to-image/dom-to-image-more/LICENSE"
}
}
]
}

View File

@@ -0,0 +1,7 @@
{
"title": "$:/plugins/tiddlywiki/dom-to-image",
"name": "Dom-to-image",
"description": "Save DOM nodes as images",
"list": "readme docs",
"stability": "STABILITY_1_EXPERIMENTAL"
}

View File

@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/dom-to-image/readme
* [[dom-to-image-more|https://github.com/1904labs/dom-to-image-more]], an open source library for saving web content as images

View File

@@ -0,0 +1,92 @@
/*\
title: $:/plugins/tiddlywiki/dom-to-image/startup.js
type: application/javascript
module-type: startup
dom-to-image initialisation
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
// Export name and synchronous status
exports.name = "dom-to-image";
exports.after = ["rootwidget"];
exports.before = ["render"];
exports.synchronous = true;
exports.startup = function() {
var getPropertiesWithPrefix = function(properties,prefix) {
var result = Object.create(null);
$tw.utils.each(properties,function(value,name) {
if(name.indexOf(prefix) === 0) {
result[name.substring(prefix.length)] = properties[name];
}
});
return result;
};
$tw.rootWidget.addEventListener("tm-save-dom-to-image",function(event) {
var self=this,
params = event.paramObject || {},
domToImage = require("$:/plugins/tiddlywiki/dom-to-image/dom-to-image-more.js"),
domNode = document.querySelector(params.selector || "body.tc-body"),
oncompletion = params.oncompletion,
variables = getPropertiesWithPrefix(params,"var-");
if(domNode) {
var method = "toPng";
switch(params.format) {
case "jpeg":
// Intentional fallthrough
case "jpg":
method = "toJpeg";
break;
case "svg":
method = "toSvg";
break;
}
domToImage[method](domNode,{
height: $tw.utils.parseInt(params.height) || domNode.offsetHeight,
width: $tw.utils.parseInt(params.width) || domNode.offsetWidth,
quality: $tw.utils.parseNumber(params.quality),
scale: $tw.utils.parseNumber(params.scale) || 1
})
.then(function(dataUrl) {
// Save the image
if(params["save-file"]) {
var link = document.createElement("a");
link.download = params["save-file"];
link.href = dataUrl;
link.click();
}
// Save the tiddler
if(params["save-title"]) {
if(dataUrl.indexOf("data:image/svg+xml;") === 0) {
var commaIndex = dataUrl.indexOf(",");
$tw.wiki.addTiddler(new $tw.Tiddler({
title: params["save-title"],
type: "image/svg+xml",
"text": decodeURIComponent(dataUrl.substring(commaIndex + 1))
}));
} else {
var parts = dataUrl.split(";base64,");
$tw.wiki.addTiddler(new $tw.Tiddler({
title: params["save-title"],
type: parts[0].split(":")[1],
"text": parts[1]
}));
}
}
self.wiki.invokeActionString(oncompletion,self,variables,{parentWidget: $tw.rootWidget});
})
.catch(function(error) {
console.error('oops, something went wrong!', error);
});
}
});
};
})();

View File

@@ -25,6 +25,61 @@ exports.startup = function() {
require("$:/plugins/tiddlywiki/geospatial/leaflet.markercluster.js");
}
// Install geolocation message handler
$tw.rootWidget.addEventListener("tm-save-dom-to-image",function(event) {
var params = event.paramObject || {},
domToImage = require("$:/plugins/tiddlywiki/geospatial/dom-to-image-more.js"),
domNode = document.querySelector(params.selector || "body.tc-body");
if(domNode) {
var method = "toPng";
switch(params.format) {
case "jpeg":
// Intentional fallthrough
case "jpg":
method = "toJpeg";
break;
case "svg":
method = "toSvg";
break;
}
domToImage[method](domNode,{
height: $tw.utils.parseInt(params.height) || domNode.offsetHeight,
width: $tw.utils.parseInt(params.width) || domNode.offsetWidth,
quality: $tw.utils.parseNumber(params.quality),
scale: $tw.utils.parseNumber(params.scale) || 1
})
.then(function(dataUrl) {
// Save the image
if(params["save-file"]) {
var link = document.createElement("a");
link.download = params["save-file"];
link.href = dataUrl;
link.click();
}
// Save the tiddler
if(params["save-title"]) {
if(dataUrl.indexOf("data:image/svg+xml;") === 0) {
var commaIndex = dataUrl.indexOf(",");
$tw.wiki.addTiddler(new $tw.Tiddler({
title: params["save-title"],
type: "image/svg+xml",
"text": decodeURIComponent(dataUrl.substring(commaIndex + 1))
}));
} else {
var parts = dataUrl.split(";base64,");
$tw.wiki.addTiddler(new $tw.Tiddler({
title: params["save-title"],
type: parts[0].split(":")[1],
"text": parts[1]
}));
}
}
})
.catch(function(error) {
console.error('oops, something went wrong!', error);
});
}
});
// Install geolocation message handler
$tw.rootWidget.addEventListener("tm-request-geolocation",function(event) {
var widget = event.widget,
wiki = widget.wiki || $tw.wiki,