Compare commits
64 Commits
barcode-re
...
improved-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66b0e4b643 | ||
|
|
afd7155a9d | ||
|
|
ad98853b7e | ||
|
|
646620fc42 | ||
|
|
d77eee291c | ||
|
|
758089cbb3 | ||
|
|
6567843927 | ||
|
|
4b56cb4298 | ||
|
|
902c7f55ba | ||
|
|
801e8e312c | ||
|
|
23a576b8bd | ||
|
|
6a52081d6b | ||
|
|
246751be1b | ||
|
|
4ebaba8e89 | ||
|
|
efaa8dd1e8 | ||
|
|
326ae61929 | ||
|
|
c185e373c5 | ||
|
|
2ffbfd84a5 | ||
|
|
faef02df7a | ||
|
|
c93d56667e | ||
|
|
3855a9f013 | ||
|
|
4d548580e6 | ||
|
|
9773dff1e3 | ||
|
|
c6604c0c56 | ||
|
|
e521ee2133 | ||
|
|
a7bd134c35 | ||
|
|
d3f5695601 | ||
|
|
efa4f34131 | ||
|
|
fffbedd6b9 | ||
|
|
ff1437e439 | ||
|
|
32966c9e91 | ||
|
|
96b0543351 | ||
|
|
b7562f0c7b | ||
|
|
4c9c85aec5 | ||
|
|
7726982d71 | ||
|
|
774058912d | ||
|
|
106e1c68df | ||
|
|
12f42ad62f | ||
|
|
66a8e2dbf2 | ||
|
|
327ecf8f66 | ||
|
|
35e45f3b90 | ||
|
|
93bc9e4309 | ||
|
|
23b75bbc5d | ||
|
|
68da654eb3 | ||
|
|
1eceb5f47f | ||
|
|
def508a220 | ||
|
|
3fb71e2553 | ||
|
|
d17525ec8e | ||
|
|
5bb8155422 | ||
|
|
bb2973fc29 | ||
|
|
bbaa0890b5 | ||
|
|
b4a862c618 | ||
|
|
1be8f0a933 | ||
|
|
773c1f83f2 | ||
|
|
9ee1ef7e23 | ||
|
|
8effb3f218 | ||
|
|
780e5d33a4 | ||
|
|
526e997aa4 | ||
|
|
e4d8849f22 | ||
|
|
bd99cf3385 | ||
|
|
711d1658e2 | ||
|
|
b82f012c0c | ||
|
|
f383863654 | ||
|
|
697dc8db4c |
10
.github/workflows/ci.yml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
- master
|
- master
|
||||||
- tiddlywiki-com
|
- tiddlywiki-com
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: "12"
|
NODE_VERSION: "18"
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -14,7 +14,13 @@ jobs:
|
|||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: "${{ env.NODE_VERSION }}"
|
node-version: "${{ env.NODE_VERSION }}"
|
||||||
- run: "./bin/test.sh"
|
- run: "./bin/ci-test.sh"
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: playwright-report/
|
||||||
|
retention-days: 30
|
||||||
build-prerelease:
|
build-prerelease:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
|
|||||||
4
.gitignore
vendored
@@ -5,4 +5,6 @@
|
|||||||
tmp/
|
tmp/
|
||||||
output/
|
output/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
|||||||
16
bin/ci-test.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# test TiddlyWiki5 for tiddlywiki.com
|
||||||
|
|
||||||
|
node ./tiddlywiki.js \
|
||||||
|
./editions/test \
|
||||||
|
--verbose \
|
||||||
|
--version \
|
||||||
|
--rendertiddler $:/core/save/all test.html text/plain \
|
||||||
|
--test \
|
||||||
|
|| exit 1
|
||||||
|
|
||||||
|
npm install playwright @playwright/test
|
||||||
|
npx playwright install chromium firefox --with-deps
|
||||||
|
|
||||||
|
npx playwright test
|
||||||
12
boot/boot.js
@@ -2674,6 +2674,18 @@ $tw.hooks.addHook = function(hookName,definition) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Delete hooks from the hashmap
|
||||||
|
*/
|
||||||
|
$tw.hooks.removeHook = function(hookName,definition) {
|
||||||
|
if($tw.utils.hop($tw.hooks.names,hookName)) {
|
||||||
|
var p = $tw.hooks.names[hookName].indexOf(definition);
|
||||||
|
if(p !== -1) {
|
||||||
|
$tw.hooks.names[hookName].splice(p, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Invoke the hook by key
|
Invoke the hook by key
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ title: $:/language/Exporters/
|
|||||||
StaticRiver: Static HTML
|
StaticRiver: Static HTML
|
||||||
JsonFile: JSON file
|
JsonFile: JSON file
|
||||||
CsvFile: CSV file
|
CsvFile: CSV file
|
||||||
TidFile: ".tid" file
|
TidFile: TID text file
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ The following options are supported:
|
|||||||
** ''yes'' will "explode" plugins into separate tiddler files and save them to the plugin directory within the wiki folder
|
** ''yes'' will "explode" plugins into separate tiddler files and save them to the plugin directory within the wiki folder
|
||||||
** ''no'' will suppress exploding plugins into their constituent tiddler files. It will save the plugin as a single JSON tiddler in the tiddlers folder
|
** ''no'' will suppress exploding plugins into their constituent tiddler files. It will save the plugin as a single JSON tiddler in the tiddlers folder
|
||||||
|
|
||||||
Note that both ''explodePlugins'' options will produce wiki folders that build the same exact same original wiki. The difference lies in how plugins are represented in the wiki folder.
|
Note that both ''explodePlugins'' options will produce wiki folders that build the exact same original wiki. The difference lies in how plugins are represented in the wiki folder.
|
||||||
|
|
||||||
A common usage is to convert a TiddlyWiki HTML file into a wiki folder:
|
A common usage is to convert a TiddlyWiki HTML file into a wiki folder:
|
||||||
|
|
||||||
@@ -31,4 +31,4 @@ Save the plugin to the tiddlers directory of the target wiki folder:
|
|||||||
|
|
||||||
```
|
```
|
||||||
tiddlywiki --load ./mywiki.html --savewikifolder ./mywikifolder explodePlugins=no
|
tiddlywiki --load ./mywiki.html --savewikifolder ./mywikifolder explodePlugins=no
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
title: $:/language/Docs/Types/image/svg+xml
|
title: $:/language/Docs/Types/image/svg+xml
|
||||||
description: Structured Vector Graphics image
|
description: SVG image
|
||||||
name: image/svg+xml
|
name: image/svg+xml
|
||||||
group: Image
|
group: Image
|
||||||
group-sort: 1
|
group-sort: 1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
title: $:/language/Docs/Types/image/x-icon
|
title: $:/language/Docs/Types/image/x-icon
|
||||||
description: ICO format icon file
|
description: ICO icon
|
||||||
name: image/x-icon
|
name: image/x-icon
|
||||||
group: Image
|
group: Image
|
||||||
group-sort: 1
|
group-sort: 1
|
||||||
|
|||||||
@@ -28,12 +28,8 @@ function getAllFilterOperators() {
|
|||||||
Export our filter function
|
Export our filter function
|
||||||
*/
|
*/
|
||||||
exports.all = function(source,operator,options) {
|
exports.all = function(source,operator,options) {
|
||||||
// Get our suboperators
|
|
||||||
var allFilterOperators = getAllFilterOperators();
|
|
||||||
// Cycle through the suboperators accumulating their results
|
|
||||||
var results = new $tw.utils.LinkedList(),
|
|
||||||
subops = operator.operand.split("+");
|
|
||||||
// Check for common optimisations
|
// Check for common optimisations
|
||||||
|
var subops = operator.operand.split("+");
|
||||||
if(subops.length === 1 && subops[0] === "") {
|
if(subops.length === 1 && subops[0] === "") {
|
||||||
return source;
|
return source;
|
||||||
} else if(subops.length === 1 && subops[0] === "tiddlers") {
|
} else if(subops.length === 1 && subops[0] === "tiddlers") {
|
||||||
@@ -46,6 +42,10 @@ exports.all = function(source,operator,options) {
|
|||||||
return options.wiki.eachShadowPlusTiddlers;
|
return options.wiki.eachShadowPlusTiddlers;
|
||||||
}
|
}
|
||||||
// Do it the hard way
|
// Do it the hard way
|
||||||
|
// Get our suboperators
|
||||||
|
var allFilterOperators = getAllFilterOperators();
|
||||||
|
// Cycle through the suboperators accumulating their results
|
||||||
|
var results = new $tw.utils.LinkedList();
|
||||||
for(var t=0; t<subops.length; t++) {
|
for(var t=0; t<subops.length; t++) {
|
||||||
var subop = allFilterOperators[subops[t]];
|
var subop = allFilterOperators[subops[t]];
|
||||||
if(subop) {
|
if(subop) {
|
||||||
|
|||||||
@@ -18,16 +18,20 @@ Export our filter functions
|
|||||||
|
|
||||||
exports.decodebase64 = function(source,operator,options) {
|
exports.decodebase64 = function(source,operator,options) {
|
||||||
var results = [];
|
var results = [];
|
||||||
|
var binary = operator.suffixes && operator.suffixes.indexOf("binary") !== -1;
|
||||||
|
var urlsafe = operator.suffixes && operator.suffixes.indexOf("urlsafe") !== -1;
|
||||||
source(function(tiddler,title) {
|
source(function(tiddler,title) {
|
||||||
results.push($tw.utils.base64Decode(title));
|
results.push($tw.utils.base64Decode(title,binary,urlsafe));
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.encodebase64 = function(source,operator,options) {
|
exports.encodebase64 = function(source,operator,options) {
|
||||||
var results = [];
|
var results = [];
|
||||||
|
var binary = operator.suffixes && operator.suffixes.indexOf("binary") !== -1;
|
||||||
|
var urlsafe = operator.suffixes && operator.suffixes.indexOf("urlsafe") !== -1;
|
||||||
source(function(tiddler,title) {
|
source(function(tiddler,title) {
|
||||||
results.push($tw.utils.base64Encode(title));
|
results.push($tw.utils.base64Encode(title,binary,urlsafe));
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,6 +68,54 @@ exports["jsontype"] = function(source,operator,options) {
|
|||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports["jsonset"] = function(source,operator,options) {
|
||||||
|
var suffixes = operator.suffixes || [],
|
||||||
|
type = suffixes[0] && suffixes[0][0],
|
||||||
|
indexes = operator.operands.slice(0,-1),
|
||||||
|
value = operator.operands[operator.operands.length - 1],
|
||||||
|
results = [];
|
||||||
|
if(operator.operands.length === 1 && operator.operands[0] === "") {
|
||||||
|
value = undefined; // Prevents the value from being assigned
|
||||||
|
}
|
||||||
|
switch(type) {
|
||||||
|
case "string":
|
||||||
|
// Use value unchanged
|
||||||
|
break;
|
||||||
|
case "boolean":
|
||||||
|
value = (value === "true" ? true : (value === "false" ? false : undefined));
|
||||||
|
break;
|
||||||
|
case "number":
|
||||||
|
value = $tw.utils.parseNumber(value);
|
||||||
|
break;
|
||||||
|
case "array":
|
||||||
|
indexes = operator.operands;
|
||||||
|
value = [];
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
indexes = operator.operands;
|
||||||
|
value = {};
|
||||||
|
break;
|
||||||
|
case "null":
|
||||||
|
indexes = operator.operands;
|
||||||
|
value = null;
|
||||||
|
break;
|
||||||
|
case "json":
|
||||||
|
value = $tw.utils.parseJSONSafe(value,function() {return undefined;});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Use value unchanged
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
source(function(tiddler,title) {
|
||||||
|
var data = $tw.utils.parseJSONSafe(title,title);
|
||||||
|
if(data) {
|
||||||
|
data = setDataItem(data,indexes,value);
|
||||||
|
results.push(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid
|
Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid
|
||||||
*/
|
*/
|
||||||
@@ -186,5 +234,34 @@ function getDataItem(data,indexes) {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Given a JSON data structure, an array of index strings and a value, return the data structure with the value added at the end of the index chain. If any of the index strings are invalid then the JSON data structure is returned unmodified. If the root item is targetted then a different data object will be returned
|
||||||
|
*/
|
||||||
|
function setDataItem(data,indexes,value) {
|
||||||
|
// Ignore attempts to assign undefined
|
||||||
|
if(value === undefined) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
// Check for the root item
|
||||||
|
if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
// Traverse the JSON data structure using the index chain
|
||||||
|
var current = data;
|
||||||
|
for(var i = 0; i < indexes.length - 1; i++) {
|
||||||
|
var index = indexes[i];
|
||||||
|
if($tw.utils.hop(current,index)) {
|
||||||
|
current = current[index];
|
||||||
|
} else {
|
||||||
|
// Return the original JSON data structure if any of the index strings are invalid
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the value to the end of the index chain
|
||||||
|
var lastIndex = indexes[indexes.length - 1];
|
||||||
|
current[lastIndex] = value;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -58,6 +58,7 @@ Last entry/entries in list
|
|||||||
exports.last = function(source,operator,options) {
|
exports.last = function(source,operator,options) {
|
||||||
var count = $tw.utils.getInt(operator.operand,1),
|
var count = $tw.utils.getInt(operator.operand,1),
|
||||||
results = [];
|
results = [];
|
||||||
|
if(count === 0) return results;
|
||||||
source(function(tiddler,title) {
|
source(function(tiddler,title) {
|
||||||
results.push(title);
|
results.push(title);
|
||||||
});
|
});
|
||||||
|
|||||||
120
core/modules/parsers/wikiparser/rules/conditional.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/*\
|
||||||
|
title: $:/core/modules/parsers/wikiparser/rules/conditional.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: wikirule
|
||||||
|
|
||||||
|
Conditional shortcut syntax
|
||||||
|
|
||||||
|
```
|
||||||
|
This is a <% if [{something}] %>Elephant<% elseif [{else}] %>Pelican<% else %>Crocodile<% endif %>
|
||||||
|
```
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
exports.name = "conditional";
|
||||||
|
exports.types = {inline: true, block: true};
|
||||||
|
|
||||||
|
exports.init = function(parser) {
|
||||||
|
this.parser = parser;
|
||||||
|
// Regexp to match
|
||||||
|
this.matchRegExp = /\<\%\s*if\s+/mg;
|
||||||
|
this.terminateIfRegExp = /\%\>/mg;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.findNextMatch = function(startPos) {
|
||||||
|
// Look for the next <% if shortcut
|
||||||
|
this.matchRegExp.lastIndex = startPos;
|
||||||
|
this.match = this.matchRegExp.exec(this.parser.source);
|
||||||
|
// If not found then return no match
|
||||||
|
if(!this.match) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// Check for the next %>
|
||||||
|
this.terminateIfRegExp.lastIndex = this.match.index;
|
||||||
|
this.terminateIfMatch = this.terminateIfRegExp.exec(this.parser.source);
|
||||||
|
// If not found then return no match
|
||||||
|
if(!this.terminateIfMatch) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// Return the position at which the construction was found
|
||||||
|
return this.match.index;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parse the most recent match
|
||||||
|
*/
|
||||||
|
exports.parse = function() {
|
||||||
|
// Get the filter condition
|
||||||
|
var filterCondition = this.parser.source.substring(this.match.index + this.match[0].length,this.terminateIfMatch.index);
|
||||||
|
// Advance the parser position to past the %>
|
||||||
|
this.parser.pos = this.terminateIfMatch.index + this.terminateIfMatch[0].length;
|
||||||
|
// Parse the if clause
|
||||||
|
return this.parseIfClause(filterCondition);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.parseIfClause = function(filterCondition) {
|
||||||
|
// Create the list widget
|
||||||
|
var listWidget = {
|
||||||
|
type: "list",
|
||||||
|
tag: "$list",
|
||||||
|
isBlock: this.is.block,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "list-template",
|
||||||
|
tag: "$list-template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "list-empty",
|
||||||
|
tag: "$list-empty"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
$tw.utils.addAttributeToParseTreeNode(listWidget,"filter",filterCondition);
|
||||||
|
$tw.utils.addAttributeToParseTreeNode(listWidget,"variable","condition");
|
||||||
|
$tw.utils.addAttributeToParseTreeNode(listWidget,"limit","1");
|
||||||
|
// Check for an immediately following double linebreak
|
||||||
|
var hasLineBreak = !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
|
||||||
|
// Parse the body looking for else or endif
|
||||||
|
var reEndString = "\\<\\%\\s*(endif)\\s*\\%\\>|\\<\\%\\s*(else)\\s*\\%\\>|\\<\\%\\s*(elseif)\\s+([\\s\\S]+?)\\%\\>",
|
||||||
|
ex;
|
||||||
|
if(hasLineBreak) {
|
||||||
|
ex = this.parser.parseBlocksTerminatedExtended(reEndString);
|
||||||
|
} else {
|
||||||
|
var reEnd = new RegExp(reEndString,"mg");
|
||||||
|
ex = this.parser.parseInlineRunTerminatedExtended(reEnd,{eatTerminator: true});
|
||||||
|
}
|
||||||
|
// Put the body into the list template
|
||||||
|
listWidget.children[0].children = ex.tree;
|
||||||
|
// Check for an else or elseif
|
||||||
|
if(ex.match) {
|
||||||
|
if(ex.match[1] === "endif") {
|
||||||
|
// Nothing to do if we just found an endif
|
||||||
|
} else if(ex.match[2] === "else") {
|
||||||
|
// Check for an immediately following double linebreak
|
||||||
|
hasLineBreak = !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
|
||||||
|
// If we found an else then we need to parse the body looking for the endif
|
||||||
|
var reEndString = "\\<\\%\\s*(endif)\\s*\\%\\>",
|
||||||
|
ex;
|
||||||
|
if(hasLineBreak) {
|
||||||
|
ex = this.parser.parseBlocksTerminatedExtended(reEndString);
|
||||||
|
} else {
|
||||||
|
var reEnd = new RegExp(reEndString,"mg");
|
||||||
|
ex = this.parser.parseInlineRunTerminatedExtended(reEnd,{eatTerminator: true});
|
||||||
|
}
|
||||||
|
// Put the parsed content inside the list empty template
|
||||||
|
listWidget.children[1].children = ex.tree;
|
||||||
|
} else if(ex.match[3] === "elseif") {
|
||||||
|
// Parse the elseif clause by reusing this parser, passing the new filter condition
|
||||||
|
listWidget.children[1].children = this.parseIfClause(ex.match[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return the parse tree node
|
||||||
|
return [listWidget];
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -81,6 +81,9 @@ exports.parse = function() {
|
|||||||
}
|
}
|
||||||
return [tiddlerNode];
|
return [tiddlerNode];
|
||||||
} else {
|
} else {
|
||||||
|
// No template or text reference is provided, so we'll use a blank target. Otherwise we'll generate
|
||||||
|
// a transclude widget that transcludes the current tiddler, often leading to recursion errors
|
||||||
|
transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: ""};
|
||||||
return [transcludeNode];
|
return [transcludeNode];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ exports.parse = function() {
|
|||||||
}
|
}
|
||||||
return [tiddlerNode];
|
return [tiddlerNode];
|
||||||
} else {
|
} else {
|
||||||
|
// No template or text reference is provided, so we'll use a blank target. Otherwise we'll generate
|
||||||
|
// a transclude widget that transcludes the current tiddler, often leading to recursion errors
|
||||||
|
transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: ""};
|
||||||
return [transcludeNode];
|
return [transcludeNode];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ Parse a block from the current position
|
|||||||
terminatorRegExpString: optional regular expression string that identifies the end of plain paragraphs. Must not include capturing parenthesis
|
terminatorRegExpString: optional regular expression string that identifies the end of plain paragraphs. Must not include capturing parenthesis
|
||||||
*/
|
*/
|
||||||
WikiParser.prototype.parseBlock = function(terminatorRegExpString) {
|
WikiParser.prototype.parseBlock = function(terminatorRegExpString) {
|
||||||
var terminatorRegExp = terminatorRegExpString ? new RegExp("(" + terminatorRegExpString + "|\\r?\\n\\r?\\n)","mg") : /(\r?\n\r?\n)/mg;
|
var terminatorRegExp = terminatorRegExpString ? new RegExp(terminatorRegExpString + "|\\r?\\n\\r?\\n","mg") : /(\r?\n\r?\n)/mg;
|
||||||
this.skipWhitespace();
|
this.skipWhitespace();
|
||||||
if(this.pos >= this.sourceLength) {
|
if(this.pos >= this.sourceLength) {
|
||||||
return [];
|
return [];
|
||||||
@@ -264,11 +264,21 @@ WikiParser.prototype.parseBlocksUnterminated = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Parse blocks of text until a terminating regexp is encountered
|
Parse blocks of text until a terminating regexp is encountered. Wrapper for parseBlocksTerminatedExtended that just returns the parse tree
|
||||||
*/
|
*/
|
||||||
WikiParser.prototype.parseBlocksTerminated = function(terminatorRegExpString) {
|
WikiParser.prototype.parseBlocksTerminated = function(terminatorRegExpString) {
|
||||||
var terminatorRegExp = new RegExp("(" + terminatorRegExpString + ")","mg"),
|
var ex = this.parseBlocksTerminatedExtended(terminatorRegExpString);
|
||||||
tree = [];
|
return ex.tree;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parse blocks of text until a terminating regexp is encountered
|
||||||
|
*/
|
||||||
|
WikiParser.prototype.parseBlocksTerminatedExtended = function(terminatorRegExpString) {
|
||||||
|
var terminatorRegExp = new RegExp(terminatorRegExpString,"mg"),
|
||||||
|
result = {
|
||||||
|
tree: []
|
||||||
|
};
|
||||||
// Skip any whitespace
|
// Skip any whitespace
|
||||||
this.skipWhitespace();
|
this.skipWhitespace();
|
||||||
// Check if we've got the end marker
|
// Check if we've got the end marker
|
||||||
@@ -277,7 +287,7 @@ WikiParser.prototype.parseBlocksTerminated = function(terminatorRegExpString) {
|
|||||||
// Parse the text into blocks
|
// Parse the text into blocks
|
||||||
while(this.pos < this.sourceLength && !(match && match.index === this.pos)) {
|
while(this.pos < this.sourceLength && !(match && match.index === this.pos)) {
|
||||||
var blocks = this.parseBlock(terminatorRegExpString);
|
var blocks = this.parseBlock(terminatorRegExpString);
|
||||||
tree.push.apply(tree,blocks);
|
result.tree.push.apply(result.tree,blocks);
|
||||||
// Skip any whitespace
|
// Skip any whitespace
|
||||||
this.skipWhitespace();
|
this.skipWhitespace();
|
||||||
// Check if we've got the end marker
|
// Check if we've got the end marker
|
||||||
@@ -286,8 +296,9 @@ WikiParser.prototype.parseBlocksTerminated = function(terminatorRegExpString) {
|
|||||||
}
|
}
|
||||||
if(match && match.index === this.pos) {
|
if(match && match.index === this.pos) {
|
||||||
this.pos = match.index + match[0].length;
|
this.pos = match.index + match[0].length;
|
||||||
|
result.match = match;
|
||||||
}
|
}
|
||||||
return tree;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -330,6 +341,11 @@ WikiParser.prototype.parseInlineRunUnterminated = function(options) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,options) {
|
WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,options) {
|
||||||
|
var ex = this.parseInlineRunTerminatedExtended(terminatorRegExp,options);
|
||||||
|
return ex.tree;
|
||||||
|
};
|
||||||
|
|
||||||
|
WikiParser.prototype.parseInlineRunTerminatedExtended = function(terminatorRegExp,options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var tree = [];
|
var tree = [];
|
||||||
// Find the next occurrence of the terminator
|
// Find the next occurrence of the terminator
|
||||||
@@ -349,7 +365,10 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
|
|||||||
if(options.eatTerminator) {
|
if(options.eatTerminator) {
|
||||||
this.pos += terminatorMatch[0].length;
|
this.pos += terminatorMatch[0].length;
|
||||||
}
|
}
|
||||||
return tree;
|
return {
|
||||||
|
match: terminatorMatch,
|
||||||
|
tree: tree
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Process any inline rule, along with the text preceding it
|
// Process any inline rule, along with the text preceding it
|
||||||
@@ -373,7 +392,9 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
|
|||||||
this.pushTextWidget(tree,this.source.substr(this.pos),this.pos,this.sourceLength);
|
this.pushTextWidget(tree,this.source.substr(this.pos),this.pos,this.sourceLength);
|
||||||
}
|
}
|
||||||
this.pos = this.sourceLength;
|
this.pos = this.sourceLength;
|
||||||
return tree;
|
return {
|
||||||
|
tree: tree
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ GitHubSaver.prototype.save = function(text,method,callback) {
|
|||||||
headers = {
|
headers = {
|
||||||
"Accept": "application/vnd.github.v3+json",
|
"Accept": "application/vnd.github.v3+json",
|
||||||
"Content-Type": "application/json;charset=UTF-8",
|
"Content-Type": "application/json;charset=UTF-8",
|
||||||
"Authorization": "Basic " + window.btoa(username + ":" + password),
|
"Authorization": "Basic " + $tw.utils.base64Encode(username + ":" + password),
|
||||||
"If-None-Match": ""
|
"If-None-Match": ""
|
||||||
};
|
};
|
||||||
// Bail if we don't have everything we need
|
// Bail if we don't have everything we need
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ exports.startup = function() {
|
|||||||
variables = $tw.utils.extend({},paramObject,{currentTiddler: title, "tv-window-id": windowID});
|
variables = $tw.utils.extend({},paramObject,{currentTiddler: title, "tv-window-id": windowID});
|
||||||
// Open the window
|
// Open the window
|
||||||
var srcWindow,
|
var srcWindow,
|
||||||
srcDocument;
|
srcDocument;
|
||||||
// In case that popup blockers deny opening a new window
|
// In case that popup blockers deny opening a new window
|
||||||
try {
|
try {
|
||||||
srcWindow = window.open("","external-" + windowID,"scrollbars,width=" + width + ",height=" + height + (top ? ",top=" + top : "" ) + (left ? ",left=" + left : "" )),
|
srcWindow = window.open("","external-" + windowID,"scrollbars,width=" + width + ",height=" + height + (top ? ",top=" + top : "" ) + (left ? ",left=" + left : "" )),
|
||||||
@@ -52,6 +52,7 @@ exports.startup = function() {
|
|||||||
$tw.windows[windowID] = srcWindow;
|
$tw.windows[windowID] = srcWindow;
|
||||||
// Check for reopening the same window
|
// Check for reopening the same window
|
||||||
if(srcWindow.haveInitialisedWindow) {
|
if(srcWindow.haveInitialisedWindow) {
|
||||||
|
srcWindow.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Initialise the document
|
// Initialise the document
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
|
|||||||
Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
|
Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
|
||||||
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
|
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
|
||||||
Syncer.prototype.titleSyncThrottleInterval = "$:/config/SyncThrottleInterval";
|
Syncer.prototype.titleSyncThrottleInterval = "$:/config/SyncThrottleInterval";
|
||||||
Syncer.prototype.taskTimerInterval = 1 * 1000; // Interval for sync timer
|
Syncer.prototype.taskTimerInterval = 0.25 * 1000; // Interval for sync timer
|
||||||
Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s...
|
Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s...
|
||||||
Syncer.prototype.errorRetryInterval = 5 * 1000; // Interval to retry after an error
|
Syncer.prototype.errorRetryInterval = 5 * 1000; // Interval to retry after an error
|
||||||
Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s
|
Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s
|
||||||
@@ -74,9 +74,11 @@ function Syncer(options) {
|
|||||||
this.titlesHaveBeenLazyLoaded = {}; // Hashmap of titles of tiddlers that have already been lazily loaded from the server
|
this.titlesHaveBeenLazyLoaded = {}; // Hashmap of titles of tiddlers that have already been lazily loaded from the server
|
||||||
// Timers
|
// Timers
|
||||||
this.taskTimerId = null; // Timer for task dispatch
|
this.taskTimerId = null; // Timer for task dispatch
|
||||||
this.pollTimerId = null; // Timer for polling server
|
|
||||||
// Number of outstanding requests
|
// Number of outstanding requests
|
||||||
this.numTasksInProgress = 0;
|
this.numTasksInProgress = 0;
|
||||||
|
// True when we want to force an immediate sync from the server
|
||||||
|
this.forceSyncFromServer = false;
|
||||||
|
this.timestampLastSyncFromServer = new Date();
|
||||||
// Listen out for changes to tiddlers
|
// Listen out for changes to tiddlers
|
||||||
this.wiki.addEventListener("change",function(changes) {
|
this.wiki.addEventListener("change",function(changes) {
|
||||||
// Filter the changes to just include ones that are being synced
|
// Filter the changes to just include ones that are being synced
|
||||||
@@ -187,6 +189,7 @@ Syncer.prototype.readTiddlerInfo = function() {
|
|||||||
// Record information for known tiddlers
|
// Record information for known tiddlers
|
||||||
var self = this,
|
var self = this,
|
||||||
tiddlers = this.getSyncedTiddlers();
|
tiddlers = this.getSyncedTiddlers();
|
||||||
|
this.logger.log("Initialising tiddlerInfo for " + tiddlers.length + " tiddlers");
|
||||||
$tw.utils.each(tiddlers,function(title) {
|
$tw.utils.each(tiddlers,function(title) {
|
||||||
var tiddler = self.wiki.getTiddler(title);
|
var tiddler = self.wiki.getTiddler(title);
|
||||||
if(tiddler) {
|
if(tiddler) {
|
||||||
@@ -203,33 +206,38 @@ Syncer.prototype.readTiddlerInfo = function() {
|
|||||||
Checks whether the wiki is dirty (ie the window shouldn't be closed)
|
Checks whether the wiki is dirty (ie the window shouldn't be closed)
|
||||||
*/
|
*/
|
||||||
Syncer.prototype.isDirty = function() {
|
Syncer.prototype.isDirty = function() {
|
||||||
this.logger.log("Checking dirty status");
|
var self = this;
|
||||||
// Check tiddlers that are in the store and included in the filter function
|
function checkIsDirty() {
|
||||||
var titles = this.getSyncedTiddlers();
|
// Check tiddlers that are in the store and included in the filter function
|
||||||
for(var index=0; index<titles.length; index++) {
|
var titles = self.getSyncedTiddlers();
|
||||||
var title = titles[index],
|
for(var index=0; index<titles.length; index++) {
|
||||||
tiddlerInfo = this.tiddlerInfo[title];
|
var title = titles[index],
|
||||||
if(this.wiki.tiddlerExists(title)) {
|
tiddlerInfo = self.tiddlerInfo[title];
|
||||||
if(tiddlerInfo) {
|
if(self.wiki.tiddlerExists(title)) {
|
||||||
// If the tiddler is known on the server and has been modified locally then it needs to be saved to the server
|
if(tiddlerInfo) {
|
||||||
if(this.wiki.getChangeCount(title) > tiddlerInfo.changeCount) {
|
// If the tiddler is known on the server and has been modified locally then it needs to be saved to the server
|
||||||
|
if(self.wiki.getChangeCount(title) > tiddlerInfo.changeCount) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the tiddler isn't known on the server then it needs to be saved to the server
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// If the tiddler isn't known on the server then it needs to be saved to the server
|
}
|
||||||
|
// Check tiddlers that are known from the server but not currently in the store
|
||||||
|
titles = Object.keys(self.tiddlerInfo);
|
||||||
|
for(index=0; index<titles.length; index++) {
|
||||||
|
if(!self.wiki.tiddlerExists(titles[index])) {
|
||||||
|
// There must be a pending delete
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
// Check tiddlers that are known from the server but not currently in the store
|
var dirtyStatus = checkIsDirty();
|
||||||
titles = Object.keys(this.tiddlerInfo);
|
this.logger.log("Dirty status was " + dirtyStatus);
|
||||||
for(index=0; index<titles.length; index++) {
|
return dirtyStatus;
|
||||||
if(!this.wiki.tiddlerExists(titles[index])) {
|
|
||||||
// There must be a pending delete
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -258,6 +266,7 @@ Syncer.prototype.storeTiddler = function(tiddlerFields) {
|
|||||||
adaptorInfo: this.syncadaptor.getTiddlerInfo(tiddler),
|
adaptorInfo: this.syncadaptor.getTiddlerInfo(tiddler),
|
||||||
changeCount: this.wiki.getChangeCount(tiddlerFields.title)
|
changeCount: this.wiki.getChangeCount(tiddlerFields.title)
|
||||||
};
|
};
|
||||||
|
this.logger.log("Updating tiddler info in syncer.storeTiddler for " + tiddlerFields.title + " " + JSON.stringify(this.tiddlerInfo[tiddlerFields.title]));
|
||||||
};
|
};
|
||||||
|
|
||||||
Syncer.prototype.getStatus = function(callback) {
|
Syncer.prototype.getStatus = function(callback) {
|
||||||
@@ -293,90 +302,8 @@ Syncer.prototype.getStatus = function(callback) {
|
|||||||
Synchronise from the server by reading the skinny tiddler list and queuing up loads for any tiddlers that we don't already have up to date
|
Synchronise from the server by reading the skinny tiddler list and queuing up loads for any tiddlers that we don't already have up to date
|
||||||
*/
|
*/
|
||||||
Syncer.prototype.syncFromServer = function() {
|
Syncer.prototype.syncFromServer = function() {
|
||||||
var self = this,
|
this.forceSyncFromServer = true;
|
||||||
cancelNextSync = function() {
|
this.processTaskQueue();
|
||||||
if(self.pollTimerId) {
|
|
||||||
clearTimeout(self.pollTimerId);
|
|
||||||
self.pollTimerId = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
triggerNextSync = function() {
|
|
||||||
self.pollTimerId = setTimeout(function() {
|
|
||||||
self.pollTimerId = null;
|
|
||||||
self.syncFromServer.call(self);
|
|
||||||
},self.pollTimerInterval);
|
|
||||||
},
|
|
||||||
syncSystemFromServer = (self.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes" ? true : false);
|
|
||||||
if(this.syncadaptor && this.syncadaptor.getUpdatedTiddlers) {
|
|
||||||
this.logger.log("Retrieving updated tiddler list");
|
|
||||||
cancelNextSync();
|
|
||||||
this.syncadaptor.getUpdatedTiddlers(self,function(err,updates) {
|
|
||||||
triggerNextSync();
|
|
||||||
if(err) {
|
|
||||||
self.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(updates) {
|
|
||||||
$tw.utils.each(updates.modifications,function(title) {
|
|
||||||
self.titlesToBeLoaded[title] = true;
|
|
||||||
});
|
|
||||||
$tw.utils.each(updates.deletions,function(title) {
|
|
||||||
if(syncSystemFromServer || !self.wiki.isSystemTiddler(title)) {
|
|
||||||
delete self.tiddlerInfo[title];
|
|
||||||
self.logger.log("Deleting tiddler missing from server:",title);
|
|
||||||
self.wiki.deleteTiddler(title);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(updates.modifications.length > 0 || updates.deletions.length > 0) {
|
|
||||||
self.processTaskQueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
|
|
||||||
this.logger.log("Retrieving skinny tiddler list");
|
|
||||||
cancelNextSync();
|
|
||||||
this.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) {
|
|
||||||
triggerNextSync();
|
|
||||||
// Check for errors
|
|
||||||
if(err) {
|
|
||||||
self.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Keep track of which tiddlers we already know about have been reported this time
|
|
||||||
var previousTitles = Object.keys(self.tiddlerInfo);
|
|
||||||
// Process each incoming tiddler
|
|
||||||
for(var t=0; t<tiddlers.length; t++) {
|
|
||||||
// Get the incoming tiddler fields, and the existing tiddler
|
|
||||||
var tiddlerFields = tiddlers[t],
|
|
||||||
incomingRevision = tiddlerFields.revision + "",
|
|
||||||
tiddler = self.wiki.tiddlerExists(tiddlerFields.title) && self.wiki.getTiddler(tiddlerFields.title),
|
|
||||||
tiddlerInfo = self.tiddlerInfo[tiddlerFields.title],
|
|
||||||
currRevision = tiddlerInfo ? tiddlerInfo.revision : null,
|
|
||||||
indexInPreviousTitles = previousTitles.indexOf(tiddlerFields.title);
|
|
||||||
if(indexInPreviousTitles !== -1) {
|
|
||||||
previousTitles.splice(indexInPreviousTitles,1);
|
|
||||||
}
|
|
||||||
// Ignore the incoming tiddler if it's the same as the revision we've already got
|
|
||||||
if(currRevision !== incomingRevision) {
|
|
||||||
// Only load the skinny version if we don't already have a fat version of the tiddler
|
|
||||||
if(!tiddler || tiddler.fields.text === undefined) {
|
|
||||||
self.storeTiddler(tiddlerFields);
|
|
||||||
}
|
|
||||||
// Do a full load of this tiddler
|
|
||||||
self.titlesToBeLoaded[tiddlerFields.title] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Delete any tiddlers that were previously reported but missing this time
|
|
||||||
$tw.utils.each(previousTitles,function(title) {
|
|
||||||
if(syncSystemFromServer || !self.wiki.isSystemTiddler(title)) {
|
|
||||||
delete self.tiddlerInfo[title];
|
|
||||||
self.logger.log("Deleting tiddler missing from server:",title);
|
|
||||||
self.wiki.deleteTiddler(title);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.processTaskQueue();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -498,6 +425,7 @@ Syncer.prototype.processTaskQueue = function() {
|
|||||||
if((!this.syncadaptor.isReady || this.syncadaptor.isReady()) && this.numTasksInProgress === 0) {
|
if((!this.syncadaptor.isReady || this.syncadaptor.isReady()) && this.numTasksInProgress === 0) {
|
||||||
// Choose the next task to perform
|
// Choose the next task to perform
|
||||||
var task = this.chooseNextTask();
|
var task = this.chooseNextTask();
|
||||||
|
self.logger.log("Chosen next task " + task);
|
||||||
// Perform the task if we had one
|
// Perform the task if we had one
|
||||||
if(typeof task === "object" && task !== null) {
|
if(typeof task === "object" && task !== null) {
|
||||||
this.numTasksInProgress += 1;
|
this.numTasksInProgress += 1;
|
||||||
@@ -510,7 +438,7 @@ Syncer.prototype.processTaskQueue = function() {
|
|||||||
} else {
|
} else {
|
||||||
self.updateDirtyStatus();
|
self.updateDirtyStatus();
|
||||||
// Process the next task
|
// Process the next task
|
||||||
self.processTaskQueue.call(self);
|
self.processTaskQueue.call(self);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -518,31 +446,39 @@ Syncer.prototype.processTaskQueue = function() {
|
|||||||
this.updateDirtyStatus();
|
this.updateDirtyStatus();
|
||||||
// And trigger a timeout if there is a pending task
|
// And trigger a timeout if there is a pending task
|
||||||
if(task === true) {
|
if(task === true) {
|
||||||
this.triggerTimeout();
|
this.triggerTimeout(this.taskTimerInterval);
|
||||||
|
} else {
|
||||||
|
this.triggerTimeout(this.pollTimerInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.updateDirtyStatus();
|
this.updateDirtyStatus();
|
||||||
|
this.triggerTimeout(this.taskTimerInterval);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Syncer.prototype.triggerTimeout = function(interval) {
|
Syncer.prototype.triggerTimeout = function(interval) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if(!this.taskTimerId) {
|
if(this.taskTimerId) {
|
||||||
this.taskTimerId = setTimeout(function() {
|
clearTimeout(this.taskTimerId);
|
||||||
self.taskTimerId = null;
|
|
||||||
self.processTaskQueue.call(self);
|
|
||||||
},interval || self.taskTimerInterval);
|
|
||||||
}
|
}
|
||||||
|
this.taskTimerId = setTimeout(function() {
|
||||||
|
self.taskTimerId = null;
|
||||||
|
self.processTaskQueue.call(self);
|
||||||
|
},interval || self.taskTimerInterval);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Choose the next sync task. We prioritise saves, then deletes, then loads from the server
|
Choose the next sync task. We prioritise saves to the server, then getting updates from the server, then deletes to the server, then loads from the server
|
||||||
|
|
||||||
Returns either a task object, null if there's no upcoming tasks, or the boolean true if there are pending tasks that aren't yet due
|
Returns either:
|
||||||
|
* a task object
|
||||||
|
* the boolean true if there are pending sync tasks that aren't yet due
|
||||||
|
* null if there's no pending sync tasks (just the next poll)
|
||||||
*/
|
*/
|
||||||
Syncer.prototype.chooseNextTask = function() {
|
Syncer.prototype.chooseNextTask = function() {
|
||||||
var thresholdLastSaved = (new Date()) - this.throttleInterval,
|
var now = new Date(),
|
||||||
|
thresholdLastSaved = now - this.throttleInterval,
|
||||||
havePending = null;
|
havePending = null;
|
||||||
// First we look for tiddlers that have been modified locally and need saving back to the server
|
// First we look for tiddlers that have been modified locally and need saving back to the server
|
||||||
var titles = this.getSyncedTiddlers();
|
var titles = this.getSyncedTiddlers();
|
||||||
@@ -556,14 +492,18 @@ Syncer.prototype.chooseNextTask = function() {
|
|||||||
isReadyToSave = !tiddlerInfo || !tiddlerInfo.timestampLastSaved || tiddlerInfo.timestampLastSaved < thresholdLastSaved;
|
isReadyToSave = !tiddlerInfo || !tiddlerInfo.timestampLastSaved || tiddlerInfo.timestampLastSaved < thresholdLastSaved;
|
||||||
if(hasChanged) {
|
if(hasChanged) {
|
||||||
if(isReadyToSave) {
|
if(isReadyToSave) {
|
||||||
return new SaveTiddlerTask(this,title);
|
return new SaveTiddlerTask(this,title);
|
||||||
} else {
|
} else {
|
||||||
havePending = true;
|
havePending = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Second, we check tiddlers that are known from the server but not currently in the store, and so need deleting on the server
|
// Second we check for an outstanding sync from server
|
||||||
|
if(this.forceSyncFromServer || (this.timestampLastSyncFromServer && (now.valueOf() >= (this.timestampLastSyncFromServer.valueOf() + this.pollTimerInterval)))) {
|
||||||
|
return new SyncFromServerTask(this);
|
||||||
|
}
|
||||||
|
// Third, we check tiddlers that are known from the server but not currently in the store, and so need deleting on the server
|
||||||
titles = Object.keys(this.tiddlerInfo);
|
titles = Object.keys(this.tiddlerInfo);
|
||||||
for(index=0; index<titles.length; index++) {
|
for(index=0; index<titles.length; index++) {
|
||||||
title = titles[index];
|
title = titles[index];
|
||||||
@@ -573,13 +513,13 @@ Syncer.prototype.chooseNextTask = function() {
|
|||||||
return new DeleteTiddlerTask(this,title);
|
return new DeleteTiddlerTask(this,title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check for tiddlers that need loading
|
// Finally, check for tiddlers that need loading
|
||||||
title = Object.keys(this.titlesToBeLoaded)[0];
|
title = Object.keys(this.titlesToBeLoaded)[0];
|
||||||
if(title) {
|
if(title) {
|
||||||
delete this.titlesToBeLoaded[title];
|
delete this.titlesToBeLoaded[title];
|
||||||
return new LoadTiddlerTask(this,title);
|
return new LoadTiddlerTask(this,title);
|
||||||
}
|
}
|
||||||
// No tasks are ready
|
// No tasks are ready now, but might be in the future
|
||||||
return havePending;
|
return havePending;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -589,6 +529,10 @@ function SaveTiddlerTask(syncer,title) {
|
|||||||
this.type = "save";
|
this.type = "save";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SaveTiddlerTask.prototype.toString = function() {
|
||||||
|
return "SAVE " + this.title;
|
||||||
|
}
|
||||||
|
|
||||||
SaveTiddlerTask.prototype.run = function(callback) {
|
SaveTiddlerTask.prototype.run = function(callback) {
|
||||||
var self = this,
|
var self = this,
|
||||||
changeCount = this.syncer.wiki.getChangeCount(this.title),
|
changeCount = this.syncer.wiki.getChangeCount(this.title),
|
||||||
@@ -607,6 +551,7 @@ SaveTiddlerTask.prototype.run = function(callback) {
|
|||||||
revision: revision,
|
revision: revision,
|
||||||
timestampLastSaved: new Date()
|
timestampLastSaved: new Date()
|
||||||
};
|
};
|
||||||
|
self.syncer.logger.log("Updating tiddler info in SaveTiddlerTask.run for " + self.title + " " + JSON.stringify(self.syncer.tiddlerInfo[self.title]));
|
||||||
// Invoke the callback
|
// Invoke the callback
|
||||||
callback(null);
|
callback(null);
|
||||||
},{
|
},{
|
||||||
@@ -624,6 +569,10 @@ function DeleteTiddlerTask(syncer,title) {
|
|||||||
this.type = "delete";
|
this.type = "delete";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeleteTiddlerTask.prototype.toString = function() {
|
||||||
|
return "DELETE " + this.title;
|
||||||
|
}
|
||||||
|
|
||||||
DeleteTiddlerTask.prototype.run = function(callback) {
|
DeleteTiddlerTask.prototype.run = function(callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.syncer.logger.log("Dispatching 'delete' task:",this.title);
|
this.syncer.logger.log("Dispatching 'delete' task:",this.title);
|
||||||
@@ -633,6 +582,7 @@ DeleteTiddlerTask.prototype.run = function(callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
// Remove the info stored about this tiddler
|
// Remove the info stored about this tiddler
|
||||||
|
self.syncer.logger.log("Deleting tiddler info in DeleteTiddlerTask.run for " + self.title);
|
||||||
delete self.syncer.tiddlerInfo[self.title];
|
delete self.syncer.tiddlerInfo[self.title];
|
||||||
// Invoke the callback
|
// Invoke the callback
|
||||||
callback(null);
|
callback(null);
|
||||||
@@ -647,6 +597,10 @@ function LoadTiddlerTask(syncer,title) {
|
|||||||
this.type = "load";
|
this.type = "load";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadTiddlerTask.prototype.toString = function() {
|
||||||
|
return "LOAD " + this.title;
|
||||||
|
}
|
||||||
|
|
||||||
LoadTiddlerTask.prototype.run = function(callback) {
|
LoadTiddlerTask.prototype.run = function(callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.syncer.logger.log("Dispatching 'load' task:",this.title);
|
this.syncer.logger.log("Dispatching 'load' task:",this.title);
|
||||||
@@ -664,6 +618,94 @@ LoadTiddlerTask.prototype.run = function(callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function SyncFromServerTask(syncer) {
|
||||||
|
this.syncer = syncer;
|
||||||
|
this.type = "syncfromserver";
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncFromServerTask.prototype.toString = function() {
|
||||||
|
return "SYNCFROMSERVER";
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncFromServerTask.prototype.run = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
var syncSystemFromServer = (self.syncer.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes" ? true : false);
|
||||||
|
var successCallback = function() {
|
||||||
|
self.syncer.forceSyncFromServer = false;
|
||||||
|
self.syncer.timestampLastSyncFromServer = new Date();
|
||||||
|
callback(null);
|
||||||
|
};
|
||||||
|
if(this.syncer.syncadaptor.getUpdatedTiddlers) {
|
||||||
|
self.syncer.logger.log("Retrieving updated tiddler list");
|
||||||
|
this.syncer.syncadaptor.getUpdatedTiddlers(self,function(err,updates) {
|
||||||
|
if(err) {
|
||||||
|
self.syncer.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
if(updates) {
|
||||||
|
$tw.utils.each(updates.modifications,function(title) {
|
||||||
|
self.syncer.titlesToBeLoaded[title] = true;
|
||||||
|
});
|
||||||
|
$tw.utils.each(updates.deletions,function(title) {
|
||||||
|
if(syncSystemFromServer || !self.syncer.wiki.isSystemTiddler(title)) {
|
||||||
|
delete self.syncer.tiddlerInfo[title];
|
||||||
|
self.syncer.logger.log("Deleting tiddler missing from server:",title);
|
||||||
|
self.syncer.wiki.deleteTiddler(title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return successCallback();
|
||||||
|
});
|
||||||
|
} else if(this.syncer.syncadaptor.getSkinnyTiddlers) {
|
||||||
|
this.syncer.logger.log("Retrieving skinny tiddler list");
|
||||||
|
this.syncer.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) {
|
||||||
|
self.syncer.logger.log("Retrieved skinny tiddler list");
|
||||||
|
// Check for errors
|
||||||
|
if(err) {
|
||||||
|
self.syncer.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
// Keep track of which tiddlers we already know about have been reported this time
|
||||||
|
var previousTitles = Object.keys(self.syncer.tiddlerInfo);
|
||||||
|
// Process each incoming tiddler
|
||||||
|
for(var t=0; t<tiddlers.length; t++) {
|
||||||
|
// Get the incoming tiddler fields, and the existing tiddler
|
||||||
|
var tiddlerFields = tiddlers[t],
|
||||||
|
incomingRevision = tiddlerFields.revision + "",
|
||||||
|
tiddler = self.syncer.wiki.tiddlerExists(tiddlerFields.title) && self.syncer.wiki.getTiddler(tiddlerFields.title),
|
||||||
|
tiddlerInfo = self.syncer.tiddlerInfo[tiddlerFields.title],
|
||||||
|
currRevision = tiddlerInfo ? tiddlerInfo.revision : null,
|
||||||
|
indexInPreviousTitles = previousTitles.indexOf(tiddlerFields.title);
|
||||||
|
if(indexInPreviousTitles !== -1) {
|
||||||
|
previousTitles.splice(indexInPreviousTitles,1);
|
||||||
|
}
|
||||||
|
// Ignore the incoming tiddler if it's the same as the revision we've already got
|
||||||
|
if(currRevision !== incomingRevision) {
|
||||||
|
// Only load the skinny version if we don't already have a fat version of the tiddler
|
||||||
|
if(!tiddler || tiddler.fields.text === undefined) {
|
||||||
|
self.syncer.storeTiddler(tiddlerFields);
|
||||||
|
}
|
||||||
|
// Do a full load of this tiddler
|
||||||
|
self.syncer.titlesToBeLoaded[tiddlerFields.title] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Delete any tiddlers that were previously reported but missing this time
|
||||||
|
$tw.utils.each(previousTitles,function(title) {
|
||||||
|
if(syncSystemFromServer || !self.syncer.wiki.isSystemTiddler(title)) {
|
||||||
|
delete self.syncer.tiddlerInfo[title];
|
||||||
|
self.syncer.logger.log("Deleting tiddler missing from server:",title);
|
||||||
|
self.syncer.wiki.deleteTiddler(title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.syncer.forceSyncFromServer = false;
|
||||||
|
self.syncer.timestampLastSyncFromServer = new Date();
|
||||||
|
return successCallback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return successCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
exports.Syncer = Syncer;
|
exports.Syncer = Syncer;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ HttpClientRequest.prototype.send = function(callback) {
|
|||||||
for (var i=0; i<len; i++) {
|
for (var i=0; i<len; i++) {
|
||||||
binary += String.fromCharCode(bytes[i]);
|
binary += String.fromCharCode(bytes[i]);
|
||||||
}
|
}
|
||||||
resultVariables.data = window.btoa(binary);
|
resultVariables.data = $tw.utils.base64Encode(binary,true);
|
||||||
}
|
}
|
||||||
self.wiki.addTiddler(new $tw.Tiddler(self.wiki.getTiddler(requestTrackerTitle),{
|
self.wiki.addTiddler(new $tw.Tiddler(self.wiki.getTiddler(requestTrackerTitle),{
|
||||||
status: completionCode,
|
status: completionCode,
|
||||||
|
|||||||
@@ -819,18 +819,41 @@ exports.hashString = function(str) {
|
|||||||
},0);
|
},0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Decode a base64 string
|
Decode a base64 string
|
||||||
*/
|
*/
|
||||||
exports.base64Decode = function(string64) {
|
exports.base64Decode = function(string64,binary,urlsafe) {
|
||||||
return base64utf8.base64.decode.call(base64utf8,string64);
|
var encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64;
|
||||||
|
if(binary) return exports.atob(encoded)
|
||||||
|
else return base64utf8.base64.decode.call(base64utf8,encoded);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Encode a string to base64
|
Encode a string to base64
|
||||||
*/
|
*/
|
||||||
exports.base64Encode = function(string64) {
|
exports.base64Encode = function(string64,binary,urlsafe) {
|
||||||
return base64utf8.base64.encode.call(base64utf8,string64);
|
var encoded;
|
||||||
|
if(binary) encoded = exports.btoa(string64);
|
||||||
|
else encoded = base64utf8.base64.encode.call(base64utf8,string64);
|
||||||
|
if(urlsafe) {
|
||||||
|
encoded = encoded.replace(/\+/g,'-').replace(/\//g,'_');
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -58,24 +58,25 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
|
|||||||
if(this.wiki.isImageTiddler(this.imageSource)) {
|
if(this.wiki.isImageTiddler(this.imageSource)) {
|
||||||
var type = tiddler.fields.type,
|
var type = tiddler.fields.type,
|
||||||
text = tiddler.fields.text,
|
text = tiddler.fields.text,
|
||||||
_canonical_uri = tiddler.fields._canonical_uri;
|
_canonical_uri = tiddler.fields._canonical_uri,
|
||||||
|
typeInfo = $tw.config.contentTypeInfo[type] || {},
|
||||||
|
deserializerType = typeInfo.deserializerType || type;
|
||||||
// If the tiddler has body text then it doesn't need to be lazily loaded
|
// If the tiddler has body text then it doesn't need to be lazily loaded
|
||||||
if(text) {
|
if(text) {
|
||||||
// Render the appropriate element for the image type
|
// Render the appropriate element for the image type by looking up the encoding in the content type info
|
||||||
switch(type) {
|
var encoding = typeInfo.encoding || "utf8";
|
||||||
case "application/pdf":
|
if (encoding === "base64") {
|
||||||
|
// .pdf .png .jpg etc.
|
||||||
|
src = "data:" + deserializerType + ";base64," + text;
|
||||||
|
if (deserializerType === "application/pdf") {
|
||||||
tag = "embed";
|
tag = "embed";
|
||||||
src = "data:application/pdf;base64," + text;
|
}
|
||||||
break;
|
} else {
|
||||||
case "image/svg+xml":
|
// .svg .tid .xml etc.
|
||||||
src = "data:image/svg+xml," + encodeURIComponent(text);
|
src = "data:" + deserializerType + "," + encodeURIComponent(text);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
src = "data:" + type + ";base64," + text;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else if(_canonical_uri) {
|
} else if(_canonical_uri) {
|
||||||
switch(type) {
|
switch(deserializerType) {
|
||||||
case "application/pdf":
|
case "application/pdf":
|
||||||
tag = "embed";
|
tag = "embed";
|
||||||
src = _canonical_uri;
|
src = _canonical_uri;
|
||||||
|
|||||||
@@ -28,6 +28,18 @@ Inherit from the base widget class
|
|||||||
*/
|
*/
|
||||||
ListWidget.prototype = new Widget();
|
ListWidget.prototype = new Widget();
|
||||||
|
|
||||||
|
ListWidget.prototype.initialise = function(parseTreeNode,options) {
|
||||||
|
// Bail if parseTreeNode is undefined, meaning that the ListWidget constructor was called without any arguments so that it can be subclassed
|
||||||
|
if(parseTreeNode === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// First call parent constructor to set everything else up
|
||||||
|
Widget.prototype.initialise.call(this,parseTreeNode,options);
|
||||||
|
// Now look for <$list-template> and <$list-empty> widgets as immediate child widgets
|
||||||
|
// This is safe to do during initialization because parse trees never change after creation
|
||||||
|
this.findExplicitTemplates();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Render this widget into the DOM
|
Render this widget into the DOM
|
||||||
*/
|
*/
|
||||||
@@ -60,6 +72,7 @@ ListWidget.prototype.render = function(parent,nextSibling) {
|
|||||||
Compute the internal state of the widget
|
Compute the internal state of the widget
|
||||||
*/
|
*/
|
||||||
ListWidget.prototype.execute = function() {
|
ListWidget.prototype.execute = function() {
|
||||||
|
var self = this;
|
||||||
// Get our attributes
|
// Get our attributes
|
||||||
this.template = this.getAttribute("template");
|
this.template = this.getAttribute("template");
|
||||||
this.editTemplate = this.getAttribute("editTemplate");
|
this.editTemplate = this.getAttribute("editTemplate");
|
||||||
@@ -85,18 +98,51 @@ ListWidget.prototype.execute = function() {
|
|||||||
this.history = [];
|
this.history = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ListWidget.prototype.findExplicitTemplates = function() {
|
||||||
|
var self = this;
|
||||||
|
this.explicitListTemplate = null;
|
||||||
|
this.explicitEmptyTemplate = null;
|
||||||
|
this.hasTemplateInBody = false;
|
||||||
|
var searchChildren = function(childNodes) {
|
||||||
|
$tw.utils.each(childNodes,function(node) {
|
||||||
|
if(node.type === "list-template") {
|
||||||
|
self.explicitListTemplate = node.children;
|
||||||
|
} else if(node.type === "list-empty") {
|
||||||
|
self.explicitEmptyTemplate = node.children;
|
||||||
|
} else if(node.type === "element" && node.tag === "p") {
|
||||||
|
searchChildren(node.children);
|
||||||
|
} else {
|
||||||
|
self.hasTemplateInBody = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
searchChildren(this.parseTreeNode.children);
|
||||||
|
}
|
||||||
|
|
||||||
ListWidget.prototype.getTiddlerList = function() {
|
ListWidget.prototype.getTiddlerList = function() {
|
||||||
|
var limit = $tw.utils.getInt(this.getAttribute("limit",""),undefined);
|
||||||
var defaultFilter = "[!is[system]sort[title]]";
|
var defaultFilter = "[!is[system]sort[title]]";
|
||||||
return this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this);
|
var results = this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this);
|
||||||
|
if(limit !== undefined) {
|
||||||
|
if(limit >= 0) {
|
||||||
|
results = results.slice(0,limit);
|
||||||
|
} else {
|
||||||
|
results = results.slice(limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
ListWidget.prototype.getEmptyMessage = function() {
|
ListWidget.prototype.getEmptyMessage = function() {
|
||||||
var parser,
|
var parser,
|
||||||
emptyMessage = this.getAttribute("emptyMessage","");
|
emptyMessage = this.getAttribute("emptyMessage");
|
||||||
// this.wiki.parseText() calls
|
// If emptyMessage attribute is not present or empty then look for an explicit empty template
|
||||||
// new Parser(..), which should only be done, if needed, because it's heavy!
|
if(!emptyMessage) {
|
||||||
if (emptyMessage === "") {
|
if(this.explicitEmptyTemplate) {
|
||||||
return [];
|
return this.explicitEmptyTemplate;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
parser = this.wiki.parseText("text/vnd.tiddlywiki",emptyMessage,{parseAsInline: true});
|
parser = this.wiki.parseText("text/vnd.tiddlywiki",emptyMessage,{parseAsInline: true});
|
||||||
if(parser) {
|
if(parser) {
|
||||||
@@ -122,12 +168,19 @@ ListWidget.prototype.makeItemTemplate = function(title,index) {
|
|||||||
if(template) {
|
if(template) {
|
||||||
templateTree = [{type: "transclude", attributes: {tiddler: {type: "string", value: template}}}];
|
templateTree = [{type: "transclude", attributes: {tiddler: {type: "string", value: template}}}];
|
||||||
} else {
|
} else {
|
||||||
|
// Check for child nodes of the list widget
|
||||||
if(this.parseTreeNode.children && this.parseTreeNode.children.length > 0) {
|
if(this.parseTreeNode.children && this.parseTreeNode.children.length > 0) {
|
||||||
templateTree = this.parseTreeNode.children;
|
// Check for a <$list-item> widget
|
||||||
} else {
|
if(this.explicitListTemplate) {
|
||||||
|
templateTree = this.explicitListTemplate;
|
||||||
|
} else if(this.hasTemplateInBody) {
|
||||||
|
templateTree = this.parseTreeNode.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!templateTree || templateTree.length === 0) {
|
||||||
// Default template is a link to the title
|
// Default template is a link to the title
|
||||||
templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [
|
templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [
|
||||||
{type: "text", text: title}
|
{type: "text", text: title}
|
||||||
]}]}];
|
]}]}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,6 +278,8 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
|
|||||||
// If we are providing an counter variable then we must refresh the items, otherwise we can rearrange them
|
// If we are providing an counter variable then we must refresh the items, otherwise we can rearrange them
|
||||||
var hasRefreshed = false,t;
|
var hasRefreshed = false,t;
|
||||||
if(this.counterName) {
|
if(this.counterName) {
|
||||||
|
var mustRefreshOldLast = false;
|
||||||
|
var oldLength = this.children.length;
|
||||||
// Cycle through the list and remove and re-insert the first item that has changed, and all the remaining items
|
// Cycle through the list and remove and re-insert the first item that has changed, and all the remaining items
|
||||||
for(t=0; t<this.list.length; t++) {
|
for(t=0; t<this.list.length; t++) {
|
||||||
if(hasRefreshed || !this.children[t] || this.children[t].parseTreeNode.itemTitle !== this.list[t]) {
|
if(hasRefreshed || !this.children[t] || this.children[t].parseTreeNode.itemTitle !== this.list[t]) {
|
||||||
@@ -232,6 +287,9 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
|
|||||||
this.removeListItem(t);
|
this.removeListItem(t);
|
||||||
}
|
}
|
||||||
this.insertListItem(t,this.list[t]);
|
this.insertListItem(t,this.list[t]);
|
||||||
|
if(!hasRefreshed && t === oldLength) {
|
||||||
|
mustRefreshOldLast = true;
|
||||||
|
}
|
||||||
hasRefreshed = true;
|
hasRefreshed = true;
|
||||||
} else {
|
} else {
|
||||||
// Refresh the item we're reusing
|
// Refresh the item we're reusing
|
||||||
@@ -239,6 +297,12 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
|
|||||||
hasRefreshed = hasRefreshed || refreshed;
|
hasRefreshed = hasRefreshed || refreshed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If items were inserted then we must recreate the item that used to be at the last position as it is no longer last
|
||||||
|
if(mustRefreshOldLast && oldLength > 0) {
|
||||||
|
var oldLastIdx = oldLength-1;
|
||||||
|
this.removeListItem(oldLastIdx);
|
||||||
|
this.insertListItem(oldLastIdx,this.list[oldLastIdx]);
|
||||||
|
}
|
||||||
// If there are items to remove and we have not refreshed then recreate the item that will now be at the last position
|
// If there are items to remove and we have not refreshed then recreate the item that will now be at the last position
|
||||||
if(!hasRefreshed && this.children.length > this.list.length) {
|
if(!hasRefreshed && this.children.length > this.list.length) {
|
||||||
this.removeListItem(this.list.length-1);
|
this.removeListItem(this.list.length-1);
|
||||||
@@ -363,4 +427,27 @@ ListItemWidget.prototype.refresh = function(changedTiddlers) {
|
|||||||
|
|
||||||
exports.listitem = ListItemWidget;
|
exports.listitem = ListItemWidget;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make <$list-template> and <$list-empty> widgets that do nothing
|
||||||
|
*/
|
||||||
|
var ListTemplateWidget = function(parseTreeNode,options) {
|
||||||
|
// Main initialisation inherited from widget.js
|
||||||
|
this.initialise(parseTreeNode,options);
|
||||||
|
};
|
||||||
|
ListTemplateWidget.prototype = new Widget();
|
||||||
|
ListTemplateWidget.prototype.render = function() {}
|
||||||
|
ListTemplateWidget.prototype.refresh = function() { return false; }
|
||||||
|
|
||||||
|
exports["list-template"] = ListTemplateWidget;
|
||||||
|
|
||||||
|
var ListEmptyWidget = function(parseTreeNode,options) {
|
||||||
|
// Main initialisation inherited from widget.js
|
||||||
|
this.initialise(parseTreeNode,options);
|
||||||
|
};
|
||||||
|
ListEmptyWidget.prototype = new Widget();
|
||||||
|
ListEmptyWidget.prototype.render = function() {}
|
||||||
|
ListEmptyWidget.prototype.refresh = function() { return false; }
|
||||||
|
|
||||||
|
exports["list-empty"] = ListEmptyWidget;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ TranscludeWidget.prototype.collectAttributes = function() {
|
|||||||
this.recursionMarker = this.getAttribute("recursionMarker","yes");
|
this.recursionMarker = this.getAttribute("recursionMarker","yes");
|
||||||
} else {
|
} else {
|
||||||
this.transcludeVariable = this.getAttribute("$variable");
|
this.transcludeVariable = this.getAttribute("$variable");
|
||||||
|
this.transcludeVariableIsFunction = false;
|
||||||
this.transcludeType = this.getAttribute("$type");
|
this.transcludeType = this.getAttribute("$type");
|
||||||
this.transcludeOutput = this.getAttribute("$output","text/html");
|
this.transcludeOutput = this.getAttribute("$output","text/html");
|
||||||
this.transcludeTitle = this.getAttribute("$tiddler",this.getVariable("currentTiddler"));
|
this.transcludeTitle = this.getAttribute("$tiddler",this.getVariable("currentTiddler"));
|
||||||
@@ -184,7 +185,9 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
|
|||||||
if(this.transcludeVariable) {
|
if(this.transcludeVariable) {
|
||||||
// Transcluding a variable
|
// Transcluding a variable
|
||||||
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()});
|
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()});
|
||||||
|
this.transcludeVariableIsFunction = variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition;
|
||||||
text = variableInfo.text;
|
text = variableInfo.text;
|
||||||
|
this.transcludeFunctionResult = text;
|
||||||
return {
|
return {
|
||||||
text: variableInfo.text,
|
text: variableInfo.text,
|
||||||
type: this.transcludeType
|
type: this.transcludeType
|
||||||
@@ -219,21 +222,24 @@ TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) {
|
|||||||
// Transcluding a variable
|
// Transcluding a variable
|
||||||
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}),
|
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}),
|
||||||
srcVariable = variableInfo && variableInfo.srcVariable;
|
srcVariable = variableInfo && variableInfo.srcVariable;
|
||||||
|
if(srcVariable && srcVariable.isFunctionDefinition) {
|
||||||
|
this.transcludeVariableIsFunction = true;
|
||||||
|
this.transcludeFunctionResult = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || "";
|
||||||
|
}
|
||||||
if(variableInfo.text) {
|
if(variableInfo.text) {
|
||||||
if(srcVariable && srcVariable.isFunctionDefinition) {
|
if(srcVariable && srcVariable.isFunctionDefinition) {
|
||||||
var result = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || "";
|
|
||||||
parser = {
|
parser = {
|
||||||
tree: [{
|
tree: [{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: result
|
text: this.transcludeFunctionResult
|
||||||
}],
|
}],
|
||||||
source: result,
|
source: this.transcludeFunctionResult,
|
||||||
type: "text/vnd.tiddlywiki"
|
type: "text/vnd.tiddlywiki"
|
||||||
};
|
};
|
||||||
if(parseAsInline) {
|
if(parseAsInline) {
|
||||||
parser.tree[0] = {
|
parser.tree[0] = {
|
||||||
type: "text",
|
type: "text",
|
||||||
text: result
|
text: this.transcludeFunctionResult
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
parser.tree[0] = {
|
parser.tree[0] = {
|
||||||
@@ -241,7 +247,7 @@ TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) {
|
|||||||
tag: "p",
|
tag: "p",
|
||||||
children: [{
|
children: [{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: result
|
text: this.transcludeFunctionResult
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,12 +436,19 @@ TranscludeWidget.prototype.parserNeedsRefresh = function() {
|
|||||||
return (this.sourceText === undefined || parserInfo.sourceText !== this.sourceText || parserInfo.parserType !== this.parserType)
|
return (this.sourceText === undefined || parserInfo.sourceText !== this.sourceText || parserInfo.parserType !== this.parserType)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TranscludeWidget.prototype.functionNeedsRefresh = function() {
|
||||||
|
var oldResult = this.transcludeFunctionResult;
|
||||||
|
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()});
|
||||||
|
var newResult = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || "";
|
||||||
|
return oldResult !== newResult;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
||||||
*/
|
*/
|
||||||
TranscludeWidget.prototype.refresh = function(changedTiddlers) {
|
TranscludeWidget.prototype.refresh = function(changedTiddlers) {
|
||||||
var changedAttributes = this.computeAttributes();
|
var changedAttributes = this.computeAttributes();
|
||||||
if(($tw.utils.count(changedAttributes) > 0) || (!this.transcludeVariable && changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) {
|
if(($tw.utils.count(changedAttributes) > 0) || (this.transcludeVariableIsFunction && this.functionNeedsRefresh()) || (!this.transcludeVariable && changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) {
|
||||||
this.refreshSelf();
|
this.refreshSelf();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ caption: {{$:/language/ControlPanel/Basics/Caption}}
|
|||||||
|<$link to="$:/SiteSubtitle"><<lingo Subtitle/Prompt>></$link> |<$edit-text tiddler="$:/SiteSubtitle" default="" tag="input"/> |
|
|<$link to="$:/SiteSubtitle"><<lingo Subtitle/Prompt>></$link> |<$edit-text tiddler="$:/SiteSubtitle" default="" tag="input"/> |
|
||||||
|<$link to="$:/status/UserName"><<lingo Username/Prompt>></$link> |<$edit-text tiddler="$:/status/UserName" default="" tag="input"/> |
|
|<$link to="$:/status/UserName"><<lingo Username/Prompt>></$link> |<$edit-text tiddler="$:/status/UserName" default="" tag="input"/> |
|
||||||
|<$link to="$:/config/AnimationDuration"><<lingo AnimDuration/Prompt>></$link> |<$edit-text tiddler="$:/config/AnimationDuration" default="" tag="input"/> |
|
|<$link to="$:/config/AnimationDuration"><<lingo AnimDuration/Prompt>></$link> |<$edit-text tiddler="$:/config/AnimationDuration" default="" tag="input"/> |
|
||||||
|<$link to="$:/DefaultTiddlers"><<lingo DefaultTiddlers/Prompt>></$link> |<<lingo DefaultTiddlers/TopHint>><br> <$edit class="tc-edit-texteditor" tiddler="$:/DefaultTiddlers"/><br>//<<lingo DefaultTiddlers/BottomHint>>// |
|
|<$link to="$:/DefaultTiddlers"><<lingo DefaultTiddlers/Prompt>></$link> |<<lingo DefaultTiddlers/TopHint>><br> <$edit class="tc-edit-texteditor" tiddler="$:/DefaultTiddlers" autoHeight="yes"/><br>//<<lingo DefaultTiddlers/BottomHint>>// |
|
||||||
|<$link to="$:/language/DefaultNewTiddlerTitle"><<lingo NewTiddler/Title/Prompt>></$link> |<$edit-text tiddler="$:/language/DefaultNewTiddlerTitle" default="" tag="input"/> |
|
|<$link to="$:/language/DefaultNewTiddlerTitle"><<lingo NewTiddler/Title/Prompt>></$link> |<$edit-text tiddler="$:/language/DefaultNewTiddlerTitle" default="" tag="input"/> |
|
||||||
|<$link to="$:/config/NewJournal/Title"><<lingo NewJournal/Title/Prompt>></$link> |<$edit-text tiddler="$:/config/NewJournal/Title" default="" tag="input"/> |
|
|<$link to="$:/config/NewJournal/Title"><<lingo NewJournal/Title/Prompt>></$link> |<$edit-text tiddler="$:/config/NewJournal/Title" default="" tag="input"/> |
|
||||||
|<$link to="$:/config/NewJournal/Text"><<lingo NewJournal/Text/Prompt>></$link> |<$edit tiddler="$:/config/NewJournal/Text" class="tc-edit-texteditor" default=""/> |
|
|<$link to="$:/config/NewJournal/Text"><<lingo NewJournal/Text/Prompt>></$link> |<$edit tiddler="$:/config/NewJournal/Text" class="tc-edit-texteditor" default="" autoHeight="yes"/> |
|
||||||
|<$link to="$:/config/NewTiddler/Tags"><<lingo NewTiddler/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewTiddler/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|
|<$link to="$:/config/NewTiddler/Tags"><<lingo NewTiddler/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewTiddler/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|
||||||
|<$link to="$:/config/NewJournal/Tags"><<lingo NewJournal/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewJournal/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|
|<$link to="$:/config/NewJournal/Tags"><<lingo NewJournal/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewJournal/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|
||||||
|<$link to="$:/config/AutoFocus"><<lingo AutoFocus/Prompt>></$link> |{{$:/snippets/minifocusswitcher}} |
|
|<$link to="$:/config/AutoFocus"><<lingo AutoFocus/Prompt>></$link> |{{$:/snippets/minifocusswitcher}} |
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
title: $:/core/ui/EditTemplate/body/default
|
title: $:/core/ui/EditTemplate/body/default
|
||||||
|
|
||||||
|
\function edit-preview-state()
|
||||||
|
[{$:/config/ShowEditPreview/PerTiddler}!match[yes]then[$:/state/showeditpreview]] :else[<qualify "$:/state/showeditpreview">] +[get[text]] :else[[no]]
|
||||||
|
\end
|
||||||
|
|
||||||
\define config-visibility-title()
|
\define config-visibility-title()
|
||||||
$:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
|
$:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
|
||||||
\end
|
\end
|
||||||
@@ -10,15 +14,16 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
|
|||||||
|
|
||||||
\whitespace trim
|
\whitespace trim
|
||||||
<$let
|
<$let
|
||||||
edit-preview-state={{{ [{$:/config/ShowEditPreview/PerTiddler}!match[yes]then[$:/state/showeditpreview]] :else[<qualify "$:/state/showeditpreview">] }}}
|
|
||||||
importTitle=<<qualify $:/ImportImage>>
|
importTitle=<<qualify $:/ImportImage>>
|
||||||
importState=<<qualify $:/state/ImportImage>> >
|
importState=<<qualify $:/state/ImportImage>> >
|
||||||
<$dropzone importTitle=<<importTitle>> autoOpenOnImport="no" contentTypesFilter={{$:/config/Editor/ImportContentTypesFilter}} class="tc-dropzone-editor" enable={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}} filesOnly="yes" actions=<<importFileActions>> >
|
<$dropzone importTitle=<<importTitle>> autoOpenOnImport="no" contentTypesFilter={{$:/config/Editor/ImportContentTypesFilter}} class="tc-dropzone-editor" enable={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}} filesOnly="yes" actions=<<importFileActions>> >
|
||||||
<$reveal stateTitle=<<edit-preview-state>> type="match" text="yes" tag="div">
|
<div>
|
||||||
<div class="tc-tiddler-preview">
|
<div class={{{ [function[edit-preview-state]match[yes]then[tc-tiddler-preview]else[tc-tiddler-preview-hidden]] [[tc-tiddler-editor]] +[join[ ]] }}}>
|
||||||
|
|
||||||
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
|
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
|
||||||
|
|
||||||
|
<$list filter="[function[edit-preview-state]match[yes]]" variable="ignore">
|
||||||
|
|
||||||
<div class="tc-tiddler-preview-preview" data-tiddler-title={{!!draft.title}} data-tags={{!!tags}}>
|
<div class="tc-tiddler-preview-preview" data-tiddler-title={{!!draft.title}} data-tags={{!!tags}}>
|
||||||
|
|
||||||
<$transclude tiddler={{$:/state/editpreviewtype}} mode="inline">
|
<$transclude tiddler={{$:/state/editpreviewtype}} mode="inline">
|
||||||
@@ -29,13 +34,12 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</$list>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</$reveal>
|
|
||||||
|
|
||||||
<$reveal stateTitle=<<edit-preview-state>> type="nomatch" text="yes" tag="div">
|
</div>
|
||||||
|
|
||||||
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
|
|
||||||
|
|
||||||
</$reveal>
|
|
||||||
</$dropzone>
|
</$dropzone>
|
||||||
|
|
||||||
</$let>
|
</$let>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ $value={{{ [subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
|
|||||||
</td>
|
</td>
|
||||||
<td class="tc-edit-field-remove">
|
<td class="tc-edit-field-remove">
|
||||||
<$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}>
|
<$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}>
|
||||||
<$action-deletefield $field=<<currentField>>/><$set name="currentTiddlerCSSescaped" value={{{ [<currentTiddler>escapecss[]] }}}><$action-sendmessage $message="tm-focus-selector" $param=<<current-tiddler-new-field-selector>>/></$set>
|
<$action-deletefield $field=<<currentField>>/>
|
||||||
{{$:/core/images/delete-button}}
|
{{$:/core/images/delete-button}}
|
||||||
</$button>
|
</$button>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d
|
|||||||
<em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em>
|
<em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em>
|
||||||
<div class="tc-type-selector-dropdown-wrapper">
|
<div class="tc-type-selector-dropdown-wrapper">
|
||||||
<div class="tc-type-selector"><$fieldmangler>
|
<div class="tc-type-selector"><$fieldmangler>
|
||||||
<$macrocall $name="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" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] ~[[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="[<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button>
|
<$macrocall $name="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" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] ~[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<<input-cancel-actions>>/><$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button><$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button>
|
||||||
</$fieldmangler></div>
|
</$fieldmangler></div>
|
||||||
|
|
||||||
<div class="tc-block-dropdown-wrapper">
|
<div class="tc-block-dropdown-wrapper">
|
||||||
|
|||||||
@@ -9,11 +9,17 @@ button-classes: tc-text-editor-toolbar-item-start-group
|
|||||||
shortcuts: ((preview))
|
shortcuts: ((preview))
|
||||||
|
|
||||||
\whitespace trim
|
\whitespace trim
|
||||||
|
<$let
|
||||||
|
edit-preview-state={{{ [{$:/config/ShowEditPreview/PerTiddler}!match[yes]then[$:/state/showeditpreview]] :else[<qualify "$:/state/showeditpreview">] }}}
|
||||||
|
>
|
||||||
<$reveal state=<<edit-preview-state>> type="match" text="yes" tag="span">
|
<$reveal state=<<edit-preview-state>> type="match" text="yes" tag="span">
|
||||||
{{$:/core/images/preview-open}}
|
{{$:/core/images/preview-open}}
|
||||||
<$action-setfield $tiddler=<<edit-preview-state>> $value="no"/>
|
<$action-setfield $tiddler=<<edit-preview-state>> $value="no"/>
|
||||||
|
<$action-sendmessage $message="tm-edit-text-operation" $param="focus-editor"/>
|
||||||
</$reveal>
|
</$reveal>
|
||||||
<$reveal state=<<edit-preview-state>> type="nomatch" text="yes" tag="span">
|
<$reveal state=<<edit-preview-state>> type="nomatch" text="yes" tag="span">
|
||||||
{{$:/core/images/preview-closed}}
|
{{$:/core/images/preview-closed}}
|
||||||
<$action-setfield $tiddler=<<edit-preview-state>> $value="yes"/>
|
<$action-setfield $tiddler=<<edit-preview-state>> $value="yes"/>
|
||||||
|
<$action-sendmessage $message="tm-edit-text-operation" $param="focus-editor"/>
|
||||||
</$reveal>
|
</$reveal>
|
||||||
|
</$let>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ code-body: yes
|
|||||||
|
|
||||||
<$navigator story="$:/StoryList" history="$:/HistoryList" openLinkFromInsideRiver={{$:/config/Navigation/openLinkFromInsideRiver}} openLinkFromOutsideRiver={{$:/config/Navigation/openLinkFromOutsideRiver}} relinkOnRename={{$:/config/RelinkOnRename}}>
|
<$navigator story="$:/StoryList" history="$:/HistoryList" openLinkFromInsideRiver={{$:/config/Navigation/openLinkFromInsideRiver}} openLinkFromOutsideRiver={{$:/config/Navigation/openLinkFromOutsideRiver}} relinkOnRename={{$:/config/RelinkOnRename}}>
|
||||||
|
|
||||||
<$dropzone enable=<<tv-enable-drag-and-drop>>>
|
<$dropzone enable=<<tv-enable-drag-and-drop>> class="tc-dropzone tc-page-container-inner">
|
||||||
|
|
||||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/PageTemplate]!has[draft.of]]" variable="listItem">
|
<$list filter="[all[shadows+tiddlers]tag[$:/tags/PageTemplate]!has[draft.of]]" variable="listItem">
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ tags: $:/tags/Macro
|
|||||||
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item" >
|
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item" >
|
||||||
<li class=<<toc-item-class>>>
|
<li class=<<toc-item-class>>>
|
||||||
<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}>
|
<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}>
|
||||||
<$list filter="[all[current]tagging[]$sort$limit[1]]" variable="ignore" emptyMessage="<$button class='tc-btn-invisible'>{{$:/core/images/blank}}</$button>">
|
<$list filter="[all[current]tagging[]$sort$limit[1]] -[subfilter<__exclude__>]" variable="ignore" emptyMessage="<$button class='tc-btn-invisible'>{{$:/core/images/blank}}</$button>">
|
||||||
<$reveal type="nomatch" stateTitle=<<toc-state>> text="open">
|
<$reveal type="nomatch" stateTitle=<<toc-state>> text="open">
|
||||||
<$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep">
|
<$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep">
|
||||||
<$transclude tiddler=<<toc-closed-icon>> />
|
<$transclude tiddler=<<toc-closed-icon>> />
|
||||||
@@ -145,7 +145,7 @@ tags: $:/tags/Macro
|
|||||||
<$qualify name="toc-state" title={{{ [[$:/state/toc]addsuffix<__path__>addsuffix[-]addsuffix<currentTiddler>] }}}>
|
<$qualify name="toc-state" title={{{ [[$:/state/toc]addsuffix<__path__>addsuffix[-]addsuffix<currentTiddler>] }}}>
|
||||||
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item">
|
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item">
|
||||||
<li class=<<toc-item-class>>>
|
<li class=<<toc-item-class>>>
|
||||||
<$list filter="[all[current]tagging[]$sort$limit[1]]" variable="ignore" emptyMessage="""<$button class="tc-btn-invisible">{{$:/core/images/blank}}</$button><span class="toc-item-muted"><<toc-caption>></span>""">
|
<$list filter="[all[current]tagging[]$sort$limit[1]] -[subfilter<__exclude__>]" variable="ignore" emptyMessage="""<$button class="tc-btn-invisible">{{$:/core/images/blank}}</$button><span class="toc-item-muted"><<toc-caption>></span>""">
|
||||||
<$reveal type="nomatch" stateTitle=<<toc-state>> text="open">
|
<$reveal type="nomatch" stateTitle=<<toc-state>> text="open">
|
||||||
<$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep">
|
<$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep">
|
||||||
<$transclude tiddler=<<toc-closed-icon>> />
|
<$transclude tiddler=<<toc-closed-icon>> />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
editions/de-AT-server/tiddlers/system/favicon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
@@ -1,2 +1,2 @@
|
|||||||
title: $:/favicon.ico
|
title: $:/favicon.ico
|
||||||
type: image/x-icon
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
editions/dev/tiddlers/images/favicon.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
@@ -1,2 +1,2 @@
|
|||||||
title: $:/favicon.ico
|
title: $:/favicon.ico
|
||||||
type: image/x-icon
|
type: image/png
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
created: 20141122200310516
|
created: 20141122200310516
|
||||||
modified: 20201213161842776
|
modified: 20230923031318421
|
||||||
|
tags: Mechanisms
|
||||||
title: HookMechanism
|
title: HookMechanism
|
||||||
type: text/vnd.tiddlywiki
|
type: text/vnd.tiddlywiki
|
||||||
|
|
||||||
The hook mechanism provides a way for plugins to intercept and modify default functionality. Hooks are added as follows:
|
The hook mechanism provides a way for plugins to intercept and modify default functionality.
|
||||||
|
|
||||||
|
!! Add a hook
|
||||||
|
|
||||||
|
Hooks are added as follows:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
/*
|
/*
|
||||||
@@ -13,6 +18,8 @@ handler: function to be called when hook is invoked
|
|||||||
$tw.hooks.addHook(name,handler);
|
$tw.hooks.addHook(name,handler);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! Params and return
|
||||||
|
|
||||||
The handler function will be called with parameters that depend on the specific hook in question, but they always follow the pattern `handler(value,params...)`
|
The handler function will be called with parameters that depend on the specific hook in question, but they always follow the pattern `handler(value,params...)`
|
||||||
|
|
||||||
* ''value'': an optional value that is to be transformed by the hook function
|
* ''value'': an optional value that is to be transformed by the hook function
|
||||||
@@ -20,11 +27,29 @@ The handler function will be called with parameters that depend on the specific
|
|||||||
|
|
||||||
If required by the hook in question, the handler function must return the modified ''value''.
|
If required by the hook in question, the handler function must return the modified ''value''.
|
||||||
|
|
||||||
|
!!! Multiple handlers
|
||||||
|
|
||||||
Multiple handlers can be assigned to the same name using repeated calls. When a hook is invoked by name all registered functions will be called sequentially in their order of addition.
|
Multiple handlers can be assigned to the same name using repeated calls. When a hook is invoked by name all registered functions will be called sequentially in their order of addition.
|
||||||
|
|
||||||
Note that the ''value'' passed to the subsequent hook function will be the return value of the previous hook function.
|
Note that the ''value'' passed to the subsequent hook function will be the return value of the previous hook function.
|
||||||
|
|
||||||
Though not essential care should be taken to ensure that hooks are added before they are invoked. For example: [[Hook: th-opening-default-tiddlers-list]] should ideally be added before the story startup module is invoked otherwise any hook specified additions to the default tiddlers will not be seen on the initial loading of the page, though will be visible if the user clicks the home button.
|
Be careful not to `addHook` in widget's `render` method, which will be call several times. You could `addHook` in methods that only called once, e.g. the constructor of widget class. Otherwise you should `removeHook` then add it again.
|
||||||
|
|
||||||
|
!!! Timing of registration
|
||||||
|
|
||||||
|
Though not essential care should be taken to ensure that hooks are added before they are invoked.
|
||||||
|
|
||||||
|
For example: [[Hook: th-opening-default-tiddlers-list]] should ideally be added before the story startup module is invoked. Otherwise any hook specified additions to the default tiddlers will not be seen on the initial loading of the page, though will be visible if the user clicks the home button.
|
||||||
|
|
||||||
|
!! Remove a hook
|
||||||
|
|
||||||
|
You should clean up the callback when your widget is going to unmount.
|
||||||
|
|
||||||
|
```js
|
||||||
|
$tw.hooks.removeHook(handler)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `handler` should be the same function instance you used in `addHook` (check by `===`). You can save it to `this.xxxHookHandler` on your widget, and call `removeHook` in [[destroy method|Widget `destroy` method examples]].
|
||||||
|
|
||||||
!! Example
|
!! Example
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"static": [
|
"static": [
|
||||||
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
||||||
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
||||||
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain",
|
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain","$:/core/templates/static.tiddler.html",
|
||||||
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
editions/es-ES-server/tiddlers/system/favicon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
@@ -1,2 +1,2 @@
|
|||||||
title: $:/favicon.ico
|
title: $:/favicon.ico
|
||||||
type: image/x-icon
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
editions/es-ES/tiddlers/images/favicon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
@@ -1,2 +1,2 @@
|
|||||||
title: $:/favicon.ico
|
title: $:/favicon.ico
|
||||||
type: image/x-icon
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
editions/es-ES/tiddlers/images/green_favicon.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
@@ -1,2 +1,2 @@
|
|||||||
title: $:/green_favicon.ico
|
title: $:/green_favicon.ico
|
||||||
type: image/x-icon
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,2 +0,0 @@
|
|||||||
title: $:/favicon.ico
|
|
||||||
type: image/x-icon
|
|
||||||
BIN
editions/fr-FR-server/tiddlers/system/favicon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
2
editions/fr-FR-server/tiddlers/system/favicon.png.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
title: $:/favicon.ico
|
||||||
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,2 +0,0 @@
|
|||||||
title: $:/favicon.ico
|
|
||||||
type: image/x-icon
|
|
||||||
BIN
editions/fr-FR/tiddlers/images/favicon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
2
editions/fr-FR/tiddlers/images/favicon.png.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
title: $:/favicon.ico
|
||||||
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
editions/fr-FR/tiddlers/images/green_favicon.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
@@ -1,2 +1,2 @@
|
|||||||
title: $:/green_favicon.ico
|
title: $:/green_favicon.ico
|
||||||
type: image/x-icon
|
type: image/png
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"static": [
|
"static": [
|
||||||
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
||||||
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
||||||
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain",
|
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain","$:/core/templates/static.tiddler.html",
|
||||||
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,2 +0,0 @@
|
|||||||
title: $:/favicon.ico
|
|
||||||
type: image/x-icon
|
|
||||||
BIN
editions/ja-JP/tiddlers/images/favicon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
2
editions/ja-JP/tiddlers/images/favicon.png.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
title: $:/favicon.ico
|
||||||
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
editions/ja-JP/tiddlers/images/green_favicon.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
@@ -1,2 +1,2 @@
|
|||||||
title: $:/green_favicon.ico
|
title: $:/green_favicon.ico
|
||||||
type: image/x-icon
|
type: image/png
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"static": [
|
"static": [
|
||||||
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
||||||
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
||||||
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain",
|
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain","$:/core/templates/static.tiddler.html",
|
||||||
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,2 +0,0 @@
|
|||||||
title: $:/favicon.ico
|
|
||||||
type: image/x-icon
|
|
||||||
BIN
editions/ko-KR-server/tiddlers/system/favicon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
2
editions/ko-KR-server/tiddlers/system/favicon.png.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
title: $:/favicon.ico
|
||||||
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,2 +0,0 @@
|
|||||||
title: $:/favicon.ico
|
|
||||||
type: image/x-icon
|
|
||||||
BIN
editions/ko-KR/tiddlers/images/favicon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
2
editions/ko-KR/tiddlers/images/favicon.png.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
title: $:/favicon.ico
|
||||||
|
type: image/png
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
editions/ko-KR/tiddlers/images/green_favicon.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
@@ -1,2 +1,2 @@
|
|||||||
title: $:/green_favicon.ico
|
title: $:/green_favicon.ico
|
||||||
type: image/x-icon
|
type: image/png
|
||||||
@@ -1,37 +1,105 @@
|
|||||||
caption: 5.3.2
|
caption: 5.3.2
|
||||||
created: 20230820114855583
|
created: 20231016122502955
|
||||||
modified: 20230820114855583
|
modified: 20231016122502955
|
||||||
tags: ReleaseNotes
|
tags: ReleaseNotes
|
||||||
title: Release 5.3.2
|
title: Release 5.3.2
|
||||||
type: text/vnd.tiddlywiki
|
type: text/vnd.tiddlywiki
|
||||||
|
description: Under development
|
||||||
|
|
||||||
//[[See GitHub for detailed change history of this release|https://github.com/Jermolene/TiddlyWiki5/compare/v5.3.1...master]]//
|
//[[See GitHub for detailed change history of this release|https://github.com/Jermolene/TiddlyWiki5/compare/v5.3.1...master]]//
|
||||||
|
|
||||||
|
! Major Improvements
|
||||||
|
|
||||||
|
!! Conditional Shortcut Syntax
|
||||||
|
|
||||||
|
<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7710">> a new [[shortcut syntax|Conditional Shortcut Syntax]] for concisely expressing if-then-else logic. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
<% if [<animal>match[Elephant]] %>
|
||||||
|
It is an elephant
|
||||||
|
<% elseif [<animal>match[Giraffe]] %>
|
||||||
|
It is a giraffe
|
||||||
|
<% else %>
|
||||||
|
It is completely unknown
|
||||||
|
<% endif %>
|
||||||
|
```
|
||||||
|
|
||||||
|
!! Explicit Templates for the ListWidget
|
||||||
|
|
||||||
|
<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7784">> support for `<$list-template>` and `<$list-empty>` as immediate children of the <<.wid "ListWidget">> widget to specify the list item template and/or the empty template. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
<$list filter=<<filter>>>
|
||||||
|
<$list-template>
|
||||||
|
<$text text=<<currentTiddler>>/>
|
||||||
|
</$list-template>
|
||||||
|
<$list-empty>
|
||||||
|
None!
|
||||||
|
</$list-empty>
|
||||||
|
</$list>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the <<.attr "emptyMessage">> and <<.attr "template">> attributes take precedence if they are present.
|
||||||
|
|
||||||
|
!! jsonset operator
|
||||||
|
|
||||||
|
<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7742">> [[jsonset Operator]] for setting values within JSON objects
|
||||||
|
|
||||||
|
!! QR Code Reader
|
||||||
|
|
||||||
|
<<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/7746">> QR Code plugin to be able to read QR codes and a number of other bar code formats
|
||||||
|
|
||||||
! Translation improvement
|
! Translation improvement
|
||||||
|
|
||||||
Improvements to the following translations:
|
Improvements to the following translations:
|
||||||
|
|
||||||
*
|
* Chinese
|
||||||
|
* Polish
|
||||||
|
* Spanish
|
||||||
|
|
||||||
|
! Plugin Improvements
|
||||||
|
|
||||||
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/1be8f0a9336952d4745d2bd4f2327e353580a272">> Comments Plugin to use predefined palette colours
|
||||||
|
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7785">> Evernote Importer Plugin to support images and other attachments
|
||||||
|
|
||||||
! Widget Improvements
|
! Widget Improvements
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
|
! Usability Improvements
|
||||||
|
|
||||||
|
* <<.link-badge-updated "https://github.com/Jermolene/TiddlyWiki5/pull/7747">> editor preview button to automatically focus the editor
|
||||||
|
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7764">> file type names in the export menu
|
||||||
|
|
||||||
! Hackability Improvements
|
! Hackability Improvements
|
||||||
|
|
||||||
*
|
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7737">> an automatic build of the external core TiddlyWiki at https://tiddlywiki.com/empty-external-core.html
|
||||||
|
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7690">> the default page layout to better support CSS grid and flexbox layouts
|
||||||
|
|
||||||
! Bug Fixes
|
! Bug Fixes
|
||||||
|
|
||||||
*
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/issues/7665">> `{{}}` generating a recursion error
|
||||||
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7758">> ordering of Vanilla stylesheets
|
||||||
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/fa9bfa07a095548eb2f8339b0b1b816d2e6794ef">> missing closing tag in tag-pill-inner macro
|
||||||
|
* <<.link-badge-removed "https://github.com/Jermolene/TiddlyWiki5/issues/7732">> invalid "type" attribute from textarea elements generated by the EditTextWidget
|
||||||
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7749">> editor "type" dropdown state tiddlers
|
||||||
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7712">> handling of "counter-last" variable when appending items with the ListWidget
|
||||||
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/6088">> upgrade download link in Firefox
|
||||||
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7698">> refreshing of transcluded functions
|
||||||
|
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7789">> resizing of height of textareas in control panel
|
||||||
|
|
||||||
! Node.js Improvements
|
! Node.js Improvements
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
|
! Performance Improvements
|
||||||
|
|
||||||
|
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7702">> performance of predefined patterns with [[all Operator]]
|
||||||
|
* <<.link-badge-updated "https://github.com/Jermolene/TiddlyWiki5/issues/7671">> favicon format to PNG
|
||||||
|
|
||||||
! Developer Improvements
|
! Developer Improvements
|
||||||
|
|
||||||
*
|
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7751">> global hook handling to support removing hooks
|
||||||
|
|
||||||
! Acknowledgements
|
! Acknowledgements
|
||||||
|
|
||||||
@@ -39,22 +107,16 @@ Improvements to the following translations:
|
|||||||
|
|
||||||
<<.contributors """
|
<<.contributors """
|
||||||
AnthonyMuscio
|
AnthonyMuscio
|
||||||
btheado
|
BramChen
|
||||||
catter-fly
|
BuckarooBanzay
|
||||||
cmo-pomerium
|
BurningTreeC
|
||||||
CrossEye
|
EvidentlyCube
|
||||||
flibbles
|
joebordes
|
||||||
hffqyd
|
kookma
|
||||||
lilscribby
|
|
||||||
linonetwo
|
linonetwo
|
||||||
Marxsal
|
|
||||||
mateuszwilczek
|
mateuszwilczek
|
||||||
pille1842
|
|
||||||
pmario
|
pmario
|
||||||
rmunn
|
rmunn
|
||||||
saqimtiaz
|
simonbaird
|
||||||
stevesunypoly
|
T1mL3arn
|
||||||
TiddlyTweeter
|
|
||||||
twMat
|
|
||||||
yaisog
|
|
||||||
""">>
|
""">>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 118 KiB |
@@ -1,2 +0,0 @@
|
|||||||
title: $:/favicon.ico
|
|
||||||
type: image/x-icon
|
|
||||||
BIN
editions/prerelease/tiddlers/system/favicon.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
2
editions/prerelease/tiddlers/system/favicon.png.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
title: $:/favicon.ico
|
||||||
|
type: image/png
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"static": [
|
"static": [
|
||||||
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
||||||
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
||||||
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain",
|
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain","$:/core/templates/static.tiddler.html",
|
||||||
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"],
|
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"],
|
||||||
"tiddlywikicore": [
|
"tiddlywikicore": [
|
||||||
"--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffix<version>addsuffix[.js]]","text/plain"]
|
"--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffix<version>addsuffix[.js]]","text/plain"]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"static": [
|
"static": [
|
||||||
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
||||||
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
||||||
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain",
|
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain","$:/core/templates/static.tiddler.html",
|
||||||
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
25
editions/test/playwright.spec.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
const {resolve} = require('path');
|
||||||
|
|
||||||
|
const indexPath = resolve(__dirname, 'output', 'test.html');
|
||||||
|
const crossPlatformIndexPath = indexPath.replace(/^\/+/, '');
|
||||||
|
|
||||||
|
|
||||||
|
test('get started link', async ({ page }) => {
|
||||||
|
// The tests can take a while to run
|
||||||
|
const timeout = 1000 * 30;
|
||||||
|
test.setTimeout(timeout);
|
||||||
|
|
||||||
|
// Load the generated test TW html
|
||||||
|
await page.goto(`file:///${crossPlatformIndexPath}`);
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
await expect(page.locator('.tc-site-title'), "Expected correct page title to verify the test page was loaded").toHaveText('TiddlyWiki5');
|
||||||
|
|
||||||
|
// Wait for jasmine results bar to appear
|
||||||
|
await expect(page.locator('.jasmine-overall-result'), "Expected jasmine test results bar to be present").toBeVisible({timeout});
|
||||||
|
|
||||||
|
// Assert the tests have passed
|
||||||
|
await expect(page.locator('.jasmine-overall-result.jasmine-failed'), "Expected jasmine tests to not have failed").not.toBeVisible();
|
||||||
|
await expect(page.locator('.jasmine-overall-result.jasmine-passed'), "Expected jasmine tests to have passed").toBeVisible();
|
||||||
|
});
|
||||||
26
editions/test/tiddlers/tests/data/conditionals/Basic.tid
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
title: Conditionals/Basic
|
||||||
|
description: Basic conditional shortcut syntax
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Text
|
||||||
|
|
||||||
|
This is a <% if [<something>match[one]] %>Elephant<% endif %>, I think.
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
<$let something="one">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="two">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a Elephant, I think.
|
||||||
|
</p><p>
|
||||||
|
This is a , I think.
|
||||||
|
</p>
|
||||||
37
editions/test/tiddlers/tests/data/conditionals/BlockMode.tid
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
title: Conditionals/BlockMode
|
||||||
|
description: Basic conditional shortcut syntax in block mode
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\procedure test(animal)
|
||||||
|
<% if [<animal>match[Elephant]] %>
|
||||||
|
|
||||||
|
! It is an elephant
|
||||||
|
|
||||||
|
<% else %>
|
||||||
|
|
||||||
|
<% if [<animal>match[Giraffe]] %>
|
||||||
|
|
||||||
|
! It is a giraffe
|
||||||
|
|
||||||
|
<% else %>
|
||||||
|
|
||||||
|
! It is completely unknown
|
||||||
|
|
||||||
|
<% endif %>
|
||||||
|
|
||||||
|
<% endif %>
|
||||||
|
|
||||||
|
\end
|
||||||
|
|
||||||
|
<<test "Giraffe">>
|
||||||
|
|
||||||
|
<<test "Elephant">>
|
||||||
|
|
||||||
|
<<test "Antelope">>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<h1 class="">It is a giraffe</h1><h1 class="">It is an elephant</h1><h1 class="">It is completely unknown</h1>
|
||||||
26
editions/test/tiddlers/tests/data/conditionals/Else.tid
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
title: Conditionals/Else
|
||||||
|
description: Else conditional shortcut syntax
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Text
|
||||||
|
|
||||||
|
This is a <% if [<something>match[one]] %>Elephant<% else %>Crocodile<% endif %>, I think.
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
<$let something="one">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="two">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a Elephant, I think.
|
||||||
|
</p><p>
|
||||||
|
This is a Crocodile, I think.
|
||||||
|
</p>
|
||||||
32
editions/test/tiddlers/tests/data/conditionals/Elseif.tid
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
title: Conditionals/Elseif
|
||||||
|
description: Elseif conditional shortcut syntax
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Text
|
||||||
|
|
||||||
|
This is a <% if [<something>match[one]] %>Elephant<% elseif [<something>match[two]] %>Antelope<% else %>Crocodile<% endif %>, I think.
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
<$let something="one">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="two">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="three">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a Elephant, I think.
|
||||||
|
</p><p>
|
||||||
|
This is a Antelope, I think.
|
||||||
|
</p><p>
|
||||||
|
This is a Crocodile, I think.
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
title: Conditionals/MissingEndif
|
||||||
|
description: Conditional shortcut syntax with missing endif
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Text
|
||||||
|
|
||||||
|
This is a <% if [<something>match[one]] %>Elephant
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
<$let something="one">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="two">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a Elephant
|
||||||
|
</p><p>
|
||||||
|
This is a
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
title: Conditionals/MultipleResults
|
||||||
|
description: Check that multiple results from the filter are ignored
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
This is a <% if 1 2 3 4 5 6 %>Elephant<% endif %>, I think.
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>This is a Elephant, I think.</p>
|
||||||
38
editions/test/tiddlers/tests/data/conditionals/Nested.tid
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
title: Conditionals/Nested
|
||||||
|
description: Nested conditional shortcut syntax
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\procedure test(animal)
|
||||||
|
<% if [<animal>match[Elephant]] %>
|
||||||
|
It is an elephant
|
||||||
|
<% else %>
|
||||||
|
<% if [<animal>match[Giraffe]] %>
|
||||||
|
It is a giraffe
|
||||||
|
<% else %>
|
||||||
|
It is completely unknown
|
||||||
|
<% endif %>
|
||||||
|
<% endif %>
|
||||||
|
\end
|
||||||
|
|
||||||
|
<<test "Giraffe">>
|
||||||
|
|
||||||
|
<<test "Elephant">>
|
||||||
|
|
||||||
|
<<test "Antelope">>
|
||||||
|
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
It is a giraffe
|
||||||
|
|
||||||
|
|
||||||
|
It is an elephant
|
||||||
|
|
||||||
|
|
||||||
|
It is completely unknown
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
title: Conditionals/NestedElseif
|
||||||
|
description: Nested elseif conditional shortcut syntax
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Text
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
This is a 
|
||||||
|
<% if [<something>match[one]] %>
|
||||||
|
<% if [<another>match[one]] %>
|
||||||
|
Indian
|
||||||
|
<% elseif [<another>match[two]] %>
|
||||||
|
African
|
||||||
|
<% else %>
|
||||||
|
Unknown
|
||||||
|
<% endif %>
|
||||||
|
 Elephant
|
||||||
|
<% elseif [<something>match[two]] %>
|
||||||
|
Antelope
|
||||||
|
<% else %>
|
||||||
|
Crocodile
|
||||||
|
<% endif %>
|
||||||
|
, I think.
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
<$let something="one" another="one">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="one" another="two">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="one" another="three">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="two">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
|
||||||
|
<$let something="three">
|
||||||
|
{{Text}}
|
||||||
|
</$let>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a Indian Elephant, I think.
|
||||||
|
</p><p>
|
||||||
|
This is a African Elephant, I think.
|
||||||
|
</p><p>
|
||||||
|
This is a Unknown Elephant, I think.
|
||||||
|
</p><p>
|
||||||
|
This is a Antelope, I think.
|
||||||
|
</p><p>
|
||||||
|
This is a Crocodile, I think.
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
title: ListWidget/WithExplicitTemplates
|
||||||
|
description: List widget with explicit templates
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
\procedure test(filter)
|
||||||
|
<$list filter=<<filter>>>
|
||||||
|
<$list-template>
|
||||||
|
<$text text=<<currentTiddler>>/>
|
||||||
|
</$list-template>
|
||||||
|
<$list-empty>
|
||||||
|
None!
|
||||||
|
</$list-empty>
|
||||||
|
</$list>
|
||||||
|
\end
|
||||||
|
|
||||||
|
<<test "1 2 3">>
|
||||||
|
|
||||||
|
<<test "">>
|
||||||
|
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>123</p><p>None!</p>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
title: ListWidget/WithExplicitTemplatesInBlockMode
|
||||||
|
description: List widget with explicit templates in block mode
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
\procedure test(filter)
|
||||||
|
<$list filter=<<filter>>>
|
||||||
|
|
||||||
|
<$list-template>
|
||||||
|
<$text text=<<currentTiddler>>/>
|
||||||
|
</$list-template>
|
||||||
|
|
||||||
|
<$list-empty>
|
||||||
|
None!
|
||||||
|
</$list-empty>
|
||||||
|
|
||||||
|
</$list>
|
||||||
|
\end
|
||||||
|
|
||||||
|
<<test "1 2 3">>
|
||||||
|
|
||||||
|
<<test "">>
|
||||||
|
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
123None!
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
title: ListWidget/WithExplicitTemplatesOverriddenByAttributes
|
||||||
|
description: List widget with explicit templates
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
\procedure test(filter)
|
||||||
|
<$list filter=<<filter>> emptyMessage="Zero" template="Template">
|
||||||
|
<$list-template>
|
||||||
|
<$text text=<<currentTiddler>>/>
|
||||||
|
</$list-template>
|
||||||
|
<$list-empty>
|
||||||
|
None!
|
||||||
|
</$list-empty>
|
||||||
|
</$list>
|
||||||
|
\end
|
||||||
|
|
||||||
|
<<test "1 2 3">>
|
||||||
|
|
||||||
|
<<test "">>
|
||||||
|
|
||||||
|
+
|
||||||
|
title: Template
|
||||||
|
|
||||||
|
<$text text=<<currentTiddler>>/><$text text=<<currentTiddler>>/>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>112233</p><p>Zero</p>
|
||||||
25
editions/test/tiddlers/tests/data/list-widget/WithLimit.tid
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
title: ListWidget/WithLimit
|
||||||
|
description: List widget with limit
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
Zero: <$list filter="1 2 3 4" limit="0" template="Template"/>
|
||||||
|
|
||||||
|
One: <$list filter="1 2 3 4" limit="1" template="Template"/>
|
||||||
|
|
||||||
|
Two: <$list filter="1 2 3 4" limit="2" template="Template"/>
|
||||||
|
|
||||||
|
Minus Two: <$list filter="1 2 3 4" limit="-2" template="Template"/>
|
||||||
|
|
||||||
|
+
|
||||||
|
title: Template
|
||||||
|
|
||||||
|
<$text text=<<currentTiddler>>/>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>Zero: </p><p>One: 1</p><p>Two: 12</p><p>Minus Two: 34
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
title: ListWidget/WithMissingTemplate
|
||||||
|
description: List widget with explicit templates
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
+
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
\procedure test(filter)
|
||||||
|
<$list filter=<<filter>>>
|
||||||
|
<$list-empty>
|
||||||
|
None!
|
||||||
|
</$list-empty>
|
||||||
|
</$list>
|
||||||
|
\end
|
||||||
|
|
||||||
|
<<test "1 2 3">>
|
||||||
|
|
||||||
|
<<test "">>
|
||||||
|
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p><span><a class="tc-tiddlylink tc-tiddlylink-missing" href="#1">1</a></span><span><a class="tc-tiddlylink tc-tiddlylink-missing" href="#2">2</a></span><span><a class="tc-tiddlylink tc-tiddlylink-missing" href="#3">3</a></span></p><p>None!</p>
|
||||||