mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-02-09 19:50:22 +00:00
Compare commits
136 Commits
let-filter
...
mvv-furthe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15f74118a6 | ||
|
|
bddd7bab17 | ||
|
|
c0dd429274 | ||
|
|
dd116af41b | ||
|
|
50ec0dd4b6 | ||
|
|
0177f09823 | ||
|
|
643cabf9c8 | ||
|
|
5cf3fcd843 | ||
|
|
67f13c585d | ||
|
|
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 | ||
|
|
1b8a2caa23 | ||
|
|
ecba671bcf | ||
|
|
fc1e53a777 | ||
|
|
2b6bdcbf97 | ||
|
|
119706a918 | ||
|
|
86d15585b6 | ||
|
|
4b824795c8 | ||
|
|
cd173a959d | ||
|
|
86d61e09bd | ||
|
|
7c197f6ecc | ||
|
|
8f8b46dab7 | ||
|
|
7b013af240 | ||
|
|
05f3e6d5a0 | ||
|
|
48eeb4603a | ||
|
|
fc74219c0b | ||
|
|
86c4770a28 | ||
|
|
ffde2da16c | ||
|
|
da41a55f29 | ||
|
|
846ac9a0dd | ||
|
|
65edda224b | ||
|
|
41dac42f3b | ||
|
|
87d9754a4e | ||
|
|
5cd3084298 | ||
|
|
4c27c09b4d | ||
|
|
4bb0bc5527 | ||
|
|
6cb333b65b | ||
|
|
3378497816 | ||
|
|
033d5cf225 | ||
|
|
52d73eb1a8 | ||
|
|
5d1c1eaf87 | ||
|
|
d81204c6ab | ||
|
|
e001c21bf5 |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
boot/** -linguist-generated
|
||||
**/tiddlywiki.files linguist-language=JSON
|
||||
**/tiddlywiki.info linguist-language=JSON
|
||||
**/plugin.info linguist-language=JSON
|
||||
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. -->
|
||||
67
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
67
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,67 +0,0 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve TiddlyWiki 5
|
||||
title: "[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.
|
||||
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -4,17 +4,23 @@ about: Suggest an idea for TiddlyWiki 5
|
||||
title: "[IDEA]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
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 \
|
||||
|
||||
79
boot/boot.js
79
boot/boot.js
@@ -8,6 +8,8 @@ On the server this file is executed directly to boot TiddlyWiki. In the browser,
|
||||
|
||||
\*/
|
||||
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
var _boot = (function($tw) {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
@@ -44,12 +46,8 @@ $tw.utils.hop = function(object,property) {
|
||||
return object ? Object.prototype.hasOwnProperty.call(object,property) : false;
|
||||
};
|
||||
|
||||
/*
|
||||
Determine if a value is an array
|
||||
*/
|
||||
$tw.utils.isArray = function(value) {
|
||||
return Object.prototype.toString.call(value) == "[object Array]";
|
||||
};
|
||||
/** @deprecated Use Array.isArray instead */
|
||||
$tw.utils.isArray = value => Array.isArray(value);
|
||||
|
||||
/*
|
||||
Check if an array is equal by value and by reference.
|
||||
@@ -128,35 +126,22 @@ $tw.utils.pushTop = function(array,value) {
|
||||
return array;
|
||||
};
|
||||
|
||||
/*
|
||||
Determine if a value is a date
|
||||
*/
|
||||
$tw.utils.isDate = function(value) {
|
||||
return Object.prototype.toString.call(value) === "[object Date]";
|
||||
};
|
||||
/** @deprecated Use instanceof Date instead */
|
||||
$tw.utils.isDate = value => value instanceof Date;
|
||||
|
||||
/*
|
||||
Iterate through all the own properties of an object or array. Callback is invoked with (element,title,object)
|
||||
*/
|
||||
/** @deprecated Use array iterative methods instead */
|
||||
$tw.utils.each = function(object,callback) {
|
||||
var next,f,length;
|
||||
if(object) {
|
||||
if(Object.prototype.toString.call(object) == "[object Array]") {
|
||||
for(f=0, length=object.length; f<length; f++) {
|
||||
next = callback(object[f],f,object);
|
||||
if(next === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(Array.isArray(object)) {
|
||||
object.every((element,index,array) => {
|
||||
const next = callback(element,index,array);
|
||||
return next !== false;
|
||||
});
|
||||
} else {
|
||||
var keys = Object.keys(object);
|
||||
for(f=0, length=keys.length; f<length; f++) {
|
||||
var key = keys[f];
|
||||
next = callback(object[key],key,object);
|
||||
if(next === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Object.entries(object).every(entry => {
|
||||
const next = callback(entry[1], entry[0], object);
|
||||
return next !== false;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -335,28 +320,26 @@ $tw.utils.htmlDecode = function(s) {
|
||||
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() {
|
||||
var href = window.location.href;
|
||||
var idx = href.indexOf('#');
|
||||
const href = window.location.href,
|
||||
idx = href.indexOf("#");
|
||||
|
||||
if(idx === -1) {
|
||||
return "#";
|
||||
} else if(href.substr(idx + 1,1) === "#" || href.substr(idx + 1,3) === "%23") {
|
||||
}
|
||||
|
||||
const afterHash = href.substring(idx + 1);
|
||||
if(afterHash.startsWith("#") || afterHash.startsWith("%23")) {
|
||||
// Special case: ignore location hash if it itself starts with a #
|
||||
return "#";
|
||||
} else {
|
||||
return href.substring(idx);
|
||||
}
|
||||
return href.substring(idx);
|
||||
};
|
||||
|
||||
/*
|
||||
Pad a string to a given length with "0"s. Length defaults to 2
|
||||
*/
|
||||
$tw.utils.pad = function(value,length) {
|
||||
length = length || 2;
|
||||
var s = value.toString();
|
||||
if(s.length < length) {
|
||||
s = "000000000000000000000000000".substr(0,length - s.length) + s;
|
||||
}
|
||||
return s;
|
||||
|
||||
/** @deprecated Pad a string to a given length with "0"s. Length defaults to 2 */
|
||||
$tw.utils.pad = function(value,length = 2) {
|
||||
const s = value.toString();
|
||||
return s.padStart(length, "0");
|
||||
};
|
||||
|
||||
// Convert a date into UTC YYYYMMDDHHMMSSmmm format
|
||||
@@ -630,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);
|
||||
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)
|
||||
@@ -2801,6 +2784,8 @@ return $tw;
|
||||
|
||||
});
|
||||
|
||||
/* eslint-enable @stylistic/indent */
|
||||
|
||||
if(typeof(exports) !== "undefined") {
|
||||
exports.TiddlyWiki = _boot;
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,8 @@ See Boot.js for further details of the boot process.
|
||||
|
||||
\*/
|
||||
|
||||
/* eslint-disable @stylistic/indent */
|
||||
|
||||
var _bootprefix = (function($tw) {
|
||||
|
||||
"use strict";
|
||||
@@ -114,6 +116,8 @@ return $tw;
|
||||
|
||||
});
|
||||
|
||||
/* eslint-enable @stylistic/indent */
|
||||
|
||||
if(typeof(exports) === "undefined") {
|
||||
// Set up $tw global for the browser
|
||||
window.$tw = _bootprefix(window.$tw);
|
||||
|
||||
14
community/people/ChristianByron.tid
Normal file
14
community/people/ChristianByron.tid
Normal file
@@ -0,0 +1,14 @@
|
||||
title: @Christian_Byron
|
||||
tags: Community/Person
|
||||
fullname: Christian Byron
|
||||
talk.tiddlywiki.org: Christian_Byron
|
||||
github: ceebeetree
|
||||
linkedin: www.linkedin.com/in/christian-byron-b84a594/
|
||||
avatar: /9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgICAgJCAkKCgkNDgwODRMREBARExwUFhQWFBwrGx8bGx8bKyYuJSMlLiZENS8vNUROQj5CTl9VVV93cXecnNEBCAgICAkICQoKCQ0ODA4NExEQEBETHBQWFBYUHCsbHxsbHxsrJi4lIyUuJkQ1Ly81RE5CPkJOX1VVX3dxd5yc0f/CABEIACAAIAMBIgACEQEDEQH/xAAuAAEBAAMBAAAAAAAAAAAAAAAHBgEDBQQBAAMBAAAAAAAAAAAAAAAAAAABAwX/2gAMAwEAAhADEAAAADv2xtJlY03sqePW3ARS1RSydIhcH//EACcQAAICAgIBAgYDAAAAAAAAAAECAwQFEQASMRMhBhBBk8HRIzJx/9oACAEBAAE/AMFQxs+NExqJLMCwYE+SOT4bF3qr+hAIpRsDQ6lWH0Yco4S/eVniRVQHXZzrZ5dwGQpQtNII2RfJVvHMRl5cbKxC94n/ALp+RxfiKpNcgMMUqPIwjcnWip/I5XtUowaL3Ujir/xt79Glb6/4OZ7MV5oEpUzuIa7MPB14A5jpoYLsEsydo1bbLre+CWEEEYab7Uf74ZYSSThpvtR/vmRmhnuzywp1jZtquta+VPM49qlcy24lf017At7g8uZnHrUsGK3Ez+m3UBvcnXy//8QAHhEAAgEFAAMAAAAAAAAAAAAAAQIDAAQRIkEyUaH/2gAIAQIBAT8AmiuVlZkLEeQOflJPcvMAF0z65V+h0YIW52rBDuxUrztf/8QAIxEBAAEDAwMFAAAAAAAAAAAAAgEAAxEEBSMSQcEiMVJxof/aAAgBAwEBPwC/Z1ZvNBOYz1Gc/lDUat3ySPRM/H2P3W4hcbIldpxnxW3BcjQk9oznzX//2Q==
|
||||
|
||||
Hello ~TiddlyWikiers - I have been a long time fan, recent contributor to the TW community.
|
||||
Recently I have volunteered to run the [[TiddlyWiki Newsletter|https://tiddlywiki.substack.com/]] to spread the great news about TW.
|
||||
|
||||
I have been in the IT industry for about thirty years, mostly as a consultant and technical arcitect.
|
||||
More recently I went back to study a masters in IT focussing on AI and data science.
|
||||
Now my partner and I have started our own business ([[Sphere Innovations|https://sphere-innovations.com.au]]) - in consulting and building web applications for small to medium size businesses here in Australia.
|
||||
19
community/people/LinOnetwo.tid
Normal file
19
community/people/LinOnetwo.tid
Normal file
@@ -0,0 +1,19 @@
|
||||
avatar: /9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgICAgJCAkKCgkNDgwODRMREBARExwUFhQWFBwrGx8bGx8bKyYuJSMlLiZENS8vNUROQj5CTl9VVV93cXecnNEBCAgICAkICQoKCQ0ODA4NExEQEBETHBQWFBYUHCsbHxsbHxsrJi4lIyUuJkQ1Ly81RE5CPkJOX1VVX3dxd5yc0f/CABEIACAAIAMBIgACEQEDEQH/xAAuAAEAAwEBAAAAAAAAAAAAAAAGAwQHAgUBAAMBAAAAAAAAAAAAAAAAAAACAwT/2gAMAwEAAhADEAAAAOfCWAMdKKetM4wOvY5OcvZnrYf/xAApEAACAQQBBAECBwAAAAAAAAABAgMABAURQQYSIVETFCIxMkJicYKh/9oACAEBAAE/AEtysaStr7mPaPeuazWdMM4gEnfPryW8hBUuZvou2RXRxyreBWPmgyNqs8f8MOQalhdY7Vz+R4/s/qfP+1edNi/zl7HDcFbmS3E8CcMR4INP0PkBhklIm+sZNtFtQiV0nj57Owl+dSrSTFgD6/CtH4VV9lU3oAbPngAVY389lc5URuUZkMxhnR4pvW0VwDqsP1FNmLWYqCpikMbngmliJNY+aKzyTxXS6lRAyg/u5rq+5x2RsuyTa3MQMlvKniRGThTUd1JYXUdzAwDvqVxGdRXMbfrVOD7HBrG3mNEsU8z98TRhl9eRzX//xAAcEQACAgIDAAAAAAAAAAAAAAABAgARAzESIVH/2gAIAQIBAT8ARuXZPsul3Eoje5lBQWBP/8QAGREAAwEBAQAAAAAAAAAAAAAAAAECEiER/9oACAEDAQE/AM98Lk7LJe20z//Z
|
||||
created: 20251110102157310
|
||||
first-sighting: 2019-03-01
|
||||
fullname: Lin Onetwo
|
||||
github: linonetwo
|
||||
homepage: https://wiki.onetwo.website/
|
||||
modified: 20251111184556193
|
||||
tags: Community/Person Community/Team/Contributors
|
||||
talk.tiddlywiki.org: linonetwo
|
||||
title: @linonetwo
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
Since 2014, when I started college, I've been on a quest for a lifelong PKM tool. I cherish my life and all my experiences, and I don’t want to forget any of them. When I’m deeply focused on a task, it’s easy to lose sight of other important parts of my life—so I needed a system to help me stay balanced.
|
||||
|
||||
Early on, I tried TiddlyWiki several times, but I was initially put off by its save mechanism and markup editing. That changed when I discovered an auto-backup script, which gave me the confidence to fully commit. Over time, I improved the script and eventually transitioned to using TidGi-Desktop and TidGi-Mobile.
|
||||
|
||||
Today, my TiddlyWiki holds all my game design ideas and progress logs—it has truly become my second brain. With the help of LLM-powered programming tools, I’ve enhanced it with numerous plugins, allowing me to manage my mind in a more programmable and structured way. As a game developer, TiddlyWiki isn't the core of my professional work; But I've invested so much time because it's fundamentally about upgrading my mind.
|
||||
|
||||
Most of my notes are open by default and shared publicly on my homepage as a digital garden.
|
||||
@@ -1,6 +0,0 @@
|
||||
title: Newsletter Team
|
||||
tags: Community/Team
|
||||
modified: 20250909171928024
|
||||
created: 20250909171928024
|
||||
|
||||
The Newsletter Team is responsible for producing the TiddlyWiki Newsletter, a monthly email newsletter that highlights news, updates, and community contributions related to TiddlyWiki.
|
||||
11
community/project/teams/Quality Assurance Team.tid
Normal file
11
community/project/teams/Quality Assurance Team.tid
Normal file
@@ -0,0 +1,11 @@
|
||||
title: Quality Assurance Team
|
||||
created: 20251112125742296
|
||||
modified: 20251112125742296
|
||||
tags: Community/Team
|
||||
team:
|
||||
leader: @Leilei332
|
||||
|
||||
title: Quality Assurance Team
|
||||
|
||||
The Quality Assurance Team is responsible for ensuring the quality and reliability of TiddlyWiki releases. This includes reviewing code submissions, testing new features, identifying bugs, and verifying that fixes are effective.
|
||||
|
||||
15
community/project/teams/TiddlyWiki Newsletter Team.tid
Normal file
15
community/project/teams/TiddlyWiki Newsletter Team.tid
Normal file
@@ -0,0 +1,15 @@
|
||||
title: TiddlyWiki Newsletter Team
|
||||
tags: Community/Team
|
||||
modified: 20251219090709874
|
||||
created: 20250909171928024
|
||||
leader: @Christian_Byron
|
||||
|
||||
The Newsletter Team is responsible for producing the [[TiddlyWiki Newsletter]]. We would love to have your help if you would like to get involved.
|
||||
|
||||
! Audience
|
||||
|
||||
The newsletter is intended for TiddlyWiki end users who do not track all the discussions on https://talk.tiddlywiki.org/.
|
||||
|
||||
Coverage of developer topics such as JavaScript and intricate wikitext should be handled thoughtfully to avoid alienating the core audience of end users.
|
||||
|
||||
Subscribing to the newsletter is intended to give people confidence that they will not miss any important developments.
|
||||
@@ -1,5 +1,5 @@
|
||||
title: Community/Team
|
||||
modified: 20250909171928024
|
||||
created: 20250909171928024
|
||||
list: [[Project Team]] [[Core Team]] [[Documentation Team]] [[MultiWikiServer Team]] [[Newsletter Team]] [[Infrastructure Team]] [[Succession Team]]
|
||||
list: [[Project Team]] [[Core Team]] [[Documentation Team]] [[Quality Assurance Team]] [[Infrastructure Team]] [[MultiWikiServer Team]] [[Newsletter Team]] [[Succession Team]]
|
||||
|
||||
|
||||
@@ -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,11 @@ 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
|
||||
Advanced/CascadeInfo/Hint: These are the view template segments (tagged <<tag "$:/tags/ViewTemplate">>) using a cascade filter and their resulting template for the current tiddler.
|
||||
Advanced/CascadeInfo/Detail/View: View
|
||||
Advanced/CascadeInfo/Detail/ActiveCascadeFilter: Active cascade filter
|
||||
Advanced/CascadeInfo/Detail/Template: Template
|
||||
Fields/Caption: Fields
|
||||
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;
|
||||
@@ -156,8 +156,8 @@ Fix the height of textarea to fit content
|
||||
FramedEngine.prototype.fixHeight = function() {
|
||||
// Make sure styles are updated
|
||||
this.copyStyles();
|
||||
// Adjust height
|
||||
if(this.widget.editTag === "textarea") {
|
||||
// If .editRows is initialised, it takes precedence
|
||||
if(this.widget.editTag === "textarea" && !this.widget.editRows) {
|
||||
if(this.widget.editAutoHeight) {
|
||||
if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) {
|
||||
var newHeight = $tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight);
|
||||
|
||||
@@ -100,7 +100,8 @@ SimpleEngine.prototype.getText = function() {
|
||||
Fix the height of textarea to fit content
|
||||
*/
|
||||
SimpleEngine.prototype.fixHeight = function() {
|
||||
if(this.widget.editTag === "textarea") {
|
||||
// If .editRows is initialised, it takes precedence
|
||||
if((this.widget.editTag === "textarea") && !this.widget.editRows) {
|
||||
if(this.widget.editAutoHeight) {
|
||||
if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) {
|
||||
$tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight);
|
||||
|
||||
@@ -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;
|
||||
@@ -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 {
|
||||
@@ -142,7 +143,14 @@ exports.parseParameterDefinition = function(paramString,options) {
|
||||
var paramInfo = {name: paramMatch[1]},
|
||||
defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5];
|
||||
if(defaultValue !== undefined) {
|
||||
paramInfo["default"] = defaultValue;
|
||||
// Check for an MVV reference ((varname))
|
||||
var mvvDefaultMatch = /^\(\(([^)|]+)\)\)$/.exec(defaultValue);
|
||||
if(mvvDefaultMatch) {
|
||||
paramInfo.defaultType = "multivalue-variable";
|
||||
paramInfo.defaultVariable = mvvDefaultMatch[1];
|
||||
} else {
|
||||
paramInfo["default"] = defaultValue;
|
||||
}
|
||||
}
|
||||
params.push(paramInfo);
|
||||
// Look for the next parameter
|
||||
@@ -173,7 +181,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 +214,201 @@ 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;
|
||||
};
|
||||
|
||||
/*
|
||||
Look for an MVV (multi-valued variable) reference as a transclusion, i.e. ((varname)) or ((varname params))
|
||||
Returns null if not found, or a parse tree node of type "transclude" with isMVV: true
|
||||
*/
|
||||
exports.parseMVVReferenceAsTransclusion = function(source,pos) {
|
||||
var node = {
|
||||
type: "transclude",
|
||||
isMVV: true,
|
||||
start: pos,
|
||||
attributes: {},
|
||||
orderedAttributes: []
|
||||
};
|
||||
// Define our regexps
|
||||
var reVarName = /([^\s>"'=:)]+)/g;
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Look for a double opening parenthesis
|
||||
var token = $tw.utils.parseTokenString(source,pos,"((");
|
||||
if(!token) {
|
||||
return null;
|
||||
}
|
||||
pos = token.end;
|
||||
// Get the variable name
|
||||
token = $tw.utils.parseTokenRegExp(source,pos,reVarName);
|
||||
if(!token) {
|
||||
return null;
|
||||
}
|
||||
$tw.utils.addAttributeToParseTreeNode(node,"$variable",token.match[1]);
|
||||
pos = token.end;
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Look for a double closing parenthesis
|
||||
token = $tw.utils.parseTokenString(source,pos,"))");
|
||||
if(!token) {
|
||||
return null;
|
||||
}
|
||||
node.end = token.end;
|
||||
return node;
|
||||
};
|
||||
|
||||
/*
|
||||
Parse macro parameters as attributes. Returns the position after the last attribute
|
||||
*/
|
||||
exports.parseMacroParametersAsAttributes = function(node,source,pos) {
|
||||
var position = 0,
|
||||
attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos);
|
||||
while(attribute) {
|
||||
if(!attribute.name) {
|
||||
attribute.name = (position++) + "";
|
||||
attribute.isPositional = true;
|
||||
}
|
||||
node.orderedAttributes.push(attribute);
|
||||
node.attributes[attribute.name] = attribute;
|
||||
pos = attribute.end;
|
||||
// Get the next attribute
|
||||
attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos);
|
||||
}
|
||||
node.end = pos;
|
||||
return pos;
|
||||
};
|
||||
|
||||
/*
|
||||
Parse a macro parameter as an attribute. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}, with the name being optional
|
||||
*/
|
||||
exports.parseMacroParameterAsAttribute = function(source,pos) {
|
||||
var node = {
|
||||
start: pos
|
||||
};
|
||||
// Define our regexps
|
||||
var reAttributeName = /([^\/\s>"'`=:]+)/g,
|
||||
reUnquotedAttribute = /((?:(?:>(?!>))|[^\s>"'])+)/g,
|
||||
reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g,
|
||||
reIndirectValue = /\{\{([^\}]+)\}\}/g,
|
||||
reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g;
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Get the attribute name and the separator token
|
||||
var nameToken = $tw.utils.parseTokenRegExp(source,pos,reAttributeName),
|
||||
namePos = nameToken && $tw.utils.skipWhiteSpace(source,nameToken.end),
|
||||
separatorToken = nameToken && $tw.utils.parseTokenRegExp(source,namePos,/=|:/g),
|
||||
isNewStyleSeparator = false; // If there is no separator then we don't allow new style values
|
||||
// If we have a name and a separator then we have a named attribute
|
||||
if(nameToken && separatorToken) {
|
||||
node.name = nameToken.match[1];
|
||||
// key value separator is `=` or `:`
|
||||
node.assignmentOperator = separatorToken.match[0];
|
||||
pos = separatorToken.end;
|
||||
isNewStyleSeparator = (node.assignmentOperator === "=");
|
||||
}
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Look for a string literal
|
||||
var stringLiteral = $tw.utils.parseStringLiteral(source,pos);
|
||||
if(stringLiteral) {
|
||||
pos = stringLiteral.end;
|
||||
node.type = "string";
|
||||
node.value = stringLiteral.value;
|
||||
// Mark the value as having been quoted in the source
|
||||
node.quoted = true;
|
||||
} else {
|
||||
// Look for a filtered value
|
||||
var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue);
|
||||
if(filteredValue && isNewStyleSeparator) {
|
||||
pos = filteredValue.end;
|
||||
node.type = "filtered";
|
||||
node.filter = filteredValue.match[1];
|
||||
} else {
|
||||
// Look for an indirect value
|
||||
var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
|
||||
if(indirectValue && isNewStyleSeparator) {
|
||||
pos = indirectValue.end;
|
||||
node.type = "indirect";
|
||||
node.textReference = indirectValue.match[1];
|
||||
} else {
|
||||
// Look for a macro invocation value
|
||||
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
|
||||
if(macroInvocation && isNewStyleSeparator) {
|
||||
pos = macroInvocation.end;
|
||||
node.type = "macro";
|
||||
node.value = macroInvocation;
|
||||
} else {
|
||||
// Look for an MVV reference value
|
||||
var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
|
||||
if(mvvReference && isNewStyleSeparator) {
|
||||
pos = mvvReference.end;
|
||||
node.type = "macro";
|
||||
node.value = mvvReference;
|
||||
node.isMVV = true;
|
||||
} else {
|
||||
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
|
||||
if(substitutedValue && isNewStyleSeparator) {
|
||||
pos = substitutedValue.end;
|
||||
node.type = "substituted";
|
||||
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
|
||||
} else {
|
||||
// Look for a unquoted value
|
||||
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
|
||||
if(unquotedValue) {
|
||||
pos = unquotedValue.end;
|
||||
node.type = "string";
|
||||
node.value = unquotedValue.match[1];
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bail if we don't have a value
|
||||
if(!node.type) {
|
||||
return null;
|
||||
}
|
||||
// Update the end position
|
||||
node.end = pos;
|
||||
return node;
|
||||
};
|
||||
|
||||
@@ -296,7 +477,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 = {
|
||||
@@ -346,19 +527,20 @@ exports.parseAttribute = function(source,pos) {
|
||||
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];
|
||||
// Look for a macro invocation value
|
||||
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
|
||||
if(macroInvocation) {
|
||||
pos = macroInvocation.end;
|
||||
node.type = "macro";
|
||||
node.value = macroInvocation;
|
||||
} else {
|
||||
// Look for a macro invocation value
|
||||
var macroInvocation = $tw.utils.parseMacroInvocation(source,pos);
|
||||
if(macroInvocation) {
|
||||
pos = macroInvocation.end;
|
||||
// Look for an MVV reference value
|
||||
var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
|
||||
if(mvvReference) {
|
||||
pos = mvvReference.end;
|
||||
node.type = "macro";
|
||||
node.value = macroInvocation;
|
||||
node.value = mvvReference;
|
||||
node.isMVV = true;
|
||||
} else {
|
||||
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
|
||||
if(substitutedValue) {
|
||||
@@ -366,8 +548,16 @@ exports.parseAttribute = function(source,pos) {
|
||||
node.type = "substituted";
|
||||
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
|
||||
} else {
|
||||
node.type = "string";
|
||||
node.value = "true";
|
||||
// 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 {
|
||||
node.type = "string";
|
||||
node.value = "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,6 +565,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";
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ Instantiate parse rule
|
||||
exports.init = function(parser) {
|
||||
this.parser = parser;
|
||||
// Regexp to match
|
||||
this.matchRegExp = /\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*))?\)(\s*\r?\n)?/mg;
|
||||
this.matchRegExp = /\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*(?:\)\)[^)]*)*))?\)(\s*\r?\n)?/mg;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -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
|
||||
|
||||
95
core/modules/parsers/wikiparser/rules/mvvdisplayinline.js
Normal file
95
core/modules/parsers/wikiparser/rules/mvvdisplayinline.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/*\
|
||||
title: $:/core/modules/parsers/wikiparser/rules/mvvdisplayinline.js
|
||||
type: application/javascript
|
||||
module-type: wikirule
|
||||
|
||||
Wiki rule for inline display of multi-valued variables and filter results.
|
||||
|
||||
Variable display: ((varname)) or ((varname||separator))
|
||||
Filter display: (((filter))) or (((filter||separator)))
|
||||
|
||||
The default separator is ", " (comma space).
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
exports.name = "mvvdisplayinline";
|
||||
exports.types = {inline: true};
|
||||
|
||||
exports.init = function(parser) {
|
||||
this.parser = parser;
|
||||
};
|
||||
|
||||
exports.findNextMatch = function(startPos) {
|
||||
var source = this.parser.source;
|
||||
var nextStart = startPos;
|
||||
while((nextStart = source.indexOf("((",nextStart)) >= 0) {
|
||||
if(source.charAt(nextStart + 2) === "(") {
|
||||
// Filter mode: (((filter))) or (((filter||sep)))
|
||||
var match = /^\(\(\(([\s\S]+?)\)\)\)/.exec(source.substring(nextStart));
|
||||
if(match) {
|
||||
// Check for separator: split on last || before )))
|
||||
var inner = match[1];
|
||||
var sepIndex = inner.lastIndexOf("||");
|
||||
if(sepIndex >= 0) {
|
||||
this.nextMatch = {
|
||||
type: "filter",
|
||||
filter: inner.substring(0,sepIndex),
|
||||
separator: inner.substring(sepIndex + 2),
|
||||
start: nextStart,
|
||||
end: nextStart + match[0].length
|
||||
};
|
||||
} else {
|
||||
this.nextMatch = {
|
||||
type: "filter",
|
||||
filter: inner,
|
||||
separator: ", ",
|
||||
start: nextStart,
|
||||
end: nextStart + match[0].length
|
||||
};
|
||||
}
|
||||
return nextStart;
|
||||
}
|
||||
} else {
|
||||
// Variable mode: ((varname)) or ((varname||sep))
|
||||
var match = /^\(\(([^()|]+?)(?:\|\|([^)]*))?\)\)/.exec(source.substring(nextStart));
|
||||
if(match) {
|
||||
this.nextMatch = {
|
||||
type: "variable",
|
||||
varName: match[1],
|
||||
separator: match[2] !== undefined ? match[2] : ", ",
|
||||
start: nextStart,
|
||||
end: nextStart + match[0].length
|
||||
};
|
||||
return nextStart;
|
||||
}
|
||||
}
|
||||
nextStart += 2;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/*
|
||||
Parse the most recent match
|
||||
*/
|
||||
exports.parse = function() {
|
||||
var match = this.nextMatch;
|
||||
this.nextMatch = null;
|
||||
this.parser.pos = match.end;
|
||||
var filter, sep = match.separator;
|
||||
if(match.type === "variable") {
|
||||
filter = "[(" + match.varName + ")join[" + sep + "]]";
|
||||
} else {
|
||||
filter = match.filter + " +[join[" + sep + "]]";
|
||||
}
|
||||
return [{
|
||||
type: "text",
|
||||
attributes: {
|
||||
text: {name: "text", type: "filtered", filter: filter}
|
||||
},
|
||||
orderedAttributes: [
|
||||
{name: "text", type: "filtered", filter: filter}
|
||||
]
|
||||
}];
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
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));
|
||||
58
core/modules/utils/deprecated.js
Normal file
58
core/modules/utils/deprecated.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/*\
|
||||
title: $:/core/modules/utils/deprecated.js
|
||||
type: application/javascript
|
||||
module-type: utils
|
||||
|
||||
Deprecated util functions
|
||||
|
||||
\*/
|
||||
|
||||
exports.logTable = data => console.table(data);
|
||||
|
||||
exports.repeat = (str,count) => str.repeat(count);
|
||||
|
||||
exports.startsWith = (str,search) => str.startsWith(search);
|
||||
|
||||
exports.endsWith = (str,search) => str.endsWith(search);
|
||||
|
||||
exports.trim = function(str) {
|
||||
if(typeof str === "string") {
|
||||
return str.trim();
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
exports.hopArray = (object,array) => array.some(element => $tw.utils.hop(object,element));
|
||||
|
||||
exports.sign = Math.sign;
|
||||
|
||||
exports.strEndsWith = (str,ending,position) => str.endsWith(ending,position);
|
||||
|
||||
exports.stringifyNumber = num => num.toString();
|
||||
|
||||
exports.tagToCssSelector = function(tagName) {
|
||||
return "tc-tagged-" + encodeURIComponent(tagName).replace(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^`{\|}~,]/mg,function(c) {
|
||||
return "\\" + c;
|
||||
});
|
||||
};
|
||||
|
||||
exports.domContains = (a,b) => a.compareDocumentPosition(b) & 16;
|
||||
|
||||
exports.domMatchesSelector = (node,selector) => node.matches(selector);
|
||||
|
||||
exports.hasClass = (el,className) => el.classList && el.classList.contains(className);
|
||||
|
||||
exports.addClass = function(el,className) {
|
||||
el.classList && className && el.classList.add(className);
|
||||
};
|
||||
|
||||
exports.removeClass = function(el,className) {
|
||||
el.classList && className && el.classList.remove(className);
|
||||
};
|
||||
|
||||
exports.toggleClass = function(el,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
@@ -11,19 +11,6 @@ Various static DOM-related utility functions.
|
||||
|
||||
var Popup = require("$:/core/modules/utils/dom/popup.js");
|
||||
|
||||
/*
|
||||
Determines whether element 'a' contains element 'b'
|
||||
Code thanks to John Resig, http://ejohn.org/blog/comparing-document-position/
|
||||
*/
|
||||
exports.domContains = function(a,b) {
|
||||
return a.contains ?
|
||||
a !== b && a.contains(b) :
|
||||
!!(a.compareDocumentPosition(b) & 16);
|
||||
};
|
||||
|
||||
exports.domMatchesSelector = function(node,selector) {
|
||||
return node.matches ? node.matches(selector) : node.msMatchesSelector(selector);
|
||||
};
|
||||
|
||||
/*
|
||||
Select text in a an input or textarea (setSelectionRange crashes on certain input types)
|
||||
@@ -49,38 +36,6 @@ exports.removeChildren = function(node) {
|
||||
}
|
||||
};
|
||||
|
||||
exports.hasClass = function(el,className) {
|
||||
return el && el.hasAttribute && el.hasAttribute("class") && el.getAttribute("class").split(" ").indexOf(className) !== -1;
|
||||
};
|
||||
|
||||
exports.addClass = function(el,className) {
|
||||
var c = (el.getAttribute("class") || "").split(" ");
|
||||
if(c.indexOf(className) === -1) {
|
||||
c.push(className);
|
||||
el.setAttribute("class",c.join(" "));
|
||||
}
|
||||
};
|
||||
|
||||
exports.removeClass = function(el,className) {
|
||||
var c = (el.getAttribute("class") || "").split(" "),
|
||||
p = c.indexOf(className);
|
||||
if(p !== -1) {
|
||||
c.splice(p,1);
|
||||
el.setAttribute("class",c.join(" "));
|
||||
}
|
||||
};
|
||||
|
||||
exports.toggleClass = function(el,className,status) {
|
||||
if(status === undefined) {
|
||||
status = !exports.hasClass(el,className);
|
||||
}
|
||||
if(status) {
|
||||
exports.addClass(el,className);
|
||||
} else {
|
||||
exports.removeClass(el,className);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Get the first parent element that has scrollbars or use the body as fallback.
|
||||
*/
|
||||
@@ -297,10 +252,6 @@ exports.copyToClipboard = function(text,options) {
|
||||
document.body.removeChild(textArea);
|
||||
};
|
||||
|
||||
exports.getLocationPath = function() {
|
||||
return window.location.toString().split("#")[0];
|
||||
};
|
||||
|
||||
/*
|
||||
Collect DOM variables
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
})();
|
||||
|
||||
@@ -48,31 +48,6 @@ exports.warning = function(text) {
|
||||
exports.log(text,"brown/orange");
|
||||
};
|
||||
|
||||
/*
|
||||
Log a table of name: value or name: [values...] pairs
|
||||
*/
|
||||
exports.logTable = function(data) {
|
||||
var hasArrays = false;
|
||||
$tw.utils.each(data,function(value,name) {
|
||||
if($tw.utils.isArray(value)) {
|
||||
hasArrays = true;
|
||||
}
|
||||
});
|
||||
if(console.table && !hasArrays) {
|
||||
console.table(data);
|
||||
} else {
|
||||
$tw.utils.each(data,function(value,name) {
|
||||
if($tw.utils.isArray(value)) {
|
||||
for(var t=0; t<value.length; t++) {
|
||||
console.log(`${name}[${t}]: ${value[t]}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`${name}: ${value}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Return the integer represented by the str (string).
|
||||
Return the dflt (default) parameter if str is not a base-10 number.
|
||||
@@ -80,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
|
||||
@@ -91,52 +66,15 @@ exports.replaceString = function(text,search,replace) {
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Repeats a string
|
||||
*/
|
||||
exports.repeat = function(str,count) {
|
||||
var result = "";
|
||||
for(var t=0;t<count;t++) {
|
||||
result += str;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/*
|
||||
Check if a string starts with another string
|
||||
*/
|
||||
exports.startsWith = function(str,search) {
|
||||
return str.substring(0, search.length) === search;
|
||||
};
|
||||
|
||||
/*
|
||||
Check if a string ends with another string
|
||||
*/
|
||||
exports.endsWith = function(str,search) {
|
||||
return str.substring(str.length - search.length) === search;
|
||||
};
|
||||
|
||||
/*
|
||||
Trim whitespace from the start and end of a string
|
||||
Thanks to Steven Levithan, http://blog.stevenlevithan.com/archives/faster-trim-javascript
|
||||
*/
|
||||
exports.trim = function(str) {
|
||||
if(typeof str === "string") {
|
||||
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -146,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;
|
||||
@@ -163,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
|
||||
@@ -212,18 +150,6 @@ exports.count = function(object) {
|
||||
return Object.keys(object || {}).length;
|
||||
};
|
||||
|
||||
/*
|
||||
Determine whether an array-item is an object-property
|
||||
*/
|
||||
exports.hopArray = function(object,array) {
|
||||
for(var i=0; i<array.length; i++) {
|
||||
if($tw.utils.hop(object,array[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
Remove entries from an array
|
||||
array: array to modify
|
||||
@@ -316,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 || "",
|
||||
@@ -432,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);
|
||||
@@ -642,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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -661,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
|
||||
};
|
||||
|
||||
@@ -675,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
|
||||
};
|
||||
|
||||
@@ -691,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, "\\$&");
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -774,7 +747,7 @@ exports.parseTextReference = function(textRef) {
|
||||
}
|
||||
} else {
|
||||
// If we couldn't parse it
|
||||
result.title = textRef
|
||||
result.title = textRef;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
@@ -833,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);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -894,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;
|
||||
};
|
||||
@@ -952,44 +882,6 @@ exports.makeDataUri = function(text,type,_canonical_uri) {
|
||||
return parts.join("");
|
||||
};
|
||||
|
||||
/*
|
||||
Useful for finding out the fully escaped CSS selector equivalent to a given tag. For example:
|
||||
|
||||
$tw.utils.tagToCssSelector("$:/tags/Stylesheet") --> tc-tagged-\%24\%3A\%2Ftags\%2FStylesheet
|
||||
*/
|
||||
exports.tagToCssSelector = function(tagName) {
|
||||
return "tc-tagged-" + encodeURIComponent(tagName).replace(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^`{\|}~,]/mg,function(c) {
|
||||
return "\\" + c;
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
IE does not have sign function
|
||||
*/
|
||||
exports.sign = Math.sign || function(x) {
|
||||
x = +x; // convert to a number
|
||||
if(x === 0 || isNaN(x)) {
|
||||
return x;
|
||||
}
|
||||
return x > 0 ? 1 : -1;
|
||||
};
|
||||
|
||||
/*
|
||||
IE does not have an endsWith function
|
||||
*/
|
||||
exports.strEndsWith = function(str,ending,position) {
|
||||
if(str.endsWith) {
|
||||
return str.endsWith(ending,position);
|
||||
} else {
|
||||
if(typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > str.length) {
|
||||
position = str.length;
|
||||
}
|
||||
position -= ending.length;
|
||||
var lastIndex = str.indexOf(ending, position);
|
||||
return lastIndex !== -1 && lastIndex === position;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Return system information useful for debugging
|
||||
*/
|
||||
@@ -1016,10 +908,6 @@ exports.parseInt = function(str) {
|
||||
return parseInt(str,10) || 0;
|
||||
};
|
||||
|
||||
exports.stringifyNumber = function(num) {
|
||||
return num + "";
|
||||
};
|
||||
|
||||
exports.makeCompareFunction = function(type,options) {
|
||||
options = options || {};
|
||||
// set isCaseSensitive to true if not defined in options
|
||||
@@ -1073,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};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/*\
|
||||
title: $:/core/modules/widgets/action-log.js
|
||||
type: application/javascript
|
||||
@@ -32,7 +33,7 @@ LogWidget.prototype.execute = function(){
|
||||
this.message = this.getAttribute("$$message","debug");
|
||||
this.logAll = this.getAttribute("$$all","no") === "yes" ? true : false;
|
||||
this.filter = this.getAttribute("$$filter");
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Refresh the widget by ensuring our attributes are up to date
|
||||
@@ -69,10 +70,11 @@ LogWidget.prototype.log = function() {
|
||||
});
|
||||
// Collect values of all variables, using the source text for functions
|
||||
for(var v in this.variables) {
|
||||
var variableInfo = this.getVariableInfo(v);
|
||||
if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
|
||||
allVars[v] = variableInfo.text;
|
||||
var variable = this.parentWidget && this.parentWidget.variables[v];
|
||||
if(variable && variable.isFunctionDefinition) {
|
||||
allVars[v] = variable.value;
|
||||
} else {
|
||||
var variableInfo = this.getVariableInfo(v);
|
||||
allVars[v] = variableInfo.resultList.length > 1 ? variableInfo.resultList : variableInfo.text;
|
||||
}
|
||||
}
|
||||
@@ -94,6 +96,6 @@ LogWidget.prototype.log = function() {
|
||||
console.groupEnd();
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
};
|
||||
|
||||
exports["action-log"] = LogWidget;
|
||||
|
||||
@@ -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);
|
||||
@@ -34,25 +34,33 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
// Create the diff
|
||||
var dmpObject = new dmp.diff_match_patch(),
|
||||
diffs = dmpObject.diff_main(this.getAttribute("source",""),this.getAttribute("dest",""));
|
||||
// Create the diff object
|
||||
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) {
|
||||
@@ -64,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);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -132,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) {
|
||||
if(changedAttributes.source || changedAttributes.dest || changedAttributes.cleanup || changedAttributes.mode || changedAttributes.editcost) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
} else {
|
||||
@@ -140,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);
|
||||
|
||||
@@ -61,7 +61,9 @@ ParametersWidget.prototype.execute = function() {
|
||||
if(name.substr(0,2) === "$$") {
|
||||
name = name.substr(1);
|
||||
}
|
||||
var value = pointer.getTransclusionParameter(name,index,self.getAttribute(attr.name,""));
|
||||
var defaultValue = (self.multiValuedAttributes && self.multiValuedAttributes[attr.name])
|
||||
|| self.getAttribute(attr.name,"");
|
||||
var value = pointer.getTransclusionParameter(name,index,defaultValue);
|
||||
self.setVariable(name,value);
|
||||
});
|
||||
// Assign any metaparameters
|
||||
@@ -80,7 +82,8 @@ ParametersWidget.prototype.execute = function() {
|
||||
if(name.substr(0,2) === "$$") {
|
||||
name = name.substr(1);
|
||||
}
|
||||
var value = self.getAttribute(attr.name,"");
|
||||
var value = (self.multiValuedAttributes && self.multiValuedAttributes[attr.name])
|
||||
|| self.getAttribute(attr.name,"");
|
||||
self.setVariable(name,value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -89,13 +89,33 @@ RevealWidget.prototype.positionPopup = function(domNode) {
|
||||
top = this.popup.top + this.popup.height;
|
||||
break;
|
||||
}
|
||||
// if requested, clamp the popup so that it will always be fully inside its parent (the first upstream element with position:relative), as long as the popup is smaller than its parent
|
||||
// if position is absolute then clamping is done to the canvas boundary, since there is no "parent"
|
||||
if(this.clampToParent !== "none") {
|
||||
if(this.popup.absolute) {
|
||||
var parentWidth = window.innerWidth,
|
||||
parentHeight = window.innerHeight;
|
||||
} else {
|
||||
var parentWidth = domNode.offsetParent.offsetWidth,
|
||||
parentHeight = domNode.offsetParent.offsetHeight;
|
||||
}
|
||||
var right = left + domNode.offsetWidth,
|
||||
bottom = top + domNode.offsetHeight;
|
||||
if((this.clampToParent === "both" || this.clampToParent === "right") && right > parentWidth) {
|
||||
left = parentWidth - domNode.offsetWidth;
|
||||
}
|
||||
if((this.clampToParent === "both" || this.clampToParent === "bottom") && bottom > parentHeight) {
|
||||
top = parentHeight - domNode.offsetHeight;
|
||||
}
|
||||
// clamping on left and top sides is taken care of by positionAllowNegative
|
||||
}
|
||||
if(!this.positionAllowNegative) {
|
||||
left = Math.max(0,left);
|
||||
top = Math.max(0,top);
|
||||
}
|
||||
if (this.popup.absolute) {
|
||||
if(this.popup.absolute) {
|
||||
// Traverse the offsetParent chain and correct the offset to make it relative to the parent node.
|
||||
for (var offsetParentDomNode = domNode.offsetParent; offsetParentDomNode; offsetParentDomNode = offsetParentDomNode.offsetParent) {
|
||||
for(var offsetParentDomNode = domNode.offsetParent; offsetParentDomNode; offsetParentDomNode = offsetParentDomNode.offsetParent) {
|
||||
left -= offsetParentDomNode.offsetLeft;
|
||||
top -= offsetParentDomNode.offsetTop;
|
||||
}
|
||||
@@ -123,6 +143,7 @@ RevealWidget.prototype.execute = function() {
|
||||
this.openAnimation = this.animate === "no" ? undefined : "open";
|
||||
this.closeAnimation = this.animate === "no" ? undefined : "close";
|
||||
this.updatePopupPosition = this.getAttribute("updatePopupPosition","no") === "yes";
|
||||
this.clampToParent = this.getAttribute("clamp","none");
|
||||
// Compute the title of the state tiddler and read it
|
||||
this.stateTiddlerTitle = this.state;
|
||||
this.stateTitle = this.getAttribute("stateTitle");
|
||||
@@ -141,7 +162,7 @@ Read the state tiddler
|
||||
RevealWidget.prototype.readState = function() {
|
||||
// Read the information from the state tiddler
|
||||
var state,
|
||||
defaultState = this["default"];
|
||||
defaultState = this["default"];
|
||||
if(this.stateTitle) {
|
||||
var stateTitleTiddler = this.wiki.getTiddler(this.stateTitle);
|
||||
if(this.stateField) {
|
||||
@@ -252,18 +273,18 @@ RevealWidget.prototype.updateState = function() {
|
||||
this.renderChildren(domNode,null);
|
||||
}
|
||||
// Animate our DOM node
|
||||
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) {
|
||||
this.positionPopup(domNode);
|
||||
$tw.utils.addClass(domNode,"tc-popup"); // Make sure that clicks don't dismiss popups within the revealed content
|
||||
|
||||
}
|
||||
if(this.isOpen) {
|
||||
domNode.removeAttribute("hidden");
|
||||
$tw.anim.perform(this.openAnimation,domNode);
|
||||
// Position popup after making it visible to ensure correct dimensions
|
||||
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup") {
|
||||
this.positionPopup(domNode);
|
||||
$tw.utils.addClass(domNode,"tc-popup"); // Make sure that clicks don't dismiss popups within the revealed content
|
||||
}
|
||||
$tw.anim.perform(this.openAnimation,domNode);
|
||||
} else {
|
||||
$tw.anim.perform(this.closeAnimation,domNode,{callback: function() {
|
||||
//make sure that the state hasn't changed during the close animation
|
||||
self.readState()
|
||||
self.readState();
|
||||
if(!self.isOpen) {
|
||||
domNode.setAttribute("hidden","true");
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
}})];
|
||||
@@ -148,8 +158,10 @@ Collect string parameters
|
||||
TranscludeWidget.prototype.collectStringParameters = function() {
|
||||
var self = this;
|
||||
this.stringParametersByName = Object.create(null);
|
||||
this.multiValuedParametersByName = Object.create(null);
|
||||
if(!this.legacyMode) {
|
||||
$tw.utils.each(this.attributes,function(value,name) {
|
||||
var attrName = name; // Save original attribute name for MVV lookup
|
||||
if(name.charAt(0) === "$") {
|
||||
if(name.charAt(1) === "$") {
|
||||
// Attributes starting $$ represent parameters starting with a single $
|
||||
@@ -160,6 +172,9 @@ TranscludeWidget.prototype.collectStringParameters = function() {
|
||||
}
|
||||
}
|
||||
self.stringParametersByName[name] = value;
|
||||
if(self.multiValuedAttributes && self.multiValuedAttributes[attrName]) {
|
||||
self.multiValuedParametersByName[name] = self.multiValuedAttributes[attrName];
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -303,7 +318,16 @@ TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) {
|
||||
if(name.charAt(0) === "$") {
|
||||
name = "$" + name;
|
||||
}
|
||||
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"])
|
||||
if(param.defaultType === "multivalue-variable") {
|
||||
// Construct MVV attribute for the default
|
||||
var mvvNode = {type: "transclude", isMVV: true, attributes: {}, orderedAttributes: []};
|
||||
$tw.utils.addAttributeToParseTreeNode(mvvNode,"$variable",param.defaultVariable);
|
||||
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],{
|
||||
name: name, type: "macro", isMVV: true, value: mvvNode
|
||||
});
|
||||
} else {
|
||||
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"]);
|
||||
}
|
||||
});
|
||||
} else if(srcVariable && !srcVariable.isFunctionDefinition) {
|
||||
// For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
|
||||
@@ -354,7 +378,11 @@ TranscludeWidget.prototype.getOrderedTransclusionParameters = function() {
|
||||
// Collect the parameters
|
||||
for(var name in this.stringParametersByName) {
|
||||
var value = this.stringParametersByName[name];
|
||||
result.push({name: name, value: value});
|
||||
var param = {name: name, value: value};
|
||||
if(this.multiValuedParametersByName[name]) {
|
||||
param.multiValue = this.multiValuedParametersByName[name];
|
||||
}
|
||||
result.push(param);
|
||||
}
|
||||
// Sort numerical parameter names first
|
||||
result.sort(function(a,b) {
|
||||
@@ -384,10 +412,16 @@ Fetch the value of a parameter
|
||||
*/
|
||||
TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaultValue) {
|
||||
if(name in this.stringParametersByName) {
|
||||
if(this.multiValuedParametersByName[name]) {
|
||||
return this.multiValuedParametersByName[name];
|
||||
}
|
||||
return this.stringParametersByName[name];
|
||||
} else {
|
||||
var name = "" + index;
|
||||
if(name in this.stringParametersByName) {
|
||||
if(this.multiValuedParametersByName[name]) {
|
||||
return this.multiValuedParametersByName[name];
|
||||
}
|
||||
return this.stringParametersByName[name];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
},
|
||||
@@ -379,19 +381,31 @@ filterFn: only include attributes where filterFn(name) returns true
|
||||
Widget.prototype.computeAttributes = function(options) {
|
||||
options = options || {};
|
||||
var changedAttributes = {},
|
||||
self = this;
|
||||
self = this,
|
||||
newMultiValuedAttributes = Object.create(null);
|
||||
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
|
||||
if(options.filterFn) {
|
||||
if(!options.filterFn(name)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
var value = self.computeAttribute(attribute);
|
||||
if(self.attributes[name] !== value) {
|
||||
var value = self.computeAttribute(attribute),
|
||||
multiValue = null;
|
||||
if($tw.utils.isArray(value)) {
|
||||
multiValue = value;
|
||||
newMultiValuedAttributes[name] = multiValue;
|
||||
value = value[0] || "";
|
||||
}
|
||||
var changed = (self.attributes[name] !== value);
|
||||
if(!changed && multiValue && self.multiValuedAttributes) {
|
||||
changed = !$tw.utils.isArrayEqual(self.multiValuedAttributes[name] || [], multiValue);
|
||||
}
|
||||
if(changed) {
|
||||
self.attributes[name] = value;
|
||||
changedAttributes[name] = true;
|
||||
}
|
||||
});
|
||||
this.multiValuedAttributes = newMultiValuedAttributes;
|
||||
return changedAttributes;
|
||||
};
|
||||
|
||||
@@ -414,8 +428,22 @@ Widget.prototype.computeAttribute = function(attribute,options) {
|
||||
value = [value];
|
||||
}
|
||||
} else if(attribute.type === "macro") {
|
||||
var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params});
|
||||
if(options.asList) {
|
||||
// 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 || attribute.isMVV) {
|
||||
value = variableInfo.resultList;
|
||||
} else {
|
||||
value = variableInfo.text;
|
||||
@@ -771,9 +799,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 +839,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 = [];
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -200,7 +200,7 @@ exports.generateNewTitle = function(baseTitle,options) {
|
||||
c = (parseInt(options.startCount,10) > 0) ? parseInt(options.startCount,10) : 0,
|
||||
prefix = (typeof(options.prefix) === "string") ? options.prefix : " ";
|
||||
|
||||
if (template) {
|
||||
if(template) {
|
||||
// "count" is important to avoid an endless loop in while(...)!!
|
||||
template = (/\$count:?(\d+)?\$/i.test(template)) ? template : template + "$count$";
|
||||
// .formatTitleString() expects strings as input
|
||||
@@ -209,7 +209,7 @@ exports.generateNewTitle = function(baseTitle,options) {
|
||||
title = $tw.utils.formatTitleString(template,{"base":baseTitle,"separator":prefix,"counter":(++c)+""});
|
||||
}
|
||||
} else {
|
||||
if (c > 0) {
|
||||
if(c > 0) {
|
||||
title = baseTitle + prefix + c;
|
||||
}
|
||||
while(this.tiddlerExists(title) || this.isShadowTiddler(title) || this.findDraft(title)) {
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -532,7 +505,7 @@ exports.getTiddlerLinks = function(title) {
|
||||
return self.extractLinks(parser.tree);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}).slice(0);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -551,8 +524,9 @@ exports.getTiddlerBacklinks = function(targetTitle) {
|
||||
backlinks.push(title);
|
||||
}
|
||||
});
|
||||
return backlinks;
|
||||
}
|
||||
return backlinks;
|
||||
return backlinks.slice(0);
|
||||
};
|
||||
|
||||
|
||||
@@ -578,7 +552,7 @@ exports.extractTranscludes = function(parseTreeRoot, title) {
|
||||
}
|
||||
}
|
||||
} else if(parseTreeNode.attributes.tiddler) {
|
||||
if (parseTreeNode.attributes.tiddler.type === "string") {
|
||||
if(parseTreeNode.attributes.tiddler.type === "string") {
|
||||
// Old transclude widget usage
|
||||
value = parseTreeNode.attributes.tiddler.value;
|
||||
}
|
||||
@@ -618,7 +592,7 @@ exports.getTiddlerTranscludes = function(title) {
|
||||
return self.extractTranscludes(parser.tree,title);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}).slice(0);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -630,9 +604,9 @@ exports.getTiddlerBacktranscludes = function(targetTitle) {
|
||||
backtranscludes = backIndexer && backIndexer.subIndexers.transclude.lookup(targetTitle);
|
||||
|
||||
if(!backtranscludes) {
|
||||
backtranscludes = [];
|
||||
return [];
|
||||
}
|
||||
return backtranscludes;
|
||||
return backtranscludes.slice(0);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -641,7 +615,7 @@ Return a hashmap of tiddler titles that are referenced but not defined. Each val
|
||||
exports.getMissingTitles = function() {
|
||||
var self = this,
|
||||
missing = [];
|
||||
// We should cache the missing tiddler list, even if we recreate it every time any tiddler is modified
|
||||
// We should cache the missing tiddler list, even if we recreate it every time any tiddler is modified
|
||||
this.forEachTiddler(function(title,tiddler) {
|
||||
var links = self.getTiddlerLinks(title);
|
||||
$tw.utils.each(links,function(link) {
|
||||
@@ -683,7 +657,7 @@ exports.getTiddlersWithTag = function(tag) {
|
||||
return self.sortByList(tagmap[tag],tag);
|
||||
});
|
||||
}
|
||||
return results;
|
||||
return results.slice(0);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -734,7 +708,7 @@ exports.findListingsOfTiddler = function(targetTitle,fieldName) {
|
||||
for(var i = 0; i < list.length; i++) {
|
||||
var listItem = list[i],
|
||||
listing = listings[listItem] || [];
|
||||
if (listing.indexOf(title) === -1) {
|
||||
if(listing.indexOf(title) === -1) {
|
||||
listing.push(title);
|
||||
}
|
||||
listings[listItem] = listing;
|
||||
@@ -743,7 +717,7 @@ exports.findListingsOfTiddler = function(targetTitle,fieldName) {
|
||||
});
|
||||
return listings;
|
||||
});
|
||||
return listings[targetTitle] || [];
|
||||
return (listings[targetTitle] || []).slice(0);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -782,7 +756,7 @@ exports.sortByList = function(array,listTitle) {
|
||||
}
|
||||
}
|
||||
// If a new position is specified, let's move it
|
||||
if (newPos !== -1) {
|
||||
if(newPos !== -1) {
|
||||
// get its current Pos, and make sure
|
||||
// sure that it's _actually_ in the list
|
||||
// and that it would _actually_ move
|
||||
@@ -1059,7 +1033,7 @@ Options include:
|
||||
exports.parseText = function(type,text,options) {
|
||||
text = text || "";
|
||||
options = options || {};
|
||||
var Parser = $tw.utils.getParser(type,options)
|
||||
var Parser = $tw.utils.getParser(type,options);
|
||||
// Return the parser instance
|
||||
return new Parser(type,text,{
|
||||
parseAsInline: options.parseAsInline,
|
||||
@@ -1078,11 +1052,11 @@ exports.parseTiddler = function(title,options) {
|
||||
tiddler = this.getTiddler(title),
|
||||
self = this;
|
||||
return tiddler ? this.getCacheForTiddler(title,cacheType,function() {
|
||||
if(tiddler.hasField("_canonical_uri")) {
|
||||
options._canonical_uri = tiddler.fields._canonical_uri;
|
||||
}
|
||||
return self.parseText(tiddler.fields.type,tiddler.fields.text,options);
|
||||
}) : null;
|
||||
if(tiddler.hasField("_canonical_uri")) {
|
||||
options._canonical_uri = tiddler.fields._canonical_uri;
|
||||
}
|
||||
return self.parseText(tiddler.fields.type,tiddler.fields.text,options);
|
||||
}) : null;
|
||||
};
|
||||
|
||||
exports.parseTextReference = function(title,field,index,options) {
|
||||
@@ -1138,7 +1112,7 @@ exports.getTextReferenceParserInfo = function(title,field,index,options) {
|
||||
parserInfo.parserType = null;
|
||||
}
|
||||
return parserInfo;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Parse a block of text of a specified MIME type
|
||||
@@ -1148,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) {
|
||||
@@ -1164,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];
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1242,7 +1217,7 @@ exports.makeTranscludeWidget = function(title,options) {
|
||||
name: "recursionMarker",
|
||||
type: "string",
|
||||
value: options.recursionMarker || "yes"
|
||||
},
|
||||
},
|
||||
tiddler: {
|
||||
name: "tiddler",
|
||||
type: "string",
|
||||
@@ -1385,7 +1360,7 @@ exports.search = function(text,options) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Accumulate the array of fields to be searched or excluded from the search
|
||||
// Accumulate the array of fields to be searched or excluded from the search
|
||||
var fields = [];
|
||||
if(options.field) {
|
||||
if($tw.utils.isArray(options.field)) {
|
||||
@@ -1518,7 +1493,7 @@ exports.checkTiddlerText = function(title,targetText,options) {
|
||||
targetText = targetText.toLowerCase();
|
||||
}
|
||||
return text === targetText;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Execute an action string without an associated context widget
|
||||
@@ -1642,7 +1617,7 @@ exports.findDraft = function(targetTitle) {
|
||||
}
|
||||
});
|
||||
return draftTitle;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Check whether the specified draft tiddler has been modified.
|
||||
@@ -1669,7 +1644,7 @@ historyTitle: title of history tiddler (defaults to $:/HistoryList)
|
||||
exports.addToHistory = function(title,fromPageRect,historyTitle) {
|
||||
var story = new $tw.Story({wiki: this, historyTitle: historyTitle});
|
||||
story.addToHistory(title,fromPageRect);
|
||||
console.log("$tw.wiki.addToHistory() is deprecated since V5.1.23! Use the this.story.addToHistory() from the story-object!")
|
||||
console.log("$tw.wiki.addToHistory() is deprecated since V5.1.23! Use the this.story.addToHistory() from the story-object!");
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -1682,19 +1657,21 @@ options: see story.js
|
||||
exports.addToStory = function(title,fromTitle,storyTitle,options) {
|
||||
var story = new $tw.Story({wiki: this, storyTitle: storyTitle});
|
||||
story.addToStory(title,fromTitle,options);
|
||||
console.log("$tw.wiki.addToStory() is deprecated since V5.1.23! Use the this.story.addToStory() from the story-object!")
|
||||
console.log("$tw.wiki.addToStory() is deprecated since V5.1.23! Use the this.story.addToStory() from the story-object!");
|
||||
};
|
||||
|
||||
/*
|
||||
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]] }}};
|
||||
}
|
||||
@@ -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}}
|
||||
|
||||
|
||||
@@ -49,6 +49,27 @@ title: $:/core/ui/ImportListing
|
||||
\end
|
||||
|
||||
\whitespace trim
|
||||
<$let importJson={{{ [{$:/Import}] }}}>
|
||||
<$let importTitles={{{ [<importJson>jsonindexes[tiddlers]] }}}>
|
||||
<$let importTypes={{{ [(importTitles)] :map[<importJson>jsonget[tiddlers],<currentTiddler>,[type]] }}}>
|
||||
<$let anyMatch={{{ [all[shadows+tiddlers]tag[$:/tags/ImportOptions]get[condition]] :map[(importTypes)subfilter<currentTiddler>] +[!is[blank]limit[1]] }}}>
|
||||
<%if [<anyMatch>!is[blank]] %>
|
||||
<div class="tc-import-option">
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/ImportOptions]]" variable="importOption">
|
||||
<$let condition={{{ [<importOption>get[condition]] }}}>
|
||||
<$let hasMatch={{{ [(importTypes)subfilter<condition>limit[1]] }}}>
|
||||
<%if [<hasMatch>!is[blank]] %>
|
||||
<$transclude tiddler=<<importOption>>/>
|
||||
<%endif%>
|
||||
</$let>
|
||||
</$let>
|
||||
</$list>
|
||||
</div>
|
||||
<%endif%>
|
||||
</$let>
|
||||
</$let>
|
||||
</$let>
|
||||
</$let>
|
||||
<div class="tc-table-wrapper">
|
||||
<table class="tc-import-table">
|
||||
<tbody>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
43
core/ui/TiddlerInfo/Advanced/CascadeInfo.tid
Normal file
43
core/ui/TiddlerInfo/Advanced/CascadeInfo.tid
Normal file
@@ -0,0 +1,43 @@
|
||||
title: $:/core/ui/TiddlerInfo/Advanced/CascadeInfo
|
||||
tags: $:/tags/TiddlerInfo/Advanced
|
||||
|
||||
\define lingo-base() $:/language/TiddlerInfo/Advanced/CascadeInfo/
|
||||
|
||||
<$let infoTiddler=<<currentTiddler>>>
|
||||
|
||||
''<<lingo Heading>>''
|
||||
|
||||
<<lingo Hint>>
|
||||
|
||||
<table class="tc-max-width">
|
||||
<thead>
|
||||
<$list filter="[[View]] [[ActiveCascadeFilter]] [[Template]]" variable="th">
|
||||
<th><$transclude $variable="lingo" title=`Detail/$(th)$`/></th>
|
||||
</$list>
|
||||
</thead>
|
||||
<$list filter="[[$:/tags/ViewTemplate]tagging[]]" variable="ViewTemplate">
|
||||
<tr>
|
||||
<$let
|
||||
view={{{ [<ViewTemplate>]+[split[/]last[]] }}}
|
||||
tagFilter=`$:/tags/ViewTemplate${ [<view>titlecase[]] }$Filter`
|
||||
activeCascadeFilterTiddler={{{ [all[shadows+tiddlers]tag<tagFilter>!is[draft]]:filter[<storyTiddler>subfilter{!!text}]+[first[]] }}}
|
||||
activeCascadeFilter={{{ [<activeCascadeFilterTiddler>get[text]] }}}
|
||||
activeTemplateTiddler={{{ [<currentTiddler>]:cascade[all[shadows+tiddlers]tag<tagFilter>!is[draft]get[text]] }}}
|
||||
>
|
||||
<%if [<activeCascadeFilterTiddler>!is[blank]]%>
|
||||
<td>
|
||||
<$link to=<<ViewTemplate>> ><<view>></$link>
|
||||
</td>
|
||||
<td>
|
||||
<$link to=<<activeCascadeFilterTiddler>> />
|
||||
</td>
|
||||
<td style="text-align:center;">
|
||||
<$link class="tc-btn-invisible" to=<<activeTemplateTiddler>>>
|
||||
<$button class="tc-btn-invisible">{{$:/core/images/file}}</$button>
|
||||
</$link>
|
||||
</td>
|
||||
<%endif%>
|
||||
</$let>
|
||||
</tr>
|
||||
</$list>
|
||||
</table>
|
||||
@@ -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": [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user