1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-02-18 16:09:52 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Jeremy Ruston
88619fc215 Add changenote 2025-12-29 21:09:17 +00:00
Jeremy Ruston
dfc5e9e6e5 Initial Commit 2025-12-29 21:01:38 +00:00
424 changed files with 3824 additions and 7085 deletions

View File

@@ -1,62 +0,0 @@
---
name: Bug report
about: Create a report to help us improve TiddlyWiki 5
title: "[Report] "
type: report
---
<!-- Remove elements, that you do not need -->
<!-- Add screenshots where needed -->
**Problem Description**
<!-- Describe your problem: A clear and concise description of what your problem is -->
**To Reproduce**
Steps to reproduce the behavior:
1. At https://tiddlywiki.com
2. Click on ...
3. Scroll down to ...
4. See ...
**Expected behavior**
As a user,
<!-- As a developer, -->
I would expect ...
**TiddlyWiki Configuration**
<!-- Please complete the following information -->
- Report created with: [Wiki Information](https://tiddlywiki.com/#%24%3A%2Fcore%2Fui%2FControlPanel%2FWikiInformation)
<!-- Your report comes here -->
<!-- or -->
<!-- Add it manually -->
- Version: <!-- e.g. v5.3.8 -->
- Saving mechanism: <!-- e.g. Node.js, TiddlyDesktop, TiddlyHost etc -->
- Plugins installed: <!-- e.g. Freelinks, TiddlyMap ... other 3rd party plugins -->
**Desktop**
<!-- Please complete the following information -->
- OS: <!-- e.g. iOS -->
- Browser: <!-- e.g. chrome, safari, FireFox -- Version: -->
**Smartphone**
<!-- Please complete the following information -->
- Device: <!-- e.g. iPhone6 -->
- OS: <!-- e.g. iOS8.1 -->
- Browser: <!-- e.g. stock browser, safari, FireFox -- Version: -->
**Additional context**
<!-- Add any other context about the problem here. -->

68
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Bug report
description: Create a report to help us improve TiddlyWiki 5
title: "[BUG] "
type: bug
body:
- type: textarea
id: Describe
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: Expected
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: false
- type: textarea
id: Reproduce
attributes:
label: To Reproduce
description: "Steps to reproduce the behavior:"
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: false
- type: textarea
id: Screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
placeholder: Drag image here to upload screenshot!
validations:
required: false
- type: textarea
id: Configuration
attributes:
label: TiddlyWiki Configuration
description: please complete the following information
placeholder: |
- Version [e.g. v5.1.24]
- Saving mechanism [e.g. Node.js, TiddlyDesktop, TiddlyHost etc]
- Plugins installed [e.g. Freelinks, TiddlyMap]
### Desktop (please complete the following information):
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
### Smartphone (please complete the following information):
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
validations:
required: true
- type: textarea
id: Context
attributes:
label: Additional context
description: Add any other context about the problem here.

View File

@@ -4,23 +4,18 @@ about: Suggest an idea for TiddlyWiki 5
title: "[IDEA]" title: "[IDEA]"
labels: '' labels: ''
assignees: '' assignees: ''
type: idea type: feature
--- ---
**Is your idea related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
A clear and concise description of what the problem is. Eg:
As a user, I would like [...]
**Describe the solution you'd like** **Describe the solution you'd like**
A clear and concise description of what you want to happen. A clear and concise description of what you want to happen.
**Describe alternatives you've considered** **Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered. A clear and concise description of any alternative solutions or features you've considered.
**Additional context** **Additional context**
Add any other context or screenshots about the feature request here. Add any other context or screenshots about the feature request here.

View File

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

View File

@@ -316,25 +316,8 @@ $tw.utils.htmlDecode = function(s) {
return s.toString().replace(/&lt;/mg,"<").replace(/&nbsp;/mg,"\xA0").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&"); return s.toString().replace(/&lt;/mg,"<").replace(/&nbsp;/mg,"\xA0").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
}; };
/* /** @deprecated Use window.location.hash instead. */
Get the browser location.hash. We don't use location.hash because of the way that Firefox auto-urldecodes it (see http://stackoverflow.com/questions/1703552/encoding-of-window-location-hash) $tw.utils.getLocationHash = () => window.location.hash;
*/
$tw.utils.getLocationHash = function() {
const href = window.location.href,
idx = href.indexOf("#");
if(idx === -1) {
return "#";
}
const afterHash = href.substring(idx + 1);
if(afterHash.startsWith("#") || afterHash.startsWith("%23")) {
// Special case: ignore location hash if it itself starts with a #
return "#";
}
return href.substring(idx);
};
/** @deprecated Pad a string to a given length with "0"s. Length defaults to 2 */ /** @deprecated Pad a string to a given length with "0"s. Length defaults to 2 */
$tw.utils.pad = function(value,length = 2) { $tw.utils.pad = function(value,length = 2) {
@@ -613,7 +596,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
// Compile the code into a function // Compile the code into a function
var fn; var fn;
if($tw.browser) { if($tw.browser) {
fn = Function("return " + code + "\n\n//# sourceURL=" + filename)(); // See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839 fn = window["eval"](code + "\n\n//# sourceURL=" + filename); // eslint-disable-line no-eval -- See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
} else { } else {
if(sandbox){ if(sandbox){
fn = vm.runInContext(code,sandbox,filename) 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}}); var text = state.wiki.renderTiddler(renderType,renderTemplate,{parseAsInline: true, variables: {currentTiddler: title}});
var headers = {"Content-Type": renderType}; // 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,headers,text,"utf8"); state.sendResponse(200,{},text,"utf8");
} else { } else {
response.writeHead(404); response.writeHead(404);
response.end(); response.end();

View File

@@ -42,8 +42,6 @@ function Server(options) {
} }
// Setup the default required plugins // Setup the default required plugins
this.requiredPlugins = this.get("required-plugins").split(','); this.requiredPlugins = this.get("required-plugins").split(',');
// Initialise CORS
this.corsEnable = this.get("cors-enable") === "yes";
// Initialise CSRF // Initialise CSRF
this.csrfDisable = this.get("csrf-disable") === "yes"; this.csrfDisable = this.get("csrf-disable") === "yes";
// Initialize Gzip compression // Initialize Gzip compression
@@ -263,13 +261,6 @@ Server.prototype.requestHandler = function(request,response,options) {
state.urlInfo = url.parse(request.url); state.urlInfo = url.parse(request.url);
state.queryParameters = querystring.parse(state.urlInfo.query); state.queryParameters = querystring.parse(state.urlInfo.query);
state.pathPrefix = options.pathPrefix || this.get("path-prefix") || ""; state.pathPrefix = options.pathPrefix || this.get("path-prefix") || "";
// Enable CORS
if(this.corsEnable) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Expose-Headers", "*");
}
state.sendResponse = sendResponse.bind(self,request,response); state.sendResponse = sendResponse.bind(self,request,response);
// Get the principals authorized to access this resource // Get the principals authorized to access this resource
state.authorizationType = options.authorizationType || this.methodMappings[request.method] || "readers"; state.authorizationType = options.authorizationType || this.methodMappings[request.method] || "readers";
@@ -294,12 +285,6 @@ Server.prototype.requestHandler = function(request,response,options) {
response.end(); response.end();
return; return;
} }
// Reply to OPTIONS
if(this.corsEnable && request.method === "OPTIONS") {
response.writeHead(204);
response.end();
return;
}
// Find the route that matches this path // Find the route that matches this path
var route = self.findMatchingRoute(request,state); var route = self.findMatchingRoute(request,state);
// Optionally output debug info // Optionally output debug info

View File

@@ -1,30 +0,0 @@
/*\
title: $:/core-modules/modules/utils/base64.js
type: application/javascript
module-type: utils-node
Base64 UTF-8 utlity functions.
\*/
"use strict";
const{ TextEncoder, TextDecoder } = require("node:util");
exports.btoa = binstr => Buffer.from(binstr, "binary").toString("base64");
exports.atob = b64 => Buffer.from(b64, "base64").toString("binary");
function base64ToBytes(base64) {
const binString = exports.atob(base64);
return Uint8Array.from(binString, m => m.codePointAt(0));
};
function bytesToBase64(bytes) {
const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join("");
return exports.btoa(binString);
};
exports.base64EncodeUtf8 = str => bytesToBase64(new TextEncoder().encode(str));
exports.base64DecodeUtf8 = str => new TextDecoder().decode(base64ToBytes(str));

View File

@@ -1,95 +0,0 @@
/*\
title: $:/core-server/modules/utils/escapecss.js
type: application/javascript
module-type: utils-node
Provides CSS.escape() functionality.
\*/
"use strict";
exports.escapeCSS = (function() {
// see also https://drafts.csswg.org/cssom/#serialize-an-identifier
/* eslint-disable */
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
return function(value) {
if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.');
}
var string = String(value);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: theres no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit == 0x0000) {
result += '\uFFFD';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index == 0 &&
length == 1 &&
codeUnit == 0x002D
) {
result += '\\' + string.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
return result;
};
/* eslint-enable */
})();

View File

