mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-02-07 18:50:21 +00:00
Compare commits
101 Commits
fix-browse
...
background
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a83855329a | ||
|
|
364e2f2cb5 | ||
|
|
67f13c585d | ||
|
|
6e85d99ed5 | ||
|
|
5791627a50 | ||
|
|
32405c3a71 | ||
|
|
1bbb7fd53b | ||
|
|
599933c34d | ||
|
|
b1ccb82e0a | ||
|
|
ea648c7d15 | ||
|
|
a3a4e91751 | ||
|
|
c96d398712 | ||
|
|
821dcaf002 | ||
|
|
9247a87e11 | ||
|
|
bda54b0ad5 | ||
|
|
cd8b1faa74 | ||
|
|
0673426f5a | ||
|
|
d376ada241 | ||
|
|
33b2f514fb | ||
|
|
46fe3ca988 | ||
|
|
bf7c0b575c | ||
|
|
b236373064 | ||
|
|
d15398fc09 | ||
|
|
6bc77cf3e2 | ||
|
|
dc764b3a4a | ||
|
|
9c09841eda | ||
|
|
9d5be2e9f8 | ||
|
|
486d3bd326 | ||
|
|
196683915c | ||
|
|
42a3928960 | ||
|
|
2e76cc08a1 | ||
|
|
891e4fcb2b | ||
|
|
f6fd5ff261 | ||
|
|
526aaa3db8 | ||
|
|
455f1be3fb | ||
|
|
bffa0bb95a | ||
|
|
a1ef2ef6d4 | ||
|
|
75edd9b488 | ||
|
|
cde9c931c8 | ||
|
|
d07fe25cdb | ||
|
|
a40ce29451 | ||
|
|
75647eb623 | ||
|
|
70b4557738 | ||
|
|
efe58e41bc | ||
|
|
eb3a80968e | ||
|
|
62ae4b24bc | ||
|
|
ded76aa84f | ||
|
|
79e3d14698 | ||
|
|
763d717a13 | ||
|
|
e42ed6808e | ||
|
|
844564180f | ||
|
|
a670de0e95 | ||
|
|
faee49ee01 | ||
|
|
dd20be49f0 | ||
|
|
a27f74bbdc | ||
|
|
ae4e99951a | ||
|
|
be84dee26b | ||
|
|
00e17874f0 | ||
|
|
9041f099a3 | ||
|
|
3ba31be2a8 | ||
|
|
99d8afd515 | ||
|
|
2ab5f26644 | ||
|
|
419fe68ee2 | ||
|
|
0e765bdbdb | ||
|
|
855d8a9638 | ||
|
|
afcf108d29 | ||
|
|
8f9acc0ca2 | ||
|
|
5e4b8fbb3c | ||
|
|
8e301178a4 | ||
|
|
a72d3a09bf | ||
|
|
56634ffe29 | ||
|
|
24c317e1ab | ||
|
|
07329c6849 | ||
|
|
47ab3476f6 | ||
|
|
f0e64660f2 | ||
|
|
9663e65f4b | ||
|
|
7cb422242a | ||
|
|
f075f24e6b | ||
|
|
5fa1098c03 | ||
|
|
92dc927c7b | ||
|
|
98a61f01bb | ||
|
|
f3fa69e229 | ||
|
|
e2fb22ade0 | ||
|
|
7fb8560908 | ||
|
|
ff7814360e | ||
|
|
2d9303c6ff | ||
|
|
6aee5eb0c7 | ||
|
|
6eb881bffe | ||
|
|
2a5ce95d99 | ||
|
|
bd4fdd8f2e | ||
|
|
09379abd5d | ||
|
|
c6906120d8 | ||
|
|
c4c60933f4 | ||
|
|
a3979cda9c | ||
|
|
3c8ee86e23 | ||
|
|
921c0174fb | ||
|
|
4196d96adc | ||
|
|
743e99d12d | ||
|
|
6beeb23d10 | ||
|
|
838fad916d | ||
|
|
935e89bd93 |
62
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
62
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
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
68
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,68 +0,0 @@
|
||||
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.
|
||||
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -4,18 +4,23 @@ about: Suggest an idea for TiddlyWiki 5
|
||||
title: "[IDEA]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
type: feature
|
||||
type: idea
|
||||
|
||||
---
|
||||
|
||||
**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 [...]
|
||||
**Is your idea related to a problem? Please describe.**
|
||||
|
||||
A clear and concise description of what the problem is. Eg:
|
||||
As a user, I would like [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
@@ -120,7 +120,6 @@ node $TW5_BUILD_TIDDLYWIKI \
|
||||
|| exit 1
|
||||
|
||||
# /empty.html Empty
|
||||
# /empty.hta For Internet Explorer
|
||||
# /empty-external-core.html External core empty
|
||||
# /tiddlywikicore-<version>.js Core plugin javascript
|
||||
node $TW5_BUILD_TIDDLYWIKI \
|
||||
|
||||
23
boot/boot.js
23
boot/boot.js
@@ -316,8 +316,25 @@ $tw.utils.htmlDecode = function(s) {
|
||||
return s.toString().replace(/</mg,"<").replace(/ /mg,"\xA0").replace(/>/mg,">").replace(/"/mg,"\"").replace(/&/mg,"&");
|
||||
};
|
||||
|
||||
/** @deprecated Use window.location.hash instead. */
|
||||
$tw.utils.getLocationHash = () => window.location.hash;
|
||||
/*
|
||||
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 = 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 */
|
||||
$tw.utils.pad = function(value,length = 2) {
|
||||
@@ -596,7 +613,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
|
||||
// Compile the code into a function
|
||||
var fn;
|
||||
if($tw.browser) {
|
||||
fn = window["eval"](code + "\n\n//# sourceURL=" + filename); // eslint-disable-line no-eval -- See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
|
||||
fn = Function("return " + code + "\n\n//# sourceURL=" + filename)(); // See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
|
||||
} else {
|
||||
if(sandbox){
|
||||
fn = vm.runInContext(code,sandbox,filename)
|
||||
|
||||
@@ -33,8 +33,8 @@ exports.handler = function(request,response,state) {
|
||||
}
|
||||
var text = state.wiki.renderTiddler(renderType,renderTemplate,{parseAsInline: true, variables: {currentTiddler: title}});
|
||||
|
||||
// Naughty not to set a content-type, but it's the easiest way to ensure the browser will see HTML pages as HTML, and accept plain text tiddlers as CSS or JS
|
||||
state.sendResponse(200,{},text,"utf8");
|
||||
var headers = {"Content-Type": renderType};
|
||||
state.sendResponse(200,headers,text,"utf8");
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
|
||||
@@ -42,6 +42,8 @@ function Server(options) {
|
||||
}
|
||||
// Setup the default required plugins
|
||||
this.requiredPlugins = this.get("required-plugins").split(',');
|
||||
// Initialise CORS
|
||||
this.corsEnable = this.get("cors-enable") === "yes";
|
||||
// Initialise CSRF
|
||||
this.csrfDisable = this.get("csrf-disable") === "yes";
|
||||
// Initialize Gzip compression
|
||||
@@ -261,6 +263,13 @@ Server.prototype.requestHandler = function(request,response,options) {
|
||||
state.urlInfo = url.parse(request.url);
|
||||
state.queryParameters = querystring.parse(state.urlInfo.query);
|
||||
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);
|
||||
// Get the principals authorized to access this resource
|
||||
state.authorizationType = options.authorizationType || this.methodMappings[request.method] || "readers";
|
||||
@@ -285,6 +294,12 @@ Server.prototype.requestHandler = function(request,response,options) {
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
// Reply to OPTIONS
|
||||
if(this.corsEnable && request.method === "OPTIONS") {
|
||||
response.writeHead(204);
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
// Find the route that matches this path
|
||||
var route = self.findMatchingRoute(request,state);
|
||||
// Optionally output debug info
|
||||
|
||||
30
core-server/utils/base64.js
Normal file
30
core-server/utils/base64.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/*\
|
||||
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));
|
||||
95
core-server/utils/escapecss.js
Normal file
95
core-server/utils/escapecss.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/*\
|
||||
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: there’s 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 */
|
||||
})();
|
||||
@@ -5,3 +5,4 @@ TiddlyWiki incorporates code from these fine OpenSource projects:
|
||||
* [[The Stanford Javascript Crypto Library|http://bitwiseshiftleft.github.io/sjcl/]]
|
||||
* [[The Jasmine JavaScript Test Framework|https://jasmine.github.io/]]
|
||||
* [[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]]
|
||||
|
||||
@@ -6,6 +6,7 @@ Appearance/Caption: Appearance
|
||||
Appearance/Hint: Ways to customise the appearance of your TiddlyWiki.
|
||||
Basics/AnimDuration/Prompt: Animation duration
|
||||
Basics/AutoFocus/Prompt: Default focus field for new tiddlers
|
||||
Basics/AutoFocusEdit/Prompt: Default focus field for existing tiddlers
|
||||
Basics/Caption: Basics
|
||||
Basics/DefaultTiddlers/BottomHint: Use [[double square brackets]] for titles with spaces. Or you can choose to {{retain story ordering||$:/snippets/retain-story-ordering-button}}
|
||||
Basics/DefaultTiddlers/Prompt: Default tiddlers
|
||||
|
||||
4
core/language/en-GB/Draft.multids
Normal file
4
core/language/en-GB/Draft.multids
Normal file
@@ -0,0 +1,4 @@
|
||||
title: $:/language/Draft/
|
||||
|
||||
Attribution: Draft of '<<draft-title>>' by {{$:/status/UserName}}
|
||||
Title: Draft of '<<draft-title>>'
|
||||
@@ -1,5 +1,6 @@
|
||||
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:
|
||||
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.
|
||||
|
||||
@@ -9,6 +9,7 @@ Advanced/ShadowInfo/NotShadow/Hint: The tiddler <$link to=<<infoTiddler>>><$text
|
||||
Advanced/ShadowInfo/Shadow/Hint: The tiddler <$link to=<<infoTiddler>>><$text text=<<infoTiddler>>/></$link> is a shadow tiddler
|
||||
Advanced/ShadowInfo/Shadow/Source: It is defined in the plugin <$link to=<<pluginTiddler>>><$text text=<<pluginTiddler>>/></$link>
|
||||
Advanced/ShadowInfo/OverriddenShadow/Hint: It is overridden by an ordinary tiddler
|
||||
Advanced/CascadeInfo/Heading: Cascade Details
|
||||
Fields/Caption: Fields
|
||||
List/Caption: List
|
||||
List/Empty: This tiddler does not have a list
|
||||
|
||||
116
core/modules/background-actions.js
Normal file
116
core/modules/background-actions.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/*\
|
||||
title: $:/core/modules/background-actions.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
Class to dispatch actions when filters change
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
class BackgroundActionDispatcher {
|
||||
constructor(filterTracker, wiki) {
|
||||
this.filterTracker = filterTracker;
|
||||
this.wiki = wiki;
|
||||
this.nextTrackedFilterId = 1;
|
||||
this.trackedFilters = new Map(); // Use Map for better key management
|
||||
// Track the filter for the background actions
|
||||
this.filterTracker.track({
|
||||
filterString: "[all[tiddlers+shadows]tag[$:/tags/BackgroundAction]!is[draft]]",
|
||||
fnEnter: title => this.trackFilter(title),
|
||||
fnLeave: (title, enterValue) => this.untrackFilter(enterValue),
|
||||
fnChange: (title, enterValue) => {
|
||||
this.untrackFilter(enterValue);
|
||||
return this.trackFilter(title);
|
||||
},
|
||||
fnProcess: changes => this.process(changes)
|
||||
});
|
||||
}
|
||||
|
||||
trackFilter(title) {
|
||||
const tiddler = this.wiki.getTiddler(title);
|
||||
const id = this.nextTrackedFilterId++;
|
||||
const tracker = new BackgroundActionTracker({
|
||||
wiki: this.wiki,
|
||||
title,
|
||||
trackFilter: tiddler.fields["track-filter"],
|
||||
actions: tiddler.fields.text
|
||||
});
|
||||
this.trackedFilters.set(id, tracker);
|
||||
return id;
|
||||
}
|
||||
|
||||
untrackFilter(enterValue) {
|
||||
const tracker = this.trackedFilters.get(enterValue);
|
||||
if(tracker) {
|
||||
tracker.destroy();
|
||||
}
|
||||
this.trackedFilters.delete(enterValue);
|
||||
}
|
||||
|
||||
process(changes) {
|
||||
for(const tracker of this.trackedFilters.values()) {
|
||||
tracker.process(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Represents an individual tracked filter. Options include:
|
||||
wiki: wiki to use
|
||||
title: title of the tiddler being tracked
|
||||
trackFilter: filter string to track changes
|
||||
actions: actions to be executed when the filter changes
|
||||
*/
|
||||
class BackgroundActionTracker {
|
||||
constructor({wiki, title, trackFilter, actions}) {
|
||||
this.wiki = wiki;
|
||||
this.title = title;
|
||||
this.trackFilter = trackFilter;
|
||||
this.actions = actions;
|
||||
this.filterTracker = new $tw.FilterTracker(this.wiki);
|
||||
this.hasChanged = false;
|
||||
this.trackerID = this.filterTracker.track({
|
||||
filterString: this.trackFilter,
|
||||
fnEnter: () => { this.hasChanged = true; },
|
||||
fnLeave: () => { this.hasChanged = true; },
|
||||
fnProcess: changes => {
|
||||
if(this.hasChanged) {
|
||||
this.hasChanged = false;
|
||||
console.log("Processing background action", this.title);
|
||||
const tiddler = this.wiki.getTiddler(this.title);
|
||||
let doActions = true;
|
||||
if(tiddler && tiddler.fields.platforms) {
|
||||
doActions = false;
|
||||
const platforms = $tw.utils.parseStringArray(tiddler.fields.platforms);
|
||||
if(($tw.browser && platforms.includes("browser")) || ($tw.node && platforms.includes("node"))) {
|
||||
doActions = true;
|
||||
}
|
||||
}
|
||||
if(doActions) {
|
||||
this.wiki.invokeActionString(
|
||||
this.actions,
|
||||
null,
|
||||
{
|
||||
currentTiddler: this.title
|
||||
},{
|
||||
parentWidget: $tw.rootWidget
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
process(changes) {
|
||||
this.filterTracker.handleChangeEvent(changes);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.filterTracker.untrack(this.trackerID);
|
||||
}
|
||||
}
|
||||
|
||||
exports.BackgroundActionDispatcher = BackgroundActionDispatcher;
|
||||
@@ -48,8 +48,8 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
|
||||
this.toolbarNode = this.document.createElement("div");
|
||||
this.toolbarNode.className = "tc-editor-toolbar";
|
||||
parent.insertBefore(this.toolbarNode,nextSibling);
|
||||
this.renderChildren(this.toolbarNode,null);
|
||||
this.domNodes.push(this.toolbarNode);
|
||||
this.renderChildren(this.toolbarNode,null);
|
||||
}
|
||||
// Create our element
|
||||
var editInfo = this.getEditInfo(),
|
||||
|
||||
106
core/modules/filter-tracker.js
Normal file
106
core/modules/filter-tracker.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/*\
|
||||
title: $:/core/modules/filter-tracker.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
Class to track the results of a filter string
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
class FilterTracker {
|
||||
constructor(wiki) {
|
||||
this.wiki = wiki;
|
||||
this.trackers = new Map();
|
||||
this.nextTrackerId = 1;
|
||||
}
|
||||
|
||||
handleChangeEvent(changes) {
|
||||
this.processTrackers();
|
||||
this.processChanges(changes);
|
||||
}
|
||||
|
||||
/*
|
||||
Add a tracker to the filter tracker. Returns null if any of the parameters are invalid, or a tracker id if the tracker was added successfully. Options include:
|
||||
filterString: the filter string to track
|
||||
fnEnter: function to call when a title enters the filter results. Called even if the tiddler does not actually exist. Called as (title), and should return a truthy value that is stored in the tracker as the "enterValue"
|
||||
fnLeave: function to call when a title leaves the filter results. Called as (title,enterValue)
|
||||
fnChange: function to call when a tiddler changes in the filter results. Only called for filter results that identify a tiddler or shadow tiddler. Called as (title,enterValue), and may optionally return a replacement enterValue
|
||||
fnProcess: function to call each time the tracker is processed, after any enter, leave or change functions are called. Called as (changes)
|
||||
*/
|
||||
track(options = {}) {
|
||||
const {
|
||||
filterString,
|
||||
fnEnter,
|
||||
fnLeave,
|
||||
fnChange,
|
||||
fnProcess
|
||||
} = options;
|
||||
const id = this.nextTrackerId++;
|
||||
const tracker = {
|
||||
id,
|
||||
filterString,
|
||||
fnEnter,
|
||||
fnLeave,
|
||||
fnChange,
|
||||
fnProcess,
|
||||
previousResults: [],
|
||||
resultValues: {}
|
||||
};
|
||||
this.trackers.set(id, tracker);
|
||||
// Process the tracker
|
||||
this.processTracker(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
untrack(id) {
|
||||
this.trackers.delete(id);
|
||||
}
|
||||
|
||||
processTrackers() {
|
||||
for(const id of this.trackers.keys()) {
|
||||
this.processTracker(id);
|
||||
}
|
||||
}
|
||||
|
||||
processTracker(id) {
|
||||
const tracker = this.trackers.get(id);
|
||||
if(!tracker) return;
|
||||
const results = [];
|
||||
// Evaluate the filter and remove duplicate results
|
||||
$tw.utils.each(this.wiki.filterTiddlers(tracker.filterString), title => {
|
||||
$tw.utils.pushTop(results, title);
|
||||
});
|
||||
// Process the newly entered results
|
||||
results.forEach(title => {
|
||||
if(!tracker.previousResults.includes(title) && !tracker.resultValues[title] && tracker.fnEnter) {
|
||||
tracker.resultValues[title] = tracker.fnEnter(title) || true;
|
||||
}
|
||||
});
|
||||
// Process the results that have just left
|
||||
tracker.previousResults.forEach(title => {
|
||||
if(!results.includes(title) && tracker.resultValues[title] && tracker.fnLeave) {
|
||||
tracker.fnLeave(title, tracker.resultValues[title]);
|
||||
delete tracker.resultValues[title];
|
||||
}
|
||||
});
|
||||
// Update the previous results
|
||||
tracker.previousResults = results;
|
||||
}
|
||||
|
||||
processChanges(changes) {
|
||||
for(const tracker of this.trackers.values()) {
|
||||
Object.keys(changes).forEach(title => {
|
||||
if(title && tracker.previousResults.includes(title) && tracker.fnChange) {
|
||||
tracker.resultValues[title] = tracker.fnChange(title, tracker.resultValues[title]) || tracker.resultValues[title];
|
||||
}
|
||||
});
|
||||
if(tracker.fnProcess) {
|
||||
tracker.fnProcess(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.FilterTracker = FilterTracker;
|
||||
@@ -88,8 +88,8 @@ function parseFilterOperation(operators,filterString,p) {
|
||||
rexMatch = rex.exec(filterString.substring(p));
|
||||
if(rexMatch) {
|
||||
operator.regexp = new RegExp(rexMatch[1], rexMatch[2]);
|
||||
// DEPRECATION WARNING
|
||||
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
|
||||
// DEPRECATION WARNING
|
||||
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
|
||||
nextBracketPos = p + rex.lastIndex - 1;
|
||||
}
|
||||
else {
|
||||
@@ -232,12 +232,7 @@ exports.getFilterRunPrefixes = function() {
|
||||
|
||||
exports.filterTiddlers = function(filterString,widget,source) {
|
||||
var fn = this.compileFilter(filterString);
|
||||
try {
|
||||
const fnResult = fn.call(this,source,widget);
|
||||
return fnResult;
|
||||
} catch(e) {
|
||||
return [`${$tw.language.getString("Error/Filter")}: ${e}`];
|
||||
}
|
||||
return fn.call(this,source,widget);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -319,19 +314,19 @@ exports.compileFilter = function(filterString) {
|
||||
|
||||
// Invoke the appropriate filteroperator module
|
||||
results = operatorFunction(accumulator,{
|
||||
operator: operator.operator,
|
||||
operand: operands.length > 0 ? operands[0] : undefined,
|
||||
operands: operands,
|
||||
multiValueOperands: multiValueOperands,
|
||||
isMultiValueOperand: isMultiValueOperand,
|
||||
prefix: operator.prefix,
|
||||
suffix: operator.suffix,
|
||||
suffixes: operator.suffixes,
|
||||
regexp: operator.regexp
|
||||
},{
|
||||
wiki: self,
|
||||
widget: widget
|
||||
});
|
||||
operator: operator.operator,
|
||||
operand: operands.length > 0 ? operands[0] : undefined,
|
||||
operands: operands,
|
||||
multiValueOperands: multiValueOperands,
|
||||
isMultiValueOperand: isMultiValueOperand,
|
||||
prefix: operator.prefix,
|
||||
suffix: operator.suffix,
|
||||
suffixes: operator.suffixes,
|
||||
regexp: operator.regexp
|
||||
},{
|
||||
wiki: self,
|
||||
widget: widget
|
||||
});
|
||||
if($tw.utils.isArray(results)) {
|
||||
accumulator = self.makeTiddlerIterator(results);
|
||||
} else {
|
||||
|
||||
@@ -14,31 +14,31 @@ Export our filter function
|
||||
*/
|
||||
exports.sort = function(source,operator,options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,false);
|
||||
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,false,undefined,operator.operands[1]);
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.nsort = function(source,operator,options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,true);
|
||||
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,true,undefined,operator.operands[1]);
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.sortan = function(source, operator, options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results, operator.operand || "title", operator.prefix === "!",false,false,true);
|
||||
options.wiki.sortTiddlers(results, operator.operands[0] || "title", operator.prefix === "!",false,false,true,operator.operands[1]);
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.sortcs = function(source,operator,options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,false);
|
||||
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,false,undefined,operator.operands[1]);
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.nsortcs = function(source,operator,options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,true);
|
||||
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,true,undefined,operator.operands[1]);
|
||||
return results;
|
||||
};
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@ exports.trim = function(source,operator,options) {
|
||||
operand = (operator.operand || ""),
|
||||
fnCalc;
|
||||
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") {
|
||||
fnCalc = function(a,b) {return [$tw.utils.trimSuffix(a,b)];}
|
||||
fnCalc = function(a,b) {return [$tw.utils.trimSuffix(a,b)];};
|
||||
} else {
|
||||
if(operand === "") {
|
||||
fnCalc = function(a) {return [$tw.utils.trim(a)];}
|
||||
fnCalc = function(a) {return [$tw.utils.trim(a)];};
|
||||
} 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) {
|
||||
@@ -71,107 +71,53 @@ exports.join = makeStringReducingOperator(
|
||||
},null
|
||||
);
|
||||
|
||||
var dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
|
||||
const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
|
||||
|
||||
exports.levenshtein = makeStringBinaryOperator(
|
||||
function(a,b) {
|
||||
var dmpObject = new dmp.diff_match_patch(),
|
||||
diffs = dmpObject.diff_main(a,b);
|
||||
return [dmpObject.diff_levenshtein(diffs) + ""];
|
||||
const diffs = dmp.diffMain(a,b);
|
||||
return [dmp.diffLevenshtein(diffs).toString()];
|
||||
}
|
||||
);
|
||||
|
||||
// these two functions are adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
|
||||
// this function is adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
|
||||
function diffLineWordMode(text1,text2,mode) {
|
||||
var dmpObject = new dmp.diff_match_patch();
|
||||
var a = diffPartsToChars(text1,text2,mode);
|
||||
var a = $tw.utils.diffPartsToChars(text1,text2,mode);
|
||||
var lineText1 = a.chars1;
|
||||
var lineText2 = a.chars2;
|
||||
var lineArray = a.lineArray;
|
||||
var diffs = dmpObject.diff_main(lineText1,lineText2,false);
|
||||
dmpObject.diff_charsToLines_(diffs,lineArray);
|
||||
var diffs = dmp.diffMain(lineText1,lineText2,false);
|
||||
dmp.diffCharsToLines(diffs,lineArray);
|
||||
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) {
|
||||
var dmpObject = new dmp.diff_match_patch(),
|
||||
suffix = operator.suffix || "",
|
||||
var suffix = operator.suffix || "",
|
||||
result = [];
|
||||
|
||||
source(function(tiddler,title) {
|
||||
var diffs, patches;
|
||||
if(suffix === "lines" || suffix === "words") {
|
||||
diffs = diffLineWordMode(title,operator.operand,suffix);
|
||||
patches = dmpObject.patch_make(title,diffs);
|
||||
} else {
|
||||
patches = dmpObject.patch_make(title,operator.operand);
|
||||
}
|
||||
Array.prototype.push.apply(result,[dmpObject.patch_toText(patches)]);
|
||||
});
|
||||
source(function(tiddler,title) {
|
||||
let diffs, patches;
|
||||
if(suffix === "lines" || suffix === "words") {
|
||||
diffs = diffLineWordMode(title,operator.operand,suffix);
|
||||
patches = dmp.patchMake(title,diffs);
|
||||
} else {
|
||||
patches = dmp.patchMake(title,operator.operand);
|
||||
}
|
||||
Array.prototype.push.apply(result,[dmp.patchToText(patches)]);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
exports.applypatches = makeStringBinaryOperator(
|
||||
function(a,b) {
|
||||
var dmpObject = new dmp.diff_match_patch(),
|
||||
patches;
|
||||
let patches;
|
||||
try {
|
||||
patches = dmpObject.patch_fromText(b);
|
||||
patches = dmp.patchFromText(b);
|
||||
} catch(e) {
|
||||
}
|
||||
if(patches) {
|
||||
return [dmpObject.patch_apply(patches,a)[0]];
|
||||
return [dmp.patchApply(patches,a)[0]];
|
||||
} else {
|
||||
return [a];
|
||||
}
|
||||
@@ -279,7 +225,7 @@ exports.pad = function(source,operator,options) {
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
};
|
||||
|
||||
exports.charcode = function(source,operator,options) {
|
||||
var chars = [];
|
||||
|
||||
67
core/modules/info/mediaquerytracker.js
Normal file
67
core/modules/info/mediaquerytracker.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*\
|
||||
title: $:/core/modules/info/mediaquerytracker.js
|
||||
type: application/javascript
|
||||
module-type: info
|
||||
|
||||
Initialise $:/info/ tiddlers derived from media queries via
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
|
||||
if($tw.browser) {
|
||||
// Functions to start and stop tracking a particular media query tracker tiddler
|
||||
function track(title) {
|
||||
var result = {},
|
||||
tiddler = $tw.wiki.getTiddler(title);
|
||||
if(tiddler) {
|
||||
var mediaQuery = tiddler.fields["media-query"],
|
||||
infoTiddler = tiddler.fields["info-tiddler"],
|
||||
infoTiddlerAlt = tiddler.fields["info-tiddler-alt"];
|
||||
if(mediaQuery && infoTiddler) {
|
||||
// Evaluate and track the media query
|
||||
result.mqList = window.matchMedia(mediaQuery);
|
||||
function getResultTiddlers() {
|
||||
var value = result.mqList.matches ? "yes" : "no",
|
||||
tiddlers = [];
|
||||
tiddlers.push({title: infoTiddler, text: value});
|
||||
if(infoTiddlerAlt) {
|
||||
tiddlers.push({title: infoTiddlerAlt, text: value});
|
||||
}
|
||||
return tiddlers;
|
||||
};
|
||||
updateInfoTiddlersCallback(getResultTiddlers());
|
||||
result.handler = function(event) {
|
||||
updateInfoTiddlersCallback(getResultTiddlers());
|
||||
};
|
||||
result.mqList.addEventListener("change",result.handler);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function untrack(enterValue) {
|
||||
if(enterValue.mqList && enterValue.handler) {
|
||||
enterValue.mqList.removeEventListener("change",enterValue.handler);
|
||||
}
|
||||
}
|
||||
// Track media query tracker tiddlers
|
||||
function fnEnter(title) {
|
||||
return track(title);
|
||||
}
|
||||
function fnLeave(title,enterValue) {
|
||||
untrack(enterValue);
|
||||
}
|
||||
function fnChange(title,enterValue) {
|
||||
untrack(enterValue);
|
||||
return track(title);
|
||||
}
|
||||
$tw.filterTracker.track({
|
||||
filterString: "[all[tiddlers+shadows]tag[$:/tags/MediaQueryTracker]!is[draft]]",
|
||||
fnEnter: fnEnter,
|
||||
fnLeave: fnLeave,
|
||||
fnChange: fnChange
|
||||
});
|
||||
}
|
||||
return [];
|
||||
};
|
||||
@@ -33,13 +33,6 @@ exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
|
||||
// Screen size
|
||||
infoTiddlerFields.push({title: "$:/info/browser/screen/width", text: window.screen.width.toString()});
|
||||
infoTiddlerFields.push({title: "$:/info/browser/screen/height", text: window.screen.height.toString()});
|
||||
// Dark mode through event listener on MediaQueryList
|
||||
var mqList = window.matchMedia("(prefers-color-scheme: dark)"),
|
||||
getDarkModeTiddler = function() {return {title: "$:/info/darkmode", text: mqList.matches ? "yes" : "no"};};
|
||||
infoTiddlerFields.push(getDarkModeTiddler());
|
||||
mqList.addListener(function(event) {
|
||||
updateInfoTiddlersCallback([getDarkModeTiddler()]);
|
||||
});
|
||||
// Language
|
||||
infoTiddlerFields.push({title: "$:/info/browser/language", text: navigator.language || ""});
|
||||
}
|
||||
|
||||
@@ -11,17 +11,16 @@ The image parser parses an image into an embeddable HTML element
|
||||
|
||||
var ImageParser = function(type,text,options) {
|
||||
var element = {
|
||||
type: "element",
|
||||
tag: "img",
|
||||
attributes: {}
|
||||
};
|
||||
type: "image",
|
||||
attributes: {}
|
||||
};
|
||||
if(options._canonical_uri) {
|
||||
element.attributes.src = {type: "string", value: options._canonical_uri};
|
||||
element.attributes.source = {type: "string", value: options._canonical_uri};
|
||||
} else if(text) {
|
||||
if(type === "image/svg+xml" || type === ".svg") {
|
||||
element.attributes.src = {type: "string", value: "data:image/svg+xml," + encodeURIComponent(text)};
|
||||
element.attributes.source = {type: "string", value: "data:image/svg+xml," + encodeURIComponent(text)};
|
||||
} else {
|
||||
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
|
||||
element.attributes.source = {type: "string", value: "data:" + type + ";base64," + text};
|
||||
}
|
||||
}
|
||||
this.tree = [element];
|
||||
|
||||
@@ -107,13 +107,14 @@ exports.parseStringLiteral = function(source,pos) {
|
||||
type: "string",
|
||||
start: pos
|
||||
};
|
||||
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')/g;
|
||||
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')|\[\[((?:[^\]]|\](?!\]))*)\]\]/g;
|
||||
reString.lastIndex = pos;
|
||||
var match = reString.exec(source);
|
||||
if(match && match.index === pos) {
|
||||
node.value = match[1] !== undefined ? match[1] :(
|
||||
match[2] !== undefined ? match[2] : match[3]
|
||||
);
|
||||
match[2] !== undefined ? match[2] : (
|
||||
match[3] !== undefined ? match[3] : match[4]
|
||||
));
|
||||
node.end = pos + match[0].length;
|
||||
return node;
|
||||
} else {
|
||||
@@ -173,7 +174,7 @@ exports.parseMacroParameter = function(source,pos) {
|
||||
start: pos
|
||||
};
|
||||
// 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
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Look for the parameter
|
||||
@@ -206,28 +207,152 @@ exports.parseMacroParameter = function(source,pos) {
|
||||
Look for a macro invocation. Returns null if not found, or {type: "transclude", attributes:, start:, end:}
|
||||
*/
|
||||
exports.parseMacroInvocationAsTransclusion = function(source,pos) {
|
||||
var node = $tw.utils.parseMacroInvocation(source,pos);
|
||||
if(node) {
|
||||
var positionalName = 0,
|
||||
transclusion = {
|
||||
type: "transclude",
|
||||
start: node.start,
|
||||
end: node.end
|
||||
};
|
||||
$tw.utils.addAttributeToParseTreeNode(transclusion,"$variable",node.name);
|
||||
$tw.utils.each(node.params,function(param) {
|
||||
var name = param.name;
|
||||
if(name) {
|
||||
if(name.charAt(0) === "$") {
|
||||
name = "$" + name;
|
||||
}
|
||||
$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;
|
||||
var node = {
|
||||
type: "transclude",
|
||||
start: pos,
|
||||
attributes: {},
|
||||
orderedAttributes: []
|
||||
};
|
||||
// Define our regexps
|
||||
var reVarName = /([^\s>"'=:]+)/g;
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Look for a double opening angle bracket
|
||||
var token = $tw.utils.parseTokenString(source,pos,"<<");
|
||||
if(!token) {
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
/*
|
||||
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 unquoted value
|
||||
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
|
||||
if(unquotedValue) {
|
||||
pos = unquotedValue.end;
|
||||
node.type = "string";
|
||||
node.value = unquotedValue.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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bail if we don't have a value
|
||||
if(!node.type) {
|
||||
return null;
|
||||
}
|
||||
// Update the end position
|
||||
node.end = pos;
|
||||
return node;
|
||||
};
|
||||
|
||||
@@ -296,7 +421,7 @@ exports.parseFilterVariable = function(source) {
|
||||
};
|
||||
|
||||
/*
|
||||
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:,}
|
||||
Look for an HTML attribute definition. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}
|
||||
*/
|
||||
exports.parseAttribute = function(source,pos) {
|
||||
var node = {
|
||||
@@ -354,7 +479,7 @@ exports.parseAttribute = function(source,pos) {
|
||||
node.value = unquotedValue.match[1];
|
||||
} else {
|
||||
// Look for a macro invocation value
|
||||
var macroInvocation = $tw.utils.parseMacroInvocation(source,pos);
|
||||
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
|
||||
if(macroInvocation) {
|
||||
pos = macroInvocation.end;
|
||||
node.type = "macro";
|
||||
@@ -375,6 +500,7 @@ exports.parseAttribute = function(source,pos) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If there is no equals sign or colon, then this is an attribute with no value, defaulting to "true"
|
||||
node.type = "string";
|
||||
node.value = "true";
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ exports.parse = function() {
|
||||
var paramString = this.match[2],
|
||||
params = [];
|
||||
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);
|
||||
while(paramMatch) {
|
||||
// Save the parameter details
|
||||
|
||||
@@ -23,27 +23,6 @@ exports.init = function(parser) {
|
||||
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg;
|
||||
};
|
||||
|
||||
/*
|
||||
Reject the match if we don't have a template or text reference
|
||||
*/
|
||||
exports.findNextMatch = function(startPos) {
|
||||
this.matchRegExp.lastIndex = startPos;
|
||||
this.match = this.matchRegExp.exec(this.parser.source);
|
||||
if(this.match) {
|
||||
var template = $tw.utils.trim(this.match[2]),
|
||||
textRef = $tw.utils.trim(this.match[1]);
|
||||
// Bail if we don't have a template or text reference
|
||||
if(!template && !textRef) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this.match.index;
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return this.match ? this.match.index : undefined;
|
||||
};
|
||||
|
||||
exports.parse = function() {
|
||||
// Move past the match
|
||||
this.parser.pos = this.matchRegExp.lastIndex;
|
||||
|
||||
@@ -23,27 +23,6 @@ exports.init = function(parser) {
|
||||
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}/mg;
|
||||
};
|
||||
|
||||
/*
|
||||
Reject the match if we don't have a template or text reference
|
||||
*/
|
||||
exports.findNextMatch = function(startPos) {
|
||||
this.matchRegExp.lastIndex = startPos;
|
||||
this.match = this.matchRegExp.exec(this.parser.source);
|
||||
if(this.match) {
|
||||
var template = $tw.utils.trim(this.match[2]),
|
||||
textRef = $tw.utils.trim(this.match[1]);
|
||||
// Bail if we don't have a template or text reference
|
||||
if(!template && !textRef) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this.match.index;
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return this.match ? this.match.index : undefined;
|
||||
};
|
||||
|
||||
exports.parse = function() {
|
||||
// Move past the match
|
||||
this.parser.pos = this.matchRegExp.lastIndex;
|
||||
|
||||
@@ -6,10 +6,7 @@ module-type: saver
|
||||
Handles saving changes via window.postMessage() to the window.parent
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
@@ -63,4 +60,3 @@ exports.create = function(wiki) {
|
||||
return new PostMessageSaver(wiki);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -13,6 +13,11 @@ Load core modules
|
||||
exports.name = "load-modules";
|
||||
exports.synchronous = true;
|
||||
|
||||
// Set to `true` to enable performance instrumentation
|
||||
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";
|
||||
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
|
||||
exports.startup = function() {
|
||||
// Load modules
|
||||
$tw.modules.applyMethods("utils",$tw.utils);
|
||||
@@ -31,6 +36,27 @@ exports.startup = function() {
|
||||
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
|
||||
$tw.macros = $tw.modules.getModulesByTypeAsHashmap("macro");
|
||||
$tw.wiki.initParsers();
|
||||
// --------------------------
|
||||
// The rest of the startup process here is not strictly to do with loading modules, but are needed before other startup
|
||||
// modules are executed. It is easier to put them here than to introduce a new startup module
|
||||
// --------------------------
|
||||
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
|
||||
$tw.rootWidget = new widget.widget({
|
||||
type: "widget",
|
||||
children: []
|
||||
},{
|
||||
wiki: $tw.wiki,
|
||||
document: $tw.browser ? document : $tw.fakeDocument
|
||||
});
|
||||
// Set up the performance framework
|
||||
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
|
||||
// Kick off the filter tracker
|
||||
$tw.filterTracker = new $tw.FilterTracker($tw.wiki);
|
||||
$tw.wiki.addEventListener("change",function(changes) {
|
||||
$tw.filterTracker.handleChangeEvent(changes);
|
||||
});
|
||||
// Kick off the background action dispatcher
|
||||
$tw.backgroundActionDispatcher = new $tw.BackgroundActionDispatcher($tw.filterTracker,$tw.wiki);
|
||||
if($tw.node) {
|
||||
$tw.Commander.initCommands();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ exports.synchronous = true;
|
||||
// Default story and history lists
|
||||
var PAGE_TITLE_TITLE = "$:/core/wiki/title";
|
||||
var PAGE_STYLESHEET_TITLE = "$:/core/ui/PageStylesheet";
|
||||
var ROOT_STYLESHEET_TITLE = "$:/core/ui/RootStylesheet";
|
||||
var PAGE_TEMPLATE_TITLE = "$:/core/ui/RootTemplate";
|
||||
|
||||
// Time (in ms) that we defer refreshing changes to draft tiddlers
|
||||
@@ -45,13 +44,22 @@ exports.startup = function() {
|
||||
publishTitle();
|
||||
}
|
||||
});
|
||||
|
||||
var styleParser = $tw.wiki.parseTiddler(ROOT_STYLESHEET_TITLE,{parseAsInline: true}),
|
||||
styleWidgetNode = $tw.wiki.makeWidget(styleParser,{document: document});
|
||||
styleWidgetNode.render(document.head,null);
|
||||
|
||||
// Set up the styles
|
||||
$tw.styleWidgetNode = $tw.wiki.makeTranscludeWidget(PAGE_STYLESHEET_TITLE,{document: $tw.fakeDocument});
|
||||
$tw.styleContainer = $tw.fakeDocument.createElement("style");
|
||||
$tw.styleWidgetNode.render($tw.styleContainer,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) {
|
||||
styleWidgetNode.refresh(changes,document.head,null);
|
||||
if($tw.styleWidgetNode.refresh(changes,$tw.styleContainer,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
|
||||
$tw.perf.report("mainRender",function() {
|
||||
@@ -60,7 +68,7 @@ exports.startup = function() {
|
||||
$tw.utils.addClass($tw.pageContainer,"tc-page-container-wrapper");
|
||||
document.body.insertBefore($tw.pageContainer,document.body.firstChild);
|
||||
$tw.pageWidgetNode.render($tw.pageContainer,null);
|
||||
$tw.hooks.invokeHook("th-page-refreshed");
|
||||
$tw.hooks.invokeHook("th-page-refreshed");
|
||||
})();
|
||||
// Remove any splash screen elements
|
||||
var removeList = document.querySelectorAll(".tc-remove-when-wiki-loaded");
|
||||
|
||||
@@ -14,11 +14,6 @@ exports.name = "startup";
|
||||
exports.after = ["load-modules"];
|
||||
exports.synchronous = true;
|
||||
|
||||
// Set to `true` to enable performance instrumentation
|
||||
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";
|
||||
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
|
||||
exports.startup = function() {
|
||||
// Minimal browser detection
|
||||
if($tw.browser) {
|
||||
@@ -54,16 +49,6 @@ exports.startup = function() {
|
||||
}
|
||||
// Initialise version
|
||||
$tw.version = $tw.utils.extractVersionInfo();
|
||||
// Set up the performance framework
|
||||
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
|
||||
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
|
||||
$tw.rootWidget = new widget.widget({
|
||||
type: "widget",
|
||||
children: []
|
||||
},{
|
||||
wiki: $tw.wiki,
|
||||
document: $tw.browser ? document : $tw.fakeDocument
|
||||
});
|
||||
// Kick off the language manager and switcher
|
||||
$tw.language = new $tw.Language();
|
||||
$tw.languageSwitcher = new $tw.PluginSwitcher({
|
||||
|
||||
@@ -63,16 +63,24 @@ exports.startup = function() {
|
||||
$tw.eventBus.emit("window:closed",{windowID});
|
||||
},false);
|
||||
// Set up the styles
|
||||
var styleParser = $tw.wiki.parseTiddler("$:/core/ui/RootStylesheet",{parseAsInline: true}),
|
||||
styleWidgetNode = $tw.wiki.makeWidget(styleParser,{document: srcDocument});
|
||||
styleWidgetNode.render(srcDocument.head,null);
|
||||
var styleWidgetNode = $tw.wiki.makeTranscludeWidget("$:/core/ui/PageStylesheet",{
|
||||
document: $tw.fakeDocument,
|
||||
variables: variables,
|
||||
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
|
||||
var parser = $tw.wiki.parseTiddler(template),
|
||||
widgetNode = $tw.wiki.makeWidget(parser,{document: srcDocument, parentWidget: $tw.rootWidget, variables: variables});
|
||||
widgetNode.render(srcDocument.body,srcDocument.body.firstChild);
|
||||
// Function to handle refreshes
|
||||
refreshHandler = function(changes) {
|
||||
styleWidgetNode.refresh(changes);
|
||||
if(styleWidgetNode.refresh(changes,styleContainer,null)) {
|
||||
styleElement.innerHTML = styleContainer.textContent;
|
||||
}
|
||||
widgetNode.refresh(changes);
|
||||
};
|
||||
$tw.wiki.addEventListener("change",refreshHandler);
|
||||
|
||||
31
core/modules/utils/base64.js
Normal file
31
core/modules/utils/base64.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*\
|
||||
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));
|
||||
@@ -44,15 +44,15 @@ exports.domMatchesSelector = (node,selector) => node.matches(selector);
|
||||
exports.hasClass = (el,className) => el.classList && el.classList.contains(className);
|
||||
|
||||
exports.addClass = function(el,className) {
|
||||
el.classList && el.classList.add(className);
|
||||
el.classList && className && el.classList.add(className);
|
||||
};
|
||||
|
||||
exports.removeClass = function(el,className) {
|
||||
el.classList && el.classList.remove(className);
|
||||
el.classList && className && el.classList.remove(className);
|
||||
};
|
||||
|
||||
exports.toggleClass = function(el,className,status) {
|
||||
el.classList && el.classList.toggle(className, status);
|
||||
el.classList && className && el.classList.toggle(className, status);
|
||||
};
|
||||
|
||||
exports.getLocationPath = () => window.location.origin + window.location.pathname;
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ module-type: utils
|
||||
Custom errors for TiddlyWiki.
|
||||
|
||||
\*/
|
||||
|
||||
function TranscludeRecursionError() {
|
||||
Error.apply(this,arguments);
|
||||
this.signatures = Object.create(null);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*\
|
||||
title: $:/core/modules/utils/escapecss.js
|
||||
type: application/javascript
|
||||
module-type: utils
|
||||
module-type: utils-browser
|
||||
|
||||
Provides CSS.escape() functionality.
|
||||
|
||||
@@ -9,92 +9,6 @@ Provides CSS.escape() functionality.
|
||||
|
||||
"use strict";
|
||||
|
||||
// TODO -- resolve this construction
|
||||
exports.escapeCSS = (function() {
|
||||
// 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: there’s 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;
|
||||
};
|
||||
return window.CSS.escape;
|
||||
})();
|
||||
|
||||
@@ -37,6 +37,7 @@ Object.defineProperty(TW_Node.prototype, 'TEXT_NODE', {
|
||||
var TW_TextNode = function(text) {
|
||||
bumpSequenceNumber(this);
|
||||
this.textContent = text + "";
|
||||
this.children = [];
|
||||
};
|
||||
|
||||
Object.setPrototypeOf(TW_TextNode.prototype,TW_Node.prototype);
|
||||
|
||||
@@ -8,10 +8,7 @@ Messaging utilities for use with window.postMessage() etc.
|
||||
This module intentionally has no dependencies so that it can be included in non-TiddlyWiki projects
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var RESPONSE_TIMEOUT = 2 * 1000;
|
||||
@@ -122,5 +119,3 @@ BrowserMessagingPublisher.prototype.close = function() {
|
||||
};
|
||||
|
||||
exports.BrowserMessagingPublisher = BrowserMessagingPublisher;
|
||||
|
||||
})();
|
||||
|
||||
@@ -55,7 +55,7 @@ Return the dflt (default) parameter if str is not a base-10 number.
|
||||
exports.getInt = function(str,deflt) {
|
||||
var i = parseInt(str,10);
|
||||
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
|
||||
@@ -69,12 +69,12 @@ exports.replaceString = function(text,search,replace) {
|
||||
exports.trimPrefix = function(str,unwanted) {
|
||||
if(typeof str === "string" && typeof unwanted === "string") {
|
||||
if(unwanted === "") {
|
||||
return str.replace(/^\s\s*/, '');
|
||||
return str.replace(/^\s\s*/, "");
|
||||
} else {
|
||||
// Safely regexp-escape the unwanted text
|
||||
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
var regex = new RegExp('^(' + unwanted + ')+');
|
||||
return str.replace(regex, '');
|
||||
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
var regex = new RegExp("^(" + unwanted + ")+");
|
||||
return str.replace(regex, "");
|
||||
}
|
||||
} else {
|
||||
return str;
|
||||
@@ -84,12 +84,12 @@ exports.trimPrefix = function(str,unwanted) {
|
||||
exports.trimSuffix = function(str,unwanted) {
|
||||
if(typeof str === "string" && typeof unwanted === "string") {
|
||||
if(unwanted === "") {
|
||||
return str.replace(/\s\s*$/, '');
|
||||
return str.replace(/\s\s*$/, "");
|
||||
} else {
|
||||
// Safely regexp-escape the unwanted text
|
||||
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
var regex = new RegExp('(' + unwanted + ')+$');
|
||||
return str.replace(regex, '');
|
||||
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
var regex = new RegExp("(" + unwanted + ")+$");
|
||||
return str.replace(regex, "");
|
||||
}
|
||||
} else {
|
||||
return str;
|
||||
@@ -101,14 +101,14 @@ Convert a string to sentence case (ie capitalise first letter)
|
||||
*/
|
||||
exports.toSentenceCase = function(str) {
|
||||
return (str || "").replace(/^\S/, function(c) {return c.toUpperCase();});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Convert a string to title case (ie capitalise each initial letter)
|
||||
*/
|
||||
exports.toTitleCase = function(str) {
|
||||
return (str || "").replace(/(^|\s)\S/g, function(c) {return c.toUpperCase();});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Find the line break preceding a given position in a string
|
||||
@@ -242,6 +242,53 @@ exports.slowInSlowOut = function(t) {
|
||||
return (1 - ((Math.cos(t * Math.PI) + 1) / 2));
|
||||
};
|
||||
|
||||
exports.copyObjectPropertiesSafe = function(object) {
|
||||
const seen = new Set(),
|
||||
isDOMElement = value => value instanceof Node || value instanceof Window;
|
||||
|
||||
function safeCopy(obj) {
|
||||
// skip circular references
|
||||
if(seen.has(obj)) {
|
||||
return undefined;
|
||||
}
|
||||
// primitives and null are safe
|
||||
if(typeof obj !== "object" || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
// skip DOM elements
|
||||
if(isDOMElement(obj)) {
|
||||
return undefined;
|
||||
}
|
||||
// copy arrays, preserving positions
|
||||
if(Array.isArray(obj)) {
|
||||
return obj.map(item => {
|
||||
const value = safeCopy(item);
|
||||
return value === undefined ? null : value;
|
||||
});
|
||||
}
|
||||
|
||||
seen.add(obj);
|
||||
const copy = {};
|
||||
let key,
|
||||
value;
|
||||
for(key in obj) {
|
||||
try {
|
||||
value = safeCopy(obj[key]);
|
||||
if(value !== undefined) {
|
||||
copy[key] = value;
|
||||
}
|
||||
} catch(e) {
|
||||
// silently skip unserializable properties
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
const result = safeCopy(object);
|
||||
seen.clear();
|
||||
return result;
|
||||
};
|
||||
|
||||
exports.formatTitleString = function(template,options) {
|
||||
var base = options.base || "",
|
||||
separator = options.separator || "",
|
||||
@@ -358,8 +405,8 @@ exports.formatDateString = function(date,template) {
|
||||
}],
|
||||
[/^TZD/, function() {
|
||||
var tz = date.getTimezoneOffset(),
|
||||
atz = Math.abs(tz);
|
||||
return (tz < 0 ? '+' : '-') + $tw.utils.pad(Math.floor(atz / 60)) + ':' + $tw.utils.pad(atz % 60);
|
||||
atz = Math.abs(tz);
|
||||
return (tz < 0 ? "+" : "-") + $tw.utils.pad(Math.floor(atz / 60)) + ":" + $tw.utils.pad(atz % 60);
|
||||
}],
|
||||
[/^wYY/, function() {
|
||||
return $tw.utils.pad($tw.utils.getYearForWeekNo(date) - 2000);
|
||||
@@ -568,9 +615,9 @@ exports.unescapeLineBreaks = function(s) {
|
||||
exports.escape = function(ch) {
|
||||
var charCode = ch.charCodeAt(0);
|
||||
if(charCode <= 0xFF) {
|
||||
return '\\x' + $tw.utils.pad(charCode.toString(16).toUpperCase());
|
||||
return "\\x" + $tw.utils.pad(charCode.toString(16).toUpperCase());
|
||||
} else {
|
||||
return '\\u' + $tw.utils.pad(charCode.toString(16).toUpperCase(),4);
|
||||
return "\\u" + $tw.utils.pad(charCode.toString(16).toUpperCase(),4);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -587,11 +634,11 @@ exports.stringify = function(s, rawUnicode) {
|
||||
*/
|
||||
var regex = rawUnicode ? /[\x00-\x1f]/g : /[\x00-\x1f\x80-\uFFFF]/g;
|
||||
return (s || "")
|
||||
.replace(/\\/g, '\\\\') // backslash
|
||||
.replace(/\\/g, "\\\\") // backslash
|
||||
.replace(/"/g, '\\"') // double quote character
|
||||
.replace(/'/g, "\\'") // single quote character
|
||||
.replace(/\r/g, '\\r') // carriage return
|
||||
.replace(/\n/g, '\\n') // line feed
|
||||
.replace(/\r/g, "\\r") // carriage return
|
||||
.replace(/\n/g, "\\n") // line feed
|
||||
.replace(regex, exports.escape); // non-ASCII characters
|
||||
};
|
||||
|
||||
@@ -601,15 +648,15 @@ exports.jsonStringify = function(s, rawUnicode) {
|
||||
// See http://www.json.org/
|
||||
var regex = rawUnicode ? /[\x00-\x1f]/g : /[\x00-\x1f\x80-\uFFFF]/g;
|
||||
return (s || "")
|
||||
.replace(/\\/g, '\\\\') // backslash
|
||||
.replace(/\\/g, "\\\\") // backslash
|
||||
.replace(/"/g, '\\"') // double quote character
|
||||
.replace(/\r/g, '\\r') // carriage return
|
||||
.replace(/\n/g, '\\n') // line feed
|
||||
.replace(/\x08/g, '\\b') // backspace
|
||||
.replace(/\x0c/g, '\\f') // formfeed
|
||||
.replace(/\t/g, '\\t') // tab
|
||||
.replace(/\r/g, "\\r") // carriage return
|
||||
.replace(/\n/g, "\\n") // line feed
|
||||
.replace(/\x08/g, "\\b") // backspace
|
||||
.replace(/\x0c/g, "\\f") // formfeed
|
||||
.replace(/\t/g, "\\t") // tab
|
||||
.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
|
||||
};
|
||||
|
||||
@@ -617,7 +664,7 @@ exports.jsonStringify = function(s, rawUnicode) {
|
||||
Escape the RegExp special characters with a preceding backslash
|
||||
*/
|
||||
exports.escapeRegExp = function(s) {
|
||||
return s.replace(/[\-\/\\\^\$\*\+\?\.\(\)\|\[\]\{\}]/g, '\\$&');
|
||||
return s.replace(/[\-\/\\\^\$\*\+\?\.\(\)\|\[\]\{\}]/g, "\\$&");
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -700,7 +747,7 @@ exports.parseTextReference = function(textRef) {
|
||||
}
|
||||
} else {
|
||||
// If we couldn't parse it
|
||||
result.title = textRef
|
||||
result.title = textRef;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
@@ -759,60 +806,17 @@ Cryptographic hash function as used by sha256 filter operator
|
||||
options.length .. number of characters returned defaults to 64
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
exports.base64Decode = function(string64,binary,urlsafe) {
|
||||
const encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64;
|
||||
if(binary) return exports.atob(encoded)
|
||||
else return exports.base64DecodeUtf8(encoded);
|
||||
const encoded = urlsafe ? string64.replace(/_/g,"/").replace(/-/g,"+") : string64;
|
||||
if(binary) return $tw.utils.atob(encoded);
|
||||
else return $tw.utils.base64DecodeUtf8(encoded);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -820,10 +824,10 @@ Encode a string to base64
|
||||
*/
|
||||
exports.base64Encode = function(string64,binary,urlsafe) {
|
||||
let encoded;
|
||||
if(binary) encoded = exports.btoa(string64);
|
||||
else encoded = exports.base64EncodeUtf8(string64);
|
||||
if(binary) encoded = $tw.utils.btoa(string64);
|
||||
else encoded = $tw.utils.base64EncodeUtf8(string64);
|
||||
if(urlsafe) {
|
||||
encoded = encoded.replace(/\+/g,'-').replace(/\//g,'_');
|
||||
encoded = encoded.replace(/\+/g,"-").replace(/\//g,"_");
|
||||
}
|
||||
return encoded;
|
||||
};
|
||||
@@ -957,3 +961,56 @@ exports.makeCompareFunction = function(type,options) {
|
||||
};
|
||||
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};
|
||||
};
|
||||
|
||||
@@ -80,8 +80,8 @@ BrowseWidget.prototype.render = function(parent,nextSibling) {
|
||||
});
|
||||
// Insert element
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -9,6 +9,8 @@ Button widget
|
||||
|
||||
"use strict";
|
||||
|
||||
const ALLOWED_SELECTED_ARIA_ATTR = ["aria-checked", "aria-selected", "aria-pressed"];
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var Popup = require("$:/core/modules/utils/dom/popup.js");
|
||||
@@ -44,9 +46,14 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
|
||||
var classes = this["class"].split(" ") || [],
|
||||
isPoppedUp = (this.popup || this.popupTitle) && this.isPoppedUp();
|
||||
if(this.selectedClass) {
|
||||
if((this.set || this.setTitle) && this.setTo && this.isSelected()) {
|
||||
$tw.utils.pushTop(classes, this.selectedClass.split(" "));
|
||||
domNode.setAttribute("aria-checked", "true");
|
||||
if((this.set || this.setTitle) && this.setTo) {
|
||||
const selectedAria = ALLOWED_SELECTED_ARIA_ATTR.includes(this.selectedAria) ? this.selectedAria : "aria-checked";
|
||||
if(this.isSelected()) {
|
||||
$tw.utils.pushTop(classes, this.selectedClass.split(" "));
|
||||
domNode.setAttribute(selectedAria, "true");
|
||||
} else {
|
||||
domNode.setAttribute(selectedAria, "false");
|
||||
}
|
||||
}
|
||||
if(isPoppedUp) {
|
||||
$tw.utils.pushTop(classes,this.selectedClass.split(" "));
|
||||
@@ -128,8 +135,8 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
|
||||
}
|
||||
// Insert element
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -221,6 +228,7 @@ ButtonWidget.prototype.execute = function() {
|
||||
this.style = this.getAttribute("style");
|
||||
this["class"] = this.getAttribute("class","");
|
||||
this.selectedClass = this.getAttribute("selectedClass");
|
||||
this.selectedAria = this.getAttribute("selectedAria");
|
||||
this.defaultSetValue = this.getAttribute("default","");
|
||||
this.buttonTag = this.getAttribute("tag");
|
||||
this.dragTiddler = this.getAttribute("dragTiddler");
|
||||
|
||||
@@ -64,8 +64,8 @@ CheckboxWidget.prototype.render = function(parent,nextSibling) {
|
||||
]);
|
||||
// Insert the label into the DOM and render any children
|
||||
parent.insertBefore(this.labelDomNode,nextSibling);
|
||||
this.renderChildren(this.spanDomNode,null);
|
||||
this.domNodes.push(this.labelDomNode);
|
||||
this.renderChildren(this.spanDomNode,null);
|
||||
};
|
||||
|
||||
CheckboxWidget.prototype.getValue = function() {
|
||||
|
||||
@@ -9,8 +9,8 @@ Widget to display a diff between two texts
|
||||
|
||||
"use strict";
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget,
|
||||
dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
|
||||
|
||||
var DiffTextWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
@@ -35,25 +35,32 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
// Create the diff object
|
||||
var dmpObject = new dmp.diff_match_patch();
|
||||
dmpObject.Diff_EditCost = $tw.utils.parseNumber(this.getAttribute("editcost","4"));
|
||||
var diffs = dmpObject.diff_main(this.getAttribute("source",""),this.getAttribute("dest",""));
|
||||
const editCost = $tw.utils.parseNumber(this.getAttribute("editcost","4"));
|
||||
const mode = this.getAttribute("mode") || "chars";
|
||||
let diffs;
|
||||
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
|
||||
switch(this.getAttribute("cleanup","semantic")) {
|
||||
case "none":
|
||||
// No cleanup
|
||||
break;
|
||||
case "efficiency":
|
||||
dmpObject.diff_cleanupEfficiency(diffs);
|
||||
dmp.diffCleanupEfficiency(diffs, {diffEditCost: editCost});
|
||||
break;
|
||||
default: // case "semantic"
|
||||
dmpObject.diff_cleanupSemantic(diffs);
|
||||
dmp.diffCleanupSemantic(diffs);
|
||||
break;
|
||||
}
|
||||
// Create the elements
|
||||
var domContainer = this.document.createElement("div"),
|
||||
domDiff = this.createDiffDom(diffs);
|
||||
parent.insertBefore(domContainer,nextSibling);
|
||||
// Save our container
|
||||
this.domNodes.push(domContainer);
|
||||
// Set variables
|
||||
this.setVariable("diff-count",diffs.reduce(function(acc,diff) {
|
||||
if(diff[0] !== dmp.DIFF_EQUAL) {
|
||||
@@ -65,8 +72,6 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.renderChildren(domContainer,null);
|
||||
// Render the diff
|
||||
domContainer.appendChild(domDiff);
|
||||
// Save our container
|
||||
this.domNodes.push(domContainer);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -133,7 +138,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||
*/
|
||||
DiffTextWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.source || changedAttributes.dest || changedAttributes.cleanup || changedAttributes.editcost) {
|
||||
if(changedAttributes.source || changedAttributes.dest || changedAttributes.cleanup || changedAttributes.mode || changedAttributes.editcost) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
} else {
|
||||
@@ -141,4 +146,15 @@ 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;
|
||||
|
||||
@@ -56,6 +56,7 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
|
||||
});
|
||||
// Insert the node into the DOM and render any children
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
// Add event handlers
|
||||
if(this.dragEnable) {
|
||||
@@ -70,7 +71,6 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
|
||||
selector: self.dragHandleSelector
|
||||
});
|
||||
}
|
||||
this.domNodes.push(domNode);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -57,8 +57,8 @@ DroppableWidget.prototype.render = function(parent,nextSibling) {
|
||||
}
|
||||
// Insert element
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
// Stack of outstanding enter/leave events
|
||||
this.currentlyEntered = [];
|
||||
};
|
||||
|
||||
@@ -77,8 +77,8 @@ ElementWidget.prototype.render = function(parent,nextSibling) {
|
||||
// Allow hooks to manipulate the DOM node. Eg: Add debug info
|
||||
$tw.hooks.invokeHook("th-dom-rendering-element", domNode, this);
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -39,75 +39,12 @@ EventWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.domNode = domNode;
|
||||
// Assign classes
|
||||
this.assignDomNodeClasses();
|
||||
// Add our event handler
|
||||
$tw.utils.each(this.types,function(type) {
|
||||
domNode.addEventListener(type,function(event) {
|
||||
var selector = self.getAttribute("selector"),
|
||||
matchSelector = self.getAttribute("matchSelector"),
|
||||
actions = self.getAttribute("$"+type),
|
||||
stopPropagation = self.getAttribute("stopPropagation","onaction"),
|
||||
selectedNode = event.target,
|
||||
selectedNodeRect,
|
||||
catcherNodeRect,
|
||||
variables = {};
|
||||
// Firefox can fire dragover and dragenter events on text nodes instead of their parents
|
||||
if(selectedNode.nodeType === 3) {
|
||||
selectedNode = selectedNode.parentNode;
|
||||
}
|
||||
// Check that the selected node matches any matchSelector
|
||||
if(matchSelector && !$tw.utils.domMatchesSelector(selectedNode,matchSelector)) {
|
||||
return false;
|
||||
}
|
||||
if(selector) {
|
||||
// Search ancestors for a node that matches the selector
|
||||
while(!$tw.utils.domMatchesSelector(selectedNode,selector) && selectedNode !== domNode) {
|
||||
selectedNode = selectedNode.parentNode;
|
||||
}
|
||||
// Exit if we didn't find one
|
||||
if(selectedNode === domNode) {
|
||||
return false;
|
||||
}
|
||||
// Only set up variables if we have actions to invoke
|
||||
if(actions) {
|
||||
variables = $tw.utils.collectDOMVariables(selectedNode,self.domNode,event);
|
||||
}
|
||||
}
|
||||
// Execute our actions with the variables
|
||||
if(actions) {
|
||||
// Add a variable for the modifier key
|
||||
variables.modifier = $tw.keyboardManager.getEventModifierKeyDescriptor(event);
|
||||
// Add a variable for the mouse button
|
||||
if("button" in event) {
|
||||
if(event.button === 0) {
|
||||
variables["event-mousebutton"] = "left";
|
||||
} else if(event.button === 1) {
|
||||
variables["event-mousebutton"] = "middle";
|
||||
} else if(event.button === 2) {
|
||||
variables["event-mousebutton"] = "right";
|
||||
}
|
||||
}
|
||||
variables["event-type"] = event.type.toString();
|
||||
if(typeof event.detail === "object" && !!event.detail) {
|
||||
$tw.utils.each(event.detail,function(detailValue,detail) {
|
||||
variables["event-detail-" + detail] = detailValue.toString();
|
||||
});
|
||||
} else if(!!event.detail) {
|
||||
variables["event-detail"] = event.detail.toString();
|
||||
}
|
||||
self.invokeActionString(actions,self,event,variables);
|
||||
}
|
||||
if((actions && stopPropagation === "onaction") || stopPropagation === "always") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},false);
|
||||
});
|
||||
// Add our event handlers
|
||||
this.toggleListeners();
|
||||
// Insert element
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -122,11 +59,232 @@ EventWidget.prototype.execute = function() {
|
||||
self.types.push(key.slice(1));
|
||||
}
|
||||
});
|
||||
this.pointerCaptureMode = this.getAttribute("pointerCapture","no");
|
||||
this.elementTag = this.getAttribute("tag");
|
||||
// Make child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
|
||||
/*
|
||||
Cache and pre-create all event listeners, called when first needed
|
||||
*/
|
||||
EventWidget.prototype.cacheEventListeners = function() {
|
||||
if(this._eventListeners) {
|
||||
return;
|
||||
}
|
||||
this._eventListeners = Object.create(null);
|
||||
this._captureActiveListeners = Object.create(null);
|
||||
this._dynamicOnlyEvents = ["pointerup","pointercancel","pointermove"];
|
||||
|
||||
const clearPointerCapture = event => {
|
||||
if(Number.isInteger(this._capturePointerId)) {
|
||||
this.stopPointerCapture(this._capturePointerId);
|
||||
}
|
||||
};
|
||||
|
||||
const attachDynamicOnlyListeners = () => {
|
||||
this._dynamicOnlyEvents.forEach(dt => {
|
||||
const listener = this._eventListeners[dt];
|
||||
if(listener) {
|
||||
this._captureActiveListeners[dt] = listener;
|
||||
this.domNode.addEventListener(dt, listener, false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Dynamic pointer capture listeners
|
||||
if(this.pointerCaptureMode === "dynamic") {
|
||||
["pointerup","pointercancel"].forEach(type => {
|
||||
this._eventListeners[type] = event => {
|
||||
const selectedNode = this.checkEvent(event, type);
|
||||
if(selectedNode) {
|
||||
clearPointerCapture(event);
|
||||
}
|
||||
// Remove dynamic-only listeners
|
||||
this.cleanupDynamicListeners();
|
||||
return this.handleEvent(event, type, selectedNode);
|
||||
};
|
||||
});
|
||||
if(!this.types.includes("pointerdown")) {
|
||||
this.types.push("pointerdown");
|
||||
}
|
||||
}
|
||||
|
||||
// Create any listeners not already defined above
|
||||
this.types.forEach(type => {
|
||||
if(!this._eventListeners[type]) {
|
||||
this._eventListeners[type] = event => {
|
||||
const selectedNode = this.checkEvent(event, type);
|
||||
if(!selectedNode) {
|
||||
return false;
|
||||
}
|
||||
// Handle pointer capture for pointerdown
|
||||
if(type === "pointerdown") {
|
||||
if(this.pointerCaptureMode !== "no") {
|
||||
this.startPointerCapture(event.pointerId, event.target);
|
||||
}
|
||||
|
||||
if(this.pointerCaptureMode === "dynamic") {
|
||||
attachDynamicOnlyListeners();
|
||||
}
|
||||
} else if(type === "pointerup" || type === "pointercancel") {
|
||||
clearPointerCapture(event);
|
||||
}
|
||||
return this.handleEvent(event, type, selectedNode);
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Check if an event qualifies and return the matching selected node
|
||||
*/
|
||||
EventWidget.prototype.checkEvent = function(event, type) {
|
||||
const domNode = this.domNode;
|
||||
let node = event.target;
|
||||
|
||||
// Use capture target if valid
|
||||
if(this._captureTarget && event.pointerId !== undefined) {
|
||||
if(document.contains(this._captureTarget)) {
|
||||
node = this._captureTarget;
|
||||
} else {
|
||||
// Clear stale reference
|
||||
this.stopPointerCapture(this._capturePointerId);
|
||||
node = event.target;
|
||||
}
|
||||
}
|
||||
|
||||
if(node && node.nodeType === 3) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
if(!node || node.nodeType !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selector = this.getAttribute("selector"),
|
||||
matchSelector = this.getAttribute("matchSelector");
|
||||
|
||||
if(matchSelector && !node.matches(matchSelector)) {
|
||||
return null;
|
||||
}
|
||||
if(selector) {
|
||||
const match = node.closest(selector);
|
||||
if(!match || match === domNode || !domNode.contains(match)) {
|
||||
return null;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
/*
|
||||
Handle the event and execute actions
|
||||
*/
|
||||
EventWidget.prototype.handleEvent = function(event, type, selectedNode) {
|
||||
if(!selectedNode) {
|
||||
return false;
|
||||
}
|
||||
let actions = this.getAttribute("$"+type),
|
||||
stopPropagation = this.getAttribute("stopPropagation","onaction");
|
||||
|
||||
if(actions) {
|
||||
let variables = $tw.utils.extend(
|
||||
{},
|
||||
$tw.utils.collectDOMVariables(selectedNode, this.domNode, event),
|
||||
{
|
||||
"eventJSON": JSON.stringify($tw.utils.copyObjectPropertiesSafe(event)),
|
||||
"modifier": $tw.keyboardManager.getEventModifierKeyDescriptor(event),
|
||||
"event-type": event.type.toString()
|
||||
}
|
||||
);
|
||||
|
||||
if("button" in event) {
|
||||
const mouseButtonMap = {0:"left",1:"middle",2:"right"};
|
||||
variables["event-mousebutton"] = mouseButtonMap[event.button];
|
||||
}
|
||||
this.invokeActionString(actions, this, event, variables);
|
||||
}
|
||||
|
||||
if((actions && stopPropagation === "onaction") || stopPropagation === "always") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
EventWidget.prototype.startPointerCapture = function(pointerId, captureTarget) {
|
||||
// Start capture only if none active; pointerId can be 0
|
||||
if(!Number.isInteger(this._capturePointerId) && this.domNode && this.domNode.setPointerCapture) {
|
||||
this.domNode.setPointerCapture(pointerId);
|
||||
this._capturePointerId = pointerId;
|
||||
this._captureTarget = captureTarget;
|
||||
}
|
||||
};
|
||||
|
||||
EventWidget.prototype.stopPointerCapture = function(pointerId) {
|
||||
if(this.domNode && this.domNode.hasPointerCapture && this.domNode.hasPointerCapture(pointerId)) {
|
||||
this.domNode.releasePointerCapture(pointerId);
|
||||
}
|
||||
this._capturePointerId = undefined;
|
||||
this._captureTarget = undefined;
|
||||
};
|
||||
|
||||
/*
|
||||
Attach all relevant listeners
|
||||
*/
|
||||
EventWidget.prototype.attachListeners = function() {
|
||||
this.cacheEventListeners();
|
||||
const domNode = this.domNode;
|
||||
Object.keys(this._eventListeners).forEach(type => {
|
||||
if(this.pointerCaptureMode === "dynamic" && this._dynamicOnlyEvents.includes(type)) {
|
||||
return; //skip dynamic-only events
|
||||
}
|
||||
domNode.addEventListener(type, this._eventListeners[type], false);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Remove dynamic active listeners
|
||||
*/
|
||||
EventWidget.prototype.cleanupDynamicListeners = function() {
|
||||
const domNode = this.domNode;
|
||||
Object.keys(this._captureActiveListeners || {}).forEach(type => {
|
||||
domNode.removeEventListener(type, this._captureActiveListeners[type], false);
|
||||
});
|
||||
this._captureActiveListeners = Object.create(null);
|
||||
};
|
||||
|
||||
/*
|
||||
Remove all listeners
|
||||
*/
|
||||
EventWidget.prototype.removeAllListeners = function() {
|
||||
if(Number.isInteger(this._capturePointerId)) {
|
||||
this.stopPointerCapture(this._capturePointerId);
|
||||
}
|
||||
const domNode = this.domNode;
|
||||
Object.keys(this._eventListeners || {}).forEach(type => {
|
||||
domNode.removeEventListener(type, this._eventListeners[type], false);
|
||||
});
|
||||
this.cleanupDynamicListeners();
|
||||
this._captureTarget = null;
|
||||
};
|
||||
|
||||
/*
|
||||
Enable or disable listeners
|
||||
*/
|
||||
EventWidget.prototype.toggleListeners = function() {
|
||||
let disabled = this.getAttribute("disabled","no") === "yes";
|
||||
if(disabled) {
|
||||
this.removeAllListeners();
|
||||
} else {
|
||||
this.attachListeners();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Assign DOM node classes
|
||||
*/
|
||||
EventWidget.prototype.assignDomNodeClasses = function() {
|
||||
var classes = this.getAttribute("class","").split(" ");
|
||||
classes.push("tc-eventcatcher");
|
||||
@@ -134,18 +292,23 @@ EventWidget.prototype.assignDomNodeClasses = function() {
|
||||
};
|
||||
|
||||
/*
|
||||
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
||||
Refresh widget
|
||||
*/
|
||||
EventWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes(),
|
||||
changedAttributesCount = $tw.utils.count(changedAttributes);
|
||||
if(changedAttributesCount === 1 && changedAttributes["class"]) {
|
||||
this.assignDomNodeClasses();
|
||||
} else if(changedAttributesCount > 0) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
const changedAttributes = this.computeAttributes(),
|
||||
changedKeys = Object.keys(changedAttributes),
|
||||
canUpdateAttributes = changedKeys.every(key => key === "class" || key === "disabled");
|
||||
if(canUpdateAttributes) {
|
||||
if(changedAttributes["class"]) {
|
||||
this.assignDomNodeClasses();
|
||||
}
|
||||
if(changedAttributes["disabled"]) {
|
||||
this.toggleListeners();
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.eventcatcher = EventWidget;
|
||||
|
||||
@@ -45,7 +45,7 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.execute();
|
||||
// Create element
|
||||
// Determine what type of image it is
|
||||
var tag = "img", src = "",
|
||||
var tag = "img", src = "", self = this,
|
||||
tiddler = this.wiki.getTiddler(this.imageSource);
|
||||
if(!tiddler) {
|
||||
// The source isn't the title of a tiddler, so we'll assume it's a URL
|
||||
@@ -115,11 +115,21 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
|
||||
if(this.lazyLoading && tag === "img") {
|
||||
domNode.setAttribute("loading",this.lazyLoading);
|
||||
}
|
||||
this.assignAttributes(domNode,{
|
||||
sourcePrefix: "data-",
|
||||
destPrefix: "data-"
|
||||
});
|
||||
// Add classes when the image loads or fails
|
||||
$tw.utils.addClass(domNode,"tc-image-loading");
|
||||
domNode.addEventListener("load",function() {
|
||||
domNode.addEventListener("load",function(event) {
|
||||
$tw.utils.removeClass(domNode,"tc-image-loading");
|
||||
$tw.utils.addClass(domNode,"tc-image-loaded");
|
||||
if(self.loadedActions) {
|
||||
var variables = $tw.utils.collectDOMVariables(domNode,null,event);
|
||||
variables["img-natural-width"] = domNode.naturalWidth.toString();
|
||||
variables["img-natural-height"] = domNode.naturalHeight.toString();
|
||||
self.invokeActionString(self.loadedActions,self,event,variables);
|
||||
}
|
||||
},false);
|
||||
domNode.addEventListener("error",function() {
|
||||
$tw.utils.removeClass(domNode,"tc-image-loading");
|
||||
@@ -143,17 +153,31 @@ ImageWidget.prototype.execute = function() {
|
||||
this.imageTooltip = this.getAttribute("tooltip");
|
||||
this.imageAlt = this.getAttribute("alt");
|
||||
this.lazyLoading = this.getAttribute("loading");
|
||||
this.loadedActions = this.getAttribute("loadActions");
|
||||
};
|
||||
|
||||
/*
|
||||
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
||||
*/
|
||||
ImageWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource]) {
|
||||
var changedAttributes = this.computeAttributes(),
|
||||
hasChangedAttributes = $tw.utils.count(changedAttributes) > 0;
|
||||
if(changedAttributes.source || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource] ||changedAttributes.loadActions) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
} else {
|
||||
} else if(hasChangedAttributes) {
|
||||
this.assignAttributes(this.domNodes[0],{
|
||||
sourcePrefix: "data-",
|
||||
destPrefix: "data-"
|
||||
});
|
||||
if(changedAttributes.width) {
|
||||
this.domNodes[0].setAttribute("width",this.getAttribute("width"));
|
||||
}
|
||||
if(changedAttributes.height) {
|
||||
this.domNodes[0].setAttribute("height",this.getAttribute("height"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -45,8 +45,8 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) {
|
||||
]);
|
||||
// Insert element
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
};
|
||||
|
||||
KeyboardWidget.prototype.handleChangeEvent = function(event) {
|
||||
|
||||
@@ -7,7 +7,6 @@ This widget allows defining multiple variables at once, while allowing
|
||||
the later variables to depend upon the earlier ones.
|
||||
|
||||
```
|
||||
\define helloworld() Hello world!
|
||||
<$let currentTiddler="target" value={{!!value}} currentTiddler="different">
|
||||
{{!!value}} will be different from <<value>>
|
||||
</$let>
|
||||
@@ -56,7 +55,7 @@ LetWidget.prototype.computeAttributes = function() {
|
||||
});
|
||||
// Run through again, setting variables and looking for differences
|
||||
$tw.utils.each(this.currentValueFor,function(value,name) {
|
||||
if(!$tw.utils.isArrayEqual(self.attributes[name],value)) {
|
||||
if(self.attributes[name] === undefined || !$tw.utils.isArrayEqual(self.attributes[name],value)) {
|
||||
self.attributes[name] = value;
|
||||
self.setVariable(name,value);
|
||||
changedAttributes[name] = true;
|
||||
@@ -68,7 +67,7 @@ LetWidget.prototype.computeAttributes = function() {
|
||||
LetWidget.prototype.getVariableInfo = function(name,options) {
|
||||
// Special handling: If this variable exists in this very $let, we can
|
||||
// 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];
|
||||
return {
|
||||
text: value[0] || "",
|
||||
|
||||
@@ -50,8 +50,8 @@ LinkWidget.prototype.render = function(parent,nextSibling) {
|
||||
destPrefix: "aria-"
|
||||
});
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,7 +86,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
|
||||
classes.push(this.linkClasses);
|
||||
}
|
||||
} else if(this.overrideClasses !== "") {
|
||||
classes.push(this.overrideClasses)
|
||||
classes.push(this.overrideClasses);
|
||||
}
|
||||
if(classes.length > 0) {
|
||||
domNode.setAttribute("class",classes.join(" "));
|
||||
@@ -97,7 +97,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
|
||||
if(wikilinkTransformFilter) {
|
||||
// Use the filter to construct the href
|
||||
wikiLinkText = this.wiki.filterTiddlers(wikilinkTransformFilter,this,function(iterator) {
|
||||
iterator(self.wiki.getTiddler(self.to),self.to)
|
||||
iterator(self.wiki.getTiddler(self.to),self.to);
|
||||
})[0];
|
||||
} else {
|
||||
// Expand the tv-wikilink-template variable to construct the href
|
||||
@@ -121,12 +121,12 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
|
||||
var tooltipWikiText = this.tooltip || this.getVariable("tv-wikilink-tooltip");
|
||||
if(tooltipWikiText) {
|
||||
var tooltipText = this.wiki.renderText("text/plain","text/vnd.tiddlywiki",tooltipWikiText,{
|
||||
parseAsInline: true,
|
||||
variables: {
|
||||
currentTiddler: this.to
|
||||
},
|
||||
parentWidget: this
|
||||
});
|
||||
parseAsInline: true,
|
||||
variables: {
|
||||
currentTiddler: this.to
|
||||
},
|
||||
parentWidget: this
|
||||
});
|
||||
domNode.setAttribute("title",tooltipText);
|
||||
}
|
||||
if(this.role) {
|
||||
@@ -135,7 +135,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
|
||||
this.assignAttributes(domNode,{
|
||||
sourcePrefix: "aria-",
|
||||
destPrefix: "aria-"
|
||||
})
|
||||
});
|
||||
// Add a click event handler
|
||||
$tw.utils.addEventListeners(domNode,[
|
||||
{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"},
|
||||
@@ -145,6 +145,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
|
||||
$tw.utils.makeDraggable({
|
||||
domNode: domNode,
|
||||
dragTiddlerFn: function() {return self.to;},
|
||||
startActions: self.startActions,
|
||||
endActions: self.endActions,
|
||||
widget: this
|
||||
});
|
||||
} else if(this.draggable === "no") {
|
||||
@@ -157,8 +159,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
|
||||
});
|
||||
// Insert the link into the DOM and render any children
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
};
|
||||
|
||||
LinkWidget.prototype.handleClickEvent = function(event) {
|
||||
@@ -203,6 +205,8 @@ LinkWidget.prototype.execute = function() {
|
||||
this.overrideClasses = this.getAttribute("overrideClass");
|
||||
this.tabIndex = this.getAttribute("tabindex");
|
||||
this.draggable = this.getAttribute("draggable","yes");
|
||||
this.startActions = this.getAttribute("startactions");
|
||||
this.endActions = this.getAttribute("endactions");
|
||||
this.linkTag = this.getAttribute("tag","a");
|
||||
// Determine the link characteristics
|
||||
this.isMissing = !this.wiki.tiddlerExists(this.to);
|
||||
|
||||
@@ -42,8 +42,8 @@ PasswordWidget.prototype.render = function(parent,nextSibling) {
|
||||
]);
|
||||
// Insert the label into the DOM and render any children
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
};
|
||||
|
||||
PasswordWidget.prototype.handleChangeEvent = function(event) {
|
||||
|
||||
@@ -59,8 +59,8 @@ RadioWidget.prototype.render = function(parent,nextSibling) {
|
||||
]);
|
||||
// Insert the label into the DOM and render any children
|
||||
parent.insertBefore(this.labelDomNode,nextSibling);
|
||||
this.renderChildren(this.spanDomNode,null);
|
||||
this.domNodes.push(this.labelDomNode);
|
||||
this.renderChildren(this.spanDomNode,null);
|
||||
};
|
||||
|
||||
RadioWidget.prototype.getValue = function() {
|
||||
|
||||
@@ -40,6 +40,7 @@ RevealWidget.prototype.render = function(parent,nextSibling) {
|
||||
domNode.setAttribute("style",this.style);
|
||||
}
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) {
|
||||
this.positionPopup(domNode);
|
||||
@@ -48,7 +49,6 @@ RevealWidget.prototype.render = function(parent,nextSibling) {
|
||||
if(!this.isOpen) {
|
||||
domNode.setAttribute("hidden","true");
|
||||
}
|
||||
this.domNodes.push(domNode);
|
||||
};
|
||||
|
||||
RevealWidget.prototype.positionPopup = function(domNode) {
|
||||
|
||||
@@ -168,8 +168,8 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.outerDomNode.className = this["class"] || "";
|
||||
// Insert element
|
||||
parent.insertBefore(this.outerDomNode,nextSibling);
|
||||
this.renderChildren(this.innerDomNode,null);
|
||||
this.domNodes.push(this.outerDomNode);
|
||||
this.renderChildren(this.innerDomNode,null);
|
||||
// If the scroll position is bound to a tiddler
|
||||
if(this.scrollableBind) {
|
||||
// After a delay for rendering, scroll to the bound position
|
||||
|
||||
@@ -63,8 +63,8 @@ SelectWidget.prototype.render = function(parent,nextSibling) {
|
||||
domNode.setAttribute("title",this.selectTooltip);
|
||||
}
|
||||
this.parentDomNode.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
this.domNodes.push(domNode);
|
||||
this.renderChildren(domNode,null);
|
||||
this.setSelectValue();
|
||||
if(this.selectFocus == "yes") {
|
||||
this.getSelectDomNode().focus();
|
||||
@@ -82,8 +82,8 @@ SelectWidget.prototype.handleChangeEvent = function(event) {
|
||||
if(this.selectMultiple == false) {
|
||||
var value = this.getSelectDomNode().value;
|
||||
} else {
|
||||
var value = this.getSelectValues()
|
||||
value = $tw.utils.stringifyList(value);
|
||||
var value = this.getSelectValues();
|
||||
value = $tw.utils.stringifyList(value);
|
||||
}
|
||||
this.wiki.setText(this.selectTitle,this.selectField,this.selectIndex,value);
|
||||
// Trigger actions
|
||||
@@ -118,12 +118,21 @@ SelectWidget.prototype.setSelectValue = function() {
|
||||
}
|
||||
}
|
||||
// Assign it to the select element if it's different than the current value
|
||||
if (this.selectMultiple) {
|
||||
if(this.selectMultiple) {
|
||||
value = value === undefined ? "" : value;
|
||||
var select = this.getSelectDomNode();
|
||||
var values = Array.isArray(value) ? value : $tw.utils.parseStringArray(value);
|
||||
var child,
|
||||
values = Array.isArray(value) ? value : $tw.utils.parseStringArray(value);
|
||||
for(var i=0; i < select.children.length; i++){
|
||||
select.children[i].selected = values.indexOf(select.children[i].value) !== -1
|
||||
child=select.children[i];
|
||||
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 {
|
||||
var domNode = this.getSelectDomNode();
|
||||
@@ -147,14 +156,14 @@ SelectWidget.prototype.getSelectValues = function() {
|
||||
select = this.getSelectDomNode();
|
||||
result = [];
|
||||
options = select && select.options;
|
||||
for (var i=0; i<options.length; i++) {
|
||||
for(var i=0; i<options.length; i++) {
|
||||
opt = options[i];
|
||||
if (opt.selected) {
|
||||
if(opt.selected) {
|
||||
result.push(opt.value || opt.text);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Compute the internal state of the widget
|
||||
@@ -183,7 +192,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||
SelectWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
// If we're using a different tiddler/field/index then completely refresh ourselves
|
||||
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip || changedAttributes.tabindex || changedAttributes.disabled) {
|
||||
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip || changedAttributes.default || changedAttributes.tabindex || changedAttributes.disabled) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -32,16 +32,26 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) {
|
||||
} catch(error) {
|
||||
if(error instanceof $tw.utils.TranscludeRecursionError) {
|
||||
// We were infinite looping.
|
||||
// We need to try and abort as much of the loop as we can, so we will keep "throwing" upward until we find a transclusion that has a different signature.
|
||||
// Hopefully that will land us just outside where the loop began. That's where we want to issue an error.
|
||||
// Rendering widgets beneath this point may result in a freezing browser if they explode exponentially.
|
||||
// We need to try and abort as much of the loop as we
|
||||
// can, so we will keep "throwing" upward until we find
|
||||
// a transclusion that has a different signature.
|
||||
// Hopefully that will land us just outside where the
|
||||
// loop began. That's where we want to issue an error.
|
||||
// Rendering widgets beneath this point may result in a
|
||||
// freezing browser if they explode exponentially.
|
||||
var transcludeSignature = this.getVariable("transclusion");
|
||||
if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) {
|
||||
// For the first fifty transcludes we climb up, we simply collect signatures.
|
||||
// We're assuming that those first 50 will likely include all transcludes involved in the loop.
|
||||
// For the first fifty transcludes we climb up,
|
||||
// we simply collect signatures.
|
||||
// We're assuming those first 50 will likely
|
||||
// include all transcludes involved in the loop.
|
||||
error.signatures[transcludeSignature] = true;
|
||||
} else if(!error.signatures[transcludeSignature]) {
|
||||
// Now that we're past the first 50, let's look for the first signature that wasn't in the loop. That'll be where we print the error and resume rendering.
|
||||
// Now that we're past the first 50, look for
|
||||
// the first signature that wasn't in that loop.
|
||||
// That's where we print the error and resume
|
||||
// rendering.
|
||||
this.removeChildDomNodes();
|
||||
this.children = [this.makeChildWidget({type: "error", attributes: {
|
||||
"$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")}
|
||||
}})];
|
||||
|
||||
@@ -9,376 +9,215 @@ View widget
|
||||
|
||||
"use strict";
|
||||
|
||||
const Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var ViewWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
==========================================
|
||||
ViewHandler Base Class
|
||||
==========================================
|
||||
Base class for all view format handlers.
|
||||
Provides common functionality and defines the interface for subclasses.
|
||||
Inherit from the base widget class
|
||||
*/
|
||||
class ViewHandler {
|
||||
constructor(widget) {
|
||||
this.widget = widget;
|
||||
this.wiki = widget.wiki;
|
||||
this.document = widget.document;
|
||||
this.viewTitle = widget.viewTitle;
|
||||
this.viewField = widget.viewField;
|
||||
this.viewIndex = widget.viewIndex;
|
||||
this.viewSubtiddler = widget.viewSubtiddler;
|
||||
this.viewTemplate = widget.viewTemplate;
|
||||
this.viewMode = widget.viewMode;
|
||||
}
|
||||
|
||||
render(parent, nextSibling) {
|
||||
this.text = this.getValue();
|
||||
this.createTextNode(parent, nextSibling);
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.widget.getValueAsText();
|
||||
}
|
||||
|
||||
createTextNode(parent, nextSibling) {
|
||||
if(this.text) {
|
||||
const textNode = this.document.createTextNode(this.text);
|
||||
parent.insertBefore(textNode, nextSibling);
|
||||
this.widget.domNodes.push(textNode);
|
||||
} else {
|
||||
this.widget.makeChildWidgets();
|
||||
this.widget.renderChildren(parent, nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
var self = this;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ViewWidget.prototype = new Widget();
|
||||
|
||||
/*
|
||||
==========================================
|
||||
Wikified View Handler Base
|
||||
==========================================
|
||||
Base class for wikified view handlers
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
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;
|
||||
ViewWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
if(this.text) {
|
||||
var textNode = this.document.createTextNode(this.text);
|
||||
parent.insertBefore(textNode,nextSibling);
|
||||
this.domNodes.push(textNode);
|
||||
} else {
|
||||
this.makeChildWidgets();
|
||||
this.renderChildren(parent,nextSibling);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
==========================================
|
||||
ViewWidget
|
||||
==========================================
|
||||
Main widget class that orchestrates view handlers
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
class ViewWidget extends Widget {
|
||||
constructor(parseTreeNode, options) {
|
||||
super();
|
||||
this.initialise(parseTreeNode, options);
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
render(parent, nextSibling) {
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
this.viewHandler = ViewHandlerFactory.createHandler(this.viewFormat, this);
|
||||
this.viewHandler.render(parent, nextSibling);
|
||||
}
|
||||
/*
|
||||
The various formatter functions are baked into this widget for the moment. Eventually they will be replaced by macro functions
|
||||
*/
|
||||
|
||||
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);
|
||||
/*
|
||||
Retrieve the value of the widget. Options are:
|
||||
asString: Optionally return the value as a string
|
||||
*/
|
||||
ViewWidget.prototype.getValue = function(options) {
|
||||
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 {
|
||||
let tiddler;
|
||||
if(this.viewSubtiddler) {
|
||||
tiddler = this.wiki.getSubTiddler(this.viewTitle, this.viewSubtiddler);
|
||||
tiddler = this.wiki.getTiddler(this.viewTitle);
|
||||
}
|
||||
if(tiddler) {
|
||||
if(this.viewField === "text" && !this.viewSubtiddler) {
|
||||
// Calling getTiddlerText() triggers lazy loading of skinny tiddlers
|
||||
value = this.wiki.getTiddlerText(this.viewTitle);
|
||||
} 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];
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(this.viewField === "title") {
|
||||
value = this.viewTitle;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
getValueAsText() {
|
||||
return this.getValue({asString: true});
|
||||
ViewWidget.prototype.getValueAsText = function() {
|
||||
return this.getValue({asString: true});
|
||||
};
|
||||
|
||||
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 "";
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
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");
|
||||
};
|
||||
|
||||
exports.view = ViewWidget;
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
exports.view = ViewWidget;
|
||||
|
||||
@@ -151,7 +151,7 @@ Widget.prototype.getVariableInfo = function(name,options) {
|
||||
} else if(variable.isFunctionDefinition) {
|
||||
// Function evaluations
|
||||
params = self.resolveVariableParameters(variable.params,actualParams);
|
||||
var variables = options.variables || Object.create(null);
|
||||
var variables = $tw.utils.extend({},options.variables);
|
||||
// Apply default parameter values
|
||||
$tw.utils.each(variable.params,function(param,index) {
|
||||
if(param["default"]) {
|
||||
@@ -160,7 +160,7 @@ Widget.prototype.getVariableInfo = function(name,options) {
|
||||
});
|
||||
// Parameters are an array of {name:, value:, multivalue:} pairs (name and multivalue are optional)
|
||||
$tw.utils.each(params,function(param) {
|
||||
if(param.multiValue) {
|
||||
if(param.multiValue && param.multiValue.length) {
|
||||
variables[param.name] = param.multiValue;
|
||||
} else {
|
||||
variables[param.name] = param.value || "";
|
||||
@@ -233,8 +233,10 @@ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams)
|
||||
paramMultiValue = typeof param === "string" ? [param] : (param.multiValue || [paramValue]);
|
||||
}
|
||||
// If we've still not got a value, use the default, if any
|
||||
paramValue = paramValue || paramInfo["default"] || "";
|
||||
paramMultiValue = paramMultiValue || [paramValue];
|
||||
if(!paramValue) {
|
||||
paramValue = paramInfo["default"] || "";
|
||||
paramMultiValue = [paramValue];
|
||||
}
|
||||
// Store the parameter name and value
|
||||
results.push({name: paramInfo.name, value: paramValue, multiValue: paramMultiValue});
|
||||
}
|
||||
@@ -341,7 +343,7 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
|
||||
}
|
||||
} else {
|
||||
opts = opts || {};
|
||||
opts.variables = variables;
|
||||
opts.variables = $tw.utils.extend({},variables,opts.variables);
|
||||
return self.getVariable(name,opts);
|
||||
};
|
||||
},
|
||||
@@ -414,7 +416,21 @@ Widget.prototype.computeAttribute = function(attribute,options) {
|
||||
value = [value];
|
||||
}
|
||||
} else if(attribute.type === "macro") {
|
||||
var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params});
|
||||
// Get the macro name
|
||||
var macroName = attribute.value.attributes["$variable"].value;
|
||||
// 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) {
|
||||
value = variableInfo.resultList;
|
||||
} else {
|
||||
@@ -771,9 +787,9 @@ Widget.prototype.findNextSiblingDomNode = function(startIndex) {
|
||||
// Refer to this widget by its index within its parents children
|
||||
var parent = this.parentWidget,
|
||||
index = startIndex !== undefined ? startIndex : parent.children.indexOf(this);
|
||||
if(index === -1) {
|
||||
throw "node not found in parents children";
|
||||
}
|
||||
if(index === -1) {
|
||||
throw "node not found in parents children";
|
||||
}
|
||||
// Look for a DOM node in the later siblings
|
||||
while(++index < parent.children.length) {
|
||||
var domNode = parent.children[index].findFirstDomNode();
|
||||
@@ -811,21 +827,60 @@ Widget.prototype.findFirstDomNode = function() {
|
||||
};
|
||||
|
||||
/*
|
||||
Remove any DOM nodes created by this widget or its children
|
||||
Entry into destroy procedure
|
||||
options include:
|
||||
removeDOMNodes: boolean (default true)
|
||||
*/
|
||||
Widget.prototype.destroyChildren = function(options) {
|
||||
$tw.utils.each(this.children,function(childWidget) {
|
||||
childWidget.destroy(options);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Legacy entry into destroy procedure
|
||||
*/
|
||||
Widget.prototype.removeChildDomNodes = function() {
|
||||
// If this widget has directly created DOM nodes, delete them and exit. This assumes that any child widgets are contained within the created DOM nodes, which would normally be the case
|
||||
if(this.domNodes.length > 0) {
|
||||
$tw.utils.each(this.domNodes,function(domNode) {
|
||||
domNode.parentNode.removeChild(domNode);
|
||||
});
|
||||
this.domNodes = [];
|
||||
} else {
|
||||
// Otherwise, ask the child widgets to delete their DOM nodes
|
||||
$tw.utils.each(this.children,function(childWidget) {
|
||||
childWidget.removeChildDomNodes();
|
||||
});
|
||||
this.destroy({removeDOMNodes: true});
|
||||
};
|
||||
|
||||
/*
|
||||
Default destroy
|
||||
options include:
|
||||
- removeDOMNodes: boolean (default true)
|
||||
*/
|
||||
Widget.prototype.destroy = function(options) {
|
||||
const { removeDOMNodes = true } = options || {};
|
||||
let removeChildDOMNodes = removeDOMNodes;
|
||||
if(removeDOMNodes && this.domNodes.length > 0) {
|
||||
// If this widget will remove its own DOM nodes, children should not remove theirs
|
||||
removeChildDOMNodes = false;
|
||||
}
|
||||
// Destroy children first
|
||||
this.destroyChildren({removeDOMNodes: removeChildDOMNodes});
|
||||
this.children = [];
|
||||
|
||||
// Call custom cleanup method if implemented
|
||||
if(typeof this.onDestroy === "function") {
|
||||
this.onDestroy();
|
||||
}
|
||||
|
||||
// Remove our DOM nodes if needed
|
||||
if(removeDOMNodes) {
|
||||
this.removeLocalDomNodes();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Remove any DOM nodes created by this widget
|
||||
*/
|
||||
Widget.prototype.removeLocalDomNodes = function() {
|
||||
for(const domNode of this.domNodes) {
|
||||
if(domNode.parentNode) {
|
||||
domNode.parentNode.removeChild(domNode);
|
||||
}
|
||||
}
|
||||
this.domNodes = [];
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -369,31 +369,16 @@ Sort an array of tiddler titles by a specified field
|
||||
isDescending: true if the sort should be descending
|
||||
isCaseSensitive: true if the sort should consider upper and lower case letters to be different
|
||||
*/
|
||||
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric) {
|
||||
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric,locale) {
|
||||
var self = this;
|
||||
if(sortField === "title") {
|
||||
if(!isNumeric && !isAlphaNumeric) {
|
||||
if(isCaseSensitive) {
|
||||
if(isDescending) {
|
||||
titles.sort(function(a,b) {
|
||||
return b.localeCompare(a);
|
||||
});
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
}
|
||||
const sorter = new Intl.Collator(locale, { sensitivity: isCaseSensitive ? "variant" : "accent" });
|
||||
if(isDescending) {
|
||||
titles.sort((a,b) => sorter.compare(b, a));
|
||||
} else {
|
||||
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());
|
||||
});
|
||||
}
|
||||
}
|
||||
titles.sort((a,b) => sorter.compare(a, b));
|
||||
}
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
var x,y;
|
||||
@@ -414,14 +399,8 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
|
||||
}
|
||||
}
|
||||
}
|
||||
if(isAlphaNumeric) {
|
||||
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);
|
||||
const sorter = new Intl.Collator(locale, { numeric: isAlphaNumeric, sensitivity: isAlphaNumeric ? "base" : isCaseSensitive ? "variant" : "accent" });
|
||||
return isDescending ? sorter.compare(b, a) : sorter.compare(a, b);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -463,14 +442,8 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
|
||||
}
|
||||
a = String(a);
|
||||
b = String(b);
|
||||
if(isAlphaNumeric) {
|
||||
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);
|
||||
const sorter = new Intl.Collator(locale, { numeric: isAlphaNumeric, sensitivity: isAlphaNumeric ? "base" : isCaseSensitive ? "variant" : "accent" });
|
||||
return isDescending ? sorter.compare(b, a) : sorter.compare(a, b);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1149,15 +1122,16 @@ Parse a block of text of a specified MIME type
|
||||
Options include:
|
||||
substitutions: an optional array of substitutions
|
||||
*/
|
||||
exports.getSubstitutedText = function(text,widget,options) {
|
||||
exports.getSubstitutedText = function(text,thisWidget,options) {
|
||||
options = options || {};
|
||||
text = text || "";
|
||||
var self = this,
|
||||
widgetClass = widget.widget,
|
||||
substitutions = options.substitutions || [],
|
||||
output;
|
||||
// Evaluate embedded filters and substitute with first result
|
||||
output = text.replace(/\$\{([\S\s]+?)\}\$/g, function(match,filter) {
|
||||
return self.filterTiddlers(filter,widget)[0] || "";
|
||||
return self.filterTiddlers(filter,thisWidget)[0] || "";
|
||||
});
|
||||
// Process any substitutions provided in options
|
||||
$tw.utils.each(substitutions,function(substitute) {
|
||||
@@ -1165,7 +1139,7 @@ exports.getSubstitutedText = function(text,widget,options) {
|
||||
});
|
||||
// Substitute any variable references with their values
|
||||
return output.replace(/\$\((.+?)\)\$/g, function(match,varname) {
|
||||
return widget.getVariable(varname,{defaultValue: ""});
|
||||
return widgetClass.evaluateVariable(thisWidget,varname, {defaultValue: ""})[0];
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1690,12 +1664,14 @@ exports.addToStory = function(title,fromTitle,storyTitle,options) {
|
||||
Generate a title for the draft of a given tiddler
|
||||
*/
|
||||
exports.generateDraftTitle = function(title) {
|
||||
var c = 0,
|
||||
draftTitle,
|
||||
username = this.getTiddlerText("$:/status/UserName"),
|
||||
attribution = username ? " by " + username : "";
|
||||
let c = 0,
|
||||
draftTitle;
|
||||
const username = this.getTiddlerText("$:/status/UserName");
|
||||
do {
|
||||
draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'" + attribution;
|
||||
draftTitle = username ? $tw.language.getString("Draft/Attribution", {variables: {"draft-title": title}}) : $tw.language.getString("Draft/Title", {variables: {"draft-title": title}});
|
||||
if(c) {
|
||||
draftTitle = draftTitle.concat(" ", (c + 1).toString());
|
||||
}
|
||||
c++;
|
||||
} while(this.tiddlerExists(draftTitle));
|
||||
return draftTitle;
|
||||
|
||||
30
core/stylesheets/custom-properties.tid
Normal file
30
core/stylesheets/custom-properties.tid
Normal file
@@ -0,0 +1,30 @@
|
||||
title: $:/core/stylesheets/custom-properties
|
||||
|
||||
\rules only transcludeinline macrocallinline html transcludeblock
|
||||
|
||||
/* Tiddlywiki's CSS properties */
|
||||
|
||||
:root {
|
||||
<$list filter="[[$:/palettes/Vanilla]indexes[]]">
|
||||
--tpc-<<currentTiddler>>: <$transclude $variable="colour" $mode="inline" name=<<currentTiddler>>/>;
|
||||
</$list>
|
||||
|
||||
/* CSS settings */
|
||||
--tp-code-wrapping: {{$:/themes/tiddlywiki/vanilla/options/codewrapping}};
|
||||
--tp-font-family: {{$:/themes/tiddlywiki/vanilla/settings/fontfamily}};
|
||||
--tp-code-font-family: {{$:/themes/tiddlywiki/vanilla/settings/codefontfamily}};
|
||||
--tp-editor-font-family: {{$:/themes/tiddlywiki/vanilla/settings/editorfontfamily}};
|
||||
--tp-font-size: {{$:/themes/tiddlywiki/vanilla/metrics/fontsize}};
|
||||
--tp-line-height: {{$:/themes/tiddlywiki/vanilla/metrics/lineheight}};
|
||||
--tp-body-font-size: {{$:/themes/tiddlywiki/vanilla/metrics/bodyfontsize}};
|
||||
--tp-body-line-height: {{$:/themes/tiddlywiki/vanilla/metrics/bodylineheight}};
|
||||
--tp-story-left: {{$:/themes/tiddlywiki/vanilla/metrics/storyleft}};
|
||||
--tp-story-top: {{$:/themes/tiddlywiki/vanilla/metrics/storytop}};
|
||||
--tp-story-right: {{$:/themes/tiddlywiki/vanilla/metrics/storyright}};
|
||||
--tp-story-width: {{$:/themes/tiddlywiki/vanilla/metrics/storyrwidth}};
|
||||
--tp-tiddler-width: {{$:/themes/tiddlywiki/vanilla/metrics/tiddlerwidth}};
|
||||
--tp-sidebar-breakpoint: {{$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint}};
|
||||
--tp-sidebar-width: {{$:/themes/tiddlywiki/vanilla/metrics/sidebarwidth}};
|
||||
|
||||
--tp-animation-duration: {{{ [{$:/config/AnimationDuration}addsuffix[ms]] }}};
|
||||
}
|
||||
@@ -25,7 +25,6 @@ title: $:/core/templates/tiddlywiki5.html
|
||||
`{{{ [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/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}`
|
||||
<!--~~ Style section start ~~-->
|
||||
</head>
|
||||
<body class="tc-body">
|
||||
<!--~~ Raw markup for the top of the body section ~~-->
|
||||
|
||||
@@ -18,6 +18,7 @@ caption: {{$:/language/ControlPanel/Basics/Caption}}
|
||||
|<$link to="$:/config/NewTiddler/Tags"><<lingo NewTiddler/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewTiddler/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|
||||
|<$link to="$:/config/NewJournal/Tags"><<lingo NewJournal/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewJournal/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|
||||
|<$link to="$:/config/AutoFocus"><<lingo AutoFocus/Prompt>></$link> |{{$:/snippets/minifocusswitcher}} |
|
||||
|<$link to="$:/config/AutoFocusEdit"><<lingo AutoFocusEdit/Prompt>></$link> |{{$:/snippets/minifocuseditswitcher}} |
|
||||
|<<lingo Language/Prompt>> |{{$:/snippets/minilanguageswitcher}} |
|
||||
|<<lingo Tiddlers/Prompt>> |<<show-filter-count "[!is[system]sort[title]]">> |
|
||||
|<<lingo Tags/Prompt>> |<<show-filter-count "[tags[]sort[title]]">> |
|
||||
|
||||
@@ -8,7 +8,7 @@ title: $:/core/ui/EditTemplate/body/editor
|
||||
class="tc-edit-texteditor tc-edit-texteditor-body"
|
||||
placeholder={{$:/language/EditTemplate/Body/Placeholder}}
|
||||
tabindex={{$:/config/EditTabIndex}}
|
||||
focus={{{ [{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
|
||||
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[text]then[true]] ~[{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
|
||||
cancelPopups="yes"
|
||||
fileDrop={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}}
|
||||
|
||||
|
||||
@@ -103,9 +103,9 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
|
||||
<$set
|
||||
|
||||
name="buttonClasses"
|
||||
value={{!!button-classes}}
|
||||
value={{{ [subfilter{!!button-classes}] :and[join[ ]] }}}
|
||||
|
||||
><<toolbar-button>></$set>
|
||||
\end
|
||||
|
||||
<<toolbar-button-outer>>
|
||||
<<toolbar-button-outer>>
|
||||
|
||||
@@ -1,157 +1,181 @@
|
||||
title: $:/core/ui/EditTemplate/fields
|
||||
tags: $:/tags/EditTemplate
|
||||
|
||||
\procedure lingo-base() $:/language/EditTemplate/
|
||||
\function tf.config-title() [[$:/config/EditTemplateFields/Visibility/]addsuffix[$(currentField)$]substitute[]get[text]]
|
||||
|
||||
\function tf.config-filter() [[hide]] :except[title<tf.config-title>]
|
||||
|
||||
<!-- Beware this is duplicated from EditTemplate.tid. For details see bug #7054 -->
|
||||
\procedure get-field-value-tiddler-filter() [subfilter<get-field-editor-filter>sha256[16]addprefix[/]addprefix<newFieldValueTiddlerPrefix>]
|
||||
\procedure get-field-editor-filter() [<newFieldNameTiddler>get[text]else[]] :cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]] :and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
|
||||
|
||||
\procedure prefix.bracket() [
|
||||
\procedure suffix.bracket() ]
|
||||
|
||||
\function tf.current-tiddler-new-field-selector() [[data-tiddler-title=]addprefix[$(prefix.bracket)$]substitute[]addsuffix<currentTiddlerCSSescaped>addsuffix[$(suffix.bracket)$]substitute[]] .tc-edit-field-add-name-wrapper input :and[join[ ]]
|
||||
|
||||
\procedure new-field-actions()
|
||||
\whitespace trim
|
||||
<$action-sendmessage $message="tm-add-field" $name={{{ [<newFieldNameTiddler>get[text]] }}} $value={{{ [<newFieldNameTiddler>get[text]] :map[subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
|
||||
<$set name="safeNewFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
|
||||
<$action-deletetiddler $filter="[<newFieldNameTiddler>] [prefix[$:/temp/NewFieldValue]prefix<safeNewFieldValueTiddlerPrefix>] [<storeTitle>] [<searchListState>]"/>
|
||||
</$set>
|
||||
<$action-sendmessage $message="tm-focus-selector" $param=<<tf.current-tiddler-new-field-selector>>/>
|
||||
\end
|
||||
|
||||
\procedure lingo-base() $:/language/EditTemplate/
|
||||
|
||||
\procedure delete-state-tiddlers() <$action-deletetiddler $filter="[<newFieldNameTiddler>] [<storeTitle>] [<searchListState>]"/>
|
||||
|
||||
\procedure cancel-search-actions-inner()
|
||||
\whitespace trim
|
||||
<$list
|
||||
filter="[<storeTitle>has[text]] [<newFieldNameTiddler>has[text]]"
|
||||
variable="ignore"
|
||||
emptyMessage="<<cancel-delete-tiddler-actions 'cancel'>>">
|
||||
<<delete-state-tiddlers>>
|
||||
</$list>
|
||||
\procedure focus-new-field-input() <$action-sendmessage $message="tm-focus-selector" $param=`[data-tiddler-title="$(storyTiddler)$"] .tc-edit-field-add-name-wrapper input` />
|
||||
|
||||
\procedure new-field-actions()
|
||||
<$action-setfield $tiddler=<<storyTiddler>> $field={{{ [<newFieldNameTiddler>get[text]] }}} $value={{{ [<newFieldValueTiddler>get[text]] }}} />
|
||||
<$action-deletetiddler $filter="[prefix[$:/temp/NewFieldValue]prefix<newFieldValueTiddlerPrefix>]"/>
|
||||
<<delete-state-tiddlers>>
|
||||
<<focus-new-field-input>>
|
||||
\end
|
||||
|
||||
\procedure delete-field-actions()
|
||||
<$action-deletefield $field=<<currentField>>/>
|
||||
<<focus-new-field-input>>
|
||||
\end
|
||||
|
||||
\procedure cancel-search-actions()
|
||||
\whitespace trim
|
||||
<$set name="userInput" value={{{ [<storeTitle>get[text]] }}}>
|
||||
<$list
|
||||
filter="[<newFieldNameTiddler>get[text]!match<userInput>]"
|
||||
emptyMessage="<<cancel-search-actions-inner>>">
|
||||
<$action-setfield $tiddler=<<newFieldNameTiddler>> text=<<userInput>>/><$action-setfield $tiddler=<<refreshTitle>> text="yes"/>
|
||||
</$list>
|
||||
</$set>
|
||||
<$let userInput={{{ [<storeTitle>get[text]] }}}>
|
||||
<%if [<newFieldNameTiddler>get[text]!match<userInput>] %>
|
||||
<$action-setfield $tiddler=<<newFieldNameTiddler>> text=<<userInput>>/>
|
||||
<$action-setfield $tiddler=<<refreshTitle>> text="yes"/>
|
||||
<%else%>
|
||||
<%if [<storeTitle>has[text]] [<newFieldNameTiddler>has[text]] %>
|
||||
<<delete-state-tiddlers>>
|
||||
<%else%>
|
||||
<<cancel-delete-tiddler-actions 'cancel'>>
|
||||
<%endif%>
|
||||
<%endif%>
|
||||
</$let>
|
||||
\end
|
||||
|
||||
\procedure new-field()
|
||||
\whitespace trim
|
||||
<$vars name={{{ [<newFieldNameTiddler>get[text]] }}}>
|
||||
<$reveal type="nomatch" text="" default=<<name>>>
|
||||
<$button tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}>
|
||||
<$action-sendmessage $message="tm-add-field"
|
||||
$name=<<name>>
|
||||
$value={{{ [subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
|
||||
<$set name="safeNewFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
|
||||
<$action-deletetiddler $filter="[<newFieldNameTiddler>] [prefix[$:/temp/NewFieldValue]prefix<safeNewFieldValueTiddlerPrefix>] [<storeTitle>] [<searchListState>]"/>
|
||||
</$set>
|
||||
<<lingo Fields/Add/Button>>
|
||||
</$button>
|
||||
</$reveal>
|
||||
<$reveal type="match" text="" default=<<name>>>
|
||||
<$button>
|
||||
<<lingo Fields/Add/Button>>
|
||||
</$button>
|
||||
</$reveal>
|
||||
</$vars>
|
||||
<%if [<newFieldNameTiddler>get[text]!is[blank]] %>
|
||||
<$button actions="<<new-field-actions>>" tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}>
|
||||
<<lingo Fields/Add/Button>>
|
||||
</$button>
|
||||
<%else%>
|
||||
<$button>
|
||||
<<lingo Fields/Add/Button>>
|
||||
</$button>
|
||||
<%endif%>
|
||||
\end
|
||||
\whitespace trim
|
||||
|
||||
<$set name="newFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
|
||||
<div class="tc-edit-fields">
|
||||
<table class={{{ [all[current]fields[]] :filter[lookup[$:/config/EditTemplateFields/Visibility/]!match[hide]] :and[count[]!match[0]] :and[then[tc-edit-fields]] :else[[tc-edit-fields tc-edit-fields-small]] }}}>
|
||||
<tbody>
|
||||
<$list filter="[all[current]fields[]] :and[sort[title]]" variable="currentField" storyview="pop">
|
||||
<$list filter=<<tf.config-filter>> variable="temp">
|
||||
<tr class="tc-edit-field">
|
||||
<td class="tc-edit-field-name">
|
||||
<$text text=<<currentField>>/>:</td>
|
||||
<td class="tc-edit-field-value">
|
||||
<$keyboard key="((delete-field))" actions="""<$action-deletefield $field=<<currentField>>/><$set name="currentTiddlerCSSescaped" value={{{ [<currentTiddler>escapecss[]] }}}><$action-sendmessage $message="tm-focus-selector" $param=<<tf.current-tiddler-new-field-selector>>/></$set>""">
|
||||
<$transclude tiddler={{{ [<currentField>] :cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]] :and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}] }}} />
|
||||
</$keyboard>
|
||||
</td>
|
||||
<td class="tc-edit-field-remove">
|
||||
<$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}>
|
||||
<$action-deletefield $field=<<currentField>>/>
|
||||
{{$:/core/images/delete-button}}
|
||||
</$button>
|
||||
</td>
|
||||
</tr>
|
||||
</$list>
|
||||
</$list>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
\function tf.config-filter() [lookup:show[$:/config/EditTemplateFields/Visibility/]!match[hide]]
|
||||
|
||||
<$fieldmangler>
|
||||
<div class="tc-edit-field-add">
|
||||
<em class="tc-edit tc-small-gap-right">
|
||||
<<lingo Fields/Add/Prompt>>
|
||||
</em>
|
||||
<$vars refreshTitle=<<qualify "$:/temp/fieldname/refresh">> storeTitle=<<newFieldNameInputTiddler>> searchListState=<<newFieldNameSelectionTiddler>>>
|
||||
<div class="tc-edit-field-add-name-wrapper">
|
||||
<$transclude $variable="keyboard-driven-input" tiddler=<<newFieldNameTiddler>> storeTitle=<<storeTitle>> refreshTitle=<<refreshTitle>>
|
||||
selectionStateTitle=<<searchListState>> tag="input" default="" placeholder={{$:/language/EditTemplate/Fields/Add/Name/Placeholder}}
|
||||
focusPopup=<<qualify "$:/state/popup/field-dropdown">> class="tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}}
|
||||
focus={{{ [{$:/config/AutoFocus}match[fields]then[true]] :else[[false]] }}} cancelPopups="yes"
|
||||
configTiddlerFilter="[[$:/config/EditMode/fieldname-filter]]" inputCancelActions=<<cancel-search-actions>> />
|
||||
<$button popup=<<qualify "$:/state/popup/field-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Field/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Field/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button>
|
||||
<$reveal state=<<qualify "$:/state/popup/field-dropdown">> type="nomatch" text="" default="">
|
||||
<div class="tc-block-dropdown tc-edit-type-dropdown">
|
||||
<$set name="tv-show-missing-links" value="yes">
|
||||
<$linkcatcher to=<<newFieldNameTiddler>>>
|
||||
<div class="tc-dropdown-item">
|
||||
<<lingo Fields/Add/Dropdown/User>>
|
||||
</div>
|
||||
<$set name="newFieldName" value={{{ [<storeTitle>get[text]] }}}>
|
||||
<$list filter="[!is[shadow]!is[system]fields[]search:title<newFieldName>sort[]] :except[[created]] :except[[creator]] :except[[draft.of]] :except[[draft.title]] :except[[modified]] :except[[modifier]] :except[[tags]] :except[[text]] :except[[title]] :except[[type]]" variable="currentField">
|
||||
<$list filter="[<currentField>addsuffix[-primaryList]] :except[<searchListState>get[text]]" emptyMessage="""<$link to=<<currentField>> class="tc-list-item-selected"><$text text=<<currentField>>/></$link>""">
|
||||
<$link to=<<currentField>>>
|
||||
<$text text=<<currentField>>/>
|
||||
</$link>
|
||||
</$list>
|
||||
</$list>
|
||||
<div class="tc-dropdown-item">
|
||||
<<lingo Fields/Add/Dropdown/System>>
|
||||
</div>
|
||||
<$list filter="[fields[]search:title<newFieldName>sort[]] :except[!is[shadow]!is[system]fields[]]" variable="currentField">
|
||||
<$list filter="[<currentField>addsuffix[-secondaryList]] :except[<searchListState>get[text]]" emptyMessage="""<$link to=<<currentField>> class="tc-list-item-selected"><$text text=<<currentField>>/></$link>""">
|
||||
<$link to=<<currentField>>>
|
||||
<$text text=<<currentField>>/>
|
||||
</$link>
|
||||
</$list>
|
||||
</$list>
|
||||
</$set>
|
||||
</$linkcatcher>
|
||||
</$set>
|
||||
</div>
|
||||
</$reveal>
|
||||
</div>
|
||||
<$let currentTiddlerCSSescaped={{{ [<currentTiddler>escapecss[]] }}} currentTiddler={{{ [subfilter<get-field-value-tiddler-filter>] }}} currentField="text" currentFieldName={{{ [<newFieldNameTiddler>get[text]] }}}>
|
||||
<span class="tc-edit-field-add-value tc-small-gap-right">
|
||||
<$keyboard key="((add-field))" actions=<<new-field-actions>>>
|
||||
<$transclude tiddler={{{ [subfilter<get-field-editor-filter>] }}} />
|
||||
</$keyboard>
|
||||
</span>
|
||||
<span class="tc-edit-field-add-button">
|
||||
<$transclude $variable="new-field"/>
|
||||
</span>
|
||||
</$let>
|
||||
</$vars>
|
||||
</div>
|
||||
</$fieldmangler>
|
||||
</$set>
|
||||
\function tf.field-cascade()
|
||||
[<currentField>]
|
||||
:cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]]
|
||||
:and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
|
||||
\end
|
||||
|
||||
\function tf.get-field-editor()
|
||||
[<newFieldNameTiddler>get[text]else[]]
|
||||
:cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]]
|
||||
:and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
|
||||
\end
|
||||
|
||||
\function tf.primary-list-exceptions() created creator draft.of draft.title modified modifier tags text title type
|
||||
|
||||
\function tf.list-selection-class(listSuffix) [<searchListState>get[text]removesuffix<listSuffix>match<currentField>then[tc-list-item-selected]]
|
||||
|
||||
|
||||
<$let newFieldValueTiddlerPrefix={{{ [<newFieldValueTiddlerPrefix>!is[blank]else<qualify "$:/temp/NewFieldValue">] }}} >
|
||||
<div class="tc-edit-fields">
|
||||
<!-- table of user fields of the current tiddler -->
|
||||
<table class=`tc-edit-fields ${ [all[current]fields[]] :filter[tf.config-filter[]] :and[count[]match[0]then[tc-edit-fields-small]] }$`>
|
||||
<tbody>
|
||||
<$list filter="[all[current]fields[]] :and[sort[title]]" variable="currentField" storyview="pop">
|
||||
<%if [<currentField>tf.config-filter[]] %>
|
||||
<tr class="tc-edit-field">
|
||||
<td class="tc-edit-field-name">
|
||||
<$text text=<<currentField>>/>:
|
||||
</td>
|
||||
<td class="tc-edit-field-value">
|
||||
<$keyboard key="((delete-field))" actions="<<delete-field-actions>>">
|
||||
<$transclude tiddler=<<tf.field-cascade>> />
|
||||
</$keyboard>
|
||||
</td>
|
||||
<td class="tc-edit-field-remove">
|
||||
<$button actions="<<delete-field-actions>>"
|
||||
aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}
|
||||
class="tc-btn-invisible"
|
||||
tooltip={{$:/language/EditTemplate/Field/Remove/Hint}}
|
||||
>
|
||||
{{$:/core/images/delete-button}}
|
||||
</$button>
|
||||
</td>
|
||||
</tr>
|
||||
<%endif%>
|
||||
</$list>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- input control for new field name with selection dropdown -->
|
||||
<div class="tc-edit-field-add">
|
||||
<em class="tc-edit tc-small-gap-right">
|
||||
<<lingo Fields/Add/Prompt>>
|
||||
</em>
|
||||
<$let refreshTitle=<<qualify "$:/temp/fieldname/refresh">>
|
||||
storeTitle=<<newFieldNameInputTiddler>>
|
||||
searchListState=<<newFieldNameSelectionTiddler>>
|
||||
>
|
||||
<div class="tc-edit-field-add-name-wrapper">
|
||||
<$transclude $variable="keyboard-driven-input"
|
||||
cancelPopups="yes"
|
||||
class=`tc-edit-texteditor tc-popup-handle ${ [<newFieldNameTiddler>get[text]] :intersection[<storyTiddler>fields[]] :then[[tc-edit-field-exists]] }$`
|
||||
configTiddlerFilter="[[$:/config/EditMode/fieldname-filter]]"
|
||||
default=""
|
||||
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[fields]then[true]] :else[{$:/config/AutoFocus}match[fields]then[true]] :else[[false]] }}}
|
||||
focusPopup=<<qualify "$:/state/popup/field-dropdown">>
|
||||
inputAcceptVariantActions=<<save-tiddler-actions>>
|
||||
inputCancelActions=<<cancel-search-actions>>
|
||||
placeholder={{$:/language/EditTemplate/Fields/Add/Name/Placeholder}}
|
||||
refreshTitle=<<refreshTitle>>
|
||||
selectionStateTitle=<<searchListState>>
|
||||
storeTitle=<<storeTitle>>
|
||||
tag="input"
|
||||
tabindex={{$:/config/EditTabIndex}}
|
||||
tiddler=<<newFieldNameTiddler>>
|
||||
/>
|
||||
<$button aria-label={{$:/language/EditTemplate/Field/Dropdown/Caption}}
|
||||
class="tc-btn-invisible tc-btn-dropdown tc-small-gap"
|
||||
popup=<<qualify "$:/state/popup/field-dropdown">>
|
||||
tooltip={{$:/language/EditTemplate/Field/Dropdown/Hint}}
|
||||
>
|
||||
{{$:/core/images/down-arrow}}
|
||||
</$button>
|
||||
<$reveal state=<<qualify "$:/state/popup/field-dropdown">> type="nomatch" text="" default="" tag="div" class="tc-block-dropdown tc-edit-type-dropdown">
|
||||
<$let tv-show-missing-links="yes">
|
||||
<$linkcatcher to=<<newFieldNameTiddler>>>
|
||||
<div class="tc-dropdown-item">
|
||||
<<lingo Fields/Add/Dropdown/User>>
|
||||
</div>
|
||||
<$let newFieldName={{{ [<storeTitle>get[text]] }}}
|
||||
primaryListFields={{{ [!is[shadow]!is[system]fields[]format:titlelist[]join[ ]] }}}
|
||||
>
|
||||
<$list filter="[enlist<primaryListFields>search:title<newFieldName>sort[]] :except[tf.primary-list-exceptions[]]" variable="currentField">
|
||||
<$link to=<<currentField>> class=<<tf.list-selection-class "-primaryList">> >
|
||||
<$text text=<<currentField>>/>
|
||||
</$link>
|
||||
</$list>
|
||||
<div class="tc-dropdown-item">
|
||||
<<lingo Fields/Add/Dropdown/System>>
|
||||
</div>
|
||||
<$list filter="[fields[]search:title<newFieldName>!enlist<primaryListFields>sort[]]" variable="currentField">
|
||||
<$link to=<<currentField>> class=<<tf.list-selection-class "-secondaryList">>>
|
||||
<$text text=<<currentField>>/>
|
||||
</$link>
|
||||
</$list>
|
||||
</$let>
|
||||
</$linkcatcher>
|
||||
</$let>
|
||||
</$reveal>
|
||||
</div>
|
||||
|
||||
<!-- input control for new field content -->
|
||||
<$let currentFieldName={{{ [<newFieldNameTiddler>get[text]] }}}
|
||||
fieldEditor=<<tf.get-field-editor>>
|
||||
newFieldValueTiddler={{{ [<newFieldValueTiddlerPrefix>] [[/]] [<fieldEditor>sha256[16]] :and[join[]] }}}
|
||||
currentTiddler=<<newFieldValueTiddler>>
|
||||
>
|
||||
<span class="tc-edit-field-add-value tc-small-gap-right">
|
||||
<$keyboard key="((add-field))" actions="<<new-field-actions>>">
|
||||
<$transclude $tiddler=<<fieldEditor>> />
|
||||
</$keyboard>
|
||||
</span>
|
||||
<span class="tc-edit-field-add-button">
|
||||
<$transclude $variable="new-field"/>
|
||||
</span>
|
||||
</$let>
|
||||
</$let>
|
||||
</div>
|
||||
</$let>
|
||||
@@ -17,7 +17,7 @@ tags: $:/tags/EditTemplate
|
||||
<$let backgroundColor=<<colour>> >
|
||||
<span class="tc-tag-label tc-tag-list-item tc-small-gap-right"
|
||||
data-tag-title=<<currentTiddler>>
|
||||
style=`color:$(foregroundColor)$; background-color:$(backgroundColor)$;`
|
||||
style=`color:$(foregroundColor)$; background-color:$(backgroundColor)$; --tp-remove-tag-button-color:$(foregroundColor)$`
|
||||
>
|
||||
<$transclude tiddler=<<icon>>/>
|
||||
<$view field="title" format="text"/>
|
||||
|
||||
@@ -2,7 +2,11 @@ title: $:/core/ui/EditTemplate/title
|
||||
tags: $:/tags/EditTemplate
|
||||
|
||||
\whitespace trim
|
||||
<$edit-text field="draft.title" class="tc-titlebar tc-edit-texteditor" focus={{{ [{$:/config/AutoFocus}match[title]then[true]] ~[[false]] }}} tabindex={{$:/config/EditTabIndex}} cancelPopups="yes"/>
|
||||
<$edit-text field="draft.title" class="tc-titlebar tc-edit-texteditor"
|
||||
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[title]then[true]] ~[{$:/config/AutoFocus}match[title]then[true]] ~[[false]] }}}
|
||||
tabindex={{$:/config/EditTabIndex}}
|
||||
cancelPopups="yes"
|
||||
/>
|
||||
|
||||
<$vars pattern="""[\|\[\]{}]""" bad-chars="""`| [ ] { }`""">
|
||||
|
||||
|
||||
@@ -4,13 +4,28 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d
|
||||
|
||||
\procedure lingo-base() $:/language/EditTemplate/
|
||||
\procedure input-cancel-actions() <$list filter="[<storeTitle>get[text]] [<currentTiddler>get[type]] :and[limit[1]]" emptyMessage="""<<cancel-delete-tiddler-actions "cancel">>"""><$action-sendmessage $message="tm-remove-field" $param="type"/><$action-deletetiddler $filter="[<typeInputTiddler>] [<refreshTitle>] [<typeSelectionTiddler>]"/></$list>
|
||||
|
||||
\whitespace trim
|
||||
<$set name="refreshTitle" value=<<qualify "$:/temp/type-search/refresh">>>
|
||||
<div class="tc-edit-type-selector-wrapper">
|
||||
<em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em>
|
||||
<div class="tc-type-selector-dropdown-wrapper">
|
||||
<div class="tc-type-selector"><$fieldmangler>
|
||||
<$transclude $variable="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>> selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle tc-keep-focus" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] :else[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<<input-cancel-actions>>/><$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button><$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button>
|
||||
<div class="tc-type-selector">
|
||||
<$fieldmangler>
|
||||
<$transclude $variable="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>>
|
||||
selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}}
|
||||
focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle tc-keep-focus"
|
||||
tabindex={{$:/config/EditTabIndex}}
|
||||
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[type]then[true]] :else[{$:/config/AutoFocus}match[type]then[true]] :else[[false]] }}}
|
||||
cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]"
|
||||
inputCancelActions=<<input-cancel-actions>>
|
||||
/>
|
||||
<$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>
|
||||
{{$:/core/images/down-arrow}}
|
||||
</$button>
|
||||
<$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>
|
||||
{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/>
|
||||
</$button>
|
||||
</$fieldmangler></div>
|
||||
|
||||
<div class="tc-block-dropdown-wrapper">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
title: $:/core/Filters/StoryList
|
||||
tags: $:/tags/Filter
|
||||
filter: [list[$:/StoryList]] -$:/AdvancedSearch
|
||||
filter: [<tv-story-list>is[variable]then<tv-story-list>else[$:/StoryList]] =>storylist [list<storylist>] -$:/AdvancedSearch
|
||||
description: {{$:/language/Filters/StoryList}}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ code-body: yes
|
||||
|
||||
<$set name="languageTitle" value={{!!name}}>
|
||||
|
||||
<$transclude $tiddler="$:/core/stylesheets/custom-properties" $mode="block"/>
|
||||
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Stylesheet]!has[draft.of]]">
|
||||
<$transclude mode="block"/>
|
||||
</$list>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
title: $:/core/ui/PageTemplate/alerts
|
||||
tags: $:/tags/PageTemplate
|
||||
|
||||
<div class="tc-alerts" role="region" aria-label="Alerts">
|
||||
<div class="tc-alerts" role="region" aria-label={{$:/language/Alerts}}>
|
||||
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Alert]!is[draft]]" template="$:/core/ui/AlertTemplate" storyview="pop"/>
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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>
|
||||
@@ -15,7 +15,7 @@ caption: {{$:/language/SideBar/Open/Caption}}
|
||||
|
||||
\define droppable-item(button)
|
||||
\whitespace trim
|
||||
<$droppable actions=<<drop-actions>> enable=<<tv-allow-drag-and-drop>> tag="div">
|
||||
<$droppable actions=<<drop-actions>> enable=<<tv-enable-drag-and-drop>> tag="div">
|
||||
<<placeholder>>
|
||||
<div>
|
||||
$button$
|
||||
|
||||
@@ -2,7 +2,7 @@ title: $:/core/ui/TiddlerInfo
|
||||
|
||||
\whitespace trim
|
||||
<div style="position:relative;">
|
||||
<div class="tc-tiddler-controls" style="position:absolute;right:0;">
|
||||
<div class="tc-tiddler-controls tc-tiddler-info-controls">
|
||||
<$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">
|
||||
{{$:/core/images/close-button}}
|
||||
|
||||
38
core/ui/TiddlerInfo/Advanced/CascadeInfo.tid
Normal file
38
core/ui/TiddlerInfo/Advanced/CascadeInfo.tid
Normal file
@@ -0,0 +1,38 @@
|
||||
title: $:/core/ui/TiddlerInfo/Advanced/CascadeInfo
|
||||
tags: $:/tags/TiddlerInfo/Advanced
|
||||
|
||||
\define lingo-base() $:/language/TiddlerInfo/Advanced/CascadeInfo/
|
||||
<$let infoTiddler=<<currentTiddler>>>
|
||||
|
||||
''<<lingo Heading>>''
|
||||
|
||||
<table class="tc-max-width">
|
||||
<thead>
|
||||
<$list filter="[[View]] [[Active Cascade Filter]] [[Template]]" variable="heading">
|
||||
<th><<heading>></th>
|
||||
</$list>
|
||||
</thead>
|
||||
<$list filter="[[$:/tags/ViewTemplate]tagging[]]" variable="ViewTemplate">
|
||||
<tr>
|
||||
<$let
|
||||
view={{{ [<ViewTemplate>]+[split[/]last[]] }}}
|
||||
tagFilter=`$:/tags/ViewTemplate${ [<view>titlecase[]] }$Filter`
|
||||
activeCascadeFilterTiddler={{{ [all[shadows+tiddlers]tag<tagFilter>!is[draft]]:filter[<storyTiddler>subfilter{!!text}]+[first[]] }}}
|
||||
activeCascadeFilter={{{ [<activeCascadeFilterTiddler>get[text]] }}}
|
||||
activeTemplateTiddler={{{ [<currentTiddler>]:cascade[all[shadows+tiddlers]tag<tagFilter>!is[draft]get[text]] }}}
|
||||
>
|
||||
<%if [<activeCascadeFilterTiddler>!is[blank]]%>
|
||||
<td>
|
||||
<$link to=<<ViewTemplate>> ><<view>></$link>
|
||||
</td>
|
||||
<td>
|
||||
<$link to=<<activeCascadeFilterTiddler>> />
|
||||
</td>
|
||||
<td style="text-align:center;">
|
||||
<$link class="tc-btn-invisible" to=<<activeTemplateTiddler>>><$button class="tc-btn-invisible">{{$:/core/images/file}}</$button></$link>
|
||||
</td>
|
||||
<%endif%>
|
||||
</$let>
|
||||
</tr>
|
||||
</$list>
|
||||
</table>
|
||||
@@ -11,9 +11,13 @@ tags: $:/tags/ViewTemplate
|
||||
storyview="pop"
|
||||
variable="listItem"
|
||||
>
|
||||
<$set name="tv-config-toolbar-class" filter="[<tv-config-toolbar-class>] [<listItem>encodeuricomponent[]addprefix[tc-btn-]]">
|
||||
<$transclude tiddler=<<listItem>>/>
|
||||
</$set>
|
||||
<$let condition={{{ [<listItem>get[condition]] }}}>
|
||||
<%if [<condition>!is[blank]] :and[<currentTiddler>subfilter<condition>limit[1]] :else[<condition>is[blank]then[true]] %>
|
||||
<$set name="tv-config-toolbar-class" filter="[<tv-config-toolbar-class>] [<listItem>encodeuricomponent[]addprefix[tc-btn-]]">
|
||||
<$transclude tiddler=<<listItem>>/>
|
||||
</$set>
|
||||
<%endif%>
|
||||
</$let>
|
||||
</$list>
|
||||
</span>
|
||||
<$set name="tv-wikilinks" value={{$:/config/Tiddlers/TitleLinks}}>
|
||||
|
||||
@@ -41,6 +41,8 @@ Drag this link to copy this tool to another wiki
|
||||
</$wikify>
|
||||
\end capture-item-wikified
|
||||
|
||||
\function get.shadow.source() [shadowsource[]]
|
||||
|
||||
\procedure capture-wiki-info(tempWikiInfo)
|
||||
<$transclude $variable="capture-item-wikified" label="TiddlyWiki Version" value="<<version>>"/>
|
||||
<$transclude $variable="capture-item" label="Current palette" value={{$:/palette}}/>
|
||||
@@ -64,6 +66,7 @@ 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="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="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
|
||||
|
||||
\procedure template-header()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
title: $:/core/wiki/config/MediaQueryTrackers/DarkLightPreferred
|
||||
tags: $:/tags/MediaQueryTracker
|
||||
media-query: (prefers-color-scheme: dark)
|
||||
info-tiddler: $:/info/browser/darkmode
|
||||
info-tiddler-alt: $:/info/darkmode
|
||||
39
core/wiki/macros/CSS-property.tid
Normal file
39
core/wiki/macros/CSS-property.tid
Normal file
@@ -0,0 +1,39 @@
|
||||
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
|
||||
@@ -22,7 +22,7 @@ tags: $:/tags/Macro
|
||||
<$action-listops $tiddler=<<targetTiddler>> $field=<<targetField>> $subfilter="+[insertbefore<actionTiddler>,<currentTiddler>]"/>
|
||||
\end
|
||||
|
||||
\define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate)
|
||||
\define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate,displayField:"caption",startactions,endactions)
|
||||
\whitespace trim
|
||||
<$set name="_tiddler" value="""$tiddler$""" emptyValue=<<currentTiddler>> >
|
||||
<$let field-reference={{{ [<_tiddler>] "!!" [[$field$]] +[join[]] }}}
|
||||
@@ -39,10 +39,13 @@ tags: $:/tags/Macro
|
||||
>
|
||||
<div class="tc-droppable-placeholder"/>
|
||||
<div>
|
||||
<$transclude tiddler="""$itemTemplate$""">
|
||||
<$link to={{!!title}}>
|
||||
<$transclude tiddler=<<__itemTemplate__>>>
|
||||
<$link to={{!!title}}
|
||||
startactions=<<__startactions__>>
|
||||
endactions=<<__endactions__>>
|
||||
>
|
||||
<$let tv-wikilinks="no">
|
||||
<$transclude field="caption">
|
||||
<$transclude field=<<__displayField__>>>
|
||||
<$view field="title"/>
|
||||
</$transclude>
|
||||
</$let>
|
||||
@@ -92,7 +95,7 @@ tags: $:/tags/Macro
|
||||
</$set>
|
||||
\end
|
||||
|
||||
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"")
|
||||
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"",displayField:"title",startactions,endactions)
|
||||
\whitespace trim
|
||||
<span class="tc-tagged-draggable-list">
|
||||
<$set name="tag" value=<<__tag__>>>
|
||||
@@ -108,9 +111,16 @@ tags: $:/tags/Macro
|
||||
>
|
||||
<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/>
|
||||
<$genesis $type=<<__elementTag__>>>
|
||||
<$transclude tiddler="""$itemTemplate$""">
|
||||
<$link to={{!!title}}>
|
||||
<$view field="title"/>
|
||||
<$transclude tiddler=<<__itemTemplate__>>>
|
||||
<$link to={{!!title}}
|
||||
startactions=<<__startactions__>>
|
||||
endactions=<<__endactions__>>
|
||||
>
|
||||
<$let tv-wikilinks="no">
|
||||
<$transclude field=<<__displayField__>>>
|
||||
<$view field="title"/>
|
||||
</$transclude>
|
||||
</$let>
|
||||
</$link>
|
||||
</$transclude>
|
||||
</$genesis>
|
||||
|
||||
@@ -9,8 +9,9 @@ code-body: yes
|
||||
setTo=<<currentTab>>
|
||||
default=<<__default__>>
|
||||
selectedClass="tc-tab-selected"
|
||||
selectedAria="aria-selected"
|
||||
tooltip={{!!tooltip}}
|
||||
role="switch"
|
||||
role="tab"
|
||||
data-tab-title=<<currentTab>>
|
||||
>
|
||||
<$tiddler tiddler=<<save-currentTiddler>>>
|
||||
@@ -57,12 +58,12 @@ code-body: yes
|
||||
\whitespace trim
|
||||
<$qualify title=<<__state__>> name="qualifiedState">
|
||||
<$let tabsState={{{ [<__explicitState__>minlength[1]] ~[<qualifiedState>] }}}>
|
||||
<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}}>
|
||||
<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}} role="tablist">
|
||||
<div class={{{ [[tc-tab-buttons]addsuffix[ ]addsuffix<__class__>] }}}>
|
||||
<<tabs-tab-list>>
|
||||
</div>
|
||||
<div class={{{ [[tc-tab-divider]addsuffix[ ]addsuffix<__class__>] }}}/>
|
||||
<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}}>
|
||||
<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}} role="tabpanel">
|
||||
<<tabs-tab-body>>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 -->
|
||||
<!-- This could probably be optimized / removed if we would use different temp-tiddlers
|
||||
(future improvement because keeping track is comlex for humans)
|
||||
(future improvement because keeping track is complex for humans)
|
||||
-->
|
||||
\procedure delete-tag-state-tiddlers()
|
||||
<$action-deletetiddler $filter="[<newTagNameTiddler>] [<storeTitle>] [<tagSelectionState>]"/>
|
||||
@@ -111,13 +111,14 @@ The second ESC tries to close the "draft tiddler"
|
||||
refreshTitle=<<refreshTitle>>
|
||||
selectionStateTitle=<<tagSelectionState>>
|
||||
inputAcceptActions=<<add-tag-actions>>
|
||||
inputAcceptVariantActions=<<save-tiddler-actions>>
|
||||
inputCancelActions=<<clear-tags-actions>>
|
||||
tag="input"
|
||||
placeholder={{$:/language/EditTemplate/Tags/Add/Placeholder}}
|
||||
focusPopup=<<tf.tagpicker-dropdown-id>>
|
||||
class="tc-edit-texteditor tc-popup-handle"
|
||||
tabindex=<<tabIndex>>
|
||||
focus={{{ [{$:/config/AutoFocus}match[tags]then[true]] :else[[false]] }}}
|
||||
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[tags]then[true]] :else[{$:/config/AutoFocus}match[tags]then[true]] :else[[false]] }}}
|
||||
filterMinLength={{$:/config/Tags/MinLength}}
|
||||
cancelPopups=<<cancelPopups>>
|
||||
configTiddlerFilter="[[$:/core/macros/tag-picker]]"
|
||||
|
||||
@@ -15,7 +15,18 @@ tags: $:/tags/Macro
|
||||
</span>
|
||||
\end
|
||||
|
||||
\define toc-body(tag,sort:"",itemClassFilter,exclude,path)
|
||||
\define toc-level-indicator()
|
||||
\whitespace trim
|
||||
<%if [<__level__>compare:number:gt[0]]%>
|
||||
<%if [<currentTiddler>tagging[]] %>
|
||||
<span class="tc-tiny-gap-left">{{$:/core/images/new-button}}</span>
|
||||
<%else%>
|
||||
<span class="tc-tiny-gap-left">{{$:/core/images/blank}}</span>
|
||||
<%endif%>
|
||||
<% endif %>
|
||||
\end
|
||||
|
||||
\define toc-body(tag,sort:"",itemClassFilter,exclude,path,level)
|
||||
\whitespace trim
|
||||
<ol class="tc-toc">
|
||||
<$list filter="""[all[shadows+tiddlers]tag<__tag__>!has[draft.of]$sort$] -[<__tag__>] -[subfilter<__exclude__>]""">
|
||||
@@ -23,10 +34,26 @@ tags: $:/tags/Macro
|
||||
<$set name="excluded" filter="[subfilter<__exclude__>] [<__tag__>]">
|
||||
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item">
|
||||
<li class=<<toc-item-class>>>
|
||||
<$list filter="[all[current]toc-link[no]]" emptyMessage="<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}><<toc-caption>></$link>">
|
||||
<$list filter="[all[current]toc-link[no]]" >
|
||||
<$list-empty>
|
||||
<!-- link to target-field or currentTiddler -->
|
||||
<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}>
|
||||
<<toc-level-indicator>>
|
||||
<<toc-caption>>
|
||||
</$link>
|
||||
</$list-empty>
|
||||
<!-- toc-link = no -->
|
||||
<<toc-level-indicator>>
|
||||
<<toc-caption>>
|
||||
</$list>
|
||||
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>>/>
|
||||
<$let _level={{{ [<__level__>subtract[1]] }}}>
|
||||
<%if [<_level>compare:number:gt[0]]%>
|
||||
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>> level=<<_level>>/>
|
||||
<%elseif [<_level>match[-1]]%>
|
||||
<!-- show full toc, no level defined -->
|
||||
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>>/>
|
||||
<%endif%>
|
||||
</$let>
|
||||
</li>
|
||||
</$set>
|
||||
</$set>
|
||||
@@ -35,10 +62,10 @@ tags: $:/tags/Macro
|
||||
</ol>
|
||||
\end
|
||||
|
||||
\define toc(tag,sort:"",itemClassFilter:"", exclude)
|
||||
\define toc(tag,sort:"",itemClassFilter:"",exclude,level)
|
||||
\whitespace trim
|
||||
<$let __tag__={{{ [<__tag__>is[blank]then<currentTiddler>else<__tag__>] }}} >
|
||||
<$macrocall $name="toc-body" tag=<<__tag__>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<__exclude__>>/>
|
||||
<$macrocall $name="toc-body" tag=<<__tag__>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<__exclude__>> level=<<__level__>>/>
|
||||
</$let>
|
||||
\end
|
||||
|
||||
|
||||
8
core/wiki/minifocuseditswitcher.tid
Normal file
8
core/wiki/minifocuseditswitcher.tid
Normal file
@@ -0,0 +1,8 @@
|
||||
title: $:/snippets/minifocuseditswitcher
|
||||
|
||||
\whitespace trim
|
||||
<$select tiddler="$:/config/AutoFocusEdit" default={{$:/config/AutoFocus}}>
|
||||
<$list filter="title tags text">
|
||||
<option><<currentTiddler>></option>
|
||||
</$list>
|
||||
</$select>
|
||||
@@ -1,2 +1,2 @@
|
||||
title: $:/tags/TiddlerInfo/Advanced
|
||||
list: [[$:/core/ui/TiddlerInfo/Advanced/ShadowInfo]] [[$:/core/ui/TiddlerInfo/Advanced/PluginInfo]]
|
||||
list: [[$:/core/ui/TiddlerInfo/Advanced/ShadowInfo]] [[$:/core/ui/TiddlerInfo/Advanced/PluginInfo]] [[$:/core/ui/TiddlerInfo/Advanced/CascadeInfo]]
|
||||
@@ -20,8 +20,7 @@
|
||||
"index": [
|
||||
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
|
||||
"empty": [
|
||||
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain",
|
||||
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.hta","text/plain"],
|
||||
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain"],
|
||||
"favicon": [
|
||||
"--savetiddler","$:/favicon.ico","favicon.ico"],
|
||||
"static": [
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"index": [
|
||||
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
|
||||
"empty": [
|
||||
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain",
|
||||
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.hta","text/plain"],
|
||||
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain"],
|
||||
"favicon": [
|
||||
"--savetiddler","$:/favicon.ico","favicon.ico"],
|
||||
"static": [
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
"index": [
|
||||
"--render","$:/core/save/all","index.html","text/plain"],
|
||||
"empty": [
|
||||
"--render","$:/core/save/all","empty.html","text/plain",
|
||||
"--render","$:/core/save/all","empty.hta","text/plain"],
|
||||
"--render","$:/core/save/all","empty.html","text/plain"],
|
||||
"emptyexternalcore": [
|
||||
"--render","$:/core/save/offline-external-js","empty-external-core.html","text/plain",
|
||||
"--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffix<version>addsuffix[.js]]","text/plain"],
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
"--setfield","[tag[external-image]] [tag[external-text]]","text","","text/plain",
|
||||
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
|
||||
"empty": [
|
||||
"--rendertiddler","$:/editions/es-ES/download-empty","empty.html","text/plain",
|
||||
"--rendertiddler","$:/editions/es-ES/download-empty","empty.hta","text/plain"],
|
||||
"--rendertiddler","$:/editions/es-ES/download-empty","empty.html","text/plain"],
|
||||
"favicon": [
|
||||
"--savetiddler","$:/favicon.ico","favicon.ico",
|
||||
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
created: 20150220191009000
|
||||
modified: 20150602092431500
|
||||
title: $:/editions/tw5.com/railroad/macro-parameter-value
|
||||
title: $:/editions/tw5.com/railroad/call-parameter-value
|
||||
type: text/vnd.tiddlywiki.railroad
|
||||
|
||||
( '"""' [:{/'tout sauf """'/}] '"""'
|
||||
| '"' [:{/'tout sauf "'/}] '"'
|
||||
| "'" [:{/"tout sauf '"/}] "'"
|
||||
| "[[" [:{/"tout sauf ]"/}] "]]"
|
||||
| "`" [:{/"tout sauf `"/}] "`"
|
||||
| "```" [:{/"tout sauf ```"/}] "```"
|
||||
| {/"""tout sauf ' " ou espacevierge"""/}
|
||||
)
|
||||
|
||||
@@ -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>>
|
||||
|
||||
<$railroad text={{$:/editions/tw5.com/railroad/macro-parameter-value}}/>
|
||||
<$railroad text={{$:/editions/tw5.com/railroad/call-parameter-value}}/>
|
||||
|
||||
@@ -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<<:>>
|
||||
|
||||
<$railroad text={{$:/editions/tw5.com/railroad/macro-parameter-value}}/>
|
||||
<$railroad text={{$:/editions/tw5.com/railroad/call-parameter-value}}/>
|
||||
|
||||
La définition de la <<.place suite>> se fait comme suit<<:>>
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1,17 @@
|
||||
caption: savetiddlers
|
||||
color: #4DB6AC
|
||||
community-author: buggyj
|
||||
created: 20171109171935039
|
||||
delivery: Browser Extension
|
||||
description: Extension pour les navigateur Firefox
|
||||
fr-title:
|
||||
method: save
|
||||
modified: 20250809092435788
|
||||
tags: Firefox Saving [[Other Resources]] plugins
|
||||
title: savetiddlers: Extension for Firefox by buggyj
|
||||
type: text/vnd.tiddlywiki
|
||||
url: https://github.com/buggyj/savetiddlers
|
||||
|
||||
Une extension 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.
|
||||
|
||||
{{!!url}}
|
||||
@@ -27,8 +27,7 @@
|
||||
"--setfield","[tag[external-image]] [tag[external-text]]","text","","text/plain",
|
||||
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
|
||||
"empty": [
|
||||
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.html","text/plain",
|
||||
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.hta","text/plain"],
|
||||
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.html","text/plain"],
|
||||
"favicon": [
|
||||
"--savetiddler","$:/favicon.ico","favicon.ico",
|
||||
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
caption: savetiddlers
|
||||
color: #4DB6AC
|
||||
community-author: Buggyj
|
||||
created: 20171109171935039
|
||||
delivery: Browser Extension
|
||||
description: ChromeとFirefoxのブラウザ拡張機能
|
||||
method: save
|
||||
modified: 20241014110546647
|
||||
original-modified: 20210106151027189
|
||||
tags: Chrome Firefox Saving [[Other Resources]] plugins
|
||||
title: "savetiddlers" Extension for Chrome and Firefox by buggyj
|
||||
ja-title: buggyjによるChromeとFirefoxの"savetiddlers"拡張機能
|
||||
type: text/vnd.tiddlywiki
|
||||
url: https://github.com/buggyj/savetiddlers
|
||||
|
||||
Google ChromeとMozilla Firefoxの拡張機能で、TiddlyWikiの組み込み[[HTML5セーバー|Saving with the HTML5 saver]]による使いにくさの一部を解消し、正しく設定すればTiddlyFoxとほぼ同じくらい簡単に使用できるようになります。
|
||||
|
||||
https://github.com/buggyj/savetiddlers
|
||||
@@ -0,0 +1,18 @@
|
||||
caption: savetiddlers
|
||||
color: #4DB6AC
|
||||
community-author: buggyj
|
||||
created: 20171109171935039
|
||||
delivery: Browser Extension
|
||||
description: Firefoxのブラウザ拡張機能
|
||||
method: save
|
||||
modified: 20250809092435788
|
||||
original-modified: 20250809092435788
|
||||
tags: Firefox Saving [[Other Resources]] plugins
|
||||
title: savetiddlers: Extension for Firefox by buggyj
|
||||
ja-title: buggyjによるFirefoxの"savetiddlers"拡張機能
|
||||
type: text/vnd.tiddlywiki
|
||||
url: https://github.com/buggyj/savetiddlers
|
||||
|
||||
Mozilla Firefoxの拡張機能で、TiddlyWikiの組み込み[[HTML5セーバー|Saving with the HTML5 saver]]による使いにくさの一部を解消し、正しく設定すれば[[TiddlyFox]]とほぼ同じくらい簡単に使用できるようになります。
|
||||
|
||||
{{!!url}}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user