1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-02-09 19:50:22 +00:00

Compare commits

..

36 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
Mario Pietsch
c96d398712 remove empty.hta from tiddlywiki.info build targets (#9633) 2026-02-06 10:11:23 +00:00
Saq Imtiaz
821dcaf002 Ensures that getting variable info for a function doesnt pollute parent widget (#9639)
* fix: ensure getting variable info for a function doesnt pollute parent widget

* fix: makeFakeWidget should not pollute upstream
2026-02-06 11:10:47 +01:00
Jeremy Ruston
9247a87e11 Missing release note for #8810 2026-02-06 09:09:42 +00:00
Saq Imtiaz
bda54b0ad5 Adds pointer capture and disabling support to eventcatcher widget (#9609)
* feat: eventcatcher with pointer capture support

* fix: various cleanups and improvements

* chore: lint

* chore: lint

* docs: updated eventcatcher docs

* docs: added changenote

* feat: provide access to event properties via JSON blob

* fix: added updated utils file

* fix: guard against missing nodes and not element nodes
2026-02-06 09:16:34 +01:00
Théophile Desmedt
cd8b1faa74 UI: Show cascade filter details in Advanced info tab (#9634)
* UI: Show details on cascade filters in the "Advanced info" tab

* Update CascadeInfo.tid with new formatting

* Remove codeblock for conciseness

* Add change note for #9634

* Refactor CascadeInfo.tid for active filters

Improve filter logic and names of variable

* Change heading names

Updated the CascadeInfo.tid to change 'Active Filter Condition' to 'Active Cascade Filter' and adjusted the corresponding variables.
2026-02-05 19:47:15 +00:00
Saq Imtiaz
0673426f5a fix: remove fn wrappers and unneeded comments (#9637) 2026-02-05 17:06:32 +01:00
Saq Imtiaz
d376ada241 Adds a widget destroy method (#9097)
* feat: widget destroy method

* fix: revert to original implementation

* Invoke onDestroy method during widget destruction

Add custom cleanup method call in destroy process

* Update documentation for destroy method options

* Fix formatting in widget.js comments

* Refactor destroyChildren method for formatting

* Fix indentation in findNextSiblingDomNode method

* docs: added changenote

* chore: lint

* fix: remove deprecated utils method
2026-02-05 17:03:40 +01:00
Mario Pietsch
33b2f514fb Allow title, tags, text as focus for editing existing tiddlers (#9214)
* Allow title, tags, text as focus for editing existing tiddlers

* Add release note

* Update description field

* refresh select widget, it .default parameter is changed

* remove default variable, because it is not needed anymore. select widget refresh handling deals with it

* Undo select widget changes
2026-02-04 13:41:40 +01:00
Saq Imtiaz
46fe3ca988 Revert "Fix RSOE from filter operator errors (#9496)" (#9630)
This reverts commit 86c4770a28.
2026-02-04 12:53:14 +01:00
XLBilly
bf7c0b575c Fix side effects of PR 9316 (#9568)
* Fix color transition not working

* Fix plugin install button

* Fix remove tag button

* Update change note
2026-02-04 12:43:40 +01:00
XLBilly
b236373064 Intergrate tiddlywiki palette colors and settings to custom CSS properties (#9333)
* Expose tiddlywiki palette colors and settings to custom CSS properties

* Rename & use --tc prefix

* Add colors and CSS  settings

* Make CSS settings properties' name readable

* Add all CSS settings

* Add all palette colors

* Remove macrocallblock rule

* Indent with tabs

* Use --tc-color prefix

* Update docs

* Use --tpc prefix

* Add change note

* Simplify palette color rules with list widget

* Update docs

* Hardcode custom properties

* Update docs

* Remove cascades

* Update docs

* Add more docs

* Update docs

* Update docs

* Add --tp-animation-duration

* Update change note
The previous example actually doesn't work at all

* Update docs about limits of CSS variables
2026-02-04 11:37:04 +00:00
Mario Pietsch
d15398fc09 Simple TOC level parameter (#9612)
* Add toc level parameter

* Update TOC documentation

* Add toc level release note

* Update releasenote number and GH links

* Update releasenote number and GH links
2026-02-04 11:26:31 +00:00
Jeremy Ruston
6bc77cf3e2 Dynamic parameters for macro/procedure/function calls (#9055)
* Initial commit

The idea is to extend the macro call syntax to accept dynamic parameter values (ie thing:{{more}} etc). Eventually, this will work in all the contexts in which the double angle bracket syntax is valid.

This initial commit gets the tests passing, but doesn't yet activate the new functionality.

* Test for standalone macro calls with dynamic parameters

* Parse attribute macros with the new parser

This fixes the tests

* Test for attribute macros

* Add some examples

* Tweak examples

* Fix test

* Temporarily disable a broken serializer test

* Fix/dynamic macro calls test (#9459)

* Revert "Temporarily disable a broken serializer test"

This reverts commit b3144300ee.

* restore synamic parameter parse result

* lint

* lint

* remove duplicate

* Update core/modules/parsers/parseutils.js

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update editions/test/tiddlers/tests/data/serialize/DynamicWidgetAttribute.tid

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update editions/test/tiddlers/tests/data/serialize/DynamicWidgetAttribute.tid

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: mixed qouted and unquoted

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix unneeded diff

* Minor docs update

* Genuflecting to the linter

* Remove debug logging

* Add change note

* Allow single closing square brackets within double square brackets quoted strings

* Only allow new style parameter values if the separator is an equals sign

* On reflection, new style values should not be allowed for anonymous parameters

Backwards compatibility

* Docs updates

* Docs updates

---------

Co-authored-by: lin onetwo <linonetwo012@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 11:24:06 +00:00
XLBilly
dc764b3a4a Replace fill rules in tw5.com edition (#9624) 2026-02-04 11:22:32 +00:00
XLBilly
9c09841eda Make draft title translatable (#8891)
* Make draft title translatable

* Update change note

* Improve number handling
Now generates "Draft of '...' 2" instead of "Draft 2 of '...'"

* Improve whitespace handling
We no longer needs to add whitespace in languages. We now handle it in javascript.

* Update language naming

* Update zh-Hans translation

* Update change note

* Refactor logic to make it less complex
Since we don't need to care about draft numbers, we can have two different templates for draft title with and without attribution. No need to trim the string now.
Also, we can reuse the getSubstitutedText method

* Update translators edition

* fixup! Update translators edition

* Switch to transclude and variable mechanism

* Adapt translators to the new mechanism

* Update change note

* Further simplify the logic
$tw.language.getString can already wikify translatable strings. No need for this.renderText
2026-02-04 11:21:54 +00:00
Cameron Fischer
9d5be2e9f8 Recurse exception handling to better handle '{{}}' in place of recently installed fix (#9548)
* Introduced preliminary idea for infinite recurse exception

* Better handling of infinite recursion

But it could be better still...

* the TransclusionError is a proper error

Moved the magic number to be on the error's class. Not sure if that's
a great idea.

* Fixed minor minor issue that came up in conflict

The minor fix to the jasmine regexp that escaped a '+' somehow
broke some random test.

* Removing patch fix for recursion errors

* Fixed issue where buttton and other widgets don't clean up

* Added release notes for #9548

* Update test-widget.js

If I don't fix those indentations, the entire TW codebase will explode or soemthing.

* Update test-widget.js

These lint problems are wasting my time.

* Fixed all core widgets to not leak when renderChildren fails

* Updated release notes to reflect what I'm actually fixing

* Update test-widget.js

Added warning not to use for-of loop for defining tests. The iterating variable needs to have its own method scope, or it risks being the same value for all tests.
2026-02-04 11:21:16 +00:00
lin onetwo
486d3bd326 Fix sitemap plugin can't provide xml content type in GET route (#9203)
* Update get-tiddler-html.js

* Create #9203.tid
2026-02-04 11:19:56 +00:00
XLBilly
196683915c Replace window.eval with Function (#9611)
* Replace window.eval with Function

* Add back comment
2026-02-04 11:17:10 +00:00
yaisog
42a3928960 Refactor code for EditTemplate fields (#9582)
* Refactored fields.tid

* Add changenote

* Optimize indentation and remove unneeded variable

* Consolidate some variables, procedures and functions

* Streamlined conditional and reveal structures

* Eliminate the fieldmangler widget

* Add tc-edit-field-exists to new field name input if applicable

* Call save-tiddler-actions on Ctrl-Enter in name input field
2026-02-04 11:13:53 +00:00
Saq Imtiaz
2e76cc08a1 Extends image widget with support for data-attributes and on load actions (#9050)
* feat: support for data-attributes and on load actions

* feat: support for data-attributes and on load actions

* fix: typo

* fix: simplify variable assignment

* docs: added changenote
2026-02-04 11:12:58 +00:00
Saq Imtiaz
891e4fcb2b Fixes a regression in correctly evaluating default param values for functions (#9614)
* Fix: bug in multivalue default params

* Enhance release notes for version 5.4.0

Updated GitHub links and contributors for release notes.

* Add FunctionDefaultValues test for default parameters

* Update function default values in FunctionDefaultValues.tid

* Update #8972.tid

* fix: correctly resolve default values for functions

* Update #8972.tid
2026-02-04 11:12:42 +00:00
Saq Imtiaz
f6fd5ff261 Fixes bug in correctly resolving functions in text substitutions (#9598)
* fix: correctly resolve functions in text substitutions

* docs: added changenote

* fix: remove commented code
2026-02-04 11:12:16 +00:00
XLBilly
526aaa3db8 Bump katex to newest version (#9626)
* Bump katex to newest version

* Update fonts
Also migrate to woff2

* Update readme

* Update change note
2026-02-04 10:11:09 +01:00
XLBilly
455f1be3fb Fix missing semicolon in Snow White (#9625)
* Fix missing comma in Snow White

* Update change note
2026-02-04 10:09:39 +01:00
Mohammad Rahmani
bffa0bb95a Update Documentation for list-tagged-draggable and list-links-draggable. (#9553)
* Update documents for list-links-draggable Macro.tid

add : `<<.from-version 5.4.0>> `

* Update documentation for list-tagged-draggable Macro.tid

Added `<<.from-version 5.4.0>> `

* Update list-links-draggable Macro.tid

Correct extra space and remove colon

* Remove duplicate title view field in list.tid

Removed redundant view field for title in list.tid.
This PR should fix issue reported here: https://github.com/TiddlyWiki/TiddlyWiki5/issues/9555
2026-02-04 10:09:02 +01:00
194 changed files with 2324 additions and 552 deletions

View File

@@ -120,7 +120,6 @@ node $TW5_BUILD_TIDDLYWIKI \
|| exit 1
# /empty.html Empty
# /empty.hta For Internet Explorer
# /empty-external-core.html External core empty
# /tiddlywikicore-<version>.js Core plugin javascript
node $TW5_BUILD_TIDDLYWIKI \

View File

@@ -613,7 +613,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
// Compile the code into a function
var fn;
if($tw.browser) {
fn = window["eval"](code + "\n\n//# sourceURL=" + filename); // eslint-disable-line no-eval -- See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
fn = Function("return " + code + "\n\n//# sourceURL=" + filename)(); // See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
} else {
if(sandbox){
fn = vm.runInContext(code,sandbox,filename)

View File

@@ -33,8 +33,8 @@ exports.handler = function(request,response,state) {
}
var text = state.wiki.renderTiddler(renderType,renderTemplate,{parseAsInline: true, variables: {currentTiddler: title}});
// Naughty not to set a content-type, but it's the easiest way to ensure the browser will see HTML pages as HTML, and accept plain text tiddlers as CSS or JS
state.sendResponse(200,{},text,"utf8");
var headers = {"Content-Type": renderType};
state.sendResponse(200,headers,text,"utf8");
} else {
response.writeHead(404);
response.end();

View File

@@ -6,6 +6,7 @@ Appearance/Caption: Appearance
Appearance/Hint: Ways to customise the appearance of your TiddlyWiki.
Basics/AnimDuration/Prompt: Animation duration
Basics/AutoFocus/Prompt: Default focus field for new tiddlers
Basics/AutoFocusEdit/Prompt: Default focus field for existing tiddlers
Basics/Caption: Basics
Basics/DefaultTiddlers/BottomHint: Use &#91;&#91;double square brackets&#93;&#93; for titles with spaces. Or you can choose to {{retain story ordering||$:/snippets/retain-story-ordering-button}}
Basics/DefaultTiddlers/Prompt: Default tiddlers

View File

@@ -0,0 +1,4 @@
title: $:/language/Draft/
Attribution: Draft of '<<draft-title>>' by {{$:/status/UserName}}
Title: Draft of '<<draft-title>>'

View File

@@ -9,6 +9,7 @@ Advanced/ShadowInfo/NotShadow/Hint: The tiddler <$link to=<<infoTiddler>>><$text
Advanced/ShadowInfo/Shadow/Hint: The tiddler <$link to=<<infoTiddler>>><$text text=<<infoTiddler>>/></$link> is a shadow tiddler
Advanced/ShadowInfo/Shadow/Source: It is defined in the plugin <$link to=<<pluginTiddler>>><$text text=<<pluginTiddler>>/></$link>
Advanced/ShadowInfo/OverriddenShadow/Hint: It is overridden by an ordinary tiddler
Advanced/CascadeInfo/Heading: Cascade Details
Fields/Caption: Fields
List/Caption: List
List/Empty: This tiddler does not have a list

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

@@ -48,8 +48,8 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
this.toolbarNode = this.document.createElement("div");
this.toolbarNode.className = "tc-editor-toolbar";
parent.insertBefore(this.toolbarNode,nextSibling);
this.renderChildren(this.toolbarNode,null);
this.domNodes.push(this.toolbarNode);
this.renderChildren(this.toolbarNode,null);
}
// Create our element
var editInfo = this.getEditInfo(),

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

@@ -88,8 +88,8 @@ function parseFilterOperation(operators,filterString,p) {
rexMatch = rex.exec(filterString.substring(p));
if(rexMatch) {
operator.regexp = new RegExp(rexMatch[1], rexMatch[2]);
// DEPRECATION WARNING
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
// DEPRECATION WARNING
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
nextBracketPos = p + rex.lastIndex - 1;
}
else {
@@ -232,12 +232,7 @@ exports.getFilterRunPrefixes = function() {
exports.filterTiddlers = function(filterString,widget,source) {
var fn = this.compileFilter(filterString);
try {
const fnResult = fn.call(this,source,widget);
return fnResult;
} catch(e) {
return [`${$tw.language.getString("Error/Filter")}: ${e}`];
}
return fn.call(this,source,widget);
};
/*
@@ -319,19 +314,19 @@ exports.compileFilter = function(filterString) {
// Invoke the appropriate filteroperator module
results = operatorFunction(accumulator,{
operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
multiValueOperands: multiValueOperands,
isMultiValueOperand: isMultiValueOperand,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
widget: widget
});
operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
multiValueOperands: multiValueOperands,
isMultiValueOperand: isMultiValueOperand,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
widget: widget
});
if($tw.utils.isArray(results)) {
accumulator = self.makeTiddlerIterator(results);
} else {

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

@@ -23,27 +23,6 @@ exports.init = function(parser) {
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg;
};
/*
Reject the match if we don't have a template or text reference
*/
exports.findNextMatch = function(startPos) {
this.matchRegExp.lastIndex = startPos;
this.match = this.matchRegExp.exec(this.parser.source);
if(this.match) {
var template = $tw.utils.trim(this.match[2]),
textRef = $tw.utils.trim(this.match[1]);
// Bail if we don't have a template or text reference
if(!template && !textRef) {
return undefined;
} else {
return this.match.index;
}
} else {
return undefined;
}
return this.match ? this.match.index : undefined;
};
exports.parse = function() {
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;

View File

@@ -23,27 +23,6 @@ exports.init = function(parser) {
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}/mg;
};
/*
Reject the match if we don't have a template or text reference
*/
exports.findNextMatch = function(startPos) {
this.matchRegExp.lastIndex = startPos;
this.match = this.matchRegExp.exec(this.parser.source);
if(this.match) {
var template = $tw.utils.trim(this.match[2]),
textRef = $tw.utils.trim(this.match[1]);
// Bail if we don't have a template or text reference
if(!template && !textRef) {
return undefined;
} else {
return this.match.index;
}
} else {
return undefined;
}
return this.match ? this.match.index : undefined;
};
exports.parse = function() {
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;

View File

@@ -6,10 +6,7 @@ module-type: saver
Handles saving changes via window.postMessage() to the window.parent
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
@@ -63,4 +60,3 @@ exports.create = function(wiki) {
return new PostMessageSaver(wiki);
};
})();

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

@@ -6,6 +6,7 @@ module-type: utils
Custom errors for TiddlyWiki.
\*/
function TranscludeRecursionError() {
Error.apply(this,arguments);
this.signatures = Object.create(null);

View File

@@ -8,10 +8,7 @@ Messaging utilities for use with window.postMessage() etc.
This module intentionally has no dependencies so that it can be included in non-TiddlyWiki projects
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var RESPONSE_TIMEOUT = 2 * 1000;
@@ -122,5 +119,3 @@ BrowserMessagingPublisher.prototype.close = function() {
};
exports.BrowserMessagingPublisher = BrowserMessagingPublisher;
})();

View File

@@ -242,6 +242,53 @@ exports.slowInSlowOut = function(t) {
return (1 - ((Math.cos(t * Math.PI) + 1) / 2));
};
exports.copyObjectPropertiesSafe = function(object) {
const seen = new Set(),
isDOMElement = value => value instanceof Node || value instanceof Window;
function safeCopy(obj) {
// skip circular references
if(seen.has(obj)) {
return undefined;
}
// primitives and null are safe
if(typeof obj !== "object" || obj === null) {
return obj;
}
// skip DOM elements
if(isDOMElement(obj)) {
return undefined;
}
// copy arrays, preserving positions
if(Array.isArray(obj)) {
return obj.map(item => {
const value = safeCopy(item);
return value === undefined ? null : value;
});
}
seen.add(obj);
const copy = {};
let key,
value;
for(key in obj) {
try {
value = safeCopy(obj[key]);
if(value !== undefined) {
copy[key] = value;
}
} catch(e) {
// silently skip unserializable properties
}
}
return copy;
}
const result = safeCopy(object);
seen.clear();
return result;
};
exports.formatTitleString = function(template,options) {
var base = options.base || "",
separator = options.separator || "",

View File

@@ -80,8 +80,8 @@ BrowseWidget.prototype.render = function(parent,nextSibling) {
});
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
/*

View File

@@ -135,8 +135,8 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
}
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
/*

View File

@@ -64,8 +64,8 @@ CheckboxWidget.prototype.render = function(parent,nextSibling) {
]);
// Insert the label into the DOM and render any children
parent.insertBefore(this.labelDomNode,nextSibling);
this.renderChildren(this.spanDomNode,null);
this.domNodes.push(this.labelDomNode);
this.renderChildren(this.spanDomNode,null);
};
CheckboxWidget.prototype.getValue = function() {

View File

@@ -59,6 +59,8 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
var domContainer = this.document.createElement("div"),
domDiff = this.createDiffDom(diffs);
parent.insertBefore(domContainer,nextSibling);
// Save our container
this.domNodes.push(domContainer);
// Set variables
this.setVariable("diff-count",diffs.reduce(function(acc,diff) {
if(diff[0] !== dmp.DIFF_EQUAL) {
@@ -70,8 +72,6 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
this.renderChildren(domContainer,null);
// Render the diff
domContainer.appendChild(domDiff);
// Save our container
this.domNodes.push(domContainer);
};
/*

View File

@@ -56,6 +56,7 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
});
// Insert the node into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
// Add event handlers
if(this.dragEnable) {
@@ -70,7 +71,6 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
selector: self.dragHandleSelector
});
}
this.domNodes.push(domNode);
};
/*

View File

@@ -57,8 +57,8 @@ DroppableWidget.prototype.render = function(parent,nextSibling) {
}
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
// Stack of outstanding enter/leave events
this.currentlyEntered = [];
};

View File

@@ -77,8 +77,8 @@ ElementWidget.prototype.render = function(parent,nextSibling) {
// Allow hooks to manipulate the DOM node. Eg: Add debug info
$tw.hooks.invokeHook("th-dom-rendering-element", domNode, this);
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
/*

View File

@@ -39,75 +39,12 @@ EventWidget.prototype.render = function(parent,nextSibling) {
this.domNode = domNode;
// Assign classes
this.assignDomNodeClasses();
// Add our event handler
$tw.utils.each(this.types,function(type) {
domNode.addEventListener(type,function(event) {
var selector = self.getAttribute("selector"),
matchSelector = self.getAttribute("matchSelector"),
actions = self.getAttribute("$"+type),
stopPropagation = self.getAttribute("stopPropagation","onaction"),
selectedNode = event.target,
selectedNodeRect,
catcherNodeRect,
variables = {};
// Firefox can fire dragover and dragenter events on text nodes instead of their parents
if(selectedNode.nodeType === 3) {
selectedNode = selectedNode.parentNode;
}
// Check that the selected node matches any matchSelector
if(matchSelector && !$tw.utils.domMatchesSelector(selectedNode,matchSelector)) {
return false;
}
if(selector) {
// Search ancestors for a node that matches the selector
while(!$tw.utils.domMatchesSelector(selectedNode,selector) && selectedNode !== domNode) {
selectedNode = selectedNode.parentNode;
}
// Exit if we didn't find one
if(selectedNode === domNode) {
return false;
}
// Only set up variables if we have actions to invoke
if(actions) {
variables = $tw.utils.collectDOMVariables(selectedNode,self.domNode,event);
}
}
// Execute our actions with the variables
if(actions) {
// Add a variable for the modifier key
variables.modifier = $tw.keyboardManager.getEventModifierKeyDescriptor(event);
// Add a variable for the mouse button
if("button" in event) {
if(event.button === 0) {
variables["event-mousebutton"] = "left";
} else if(event.button === 1) {
variables["event-mousebutton"] = "middle";
} else if(event.button === 2) {
variables["event-mousebutton"] = "right";
}
}
variables["event-type"] = event.type.toString();
if(typeof event.detail === "object" && !!event.detail) {
$tw.utils.each(event.detail,function(detailValue,detail) {
variables["event-detail-" + detail] = detailValue.toString();
});
} else if(!!event.detail) {
variables["event-detail"] = event.detail.toString();
}
self.invokeActionString(actions,self,event,variables);
}
if((actions && stopPropagation === "onaction") || stopPropagation === "always") {
event.preventDefault();
event.stopPropagation();
return true;
}
return false;
},false);
});
// Add our event handlers
this.toggleListeners();
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
/*
@@ -122,11 +59,232 @@ EventWidget.prototype.execute = function() {
self.types.push(key.slice(1));
}
});
this.pointerCaptureMode = this.getAttribute("pointerCapture","no");
this.elementTag = this.getAttribute("tag");
// Make child widgets
this.makeChildWidgets();
};
/*
Cache and pre-create all event listeners, called when first needed
*/
EventWidget.prototype.cacheEventListeners = function() {
if(this._eventListeners) {
return;
}
this._eventListeners = Object.create(null);
this._captureActiveListeners = Object.create(null);
this._dynamicOnlyEvents = ["pointerup","pointercancel","pointermove"];
const clearPointerCapture = event => {
if(Number.isInteger(this._capturePointerId)) {
this.stopPointerCapture(this._capturePointerId);
}
};
const attachDynamicOnlyListeners = () => {
this._dynamicOnlyEvents.forEach(dt => {
const listener = this._eventListeners[dt];
if(listener) {
this._captureActiveListeners[dt] = listener;
this.domNode.addEventListener(dt, listener, false);
}
});
};
// Dynamic pointer capture listeners
if(this.pointerCaptureMode === "dynamic") {
["pointerup","pointercancel"].forEach(type => {
this._eventListeners[type] = event => {
const selectedNode = this.checkEvent(event, type);
if(selectedNode) {
clearPointerCapture(event);
}
// Remove dynamic-only listeners
this.cleanupDynamicListeners();
return this.handleEvent(event, type, selectedNode);
};
});
if(!this.types.includes("pointerdown")) {
this.types.push("pointerdown");
}
}
// Create any listeners not already defined above
this.types.forEach(type => {
if(!this._eventListeners[type]) {
this._eventListeners[type] = event => {
const selectedNode = this.checkEvent(event, type);
if(!selectedNode) {
return false;
}
// Handle pointer capture for pointerdown
if(type === "pointerdown") {
if(this.pointerCaptureMode !== "no") {
this.startPointerCapture(event.pointerId, event.target);
}
if(this.pointerCaptureMode === "dynamic") {
attachDynamicOnlyListeners();
}
} else if(type === "pointerup" || type === "pointercancel") {
clearPointerCapture(event);
}
return this.handleEvent(event, type, selectedNode);
};
}
});
};
/*
Check if an event qualifies and return the matching selected node
*/
EventWidget.prototype.checkEvent = function(event, type) {
const domNode = this.domNode;
let node = event.target;
// Use capture target if valid
if(this._captureTarget && event.pointerId !== undefined) {
if(document.contains(this._captureTarget)) {
node = this._captureTarget;
} else {
// Clear stale reference
this.stopPointerCapture(this._capturePointerId);
node = event.target;
}
}
if(node && node.nodeType === 3) {
node = node.parentNode;
}
if(!node || node.nodeType !== 1) {
return null;
}
const selector = this.getAttribute("selector"),
matchSelector = this.getAttribute("matchSelector");
if(matchSelector && !node.matches(matchSelector)) {
return null;
}
if(selector) {
const match = node.closest(selector);
if(!match || match === domNode || !domNode.contains(match)) {
return null;
}
return match;
}
return node;
};
/*
Handle the event and execute actions
*/
EventWidget.prototype.handleEvent = function(event, type, selectedNode) {
if(!selectedNode) {
return false;
}
let actions = this.getAttribute("$"+type),
stopPropagation = this.getAttribute("stopPropagation","onaction");
if(actions) {
let variables = $tw.utils.extend(
{},
$tw.utils.collectDOMVariables(selectedNode, this.domNode, event),
{
"eventJSON": JSON.stringify($tw.utils.copyObjectPropertiesSafe(event)),
"modifier": $tw.keyboardManager.getEventModifierKeyDescriptor(event),
"event-type": event.type.toString()
}
);
if("button" in event) {
const mouseButtonMap = {0:"left",1:"middle",2:"right"};
variables["event-mousebutton"] = mouseButtonMap[event.button];
}
this.invokeActionString(actions, this, event, variables);
}
if((actions && stopPropagation === "onaction") || stopPropagation === "always") {
event.preventDefault();
event.stopPropagation();
return true;
}
return false;
};
EventWidget.prototype.startPointerCapture = function(pointerId, captureTarget) {
// Start capture only if none active; pointerId can be 0
if(!Number.isInteger(this._capturePointerId) && this.domNode && this.domNode.setPointerCapture) {
this.domNode.setPointerCapture(pointerId);
this._capturePointerId = pointerId;
this._captureTarget = captureTarget;
}
};
EventWidget.prototype.stopPointerCapture = function(pointerId) {
if(this.domNode && this.domNode.hasPointerCapture && this.domNode.hasPointerCapture(pointerId)) {
this.domNode.releasePointerCapture(pointerId);
}
this._capturePointerId = undefined;
this._captureTarget = undefined;
};
/*
Attach all relevant listeners
*/
EventWidget.prototype.attachListeners = function() {
this.cacheEventListeners();
const domNode = this.domNode;
Object.keys(this._eventListeners).forEach(type => {
if(this.pointerCaptureMode === "dynamic" && this._dynamicOnlyEvents.includes(type)) {
return; //skip dynamic-only events
}
domNode.addEventListener(type, this._eventListeners[type], false);
});
};
/*
Remove dynamic active listeners
*/
EventWidget.prototype.cleanupDynamicListeners = function() {
const domNode = this.domNode;
Object.keys(this._captureActiveListeners || {}).forEach(type => {
domNode.removeEventListener(type, this._captureActiveListeners[type], false);
});
this._captureActiveListeners = Object.create(null);
};
/*
Remove all listeners
*/
EventWidget.prototype.removeAllListeners = function() {
if(Number.isInteger(this._capturePointerId)) {
this.stopPointerCapture(this._capturePointerId);
}
const domNode = this.domNode;
Object.keys(this._eventListeners || {}).forEach(type => {
domNode.removeEventListener(type, this._eventListeners[type], false);
});
this.cleanupDynamicListeners();
this._captureTarget = null;
};
/*
Enable or disable listeners
*/
EventWidget.prototype.toggleListeners = function() {
let disabled = this.getAttribute("disabled","no") === "yes";
if(disabled) {
this.removeAllListeners();
} else {
this.attachListeners();
}
};
/*
Assign DOM node classes
*/
EventWidget.prototype.assignDomNodeClasses = function() {
var classes = this.getAttribute("class","").split(" ");
classes.push("tc-eventcatcher");
@@ -134,18 +292,23 @@ EventWidget.prototype.assignDomNodeClasses = function() {
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
Refresh widget
*/
EventWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(),
changedAttributesCount = $tw.utils.count(changedAttributes);
if(changedAttributesCount === 1 && changedAttributes["class"]) {
this.assignDomNodeClasses();
} else if(changedAttributesCount > 0) {
this.refreshSelf();
return true;
const changedAttributes = this.computeAttributes(),
changedKeys = Object.keys(changedAttributes),
canUpdateAttributes = changedKeys.every(key => key === "class" || key === "disabled");
if(canUpdateAttributes) {
if(changedAttributes["class"]) {
this.assignDomNodeClasses();
}
if(changedAttributes["disabled"]) {
this.toggleListeners();
}
return this.refreshChildren(changedTiddlers);
}
return this.refreshChildren(changedTiddlers);
this.refreshSelf();
return true;
};
exports.eventcatcher = EventWidget;

View File

@@ -45,7 +45,7 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
this.execute();
// Create element
// Determine what type of image it is
var tag = "img", src = "",
var tag = "img", src = "", self = this,
tiddler = this.wiki.getTiddler(this.imageSource);
if(!tiddler) {
// The source isn't the title of a tiddler, so we'll assume it's a URL
@@ -115,11 +115,21 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
if(this.lazyLoading && tag === "img") {
domNode.setAttribute("loading",this.lazyLoading);
}
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Add classes when the image loads or fails
$tw.utils.addClass(domNode,"tc-image-loading");
domNode.addEventListener("load",function() {
domNode.addEventListener("load",function(event) {
$tw.utils.removeClass(domNode,"tc-image-loading");
$tw.utils.addClass(domNode,"tc-image-loaded");
if(self.loadedActions) {
var variables = $tw.utils.collectDOMVariables(domNode,null,event);
variables["img-natural-width"] = domNode.naturalWidth.toString();
variables["img-natural-height"] = domNode.naturalHeight.toString();
self.invokeActionString(self.loadedActions,self,event,variables);
}
},false);
domNode.addEventListener("error",function() {
$tw.utils.removeClass(domNode,"tc-image-loading");
@@ -143,17 +153,31 @@ ImageWidget.prototype.execute = function() {
this.imageTooltip = this.getAttribute("tooltip");
this.imageAlt = this.getAttribute("alt");
this.lazyLoading = this.getAttribute("loading");
this.loadedActions = this.getAttribute("loadActions");
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ImageWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource]) {
var changedAttributes = this.computeAttributes(),
hasChangedAttributes = $tw.utils.count(changedAttributes) > 0;
if(changedAttributes.source || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource] ||changedAttributes.loadActions) {
this.refreshSelf();
return true;
} else {
} else if(hasChangedAttributes) {
this.assignAttributes(this.domNodes[0],{
sourcePrefix: "data-",
destPrefix: "data-"
});
if(changedAttributes.width) {
this.domNodes[0].setAttribute("width",this.getAttribute("width"));
}
if(changedAttributes.height) {
this.domNodes[0].setAttribute("height",this.getAttribute("height"));
}
}
else {
return false;
}
};

View File

@@ -45,8 +45,8 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) {
]);
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
KeyboardWidget.prototype.handleChangeEvent = function(event) {

View File

@@ -50,8 +50,8 @@ LinkWidget.prototype.render = function(parent,nextSibling) {
destPrefix: "aria-"
});
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
}
};
@@ -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") {
@@ -157,8 +159,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
});
// Insert the link into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
LinkWidget.prototype.handleClickEvent = function(event) {
@@ -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

@@ -42,8 +42,8 @@ PasswordWidget.prototype.render = function(parent,nextSibling) {
]);
// Insert the label into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
PasswordWidget.prototype.handleChangeEvent = function(event) {

View File

@@ -59,8 +59,8 @@ RadioWidget.prototype.render = function(parent,nextSibling) {
]);
// Insert the label into the DOM and render any children
parent.insertBefore(this.labelDomNode,nextSibling);
this.renderChildren(this.spanDomNode,null);
this.domNodes.push(this.labelDomNode);
this.renderChildren(this.spanDomNode,null);
};
RadioWidget.prototype.getValue = function() {

View File

@@ -40,6 +40,7 @@ RevealWidget.prototype.render = function(parent,nextSibling) {
domNode.setAttribute("style",this.style);
}
parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) {
this.positionPopup(domNode);
@@ -48,7 +49,6 @@ RevealWidget.prototype.render = function(parent,nextSibling) {
if(!this.isOpen) {
domNode.setAttribute("hidden","true");
}
this.domNodes.push(domNode);
};
RevealWidget.prototype.positionPopup = function(domNode) {

View File

@@ -168,8 +168,8 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
this.outerDomNode.className = this["class"] || "";
// Insert element
parent.insertBefore(this.outerDomNode,nextSibling);
this.renderChildren(this.innerDomNode,null);
this.domNodes.push(this.outerDomNode);
this.renderChildren(this.innerDomNode,null);
// If the scroll position is bound to a tiddler
if(this.scrollableBind) {
// After a delay for rendering, scroll to the bound position

View File

@@ -63,8 +63,8 @@ SelectWidget.prototype.render = function(parent,nextSibling) {
domNode.setAttribute("title",this.selectTooltip);
}
this.parentDomNode.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
this.setSelectValue();
if(this.selectFocus == "yes") {
this.getSelectDomNode().focus();

View File

@@ -32,16 +32,26 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) {
} catch(error) {
if(error instanceof $tw.utils.TranscludeRecursionError) {
// We were infinite looping.
// We need to try and abort as much of the loop as we can, so we will keep "throwing" upward until we find a transclusion that has a different signature.
// Hopefully that will land us just outside where the loop began. That's where we want to issue an error.
// Rendering widgets beneath this point may result in a freezing browser if they explode exponentially.
// We need to try and abort as much of the loop as we
// can, so we will keep "throwing" upward until we find
// a transclusion that has a different signature.
// Hopefully that will land us just outside where the
// loop began. That's where we want to issue an error.
// Rendering widgets beneath this point may result in a
// freezing browser if they explode exponentially.
var transcludeSignature = this.getVariable("transclusion");
if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) {
// For the first fifty transcludes we climb up, we simply collect signatures.
// We're assuming that those first 50 will likely include all transcludes involved in the loop.
// For the first fifty transcludes we climb up,
// we simply collect signatures.
// We're assuming those first 50 will likely
// include all transcludes involved in the loop.
error.signatures[transcludeSignature] = true;
} else if(!error.signatures[transcludeSignature]) {
// Now that we're past the first 50, let's look for the first signature that wasn't in the loop. That'll be where we print the error and resume rendering.
// Now that we're past the first 50, look for
// the first signature that wasn't in that loop.
// That's where we print the error and resume
// rendering.
this.removeChildDomNodes();
this.children = [this.makeChildWidget({type: "error", attributes: {
"$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")}
}})];

View File

@@ -151,7 +151,7 @@ Widget.prototype.getVariableInfo = function(name,options) {
} else if(variable.isFunctionDefinition) {
// Function evaluations
params = self.resolveVariableParameters(variable.params,actualParams);
var variables = options.variables || Object.create(null);
var variables = $tw.utils.extend({},options.variables);
// Apply default parameter values
$tw.utils.each(variable.params,function(param,index) {
if(param["default"]) {
@@ -160,7 +160,7 @@ Widget.prototype.getVariableInfo = function(name,options) {
});
// Parameters are an array of {name:, value:, multivalue:} pairs (name and multivalue are optional)
$tw.utils.each(params,function(param) {
if(param.multiValue) {
if(param.multiValue && param.multiValue.length) {
variables[param.name] = param.multiValue;
} else {
variables[param.name] = param.value || "";
@@ -233,8 +233,10 @@ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams)
paramMultiValue = typeof param === "string" ? [param] : (param.multiValue || [paramValue]);
}
// If we've still not got a value, use the default, if any
paramValue = paramValue || paramInfo["default"] || "";
paramMultiValue = paramMultiValue || [paramValue];
if(!paramValue) {
paramValue = paramInfo["default"] || "";
paramMultiValue = [paramValue];
}
// Store the parameter name and value
results.push({name: paramInfo.name, value: paramValue, multiValue: paramMultiValue});
}
@@ -341,7 +343,7 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
}
} else {
opts = opts || {};
opts.variables = variables;
opts.variables = $tw.utils.extend({},variables,opts.variables);
return self.getVariable(name,opts);
};
},
@@ -785,9 +787,9 @@ Widget.prototype.findNextSiblingDomNode = function(startIndex) {
// Refer to this widget by its index within its parents children
var parent = this.parentWidget,
index = startIndex !== undefined ? startIndex : parent.children.indexOf(this);
if(index === -1) {
throw "node not found in parents children";
}
if(index === -1) {
throw "node not found in parents children";
}
// Look for a DOM node in the later siblings
while(++index < parent.children.length) {
var domNode = parent.children[index].findFirstDomNode();
@@ -825,21 +827,60 @@ Widget.prototype.findFirstDomNode = function() {
};
/*
Remove any DOM nodes created by this widget or its children
Entry into destroy procedure
options include:
removeDOMNodes: boolean (default true)
*/
Widget.prototype.destroyChildren = function(options) {
$tw.utils.each(this.children,function(childWidget) {
childWidget.destroy(options);
});
};
/*
Legacy entry into destroy procedure
*/
Widget.prototype.removeChildDomNodes = function() {
// If this widget has directly created DOM nodes, delete them and exit. This assumes that any child widgets are contained within the created DOM nodes, which would normally be the case
if(this.domNodes.length > 0) {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
} else {
// Otherwise, ask the child widgets to delete their DOM nodes
$tw.utils.each(this.children,function(childWidget) {
childWidget.removeChildDomNodes();
});
this.destroy({removeDOMNodes: true});
};
/*
Default destroy
options include:
- removeDOMNodes: boolean (default true)
*/
Widget.prototype.destroy = function(options) {
const { removeDOMNodes = true } = options || {};
let removeChildDOMNodes = removeDOMNodes;
if(removeDOMNodes && this.domNodes.length > 0) {
// If this widget will remove its own DOM nodes, children should not remove theirs
removeChildDOMNodes = false;
}
// Destroy children first
this.destroyChildren({removeDOMNodes: removeChildDOMNodes});
this.children = [];
// Call custom cleanup method if implemented
if(typeof this.onDestroy === "function") {
this.onDestroy();
}
// Remove our DOM nodes if needed
if(removeDOMNodes) {
this.removeLocalDomNodes();
}
};
/*
Remove any DOM nodes created by this widget
*/
Widget.prototype.removeLocalDomNodes = function() {
for(const domNode of this.domNodes) {
if(domNode.parentNode) {
domNode.parentNode.removeChild(domNode);
}
}
this.domNodes = [];
};
/*

View File

@@ -1122,15 +1122,16 @@ Parse a block of text of a specified MIME type
Options include:
substitutions: an optional array of substitutions
*/
exports.getSubstitutedText = function(text,widget,options) {
exports.getSubstitutedText = function(text,thisWidget,options) {
options = options || {};
text = text || "";
var self = this,
widgetClass = widget.widget,
substitutions = options.substitutions || [],
output;
// Evaluate embedded filters and substitute with first result
output = text.replace(/\$\{([\S\s]+?)\}\$/g, function(match,filter) {
return self.filterTiddlers(filter,widget)[0] || "";
return self.filterTiddlers(filter,thisWidget)[0] || "";
});
// Process any substitutions provided in options
$tw.utils.each(substitutions,function(substitute) {
@@ -1138,7 +1139,7 @@ exports.getSubstitutedText = function(text,widget,options) {
});
// Substitute any variable references with their values
return output.replace(/\$\((.+?)\)\$/g, function(match,varname) {
return widget.getVariable(varname,{defaultValue: ""});
return widgetClass.evaluateVariable(thisWidget,varname, {defaultValue: ""})[0];
});
};
@@ -1663,12 +1664,14 @@ exports.addToStory = function(title,fromTitle,storyTitle,options) {
Generate a title for the draft of a given tiddler
*/
exports.generateDraftTitle = function(title) {
var c = 0,
draftTitle,
username = this.getTiddlerText("$:/status/UserName"),
attribution = username ? " by " + username : "";
let c = 0,
draftTitle;
const username = this.getTiddlerText("$:/status/UserName");
do {
draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'" + attribution;
draftTitle = username ? $tw.language.getString("Draft/Attribution", {variables: {"draft-title": title}}) : $tw.language.getString("Draft/Title", {variables: {"draft-title": title}});
if(c) {
draftTitle = draftTitle.concat(" ", (c + 1).toString());
}
c++;
} while(this.tiddlerExists(draftTitle));
return draftTitle;

View File

@@ -0,0 +1,30 @@
title: $:/core/stylesheets/custom-properties
\rules only transcludeinline macrocallinline html transcludeblock
/* Tiddlywiki's CSS properties */
:root {
<$list filter="[[$:/palettes/Vanilla]indexes[]]">
--tpc-<<currentTiddler>>: <$transclude $variable="colour" $mode="inline" name=<<currentTiddler>>/>;
</$list>
/* CSS settings */
--tp-code-wrapping: {{$:/themes/tiddlywiki/vanilla/options/codewrapping}};
--tp-font-family: {{$:/themes/tiddlywiki/vanilla/settings/fontfamily}};
--tp-code-font-family: {{$:/themes/tiddlywiki/vanilla/settings/codefontfamily}};
--tp-editor-font-family: {{$:/themes/tiddlywiki/vanilla/settings/editorfontfamily}};
--tp-font-size: {{$:/themes/tiddlywiki/vanilla/metrics/fontsize}};
--tp-line-height: {{$:/themes/tiddlywiki/vanilla/metrics/lineheight}};
--tp-body-font-size: {{$:/themes/tiddlywiki/vanilla/metrics/bodyfontsize}};
--tp-body-line-height: {{$:/themes/tiddlywiki/vanilla/metrics/bodylineheight}};
--tp-story-left: {{$:/themes/tiddlywiki/vanilla/metrics/storyleft}};
--tp-story-top: {{$:/themes/tiddlywiki/vanilla/metrics/storytop}};
--tp-story-right: {{$:/themes/tiddlywiki/vanilla/metrics/storyright}};
--tp-story-width: {{$:/themes/tiddlywiki/vanilla/metrics/storyrwidth}};
--tp-tiddler-width: {{$:/themes/tiddlywiki/vanilla/metrics/tiddlerwidth}};
--tp-sidebar-breakpoint: {{$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint}};
--tp-sidebar-width: {{$:/themes/tiddlywiki/vanilla/metrics/sidebarwidth}};
--tp-animation-duration: {{{ [{$:/config/AnimationDuration}addsuffix[ms]] }}};
}

View File

@@ -18,6 +18,7 @@ caption: {{$:/language/ControlPanel/Basics/Caption}}
|<$link to="$:/config/NewTiddler/Tags"><<lingo NewTiddler/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewTiddler/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|<$link to="$:/config/NewJournal/Tags"><<lingo NewJournal/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewJournal/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|<$link to="$:/config/AutoFocus"><<lingo AutoFocus/Prompt>></$link> |{{$:/snippets/minifocusswitcher}} |
|<$link to="$:/config/AutoFocusEdit"><<lingo AutoFocusEdit/Prompt>></$link> |{{$:/snippets/minifocuseditswitcher}} |
|<<lingo Language/Prompt>> |{{$:/snippets/minilanguageswitcher}} |
|<<lingo Tiddlers/Prompt>> |<<show-filter-count "[!is[system]sort[title]]">> |
|<<lingo Tags/Prompt>> |<<show-filter-count "[tags[]sort[title]]">> |

View File

@@ -8,7 +8,7 @@ title: $:/core/ui/EditTemplate/body/editor
class="tc-edit-texteditor tc-edit-texteditor-body"
placeholder={{$:/language/EditTemplate/Body/Placeholder}}
tabindex={{$:/config/EditTabIndex}}
focus={{{ [{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[text]then[true]] ~[{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
cancelPopups="yes"
fileDrop={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}}

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,157 +1,181 @@
title: $:/core/ui/EditTemplate/fields
tags: $:/tags/EditTemplate
\procedure lingo-base() $:/language/EditTemplate/
\function tf.config-title() [[$:/config/EditTemplateFields/Visibility/]addsuffix[$(currentField)$]substitute[]get[text]]
\function tf.config-filter() [[hide]] :except[title<tf.config-title>]
<!-- Beware this is duplicated from EditTemplate.tid. For details see bug #7054 -->
\procedure get-field-value-tiddler-filter() [subfilter<get-field-editor-filter>sha256[16]addprefix[/]addprefix<newFieldValueTiddlerPrefix>]
\procedure get-field-editor-filter() [<newFieldNameTiddler>get[text]else[]] :cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]] :and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
\procedure prefix.bracket() [
\procedure suffix.bracket() ]
\function tf.current-tiddler-new-field-selector() [[data-tiddler-title=]addprefix[$(prefix.bracket)$]substitute[]addsuffix<currentTiddlerCSSescaped>addsuffix[$(suffix.bracket)$]substitute[]] .tc-edit-field-add-name-wrapper input :and[join[ ]]
\procedure new-field-actions()
\whitespace trim
<$action-sendmessage $message="tm-add-field" $name={{{ [<newFieldNameTiddler>get[text]] }}} $value={{{ [<newFieldNameTiddler>get[text]] :map[subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
<$set name="safeNewFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
<$action-deletetiddler $filter="[<newFieldNameTiddler>] [prefix[$:/temp/NewFieldValue]prefix<safeNewFieldValueTiddlerPrefix>] [<storeTitle>] [<searchListState>]"/>
</$set>
<$action-sendmessage $message="tm-focus-selector" $param=<<tf.current-tiddler-new-field-selector>>/>
\end
\procedure lingo-base() $:/language/EditTemplate/
\procedure delete-state-tiddlers() <$action-deletetiddler $filter="[<newFieldNameTiddler>] [<storeTitle>] [<searchListState>]"/>
\procedure cancel-search-actions-inner()
\whitespace trim
<$list
filter="[<storeTitle>has[text]] [<newFieldNameTiddler>has[text]]"
variable="ignore"
emptyMessage="<<cancel-delete-tiddler-actions 'cancel'>>">
<<delete-state-tiddlers>>
</$list>
\procedure focus-new-field-input() <$action-sendmessage $message="tm-focus-selector" $param=`[data-tiddler-title="$(storyTiddler)$"] .tc-edit-field-add-name-wrapper input` />
\procedure new-field-actions()
<$action-setfield $tiddler=<<storyTiddler>> $field={{{ [<newFieldNameTiddler>get[text]] }}} $value={{{ [<newFieldValueTiddler>get[text]] }}} />
<$action-deletetiddler $filter="[prefix[$:/temp/NewFieldValue]prefix<newFieldValueTiddlerPrefix>]"/>
<<delete-state-tiddlers>>
<<focus-new-field-input>>
\end
\procedure delete-field-actions()
<$action-deletefield $field=<<currentField>>/>
<<focus-new-field-input>>
\end
\procedure cancel-search-actions()
\whitespace trim
<$set name="userInput" value={{{ [<storeTitle>get[text]] }}}>
<$list
filter="[<newFieldNameTiddler>get[text]!match<userInput>]"
emptyMessage="<<cancel-search-actions-inner>>">
<$action-setfield $tiddler=<<newFieldNameTiddler>> text=<<userInput>>/><$action-setfield $tiddler=<<refreshTitle>> text="yes"/>
</$list>
</$set>
<$let userInput={{{ [<storeTitle>get[text]] }}}>
<%if [<newFieldNameTiddler>get[text]!match<userInput>] %>
<$action-setfield $tiddler=<<newFieldNameTiddler>> text=<<userInput>>/>
<$action-setfield $tiddler=<<refreshTitle>> text="yes"/>
<%else%>
<%if [<storeTitle>has[text]] [<newFieldNameTiddler>has[text]] %>
<<delete-state-tiddlers>>
<%else%>
<<cancel-delete-tiddler-actions 'cancel'>>
<%endif%>
<%endif%>
</$let>
\end
\procedure new-field()
\whitespace trim
<$vars name={{{ [<newFieldNameTiddler>get[text]] }}}>
<$reveal type="nomatch" text="" default=<<name>>>
<$button tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}>
<$action-sendmessage $message="tm-add-field"
$name=<<name>>
$value={{{ [subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
<$set name="safeNewFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
<$action-deletetiddler $filter="[<newFieldNameTiddler>] [prefix[$:/temp/NewFieldValue]prefix<safeNewFieldValueTiddlerPrefix>] [<storeTitle>] [<searchListState>]"/>
</$set>
<<lingo Fields/Add/Button>>
</$button>
</$reveal>
<$reveal type="match" text="" default=<<name>>>
<$button>
<<lingo Fields/Add/Button>>
</$button>
</$reveal>
</$vars>
<%if [<newFieldNameTiddler>get[text]!is[blank]] %>
<$button actions="<<new-field-actions>>" tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}>
<<lingo Fields/Add/Button>>
</$button>
<%else%>
<$button>
<<lingo Fields/Add/Button>>
</$button>
<%endif%>
\end
\whitespace trim
<$set name="newFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
<div class="tc-edit-fields">
<table class={{{ [all[current]fields[]] :filter[lookup[$:/config/EditTemplateFields/Visibility/]!match[hide]] :and[count[]!match[0]] :and[then[tc-edit-fields]] :else[[tc-edit-fields tc-edit-fields-small]] }}}>
<tbody>
<$list filter="[all[current]fields[]] :and[sort[title]]" variable="currentField" storyview="pop">
<$list filter=<<tf.config-filter>> variable="temp">
<tr class="tc-edit-field">
<td class="tc-edit-field-name">
<$text text=<<currentField>>/>:</td>
<td class="tc-edit-field-value">
<$keyboard key="((delete-field))" actions="""<$action-deletefield $field=<<currentField>>/><$set name="currentTiddlerCSSescaped" value={{{ [<currentTiddler>escapecss[]] }}}><$action-sendmessage $message="tm-focus-selector" $param=<<tf.current-tiddler-new-field-selector>>/></$set>""">
<$transclude tiddler={{{ [<currentField>] :cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]] :and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}] }}} />
</$keyboard>
</td>
<td class="tc-edit-field-remove">
<$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}>
<$action-deletefield $field=<<currentField>>/>
{{$:/core/images/delete-button}}
</$button>
</td>
</tr>
</$list>
</$list>
</tbody>
</table>
</div>
\function tf.config-filter() [lookup:show[$:/config/EditTemplateFields/Visibility/]!match[hide]]
<$fieldmangler>
<div class="tc-edit-field-add">
<em class="tc-edit tc-small-gap-right">
<<lingo Fields/Add/Prompt>>
</em>
<$vars refreshTitle=<<qualify "$:/temp/fieldname/refresh">> storeTitle=<<newFieldNameInputTiddler>> searchListState=<<newFieldNameSelectionTiddler>>>
<div class="tc-edit-field-add-name-wrapper">
<$transclude $variable="keyboard-driven-input" tiddler=<<newFieldNameTiddler>> storeTitle=<<storeTitle>> refreshTitle=<<refreshTitle>>
selectionStateTitle=<<searchListState>> tag="input" default="" placeholder={{$:/language/EditTemplate/Fields/Add/Name/Placeholder}}
focusPopup=<<qualify "$:/state/popup/field-dropdown">> class="tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}}
focus={{{ [{$:/config/AutoFocus}match[fields]then[true]] :else[[false]] }}} cancelPopups="yes"
configTiddlerFilter="[[$:/config/EditMode/fieldname-filter]]" inputCancelActions=<<cancel-search-actions>> />
<$button popup=<<qualify "$:/state/popup/field-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Field/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Field/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button>
<$reveal state=<<qualify "$:/state/popup/field-dropdown">> type="nomatch" text="" default="">
<div class="tc-block-dropdown tc-edit-type-dropdown">
<$set name="tv-show-missing-links" value="yes">
<$linkcatcher to=<<newFieldNameTiddler>>>
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/User>>
</div>
<$set name="newFieldName" value={{{ [<storeTitle>get[text]] }}}>
<$list filter="[!is[shadow]!is[system]fields[]search:title<newFieldName>sort[]] :except[[created]] :except[[creator]] :except[[draft.of]] :except[[draft.title]] :except[[modified]] :except[[modifier]] :except[[tags]] :except[[text]] :except[[title]] :except[[type]]" variable="currentField">
<$list filter="[<currentField>addsuffix[-primaryList]] :except[<searchListState>get[text]]" emptyMessage="""<$link to=<<currentField>> class="tc-list-item-selected"><$text text=<<currentField>>/></$link>""">
<$link to=<<currentField>>>
<$text text=<<currentField>>/>
</$link>
</$list>
</$list>
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/System>>
</div>
<$list filter="[fields[]search:title<newFieldName>sort[]] :except[!is[shadow]!is[system]fields[]]" variable="currentField">
<$list filter="[<currentField>addsuffix[-secondaryList]] :except[<searchListState>get[text]]" emptyMessage="""<$link to=<<currentField>> class="tc-list-item-selected"><$text text=<<currentField>>/></$link>""">
<$link to=<<currentField>>>
<$text text=<<currentField>>/>
</$link>
</$list>
</$list>
</$set>
</$linkcatcher>
</$set>
</div>
</$reveal>
</div>
<$let currentTiddlerCSSescaped={{{ [<currentTiddler>escapecss[]] }}} currentTiddler={{{ [subfilter<get-field-value-tiddler-filter>] }}} currentField="text" currentFieldName={{{ [<newFieldNameTiddler>get[text]] }}}>
<span class="tc-edit-field-add-value tc-small-gap-right">
<$keyboard key="((add-field))" actions=<<new-field-actions>>>
<$transclude tiddler={{{ [subfilter<get-field-editor-filter>] }}} />
</$keyboard>
</span>
<span class="tc-edit-field-add-button">
<$transclude $variable="new-field"/>
</span>
</$let>
</$vars>
</div>
</$fieldmangler>
</$set>
\function tf.field-cascade()
[<currentField>]
:cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]]
:and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
\end
\function tf.get-field-editor()
[<newFieldNameTiddler>get[text]else[]]
:cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]]
:and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
\end
\function tf.primary-list-exceptions() created creator draft.of draft.title modified modifier tags text title type
\function tf.list-selection-class(listSuffix) [<searchListState>get[text]removesuffix<listSuffix>match<currentField>then[tc-list-item-selected]]
<$let newFieldValueTiddlerPrefix={{{ [<newFieldValueTiddlerPrefix>!is[blank]else<qualify "$:/temp/NewFieldValue">] }}} >
<div class="tc-edit-fields">
<!-- table of user fields of the current tiddler -->
<table class=`tc-edit-fields ${ [all[current]fields[]] :filter[tf.config-filter[]] :and[count[]match[0]then[tc-edit-fields-small]] }$`>
<tbody>
<$list filter="[all[current]fields[]] :and[sort[title]]" variable="currentField" storyview="pop">
<%if [<currentField>tf.config-filter[]] %>
<tr class="tc-edit-field">
<td class="tc-edit-field-name">
<$text text=<<currentField>>/>:
</td>
<td class="tc-edit-field-value">
<$keyboard key="((delete-field))" actions="<<delete-field-actions>>">
<$transclude tiddler=<<tf.field-cascade>> />
</$keyboard>
</td>
<td class="tc-edit-field-remove">
<$button actions="<<delete-field-actions>>"
aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}
class="tc-btn-invisible"
tooltip={{$:/language/EditTemplate/Field/Remove/Hint}}
>
{{$:/core/images/delete-button}}
</$button>
</td>
</tr>
<%endif%>
</$list>
</tbody>
</table>
</div>
<!-- input control for new field name with selection dropdown -->
<div class="tc-edit-field-add">
<em class="tc-edit tc-small-gap-right">
<<lingo Fields/Add/Prompt>>
</em>
<$let refreshTitle=<<qualify "$:/temp/fieldname/refresh">>
storeTitle=<<newFieldNameInputTiddler>>
searchListState=<<newFieldNameSelectionTiddler>>
>
<div class="tc-edit-field-add-name-wrapper">
<$transclude $variable="keyboard-driven-input"
cancelPopups="yes"
class=`tc-edit-texteditor tc-popup-handle ${ [<newFieldNameTiddler>get[text]] :intersection[<storyTiddler>fields[]] :then[[tc-edit-field-exists]] }$`
configTiddlerFilter="[[$:/config/EditMode/fieldname-filter]]"
default=""
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[fields]then[true]] :else[{$:/config/AutoFocus}match[fields]then[true]] :else[[false]] }}}
focusPopup=<<qualify "$:/state/popup/field-dropdown">>
inputAcceptVariantActions=<<save-tiddler-actions>>
inputCancelActions=<<cancel-search-actions>>
placeholder={{$:/language/EditTemplate/Fields/Add/Name/Placeholder}}
refreshTitle=<<refreshTitle>>
selectionStateTitle=<<searchListState>>
storeTitle=<<storeTitle>>
tag="input"
tabindex={{$:/config/EditTabIndex}}
tiddler=<<newFieldNameTiddler>>
/>
<$button aria-label={{$:/language/EditTemplate/Field/Dropdown/Caption}}
class="tc-btn-invisible tc-btn-dropdown tc-small-gap"
popup=<<qualify "$:/state/popup/field-dropdown">>
tooltip={{$:/language/EditTemplate/Field/Dropdown/Hint}}
>
{{$:/core/images/down-arrow}}
</$button>
<$reveal state=<<qualify "$:/state/popup/field-dropdown">> type="nomatch" text="" default="" tag="div" class="tc-block-dropdown tc-edit-type-dropdown">
<$let tv-show-missing-links="yes">
<$linkcatcher to=<<newFieldNameTiddler>>>
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/User>>
</div>
<$let newFieldName={{{ [<storeTitle>get[text]] }}}
primaryListFields={{{ [!is[shadow]!is[system]fields[]format:titlelist[]join[ ]] }}}
>
<$list filter="[enlist<primaryListFields>search:title<newFieldName>sort[]] :except[tf.primary-list-exceptions[]]" variable="currentField">
<$link to=<<currentField>> class=<<tf.list-selection-class "-primaryList">> >
<$text text=<<currentField>>/>
</$link>
</$list>
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/System>>
</div>
<$list filter="[fields[]search:title<newFieldName>!enlist<primaryListFields>sort[]]" variable="currentField">
<$link to=<<currentField>> class=<<tf.list-selection-class "-secondaryList">>>
<$text text=<<currentField>>/>
</$link>
</$list>
</$let>
</$linkcatcher>
</$let>
</$reveal>
</div>
<!-- input control for new field content -->
<$let currentFieldName={{{ [<newFieldNameTiddler>get[text]] }}}
fieldEditor=<<tf.get-field-editor>>
newFieldValueTiddler={{{ [<newFieldValueTiddlerPrefix>] [[/]] [<fieldEditor>sha256[16]] :and[join[]] }}}
currentTiddler=<<newFieldValueTiddler>>
>
<span class="tc-edit-field-add-value tc-small-gap-right">
<$keyboard key="((add-field))" actions="<<new-field-actions>>">
<$transclude $tiddler=<<fieldEditor>> />
</$keyboard>
</span>
<span class="tc-edit-field-add-button">
<$transclude $variable="new-field"/>
</span>
</$let>
</$let>
</div>
</$let>

View File

@@ -17,7 +17,7 @@ tags: $:/tags/EditTemplate
<$let backgroundColor=<<colour>> >
<span class="tc-tag-label tc-tag-list-item tc-small-gap-right"
data-tag-title=<<currentTiddler>>
style=`color:$(foregroundColor)$; background-color:$(backgroundColor)$;`
style=`color:$(foregroundColor)$; background-color:$(backgroundColor)$; --tp-remove-tag-button-color:$(foregroundColor)$`
>
<$transclude tiddler=<<icon>>/>
<$view field="title" format="text"/>

View File

@@ -2,7 +2,11 @@ title: $:/core/ui/EditTemplate/title
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"/>
<$edit-text field="draft.title" class="tc-titlebar tc-edit-texteditor"
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[title]then[true]] ~[{$:/config/AutoFocus}match[title]then[true]] ~[[false]] }}}
tabindex={{$:/config/EditTabIndex}}
cancelPopups="yes"
/>
<$vars pattern="""[\|\[\]{}]""" bad-chars="""`| [ ] { }`""">

View File

@@ -4,13 +4,28 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d
\procedure lingo-base() $:/language/EditTemplate/
\procedure input-cancel-actions() <$list filter="[<storeTitle>get[text]] [<currentTiddler>get[type]] :and[limit[1]]" emptyMessage="""<<cancel-delete-tiddler-actions "cancel">>"""><$action-sendmessage $message="tm-remove-field" $param="type"/><$action-deletetiddler $filter="[<typeInputTiddler>] [<refreshTitle>] [<typeSelectionTiddler>]"/></$list>
\whitespace trim
<$set name="refreshTitle" value=<<qualify "$:/temp/type-search/refresh">>>
<div class="tc-edit-type-selector-wrapper">
<em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em>
<div class="tc-type-selector-dropdown-wrapper">
<div class="tc-type-selector"><$fieldmangler>
<$transclude $variable="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>> selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle tc-keep-focus" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] :else[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<<input-cancel-actions>>/><$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button><$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button>
<div class="tc-type-selector">
<$fieldmangler>
<$transclude $variable="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>>
selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}}
focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle tc-keep-focus"
tabindex={{$:/config/EditTabIndex}}
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[type]then[true]] :else[{$:/config/AutoFocus}match[type]then[true]] :else[[false]] }}}
cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]"
inputCancelActions=<<input-cancel-actions>>
/>
<$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>
{{$:/core/images/down-arrow}}
</$button>
<$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>
{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/>
</$button>
</$fieldmangler></div>
<div class="tc-block-dropdown-wrapper">

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

@@ -8,6 +8,8 @@ code-body: yes
<$set name="languageTitle" value={{!!name}}>
<$transclude $tiddler="$:/core/stylesheets/custom-properties" $mode="block"/>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Stylesheet]!has[draft.of]]">
<$transclude mode="block"/>
</$list>

View File

@@ -0,0 +1,38 @@
title: $:/core/ui/TiddlerInfo/Advanced/CascadeInfo
tags: $:/tags/TiddlerInfo/Advanced
\define lingo-base() $:/language/TiddlerInfo/Advanced/CascadeInfo/
<$let infoTiddler=<<currentTiddler>>>
''<<lingo Heading>>''
<table class="tc-max-width">
<thead>
<$list filter="[[View]] [[Active Cascade Filter]] [[Template]]" variable="heading">
<th><<heading>></th>
</$list>
</thead>
<$list filter="[[$:/tags/ViewTemplate]tagging[]]" variable="ViewTemplate">
<tr>
<$let
view={{{ [<ViewTemplate>]+[split[/]last[]] }}}
tagFilter=`$:/tags/ViewTemplate${ [<view>titlecase[]] }$Filter`
activeCascadeFilterTiddler={{{ [all[shadows+tiddlers]tag<tagFilter>!is[draft]]:filter[<storyTiddler>subfilter{!!text}]+[first[]] }}}
activeCascadeFilter={{{ [<activeCascadeFilterTiddler>get[text]] }}}
activeTemplateTiddler={{{ [<currentTiddler>]:cascade[all[shadows+tiddlers]tag<tagFilter>!is[draft]get[text]] }}}
>
<%if [<activeCascadeFilterTiddler>!is[blank]]%>
<td>
<$link to=<<ViewTemplate>> ><<view>></$link>
</td>
<td>
<$link to=<<activeCascadeFilterTiddler>> />
</td>
<td style="text-align:center;">
<$link class="tc-btn-invisible" to=<<activeTemplateTiddler>>><$button class="tc-btn-invisible">{{$:/core/images/file}}</$button></$link>
</td>
<%endif%>
</$let>
</tr>
</$list>
</table>

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

@@ -118,7 +118,7 @@ The second ESC tries to close the "draft tiddler"
focusPopup=<<tf.tagpicker-dropdown-id>>
class="tc-edit-texteditor tc-popup-handle"
tabindex=<<tabIndex>>
focus={{{ [{$:/config/AutoFocus}match[tags]then[true]] :else[[false]] }}}
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[tags]then[true]] :else[{$:/config/AutoFocus}match[tags]then[true]] :else[[false]] }}}
filterMinLength={{$:/config/Tags/MinLength}}
cancelPopups=<<cancelPopups>>
configTiddlerFilter="[[$:/core/macros/tag-picker]]"

View File

@@ -15,7 +15,18 @@ tags: $:/tags/Macro
</span>
\end
\define toc-body(tag,sort:"",itemClassFilter,exclude,path)
\define toc-level-indicator()
\whitespace trim
<%if [<__level__>compare:number:gt[0]]%>
<%if [<currentTiddler>tagging[]] %>
<span class="tc-tiny-gap-left">{{$:/core/images/new-button}}</span>
<%else%>
<span class="tc-tiny-gap-left">{{$:/core/images/blank}}</span>
<%endif%>
<% endif %>
\end
\define toc-body(tag,sort:"",itemClassFilter,exclude,path,level)
\whitespace trim
<ol class="tc-toc">
<$list filter="""[all[shadows+tiddlers]tag<__tag__>!has[draft.of]$sort$] -[<__tag__>] -[subfilter<__exclude__>]""">
@@ -23,10 +34,26 @@ tags: $:/tags/Macro
<$set name="excluded" filter="[subfilter<__exclude__>] [<__tag__>]">
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item">
<li class=<<toc-item-class>>>
<$list filter="[all[current]toc-link[no]]" emptyMessage="<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}><<toc-caption>></$link>">
<$list filter="[all[current]toc-link[no]]" >
<$list-empty>
<!-- link to target-field or currentTiddler -->
<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}>
<<toc-level-indicator>>
<<toc-caption>>
</$link>
</$list-empty>
<!-- toc-link = no -->
<<toc-level-indicator>>
<<toc-caption>>
</$list>
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>>/>
<$let _level={{{ [<__level__>subtract[1]] }}}>
<%if [<_level>compare:number:gt[0]]%>
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>> level=<<_level>>/>
<%elseif [<_level>match[-1]]%>
<!-- show full toc, no level defined -->
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>>/>
<%endif%>
</$let>
</li>
</$set>
</$set>
@@ -35,10 +62,10 @@ tags: $:/tags/Macro
</ol>
\end
\define toc(tag,sort:"",itemClassFilter:"", exclude)
\define toc(tag,sort:"",itemClassFilter:"",exclude,level)
\whitespace trim
<$let __tag__={{{ [<__tag__>is[blank]then<currentTiddler>else<__tag__>] }}} >
<$macrocall $name="toc-body" tag=<<__tag__>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<__exclude__>>/>
<$macrocall $name="toc-body" tag=<<__tag__>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<__exclude__>> level=<<__level__>>/>
</$let>
\end

View File

@@ -0,0 +1,8 @@
title: $:/snippets/minifocuseditswitcher
\whitespace trim
<$select tiddler="$:/config/AutoFocusEdit" default={{$:/config/AutoFocus}}>
<$list filter="title tags text">
<option><<currentTiddler>></option>
</$list>
</$select>

View File

@@ -1,2 +1,2 @@
title: $:/tags/TiddlerInfo/Advanced
list: [[$:/core/ui/TiddlerInfo/Advanced/ShadowInfo]] [[$:/core/ui/TiddlerInfo/Advanced/PluginInfo]]
list: [[$:/core/ui/TiddlerInfo/Advanced/ShadowInfo]] [[$:/core/ui/TiddlerInfo/Advanced/PluginInfo]] [[$:/core/ui/TiddlerInfo/Advanced/CascadeInfo]]

View File

@@ -20,8 +20,7 @@
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico"],
"static": [

View File

@@ -24,8 +24,7 @@
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico"],
"static": [

View File

@@ -10,8 +10,7 @@
"index": [
"--render","$:/core/save/all","index.html","text/plain"],
"empty": [
"--render","$:/core/save/all","empty.html","text/plain",
"--render","$:/core/save/all","empty.hta","text/plain"],
"--render","$:/core/save/all","empty.html","text/plain"],
"emptyexternalcore": [
"--render","$:/core/save/offline-external-js","empty-external-core.html","text/plain",
"--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffix<version>addsuffix[.js]]","text/plain"],

View File

@@ -27,8 +27,7 @@
"--setfield","[tag[external-image]] [tag[external-text]]","text","","text/plain",
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/es-ES/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/es-ES/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/es-ES/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico",
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],

View File

@@ -27,8 +27,7 @@
"--setfield","[tag[external-image]] [tag[external-text]]","text","","text/plain",
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico",
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],

View File

@@ -27,8 +27,7 @@
"--setfield","[tag[external-image]] [tag[external-text]]","text","","text/plain",
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/ja-JP/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/ja-JP/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/ja-JP/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico",
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],

View File

@@ -27,8 +27,7 @@
"--setfield","[tag[external-image]] [tag[external-text]]","text","","text/plain",
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/ko-KR/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/ko-KR/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/ko-KR/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico",
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],

View File

@@ -0,0 +1,16 @@
title: Functions/FunctionDefaultValues
description: Use defaults for missing parameters in functions in filters
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\function .test(prefix:Default) [[ Content]addprefix<prefix>]
<$text text={{{ [.test[Special]] }}}/>,<$text text={{{ [.test[]] }}}/>
+
title: ExpectedResult
<p>Special Content,Default Content</p>

View File

@@ -0,0 +1,38 @@
title: Functions/FunctionResolutionInSubstitute
description: Functions should resolve correctly in the substitute operator
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\function getIndex() [<index>add[1]]
\procedure template-with-var() $(getIndex)$
\procedure template-with-filteredexpression() ${ [<getIndex>] }$
\function test-with-substitute-variable()
[[abc]split[]] :map[<template-with-var>substitute[]] :and[join[ / ]]
\end
\function test-with-substitute-filteredexpression()
[[abc]split[]] :map[<template-with-filteredexpression>substitute[]] :and[join[ / ]]
\end
\function test-with-function()
[[abc]split[]] :map[function[getIndex]substitute[]] :and[join[ / ]]
\end
<<test-with-substitute-variable>>|
<<test-with-substitute-filteredexpression>>|
<<test-with-function>>
+
title: ExpectedResult
<p>1 / 2 / 3|1 / 2 / 3|1 / 2 / 3</p>

View File

@@ -177,6 +177,32 @@ describe("Widget module", function() {
expect(wrapper.innerHTML).toBe("<span class=\"tc-error\">Recursive transclusion error in transclude widget</span> <span class=\"tc-error\">Recursive transclusion error in transclude widget</span>");
});
// Do NOT use a for-of or for-in here. Each jasmine test must be
// defined in its own function context, or the `tag` variable will
// end up being the same value for all iterations of the test.
$tw.utils.each(["div","$button","$checkbox","$diff-text","$draggable","$droppable","dropzone","$eventcatcher","$keyboard","$link","$list filter=x variable=x","$radio","$reveal type=nomatch","$scrollable","$select","$view field=x"],function(tag) {
it(`${tag} cleans itself up if children rendering fails`, function() {
var wiki = new $tw.Wiki();
wiki.addTiddler({title: "TiddlerOne", text: `<$tiddler tiddler='TiddlerOne'><${tag}><$transclude />`});
var parseTreeNode = {type: "widget", children: [
{type: "transclude", attributes: {
"tiddler": {type: "string", value: "TiddlerOne"}
}}
]};
// Construct the widget node
var widgetNode = createWidgetNode(parseTreeNode,wiki);
// Render the widget node to the DOM
var wrapper = renderWidgetNode(widgetNode);
// We don't actually care exactly what the HTML contains,
// only that it's reasonably sized. If it's super large,
// that means the widget containing the bad transclusion
// didn't figure out how to clean itself up, and it cloned a bunch.
var html = wrapper.innerHTML;
expect(html).toContain("Recursive transclusion error in transclude widget");
expect(html.length).toBeLessThan(256, "CONTENTS: " + html);
});
});
it("should handle many-tiddler recursion with branching nodes", function() {
var wiki = new $tw.Wiki();
// Add a tiddler

View File

@@ -48,6 +48,7 @@ Prioritise the groups to translate first. A translation can be useful without be
* Buttons
* Control Panel
* Date
* Draft
* Edit Template
* Getting Started
* Import

View File

@@ -0,0 +1,3 @@
title: $:/status/UserName
Jermolene

View File

@@ -51,6 +51,7 @@
"--render","$:/plugins/tiddlywiki/translators/templates/ControlPanel.multids","language/ControlPanel.multids","text/plain",
"--render","$:/plugins/tiddlywiki/translators/templates/CoreReadMe.tid","language/CoreReadMe.tid","text/plain",
"--render","$:/plugins/tiddlywiki/translators/templates/Dates.multids","language/Dates.multids","text/plain",
"--render","$:/plugins/tiddlywiki/translators/templates/Draft.multids","language/Draft.multids","text/plain",
"--render","$:/plugins/tiddlywiki/translators/templates/EditTemplate.multids","language/EditTemplate.multids","text/plain",
"--render","$:/plugins/tiddlywiki/translators/templates/Exporters.multids","language/Exporters.multids","text/plain",
"--render","$:/plugins/tiddlywiki/translators/templates/Fields.multids","language/Fields.multids","text/plain",

View File

@@ -0,0 +1,11 @@
created: 20251108075028089
modified: 20260130070027124
tags: Reference
title: Core CSS Variables
type: text/vnd.tiddlywiki
<<.from-version 5.4.0>> Tiddlywiki CSS variable definitions starts with `--tp-*` and `--tpc-*`. They are mainly used to [[Write stylesheets in vanilla CSS|Writing stylesheets in vanilla CSS]]. These prefixes ''are reserved'' for Tiddlywiki, so it should not be used for user defined CSS variables. It is also not recommended to override these core CSS variables.
Core CSS variables are defined in [[$:/core/stylesheets/custom-properties]].
<<list-links "[tag[Core CSS Variables]]" class:"multi-columns">>

View File

@@ -0,0 +1,69 @@
created: 20251108075645447
modified: 20260201043953311
title: Writing stylesheets in vanilla CSS
type: text/vnd.tiddlywiki
<<.from-version 5.4.0>> Before v5.4.0, theme developers have to mix wikitext syntax with CSS syntax when writing stylesheets to intergrate Tiddlywiki color palettes and theme settings. With the introduction of [[Core CSS Variables]] in v5.4.0, theme developers can intergrate most Tiddlywiki palettes with vanilla CSS.
! Getting Tiddlywiki palette colors
Tiddlywiki's custom properties for colors are prefixed `--tpc-`. Before v5.4.0, theme developers have to use the following wikitext to get a color value of a palette:
```
.tag {
background: <<colour tag-background>>;
}
```
Since v5.4.0, theme developers can use the following CSS to get the palette color:
```css
.tag {
background: var(--tp-color-tag-background);
}
```
! Getting and processing Tiddlywiki CSS settings
See [[Core CSS Variables]] for the available CSS variables. Before v5.4.0, theme developers have to use macros with filters to get and process theme settings:
```
.tc-sidebar-header {
padding: 14px;
min-height: 32px;
margin-top: {{$:/themes/tiddlywiki/vanilla/metrics/storytop}};
transition: min-height {{$:/config/AnimationDuration}}ms ease-in-out, padding-top {{$:/config/AnimationDuration}}ms ease-in-out, padding-bottom {{$:/config/AnimationDuration}}ms ease-in-out;
}
```
Since v5.4.0, the theme settings are also available as the CSS variables, so theme developers can use the following code:
```css
.tc-sidebar-header {
padding: 14px;
min-height: 32px;
margin-top: var(--tp-story-top);
transition: min-height var(--tp-animation-duration) ease-in-out, padding-top var(--tp-animation-duration) ease-in-out, padding-bottom var(--tp-animation-duration) ease-in-out;
}
```
! Limits
CSS variables can only be used in rules, while wikitext can be used everywhere. See this example:
Old way of using wikitext in media query definitions:
```
\define sidebarbreakpoint()
<$text text={{$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint}}/>
\end
@media (min-width: <<sidebarbreakpoint>>) {
/* CSS rules */
}
```
While using CSS variables in media quert definitions doesn't work at all:
```css
@media (min-width: var(--tp-sidebar-breakpoint)) {
/* Doesn't work */
}
```

View File

@@ -193,11 +193,11 @@ tr.doc-table-subheading {
}
.doc-block-icon .tc-image-tip {
fill: <<colour primary>>;
color: <<colour primary>>;
}
.doc-block-icon .tc-image-warning {
fill: <<colour alert-highlight>>;
color: <<colour alert-highlight>>;
}
a.doc-from-version {
@@ -215,7 +215,6 @@ a.doc-from-version.doc-from-version-new {
}
a.doc-from-version svg {
fill: currentColor;
vertical-align: sub;
}
@@ -224,7 +223,6 @@ a.doc-deprecated-version.tc-tiddlylink {
border-radius: 1em;
background: red;
color: <<colour background>>;
fill: <<colour background>>;
padding: 0 0.4em;
font-size: 0.7em;
text-transform: uppercase;

View File

@@ -36,7 +36,6 @@ type: text/vnd.tiddlywiki
border-radius: 6px;
padding: 0.5em;
color: <<colour background>>;
fill: <<colour background>>;
}
.tc-cards.tc-action-card .tc-card-button svg {

View File

@@ -0,0 +1,8 @@
caption: --tp-animation-duration
created: 20260130075755351
modified: 20260130075855780
tags: [[Core CSS Variables]]
title: --tp-animation-duration CSS Variable
type: text/vnd.tiddlywiki
The `--tp-animation-duration` CSS variable represents the "Animation duration" setting in the control panel. The `ms` suffix is added.

View File

@@ -0,0 +1,8 @@
caption: --tp-body-font-size
created: 20260122111308483
modified: 20260130045633904
tags: [[Core CSS Variables]]
title: --tp-body-font-size CSS Variable
type: text/vnd.tiddlywiki
The `--tp-body-font-size` CSS variable represents the "Font size for tiddler body" setting in Theme Tweaks.

View File

@@ -0,0 +1,8 @@
caption: --tp-body-line-height
created: 20260122111249446
modified: 20260130045720240
tags: [[Core CSS Variables]]
title: --tp-body-line-height CSS Variable
type: text/vnd.tiddlywiki
The `--tp-body-line-height` CSS variable represents the "Line height for tiddler body" setting in Theme Tweaks.

View File

@@ -0,0 +1,8 @@
caption: --tp-code-font-family
created: 20260122110850673
modified: 20260130045333134
tags: [[Core CSS Variables]]
title: --tp-code-font-family CSS Variable
type: text/vnd.tiddlywiki
The `--tp-code-font-family` CSS variable represents the "Code font family" setting in Theme Tweaks.

View File

@@ -0,0 +1,8 @@
caption: --tp-code-wrapping
created: 20260122110618231
modified: 20260130045345387
tags: [[Core CSS Variables]]
title: --tp-code-wrapping CSS Variable
type: text/vnd.tiddlywiki
The `--tp-code-wrapping` CSS variable represents the "Wrap long lines in code blocks" setting in Theme Tweaks. Its value is `pre` when the setting is set to "No", and `pre-wrap` when set to "Yes".

View File

@@ -0,0 +1,8 @@
caption: --tp-editor-font-family
created: 20260122110858791
modified: 20260130045422071
tags: [[Core CSS Variables]]
title: --tp-editor-font-family CSS Variable
type: text/vnd.tiddlywiki
The `--tp-editor-font-family` CSS variable represents the "Editor font family" setting in Theme Tweaks.

View File

@@ -0,0 +1,8 @@
caption: --tp-font-family
created: 20260122110741282
modified: 20260130045301357
tags: [[Core CSS Variables]]
title: --tp-font-family CSS Variable
type: text/vnd.tiddlywiki
The `--tp-font-family` CSS variable represents the "Font family" setting in Theme Tweaks.

View File

@@ -0,0 +1,8 @@
caption: --tp-font-size
created: 20260122110956954
modified: 20260130045455466
tags: [[Core CSS Variables]]
title: --tp-font-size CSS Variable
type: text/vnd.tiddlywiki
The `--tp-font-size` CSS variable represents the "Font size" setting in Theme Tweaks.

View File

@@ -0,0 +1,8 @@
caption: --tp-line-height
created: 20260122111115266
modified: 20260130045757204
tags: [[Core CSS Variables]]
title: --tp-line-height CSS Variable
type: text/vnd.tiddlywiki
The `--tp-line-height` CSS variable represents the "Line height" setting in Theme Tweaks.

View File

@@ -0,0 +1,8 @@
caption: --tp-sidebar-breakpoint
created: 20260130065756457
modified: 20260130065841355
tags: [[Core CSS Variables]]
title: --tp-sidebar-breakpoint CSS Variable
type: text/vnd.tiddlywiki
The `--tp-sidebar-breakpoint` CSS variable represents the minimum page width at which the story river and sidebar will appear side by side.

View File

@@ -0,0 +1,8 @@
caption: --tp-sidebar-width
created: 20260130065850825
modified: 20260130065925595
tags: [[Core CSS Variables]]
title: --tp-sidebar-width CSS Variable
type: text/vnd.tiddlywiki
The `--tp-sidebar-width` CSS variable represents the width of the sidebar in fluid-fixed layout.

View File

@@ -0,0 +1,8 @@
caption: --tp-story-left
created: 20260130065252380
modified: 20260130065342364
tags: [[Core CSS Variables]]
title: --tp-story-left CSS Variable
type: text/vnd.tiddlywiki
The `--tp-story-left` CSS variable represents how far the left margin of the story river (tiddler area) is from the left of the page.

View File

@@ -0,0 +1,8 @@
caption: --tp-story-right
created: 20260130065417448
modified: 20260130065457358
tags: [[Core CSS Variables]]
title: --tp-story-right CSS Variable
type: text/vnd.tiddlywiki
The `--tp-story-right` CSS variable represents how far the left margin of the sidebar is from the left of the page.

View File

@@ -0,0 +1,8 @@
caption: --tp-story-top
created: 20260130065351753
modified: 20260130065451132
tags: [[Core CSS Variables]]
title: --tp-story-top CSS Variable
type: text/vnd.tiddlywiki
The `--tp-story-top` CSS variable represents how far the top margin of the story river is from the top of the page.

View File

@@ -0,0 +1,8 @@
caption: --tp-story-width
created: 20260130065510314
modified: 20260130065552027
tags: [[Core CSS Variables]]
title: --tp-story-width CSS Variable
type: text/vnd.tiddlywiki
The `--tp-story-width` CSS variable represents the overall width of the story river.

View File

@@ -0,0 +1,8 @@
caption: --tp-tiddler-width
created: 20260130065558913
modified: 20260130065746707
tags: [[Core CSS Variables]]
title: --tp-tiddler-width CSS Variable
type: text/vnd.tiddlywiki
The `--tp-tiddler-width` CSS variable represents the tiddler width within the story river.

View File

@@ -0,0 +1,16 @@
caption: --tpc-*
created: 20260122104142525
modified: 20260122104847331
tags: [[Core CSS Variables]]
title: --tpc-* CSS Variables
type: text/vnd.tiddlywiki
`--tpc-*` variables are CSS variables of palette colors. They can be accessed via appending the palette color names to the bottom. For example:
```css
.code {
background-color: var(--tpc-code-background);
color: var(--tpc-code-foreground);
}
```

View File

@@ -1,11 +1,43 @@
created: 20150221201838000
modified: 20150221203742000
title: Example Table of Contents: Simple
caption: Simple
tags: table-of-contents-example
created: 20150221201838000
modified: 20260124125915331
order: 1
tags: table-of-contents-example
title: Example Table of Contents: Simple
type: text/vnd.tiddlywiki
<$macrocall $name=".example" n="1"
eg="""<div class="tc-table-of-contents">
<<toc "Contents">>
</div>"""/>
---
<<.tip"""If you use several parameters in a macro call, it is advised to use ''named parameters'' for all of them""">>
<$macrocall $name=".example" n="2"
eg="""<div class="tc-table-of-contents">
<<toc tag:"Contents" level:"1">>
</div>"""/>
<$macrocall $name=".example" n="3"
eg="""<div class="tc-table-of-contents">
<<toc tag:"Contents" level:"2">>
</div>"""/>
<$macrocall $name=".example" n="4"
eg="""<div class="tc-table-of-contents">
<<toc tag:"Contents" level:"4">>
</div>"""/>
* If you want to ''change'' or ''remove'' the indicator, you can locally overwrite the `toc-level-indicator` macro.
* It is globally defined at: $:/core/macros/toc tiddler. Eg:
<$macrocall $name=".example" n="5"
eg="""\define toc-level-indicator()
<!-- remove the indicator -->
\end
<div class="tc-table-of-contents">
<<toc tag:"Contents" level:"1">>
</div>"""/>

View File

@@ -37,5 +37,5 @@ Some icons take further parameters to customise how they are rendered. For examp
The core icons are implemented as embedded [[SVG elements|Using SVG]], and not as full-blown SVG images. This means that they can be styled using CSS. For example, the CSS property `fill` can be used to change the colour of the icons. For example:
<<wikitext-example-without-html """<span style="fill: red;">{{$:/core/images/opacity}}</span>
<<wikitext-example-without-html """<span style="color: red;">{{$:/core/images/opacity}}</span>
""">>

View File

@@ -81,7 +81,7 @@ type: text/vnd.tiddlywiki
}
[data-tiddler-title="Hire the founder of TiddlyWiki"] .tc-btn-big-green svg {
fill: #cece86;
color: #cece86;
}
.yellow-note-sidebar-wrapper {

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,5 +1,5 @@
created: 20140919155729620
modified: 20240624102502089
modified: 20260124130054271
tags: Macros [[Core Macros]]
title: Table-of-Contents Macros
type: text/vnd.tiddlywiki
@@ -74,6 +74,9 @@ These two parameters are combined into a single [[filter expression|Filter Expre
; exclude <<.from-version "5.3.0">>
: This optional parameter can be used to exclude tiddlers from the TOC list. It allows a [[Title List]] or a <<.olink subfilter>>. Eg: `exclude:"HelloThere [[Title with spaces]]"` or `exclude:"[has[excludeTOC]]"`. Where the former will exclude two tiddlers and the later would exclude every tiddler that has a field <<.field excludeTOC>> independent of its value.<br>''Be aware'' that eg: `[prefix[H]]` is a shortcut for `[all[tiddlers]prefix[H]]`, which can have a performance impact, if used carelessly. So use $:/AdvancedSearch -> ''Filters'' tab to test the <<.param exclude>> parameter
; level <<.from-version "5.4.0">>
: This optional parameter can be used to define how many toc levels are shown by the toc-macro. By default all levels are shown.
!! Custom Icons
<<.from-version "5.2.4">>

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
; 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
: An optional subfilter to filter out unwanted items (eg `!tag[done]`)
;itemTemplate
: Optional title of a tiddler to use as the template for rendering list items
;emptyMessage
: Optional wikitext to display if there are no tiddlers with the specified tag
Heres a corrected and polished version of your sentence in clear, standard English:
;displayField
: 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.
; subFilter
: An optional subfilter to filter out unwanted items (eg `!tag[done]`)
; itemTemplate
: Optional title of a tiddler to use as the template for rendering list items
; 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
; 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.

Some files were not shown because too many files have changed in this diff Show More