@@ -5,4 +5,3 @@ TiddlyWiki incorporates code from these fine OpenSource projects:
* [[The Stanford Javascript Crypto Library|http://bitwiseshiftleft.github.io/sjcl/]] * [[The Stanford Javascript Crypto Library|http://bitwiseshiftleft.github.io/sjcl/]]
* [[The Jasmine JavaScript Test Framework|https://jasmine.github.io/]] * [[The Jasmine JavaScript Test Framework|https://jasmine.github.io/]]
* [[modern-normalize by Sindre Sorhus|https://github.com/sindresorhus/modern-normalize]] * [[modern-normalize by Sindre Sorhus|https://github.com/sindresorhus/modern-normalize]]
* [[diff-match-patch-es by antfu|https://github.com/antfu/diff-match-patch-es]]

View File

@@ -6,7 +6,6 @@ Appearance/Caption: Appearance
Appearance/Hint: Ways to customise the appearance of your TiddlyWiki. Appearance/Hint: Ways to customise the appearance of your TiddlyWiki.
Basics/AnimDuration/Prompt: Animation duration Basics/AnimDuration/Prompt: Animation duration
Basics/AutoFocus/Prompt: Default focus field for new tiddlers Basics/AutoFocus/Prompt: Default focus field for new tiddlers
Basics/AutoFocusEdit/Prompt: Default focus field for existing tiddlers
Basics/Caption: Basics 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/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 Basics/DefaultTiddlers/Prompt: Default tiddlers

View File

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

View File

@@ -1,6 +1,5 @@
title: $:/language/ title: $:/language/
Alerts: Alerts
AboveStory/ClassicPlugin/Warning: It looks like you are trying to load a plugin designed for ~TiddlyWiki Classic. Please note that [[these plugins do not work with TiddlyWiki version 5.x.x|https://tiddlywiki.com/#TiddlyWikiClassic]]. ~TiddlyWiki Classic plugins detected: AboveStory/ClassicPlugin/Warning: It looks like you are trying to load a plugin designed for ~TiddlyWiki Classic. Please note that [[these plugins do not work with TiddlyWiki version 5.x.x|https://tiddlywiki.com/#TiddlyWikiClassic]]. ~TiddlyWiki Classic plugins detected:
BinaryWarning/Prompt: This tiddler contains binary data BinaryWarning/Prompt: This tiddler contains binary data
ClassicWarning/Hint: This tiddler is written in TiddlyWiki Classic wiki text format, which is not fully compatible with TiddlyWiki version 5. See https://tiddlywiki.com/static/Upgrading.html for more details. ClassicWarning/Hint: This tiddler is written in TiddlyWiki Classic wiki text format, which is not fully compatible with TiddlyWiki version 5. See https://tiddlywiki.com/static/Upgrading.html for more details.

View File

@@ -9,11 +9,6 @@ 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/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/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/ShadowInfo/OverriddenShadow/Hint: It is overridden by an ordinary tiddler
Advanced/CascadeInfo/Heading: Cascade Details
Advanced/CascadeInfo/Hint: These are the view template segments (tagged <<tag "$:/tags/ViewTemplate">>) using a cascade filter and their resulting template for the current tiddler.
Advanced/CascadeInfo/Detail/View: View
Advanced/CascadeInfo/Detail/ActiveCascadeFilter: Active cascade filter
Advanced/CascadeInfo/Detail/Template: Template
Fields/Caption: Fields Fields/Caption: Fields
List/Caption: List List/Caption: List
List/Empty: This tiddler does not have a list List/Empty: This tiddler does not have a list

View File

@@ -1,116 +0,0 @@
/*\
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 = this.document.createElement("div");
this.toolbarNode.className = "tc-editor-toolbar"; this.toolbarNode.className = "tc-editor-toolbar";
parent.insertBefore(this.toolbarNode,nextSibling); parent.insertBefore(this.toolbarNode,nextSibling);
this.domNodes.push(this.toolbarNode);
this.renderChildren(this.toolbarNode,null); this.renderChildren(this.toolbarNode,null);
this.domNodes.push(this.toolbarNode);
} }
// Create our element // Create our element
var editInfo = this.getEditInfo(), var editInfo = this.getEditInfo(),

View File

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

View File

@@ -14,31 +14,31 @@ Export our filter function
*/ */
exports.sort = function(source,operator,options) { exports.sort = function(source,operator,options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,false,undefined,operator.operands[1]); options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,false);
return results; return results;
}; };
exports.nsort = function(source,operator,options) { exports.nsort = function(source,operator,options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,true,undefined,operator.operands[1]); options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,true);
return results; return results;
}; };
exports.sortan = function(source, operator, options) { exports.sortan = function(source, operator, options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results, operator.operands[0] || "title", operator.prefix === "!",false,false,true,operator.operands[1]); options.wiki.sortTiddlers(results, operator.operand || "title", operator.prefix === "!",false,false,true);
return results; return results;
}; };
exports.sortcs = function(source,operator,options) { exports.sortcs = function(source,operator,options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,false,undefined,operator.operands[1]); options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,false);
return results; return results;
}; };
exports.nsortcs = function(source,operator,options) { exports.nsortcs = function(source,operator,options) {
var results = prepare_results(source); var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,true,undefined,operator.operands[1]); options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,true);
return results; return results;
}; };

View File

@@ -37,14 +37,14 @@ exports.trim = function(source,operator,options) {
operand = (operator.operand || ""), operand = (operator.operand || ""),
fnCalc; fnCalc;
if(suffix === "prefix") { if(suffix === "prefix") {
fnCalc = function(a,b) {return [$tw.utils.trimPrefix(a,b)];}; fnCalc = function(a,b) {return [$tw.utils.trimPrefix(a,b)];}
} else if(suffix === "suffix") { } else if(suffix === "suffix") {
fnCalc = function(a,b) {return [$tw.utils.trimSuffix(a,b)];}; fnCalc = function(a,b) {return [$tw.utils.trimSuffix(a,b)];}
} else { } else {
if(operand === "") { if(operand === "") {
fnCalc = function(a) {return [$tw.utils.trim(a)];}; fnCalc = function(a) {return [$tw.utils.trim(a)];}
} else { } else {
fnCalc = function(a,b) {return [$tw.utils.trimSuffix($tw.utils.trimPrefix(a,b),b)];}; fnCalc = function(a,b) {return [$tw.utils.trimSuffix($tw.utils.trimPrefix(a,b),b)];}
} }
} }
source(function(tiddler,title) { source(function(tiddler,title) {
@@ -71,53 +71,107 @@ exports.join = makeStringReducingOperator(
},null },null
); );
const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js"); var dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
exports.levenshtein = makeStringBinaryOperator( exports.levenshtein = makeStringBinaryOperator(
function(a,b) { function(a,b) {
const diffs = dmp.diffMain(a,b); var dmpObject = new dmp.diff_match_patch(),
return [dmp.diffLevenshtein(diffs).toString()]; diffs = dmpObject.diff_main(a,b);
return [dmpObject.diff_levenshtein(diffs) + ""];
} }
); );
// this function is adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs // these two functions are adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
function diffLineWordMode(text1,text2,mode) { function diffLineWordMode(text1,text2,mode) {
var a = $tw.utils.diffPartsToChars(text1,text2,mode); var dmpObject = new dmp.diff_match_patch();
var a = diffPartsToChars(text1,text2,mode);
var lineText1 = a.chars1; var lineText1 = a.chars1;
var lineText2 = a.chars2; var lineText2 = a.chars2;
var lineArray = a.lineArray; var lineArray = a.lineArray;
var diffs = dmp.diffMain(lineText1,lineText2,false); var diffs = dmpObject.diff_main(lineText1,lineText2,false);
dmp.diffCharsToLines(diffs,lineArray); dmpObject.diff_charsToLines_(diffs,lineArray);
return diffs; return diffs;
} }
function diffPartsToChars(text1,text2,mode) {
var lineArray = [];
var lineHash = {};
lineArray[0] = '';
function diff_linesToPartsMunge_(text,mode) {
var chars = '';
var lineStart = 0;
var lineEnd = -1;
var lineArrayLength = lineArray.length,
regexpResult;
var searchRegexp = /\W+/g;
while(lineEnd < text.length - 1) {
if(mode === "words") {
regexpResult = searchRegexp.exec(text);
lineEnd = searchRegexp.lastIndex;
if(regexpResult === null) {
lineEnd = text.length;
}
lineEnd = --lineEnd;
} else {
lineEnd = text.indexOf('\n', lineStart);
if(lineEnd == -1) {
lineEnd = text.length - 1;
}
}
var line = text.substring(lineStart, lineEnd + 1);
if(lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) {
chars += String.fromCharCode(lineHash[line]);
} else {
if(lineArrayLength == maxLines) {
line = text.substring(lineStart);
lineEnd = text.length;
}
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
lineStart = lineEnd + 1;
}
return chars;
}
var maxLines = 40000;
var chars1 = diff_linesToPartsMunge_(text1,mode);
maxLines = 65535;
var chars2 = diff_linesToPartsMunge_(text2,mode);
return {chars1: chars1, chars2: chars2, lineArray: lineArray};
};
exports.makepatches = function(source,operator,options) { exports.makepatches = function(source,operator,options) {
var suffix = operator.suffix || "", var dmpObject = new dmp.diff_match_patch(),
suffix = operator.suffix || "",
result = []; result = [];
source(function(tiddler,title) { source(function(tiddler,title) {
let diffs, patches; var diffs, patches;
if(suffix === "lines" || suffix === "words") { if(suffix === "lines" || suffix === "words") {
diffs = diffLineWordMode(title,operator.operand,suffix); diffs = diffLineWordMode(title,operator.operand,suffix);
patches = dmp.patchMake(title,diffs); patches = dmpObject.patch_make(title,diffs);
} else { } else {
patches = dmp.patchMake(title,operator.operand); patches = dmpObject.patch_make(title,operator.operand);
} }
Array.prototype.push.apply(result,[dmp.patchToText(patches)]); Array.prototype.push.apply(result,[dmpObject.patch_toText(patches)]);
}); });
return result; return result;
}; };
exports.applypatches = makeStringBinaryOperator( exports.applypatches = makeStringBinaryOperator(
function(a,b) { function(a,b) {
let patches; var dmpObject = new dmp.diff_match_patch(),
patches;
try { try {
patches = dmp.patchFromText(b); patches = dmpObject.patch_fromText(b);
} catch(e) { } catch(e) {
} }
if(patches) { if(patches) {
return [dmp.patchApply(patches,a)[0]]; return [dmpObject.patch_apply(patches,a)[0]];
} else { } else {
return [a]; return [a];
} }
@@ -225,7 +279,7 @@ exports.pad = function(source,operator,options) {
} }
}); });
return results; return results;
}; }
exports.charcode = function(source,operator,options) { exports.charcode = function(source,operator,options) {
var chars = []; var chars = [];

View File

@@ -1,67 +0,0 @@
/*\
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,6 +33,13 @@ exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
// Screen size // Screen size
infoTiddlerFields.push({title: "$:/info/browser/screen/width", text: window.screen.width.toString()}); infoTiddlerFields.push({title: "$:/info/browser/screen/width", text: window.screen.width.toString()});
infoTiddlerFields.push({title: "$:/info/browser/screen/height", text: window.screen.height.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 // Language
infoTiddlerFields.push({title: "$:/info/browser/language", text: navigator.language || ""}); infoTiddlerFields.push({title: "$:/info/browser/language", text: navigator.language || ""});
} }

View File

@@ -11,16 +11,17 @@ The image parser parses an image into an embeddable HTML element
var ImageParser = function(type,text,options) { var ImageParser = function(type,text,options) {
var element = { var element = {
type: "image", type: "element",
attributes: {} tag: "img",
}; attributes: {}
};
if(options._canonical_uri) { if(options._canonical_uri) {
element.attributes.source = {type: "string", value: options._canonical_uri}; element.attributes.src = {type: "string", value: options._canonical_uri};
} else if(text) { } else if(text) {
if(type === "image/svg+xml" || type === ".svg") { if(type === "image/svg+xml" || type === ".svg") {
element.attributes.source = {type: "string", value: "data:image/svg+xml," + encodeURIComponent(text)}; element.attributes.src = {type: "string", value: "data:image/svg+xml," + encodeURIComponent(text)};
} else { } else {
element.attributes.source = {type: "string", value: "data:" + type + ";base64," + text}; element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
} }
} }
this.tree = [element]; this.tree = [element];

View File

@@ -107,14 +107,13 @@ exports.parseStringLiteral = function(source,pos) {
type: "string", type: "string",
start: pos start: pos
}; };
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')|\[\[((?:[^\]]|\](?!\]))*)\]\]/g; var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')/g;
reString.lastIndex = pos; reString.lastIndex = pos;
var match = reString.exec(source); var match = reString.exec(source);
if(match && match.index === pos) { if(match && match.index === pos) {
node.value = match[1] !== undefined ? match[1] :( node.value = match[1] !== undefined ? match[1] :(
match[2] !== undefined ? match[2] : ( match[2] !== undefined ? match[2] : match[3]
match[3] !== undefined ? match[3] : match[4] );
));
node.end = pos + match[0].length; node.end = pos + match[0].length;
return node; return node;
} else { } else {
@@ -143,14 +142,7 @@ exports.parseParameterDefinition = function(paramString,options) {
var paramInfo = {name: paramMatch[1]}, var paramInfo = {name: paramMatch[1]},
defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5]; defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5];
if(defaultValue !== undefined) { if(defaultValue !== undefined) {
// Check for an MVV reference ((varname)) paramInfo["default"] = defaultValue;
var mvvDefaultMatch = /^\(\(([^)|]+)\)\)$/.exec(defaultValue);
if(mvvDefaultMatch) {
paramInfo.defaultType = "multivalue-variable";
paramInfo.defaultVariable = mvvDefaultMatch[1];
} else {
paramInfo["default"] = defaultValue;
}
} }
params.push(paramInfo); params.push(paramInfo);
// Look for the next parameter // Look for the next parameter
@@ -181,7 +173,7 @@ exports.parseMacroParameter = function(source,pos) {
start: pos start: pos
}; };
// Define our regexp // Define our regexp
const reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[((?:[^\]]|\](?!\]))*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/y; const reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/y;
// Skip whitespace // Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos); pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for the parameter // Look for the parameter
@@ -214,201 +206,28 @@ exports.parseMacroParameter = function(source,pos) {
Look for a macro invocation. Returns null if not found, or {type: "transclude", attributes:, start:, end:} Look for a macro invocation. Returns null if not found, or {type: "transclude", attributes:, start:, end:}
*/ */
exports.parseMacroInvocationAsTransclusion = function(source,pos) { exports.parseMacroInvocationAsTransclusion = function(source,pos) {
var node = { var node = $tw.utils.parseMacroInvocation(source,pos);
type: "transclude", if(node) {
start: pos, var positionalName = 0,
attributes: {}, transclusion = {
orderedAttributes: [] type: "transclude",
}; start: node.start,
// Define our regexps end: node.end
var reVarName = /([^\s>"'=:]+)/g; };
// Skip whitespace $tw.utils.addAttributeToParseTreeNode(transclusion,"$variable",node.name);
pos = $tw.utils.skipWhiteSpace(source,pos); $tw.utils.each(node.params,function(param) {
// Look for a double opening angle bracket var name = param.name;
var token = $tw.utils.parseTokenString(source,pos,"<<"); if(name) {
if(!token) { if(name.charAt(0) === "$") {
return null; name = "$" + name;
}
pos = token.end;
// Get the variable name for the macro
token = $tw.utils.parseTokenRegExp(source,pos,reVarName);
if(!token) {
return null;
}
$tw.utils.addAttributeToParseTreeNode(node,"$variable",token.match[1]);
pos = token.end;
// Check that the tag is terminated by a space or >>
if(!$tw.utils.parseWhiteSpace(source,pos) && !(source.charAt(pos) === ">" && source.charAt(pos + 1) === ">") ) {
return null;
}
// Process attributes
pos = $tw.utils.parseMacroParametersAsAttributes(node,source,pos);
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double closing angle bracket
token = $tw.utils.parseTokenString(source,pos,">>");
if(!token) {
return null;
}
node.end = token.end;
return node;
};
/*
Look for an MVV (multi-valued variable) reference as a transclusion, i.e. ((varname)) or ((varname params))
Returns null if not found, or a parse tree node of type "transclude" with isMVV: true
*/
exports.parseMVVReferenceAsTransclusion = function(source,pos) {
var node = {
type: "transclude",
isMVV: true,
start: pos,
attributes: {},
orderedAttributes: []
};
// Define our regexps
var reVarName = /([^\s>"'=:)]+)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double opening parenthesis
var token = $tw.utils.parseTokenString(source,pos,"((");
if(!token) {
return null;
}
pos = token.end;
// Get the variable name
token = $tw.utils.parseTokenRegExp(source,pos,reVarName);
if(!token) {
return null;
}
$tw.utils.addAttributeToParseTreeNode(node,"$variable",token.match[1]);
pos = token.end;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double closing parenthesis
token = $tw.utils.parseTokenString(source,pos,"))");
if(!token) {
return null;
}
node.end = token.end;
return node;
};
/*
Parse macro parameters as attributes. Returns the position after the last attribute
*/
exports.parseMacroParametersAsAttributes = function(node,source,pos) {
var position = 0,
attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos);
while(attribute) {
if(!attribute.name) {
attribute.name = (position++) + "";
attribute.isPositional = true;
}
node.orderedAttributes.push(attribute);
node.attributes[attribute.name] = attribute;
pos = attribute.end;
// Get the next attribute
attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos);
}
node.end = pos;
return pos;
};
/*
Parse a macro parameter as an attribute. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}, with the name being optional
*/
exports.parseMacroParameterAsAttribute = function(source,pos) {
var node = {
start: pos
};
// Define our regexps
var reAttributeName = /([^\/\s>"'`=:]+)/g,
reUnquotedAttribute = /((?:(?:>(?!>))|[^\s>"'])+)/g,
reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g,
reIndirectValue = /\{\{([^\}]+)\}\}/g,
reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Get the attribute name and the separator token
var nameToken = $tw.utils.parseTokenRegExp(source,pos,reAttributeName),
namePos = nameToken && $tw.utils.skipWhiteSpace(source,nameToken.end),
separatorToken = nameToken && $tw.utils.parseTokenRegExp(source,namePos,/=|:/g),
isNewStyleSeparator = false; // If there is no separator then we don't allow new style values
// If we have a name and a separator then we have a named attribute
if(nameToken && separatorToken) {
node.name = nameToken.match[1];
// key value separator is `=` or `:`
node.assignmentOperator = separatorToken.match[0];
pos = separatorToken.end;
isNewStyleSeparator = (node.assignmentOperator === "=");
}
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a string literal
var stringLiteral = $tw.utils.parseStringLiteral(source,pos);
if(stringLiteral) {
pos = stringLiteral.end;
node.type = "string";
node.value = stringLiteral.value;
// Mark the value as having been quoted in the source
node.quoted = true;
} else {
// Look for a filtered value
var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue);
if(filteredValue && isNewStyleSeparator) {
pos = filteredValue.end;
node.type = "filtered";
node.filter = filteredValue.match[1];
} else {
// Look for an indirect value
var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
if(indirectValue && isNewStyleSeparator) {
pos = indirectValue.end;
node.type = "indirect";
node.textReference = indirectValue.match[1];
} else {
// Look for a macro invocation value
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(macroInvocation && isNewStyleSeparator) {
pos = macroInvocation.end;
node.type = "macro";
node.value = macroInvocation;
} else {
// Look for an MVV reference value
var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
if(mvvReference && isNewStyleSeparator) {
pos = mvvReference.end;
node.type = "macro";
node.value = mvvReference;
node.isMVV = true;
} else {
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
if(substitutedValue && isNewStyleSeparator) {
pos = substitutedValue.end;
node.type = "substituted";
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
} else {
// Look for a unquoted value
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
if(unquotedValue) {
pos = unquotedValue.end;
node.type = "string";
node.value = unquotedValue.match[1];
} else {
}
}
}
} }
$tw.utils.addAttributeToParseTreeNode(transclusion,{name: name,type: "string", value: param.value, start: param.start, end: param.end});
} else {
$tw.utils.addAttributeToParseTreeNode(transclusion,{name: (positionalName++) + "",type: "string", value: param.value, start: param.start, end: param.end});
} }
} });
return transclusion;
} }
// Bail if we don't have a value
if(!node.type) {
return null;
}
// Update the end position
node.end = pos;
return node; return node;
}; };
@@ -477,7 +296,7 @@ exports.parseFilterVariable = function(source) {
}; };
/* /*
Look for an HTML attribute definition. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,} Look for an HTML attribute definition. Returns null if not found, otherwise returns {type: "attribute", name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}
*/ */
exports.parseAttribute = function(source,pos) { exports.parseAttribute = function(source,pos) {
var node = { var node = {
@@ -527,20 +346,19 @@ exports.parseAttribute = function(source,pos) {
node.type = "indirect"; node.type = "indirect";
node.textReference = indirectValue.match[1]; node.textReference = indirectValue.match[1];
} else { } else {
// Look for a macro invocation value // Look for a unquoted value
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos); var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
if(macroInvocation) { if(unquotedValue) {
pos = macroInvocation.end; pos = unquotedValue.end;
node.type = "macro"; node.type = "string";
node.value = macroInvocation; node.value = unquotedValue.match[1];
} else { } else {
// Look for an MVV reference value // Look for a macro invocation value
var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos); var macroInvocation = $tw.utils.parseMacroInvocation(source,pos);
if(mvvReference) { if(macroInvocation) {
pos = mvvReference.end; pos = macroInvocation.end;
node.type = "macro"; node.type = "macro";
node.value = mvvReference; node.value = macroInvocation;
node.isMVV = true;
} else { } else {
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue); var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
if(substitutedValue) { if(substitutedValue) {
@@ -548,16 +366,8 @@ exports.parseAttribute = function(source,pos) {
node.type = "substituted"; node.type = "substituted";
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2]; node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
} else { } else {
// Look for a unquoted value node.type = "string";
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute); node.value = "true";
if(unquotedValue) {
pos = unquotedValue.end;
node.type = "string";
node.value = unquotedValue.match[1];
} else {
node.type = "string";
node.value = "true";
}
} }
} }
} }
@@ -565,7 +375,6 @@ exports.parseAttribute = function(source,pos) {
} }
} }
} else { } else {
// If there is no equals sign or colon, then this is an attribute with no value, defaulting to "true"
node.type = "string"; node.type = "string";
node.value = "true"; node.value = "true";
} }

View File

@@ -32,7 +32,7 @@ Instantiate parse rule
exports.init = function(parser) { exports.init = function(parser) {
this.parser = parser; this.parser = parser;
// Regexp to match // Regexp to match
this.matchRegExp = /\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*(?:\)\)[^)]*)*))?\)(\s*\r?\n)?/mg; this.matchRegExp = /\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*))?\)(\s*\r?\n)?/mg;
}; };
/* /*

View File

@@ -37,7 +37,7 @@ exports.parse = function() {
var paramString = this.match[2], var paramString = this.match[2],
params = []; params = [];
if(paramString !== "") { if(paramString !== "") {
var reParam = /\s*([A-Za-z0-9\-_]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[((?:[^\]]|\](?!\]))*)\]\]|([^"'\s]+)))?/mg, var reParam = /\s*([A-Za-z0-9\-_]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|([^"'\s]+)))?/mg,
paramMatch = reParam.exec(paramString); paramMatch = reParam.exec(paramString);
while(paramMatch) { while(paramMatch) {
// Save the parameter details // Save the parameter details

View File

@@ -1,95 +0,0 @@
/*\
title: $:/core/modules/parsers/wikiparser/rules/mvvdisplayinline.js
type: application/javascript
module-type: wikirule
Wiki rule for inline display of multi-valued variables and filter results.
Variable display: ((varname)) or ((varname||separator))
Filter display: (((filter))) or (((filter||separator)))
The default separator is ", " (comma space).
\*/
"use strict";
exports.name = "mvvdisplayinline";
exports.types = {inline: true};
exports.init = function(parser) {
this.parser = parser;
};
exports.findNextMatch = function(startPos) {
var source = this.parser.source;
var nextStart = startPos;
while((nextStart = source.indexOf("((",nextStart)) >= 0) {
if(source.charAt(nextStart + 2) === "(") {
// Filter mode: (((filter))) or (((filter||sep)))
var match = /^\(\(\(([\s\S]+?)\)\)\)/.exec(source.substring(nextStart));
if(match) {
// Check for separator: split on last || before )))
var inner = match[1];
var sepIndex = inner.lastIndexOf("||");
if(sepIndex >= 0) {
this.nextMatch = {
type: "filter",
filter: inner.substring(0,sepIndex),
separator: inner.substring(sepIndex + 2),
start: nextStart,
end: nextStart + match[0].length
};
} else {
this.nextMatch = {
type: "filter",
filter: inner,
separator: ", ",
start: nextStart,
end: nextStart + match[0].length
};
}
return nextStart;
}
} else {
// Variable mode: ((varname)) or ((varname||sep))
var match = /^\(\(([^()|]+?)(?:\|\|([^)]*))?\)\)/.exec(source.substring(nextStart));
if(match) {
this.nextMatch = {
type: "variable",
varName: match[1],
separator: match[2] !== undefined ? match[2] : ", ",
start: nextStart,
end: nextStart + match[0].length
};
return nextStart;
}
}
nextStart += 2;
}
return undefined;
};
/*
Parse the most recent match
*/
exports.parse = function() {
var match = this.nextMatch;
this.nextMatch = null;
this.parser.pos = match.end;
var filter, sep = match.separator;
if(match.type === "variable") {
filter = "[(" + match.varName + ")join[" + sep + "]]";
} else {
filter = match.filter + " +[join[" + sep + "]]";
}
return [{
type: "text",
attributes: {
text: {name: "text", type: "filtered", filter: filter}
},
orderedAttributes: [
{name: "text", type: "filtered", filter: filter}
]
}];
};

View File

@@ -23,6 +23,27 @@ exports.init = function(parser) {
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg; 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() { exports.parse = function() {
// Move past the match // Move past the match
this.parser.pos = this.matchRegExp.lastIndex; this.parser.pos = this.matchRegExp.lastIndex;

View File

@@ -23,6 +23,27 @@ exports.init = function(parser) {
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}/mg; 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() { exports.parse = function() {
// Move past the match // Move past the match
this.parser.pos = this.matchRegExp.lastIndex; this.parser.pos = this.matchRegExp.lastIndex;

View File

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

View File

@@ -13,11 +13,6 @@ Load core modules
exports.name = "load-modules"; exports.name = "load-modules";
exports.synchronous = true; 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() { exports.startup = function() {
// Load modules // Load modules
$tw.modules.applyMethods("utils",$tw.utils); $tw.modules.applyMethods("utils",$tw.utils);
@@ -36,27 +31,6 @@ exports.startup = function() {
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules); $tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
$tw.macros = $tw.modules.getModulesByTypeAsHashmap("macro"); $tw.macros = $tw.modules.getModulesByTypeAsHashmap("macro");
$tw.wiki.initParsers(); $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) { if($tw.node) {
$tw.Commander.initCommands(); $tw.Commander.initCommands();
} }

View File

@@ -18,6 +18,7 @@ exports.synchronous = true;
// Default story and history lists // Default story and history lists
var PAGE_TITLE_TITLE = "$:/core/wiki/title"; var PAGE_TITLE_TITLE = "$:/core/wiki/title";
var PAGE_STYLESHEET_TITLE = "$:/core/ui/PageStylesheet"; var PAGE_STYLESHEET_TITLE = "$:/core/ui/PageStylesheet";
var ROOT_STYLESHEET_TITLE = "$:/core/ui/RootStylesheet";
var PAGE_TEMPLATE_TITLE = "$:/core/ui/RootTemplate"; var PAGE_TEMPLATE_TITLE = "$:/core/ui/RootTemplate";
// Time (in ms) that we defer refreshing changes to draft tiddlers // Time (in ms) that we defer refreshing changes to draft tiddlers
@@ -44,22 +45,13 @@ exports.startup = function() {
publishTitle(); publishTitle();
} }
}); });
// Set up the styles
$tw.styleWidgetNode = $tw.wiki.makeTranscludeWidget(PAGE_STYLESHEET_TITLE,{document: $tw.fakeDocument}); var styleParser = $tw.wiki.parseTiddler(ROOT_STYLESHEET_TITLE,{parseAsInline: true}),
$tw.styleContainer = $tw.fakeDocument.createElement("style"); styleWidgetNode = $tw.wiki.makeWidget(styleParser,{document: document});
$tw.styleWidgetNode.render($tw.styleContainer,null); styleWidgetNode.render(document.head,null);
$tw.styleWidgetNode.assignedStyles = $tw.styleContainer.textContent;
$tw.styleElement = document.createElement("style");
$tw.styleElement.innerHTML = $tw.styleWidgetNode.assignedStyles;
document.head.insertBefore($tw.styleElement,document.head.firstChild);
$tw.wiki.addEventListener("change",$tw.perf.report("styleRefresh",function(changes) { $tw.wiki.addEventListener("change",$tw.perf.report("styleRefresh",function(changes) {
if($tw.styleWidgetNode.refresh(changes,$tw.styleContainer,null)) { styleWidgetNode.refresh(changes,document.head,null);
var newStyles = $tw.styleContainer.textContent;
if(newStyles !== $tw.styleWidgetNode.assignedStyles) {
$tw.styleWidgetNode.assignedStyles = newStyles;
$tw.styleElement.innerHTML = $tw.styleWidgetNode.assignedStyles;
}
}
})); }));
// Display the $:/core/ui/PageTemplate tiddler to kick off the display // Display the $:/core/ui/PageTemplate tiddler to kick off the display
$tw.perf.report("mainRender",function() { $tw.perf.report("mainRender",function() {
@@ -68,7 +60,7 @@ exports.startup = function() {
$tw.utils.addClass($tw.pageContainer,"tc-page-container-wrapper"); $tw.utils.addClass($tw.pageContainer,"tc-page-container-wrapper");
document.body.insertBefore($tw.pageContainer,document.body.firstChild); document.body.insertBefore($tw.pageContainer,document.body.firstChild);
$tw.pageWidgetNode.render($tw.pageContainer,null); $tw.pageWidgetNode.render($tw.pageContainer,null);
$tw.hooks.invokeHook("th-page-refreshed"); $tw.hooks.invokeHook("th-page-refreshed");
})(); })();
// Remove any splash screen elements // Remove any splash screen elements
var removeList = document.querySelectorAll(".tc-remove-when-wiki-loaded"); var removeList = document.querySelectorAll(".tc-remove-when-wiki-loaded");

View File

@@ -14,6 +14,11 @@ exports.name = "startup";
exports.after = ["load-modules"]; exports.after = ["load-modules"];
exports.synchronous = true; 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() { exports.startup = function() {
// Minimal browser detection // Minimal browser detection
if($tw.browser) { if($tw.browser) {
@@ -49,6 +54,16 @@ exports.startup = function() {
} }
// Initialise version // Initialise version
$tw.version = $tw.utils.extractVersionInfo(); $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 // Kick off the language manager and switcher
$tw.language = new $tw.Language(); $tw.language = new $tw.Language();
$tw.languageSwitcher = new $tw.PluginSwitcher({ $tw.languageSwitcher = new $tw.PluginSwitcher({

View File

@@ -63,24 +63,16 @@ exports.startup = function() {
$tw.eventBus.emit("window:closed",{windowID}); $tw.eventBus.emit("window:closed",{windowID});
},false); },false);
// Set up the styles // Set up the styles
var styleWidgetNode = $tw.wiki.makeTranscludeWidget("$:/core/ui/PageStylesheet",{ var styleParser = $tw.wiki.parseTiddler("$:/core/ui/RootStylesheet",{parseAsInline: true}),
document: $tw.fakeDocument, styleWidgetNode = $tw.wiki.makeWidget(styleParser,{document: srcDocument});
variables: variables, styleWidgetNode.render(srcDocument.head,null);
importPageMacros: true}),
styleContainer = $tw.fakeDocument.createElement("style");
styleWidgetNode.render(styleContainer,null);
var styleElement = srcDocument.createElement("style");
styleElement.innerHTML = styleContainer.textContent;
srcDocument.head.insertBefore(styleElement,srcDocument.head.firstChild);
// Render the text of the tiddler // Render the text of the tiddler
var parser = $tw.wiki.parseTiddler(template), var parser = $tw.wiki.parseTiddler(template),
widgetNode = $tw.wiki.makeWidget(parser,{document: srcDocument, parentWidget: $tw.rootWidget, variables: variables}); widgetNode = $tw.wiki.makeWidget(parser,{document: srcDocument, parentWidget: $tw.rootWidget, variables: variables});
widgetNode.render(srcDocument.body,srcDocument.body.firstChild); widgetNode.render(srcDocument.body,srcDocument.body.firstChild);
// Function to handle refreshes // Function to handle refreshes
refreshHandler = function(changes) { refreshHandler = function(changes) {
if(styleWidgetNode.refresh(changes,styleContainer,null)) { styleWidgetNode.refresh(changes);
styleElement.innerHTML = styleContainer.textContent;
}
widgetNode.refresh(changes); widgetNode.refresh(changes);
}; };
$tw.wiki.addEventListener("change",refreshHandler); $tw.wiki.addEventListener("change",refreshHandler);

View File

@@ -1,31 +0,0 @@
/*\
title: $:/core/modules/utils/base64.js
type: application/javascript
module-type: utils-browser
Base64 utility functions
\*/
"use strict";
/*
Base64 utility functions that work in either browser or Node.js
*/
exports.btoa = binstr => window.btoa(binstr);
exports.atob = b64 => window.atob(b64);
function base64ToBytes(base64) {
const binString = exports.atob(base64);
return Uint8Array.from(binString, m => m.codePointAt(0));
};
function bytesToBase64(bytes) {
const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join("");
return exports.btoa(binString);
};
exports.base64EncodeUtf8 = str => bytesToBase64(new TextEncoder().encode(str));
exports.base64DecodeUtf8 = str => new TextDecoder().decode(base64ToBytes(str));

View File

@@ -44,15 +44,15 @@ exports.domMatchesSelector = (node,selector) => node.matches(selector);
exports.hasClass = (el,className) => el.classList && el.classList.contains(className); exports.hasClass = (el,className) => el.classList && el.classList.contains(className);
exports.addClass = function(el,className) { exports.addClass = function(el,className) {
el.classList && className && el.classList.add(className); el.classList && el.classList.add(className);
}; };
exports.removeClass = function(el,className) { exports.removeClass = function(el,className) {
el.classList && className && el.classList.remove(className); el.classList && el.classList.remove(className);
}; };
exports.toggleClass = function(el,className,status) { exports.toggleClass = function(el,className,status) {
el.classList && className && el.classList.toggle(className, status); el.classList && el.classList.toggle(className, status);
}; };
exports.getLocationPath = () => window.location.origin + window.location.pathname; exports.getLocationPath = () => window.location.origin + window.location.pathname;

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,7 +1,7 @@
/*\ /*\
title: $:/core/modules/utils/escapecss.js title: $:/core/modules/utils/escapecss.js
type: application/javascript type: application/javascript
module-type: utils-browser module-type: utils
Provides CSS.escape() functionality. Provides CSS.escape() functionality.
@@ -9,6 +9,92 @@ Provides CSS.escape() functionality.
"use strict"; "use strict";
// TODO -- resolve this construction
exports.escapeCSS = (function() { exports.escapeCSS = (function() {
return window.CSS.escape; // use browser's native CSS.escape() function if available
if ($tw.browser && window.CSS && window.CSS.escape) {
return window.CSS.escape;
}
// otherwise, a utility method is provided
// see also https://drafts.csswg.org/cssom/#serialize-an-identifier
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
return function(value) {
if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.');
}
var string = String(value);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: theres no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit == 0x0000) {
result += '\uFFFD';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index == 0 &&
length == 1 &&
codeUnit == 0x002D
) {
result += '\\' + string.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
return result;
};
})(); })();

View File

@@ -37,7 +37,6 @@ Object.defineProperty(TW_Node.prototype, 'TEXT_NODE', {
var TW_TextNode = function(text) { var TW_TextNode = function(text) {
bumpSequenceNumber(this); bumpSequenceNumber(this);
this.textContent = text + ""; this.textContent = text + "";
this.children = [];
}; };
Object.setPrototypeOf(TW_TextNode.prototype,TW_Node.prototype); Object.setPrototypeOf(TW_TextNode.prototype,TW_Node.prototype);

View File

@@ -8,7 +8,10 @@ Messaging utilities for use with window.postMessage() etc.
This module intentionally has no dependencies so that it can be included in non-TiddlyWiki projects 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"; "use strict";
var RESPONSE_TIMEOUT = 2 * 1000; var RESPONSE_TIMEOUT = 2 * 1000;
@@ -119,3 +122,5 @@ BrowserMessagingPublisher.prototype.close = function() {
}; };
exports.BrowserMessagingPublisher = BrowserMessagingPublisher; exports.BrowserMessagingPublisher = BrowserMessagingPublisher;
})();

View File

@@ -55,7 +55,7 @@ Return the dflt (default) parameter if str is not a base-10 number.
exports.getInt = function(str,deflt) { exports.getInt = function(str,deflt) {
var i = parseInt(str,10); var i = parseInt(str,10);
return isNaN(i) ? deflt : i; return isNaN(i) ? deflt : i;
}; }
/* /*
Repeatedly replaces a substring within a string. Like String.prototype.replace, but without any of the default special handling of $ sequences in the replace string Repeatedly replaces a substring within a string. Like String.prototype.replace, but without any of the default special handling of $ sequences in the replace string
@@ -69,12 +69,12 @@ exports.replaceString = function(text,search,replace) {
exports.trimPrefix = function(str,unwanted) { exports.trimPrefix = function(str,unwanted) {
if(typeof str === "string" && typeof unwanted === "string") { if(typeof str === "string" && typeof unwanted === "string") {
if(unwanted === "") { if(unwanted === "") {
return str.replace(/^\s\s*/, ""); return str.replace(/^\s\s*/, '');
} else { } else {
// Safely regexp-escape the unwanted text // Safely regexp-escape the unwanted text
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&"); unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
var regex = new RegExp("^(" + unwanted + ")+"); var regex = new RegExp('^(' + unwanted + ')+');
return str.replace(regex, ""); return str.replace(regex, '');
} }
} else { } else {
return str; return str;
@@ -84,12 +84,12 @@ exports.trimPrefix = function(str,unwanted) {
exports.trimSuffix = function(str,unwanted) { exports.trimSuffix = function(str,unwanted) {
if(typeof str === "string" && typeof unwanted === "string") { if(typeof str === "string" && typeof unwanted === "string") {
if(unwanted === "") { if(unwanted === "") {
return str.replace(/\s\s*$/, ""); return str.replace(/\s\s*$/, '');
} else { } else {
// Safely regexp-escape the unwanted text // Safely regexp-escape the unwanted text
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&"); unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
var regex = new RegExp("(" + unwanted + ")+$"); var regex = new RegExp('(' + unwanted + ')+$');
return str.replace(regex, ""); return str.replace(regex, '');
} }
} else { } else {
return str; return str;
@@ -101,14 +101,14 @@ Convert a string to sentence case (ie capitalise first letter)
*/ */
exports.toSentenceCase = function(str) { exports.toSentenceCase = function(str) {
return (str || "").replace(/^\S/, function(c) {return c.toUpperCase();}); return (str || "").replace(/^\S/, function(c) {return c.toUpperCase();});
}; }
/* /*
Convert a string to title case (ie capitalise each initial letter) Convert a string to title case (ie capitalise each initial letter)
*/ */
exports.toTitleCase = function(str) { exports.toTitleCase = function(str) {
return (str || "").replace(/(^|\s)\S/g, function(c) {return c.toUpperCase();}); return (str || "").replace(/(^|\s)\S/g, function(c) {return c.toUpperCase();});
}; }
/* /*
Find the line break preceding a given position in a string Find the line break preceding a given position in a string
@@ -242,53 +242,6 @@ exports.slowInSlowOut = function(t) {
return (1 - ((Math.cos(t * Math.PI) + 1) / 2)); 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) { exports.formatTitleString = function(template,options) {
var base = options.base || "", var base = options.base || "",
separator = options.separator || "", separator = options.separator || "",
@@ -405,8 +358,8 @@ exports.formatDateString = function(date,template) {
}], }],
[/^TZD/, function() { [/^TZD/, function() {
var tz = date.getTimezoneOffset(), var tz = date.getTimezoneOffset(),
atz = Math.abs(tz); atz = Math.abs(tz);
return (tz < 0 ? "+" : "-") + $tw.utils.pad(Math.floor(atz / 60)) + ":" + $tw.utils.pad(atz % 60); return (tz < 0 ? '+' : '-') + $tw.utils.pad(Math.floor(atz / 60)) + ':' + $tw.utils.pad(atz % 60);
}], }],
[/^wYY/, function() { [/^wYY/, function() {
return $tw.utils.pad($tw.utils.getYearForWeekNo(date) - 2000); return $tw.utils.pad($tw.utils.getYearForWeekNo(date) - 2000);
@@ -615,9 +568,9 @@ exports.unescapeLineBreaks = function(s) {
exports.escape = function(ch) { exports.escape = function(ch) {
var charCode = ch.charCodeAt(0); var charCode = ch.charCodeAt(0);
if(charCode <= 0xFF) { if(charCode <= 0xFF) {
return "\\x" + $tw.utils.pad(charCode.toString(16).toUpperCase()); return '\\x' + $tw.utils.pad(charCode.toString(16).toUpperCase());
} else { } else {
return "\\u" + $tw.utils.pad(charCode.toString(16).toUpperCase(),4); return '\\u' + $tw.utils.pad(charCode.toString(16).toUpperCase(),4);
} }
}; };
@@ -634,11 +587,11 @@ exports.stringify = function(s, rawUnicode) {
*/ */
var regex = rawUnicode ? /[\x00-\x1f]/g : /[\x00-\x1f\x80-\uFFFF]/g; var regex = rawUnicode ? /[\x00-\x1f]/g : /[\x00-\x1f\x80-\uFFFF]/g;
return (s || "") return (s || "")
.replace(/\\/g, "\\\\") // backslash .replace(/\\/g, '\\\\') // backslash
.replace(/"/g, '\\"') // double quote character .replace(/"/g, '\\"') // double quote character
.replace(/'/g, "\\'") // single quote character .replace(/'/g, "\\'") // single quote character
.replace(/\r/g, "\\r") // carriage return .replace(/\r/g, '\\r') // carriage return
.replace(/\n/g, "\\n") // line feed .replace(/\n/g, '\\n') // line feed
.replace(regex, exports.escape); // non-ASCII characters .replace(regex, exports.escape); // non-ASCII characters
}; };
@@ -648,15 +601,15 @@ exports.jsonStringify = function(s, rawUnicode) {
// See http://www.json.org/ // See http://www.json.org/
var regex = rawUnicode ? /[\x00-\x1f]/g : /[\x00-\x1f\x80-\uFFFF]/g; var regex = rawUnicode ? /[\x00-\x1f]/g : /[\x00-\x1f\x80-\uFFFF]/g;
return (s || "") return (s || "")
.replace(/\\/g, "\\\\") // backslash .replace(/\\/g, '\\\\') // backslash
.replace(/"/g, '\\"') // double quote character .replace(/"/g, '\\"') // double quote character
.replace(/\r/g, "\\r") // carriage return .replace(/\r/g, '\\r') // carriage return
.replace(/\n/g, "\\n") // line feed .replace(/\n/g, '\\n') // line feed
.replace(/\x08/g, "\\b") // backspace .replace(/\x08/g, '\\b') // backspace
.replace(/\x0c/g, "\\f") // formfeed .replace(/\x0c/g, '\\f') // formfeed
.replace(/\t/g, "\\t") // tab .replace(/\t/g, '\\t') // tab
.replace(regex,function(s) { .replace(regex,function(s) {
return "\\u" + $tw.utils.pad(s.charCodeAt(0).toString(16).toUpperCase(),4); return '\\u' + $tw.utils.pad(s.charCodeAt(0).toString(16).toUpperCase(),4);
}); // non-ASCII characters }); // non-ASCII characters
}; };
@@ -664,7 +617,7 @@ exports.jsonStringify = function(s, rawUnicode) {
Escape the RegExp special characters with a preceding backslash Escape the RegExp special characters with a preceding backslash
*/ */
exports.escapeRegExp = function(s) { exports.escapeRegExp = function(s) {
return s.replace(/[\-\/\\\^\$\*\+\?\.\(\)\|\[\]\{\}]/g, "\\$&"); return s.replace(/[\-\/\\\^\$\*\+\?\.\(\)\|\[\]\{\}]/g, '\\$&');
}; };
/* /*
@@ -747,7 +700,7 @@ exports.parseTextReference = function(textRef) {
} }
} else { } else {
// If we couldn't parse it // If we couldn't parse it
result.title = textRef; result.title = textRef
} }
return result; return result;
}; };
@@ -806,17 +759,60 @@ Cryptographic hash function as used by sha256 filter operator
options.length .. number of characters returned defaults to 64 options.length .. number of characters returned defaults to 64
*/ */
exports.sha256 = function(str, options) { exports.sha256 = function(str, options) {
options = options || {}; options = options || {}
return $tw.sjcl.codec.hex.fromBits($tw.sjcl.hash.sha256.hash(str)).substr(0,options.length || 64); return $tw.sjcl.codec.hex.fromBits($tw.sjcl.hash.sha256.hash(str)).substr(0,options.length || 64);
}
/*
Base64 utility functions that work in either browser or Node.js
*/
if(typeof window !== 'undefined') {
exports.btoa = function(binstr) { return window.btoa(binstr); }
exports.atob = function(b64) { return window.atob(b64); }
} else {
exports.btoa = function(binstr) {
return Buffer.from(binstr, 'binary').toString('base64');
}
exports.atob = function(b64) {
return Buffer.from(b64, 'base64').toString('binary');
}
}
exports.base64ToBytes = function(base64) {
const binString = exports.atob(base64);
return Uint8Array.from(binString, (m) => m.codePointAt(0));
};
exports.bytesToBase64 = function(bytes) {
const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("");
return exports.btoa(binString);
};
exports.base64EncodeUtf8 = function(str) {
if ($tw.browser) {
return exports.bytesToBase64(new TextEncoder().encode(str));
} else {
const buff = Buffer.from(str, "utf-8");
return buff.toString("base64");
}
};
exports.base64DecodeUtf8 = function(str) {
if ($tw.browser) {
return new TextDecoder().decode(exports.base64ToBytes(str));
} else {
const buff = Buffer.from(str, "base64");
return buff.toString("utf-8");
}
}; };
/* /*
Decode a base64 string Decode a base64 string
*/ */
exports.base64Decode = function(string64,binary,urlsafe) { exports.base64Decode = function(string64,binary,urlsafe) {
const encoded = urlsafe ? string64.replace(/_/g,"/").replace(/-/g,"+") : string64; const encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64;
if(binary) return $tw.utils.atob(encoded); if(binary) return exports.atob(encoded)
else return $tw.utils.base64DecodeUtf8(encoded); else return exports.base64DecodeUtf8(encoded);
}; };
/* /*
@@ -824,10 +820,10 @@ Encode a string to base64
*/ */
exports.base64Encode = function(string64,binary,urlsafe) { exports.base64Encode = function(string64,binary,urlsafe) {
let encoded; let encoded;
if(binary) encoded = $tw.utils.btoa(string64); if(binary) encoded = exports.btoa(string64);
else encoded = $tw.utils.base64EncodeUtf8(string64); else encoded = exports.base64EncodeUtf8(string64);
if(urlsafe) { if(urlsafe) {
encoded = encoded.replace(/\+/g,"-").replace(/\//g,"_"); encoded = encoded.replace(/\+/g,'-').replace(/\//g,'_');
} }
return encoded; return encoded;
}; };
@@ -961,56 +957,3 @@ exports.makeCompareFunction = function(type,options) {
}; };
return (types[type] || types[options.defaultType] || types.number); return (types[type] || types[options.defaultType] || types.number);
}; };
/*
Split text into parts (lines or words) for diff operations
Adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
*/
exports.diffPartsToChars = function(text1,text2,mode) {
const lineArray = [""],
lineHash = Object.create(null);
function diff_linesToPartsMunge_(text,mode) {
let chars = "",
lineStart = 0,
lineEnd = -1,
lineArrayLength = lineArray.length,
regexpResult;
const searchRegexp = /\W+/g;
while(lineEnd < text.length - 1) {
if(mode === "words") {
regexpResult = searchRegexp.exec(text);
lineEnd = searchRegexp.lastIndex;
if(regexpResult === null) {
lineEnd = text.length;
}
lineEnd = --lineEnd;
} else {
lineEnd = text.indexOf("\n", lineStart);
if(lineEnd === -1) {
lineEnd = text.length - 1;
}
}
let line = text.substring(lineStart, lineEnd + 1);
if(line in lineHash) {
chars += String.fromCharCode(lineHash[line]);
} else {
if(lineArrayLength === maxLines) {
line = text.substring(lineStart);
lineEnd = text.length;
}
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
lineStart = lineEnd + 1;
}
return chars;
}
let maxLines = 40000;
const chars1 = diff_linesToPartsMunge_(text1,mode);
maxLines = 65535;
const chars2 = diff_linesToPartsMunge_(text2,mode);
return {chars1, chars2, lineArray};
};

View File

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

View File

@@ -9,8 +9,6 @@ Button widget
"use strict"; "use strict";
const ALLOWED_SELECTED_ARIA_ATTR = ["aria-checked", "aria-selected", "aria-pressed"];
var Widget = require("$:/core/modules/widgets/widget.js").widget; var Widget = require("$:/core/modules/widgets/widget.js").widget;
var Popup = require("$:/core/modules/utils/dom/popup.js"); var Popup = require("$:/core/modules/utils/dom/popup.js");
@@ -46,14 +44,9 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
var classes = this["class"].split(" ") || [], var classes = this["class"].split(" ") || [],
isPoppedUp = (this.popup || this.popupTitle) && this.isPoppedUp(); isPoppedUp = (this.popup || this.popupTitle) && this.isPoppedUp();
if(this.selectedClass) { if(this.selectedClass) {
if((this.set || this.setTitle) && this.setTo) { if((this.set || this.setTitle) && this.setTo && this.isSelected()) {
const selectedAria = ALLOWED_SELECTED_ARIA_ATTR.includes(this.selectedAria) ? this.selectedAria : "aria-checked"; $tw.utils.pushTop(classes, this.selectedClass.split(" "));
if(this.isSelected()) { domNode.setAttribute("aria-checked", "true");
$tw.utils.pushTop(classes, this.selectedClass.split(" "));
domNode.setAttribute(selectedAria, "true");
} else {
domNode.setAttribute(selectedAria, "false");
}
} }
if(isPoppedUp) { if(isPoppedUp) {
$tw.utils.pushTop(classes,this.selectedClass.split(" ")); $tw.utils.pushTop(classes,this.selectedClass.split(" "));
@@ -135,8 +128,8 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
} }
// Insert element // Insert element
parent.insertBefore(domNode,nextSibling); parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null); this.renderChildren(domNode,null);
this.domNodes.push(domNode);
}; };
/* /*
@@ -228,7 +221,6 @@ ButtonWidget.prototype.execute = function() {
this.style = this.getAttribute("style"); this.style = this.getAttribute("style");
this["class"] = this.getAttribute("class",""); this["class"] = this.getAttribute("class","");
this.selectedClass = this.getAttribute("selectedClass"); this.selectedClass = this.getAttribute("selectedClass");
this.selectedAria = this.getAttribute("selectedAria");
this.defaultSetValue = this.getAttribute("default",""); this.defaultSetValue = this.getAttribute("default","");
this.buttonTag = this.getAttribute("tag"); this.buttonTag = this.getAttribute("tag");
this.dragTiddler = this.getAttribute("dragTiddler"); this.dragTiddler = this.getAttribute("dragTiddler");

View File

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

View File

@@ -9,8 +9,8 @@ Widget to display a diff between two texts
"use strict"; "use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget; var Widget = require("$:/core/modules/widgets/widget.js").widget,
const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js"); dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
var DiffTextWidget = function(parseTreeNode,options) { var DiffTextWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options); this.initialise(parseTreeNode,options);
@@ -35,32 +35,25 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
this.computeAttributes(); this.computeAttributes();
this.execute(); this.execute();
// Create the diff object // Create the diff object
const editCost = $tw.utils.parseNumber(this.getAttribute("editcost","4")); var dmpObject = new dmp.diff_match_patch();
const mode = this.getAttribute("mode") || "chars"; dmpObject.Diff_EditCost = $tw.utils.parseNumber(this.getAttribute("editcost","4"));
let diffs; var diffs = dmpObject.diff_main(this.getAttribute("source",""),this.getAttribute("dest",""));
if(mode === "lines" || mode === "words") {
diffs = diffLineWordMode(this.getAttribute("source",""),this.getAttribute("dest",""),mode,editCost);
} else {
diffs = dmp.diffMain(this.getAttribute("source",""),this.getAttribute("dest",""),{diffEditCost: editCost});
}
// Apply required cleanup // Apply required cleanup
switch(this.getAttribute("cleanup","semantic")) { switch(this.getAttribute("cleanup","semantic")) {
case "none": case "none":
// No cleanup // No cleanup
break; break;
case "efficiency": case "efficiency":
dmp.diffCleanupEfficiency(diffs, {diffEditCost: editCost}); dmpObject.diff_cleanupEfficiency(diffs);
break; break;
default: // case "semantic" default: // case "semantic"
dmp.diffCleanupSemantic(diffs); dmpObject.diff_cleanupSemantic(diffs);
break; break;
} }
// Create the elements // Create the elements
var domContainer = this.document.createElement("div"), var domContainer = this.document.createElement("div"),
domDiff = this.createDiffDom(diffs); domDiff = this.createDiffDom(diffs);
parent.insertBefore(domContainer,nextSibling); parent.insertBefore(domContainer,nextSibling);
// Save our container
this.domNodes.push(domContainer);
// Set variables // Set variables
this.setVariable("diff-count",diffs.reduce(function(acc,diff) { this.setVariable("diff-count",diffs.reduce(function(acc,diff) {
if(diff[0] !== dmp.DIFF_EQUAL) { if(diff[0] !== dmp.DIFF_EQUAL) {
@@ -72,6 +65,8 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
this.renderChildren(domContainer,null); this.renderChildren(domContainer,null);
// Render the diff // Render the diff
domContainer.appendChild(domDiff); domContainer.appendChild(domDiff);
// Save our container
this.domNodes.push(domContainer);
}; };
/* /*
@@ -138,7 +133,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/ */
DiffTextWidget.prototype.refresh = function(changedTiddlers) { DiffTextWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
if(changedAttributes.source || changedAttributes.dest || changedAttributes.cleanup || changedAttributes.mode || changedAttributes.editcost) { if(changedAttributes.source || changedAttributes.dest || changedAttributes.cleanup || changedAttributes.editcost) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
} else { } else {
@@ -146,15 +141,4 @@ DiffTextWidget.prototype.refresh = function(changedTiddlers) {
} }
}; };
// This function is adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
function diffLineWordMode(text1,text2,mode,editCost) {
var a = $tw.utils.diffPartsToChars(text1,text2,mode);
var lineText1 = a.chars1;
var lineText2 = a.chars2;
var lineArray = a.lineArray;
var diffs = dmp.diffMain(lineText1,lineText2,{diffEditCost: editCost});
dmp.diffCharsToLines(diffs,lineArray);
return diffs;
}
exports["diff-text"] = DiffTextWidget; exports["diff-text"] = DiffTextWidget;

View File

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

View File

@@ -57,8 +57,8 @@ DroppableWidget.prototype.render = function(parent,nextSibling) {
} }
// Insert element // Insert element
parent.insertBefore(domNode,nextSibling); parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null); this.renderChildren(domNode,null);
this.domNodes.push(domNode);
// Stack of outstanding enter/leave events // Stack of outstanding enter/leave events
this.currentlyEntered = []; 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 // Allow hooks to manipulate the DOM node. Eg: Add debug info
$tw.hooks.invokeHook("th-dom-rendering-element", domNode, this); $tw.hooks.invokeHook("th-dom-rendering-element", domNode, this);
parent.insertBefore(domNode,nextSibling); parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null); this.renderChildren(domNode,null);
this.domNodes.push(domNode);
}; };
/* /*

View File

@@ -39,12 +39,75 @@ EventWidget.prototype.render = function(parent,nextSibling) {
this.domNode = domNode; this.domNode = domNode;
// Assign classes // Assign classes
this.assignDomNodeClasses(); this.assignDomNodeClasses();
// Add our event handlers // Add our event handler
this.toggleListeners(); $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);
});
// Insert element // Insert element
parent.insertBefore(domNode,nextSibling); parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null); this.renderChildren(domNode,null);
this.domNodes.push(domNode);
}; };
/* /*
@@ -59,232 +122,11 @@ EventWidget.prototype.execute = function() {
self.types.push(key.slice(1)); self.types.push(key.slice(1));
} }
}); });
this.pointerCaptureMode = this.getAttribute("pointerCapture","no");
this.elementTag = this.getAttribute("tag"); this.elementTag = this.getAttribute("tag");
// Make child widgets // Make child widgets
this.makeChildWidgets(); 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() { EventWidget.prototype.assignDomNodeClasses = function() {
var classes = this.getAttribute("class","").split(" "); var classes = this.getAttribute("class","").split(" ");
classes.push("tc-eventcatcher"); classes.push("tc-eventcatcher");
@@ -292,23 +134,18 @@ EventWidget.prototype.assignDomNodeClasses = function() {
}; };
/* /*
Refresh widget Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/ */
EventWidget.prototype.refresh = function(changedTiddlers) { EventWidget.prototype.refresh = function(changedTiddlers) {
const changedAttributes = this.computeAttributes(), var changedAttributes = this.computeAttributes(),
changedKeys = Object.keys(changedAttributes), changedAttributesCount = $tw.utils.count(changedAttributes);
canUpdateAttributes = changedKeys.every(key => key === "class" || key === "disabled"); if(changedAttributesCount === 1 && changedAttributes["class"]) {
if(canUpdateAttributes) { this.assignDomNodeClasses();
if(changedAttributes["class"]) { } else if(changedAttributesCount > 0) {
this.assignDomNodeClasses(); this.refreshSelf();
} return true;
if(changedAttributes["disabled"]) {
this.toggleListeners();
}
return this.refreshChildren(changedTiddlers);
} }
this.refreshSelf(); return this.refreshChildren(changedTiddlers);
return true;
}; };
exports.eventcatcher = EventWidget; exports.eventcatcher = EventWidget;

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ This widget allows defining multiple variables at once, while allowing
the later variables to depend upon the earlier ones. the later variables to depend upon the earlier ones.
``` ```
\define helloworld() Hello world!
<$let currentTiddler="target" value={{!!value}} currentTiddler="different"> <$let currentTiddler="target" value={{!!value}} currentTiddler="different">
{{!!value}} will be different from <<value>> {{!!value}} will be different from <<value>>
</$let> </$let>
@@ -55,7 +56,7 @@ LetWidget.prototype.computeAttributes = function() {
}); });
// Run through again, setting variables and looking for differences // Run through again, setting variables and looking for differences
$tw.utils.each(this.currentValueFor,function(value,name) { $tw.utils.each(this.currentValueFor,function(value,name) {
if(self.attributes[name] === undefined || !$tw.utils.isArrayEqual(self.attributes[name],value)) { if(!$tw.utils.isArrayEqual(self.attributes[name],value)) {
self.attributes[name] = value; self.attributes[name] = value;
self.setVariable(name,value); self.setVariable(name,value);
changedAttributes[name] = true; changedAttributes[name] = true;
@@ -67,7 +68,7 @@ LetWidget.prototype.computeAttributes = function() {
LetWidget.prototype.getVariableInfo = function(name,options) { LetWidget.prototype.getVariableInfo = function(name,options) {
// Special handling: If this variable exists in this very $let, we can // Special handling: If this variable exists in this very $let, we can
// use it, but only if it's been staged. // use it, but only if it's been staged.
if($tw.utils.hop(this.currentValueFor,name)) { if ($tw.utils.hop(this.currentValueFor,name)) {
var value = this.currentValueFor[name]; var value = this.currentValueFor[name];
return { return {
text: value[0] || "", text: value[0] || "",

View File

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

View File

@@ -61,9 +61,7 @@ ParametersWidget.prototype.execute = function() {
if(name.substr(0,2) === "$$") { if(name.substr(0,2) === "$$") {
name = name.substr(1); name = name.substr(1);
} }
var defaultValue = (self.multiValuedAttributes && self.multiValuedAttributes[attr.name]) var value = pointer.getTransclusionParameter(name,index,self.getAttribute(attr.name,""));
|| self.getAttribute(attr.name,"");
var value = pointer.getTransclusionParameter(name,index,defaultValue);
self.setVariable(name,value); self.setVariable(name,value);
}); });
// Assign any metaparameters // Assign any metaparameters
@@ -82,8 +80,7 @@ ParametersWidget.prototype.execute = function() {
if(name.substr(0,2) === "$$") { if(name.substr(0,2) === "$$") {
name = name.substr(1); name = name.substr(1);
} }
var value = (self.multiValuedAttributes && self.multiValuedAttributes[attr.name]) var value = self.getAttribute(attr.name,"");
|| self.getAttribute(attr.name,"");
self.setVariable(name,value); self.setVariable(name,value);
}); });
} }

View File

@@ -42,8 +42,8 @@ PasswordWidget.prototype.render = function(parent,nextSibling) {
]); ]);
// Insert the label into the DOM and render any children // Insert the label into the DOM and render any children
parent.insertBefore(domNode,nextSibling); parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null); this.renderChildren(domNode,null);
this.domNodes.push(domNode);
}; };
PasswordWidget.prototype.handleChangeEvent = function(event) { 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 // Insert the label into the DOM and render any children
parent.insertBefore(this.labelDomNode,nextSibling); parent.insertBefore(this.labelDomNode,nextSibling);
this.domNodes.push(this.labelDomNode);
this.renderChildren(this.spanDomNode,null); this.renderChildren(this.spanDomNode,null);
this.domNodes.push(this.labelDomNode);
}; };
RadioWidget.prototype.getValue = function() { RadioWidget.prototype.getValue = function() {

View File

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

View File

@@ -168,8 +168,8 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
this.outerDomNode.className = this["class"] || ""; this.outerDomNode.className = this["class"] || "";
// Insert element // Insert element
parent.insertBefore(this.outerDomNode,nextSibling); parent.insertBefore(this.outerDomNode,nextSibling);
this.domNodes.push(this.outerDomNode);
this.renderChildren(this.innerDomNode,null); this.renderChildren(this.innerDomNode,null);
this.domNodes.push(this.outerDomNode);
// If the scroll position is bound to a tiddler // If the scroll position is bound to a tiddler
if(this.scrollableBind) { if(this.scrollableBind) {
// After a delay for rendering, scroll to the bound position // 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); domNode.setAttribute("title",this.selectTooltip);
} }
this.parentDomNode.insertBefore(domNode,nextSibling); this.parentDomNode.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null); this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.setSelectValue(); this.setSelectValue();
if(this.selectFocus == "yes") { if(this.selectFocus == "yes") {
this.getSelectDomNode().focus(); this.getSelectDomNode().focus();
@@ -82,8 +82,8 @@ SelectWidget.prototype.handleChangeEvent = function(event) {
if(this.selectMultiple == false) { if(this.selectMultiple == false) {
var value = this.getSelectDomNode().value; var value = this.getSelectDomNode().value;
} else { } else {
var value = this.getSelectValues(); var value = this.getSelectValues()
value = $tw.utils.stringifyList(value); value = $tw.utils.stringifyList(value);
} }
this.wiki.setText(this.selectTitle,this.selectField,this.selectIndex,value); this.wiki.setText(this.selectTitle,this.selectField,this.selectIndex,value);
// Trigger actions // Trigger actions
@@ -118,21 +118,12 @@ SelectWidget.prototype.setSelectValue = function() {
} }
} }
// Assign it to the select element if it's different than the current value // Assign it to the select element if it's different than the current value
if(this.selectMultiple) { if (this.selectMultiple) {
value = value === undefined ? "" : value; value = value === undefined ? "" : value;
var select = this.getSelectDomNode(); var select = this.getSelectDomNode();
var child, var values = Array.isArray(value) ? value : $tw.utils.parseStringArray(value);
values = Array.isArray(value) ? value : $tw.utils.parseStringArray(value);
for(var i=0; i < select.children.length; i++){ for(var i=0; i < select.children.length; i++){
child=select.children[i]; select.children[i].selected = values.indexOf(select.children[i].value) !== -1
if(child.children.length === 0){
child.selected = values.indexOf(child.value) !== -1;
} else {
// grouped options
for(var y=0; y < child.children.length; y++){
child.children[y].selected = values.indexOf(child.children[y].value) !== -1;
}
}
} }
} else { } else {
var domNode = this.getSelectDomNode(); var domNode = this.getSelectDomNode();
@@ -156,14 +147,14 @@ SelectWidget.prototype.getSelectValues = function() {
select = this.getSelectDomNode(); select = this.getSelectDomNode();
result = []; result = [];
options = select && select.options; options = select && select.options;
for(var i=0; i<options.length; i++) { for (var i=0; i<options.length; i++) {
opt = options[i]; opt = options[i];
if(opt.selected) { if (opt.selected) {
result.push(opt.value || opt.text); result.push(opt.value || opt.text);
} }
} }
return result; return result;
}; }
/* /*
Compute the internal state of the widget Compute the internal state of the widget
@@ -192,7 +183,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
SelectWidget.prototype.refresh = function(changedTiddlers) { SelectWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
// If we're using a different tiddler/field/index then completely refresh ourselves // If we're using a different tiddler/field/index then completely refresh ourselves
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip || changedAttributes.default || changedAttributes.tabindex || changedAttributes.disabled) { if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip || changedAttributes.tabindex || changedAttributes.disabled) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
} else { } else {

View File

@@ -32,26 +32,16 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) {
} catch(error) { } catch(error) {
if(error instanceof $tw.utils.TranscludeRecursionError) { if(error instanceof $tw.utils.TranscludeRecursionError) {
// We were infinite looping. // We were infinite looping.
// We need to try and abort as much of the loop as we // 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.
// can, so we will keep "throwing" upward until we find // Hopefully that will land us just outside where the loop began. That's where we want to issue an error.
// a transclusion that has a different signature. // Rendering widgets beneath this point may result in a freezing browser if they explode exponentially.
// 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"); var transcludeSignature = this.getVariable("transclusion");
if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) { if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) {
// For the first fifty transcludes we climb up, // For the first fifty transcludes we climb up, we simply collect signatures.
// we simply collect signatures. // We're assuming that those first 50 will likely include all transcludes involved in the loop.
// We're assuming those first 50 will likely
// include all transcludes involved in the loop.
error.signatures[transcludeSignature] = true; error.signatures[transcludeSignature] = true;
} else if(!error.signatures[transcludeSignature]) { } else if(!error.signatures[transcludeSignature]) {
// Now that we're past the first 50, look for // 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.
// 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: { this.children = [this.makeChildWidget({type: "error", attributes: {
"$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")}
}})]; }})];
@@ -158,10 +148,8 @@ Collect string parameters
TranscludeWidget.prototype.collectStringParameters = function() { TranscludeWidget.prototype.collectStringParameters = function() {
var self = this; var self = this;
this.stringParametersByName = Object.create(null); this.stringParametersByName = Object.create(null);
this.multiValuedParametersByName = Object.create(null);
if(!this.legacyMode) { if(!this.legacyMode) {
$tw.utils.each(this.attributes,function(value,name) { $tw.utils.each(this.attributes,function(value,name) {
var attrName = name; // Save original attribute name for MVV lookup
if(name.charAt(0) === "$") { if(name.charAt(0) === "$") {
if(name.charAt(1) === "$") { if(name.charAt(1) === "$") {
// Attributes starting $$ represent parameters starting with a single $ // Attributes starting $$ represent parameters starting with a single $
@@ -172,9 +160,6 @@ TranscludeWidget.prototype.collectStringParameters = function() {
} }
} }
self.stringParametersByName[name] = value; self.stringParametersByName[name] = value;
if(self.multiValuedAttributes && self.multiValuedAttributes[attrName]) {
self.multiValuedParametersByName[name] = self.multiValuedAttributes[attrName];
}
}); });
} }
}; };
@@ -318,16 +303,7 @@ TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) {
if(name.charAt(0) === "$") { if(name.charAt(0) === "$") {
name = "$" + name; name = "$" + name;
} }
if(param.defaultType === "multivalue-variable") { $tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"])
// Construct MVV attribute for the default
var mvvNode = {type: "transclude", isMVV: true, attributes: {}, orderedAttributes: []};
$tw.utils.addAttributeToParseTreeNode(mvvNode,"$variable",param.defaultVariable);
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],{
name: name, type: "macro", isMVV: true, value: mvvNode
});
} else {
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"]);
}
}); });
} else if(srcVariable && !srcVariable.isFunctionDefinition) { } else if(srcVariable && !srcVariable.isFunctionDefinition) {
// For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__" // For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
@@ -378,11 +354,7 @@ TranscludeWidget.prototype.getOrderedTransclusionParameters = function() {
// Collect the parameters // Collect the parameters
for(var name in this.stringParametersByName) { for(var name in this.stringParametersByName) {
var value = this.stringParametersByName[name]; var value = this.stringParametersByName[name];
var param = {name: name, value: value}; result.push({name: name, value: value});
if(this.multiValuedParametersByName[name]) {
param.multiValue = this.multiValuedParametersByName[name];
}
result.push(param);
} }
// Sort numerical parameter names first // Sort numerical parameter names first
result.sort(function(a,b) { result.sort(function(a,b) {
@@ -412,16 +384,10 @@ Fetch the value of a parameter
*/ */
TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaultValue) { TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaultValue) {
if(name in this.stringParametersByName) { if(name in this.stringParametersByName) {
if(this.multiValuedParametersByName[name]) {
return this.multiValuedParametersByName[name];
}
return this.stringParametersByName[name]; return this.stringParametersByName[name];
} else { } else {
var name = "" + index; var name = "" + index;
if(name in this.stringParametersByName) { if(name in this.stringParametersByName) {
if(this.multiValuedParametersByName[name]) {
return this.multiValuedParametersByName[name];
}
return this.stringParametersByName[name]; return this.stringParametersByName[name];
} }
} }

View File

@@ -9,215 +9,376 @@ View widget
"use strict"; "use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget; const Widget = require("$:/core/modules/widgets/widget.js").widget;
var ViewWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/* /*
Inherit from the base widget class ==========================================
ViewHandler Base Class
==========================================
Base class for all view format handlers.
Provides common functionality and defines the interface for subclasses.
*/ */
ViewWidget.prototype = new Widget(); class ViewHandler {
constructor(widget) {
/* this.widget = widget;
Render this widget into the DOM this.wiki = widget.wiki;
*/ this.document = widget.document;
ViewWidget.prototype.render = function(parent,nextSibling) { this.viewTitle = widget.viewTitle;
this.parentDomNode = parent; this.viewField = widget.viewField;
this.computeAttributes(); this.viewIndex = widget.viewIndex;
this.execute(); this.viewSubtiddler = widget.viewSubtiddler;
if(this.text) { this.viewTemplate = widget.viewTemplate;
var textNode = this.document.createTextNode(this.text); this.viewMode = widget.viewMode;
parent.insertBefore(textNode,nextSibling);
this.domNodes.push(textNode);
} else {
this.makeChildWidgets();
this.renderChildren(parent,nextSibling);
} }
};
/* render(parent, nextSibling) {
Compute the internal state of the widget this.text = this.getValue();
*/ this.createTextNode(parent, nextSibling);
ViewWidget.prototype.execute = function() {
// Get parameters from our attributes
this.viewTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
this.viewSubtiddler = this.getAttribute("subtiddler");
this.viewField = this.getAttribute("field","text");
this.viewIndex = this.getAttribute("index");
this.viewFormat = this.getAttribute("format","text");
this.viewTemplate = this.getAttribute("template","");
this.viewMode = this.getAttribute("mode","block");
switch(this.viewFormat) {
case "htmlwikified":
this.text = this.getValueAsHtmlWikified(this.viewMode);
break;
case "plainwikified":
this.text = this.getValueAsPlainWikified(this.viewMode);
break;
case "htmlencodedplainwikified":
this.text = this.getValueAsHtmlEncodedPlainWikified(this.viewMode);
break;
case "htmlencoded":
this.text = this.getValueAsHtmlEncoded();
break;
case "htmltextencoded":
this.text = this.getValueAsHtmlTextEncoded();
break;
case "urlencoded":
this.text = this.getValueAsUrlEncoded();
break;
case "doubleurlencoded":
this.text = this.getValueAsDoubleUrlEncoded();
break;
case "date":
this.text = this.getValueAsDate(this.viewTemplate);
break;
case "relativedate":
this.text = this.getValueAsRelativeDate();
break;
case "stripcomments":
this.text = this.getValueAsStrippedComments();
break;
case "jsencoded":
this.text = this.getValueAsJsEncoded();
break;
default: // "text"
this.text = this.getValueAsText();
break;
} }
};
/* getValue() {
The various formatter functions are baked into this widget for the moment. Eventually they will be replaced by macro functions return this.widget.getValueAsText();
*/ }
/* createTextNode(parent, nextSibling) {
Retrieve the value of the widget. Options are: if(this.text) {
asString: Optionally return the value as a string const textNode = this.document.createTextNode(this.text);
*/ parent.insertBefore(textNode, nextSibling);
ViewWidget.prototype.getValue = function(options) { this.widget.domNodes.push(textNode);
options = options || {};
var value = options.asString ? "" : undefined;
if(this.viewIndex) {
value = this.wiki.extractTiddlerDataItem(this.viewTitle,this.viewIndex);
} else {
var tiddler;
if(this.viewSubtiddler) {
tiddler = this.wiki.getSubTiddler(this.viewTitle,this.viewSubtiddler);
} else { } else {
tiddler = this.wiki.getTiddler(this.viewTitle); this.widget.makeChildWidgets();
} this.widget.renderChildren(parent, nextSibling);
if(tiddler) {
if(this.viewField === "text" && !this.viewSubtiddler) {
// Calling getTiddlerText() triggers lazy loading of skinny tiddlers
value = this.wiki.getTiddlerText(this.viewTitle);
} else {
if($tw.utils.hop(tiddler.fields,this.viewField)) {
if(options.asString) {
value = tiddler.getFieldString(this.viewField);
} else {
value = tiddler.fields[this.viewField];
}
}
}
} else {
if(this.viewField === "title") {
value = this.viewTitle;
}
} }
} }
return value;
};
ViewWidget.prototype.getValueAsText = function() { refresh() {
return this.getValue({asString: true}); var self = this;
};
ViewWidget.prototype.getValueAsHtmlWikified = function(mode) {
return this.wiki.renderText("text/html","text/vnd.tiddlywiki",this.getValueAsText(),{
parseAsInline: mode !== "block",
parentWidget: this
});
};
ViewWidget.prototype.getValueAsPlainWikified = function(mode) {
return this.wiki.renderText("text/plain","text/vnd.tiddlywiki",this.getValueAsText(),{
parseAsInline: mode !== "block",
parentWidget: this
});
};
ViewWidget.prototype.getValueAsHtmlEncodedPlainWikified = function(mode) {
return $tw.utils.htmlEncode(this.wiki.renderText("text/plain","text/vnd.tiddlywiki",this.getValueAsText(),{
parseAsInline: mode !== "block",
parentWidget: this
}));
};
ViewWidget.prototype.getValueAsHtmlEncoded = function() {
return $tw.utils.htmlEncode(this.getValueAsText());
};
ViewWidget.prototype.getValueAsHtmlTextEncoded = function() {
return $tw.utils.htmlTextEncode(this.getValueAsText());
};
ViewWidget.prototype.getValueAsUrlEncoded = function() {
return $tw.utils.encodeURIComponentExtended(this.getValueAsText());
};
ViewWidget.prototype.getValueAsDoubleUrlEncoded = function() {
return $tw.utils.encodeURIComponentExtended($tw.utils.encodeURIComponentExtended(this.getValueAsText()));
};
ViewWidget.prototype.getValueAsDate = function(format) {
format = format || "YYYY MM DD 0hh:0mm";
var value = $tw.utils.parseDate(this.getValue());
if(value && $tw.utils.isDate(value) && value.toString() !== "Invalid Date") {
return $tw.utils.formatDateString(value,format);
} else {
return "";
}
};
ViewWidget.prototype.getValueAsRelativeDate = function(format) {
var value = $tw.utils.parseDate(this.getValue());
if(value && $tw.utils.isDate(value) && value.toString() !== "Invalid Date") {
return $tw.utils.getRelativeDate((new Date()) - (new Date(value))).description;
} else {
return "";
}
};
ViewWidget.prototype.getValueAsStrippedComments = function() {
var lines = this.getValueAsText().split("\n"),
out = [];
for(var line=0; line<lines.length; line++) {
var text = lines[line];
if(!/^\s*\/\/#/.test(text)) {
out.push(text);
}
}
return out.join("\n");
};
ViewWidget.prototype.getValueAsJsEncoded = function() {
return $tw.utils.stringify(this.getValueAsText());
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ViewWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.template || changedAttributes.format || changedTiddlers[this.viewTitle]) {
this.refreshSelf();
return true;
} else {
return false; return false;
} }
}
/*
==========================================
Wikified View Handler Base
==========================================
Base class for wikified view handlers
*/
class WikifiedViewHandler extends ViewHandler {
constructor(widget) {
super(widget);
this.fakeWidget = null;
this.fakeNode = null;
}
render(parent, nextSibling) {
this.createFakeWidget();
this.text = this.getValue();
this.createWikifiedTextNode(parent, nextSibling);
}
createFakeWidget() {
this.fakeWidget = this.wiki.makeTranscludeWidget(this.viewTitle, {
document: $tw.fakeDocument,
field: this.viewField,
index: this.viewIndex,
parseAsInline: this.viewMode !== "block",
mode: this.viewMode === "block" ? "block" : "inline",
parentWidget: this.widget,
subTiddler: this.viewSubtiddler
});
this.fakeNode = $tw.fakeDocument.createElement("div");
this.fakeWidget.makeChildWidgets();
this.fakeWidget.render(this.fakeNode, null);
}
createWikifiedTextNode(parent, nextSibling) {
const textNode = this.document.createTextNode(this.text || "");
parent.insertBefore(textNode, nextSibling);
this.widget.domNodes.push(textNode);
}
refresh(changedTiddlers) {
const refreshed = this.fakeWidget.refresh(changedTiddlers);
if(refreshed) {
const newText = this.getValue();
if(newText !== this.text) {
this.widget.domNodes[0].textContent = newText;
this.text = newText;
}
}
return refreshed;
}
}
/*
==========================================
Text View Handler
==========================================
Default handler for plain text display
*/
class TextViewHandler extends ViewHandler {}
/*
==========================================
HTML Wikified View Handler
==========================================
Handler for wikified HTML content
*/
class HTMLWikifiedViewHandler extends WikifiedViewHandler {
getValue() {
return this.fakeNode.innerHTML;
}
}
/*
==========================================
Plain Wikified View Handler
==========================================
Handler for wikified plain text content
*/
class PlainWikifiedViewHandler extends WikifiedViewHandler {
getValue() {
return this.fakeNode.textContent;
}
}
/*
==========================================
HTML Encoded Plain Wikified View Handler
==========================================
Handler for HTML-encoded wikified plain text
*/
class HTMLEncodedPlainWikifiedViewHandler extends WikifiedViewHandler {
getValue() {
return $tw.utils.htmlEncode(this.fakeNode.textContent);
}
}
/*
==========================================
HTML Encoded View Handler
==========================================
Handler for HTML-encoded text
*/
class HTMLEncodedViewHandler extends ViewHandler {
getValue() {
return $tw.utils.htmlEncode(this.widget.getValueAsText());
}
}
/*
==========================================
HTML Text Encoded View Handler
==========================================
Handler for HTML text-encoded content
*/
class HTMLTextEncodedViewHandler extends ViewHandler {
getValue() {
return $tw.utils.htmlTextEncode(this.widget.getValueAsText());
}
}
/*
==========================================
URL Encoded View Handler
==========================================
Handler for URL-encoded text
*/
class URLEncodedViewHandler extends ViewHandler {
getValue() {
return $tw.utils.encodeURIComponentExtended(this.widget.getValueAsText());
}
}
/*
==========================================
Double URL Encoded View Handler
==========================================
Handler for double URL-encoded text
*/
class DoubleURLEncodedViewHandler extends ViewHandler {
getValue() {
const text = this.widget.getValueAsText();
return $tw.utils.encodeURIComponentExtended($tw.utils.encodeURIComponentExtended(text));
}
}
/*
==========================================
Date View Handler
==========================================
Handler for date formatting
*/
class DateViewHandler extends ViewHandler {
getValue() {
const format = this.viewTemplate || "YYYY MM DD 0hh:0mm";
const rawValue = this.widget.getValueAsText();
const value = $tw.utils.parseDate(rawValue);
if(value && $tw.utils.isDate(value) && value.toString() !== "Invalid Date") {
return $tw.utils.formatDateString(value, format);
} else {
return "";
}
}
}
/*
==========================================
Relative Date View Handler
==========================================
Handler for relative date display
*/
class RelativeDateViewHandler extends ViewHandler {
getValue() {
const rawValue = this.widget.getValueAsText();
const value = $tw.utils.parseDate(rawValue);
if(value && $tw.utils.isDate(value) && value.toString() !== "Invalid Date") {
return $tw.utils.getRelativeDate((new Date()) - (new Date(value))).description;
} else {
return "";
}
}
}
/*
==========================================
Strip Comments View Handler
==========================================
Handler for stripping comments from text
*/
class StripCommentsViewHandler extends ViewHandler {
getValue() {
const lines = this.widget.getValueAsText().split("\n");
const out = [];
for(const text of lines) {
if(!/^\s*\/\/#/.test(text)) {
out.push(text);
}
}
return out.join("\n");
}
}
/*
==========================================
JS Encoded View Handler
==========================================
Handler for JavaScript string encoding
*/
class JSEncodedViewHandler extends ViewHandler {
getValue() {
return $tw.utils.stringify(this.widget.getValueAsText());
}
}
/*
==========================================
ViewHandlerFactory
==========================================
Factory for creating appropriate view handlers based on format
*/
const ViewHandlerFactory = {
handlers: {
"text": TextViewHandler,
"htmlwikified": HTMLWikifiedViewHandler,
"plainwikified": PlainWikifiedViewHandler,
"htmlencodedplainwikified": HTMLEncodedPlainWikifiedViewHandler,
"htmlencoded": HTMLEncodedViewHandler,
"htmltextencoded": HTMLTextEncodedViewHandler,
"urlencoded": URLEncodedViewHandler,
"doubleurlencoded": DoubleURLEncodedViewHandler,
"date": DateViewHandler,
"relativedate": RelativeDateViewHandler,
"stripcomments": StripCommentsViewHandler,
"jsencoded": JSEncodedViewHandler
},
createHandler(format, widget) {
const HandlerClass = this.handlers[format] || this.handlers["text"];
return new HandlerClass(widget);
},
registerHandler(format, handlerClass) {
this.handlers[format] = handlerClass;
}
}; };
exports.view = ViewWidget; /*
==========================================
ViewWidget
==========================================
Main widget class that orchestrates view handlers
*/
class ViewWidget extends Widget {
constructor(parseTreeNode, options) {
super();
this.initialise(parseTreeNode, options);
}
render(parent, nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.viewHandler = ViewHandlerFactory.createHandler(this.viewFormat, this);
this.viewHandler.render(parent, nextSibling);
}
execute() {
this.viewTitle = this.getAttribute("tiddler", this.getVariable("currentTiddler"));
this.viewSubtiddler = this.getAttribute("subtiddler");
this.viewField = this.getAttribute("field", "text");
this.viewIndex = this.getAttribute("index");
this.viewFormat = this.getAttribute("format", "text");
this.viewTemplate = this.getAttribute("template", "");
this.viewMode = this.getAttribute("mode", "block");
}
getValue(options = {}) {
let value = options.asString ? "" : undefined;
if(this.viewIndex) {
value = this.wiki.extractTiddlerDataItem(this.viewTitle, this.viewIndex);
} else {
let tiddler;
if(this.viewSubtiddler) {
tiddler = this.wiki.getSubTiddler(this.viewTitle, this.viewSubtiddler);
} else {
tiddler = this.wiki.getTiddler(this.viewTitle);
}
if(tiddler) {
if(this.viewField === "text" && !this.viewSubtiddler) {
value = this.wiki.getTiddlerText(this.viewTitle);
} else {
if($tw.utils.hop(tiddler.fields, this.viewField)) {
if(options.asString) {
value = tiddler.getFieldString(this.viewField);
} else {
value = tiddler.fields[this.viewField];
}
}
}
} else {
if(this.viewField === "title") {
value = this.viewTitle;
}
}
}
return value;
}
getValueAsText() {
return this.getValue({asString: true});
}
refresh(changedTiddlers) {
const changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index ||
changedAttributes.template || changedAttributes.format || changedTiddlers[this.viewTitle]) {
this.refreshSelf();
return true;
} else {
return this.viewHandler.refresh(changedTiddlers);
}
}
}
exports.view = ViewWidget;

View File

@@ -151,7 +151,7 @@ Widget.prototype.getVariableInfo = function(name,options) {
} else if(variable.isFunctionDefinition) { } else if(variable.isFunctionDefinition) {
// Function evaluations // Function evaluations
params = self.resolveVariableParameters(variable.params,actualParams); params = self.resolveVariableParameters(variable.params,actualParams);
var variables = $tw.utils.extend({},options.variables); var variables = options.variables || Object.create(null);
// Apply default parameter values // Apply default parameter values
$tw.utils.each(variable.params,function(param,index) { $tw.utils.each(variable.params,function(param,index) {
if(param["default"]) { 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) // Parameters are an array of {name:, value:, multivalue:} pairs (name and multivalue are optional)
$tw.utils.each(params,function(param) { $tw.utils.each(params,function(param) {
if(param.multiValue && param.multiValue.length) { if(param.multiValue) {
variables[param.name] = param.multiValue; variables[param.name] = param.multiValue;
} else { } else {
variables[param.name] = param.value || ""; variables[param.name] = param.value || "";
@@ -233,10 +233,8 @@ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams)
paramMultiValue = typeof param === "string" ? [param] : (param.multiValue || [paramValue]); paramMultiValue = typeof param === "string" ? [param] : (param.multiValue || [paramValue]);
} }
// If we've still not got a value, use the default, if any // If we've still not got a value, use the default, if any
if(!paramValue) { paramValue = paramValue || paramInfo["default"] || "";
paramValue = paramInfo["default"] || ""; paramMultiValue = paramMultiValue || [paramValue];
paramMultiValue = [paramValue];
}
// Store the parameter name and value // Store the parameter name and value
results.push({name: paramInfo.name, value: paramValue, multiValue: paramMultiValue}); results.push({name: paramInfo.name, value: paramValue, multiValue: paramMultiValue});
} }
@@ -343,7 +341,7 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
} }
} else { } else {
opts = opts || {}; opts = opts || {};
opts.variables = $tw.utils.extend({},variables,opts.variables); opts.variables = variables;
return self.getVariable(name,opts); return self.getVariable(name,opts);
}; };
}, },
@@ -381,31 +379,19 @@ filterFn: only include attributes where filterFn(name) returns true
Widget.prototype.computeAttributes = function(options) { Widget.prototype.computeAttributes = function(options) {
options = options || {}; options = options || {};
var changedAttributes = {}, var changedAttributes = {},
self = this, self = this;
newMultiValuedAttributes = Object.create(null);
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(options.filterFn) { if(options.filterFn) {
if(!options.filterFn(name)) { if(!options.filterFn(name)) {
return; return;
} }
} }
var value = self.computeAttribute(attribute), var value = self.computeAttribute(attribute);
multiValue = null; if(self.attributes[name] !== value) {
if($tw.utils.isArray(value)) {
multiValue = value;
newMultiValuedAttributes[name] = multiValue;
value = value[0] || "";
}
var changed = (self.attributes[name] !== value);
if(!changed && multiValue && self.multiValuedAttributes) {
changed = !$tw.utils.isArrayEqual(self.multiValuedAttributes[name] || [], multiValue);
}
if(changed) {
self.attributes[name] = value; self.attributes[name] = value;
changedAttributes[name] = true; changedAttributes[name] = true;
} }
}); });
this.multiValuedAttributes = newMultiValuedAttributes;
return changedAttributes; return changedAttributes;
}; };
@@ -428,22 +414,8 @@ Widget.prototype.computeAttribute = function(attribute,options) {
value = [value]; value = [value];
} }
} else if(attribute.type === "macro") { } else if(attribute.type === "macro") {
// Get the macro name var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params});
var macroName = attribute.value.attributes["$variable"].value; if(options.asList) {
// Collect macro parameters
var params = [];
$tw.utils.each(attribute.value.orderedAttributes,function(attr) {
var param = {
value: self.computeAttribute(attr)
};
if(attr.name && !attr.isPositional) {
param.name = attr.name;
}
params.push(param);
});
// Invoke the macro
var variableInfo = this.getVariableInfo(macroName,{params: params});
if(options.asList || attribute.isMVV) {
value = variableInfo.resultList; value = variableInfo.resultList;
} else { } else {
value = variableInfo.text; value = variableInfo.text;
@@ -799,9 +771,9 @@ Widget.prototype.findNextSiblingDomNode = function(startIndex) {
// Refer to this widget by its index within its parents children // Refer to this widget by its index within its parents children
var parent = this.parentWidget, var parent = this.parentWidget,
index = startIndex !== undefined ? startIndex : parent.children.indexOf(this); index = startIndex !== undefined ? startIndex : parent.children.indexOf(this);
if(index === -1) { if(index === -1) {
throw "node not found in parents children"; throw "node not found in parents children";
} }
// Look for a DOM node in the later siblings // Look for a DOM node in the later siblings
while(++index < parent.children.length) { while(++index < parent.children.length) {
var domNode = parent.children[index].findFirstDomNode(); var domNode = parent.children[index].findFirstDomNode();
@@ -839,60 +811,21 @@ Widget.prototype.findFirstDomNode = function() {
}; };
/* /*
Entry into destroy procedure Remove any DOM nodes created by this widget or its children
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() { Widget.prototype.removeChildDomNodes = function() {
this.destroy({removeDOMNodes: true}); // 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) {
/*
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); 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.domNodes = [];
}; };
/* /*

View File

@@ -369,16 +369,31 @@ Sort an array of tiddler titles by a specified field
isDescending: true if the sort should be descending isDescending: true if the sort should be descending
isCaseSensitive: true if the sort should consider upper and lower case letters to be different isCaseSensitive: true if the sort should consider upper and lower case letters to be different
*/ */
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric,locale) { exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric) {
var self = this; var self = this;
if(sortField === "title") { if(sortField === "title") {
if(!isNumeric && !isAlphaNumeric) { if(!isNumeric && !isAlphaNumeric) {
const sorter = new Intl.Collator(locale, { sensitivity: isCaseSensitive ? "variant" : "accent" }); if(isCaseSensitive) {
if(isDescending) { if(isDescending) {
titles.sort((a,b) => sorter.compare(b, a)); titles.sort(function(a,b) {
return b.localeCompare(a);
});
} else {
titles.sort(function(a,b) {
return a.localeCompare(b);
});
}
} else { } else {
titles.sort((a,b) => sorter.compare(a, b)); if(isDescending) {
} titles.sort(function(a,b) {
return b.toLowerCase().localeCompare(a.toLowerCase());
});
} else {
titles.sort(function(a,b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
}
}
} else { } else {
titles.sort(function(a,b) { titles.sort(function(a,b) {
var x,y; var x,y;
@@ -399,8 +414,14 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
} }
} }
} }
const sorter = new Intl.Collator(locale, { numeric: isAlphaNumeric, sensitivity: isAlphaNumeric ? "base" : isCaseSensitive ? "variant" : "accent" }); if(isAlphaNumeric) {
return isDescending ? sorter.compare(b, a) : sorter.compare(a, b); return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
}
if(!isCaseSensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
}); });
} }
} else { } else {
@@ -442,8 +463,14 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
} }
a = String(a); a = String(a);
b = String(b); b = String(b);
const sorter = new Intl.Collator(locale, { numeric: isAlphaNumeric, sensitivity: isAlphaNumeric ? "base" : isCaseSensitive ? "variant" : "accent" }); if(isAlphaNumeric) {
return isDescending ? sorter.compare(b, a) : sorter.compare(a, b); return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
}
if(!isCaseSensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
}); });
} }
}; };
@@ -1122,16 +1149,15 @@ Parse a block of text of a specified MIME type
Options include: Options include:
substitutions: an optional array of substitutions substitutions: an optional array of substitutions
*/ */
exports.getSubstitutedText = function(text,thisWidget,options) { exports.getSubstitutedText = function(text,widget,options) {
options = options || {}; options = options || {};
text = text || ""; text = text || "";
var self = this, var self = this,
widgetClass = widget.widget,
substitutions = options.substitutions || [], substitutions = options.substitutions || [],
output; output;
// Evaluate embedded filters and substitute with first result // Evaluate embedded filters and substitute with first result
output = text.replace(/\$\{([\S\s]+?)\}\$/g, function(match,filter) { output = text.replace(/\$\{([\S\s]+?)\}\$/g, function(match,filter) {
return self.filterTiddlers(filter,thisWidget)[0] || ""; return self.filterTiddlers(filter,widget)[0] || "";
}); });
// Process any substitutions provided in options // Process any substitutions provided in options
$tw.utils.each(substitutions,function(substitute) { $tw.utils.each(substitutions,function(substitute) {
@@ -1139,7 +1165,7 @@ exports.getSubstitutedText = function(text,thisWidget,options) {
}); });
// Substitute any variable references with their values // Substitute any variable references with their values
return output.replace(/\$\((.+?)\)\$/g, function(match,varname) { return output.replace(/\$\((.+?)\)\$/g, function(match,varname) {
return widgetClass.evaluateVariable(thisWidget,varname, {defaultValue: ""})[0]; return widget.getVariable(varname,{defaultValue: ""});
}); });
}; };
@@ -1664,14 +1690,12 @@ exports.addToStory = function(title,fromTitle,storyTitle,options) {
Generate a title for the draft of a given tiddler Generate a title for the draft of a given tiddler
*/ */
exports.generateDraftTitle = function(title) { exports.generateDraftTitle = function(title) {
let c = 0, var c = 0,
draftTitle; draftTitle,
const username = this.getTiddlerText("$:/status/UserName"); username = this.getTiddlerText("$:/status/UserName"),
attribution = username ? " by " + username : "";
do { do {
draftTitle = username ? $tw.language.getString("Draft/Attribution", {variables: {"draft-title": title}}) : $tw.language.getString("Draft/Title", {variables: {"draft-title": title}}); draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'" + attribution;
if(c) {
draftTitle = draftTitle.concat(" ", (c + 1).toString());
}
c++; c++;
} while(this.tiddlerExists(draftTitle)); } while(this.tiddlerExists(draftTitle));
return draftTitle; return draftTitle;

View File

@@ -1,30 +0,0 @@
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

@@ -25,6 +25,7 @@ title: $:/core/templates/tiddlywiki5.html
`{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/core/wiki/rawmarkup]] ||$:/core/templates/plain-text-tiddler}}} `{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/core/wiki/rawmarkup]] ||$:/core/templates/plain-text-tiddler}}}
{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkup]] ||$:/core/templates/plain-text-tiddler}}} {{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkup]] ||$:/core/templates/plain-text-tiddler}}}
{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}` {{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}`
<!--~~ Style section start ~~-->
</head> </head>
<body class="tc-body"> <body class="tc-body">
<!--~~ Raw markup for the top of the body section ~~--> <!--~~ Raw markup for the top of the body section ~~-->

View File

@@ -18,7 +18,6 @@ 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/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/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/AutoFocus"><<lingo AutoFocus/Prompt>></$link> |{{$:/snippets/minifocusswitcher}} |
|<$link to="$:/config/AutoFocusEdit"><<lingo AutoFocusEdit/Prompt>></$link> |{{$:/snippets/minifocuseditswitcher}} |
|<<lingo Language/Prompt>> |{{$:/snippets/minilanguageswitcher}} | |<<lingo Language/Prompt>> |{{$:/snippets/minilanguageswitcher}} |
|<<lingo Tiddlers/Prompt>> |<<show-filter-count "[!is[system]sort[title]]">> | |<<lingo Tiddlers/Prompt>> |<<show-filter-count "[!is[system]sort[title]]">> |
|<<lingo Tags/Prompt>> |<<show-filter-count "[tags[]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" class="tc-edit-texteditor tc-edit-texteditor-body"
placeholder={{$:/language/EditTemplate/Body/Placeholder}} placeholder={{$:/language/EditTemplate/Body/Placeholder}}
tabindex={{$:/config/EditTabIndex}} tabindex={{$:/config/EditTabIndex}}
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[text]then[true]] ~[{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}} focus={{{ [{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
cancelPopups="yes" cancelPopups="yes"
fileDrop={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}} 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 <$set
name="buttonClasses" name="buttonClasses"
value={{{ [subfilter{!!button-classes}] :and[join[ ]] }}} value={{!!button-classes}}
><<toolbar-button>></$set> ><<toolbar-button>></$set>
\end \end
<<toolbar-button-outer>> <<toolbar-button-outer>>

View File

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

View File

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

View File

@@ -4,28 +4,13 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d
\procedure lingo-base() $:/language/EditTemplate/ \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> \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 \whitespace trim
<$set name="refreshTitle" value=<<qualify "$:/temp/type-search/refresh">>> <$set name="refreshTitle" value=<<qualify "$:/temp/type-search/refresh">>>
<div class="tc-edit-type-selector-wrapper"> <div class="tc-edit-type-selector-wrapper">
<em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em> <em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em>
<div class="tc-type-selector-dropdown-wrapper"> <div class="tc-type-selector-dropdown-wrapper">
<div class="tc-type-selector"> <div class="tc-type-selector"><$fieldmangler>
<$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>
<$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> </$fieldmangler></div>
<div class="tc-block-dropdown-wrapper"> <div class="tc-block-dropdown-wrapper">

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
title: $:/core/ui/PageTemplate/alerts title: $:/core/ui/PageTemplate/alerts
tags: $:/tags/PageTemplate tags: $:/tags/PageTemplate
<div class="tc-alerts" role="region" aria-label={{$:/language/Alerts}}> <div class="tc-alerts" role="region" aria-label="Alerts">
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Alert]!is[draft]]" template="$:/core/ui/AlertTemplate" storyview="pop"/> <$list filter="[all[shadows+tiddlers]tag[$:/tags/Alert]!is[draft]]" template="$:/core/ui/AlertTemplate" storyview="pop"/>

View File

@@ -0,0 +1,15 @@
title: $:/core/ui/RootStylesheet
code-body: yes
\import [subfilter{$:/core/config/GlobalImportFilter}]
\whitespace trim
<$let currentTiddler={{$:/language}} languageTitle={{!!name}}>
<style type="text/css">
<$view tiddler="$:/themes/tiddlywiki/vanilla/reset" format="text" mode="block"/>
</style>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Stylesheet]!is[draft]] -[[$:/themes/tiddlywiki/vanilla/reset]]">
<style type="text/css">
<$view format={{{ [<currentTiddler>tag[$:/tags/Stylesheet/Static]then[text]else[plainwikified]] }}} mode="block"/>
</style>
</$list>
</$let>

View File

@@ -15,7 +15,7 @@ caption: {{$:/language/SideBar/Open/Caption}}
\define droppable-item(button) \define droppable-item(button)
\whitespace trim \whitespace trim
<$droppable actions=<<drop-actions>> enable=<<tv-enable-drag-and-drop>> tag="div"> <$droppable actions=<<drop-actions>> enable=<<tv-allow-drag-and-drop>> tag="div">
<<placeholder>> <<placeholder>>
<div> <div>
$button$ $button$

View File

@@ -2,7 +2,7 @@ title: $:/core/ui/TiddlerInfo
\whitespace trim \whitespace trim
<div style="position:relative;"> <div style="position:relative;">
<div class="tc-tiddler-controls tc-tiddler-info-controls"> <div class="tc-tiddler-controls" style="position:absolute;right:0;">
<$reveal state="$:/config/TiddlerInfo/Mode" type="match" text="sticky"> <$reveal state="$:/config/TiddlerInfo/Mode" type="match" text="sticky">
<$button set=<<tiddlerInfoState>> setTo="" tooltip={{$:/language/Buttons/Info/Hint}} aria-label={{$:/language/Buttons/Info/Caption}} class="tc-btn-invisible"> <$button set=<<tiddlerInfoState>> setTo="" tooltip={{$:/language/Buttons/Info/Hint}} aria-label={{$:/language/Buttons/Info/Caption}} class="tc-btn-invisible">
{{$:/core/images/close-button}} {{$:/core/images/close-button}}

View File

@@ -1,43 +0,0 @@
title: $:/core/ui/TiddlerInfo/Advanced/CascadeInfo
tags: $:/tags/TiddlerInfo/Advanced
\define lingo-base() $:/language/TiddlerInfo/Advanced/CascadeInfo/
<$let infoTiddler=<<currentTiddler>>>
''<<lingo Heading>>''
<<lingo Hint>>
<table class="tc-max-width">
<thead>
<$list filter="[[View]] [[ActiveCascadeFilter]] [[Template]]" variable="th">
<th><$transclude $variable="lingo" title=`Detail/$(th)$`/></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

@@ -11,13 +11,9 @@ tags: $:/tags/ViewTemplate
storyview="pop" storyview="pop"
variable="listItem" variable="listItem"
> >
<$let condition={{{ [<listItem>get[condition]] }}}> <$set name="tv-config-toolbar-class" filter="[<tv-config-toolbar-class>] [<listItem>encodeuricomponent[]addprefix[tc-btn-]]">
<%if [<condition>!is[blank]] :and[<currentTiddler>subfilter<condition>limit[1]] :else[<condition>is[blank]then[true]] %> <$transclude tiddler=<<listItem>>/>
<$set name="tv-config-toolbar-class" filter="[<tv-config-toolbar-class>] [<listItem>encodeuricomponent[]addprefix[tc-btn-]]"> </$set>
<$transclude tiddler=<<listItem>>/>
</$set>
<%endif%>
</$let>
</$list> </$list>
</span> </span>
<$set name="tv-wikilinks" value={{$:/config/Tiddlers/TitleLinks}}> <$set name="tv-wikilinks" value={{$:/config/Tiddlers/TitleLinks}}>

View File

@@ -41,8 +41,6 @@ Drag this link to copy this tool to another wiki
</$wikify> </$wikify>
\end capture-item-wikified \end capture-item-wikified
\function get.shadow.source() [shadowsource[]]
\procedure capture-wiki-info(tempWikiInfo) \procedure capture-wiki-info(tempWikiInfo)
<$transclude $variable="capture-item-wikified" label="TiddlyWiki Version" value="<<version>>"/> <$transclude $variable="capture-item-wikified" label="TiddlyWiki Version" value="<<version>>"/>
<$transclude $variable="capture-item" label="Current palette" value={{$:/palette}}/> <$transclude $variable="capture-item" label="Current palette" value={{$:/palette}}/>
@@ -66,7 +64,6 @@ Drag this link to copy this tool to another wiki
<$transclude $variable="capture-item" label="Keyboard shortcuts that have been customised" value={{{ [all[tiddlers]prefix[$:/config/shortcuts]] +[join[,]] }}}/> <$transclude $variable="capture-item" label="Keyboard shortcuts that have been customised" value={{{ [all[tiddlers]prefix[$:/config/shortcuts]] +[join[,]] }}}/>
<$transclude $variable="capture-item" label="Disabled plugins" value={{{ [all[tiddlers]prefix[$:/config/Plugins/Disabled/]] :filter[{!!text}match[yes]] :map[<currentTiddler>removeprefix[$:/config/Plugins/Disabled/]] +[join[,]] }}}/> <$transclude $variable="capture-item" label="Disabled plugins" value={{{ [all[tiddlers]prefix[$:/config/Plugins/Disabled/]] :filter[{!!text}match[yes]] :map[<currentTiddler>removeprefix[$:/config/Plugins/Disabled/]] +[join[,]] }}}/>
<$transclude $variable="capture-item" label="Plugins" value={{{ [has[plugin-type]sort[]] :filter[<currentTiddler>addprefix[$:/config/Plugins/Disabled/]get[text]else[no]!match[yes]] :map[{!!version}addprefix[ - ]addprefix<currentTiddler>] +[addprefix[ ]addprefix<crlf>join[]] }}}/> <$transclude $variable="capture-item" label="Plugins" value={{{ [has[plugin-type]sort[]] :filter[<currentTiddler>addprefix[$:/config/Plugins/Disabled/]get[text]else[no]!match[yes]] :map[{!!version}addprefix[ - ]addprefix<currentTiddler>] +[addprefix[ ]addprefix<crlf>join[]] }}}/>
<$transclude $variable="capture-item" label="Stylesheets" value={{{ [all[shadows+tiddlers]tag[$:/tags/Stylesheet]!is[draft]] :map[is[shadow]addsuffix[ ∈ ]addsuffix<get.shadow.source>else<currentTiddler>] +[addprefix[ ]addprefix<crlf>join[]] }}}/>
\end capture-wiki-info \end capture-wiki-info
\procedure template-header() \procedure template-header()

View File

@@ -1,5 +0,0 @@
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

@@ -1,39 +0,0 @@
title: $:/core/macros/CSS/property
<!-- CSS property macros -->
<!-- TODO: Deprecate the following CSS macros once 2020 baseline is supported -->
\procedure margin-start(size)
-webkit-margin-start: <<size>>;
margin-inline-start: <<size>>;
\end
\procedure margin-end(size)
-webkit-margin-end: <<size>>;
margin-inline-end: <<size>>;
\end
\procedure padding-start(size)
-webkit-padding-start: <<size>>;
padding-inline-start: <<size>>;
\end
\procedure padding-end(size)
-webkit-padding-end: <<size>>;
padding-inline-end: <<size>>;
\end
\procedure margin-inline(start,end)
-webkit-margin-start: <<start>>;
margin-inline-start: <<start>>;
-webkit-margin-end: <<end>>;
margin-inline-end: <<end>>;
\end
\procedure padding-inline(start,end)
-webkit-padding-start: <<start>>;
padding-inline-start: <<start>>;
-webkit-padding-end: <<end>>;
padding-inline-end: <<end>>;
\end

View File

@@ -122,15 +122,15 @@ tags: $:/tags/Macro
\whitespace trim \whitespace trim
\procedure keyboard-driven-input-actions() \procedure keyboard-driven-input-actions()
<%if [<event-key-descriptor>match[((input-accept))]] %> <%if [<event-key-descriptor>match[((input-accept))]] %>
<$transclude $variable=inputAcceptActions $fillignore=yes /> <<inputAcceptActions>>
<%elseif [<event-key-descriptor>match[((input-accept-variant))]] %> <%elseif [<event-key-descriptor>match[((input-accept-variant))]] %>
<$transclude $variable=inputAcceptVariantActions $fillignore=yes /> <<inputAcceptVariantActions>>
<%elseif [<event-key-descriptor>match[((input-up))]] %> <%elseif [<event-key-descriptor>match[((input-up))]] %>
<$transclude $variable=input-next-actions-before $fillignore=yes /> <<input-next-actions-before>>
<%elseif [<event-key-descriptor>match[((input-down))]] %> <%elseif [<event-key-descriptor>match[((input-down))]] %>
<$transclude $variable=input-next-actions-after $fillignore=yes /> <<input-next-actions-after>>
<%elseif [<event-key-descriptor>match[((input-cancel))]] %> <%elseif [<event-key-descriptor>match[((input-cancel))]] %>
<$transclude $variable=inputCancelActions $fillignore=yes /> <<inputCancelActions>>
<%endif%> <%endif%>
\end keyboard-driven-input-actions \end keyboard-driven-input-actions

View File

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

View File

@@ -9,9 +9,8 @@ code-body: yes
setTo=<<currentTab>> setTo=<<currentTab>>
default=<<__default__>> default=<<__default__>>
selectedClass="tc-tab-selected" selectedClass="tc-tab-selected"
selectedAria="aria-selected"
tooltip={{!!tooltip}} tooltip={{!!tooltip}}
role="tab" role="switch"
data-tab-title=<<currentTab>> data-tab-title=<<currentTab>>
> >
<$tiddler tiddler=<<save-currentTiddler>>> <$tiddler tiddler=<<save-currentTiddler>>>
@@ -58,12 +57,12 @@ code-body: yes
\whitespace trim \whitespace trim
<$qualify title=<<__state__>> name="qualifiedState"> <$qualify title=<<__state__>> name="qualifiedState">
<$let tabsState={{{ [<__explicitState__>minlength[1]] ~[<qualifiedState>] }}}> <$let tabsState={{{ [<__explicitState__>minlength[1]] ~[<qualifiedState>] }}}>
<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}} role="tablist"> <div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}}>
<div class={{{ [[tc-tab-buttons]addsuffix[ ]addsuffix<__class__>] }}}> <div class={{{ [[tc-tab-buttons]addsuffix[ ]addsuffix<__class__>] }}}>
<<tabs-tab-list>> <<tabs-tab-list>>
</div> </div>
<div class={{{ [[tc-tab-divider]addsuffix[ ]addsuffix<__class__>] }}}/> <div class={{{ [[tc-tab-divider]addsuffix[ ]addsuffix<__class__>] }}}/>
<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}} role="tabpanel"> <div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}}>
<<tabs-tab-body>> <<tabs-tab-body>>
</div> </div>
</div> </div>

View File

@@ -21,7 +21,7 @@ second-search-filter: [subfilter<tagListFilter>is[system]search:title<userInput>
<!-- clean up temporary tiddlers, so the next "pick" starts with a clean input --> <!-- clean up temporary tiddlers, so the next "pick" starts with a clean input -->
<!-- This could probably be optimized / removed if we would use different temp-tiddlers <!-- This could probably be optimized / removed if we would use different temp-tiddlers
(future improvement because keeping track is complex for humans) (future improvement because keeping track is comlex for humans)
--> -->
\procedure delete-tag-state-tiddlers() \procedure delete-tag-state-tiddlers()
<$action-deletetiddler $filter="[<newTagNameTiddler>] [<storeTitle>] [<tagSelectionState>]"/> <$action-deletetiddler $filter="[<newTagNameTiddler>] [<storeTitle>] [<tagSelectionState>]"/>
@@ -111,14 +111,13 @@ The second ESC tries to close the "draft tiddler"
refreshTitle=<<refreshTitle>> refreshTitle=<<refreshTitle>>
selectionStateTitle=<<tagSelectionState>> selectionStateTitle=<<tagSelectionState>>
inputAcceptActions=<<add-tag-actions>> inputAcceptActions=<<add-tag-actions>>
inputAcceptVariantActions=<<save-tiddler-actions>>
inputCancelActions=<<clear-tags-actions>> inputCancelActions=<<clear-tags-actions>>
tag="input" tag="input"
placeholder={{$:/language/EditTemplate/Tags/Add/Placeholder}} placeholder={{$:/language/EditTemplate/Tags/Add/Placeholder}}
focusPopup=<<tf.tagpicker-dropdown-id>> focusPopup=<<tf.tagpicker-dropdown-id>>
class="tc-edit-texteditor tc-popup-handle" class="tc-edit-texteditor tc-popup-handle"
tabindex=<<tabIndex>> tabindex=<<tabIndex>>
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[tags]then[true]] :else[{$:/config/AutoFocus}match[tags]then[true]] :else[[false]] }}} focus={{{ [{$:/config/AutoFocus}match[tags]then[true]] :else[[false]] }}}
filterMinLength={{$:/config/Tags/MinLength}} filterMinLength={{$:/config/Tags/MinLength}}
cancelPopups=<<cancelPopups>> cancelPopups=<<cancelPopups>>
configTiddlerFilter="[[$:/core/macros/tag-picker]]" configTiddlerFilter="[[$:/core/macros/tag-picker]]"

View File

@@ -15,18 +15,7 @@ tags: $:/tags/Macro
</span> </span>
\end \end
\define toc-level-indicator() \define toc-body(tag,sort:"",itemClassFilter,exclude,path)
\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 \whitespace trim
<ol class="tc-toc"> <ol class="tc-toc">
<$list filter="""[all[shadows+tiddlers]tag<__tag__>!has[draft.of]$sort$] -[<__tag__>] -[subfilter<__exclude__>]"""> <$list filter="""[all[shadows+tiddlers]tag<__tag__>!has[draft.of]$sort$] -[<__tag__>] -[subfilter<__exclude__>]""">
@@ -34,26 +23,10 @@ tags: $:/tags/Macro
<$set name="excluded" filter="[subfilter<__exclude__>] [<__tag__>]"> <$set name="excluded" filter="[subfilter<__exclude__>] [<__tag__>]">
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item"> <$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item">
<li class=<<toc-item-class>>> <li class=<<toc-item-class>>>
<$list filter="[all[current]toc-link[no]]" > <$list filter="[all[current]toc-link[no]]" emptyMessage="<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}><<toc-caption>></$link>">
<$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>> <<toc-caption>>
</$list> </$list>
<$let _level={{{ [<__level__>subtract[1]] }}}> <$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>>/>
<%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> </li>
</$set> </$set>
</$set> </$set>
@@ -62,10 +35,10 @@ tags: $:/tags/Macro
</ol> </ol>
\end \end
\define toc(tag,sort:"",itemClassFilter:"",exclude,level) \define toc(tag,sort:"",itemClassFilter:"", exclude)
\whitespace trim \whitespace trim
<$let __tag__={{{ [<__tag__>is[blank]then<currentTiddler>else<__tag__>] }}} > <$let __tag__={{{ [<__tag__>is[blank]then<currentTiddler>else<__tag__>] }}} >
<$macrocall $name="toc-body" tag=<<__tag__>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<__exclude__>> level=<<__level__>>/> <$macrocall $name="toc-body" tag=<<__tag__>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<__exclude__>>/>
</$let> </$let>
\end \end

View File

@@ -1,8 +0,0 @@
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 title: $:/tags/TiddlerInfo/Advanced
list: [[$:/core/ui/TiddlerInfo/Advanced/ShadowInfo]] [[$:/core/ui/TiddlerInfo/Advanced/PluginInfo]] [[$:/core/ui/TiddlerInfo/Advanced/CascadeInfo]] list: [[$:/core/ui/TiddlerInfo/Advanced/ShadowInfo]] [[$:/core/ui/TiddlerInfo/Advanced/PluginInfo]]

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,11 @@
created: 20150220191009000 created: 20150220191009000
modified: 20150602092431500 modified: 20150602092431500
title: $:/editions/tw5.com/railroad/call-parameter-value title: $:/editions/tw5.com/railroad/macro-parameter-value
type: text/vnd.tiddlywiki.railroad type: text/vnd.tiddlywiki.railroad
( '"""' [:{/'tout sauf """'/}] '"""' ( '"""' [:{/'tout sauf """'/}] '"""'
| '"' [:{/'tout sauf "'/}] '"' | '"' [:{/'tout sauf "'/}] '"'
| "'" [:{/"tout sauf '"/}] "'" | "'" [:{/"tout sauf '"/}] "'"
| "[[" [:{/"tout sauf ]"/}] "]]" | "[[" [:{/"tout sauf ]"/}] "]]"
| "`" [:{/"tout sauf `"/}] "`"
| "```" [:{/"tout sauf ```"/}] "```"
| {/"""tout sauf ' " ou espacevierge"""/} | {/"""tout sauf ' " ou espacevierge"""/}
) )

View File

@@ -25,4 +25,4 @@ The <<.place param-nom>> is a sequence of letters (`A`--`Z`, `a`--`z`), digits (
The <<.place valeur>> is specified as follows<<dp>> The <<.place valeur>> is specified as follows<<dp>>
<$railroad text={{$:/editions/tw5.com/railroad/call-parameter-value}}/> <$railroad text={{$:/editions/tw5.com/railroad/macro-parameter-value}}/>

View File

@@ -33,7 +33,7 @@ parametre.nom [: [:espace] ":" [:espace] defaut ]
La valeur par <<.place défaut>> d'un paramètre est spécifiée comme suit<<:>> La valeur par <<.place défaut>> d'un paramètre est spécifiée comme suit<<:>>
<$railroad text={{$:/editions/tw5.com/railroad/call-parameter-value}}/> <$railroad text={{$:/editions/tw5.com/railroad/macro-parameter-value}}/>
La définition de la <<.place suite>> se fait comme suit<<:>> La définition de la <<.place suite>> se fait comme suit<<:>>

View File

@@ -0,0 +1,17 @@
caption: savetiddlers
color: #4DB6AC
community-author: Buggyj
created: 20171109171935039
delivery: Browser Extension
description: Extension pour les navigateurs Chrome et Firefox
fr-title:
method: save
modified: 20220402105820520
tags: Chrome Firefox Saving [[Other Resources]] plugins
title: "savetiddlers" Extension for Chrome and Firefox by buggyj
type: text/vnd.tiddlywiki
url: https://github.com/buggyj/savetiddlers
Une extension pour Google Chrome et Mozilla Firefox qui fluidifie l'utilisation de [[l'enregistreur HTML5 par défaut|Saving with the HTML5 fallback saver]] de <<tw>>, et le rend presque aussi convivial que ~TiddlyFox une fois configurée.
https://github.com/buggyj/savetiddlers

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