1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-04-25 16:21:29 +00:00

Compare commits

..

14 Commits

Author SHA1 Message Date
Jeremy Ruston
faa8ddcf53 Merge branch 'master' into conditional-shortcut-syntax 2023-10-14 09:31:31 +01:00
Jeremy Ruston
c689b8e812 Merge branch 'master' into conditional-shortcut-syntax 2023-10-14 09:21:20 +01:00
Jeremy Ruston
14056143de Add from-version tag to docs 2023-09-14 19:13:10 +01:00
Jeremy Ruston
c392020152 Update docs 2023-09-14 17:54:59 +01:00
Jeremy Ruston
4a2a04f4c7 Allow block mode content within an if/then/else clause 2023-09-14 10:05:20 +01:00
Jeremy Ruston
775860110c List widget should search recursively for list-template and list-empty 2023-09-13 20:09:59 +01:00
Jeremy Ruston
a1acef7324 Don't use the widget body as the template if a list-empty widget is present
See discussion here - https://github.com/Jermolene/TiddlyWiki5/pull/7710#issuecomment-1717193296
2023-09-13 19:53:43 +01:00
Jeremy Ruston
3f16e52b82 Merge branch 'master' into conditional-shortcut-syntax 2023-09-11 18:57:17 +01:00
Jeremy Ruston
2411ebc1ca Change from {%if%} to <%if%>
See discussion here - https://talk.tiddlywiki.org/t/proposed-if-widget/7882/64
2023-09-06 15:45:51 +01:00
Jeremy Ruston
f0b4f3b2ee WIP 2023-09-06 12:32:33 +01:00
Jeremy Ruston
9d25b31974 Another test 2023-09-05 21:58:24 +01:00
Jeremy Ruston
981f905830 Add support for elseif blocks 2023-09-03 22:39:18 +01:00
Jeremy Ruston
bd1bad68d8 Update docs 2023-09-01 14:41:39 +01:00
Jeremy Ruston
c1a9215677 Initial Commit 2023-09-01 14:33:56 +01:00
116 changed files with 334 additions and 2287 deletions

View File

@@ -5,7 +5,7 @@ on:
- master
- tiddlywiki-com
env:
NODE_VERSION: "18"
NODE_VERSION: "12"
jobs:
test:
runs-on: ubuntu-latest
@@ -14,13 +14,7 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: "${{ env.NODE_VERSION }}"
- run: "./bin/ci-test.sh"
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
- run: "./bin/test.sh"
build-prerelease:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
@@ -60,7 +54,6 @@ jobs:
TW5_BUILD_TIDDLYWIKI: "./node_modules/tiddlywiki/tiddlywiki.js"
TW5_BUILD_MAIN_EDITION: "./editions/tw5.com"
TW5_BUILD_OUTPUT: "./output"
TW5_BUILD_ARCHIVE: "./output"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1

4
.gitignore vendored
View File

@@ -5,6 +5,4 @@
tmp/
output/
node_modules/
/test-results/
/playwright-report/
/playwright/.cache/

View File

@@ -84,27 +84,10 @@ echo -e -n "title: $:/build\ncommit: $TW5_BUILD_COMMIT\n\n$TW5_BUILD_DETAILS\n"
######################################################
#
# Core distributions
# Core distribution
#
######################################################
# Conditionally build archive if $TW5_BUILD_ARCHIVE variable is set, otherwise do nothing
#
# /archive/Empty-TiddlyWiki-<version>.html Empty archived version
# /archive/TiddlyWiki-<version>.html Full archived version
if [ -n "$TW5_BUILD_ARCHIVE" ]; then
node $TW5_BUILD_TIDDLYWIKI \
$TW5_BUILD_MAIN_EDITION \
--verbose \
--version \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_ARCHIVE \
--build archive \
|| exit 1
fi
# /index.html Main site
# /favicon.ico Favicon for main site
# /static.html Static rendering of default tiddlers
@@ -112,7 +95,6 @@ fi
# /static/* Static single tiddlers
# /static/static.css Static stylesheet
# /static/favicon.ico Favicon for static pages
node $TW5_BUILD_TIDDLYWIKI \
$TW5_BUILD_MAIN_EDITION \
--verbose \

View File

@@ -1,16 +0,0 @@
#!/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

View File

@@ -18,20 +18,16 @@ Export our filter functions
exports.decodebase64 = function(source,operator,options) {
var results = [];
var binary = operator.suffixes && operator.suffixes.indexOf("binary") !== -1;
var urlsafe = operator.suffixes && operator.suffixes.indexOf("urlsafe") !== -1;
source(function(tiddler,title) {
results.push($tw.utils.base64Decode(title,binary,urlsafe));
results.push($tw.utils.base64Decode(title));
});
return results;
};
exports.encodebase64 = function(source,operator,options) {
var results = [];
var binary = operator.suffixes && operator.suffixes.indexOf("binary") !== -1;
var urlsafe = operator.suffixes && operator.suffixes.indexOf("urlsafe") !== -1;
source(function(tiddler,title) {
results.push($tw.utils.base64Encode(title,binary,urlsafe));
results.push($tw.utils.base64Encode(title));
});
return results;
};

View File

@@ -68,54 +68,6 @@ exports["jsontype"] = function(source,operator,options) {
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
*/
@@ -213,18 +165,6 @@ function getDataItemType(data,indexes) {
}
}
function getItemAtIndex(item,index) {
if($tw.utils.hop(item,index)) {
return item[index];
} else if($tw.utils.isArray(item)) {
index = $tw.utils.parseInt(index);
if(index < 0) { index = index + item.length };
return item[index]; // Will be undefined if index was out-of-bounds
} else {
return undefined;
}
}
/*
Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid
*/
@@ -237,7 +177,7 @@ function getDataItem(data,indexes) {
for(var i=0; i<indexes.length; i++) {
if(item !== undefined) {
if(item !== null && ["number","string","boolean"].indexOf(typeof item) === -1) {
item = getItemAtIndex(item,indexes[i]);
item = item[indexes[i]];
} else {
item = undefined;
}
@@ -246,36 +186,5 @@ function getDataItem(data,indexes) {
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++) {
current = getItemAtIndex(current,indexes[i]);
if(current === undefined) {
// 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];
if($tw.utils.isArray(current)) {
lastIndex = $tw.utils.parseInt(lastIndex);
if(lastIndex < 0) { lastIndex = lastIndex + current.length };
}
current[lastIndex] = value;
return data;
}
})();

View File

@@ -58,7 +58,6 @@ Last entry/entries in list
exports.last = function(source,operator,options) {
var count = $tw.utils.getInt(operator.operand,1),
results = [];
if(count === 0) return results;
source(function(tiddler,title) {
results.push(title);
});

View File

@@ -14,12 +14,10 @@ The plain text parser processes blocks of source text into a degenerate parse tr
var TextParser = function(type,text,options) {
this.tree = [{
type: "genesis",
type: "codeblock",
attributes: {
$type: {name: "$type", type: "string", value: "$codeblock"},
code: {name: "code", type: "string", value: text},
language: {name: "language", type: "string", value: type},
$remappable: {name: "$remappable", type:"string", value: "no"}
code: {type: "string", value: text},
language: {type: "string", value: type}
}
}];
this.source = text;
@@ -34,3 +32,4 @@ exports["text/css"] = TextParser;
exports["application/x-tiddler-dictionary"] = TextParser;
})();

View File

@@ -194,7 +194,6 @@ Parse any pragmas at the beginning of a block of parse text
WikiParser.prototype.parsePragmas = function() {
var currentTreeBranch = this.tree;
while(true) {
var savedPos = this.pos;
// Skip whitespace
this.skipWhitespace();
// Check for the end of the text
@@ -205,7 +204,6 @@ WikiParser.prototype.parsePragmas = function() {
var nextMatch = this.findNextMatch(this.pragmaRules,this.pos);
// If not, just exit
if(!nextMatch || nextMatch.matchIndex !== this.pos) {
this.pos = savedPos;
break;
}
// Process the pragma rule

View File

@@ -31,7 +31,7 @@ GitHubSaver.prototype.save = function(text,method,callback) {
headers = {
"Accept": "application/vnd.github.v3+json",
"Content-Type": "application/json;charset=UTF-8",
"Authorization": "Basic " + $tw.utils.base64Encode(username + ":" + password),
"Authorization": "Basic " + window.btoa(username + ":" + password),
"If-None-Match": ""
};
// Bail if we don't have everything we need

View File

@@ -40,7 +40,7 @@ exports.startup = function() {
variables = $tw.utils.extend({},paramObject,{currentTiddler: title, "tv-window-id": windowID});
// Open the window
var srcWindow,
srcDocument;
srcDocument;
// In case that popup blockers deny opening a new window
try {
srcWindow = window.open("","external-" + windowID,"scrollbars,width=" + width + ",height=" + height + (top ? ",top=" + top : "" ) + (left ? ",left=" + left : "" )),
@@ -52,7 +52,6 @@ exports.startup = function() {
$tw.windows[windowID] = srcWindow;
// Check for reopening the same window
if(srcWindow.haveInitialisedWindow) {
srcWindow.focus();
return;
}
// Initialise the document

View File

@@ -313,7 +313,7 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) {
variables["dom-" + attribute.name] = attribute.value.toString();
});
if("offsetLeft" in selectedNode) {
if(selectedNode.offsetLeft) {
// Add variables with a (relative and absolute) popup coordinate string for the selected node
var nodeRect = {
left: selectedNode.offsetLeft,
@@ -338,12 +338,12 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) {
}
}
if(domNode && ("offsetWidth" in domNode)) {
if(domNode && domNode.offsetWidth) {
variables["tv-widgetnode-width"] = domNode.offsetWidth.toString();
variables["tv-widgetnode-height"] = domNode.offsetHeight.toString();
}
if(event && ("clientX" in event) && ("clientY" in event)) {
if(event && event.clientX && event.clientY) {
if(selectedNode) {
// Add variables for event X and Y position relative to selected node
selectedNodeRect = selectedNode.getBoundingClientRect();

View File

@@ -187,7 +187,7 @@ HttpClientRequest.prototype.send = function(callback) {
for (var i=0; i<len; i++) {
binary += String.fromCharCode(bytes[i]);
}
resultVariables.data = $tw.utils.base64Encode(binary,true);
resultVariables.data = window.btoa(binary);
}
self.wiki.addTiddler(new $tw.Tiddler(self.wiki.getTiddler(requestTrackerTitle),{
status: completionCode,

View File

@@ -104,11 +104,7 @@ TW_Element.prototype.setAttribute = function(name,value) {
if(this.isRaw) {
throw "Cannot setAttribute on a raw TW_Element";
}
if(name === "style") {
this.style = value;
} else {
this.attributes[name] = value + "";
}
this.attributes[name] = value + "";
};
TW_Element.prototype.setAttributeNS = function(namespace,name,value) {

View File

@@ -819,41 +819,18 @@ exports.hashString = function(str) {
},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
*/
exports.base64Decode = function(string64,binary,urlsafe) {
var encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64;
if(binary) return exports.atob(encoded)
else return base64utf8.base64.decode.call(base64utf8,encoded);
exports.base64Decode = function(string64) {
return base64utf8.base64.decode.call(base64utf8,string64);
};
/*
Encode a string to base64
*/
exports.base64Encode = function(string64,binary,urlsafe) {
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;
exports.base64Encode = function(string64) {
return base64utf8.base64.encode.call(base64utf8,string64);
};
/*

View File

@@ -70,11 +70,6 @@ BrowseWidget.prototype.render = function(parent,nextSibling) {
}
return false;
},false);
// Assign data- attributes
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
@@ -100,11 +95,6 @@ BrowseWidget.prototype.execute = function() {
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
BrowseWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if($tw.utils.count(changedAttributes) > 0) {
this.refreshSelf();
return true;
}
return false;
};

View File

@@ -59,11 +59,6 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
$tw.utils.pushTop(classes,"tc-popup-handle");
}
domNode.className = classes.join(" ");
// Assign data- attributes
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Assign other attributes
if(this.style) {
domNode.setAttribute("style",this.style);
@@ -255,7 +250,7 @@ ButtonWidget.prototype.updateDomNodeClasses = function() {
//Add new classes from updated class attribute.
$tw.utils.pushTop(domNodeClasses,newClasses);
this.domNode.className = domNodeClasses.join(" ");
};
}
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
@@ -265,15 +260,8 @@ ButtonWidget.prototype.refresh = function(changedTiddlers) {
if(changedAttributes.actions || changedAttributes.to || changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.selectedClass || changedAttributes.style || changedAttributes.dragFilter || changedAttributes.dragTiddler || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup]) || (this.popupTitle && changedTiddlers[this.popupTitle]) || changedAttributes.popupAbsCoords || changedAttributes.setTitle || changedAttributes.setField || changedAttributes.setIndex || changedAttributes.popupTitle || changedAttributes.disabled || changedAttributes["default"]) {
this.refreshSelf();
return true;
} else {
if(changedAttributes["class"]) {
this.updateDomNodeClasses();
}
this.assignAttributes(this.domNodes[0],{
changedAttributes: changedAttributes,
sourcePrefix: "data-",
destPrefix: "data-"
});
} else if(changedAttributes["class"]) {
this.updateDomNodeClasses();
}
return this.refreshChildren(changedTiddlers);
};

View File

@@ -53,11 +53,6 @@ CheckboxWidget.prototype.render = function(parent,nextSibling) {
this.labelDomNode.appendChild(this.inputDomNode);
this.spanDomNode = this.document.createElement("span");
this.labelDomNode.appendChild(this.spanDomNode);
// Assign data- attributes
this.assignAttributes(this.inputDomNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Add a click event handler
$tw.utils.addEventListeners(this.inputDomNode,[
{name: "change", handlerObject: this, handlerMethod: "handleChangeEvent"}
@@ -330,11 +325,6 @@ CheckboxWidget.prototype.refresh = function(changedTiddlers) {
$tw.utils.removeClass(this.labelDomNode,"tc-checkbox-checked");
}
}
this.assignAttributes(this.inputDomNode,{
changedAttributes: changedAttributes,
sourcePrefix: "data-",
destPrefix: "data-"
});
return this.refreshChildren(changedTiddlers) || refreshed;
}
};
@@ -342,4 +332,3 @@ CheckboxWidget.prototype.refresh = function(changedTiddlers) {
exports.checkbox = CheckboxWidget;
})();

View File

@@ -52,11 +52,6 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
classes.push("tc-draggable");
}
domNode.setAttribute("class",classes.join(" "));
// Assign data- attributes and style. attributes
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Insert the node into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
@@ -113,19 +108,13 @@ DraggableWidget.prototype.updateDomNodeClasses = function() {
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
DraggableWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.tag || changedAttributes.selector || changedAttributes.dragimagetype || changedAttributes.enable || changedAttributes.startactions || changedAttributes.endactions) {
var changedAttributes = this.computeAttributes(),
changedAttributesCount = $tw.utils.count(changedAttributes);
if(changedAttributesCount === 1 && changedAttributes["class"]) {
this.updateDomNodeClasses();
} else if(changedAttributesCount > 0) {
this.refreshSelf();
return true;
} else {
if(changedAttributes["class"]) {
this.assignDomNodeClasses();
}
this.assignAttributes(this.domNodes[0],{
changedAttributes: changedAttributes,
sourcePrefix: "data-",
destPrefix: "data-"
});
}
return this.refreshChildren(changedTiddlers);
};

View File

@@ -42,11 +42,6 @@ DroppableWidget.prototype.render = function(parent,nextSibling) {
domNode = this.document.createElement(tag);
this.domNode = domNode;
this.assignDomNodeClasses();
// Assign data- attributes and style. attributes
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Add event handlers
if(this.droppableEnable) {
$tw.utils.addEventListeners(domNode,[
@@ -171,15 +166,8 @@ DroppableWidget.prototype.refresh = function(changedTiddlers) {
if(changedAttributes.tag || changedAttributes.enable || changedAttributes.disabledClass || changedAttributes.actions || changedAttributes.effect) {
this.refreshSelf();
return true;
} else {
if(changedAttributes["class"]) {
this.assignDomNodeClasses();
}
this.assignAttributes(this.domNodes[0],{
changedAttributes: changedAttributes,
sourcePrefix: "data-",
destPrefix: "data-"
});
} else if(changedAttributes["class"]) {
this.assignDomNodeClasses();
}
return this.refreshChildren(changedTiddlers);
};

View File

@@ -58,25 +58,24 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
if(this.wiki.isImageTiddler(this.imageSource)) {
var type = tiddler.fields.type,
text = tiddler.fields.text,
_canonical_uri = tiddler.fields._canonical_uri,
typeInfo = $tw.config.contentTypeInfo[type] || {},
deserializerType = typeInfo.deserializerType || type;
_canonical_uri = tiddler.fields._canonical_uri;
// If the tiddler has body text then it doesn't need to be lazily loaded
if(text) {
// Render the appropriate element for the image type by looking up the encoding in the content type info
var encoding = typeInfo.encoding || "utf8";
if (encoding === "base64") {
// .pdf .png .jpg etc.
src = "data:" + deserializerType + ";base64," + text;
if (deserializerType === "application/pdf") {
// Render the appropriate element for the image type
switch(type) {
case "application/pdf":
tag = "embed";
}
} else {
// .svg .tid .xml etc.
src = "data:" + deserializerType + "," + encodeURIComponent(text);
src = "data:application/pdf;base64," + text;
break;
case "image/svg+xml":
src = "data:image/svg+xml," + encodeURIComponent(text);
break;
default:
src = "data:" + type + ";base64," + text;
break;
}
} else if(_canonical_uri) {
switch(deserializerType) {
switch(type) {
case "application/pdf":
tag = "embed";
src = _canonical_uri;
@@ -100,9 +99,6 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
if(this.imageClass) {
domNode.setAttribute("class",this.imageClass);
}
if(this.imageUsemap) {
domNode.setAttribute("usemap",this.imageUsemap);
}
if(this.imageWidth) {
domNode.setAttribute("width",this.imageWidth);
}
@@ -142,7 +138,6 @@ ImageWidget.prototype.execute = function() {
this.imageWidth = this.getAttribute("width");
this.imageHeight = this.getAttribute("height");
this.imageClass = this.getAttribute("class");
this.imageUsemap = this.getAttribute("usemap");
this.imageTooltip = this.getAttribute("tooltip");
this.imageAlt = this.getAttribute("alt");
this.lazyLoading = this.getAttribute("loading");
@@ -153,7 +148,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
ImageWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource]) {
if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.tooltip || changedTiddlers[this.imageSource]) {
this.refreshSelf();
return true;
} else {

View File

@@ -43,11 +43,6 @@ LinkWidget.prototype.render = function(parent,nextSibling) {
} else {
// Just insert the link text
var domNode = this.document.createElement("span");
// Assign data- attributes
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
@@ -143,11 +138,6 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
widget: this
});
}
// Assign data- attributes
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Insert the link into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
@@ -217,7 +207,8 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
LinkWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if($tw.utils.count(changedAttributes) > 0) {
if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip ||
changedAttributes["class"] || changedAttributes.tabindex || changedAttributes.draggable || changedAttributes.tag) {
this.refreshSelf();
return true;
}
@@ -227,4 +218,3 @@ LinkWidget.prototype.refresh = function(changedTiddlers) {
exports.link = LinkWidget;
})();

View File

@@ -28,18 +28,6 @@ Inherit from the base widget class
*/
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
*/
@@ -80,6 +68,8 @@ ListWidget.prototype.execute = function() {
this.counterName = this.getAttribute("counter");
this.storyViewName = this.getAttribute("storyview");
this.historyTitle = this.getAttribute("history");
// Look for <$list-template> and <$list-empty> widgets as immediate child widgets
this.findExplicitTemplates();
// Compose the list elements
this.list = this.getTiddlerList();
var members = [],
@@ -102,7 +92,6 @@ 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") {
@@ -111,8 +100,6 @@ ListWidget.prototype.findExplicitTemplates = function() {
self.explicitEmptyTemplate = node.children;
} else if(node.type === "element" && node.tag === "p") {
searchChildren(node.children);
} else {
self.hasTemplateInBody = true;
}
});
};
@@ -173,11 +160,11 @@ ListWidget.prototype.makeItemTemplate = function(title,index) {
// Check for a <$list-item> widget
if(this.explicitListTemplate) {
templateTree = this.explicitListTemplate;
} else if(this.hasTemplateInBody) {
} else if (!this.explicitEmptyTemplate) {
templateTree = this.parseTreeNode.children;
}
}
if(!templateTree || templateTree.length === 0) {
if(!templateTree) {
// 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: [
{type: "text", text: title}
@@ -427,27 +414,4 @@ ListItemWidget.prototype.refresh = function(changedTiddlers) {
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;
})();

View File

@@ -40,10 +40,6 @@ RadioWidget.prototype.render = function(parent,nextSibling) {
);
this.inputDomNode = this.document.createElement("input");
this.inputDomNode.setAttribute("type","radio");
this.assignAttributes(this.inputDomNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
if(isChecked) {
this.inputDomNode.checked = true;
}

View File

@@ -50,10 +50,6 @@ RangeWidget.prototype.render = function(parent,nextSibling) {
this.inputDomNode.setAttribute("disabled",true);
}
this.inputDomNode.value = this.getValue();
this.assignAttributes(this.inputDomNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Add a click event handler
$tw.utils.addEventListeners(this.inputDomNode,[
{name:"mousedown", handlerObject:this, handlerMethod:"handleMouseDownEvent"},

View File

@@ -171,46 +171,6 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
parent.insertBefore(this.outerDomNode,nextSibling);
this.renderChildren(this.innerDomNode,null);
this.domNodes.push(this.outerDomNode);
// If the scroll position is bound to a tiddler
if(this.scrollableBind) {
// After a delay for rendering, scroll to the bound position
setTimeout(this.updateScrollPositionFromBoundTiddler.bind(this),50);
// Save scroll position on DOM scroll event
this.outerDomNode.addEventListener("scroll",function(event) {
var existingTiddler = self.wiki.getTiddler(self.scrollableBind),
newTiddlerFields = {
title: self.scrollableBind,
"scroll-left": self.outerDomNode.scrollLeft.toString(),
"scroll-top": self.outerDomNode.scrollTop.toString()
};
if(!existingTiddler || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) {
self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields));
}
});
}
};
ScrollableWidget.prototype.updateScrollPositionFromBoundTiddler = function() {
// Bail if we're running on the fakedom
if(!this.outerDomNode.scrollTo) {
return;
}
var tiddler = this.wiki.getTiddler(this.scrollableBind);
if(tiddler) {
var scrollLeftTo = this.outerDomNode.scrollLeft;
if(parseFloat(tiddler.fields["scroll-left"]).toString() === tiddler.fields["scroll-left"]) {
scrollLeftTo = parseFloat(tiddler.fields["scroll-left"]);
}
var scrollTopTo = this.outerDomNode.scrollTop;
if(parseFloat(tiddler.fields["scroll-top"]).toString() === tiddler.fields["scroll-top"]) {
scrollTopTo = parseFloat(tiddler.fields["scroll-top"]);
}
this.outerDomNode.scrollTo({
top: scrollTopTo,
left: scrollLeftTo,
behavior: "instant"
})
}
};
/*
@@ -218,7 +178,6 @@ Compute the internal state of the widget
*/
ScrollableWidget.prototype.execute = function() {
// Get attributes
this.scrollableBind = this.getAttribute("bind");
this.fallthrough = this.getAttribute("fallthrough","yes");
this["class"] = this.getAttribute("class");
// Make child widgets
@@ -234,9 +193,6 @@ ScrollableWidget.prototype.refresh = function(changedTiddlers) {
this.refreshSelf();
return true;
}
if(changedAttributes.bind || changedTiddlers[this.getAttribute("bind")]) {
this.updateScrollPositionFromBoundTiddler();
}
return this.refreshChildren(changedTiddlers);
};

View File

@@ -40,31 +40,7 @@ SelectWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
//Create element
var domNode = this.document.createElement("select");
if(this.selectClass) {
domNode.classname = this.selectClass;
}
// Assign data- attributes
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
if(this.selectMultiple) {
domNode.setAttribute("multiple","multiple");
}
if(this.selectSize) {
domNode.setAttribute("size",this.selectSize);
}
if(this.selectTabindex) {
domNode.setAttribute("tabindex",this.selectTabindex);
}
if(this.selectTooltip) {
domNode.setAttribute("title",this.selectTooltip);
}
this.renderChildren(domNode,nextSibling);
this.parentDomNode.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(parent,nextSibling);
this.setSelectValue();
if(this.selectFocus == "yes") {
this.getSelectDomNode().focus();
@@ -137,7 +113,7 @@ SelectWidget.prototype.setSelectValue = function() {
Get the DOM node of the select element
*/
SelectWidget.prototype.getSelectDomNode = function() {
return this.domNodes[0];
return this.children[0].domNodes[0];
};
// Return an array of the selected opion values
@@ -173,7 +149,27 @@ SelectWidget.prototype.execute = function() {
this.selectTooltip = this.getAttribute("tooltip");
this.selectFocus = this.getAttribute("focus");
// Make the child widgets
this.makeChildWidgets();
var selectNode = {
type: "element",
tag: "select",
children: this.parseTreeNode.children
};
if(this.selectClass) {
$tw.utils.addAttributeToParseTreeNode(selectNode,"class",this.selectClass);
}
if(this.selectMultiple) {
$tw.utils.addAttributeToParseTreeNode(selectNode,"multiple","multiple");
}
if(this.selectSize) {
$tw.utils.addAttributeToParseTreeNode(selectNode,"size",this.selectSize);
}
if(this.selectTabindex) {
$tw.utils.addAttributeToParseTreeNode(selectNode,"tabindex",this.selectTabindex);
}
if(this.selectTooltip) {
$tw.utils.addAttributeToParseTreeNode(selectNode,"title",this.selectTooltip);
}
this.makeChildWidgets([selectNode]);
};
/*
@@ -182,21 +178,17 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
SelectWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
// If we're using a different tiddler/field/index then completely refresh ourselves
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip || changedAttributes.tabindex) {
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip) {
this.refreshSelf();
return true;
// If the target tiddler value has changed, just update setting and refresh the children
} else {
if(changedAttributes.class) {
this.selectClass = this.getAttribute("class");
this.getSelectDomNode().setAttribute("class",this.selectClass);
}
this.assignAttributes(this.getSelectDomNode(),{
changedAttributes: changedAttributes,
sourcePrefix: "data-",
destPrefix: "data-"
});
var childrenRefreshed = this.refreshChildren(changedTiddlers);
// If the target tiddler value has changed, just update setting and refresh the children
if(changedTiddlers[this.selectTitle] || childrenRefreshed) {
this.setSelectValue();
}

View File

@@ -109,7 +109,6 @@ TranscludeWidget.prototype.collectAttributes = function() {
this.recursionMarker = this.getAttribute("recursionMarker","yes");
} else {
this.transcludeVariable = this.getAttribute("$variable");
this.transcludeVariableIsFunction = false;
this.transcludeType = this.getAttribute("$type");
this.transcludeOutput = this.getAttribute("$output","text/html");
this.transcludeTitle = this.getAttribute("$tiddler",this.getVariable("currentTiddler"));
@@ -185,9 +184,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
if(this.transcludeVariable) {
// Transcluding a variable
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()});
this.transcludeVariableIsFunction = variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition;
text = variableInfo.text;
this.transcludeFunctionResult = text;
return {
text: variableInfo.text,
type: this.transcludeType
@@ -222,24 +219,21 @@ TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) {
// Transcluding a variable
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}),
srcVariable = variableInfo && variableInfo.srcVariable;
if(srcVariable && srcVariable.isFunctionDefinition) {
this.transcludeVariableIsFunction = true;
this.transcludeFunctionResult = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || "";
}
if(variableInfo.text) {
if(srcVariable && srcVariable.isFunctionDefinition) {
var result = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || "";
parser = {
tree: [{
type: "text",
text: this.transcludeFunctionResult
text: result
}],
source: this.transcludeFunctionResult,
source: result,
type: "text/vnd.tiddlywiki"
};
if(parseAsInline) {
parser.tree[0] = {
type: "text",
text: this.transcludeFunctionResult
text: result
};
} else {
parser.tree[0] = {
@@ -247,7 +241,7 @@ TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) {
tag: "p",
children: [{
type: "text",
text: this.transcludeFunctionResult
text: result
}]
}
}
@@ -436,19 +430,12 @@ TranscludeWidget.prototype.parserNeedsRefresh = function() {
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
*/
TranscludeWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(($tw.utils.count(changedAttributes) > 0) || (this.transcludeVariableIsFunction && this.functionNeedsRefresh()) || (!this.transcludeVariable && changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) {
if(($tw.utils.count(changedAttributes) > 0) || (!this.transcludeVariable && changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) {
this.refreshSelf();
return true;
} else {

View File

@@ -413,34 +413,16 @@ Widget.prototype.getAttribute = function(name,defaultText) {
};
/*
Assign the common attributes of the widget to a domNode
Assign the computed attributes of the widget to a domNode
options include:
sourcePrefix: prefix of attributes that are to be directly assigned (defaults to the empty string meaning all attributes)
destPrefix: prefix to be applied to attribute names that are to be directly assigned (defaults to the emtpy string which means no prefix is added)
changedAttributes: hashmap by attribute name of attributes to process (if missing, process all attributes)
excludeEventAttributes: ignores attributes whose name would begin with "on"
excludeEventAttributes: ignores attributes whose name begins with "on"
*/
Widget.prototype.assignAttributes = function(domNode,options) {
options = options || {};
var self = this,
changedAttributes = options.changedAttributes || this.attributes,
sourcePrefix = options.sourcePrefix || "",
destPrefix = options.destPrefix || "",
EVENT_ATTRIBUTE_PREFIX = "on";
var self = this;
var assignAttribute = function(name,value) {
// Process any style attributes before considering sourcePrefix and destPrefix
if(name.substr(0,6) === "style." && name.length > 6) {
domNode.style[$tw.utils.unHyphenateCss(name.substr(6))] = value;
return;
}
// Check if the sourcePrefix is a match
if(name.substr(0,sourcePrefix.length) === sourcePrefix) {
name = destPrefix + name.substr(sourcePrefix.length);
} else {
value = undefined;
}
// Check for excluded attribute names
if(options.excludeEventAttributes && name.substr(0,2).toLowerCase() === EVENT_ATTRIBUTE_PREFIX) {
if(options.excludeEventAttributes && name.substr(0,2) === "on") {
value = undefined;
}
if(value !== undefined) {
@@ -450,24 +432,26 @@ Widget.prototype.assignAttributes = function(domNode,options) {
namespace = "http://www.w3.org/1999/xlink";
name = name.substr(6);
}
// Setting certain attributes can cause a DOM error (eg xmlns on the svg element)
try {
domNode.setAttributeNS(namespace,name,value);
} catch(e) {
// Handle styles
if(name.substr(0,6) === "style." && name.length > 6) {
domNode.style[$tw.utils.unHyphenateCss(name.substr(6))] = value;
} else {
// Setting certain attributes can cause a DOM error (eg xmlns on the svg element)
try {
domNode.setAttributeNS(namespace,name,value);
} catch(e) {
}
}
}
};
// If the parse tree node has the orderedAttributes property then use that order
}
// Not all parse tree nodes have the orderedAttributes property
if(this.parseTreeNode.orderedAttributes) {
$tw.utils.each(this.parseTreeNode.orderedAttributes,function(attribute,index) {
if(attribute.name in changedAttributes) {
assignAttribute(attribute.name,self.getAttribute(attribute.name));
}
});
// Otherwise update each changed attribute irrespective of order
assignAttribute(attribute.name,self.attributes[attribute.name]);
});
} else {
$tw.utils.each(changedAttributes,function(value,name) {
assignAttribute(name,self.getAttribute(name));
$tw.utils.each(Object.keys(self.attributes).sort(),function(name) {
assignAttribute(name,self.attributes[name]);
});
}
};

View File

@@ -1287,7 +1287,7 @@ exports.search = function(text,options) {
console.log("Regexp error parsing /(" + text + ")/" + flags + ": ",e);
}
} else if(options.some) {
terms = text.trim().split(/[^\S\xA0]+/);
terms = text.trim().split(/ +/);
if(terms.length === 1 && terms[0] === "") {
searchTermsRegExps = null;
} else {
@@ -1298,7 +1298,7 @@ exports.search = function(text,options) {
searchTermsRegExps.push(new RegExp("(" + regExpStr + ")",flags));
}
} else { // default: words
terms = text.split(/[^\S\xA0]+/);
terms = text.split(/ +/);
if(terms.length === 1 && terms[0] === "") {
searchTermsRegExps = null;
} else {

View File

@@ -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="$:/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="$:/DefaultTiddlers"><<lingo DefaultTiddlers/Prompt>></$link> |<<lingo DefaultTiddlers/TopHint>><br> <$edit class="tc-edit-texteditor" tiddler="$:/DefaultTiddlers" autoHeight="yes"/><br>//<<lingo DefaultTiddlers/BottomHint>>// |
|<$link to="$:/DefaultTiddlers"><<lingo DefaultTiddlers/Prompt>></$link> |<<lingo DefaultTiddlers/TopHint>><br> <$edit class="tc-edit-texteditor" tiddler="$:/DefaultTiddlers"/><br>//<<lingo DefaultTiddlers/BottomHint>>// |
|<$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/Text"><<lingo NewJournal/Text/Prompt>></$link> |<$edit tiddler="$:/config/NewJournal/Text" class="tc-edit-texteditor" default="" autoHeight="yes"/> |
|<$link to="$:/config/NewJournal/Text"><<lingo NewJournal/Text/Prompt>></$link> |<$edit tiddler="$:/config/NewJournal/Text" class="tc-edit-texteditor" default=""/> |
|<$link to="$:/config/NewTiddler/Tags"><<lingo NewTiddler/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewTiddler/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|<$link to="$:/config/NewJournal/Tags"><<lingo NewJournal/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewJournal/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|<$link to="$:/config/AutoFocus"><<lingo AutoFocus/Prompt>></$link> |{{$:/snippets/minifocusswitcher}} |

View File

@@ -18,7 +18,7 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
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>> >
<div>
<div class={{{ [function[edit-preview-state]match[yes]then[tc-tiddler-preview]else[tc-tiddler-preview-hidden]] [[tc-tiddler-editor]] +[join[ ]] }}}>
<div class={{{ [function[edit-preview-state]match[yes]then[tc-tiddler-preview]] +[join[ ]] }}}>
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>

View File

@@ -54,7 +54,7 @@ $:/config/EditTemplateFields/Visibility/$(currentField)$
\whitespace trim
<$vars name={{{ [<newFieldNameTiddler>get[text]] }}}>
<$reveal type="nomatch" text="" default=<<name>>>
<$button tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}>
<$button tooltip=<<lingo Fields/Add/Button/Hint>>>
<$action-sendmessage $message="tm-add-field"
$name=<<name>>
$value={{{ [subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
@@ -89,7 +89,7 @@ $value={{{ [subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
</td>
<td class="tc-edit-field-remove">
<$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}>
<$action-deletefield $field=<<currentField>>/>
<$action-deletefield $field=<<currentField>>/><$set name="currentTiddlerCSSescaped" value={{{ [<currentTiddler>escapecss[]] }}}><$action-sendmessage $message="tm-focus-selector" $param=<<current-tiddler-new-field-selector>>/></$set>
{{$:/core/images/delete-button}}
</$button>
</td>

View File

@@ -13,7 +13,7 @@ tags: $:/tags/Macro
$(colour-picker-update-recent)$
<$transclude $variable="__actions__"/>
$actions$
<span style="display:inline-block; background-color: $(colour-picker-value)$; width: 100%; height: 100%; border-radius: 50%;"/>
@@ -23,7 +23,7 @@ $(colour-picker-update-recent)$
\define colour-picker-recent-inner(actions)
\whitespace trim
<$set name="colour-picker-value" value="$(recentColour)$">
<$macrocall $name="colour-picker-inner" actions=<<__actions__>>/>
<$macrocall $name="colour-picker-inner" actions="""$actions$"""/>
</$set>
\end
@@ -31,7 +31,7 @@ $(colour-picker-update-recent)$
\whitespace trim
{{$:/language/ColourPicker/Recent}}<$list filter="[list[$:/config/ColourPicker/Recent]]" variable="recentColour">
&#32;
<$macrocall $name="colour-picker-recent-inner" actions=<<__actions__>>/>
<$macrocall $name="colour-picker-recent-inner" actions="""$actions$"""/>
</$list>
\end
@@ -39,13 +39,13 @@ $(colour-picker-update-recent)$
\whitespace trim
<div class="tc-colour-chooser">
<$macrocall $name="colour-picker-recent" actions=<<__actions__>>/>
<$macrocall $name="colour-picker-recent" actions="""$actions$"""/>
---
<$list filter="LightPink Pink Crimson LavenderBlush PaleVioletRed HotPink DeepPink MediumVioletRed Orchid Thistle Plum Violet Magenta Fuchsia DarkMagenta Purple MediumOrchid DarkViolet DarkOrchid Indigo BlueViolet MediumPurple MediumSlateBlue SlateBlue DarkSlateBlue Lavender GhostWhite Blue MediumBlue MidnightBlue DarkBlue Navy RoyalBlue CornflowerBlue LightSteelBlue LightSlateGrey SlateGrey DodgerBlue AliceBlue SteelBlue LightSkyBlue SkyBlue DeepSkyBlue LightBlue PowderBlue CadetBlue Azure LightCyan PaleTurquoise Cyan Aqua DarkTurquoise DarkSlateGrey DarkCyan Teal MediumTurquoise LightSeaGreen Turquoise Aquamarine MediumAquamarine MediumSpringGreen MintCream SpringGreen MediumSeaGreen SeaGreen Honeydew LightGreen PaleGreen DarkSeaGreen LimeGreen Lime ForestGreen Green DarkGreen Chartreuse LawnGreen GreenYellow DarkOliveGreen YellowGreen OliveDrab Beige LightGoldenrodYellow Ivory LightYellow Yellow Olive DarkKhaki LemonChiffon PaleGoldenrod Khaki Gold Cornsilk Goldenrod DarkGoldenrod FloralWhite OldLace Wheat Moccasin Orange PapayaWhip BlanchedAlmond NavajoWhite AntiqueWhite Tan BurlyWood Bisque DarkOrange Linen Peru PeachPuff SandyBrown Chocolate SaddleBrown Seashell Sienna LightSalmon Coral OrangeRed DarkSalmon Tomato MistyRose Salmon Snow LightCoral RosyBrown IndianRed Red Brown FireBrick DarkRed Maroon White WhiteSmoke Gainsboro LightGrey Silver DarkGrey Grey DimGrey Black" variable="colour-picker-value">
&#32;
<$macrocall $name="colour-picker-inner" actions=<<__actions__>>/>
<$macrocall $name="colour-picker-inner" actions="""$actions$"""/>
</$list>
---
@@ -54,7 +54,7 @@ $(colour-picker-update-recent)$
&#32;
<$edit-text tiddler="$:/config/ColourPicker/New" type="color" tag="input"/>
<$set name="colour-picker-value" value={{$:/config/ColourPicker/New}}>
<$macrocall $name="colour-picker-inner" actions=<<__actions__>>/>
<$macrocall $name="colour-picker-inner" actions="""$actions$"""/>
</$set>
</div>

View File

@@ -5,13 +5,13 @@ title: $:/core/macros/image-picker
type: text/vnd.tiddlywiki
\define image-picker-thumbnail(actions)
<$button tag="a" tooltip="""$(imageTitle)$"""><$transclude $variable="__actions__"/><$transclude tiddler=<<imageTitle>>/></$button>
<$button tag="a" tooltip="""$(imageTitle)$""">$actions$<$transclude tiddler=<<imageTitle>>/></$button>
\end
\define image-picker-list(filter,actions)
\whitespace trim
<$list filter="""$filter$""" variable="imageTitle">
<$macrocall $name="image-picker-thumbnail" actions=<<__actions__>>/>
<$macrocall $name="image-picker-thumbnail" actions="""$actions$"""/>
&#32;
</$list>
\end
@@ -25,15 +25,15 @@ type: text/vnd.tiddlywiki
{{$:/language/SystemTiddlers/Include/Prompt}}
</$checkbox>
<$reveal state=<<state-system>> type="match" text="hide" default="hide" tag="div">
<$macrocall $name="image-picker-list" filter="""$filter$ +[!is[system]]""" actions=<<__actions__>>/>
<$macrocall $name="image-picker-list" filter="""$filter$ +[!is[system]]""" actions="""$actions$"""/>
</$reveal>
<$reveal state=<<state-system>> type="nomatch" text="hide" default="hide" tag="div">
<$macrocall $name="image-picker-list" filter="""$filter$""" actions=<<__actions__>>/>
<$macrocall $name="image-picker-list" filter="""$filter$""" actions="""$actions$"""/>
</$reveal>
</$vars>
</div>
\end
\define image-picker-include-tagged-images(actions)
<$macrocall $name="image-picker" filter="[all[shadows+tiddlers]is[image]] [all[shadows+tiddlers]tag[$:/tags/Image]] -[type[application/pdf]] +[!has[draft.of]sort[title]]" actions=<<__actions__>>/>
<$macrocall $name="image-picker" filter="[all[shadows+tiddlers]is[image]] [all[shadows+tiddlers]tag[$:/tags/Image]] -[type[application/pdf]] +[!has[draft.of]sort[title]]" actions="""$actions$"""/>
\end

View File

@@ -4,17 +4,17 @@ tags: $:/tags/Macro
\define list-links(filter,type:"ul",subtype:"li",class:"",emptyMessage,field:"caption")
\whitespace trim
<$genesis $type=<<__type__>> class=<<__class__>>>
<$list filter=<<__filter__>> emptyMessage=<<__emptyMessage__>>>
<$genesis $type=<<__subtype__>>>
<$link to={{!!title}}>
<$let tv-wikilinks="no">
<$transclude field=<<__field__>>>
<$view field="title"/>
</$transclude>
</$let>
</$link>
</$genesis>
</$list>
<$list filter=<<__filter__>> emptyMessage=<<__emptyMessage__>>>
<$genesis $type=<<__subtype__>>>
<$link to={{!!title}}>
<$let tv-wikilinks="no">
<$transclude field=<<__field__>>>
<$view field="title"/>
</$transclude>
</$let>
</$link>
</$genesis>
</$list>
</$genesis>
\end
@@ -25,42 +25,34 @@ tags: $:/tags/Macro
\define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate)
\whitespace trim
<span class="tc-links-draggable-list">
<$vars targetTiddler="""$tiddler$""" targetField="""$field$""">
<$genesis $type=<<__type__>> class="$class$">
<$list filter="[list[$tiddler$!!$field$]]" emptyMessage=<<__emptyMessage__>>>
<$droppable
actions=<<list-links-draggable-drop-actions>>
tag="""$subtype$"""
enable=<<tv-enable-drag-and-drop>>
>
<div class="tc-droppable-placeholder"/>
<div>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$let tv-wikilinks="no">
<$transclude field="caption">
<$view field="title"/>
</$transclude>
</$let>
</$link>
</$transclude>
</div>
</$droppable>
</$list>
<$tiddler tiddler="">
<$droppable
actions=<<list-links-draggable-drop-actions>>
tag="div"
enable=<<tv-enable-drag-and-drop>>
>
<div class="tc-droppable-placeholder">
{{$:/core/images/blank}}
</div>
<div style="height:0.5em;"/>
</$droppable>
</$tiddler>
</$genesis>
</$vars>
<$vars targetTiddler="""$tiddler$""" targetField="""$field$""">
<$genesis $type=<<__type__>> class="$class$">
<$list filter="[list[$tiddler$!!$field$]]" emptyMessage=<<__emptyMessage__>>>
<$droppable actions=<<list-links-draggable-drop-actions>> tag="""$subtype$""" enable=<<tv-enable-drag-and-drop>>>
<div class="tc-droppable-placeholder"/>
<div>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$let tv-wikilinks="no">
<$transclude field="caption">
<$view field="title"/>
</$transclude>
</$let>
</$link>
</$transclude>
</div>
</$droppable>
</$list>
<$tiddler tiddler="">
<$droppable actions=<<list-links-draggable-drop-actions>> tag="div" enable=<<tv-enable-drag-and-drop>>>
<div class="tc-droppable-placeholder">
{{$:/core/images/blank}}
</div>
<div style="height:0.5em;"/>
</$droppable>
</$tiddler>
</$genesis>
</$vars>
</span>
\end
@@ -68,59 +60,50 @@ tags: $:/tags/Macro
\whitespace trim
<!-- Save the current ordering of the tiddlers with this tag -->
<$set name="order" filter="[<__tag__>tagging[]]">
<!-- Remove any list-after or list-before fields from the tiddlers with this tag -->
<$list filter="[<__tag__>tagging[]]">
<$action-deletefield $field="list-before"/>
<$action-deletefield $field="list-after"/>
</$list>
<!-- Save the new order to the Tag Tiddler -->
<$action-listops $tiddler=<<__tag__>> $field="list" $filter="+[enlist<order>] +[insertbefore<actionTiddler>,<currentTiddler>]"/>
<!-- Make sure the newly added item has the right tag -->
<!-- Removing this line makes dragging tags within the dropdown work as intended -->
<!--<$action-listops $tiddler=<<actionTiddler>> $tags=<<__tag__>>/>-->
<!-- Using the following 5 lines as replacement makes dragging titles from outside into the dropdown apply the tag -->
<$list filter="[<actionTiddler>!contains:tags<__tag__>]">
<$fieldmangler tiddler=<<actionTiddler>>>
<$action-sendmessage $message="tm-add-tag" $param=<<__tag__>>/>
</$fieldmangler>
</$list>
<!-- Remove any list-after or list-before fields from the tiddlers with this tag -->
<$list filter="[<__tag__>tagging[]]">
<$action-deletefield $field="list-before"/>
<$action-deletefield $field="list-after"/>
</$list>
<!-- Save the new order to the Tag Tiddler -->
<$action-listops $tiddler=<<__tag__>> $field="list" $filter="+[enlist<order>] +[insertbefore<actionTiddler>,<currentTiddler>]"/>
<!-- Make sure the newly added item has the right tag -->
<!-- Removing this line makes dragging tags within the dropdown work as intended -->
<!--<$action-listops $tiddler=<<actionTiddler>> $tags=<<__tag__>>/>-->
<!-- Using the following 5 lines as replacement makes dragging titles from outside into the dropdown apply the tag -->
<$list filter="[<actionTiddler>!contains:tags<__tag__>]">
<$fieldmangler tiddler=<<actionTiddler>>>
<$action-sendmessage $message="tm-add-tag" $param=<<__tag__>>/>
</$fieldmangler>
</$list>
</$set>
\end
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"")
\whitespace trim
<span class="tc-tagged-draggable-list">
<$set name="tag" value=<<__tag__>>>
<$list
filter="[<__tag__>tagging[]$subFilter$]"
emptyMessage=<<__emptyMessage__>>
storyview=<<__storyview__>>
>
<$genesis $type=<<__elementTag__>> class="tc-menu-list-item">
<$droppable
actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>"""
enable=<<tv-enable-drag-and-drop>>
>
<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/>
<$genesis $type=<<__elementTag__>>>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$view field="title"/>
</$link>
</$transclude>
</$genesis>
</$droppable>
</$genesis>
</$list>
<$tiddler tiddler="">
<$droppable
actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>"""
enable=<<tv-enable-drag-and-drop>>
>
<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/>
<$genesis $type=<<__elementTag__>> style="height:0.5em;"/>
</$droppable>
</$tiddler>
</$set>
<$set name="tag" value=<<__tag__>>>
<$list filter="[<__tag__>tagging[]$subFilter$]" emptyMessage=<<__emptyMessage__>> storyview=<<__storyview__>>>
<$genesis $type=<<__elementTag__>> class="tc-menu-list-item">
<$droppable actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""" enable=<<tv-enable-drag-and-drop>>>
<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/>
<$genesis $type=<<__elementTag__>>>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$view field="title"/>
</$link>
</$transclude>
</$genesis>
</$droppable>
</$genesis>
</$list>
<$tiddler tiddler="">
<$droppable actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""" enable=<<tv-enable-drag-and-drop>>>
<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/>
<$genesis $type=<<__elementTag__>> style="height:0.5em;">
</$genesis>
</$droppable>
</$tiddler>
</$set>
</span>
\end

View File

@@ -4,15 +4,7 @@ code-body: yes
\define tabs-button()
\whitespace trim
<$button
set=<<tabsState>>
setTo=<<currentTab>>
default=<<__default__>>
selectedClass="tc-tab-selected"
tooltip={{!!tooltip}}
role="switch"
data-tab-title=<<currentTab>>
>
<$button set=<<tabsState>> setTo=<<currentTab>> default=<<__default__>> selectedClass="tc-tab-selected" tooltip={{!!tooltip}} role="switch">
<$tiddler tiddler=<<save-currentTiddler>>>
<$set name="tv-wikilinks" value="no">
<$transclude tiddler=<<__buttonTemplate__>> mode="inline">

View File

@@ -16,7 +16,7 @@ second-search-filter: [tags[]is[system]search:title<userInput>sort[]]
emptyMessage="<$action-listops $tiddler=<<saveTiddler>> $field=<<__tagField__>> $subfilter='-[<tag>]'/>"
>
<$action-listops $tiddler=<<saveTiddler>> $field=<<__tagField__>> $subfilter="[<tag>trim[]]"/>
<$transclude $variable="__actions__"/>
$actions$
</$list>
</$set>
<<delete-tag-state-tiddlers>>
@@ -102,7 +102,7 @@ second-search-filter: [tags[]is[system]search:title<userInput>sort[]]
<$set name="tag" value={{{ [<newTagNameTiddler>get[text]] }}}>
<$button set=<<newTagNameTiddler>> setTo="" class="">
<$action-listops $tiddler=<<saveTiddler>> $field=<<__tagField__>> $subfilter="[<tag>trim[]]"/>
<$transclude $variable="__actions__"/>
$actions$
<$set name="currentTiddlerCSSEscaped" value={{{ [<saveTiddler>escapecss[]] }}}>
<<delete-tag-state-tiddlers>><$action-sendmessage $message="tm-focus-selector" $param=<<get-tagpicker-focus-selector>>/>
</$set>

View File

@@ -118,7 +118,7 @@ tags: $:/tags/Macro
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item" >
<li class=<<toc-item-class>>>
<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}>
<$list filter="[all[current]tagging[]$sort$limit[1]] -[subfilter<__exclude__>]" variable="ignore" emptyMessage="<$button class='tc-btn-invisible'>{{$:/core/images/blank}}</$button>">
<$list filter="[all[current]tagging[]$sort$limit[1]]" variable="ignore" emptyMessage="<$button class='tc-btn-invisible'>{{$:/core/images/blank}}</$button>">
<$reveal type="nomatch" stateTitle=<<toc-state>> text="open">
<$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep">
<$transclude tiddler=<<toc-closed-icon>> />
@@ -145,7 +145,7 @@ tags: $:/tags/Macro
<$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">
<li class=<<toc-item-class>>>
<$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>""">
<$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>""">
<$reveal type="nomatch" stateTitle=<<toc-state>> text="open">
<$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep">
<$transclude tiddler=<<toc-closed-icon>> />

View File

@@ -1,6 +1,6 @@
caption: 5.3.2
created: 20231016122502955
modified: 20231016122502955
created: 20230820114855583
modified: 20230820114855583
tags: ReleaseNotes
title: Release 5.3.2
type: text/vnd.tiddlywiki
@@ -8,47 +8,6 @@ description: Under development
//[[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
Improvements to the following translations:
@@ -59,8 +18,7 @@ Improvements to the following translations:
! 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
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/1be8f0a9336952d4745d2bd4f2327e353580a272">> comments plugin to use predefined palette colours
! Widget Improvements
@@ -85,8 +43,6 @@ Improvements to the following translations:
* <<.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

View File

@@ -1,3 +0,0 @@
title: $:/my-scroll-position
scroll-left: 0
scroll-top: 100

View File

@@ -1,25 +0,0 @@
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();
});

View File

@@ -1,20 +0,0 @@
title: Transclude/CustomWidget/CodeblockOverride-TextParser
description: Test that overriding codeblock widget does not impact text parser
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\widget $codeblock(code)
<$transclude $variable="copy-to-clipboard" src=<<code>>/>
<$genesis $type="$codeblock" $remappable="no" code=<<code>>/>
\end
\procedure myvariable() hello
<$transclude $variable="myvariable" $type="text/plain" $output="text/plain"/>
+
title: ExpectedResult
<p>hello</p>

View File

@@ -1,27 +0,0 @@
title: Transclude/Variable/Refreshing
description: Transcluding and refreshing a function
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\function list-join(filter, sep:", ") [subfilter<filter>join<sep>]
<$tiddler tiddler="TestData">
<<list-join "[enlist{!!items}]">>
</$tiddler>
+
title: TestData
+
title: Actions
<$action-setfield $tiddler="TestData" items={{{ [range[10]join[ ]] }}}/>
+
title: ExpectedResult
<p>1, 2, 3, 4, 5, 6, 7, 8, 9, 10</p>

View File

@@ -1,15 +0,0 @@
title: Transclude/Variable/Static
description: Transcluding a function
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
items: 1 2 3 4 5 6 7 8 9 10
\function list-join(filter, sep:", ") [subfilter<filter>join<sep>]
<<list-join "[enlist{!!items}]">>
+
title: ExpectedResult
<p>1, 2, 3, 4, 5, 6, 7, 8, 9, 10</p>

View File

@@ -1,27 +0,0 @@
title: Widgets/DataAttributes/ButtonWidget
description: Data Attributes for ButtonWidget
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$button tag="div" class="myclass" data-title="mytiddler" style.color="red" onclick="clicked">
my tiddler
</$button>
<$button tag="div" class="myclass" data-title={{Temp}} style.color={{{ [[Temp]get[color]] }}}>
hello
</$button>
+
title: Actions
<$action-setfield $tiddler="Temp" $field="text" $value="Title2" color="red"/>
+
title: Temp
color: black
Title1
+
title: ExpectedResult
<p><div class="myclass" data-title="mytiddler" style="color:red;">my tiddler</div><div class="myclass" data-title="Title2" style="color:red;">hello</div></p>

View File

@@ -1,22 +0,0 @@
title: Widgets/DataAttributes/CheckboxWidget
description: Data Attributes for CheckboxWidget
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$checkbox tag="done" data-title={{Temp}} style.color={{{ [[Temp]get[color]] }}} onclick="clicked"> Is it done?</$checkbox>
+
title: Actions
<$action-setfield $tiddler="Temp" $field="text" $value="Title2" color="red"/>
+
title: Temp
color: black
Title1
+
title: ExpectedResult
<p><label class="tc-checkbox "><input data-title="Title2" type="checkbox" style="color:red;"><span>Is it done?</span></label></p>

View File

@@ -1,27 +0,0 @@
title: Widgets/DataAttributes/DraggableWidget
description: Data Attributes for DraggableWidget
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$draggable tag="div" class="myclass" data-title="mytiddler" style.color="red" onclick="clicked">
my tiddler
</$draggable>
<$draggable tag="div" class="myclass" data-title={{Temp}} style.color={{{ [[Temp]get[color]] }}}>
hello
</$draggable>
+
title: Actions
<$action-setfield $tiddler="Temp" $field="text" $value="Title2" color="red"/>
+
title: Temp
color: black
Title1
+
title: ExpectedResult
<p><div class="myclass tc-draggable" data-title="mytiddler" draggable="true" style="color:red;">my tiddler</div><div class="myclass tc-draggable" data-title="Title2" draggable="true" style="color:red;">hello</div></p>

View File

@@ -1,27 +0,0 @@
title: Widgets/DataAttributes/DroppableWidget
description: Data Attributes for DroppableWidget
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$droppable tag="div" class="myclass" data-title="mytiddler" style.color="red" onclick="clicked">
my tiddler
</$droppable>
<$droppable tag="div" class="myclass" data-title={{Temp}} style.color={{{ [[Temp]get[color]] }}}>
hello
</$droppable>
+
title: Actions
<$action-setfield $tiddler="Temp" $field="text" $value="Title2" color="red"/>
+
title: Temp
color: black
Title1
+
title: ExpectedResult
<p><div class="myclass tc-droppable" data-title="mytiddler" style="color:red;">my tiddler</div><div class="myclass tc-droppable" data-title="Title2" style="color:red;">hello</div></p>

View File

@@ -1,27 +0,0 @@
title: Widgets/DataAttributes/LinkWidget
description: Data Attributes for LinkWidget
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$link data-id="mytiddler" style.color="red" to="Temp" onclick="clicked">
link to Temp
</$link>
<$link tag="button" data-id={{Temp}} style.color={{{ [[Temp]get[color]] }}} to="SomeTiddler">
some tiddler
</$link>
+
title: Actions
<$action-setfield $tiddler="Temp" $field="text" $value="Title2" color="red"/>
+
title: Temp
color: black
Title1
+
title: ExpectedResult
<p><a class="tc-tiddlylink tc-tiddlylink-resolves" data-id="mytiddler" href="#Temp" style="color:red;">link to Temp</a><button class="tc-tiddlylink tc-tiddlylink-missing" data-id="Title2" draggable="true" style="color:red;">some tiddler</button></p>

View File

@@ -1,15 +0,0 @@
title: Widgets/DataAttributes/OrderedStyleAttributes
description: Ordered style attributes
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<div style="background:red;color:blue;" style.background="green">
hello
</div>
+
title: ExpectedResult
<p><div style="background:green;color:blue;">hello</div></p>

View File

@@ -1,27 +0,0 @@
title: Widgets/DataAttributes/SelectWidget
description: Data Attributes for SelectWidget
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$select tiddler='New Tiddler' field='text' default='Choose a new text' data-title={{Temp}} style.color={{{ [[Temp]get[color]] }}} onclick="clicked">
<option disabled>Choose a new text</option>
<option>A Tale of Two Cities</option>
<option>A New Kind of Science</option>
<option>The Dice Man</option>
</$select>
+
title: Actions
<$action-setfield $tiddler="Temp" $field="text" $value="Title2" color="red"/>
+
title: Temp
color: black
Title1
+
title: ExpectedResult
<p><select data-title="Title2" value="Choose a new text" style="color:red;"><option disabled="true">Choose a new text</option><option>A Tale of Two Cities</option><option>A New Kind of Science</option><option>The Dice Man</option></select></p>

View File

@@ -1,15 +0,0 @@
title: Widgets/ElementWidgetEventAttributes
description: Element widget should not support event attributes starting with "on"
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<div class="hello" onclick="clicked">
TiddlyWiki
</div>
+
title: ExpectedResult
<p><div class="hello">TiddlyWiki</div></p>

View File

@@ -1,15 +0,0 @@
title: Widgets/ElementWidgetStyleAttributes
description: Element widget should support style.* attributes
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<div class="hello" onclick="clicked" style.color="blue" style.color="red" style.background="yellow">
TiddlyWiki
</div>
+
title: ExpectedResult
<p><div class="hello" style="color:red;background:yellow;">TiddlyWiki</div></p>

View File

@@ -365,7 +365,6 @@ Tests the filtering mechanism.
expect(wiki.filterTiddlers("[sort[title]first[8]]").join(",")).toBe("$:/ShadowPlugin,$:/TiddlerTwo,a fourth tiddler,filter regexp test,has filter,hasList,one,Tiddler Three");
expect(wiki.filterTiddlers("[sort[title]first[x]]").join(",")).toBe("$:/ShadowPlugin");
expect(wiki.filterTiddlers("[sort[title]last[]]").join(",")).toBe("TiddlerOne");
expect(wiki.filterTiddlers("[sort[title]last[0]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[sort[title]last[2]]").join(",")).toBe("Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[sort[title]last[8]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,filter regexp test,has filter,hasList,one,Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[sort[title]last[x]]").join(",")).toBe("TiddlerOne");

View File

@@ -53,11 +53,6 @@ describe("json filter tests", function() {
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[2]]")).toEqual(["true"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[3]]")).toEqual(["false"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[4]]")).toEqual(["null"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[-5]]")).toEqual(["five"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[-4]]")).toEqual(["six"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[-3]]")).toEqual(["true"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[-2]]")).toEqual(["false"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[-1]]")).toEqual(["null"]);
});
it("should support the jsonextract operator", function() {
@@ -75,11 +70,6 @@ describe("json filter tests", function() {
expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[2]]")).toEqual(["true"]);
expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[3]]")).toEqual(["false"]);
expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[4]]")).toEqual(["null"]);
expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[-5]]")).toEqual(['"five"']);
expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[-4]]")).toEqual(['"six"']);
expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[-3]]")).toEqual(["true"]);
expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[-2]]")).toEqual(["false"]);
expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[-1]]")).toEqual(["null"]);
});
it("should support the jsonindexes operator", function() {
@@ -95,11 +85,6 @@ describe("json filter tests", function() {
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[2]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[3]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[4]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[-5]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[-4]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[-3]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[-2]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[-1]]")).toEqual([]);
});
it("should support the jsontype operator", function() {
@@ -116,30 +101,6 @@ describe("json filter tests", function() {
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[2]]")).toEqual(["boolean"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[3]]")).toEqual(["boolean"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[4]]")).toEqual(["null"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[-5]]")).toEqual(["string"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[-4]]")).toEqual(["string"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[-3]]")).toEqual(["boolean"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[-2]]")).toEqual(["boolean"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[-1]]")).toEqual(["null"]);
});
it("should support the jsonset operator", function() {
expect(wiki.filterTiddlers("[{First}jsonset[]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}']);
expect(wiki.filterTiddlers("[{First}jsonset[],[Antelope]]")).toEqual(['"Antelope"']);
expect(wiki.filterTiddlers("[{First}jsonset:number[],[not a number]]")).toEqual(['0']);
expect(wiki.filterTiddlers("[{First}jsonset[id],[Antelope]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":"Antelope"}']);
expect(wiki.filterTiddlers("[{First}jsonset:notatype[id],[Antelope]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":"Antelope"}']);
expect(wiki.filterTiddlers("[{First}jsonset:boolean[id],[false]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":false}']);
expect(wiki.filterTiddlers("[{First}jsonset:boolean[id],[Antelope]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}']);
expect(wiki.filterTiddlers("[{First}jsonset:number[id],[42]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":42}']);
expect(wiki.filterTiddlers("[{First}jsonset:null[id]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":null}']);
expect(wiki.filterTiddlers("[{First}jsonset:array[d],[f],[5]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null,[]]}}']);
expect(wiki.filterTiddlers("[{First}jsonset:object[d],[f],[5]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null,{}]}}']);
expect(wiki.filterTiddlers("[{First}jsonset:number[d],[f],[-1],[42]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,42]}}']);
expect(wiki.filterTiddlers("[{First}jsonset[missing],[id],[Antelope]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}']);
expect(wiki.filterTiddlers("[{First}jsonset:json[\"Antelope\"]]")).toEqual(['"Antelope"']);
expect(wiki.filterTiddlers("[{First}jsonset:json[id],[{\"a\":313}]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":{"a":313}}']);
expect(wiki.filterTiddlers("[{First}jsonset:json[notjson]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}']);
});
it("should support the format:json operator", function() {

View File

@@ -48,29 +48,6 @@ describe("Utility tests", function() {
expect($tw.utils.base64Decode($tw.utils.base64Encode(booksEmoji))).toBe(booksEmoji, "should round-trip correctly");
});
it("should handle base64 encoding emojis in URL-safe variant", function() {
var booksEmoji = "📚";
expect($tw.utils.base64Encode(booksEmoji, false, true)).toBe("8J-Tmg==", "if surrogate pairs are correctly treated as a single code unit then base64 should be 8J+Tmg==");
expect($tw.utils.base64Decode("8J-Tmg==", false, true)).toBe(booksEmoji);
expect($tw.utils.base64Decode($tw.utils.base64Encode(booksEmoji, false, true), false, true)).toBe(booksEmoji, "should round-trip correctly");
});
it("should handle base64 encoding binary data", function() {
var binaryData = "\xff\xfe\xfe\xff";
var encoded = $tw.utils.base64Encode(binaryData,true);
expect(encoded).toBe("//7+/w==");
var decoded = $tw.utils.base64Decode(encoded,true);
expect(decoded).toBe(binaryData, "Binary data did not round-trip correctly");
});
it("should handle base64 encoding binary data in URL-safe variant", function() {
var binaryData = "\xff\xfe\xfe\xff";
var encoded = $tw.utils.base64Encode(binaryData,true,true);
expect(encoded).toBe("__7-_w==");
var decoded = $tw.utils.base64Decode(encoded,true,true);
expect(decoded).toBe(binaryData, "Binary data did not round-trip correctly");
});
it("should handle stringifying a string array", function() {
var str = $tw.utils.stringifyList;
expect(str([])).toEqual("");

View File

@@ -1,20 +0,0 @@
title: 中文社区 - Chinese Community
tags: Community
# A Chinese community tutorial program that people can edit together:
#* Main site: [ext[https://tw-cn.netlify.app/]]
#* Accelerated access: [ext[https://tw-cn.cpolar.top/]]
#* Alternate: [ext[https://tiddly-wiki-chinese-tutorial.vercel.app]]
# Tiddlywiki Chinese Chat Forum: [ext[https://talk.tidgi.fun/topic/6]]
# Chinese translation of Tiddlywiki official website [ext[https://bramchen.github.io/tw5-docs/zh-Hans/]]
# The best Chinese introductory tutorial for newbies [ext[https://keatonlao.github.io/tiddlywiki-xp/]]
---
# 大家可以一起编辑的中文社区教程项目:
#* 主站:[ext[https://tw-cn.netlify.app/]]
#* 加速访问:[ext[https://tw-cn.cpolar.top/]]
#* 备用:[ext[https://tiddly-wiki-chinese-tutorial.vercel.app]]
# 太微中文交流论坛:[ext[https://talk.tidgi.fun/topic/6]]
# 太微官网汉化版:[ext[https://bramchen.github.io/tw5-docs/zh-Hans/]]
# 最适合新手的中文入门教程:[ext[https://keatonlao.github.io/tiddlywiki-xp/]]

View File

@@ -0,0 +1,16 @@
created: 20220417010615742
modified: 20220417011547812
tags: [[Community Plugins]] [[Community Editions]] Resources
title: TiddlyMemo by oflg
type: text/vnd.tiddlywiki
url: https://tiddlymemo.org/
Lifelong knowledge, deep in the Sea of Mind.
{{!!url}}
~TiddlyMemo uses advanced [[Incremental Learning|https://help.supermemo.org/wiki/Incremental_learning]] concepts to make it your powerful second brain for acquiring lifelong knowledge.
* [[Read Articles|https://tiddlymemo.org/#Read%20Articles]] like ~SuperMemo
* [[Learn languages|https://tiddlymemo.org/#Learn%20languages]] like ~LingQ
* [[Memory Notes|https://tiddlymemo.org/#Memory%20Notes]] like Anki

View File

@@ -1,16 +0,0 @@
created: 20220417010615742
modified: 20231005060241771
tags: [[Community Editions]]
title: Tidme by oflg
type: text/vnd.tiddlywiki
url: https://github.com/oflg/Tidme
Lifelong knowledge, deep in Mind.
{{!!url}}
Tidme uses advanced [[Incremental Learning|https://help.supermemo.org/wiki/Incremental_learning]] concepts to make it your powerful second brain for acquiring lifelong knowledge.
* Read Articles like SuperMemo
* Learn languages like LingQ
* Memory Notes like Anki

View File

@@ -1,10 +0,0 @@
created: 20220417010615742
modified: 20231005060241771
tags: [[Community Plugins]]
title: Free Spaced Repetition Scheduler for TiddlyWiki by oflg
type: text/vnd.tiddlywiki
url: https://github.com/open-spaced-repetition/fsrs4tw
TiddlyWiki-based memory programme using advanced FSRS algorithm
{{!!url}}

View File

@@ -1,7 +1,6 @@
caption: decodebase64
op-input: a [[selection of titles|Title Selection]]
op-output: the input with base 64 decoding applied
op-suffix: optional: `binary` to produce binary output, `urlsafe` for URL-safe input
op-parameter:
op-parameter-name:
op-purpose: apply base 64 decoding to a string
@@ -12,10 +11,6 @@ from-version: 5.2.6
See Mozilla Developer Network for details of [[base 64 encoding|https://developer.mozilla.org/en-US/docs/Glossary/Base64]]. TiddlyWiki uses [[library code from @nijikokun|https://gist.github.com/Nijikokun/5192472]] to handle the conversion.
The input strings must be base64 encoded. The output strings are the text (or binary data) decoded from base64 format.
The optional `binary` suffix, if present, changes how the input is processed. The input is normally assumed to be [[UTF-8|https://developer.mozilla.org/en-US/docs/Glossary/UTF-8]] text encoded in base64 form (such as what the <<.op "encodebase64">> operator produces), so only certain byte sequences in the input are valid. If the input is binary data encoded in base64 format (such as an image, audio file, video file, etc.), then use the optional `binary` suffix, which will allow all byte sequences. Note that the output will then be binary, ''not'' text, and should probably not be passed into further filter operators.
The optional `urlsafe` suffix, if present, causes the decoder to assume that the base64 input uses `-` and `_` instead of `+` and `/` for the 62nd and 63rd characters of the base64 "alphabet", which is usually referred to as "URL-safe base64" or "bae64url".
The input strings must be base64 encoded. The output strings are binary data.
<<.operator-examples "decodebase64">>

View File

@@ -1,7 +1,6 @@
caption: encodebase64
op-input: a [[selection of titles|Title Selection]]
op-output: the input with base 64 encoding applied
op-suffix: optional: `binary` to treat input as binary data, `urlsafe` for URL-safe output
op-parameter:
op-parameter-name:
op-purpose: apply base 64 encoding to a string
@@ -12,10 +11,6 @@ from-version: 5.2.6
See Mozilla Developer Network for details of [[base 64 encoding|https://developer.mozilla.org/en-US/docs/Glossary/Base64]]. TiddlyWiki uses [[library code from @nijikokun|https://gist.github.com/Nijikokun/5192472]] to handle the conversion.
The input strings are interpreted as [[UTF-8 encoded|https://developer.mozilla.org/en-US/docs/Glossary/UTF-8]] text (or binary data instead if the `binary` suffix is present). The output strings are base64 encoded.
The optional `binary` suffix, if present, causes the input string to be interpreted as binary data instead of text. Normally, an extra UTF-8 encoding step will be added before the base64 output is produced, so that emojis and other Unicode characters will be encoded correctly. If the input is binary data, such as an image, audio file, video, etc., then the UTF-8 encoding step would produce incorrect results, so using the `binary` suffix causes the UTF-8 encoding step to be skipped.
The optional `urlsafe` suffix, if present, will use the alternate "URL-safe" base64 encoding, where `-` and `_` are used instead of `+` and `/` respectively, allowing the result to be used in URL query parameters or filenames.
The input strings are interpreted as binary data. The output strings are base64 encoded.
<<.operator-examples "encodebase64">>

View File

@@ -53,14 +53,6 @@ The <<.op jsonextract>> operator uses multiple operands to specify the indexes o
[<jsondata>jsonextract[d],[g]] --> {"x":"max","y":"may","z":"maize"}
```
<<.from-version "5.3.2">> Negative indexes into an array are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on:
```
[<jsondata>jsonextract[d],[f],[-1]] --> null
[<jsondata>jsonextract[d],[f],[-2]] --> false
[<jsondata>jsonextract[d],[f],[-4]] --> "six"
```
Indexes can be dynamically composed from variables and transclusions:
```

View File

@@ -51,14 +51,6 @@ The <<.op jsonget>> operator uses multiple operands to specify the indexes of th
[<jsondata>jsonget[d],[f],[0]] --> "five"
```
<<.from-version "5.3.2">> Negative indexes into an array are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on:
```
[<jsondata>jsonget[d],[f],[-1]] --> null
[<jsondata>jsonget[d],[f],[-2]] --> false
[<jsondata>jsonget[d],[f],[-4]] --> "six"
```
Indexes can be dynamically composed from variables and transclusions:
```

View File

@@ -1,104 +0,0 @@
created: 20230915121010948
modified: 20230915121010948
tags: [[Filter Operators]] [[JSON Operators]]
title: jsonset Operator
caption: jsonset
op-purpose: set the value of a property in JSON strings
op-input: a selection of JSON strings
op-parameter: one or more indexes of the property to retrieve and sometimes a value to assign
op-output: the JSON strings with the specified property assigned
<<.from-version "5.3.2">> See [[JSON in TiddlyWiki]] for background.
The <<.op jsonset>> operator is used to set a property value in JSON strings. See also the following related operators:
* <<.olink jsonget>> to retrieve the values of a property in JSON data
* <<.olink jsontype>> to retrieve the type of a JSON value
* <<.olink jsonindexes>> to retrieve the names of the fields of a JSON object, or the indexes of a JSON array
* <<.olink jsonextract>> to retrieve a JSON value as a string of JSON
Properties within a JSON object are identified by a sequence of indexes. In the following example, the value at `[a]` is `one`, and the value at `[d][f][0]` is `five`.
```
{
"a": "one",
"b": "",
"c": "three",
"d": {
"e": "four",
"f": [
"five",
"six",
true,
false,
null
],
"g": {
"x": "max",
"y": "may",
"z": "maize"
}
}
}
```
The following examples assume that this JSON data is contained in a variable called `jsondata`.
The <<.op jsonset>> operator uses multiple operands to specify the indexes of the property to set. When used to assign strings the final operand is interpreted as the value to assign. For example:
```
[<jsondata>jsonset[d],[Jaguar]] --> {"a": "one","b": "","c": "three","d": "Jaguar"}
[<jsondata>jsonset[d],[f],[Panther]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": "Panther","g": {"x": "max","y": "may","z": "maize"}}"}
```
Negative indexes into an array are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on:
```
[<jsondata>jsonset[d],[f],[-1],[Elephant]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": ["five","six",true,false,"Elephant"],"g": {"x": "max","y": "may","z": "maize"}}"}
[<jsondata>jsonset[d],[f],[-2],[Elephant]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": ["five","six",true,"Elephant",null],"g": {"x": "max","y": "may","z": "maize"}}"}
[<jsondata>jsonset[d],[f],[-4],[Elephant]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": ["five","Elephant",true,false,null],"g": {"x": "max","y": "may","z": "maize"}}"}
```
Indexes can be dynamically composed from variables and transclusions:
```
[<jsondata>jsonset<variable>,{!!field},[0],{CurrentResult}]
```
The data type of the value to be assigned to the property can be specified with an optional suffix:
|!Suffix |!Description |
|''string'' |The string is specified as the final operand |
|''boolean'' |The boolean value is true if the final operand is the string "true" and false if the final operand is the string "false". Any other value for the final string results prevents the property from being assigned |
|''number'' |The numeric value is taken from the final operand. Invalid numbers are interpreted as zero |
|''json'' |The JSON string value is taken from the final operand. Invalid JSON prevents the property from being assigned |
|''object'' |An empty object is assigned to the property. The final operand is not used as a value |
|''array'' |An empty array is assigned to the property. The final operand is not used as a value |
|''null'' |The special value null is assigned to the property. The final operand is not used as a value |
For example:
```
Input string:
{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}
[jsonset[]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}
[jsonset[],[Antelope]] --> "Antelope"
[jsonset:number[],[not a number]] --> 0
[jsonset[id],[Antelope]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":"Antelope"}
[jsonset:notatype[id],[Antelope]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":"Antelope"}
[jsonset:boolean[id],[false]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":false}
[jsonset:boolean[id],[Antelope]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}
[jsonset:number[id],[42]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":42}
[jsonset:null[id]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":null}
[jsonset:array[d],[f],[5]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null,[]]}}
[jsonset:object[d],[f],[5]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null,{}]}}
[jsonset[missing],[id],[Antelope]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}
```
A subtlety is that the special case of a single operand sets the value of that operand as the new JSON string, entirely replacing the input object. If that operand is blank, the operation is ignored and no assignment takes place. Thus:
```
[<jsondata>jsonset[Panther]] --> "Panther"
[<jsondata>jsonset[]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": ["five", "six", true, false, null],"g": {"x": "max","y": "may","z": "maize"}}"}
```

View File

@@ -61,14 +61,6 @@ The <<.op jsontype>> operator uses multiple operands to specify the indexes of t
[<jsondata>jsontype[d],[f],[2]] --> "boolean"
```
<<.from-version "5.3.2">> Negative indexes into an array are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on:
```
[<jsondata>jsontype[d],[f],[-1]] --> "null"
[<jsondata>jsontype[d],[f],[-2]] --> "boolean"
[<jsondata>jsontype[d],[f],[-4]] --> "string"
```
Indexes can be dynamically composed from variables and transclusions:
```

View File

@@ -18,11 +18,6 @@ TiddlyWiki lets you choose where to keep your data, guaranteeing that in the dec
<$macrocall $name="flex-card" bordercolor={{!!color}} textcolor={{!!text-color}} backgroundcolor={{!!background-color}} captionField="caption" descriptionField="text"/>
</$list>
</div>
<div class="tc-cards tc-small">
<$link to="中文社区 - Chinese Community" class="tc-btn-big-green tc-card">
中文社区 - Chinese Community
</$link>
</div>
!! ''Find Out More''

View File

@@ -1,6 +1,6 @@
caption: tm-open-window
created: 20160424181447704
modified: 20230831201518773
modified: 20220301162140993
tags: Messages
title: WidgetMessage: tm-open-window
type: text/vnd.tiddlywiki
@@ -20,17 +20,10 @@ The `tm-open-window` [[message|Messages]] opens a tiddler in a new //browser// w
The `tm-open-window` message is best generated with the ActionSendMessageWidget, which in turn is triggered by a widget such as the ButtonWidget. The message is handled by the core itself.
<<.tip """When used with the ActionSendMessageWidget, <<.param 'param'>> becomes <<.param '$param'>>.<br>
Parameters <<.param template>>, <<.param windowTitle>>, <<.param width>>, <<.param height>>, <<.param left>> and <<.param top>> require the ActionSendMessageWidget. """>>
<<.tip """<<.from-version 5.2.2>>
To close a window opened with tm-open-window use [[WidgetMessage: tm-close-window]]<br>
To open a tiddler in more than one new window, use a unique value for <<.param windowID>>
""">>
<<.tip """<<.from-version 5.3.2>>
If the new window is hidden by other windows, clicking the "open" button again will bring it to the foreground and set focus to the new window. This behaviour should be consistent for all browsers now
""">>
<<.tip """When used with the ActionSendMessageWidget, <<.param 'param'>> becomes <<.param '$param'>> """>>
<<.tip """Parameters <<.param template>>, <<.param windowTitle>>, <<.param width>>, <<.param height>>, <<.param left>> and <<.param top>> require the ActionSendMessageWidget.""">>
<<.tip """<<.from-version 5.2.2>> To close a window opened with tm-open-window use [[WidgetMessage: tm-close-window]]""">>
<<.tip """<<.from-version 5.2.2>> To open a tiddler in more than one new window, use a unique value for <<.param windowID>>""">>
<$macrocall $name='wikitext-example-without-html'
src="""

View File

@@ -147,10 +147,6 @@ type: text/vnd.tiddlywiki
gap: 1em;
}
.tc-cards.tc-small {
font-size: 0.7em;
}
.tc-cards.tc-action-card {
text-align: center;
background: none;

View File

@@ -1,6 +1,6 @@
caption: browse
created: 20131024141900000
modified: 20231113093304323
modified: 20200421221304177
tags: Widgets
title: BrowseWidget
type: text/vnd.tiddlywiki
@@ -20,8 +20,6 @@ The content of the <<.wid BrowseWidget>> widget is ignored.
|accept |<<.from-version "5.1.23">> Optional comma delimited [[list of file accepted extensions|https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers]] and/or MIME types |
|message |Optional override of widget message to be generated. The files will be passed in the JavaScript object `event.target.files` |
|param |Optional parameter to be passed with the custom message |
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element |
On iPhone/iPad choosing the multiple option will remove the ability to take photographs/videos directly into TiddlyWiki.

View File

@@ -1,6 +1,6 @@
caption: button
created: 20131024141900000
modified: 20231113093304323
modified: 20220810192251345
tags: Widgets TriggeringWidgets
title: ButtonWidget
type: text/vnd.tiddlywiki
@@ -41,8 +41,6 @@ The content of the `<$button>` widget is displayed within the button.
|aria-label |Optional [[Accessibility]] label |
|tooltip |Optional tooltip |
|class |An optional CSS class name to be assigned to the HTML element|
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element |
|style |An optional CSS style attribute to be assigned to the HTML element |
|tag |An optional html tag to use instead of the default "button" |
|dragTiddler |An optional tiddler title making the button draggable and identifying the payload tiddler. See DraggableWidget for details |

View File

@@ -3,7 +3,7 @@ colors: red orange yellow blue
created: 20131024141900000
fruits: bananas oranges grapes
list: [[CheckboxWidget (tag Mode)]] [[CheckboxWidget (field Mode)]] [[CheckboxWidget (listField Mode)]] [[CheckboxWidget (index Mode)]] [[CheckboxWidget (listIndex Mode)]] [[CheckboxWidget (filter Mode)]] [[CheckboxWidget (indeterminate)]]
modified: 20231113093304323
modified: 20230316192632667
tags: Widgets TriggeringWidgets
title: CheckboxWidget
type: text/vnd.tiddlywiki
@@ -38,7 +38,5 @@ The content of the `<$checkbox>` widget is displayed within an HTML `<label>` el
|<<.attr uncheckactions>> |<<.from-version "5.1.16">> A string containing ActionWidgets to be triggered when the checkbox is unchecked |
|<<.attr checkactions>> |<<.from-version "5.1.20">> A string containing ActionWidgets to be triggered when the checkbox is checked |
|<<.attr disabled>> |<<.from-version "5.1.23">> Optionally disables the checkbox if set to <<.value yes>> (defaults to <<.value no>>)|
|<<.attr data-*>> |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML `<input>` element |
|<<.attr style.*>> |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML `<input>` element |
<<.doc-tabs>>

View File

@@ -1,6 +1,6 @@
caption: draggable
created: 20170406081938627
modified: 20231121101431149
modified: 20220715120213777
tags: Widgets TriggeringWidgets
title: DraggableWidget
type: text/vnd.tiddlywiki
@@ -20,11 +20,10 @@ See DragAndDropMechanism for an overview.
|selector|<<.from-version 5.2.2>> Optional CSS Selector to identify a DOM element within the widget that will be used as the drag handle |
|class |Optional CSS classes to assign to the DOM element created by the widget. The class `tc-draggable` is added to the the DOM element created by the widget unless the <<.param selector>> attribute is used. The class `tc-dragging` is applied to the DOM element created by the widget while the element is being dragged |
|enable |<<.from-version 5.2.3>> Optional value "no" to disable the draggable functionality (defaults to "yes") |
|startactions |Optional action string that gets invoked when dragging ''starts'' |
|endactions |Optional action string that gets invoked when dragging ''ends'' |
|dragimagetype |<<.from-version "5.2.0">> Optional type of drag image: `dom` (the default) or `blank` to disable the drag image |
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element |
Either or both of the ''tiddler'' and ''filter'' attributes must be specified in order for there to be a payload to drag.

View File

@@ -1,6 +1,6 @@
caption: droppable
created: 20170406082820317
modified: 20231121101447359
modified: 20211009122023265
tags: Widgets TriggeringWidgets
title: DroppableWidget
type: text/vnd.tiddlywiki
@@ -31,8 +31,6 @@ See DragAndDropMechanism for an overview.
|class |Optional CSS classes to assign to the draggable element. The class `tc-droppable` is added automatically, and the class `tc-dragover` is applied while an item is being dragged over the droppable element |
|tag |Optional tag to override the default of a "div" element when the widget is rendered in block mode, or a "span" element when it is rendered in inline mode |
|enable |<<.from-version "5.1.22">> Optional value "no" to disable the droppable functionality (defaults to "yes") |
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element |
Within the action string, there are two Variables generated by the DroppableWidget:

View File

@@ -1,6 +1,6 @@
caption: image
created: 20140416160234142
modified: 20231121114351165
modified: 20220721102303815
tags: Widgets
title: ImageWidget
type: text/vnd.tiddlywiki
@@ -21,7 +21,6 @@ Any content of the `<$image>` widget is ignored.
|alt |The alternative text to be associated with the image |
|class |CSS classes to be assigned to the `<img>` element |
|loading|<<.from-version "5.2.3">>Optional. Set to `lazy` to enable lazy loading of images loaded from an external URI |
|usemap|<<.from-version "5.3.2">>Optional usemap attribute to be assigned to the `<img>` element for use with HTML image maps |
The width and the height can be specified as pixel values (eg "23" or "23px") or percentages (eg "23%"). They are both optional; if not provided the browser will use CSS rules to size the image.

View File

@@ -1,6 +1,6 @@
caption: link
created: 20131024141900000
modified: 20231113093304323
modified: 20190610195105615
tags: Widgets
title: LinkWidget
type: text/vnd.tiddlywiki
@@ -23,8 +23,6 @@ The content of the link widget is rendered within the `<a>` tag representing the
|tag |Optional tag to override the default "a" element |
|class|Optional CSS classes //in addition to// the default classes (see below)|
|overrideClass|<<.from-version "5.1.16">> Optional CSS classes //instead of// the default classes |
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element |
The draggable functionality is equivalent to using the DraggableWidget with the ''tiddler'' attribute set to the link target.

View File

@@ -1,6 +1,6 @@
caption: radio
created: 20131212195353929
modified: 20231113093304323
modified: 20211009121654734
tags: Widgets TriggeringWidgets
title: RadioWidget
type: text/vnd.tiddlywiki
@@ -22,8 +22,6 @@ The content of the `<$radio>` widget is displayed within an HTML `<label>` eleme
|class |The CSS classes assigned to the label around the radio button <<.from-version "5.1.14">> `tc-radio` is always applied by default, as well as `tc-radio-selected` when selected |
|actions|<<.from-version "5.1.23">> Optional string containing ActionWidgets to be triggered when the value changes. <br>The variable: ''actionValue'' is available for the actions |
|disabled|<<.from-version "5.1.23">> Optional. Set to "yes" to disable the radio input. Defaults to "no" |
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the `<input>` HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the `<input>` HTML element |
!! Field Mode

View File

@@ -1,6 +1,6 @@
caption: range
created: 20171102134825376
modified: 20231113093304323
modified: 20211009121606405
tags: Widgets TriggeringWidgets
title: RangeWidget
type: text/vnd.tiddlywiki
@@ -26,8 +26,6 @@ The content of the `<$range>` widget is ignored.
|actionsStart|<<.from-version "5.1.23">> Optional, A string containing ~ActionWidgets to be triggered when the "handle" is ''clicked''. <br>The variable: ''actionValueHasChanged'' is always `no` here|
|actionsStop|<<.from-version "5.1.23">> Optional, A string containing ~ActionWidgets to be triggered when the "handle" is ''released''. <br>The variable: ''actionValueHasChanged'' is `yes`, ''if'' the new-value is different to the start-value |
|disabled|<<.from-version "5.1.23">> Optional, disables the range input if set to "yes". Defaults to "no"|
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the `<input>` HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the `<input>` HTML element |
! Examples

View File

@@ -1,6 +1,6 @@
caption: scrollable
created: 20140324223413403
modified: 20230731100903977
modified: 20220620115347910
tags: Widgets
title: ScrollableWidget
type: text/vnd.tiddlywiki
@@ -16,15 +16,12 @@ The content of the `<$scrollable>` widget is displayed within a pair of wrapper
|!Attribute |!Description |
|class |The CSS class(es) to be applied to the outer DIV |
|fallthrough |See below |
|bind |<<.from-version "5.3.2">> Optional title of tiddler to which the scroll position should be bound |
Binding the scroll position to a tiddler automatically copies the scroll coordinates into the `scroll-left` and `scroll-top` fields as scrolling occurs. Conversely, setting those field values will automatically cause the scrollable to scroll if it can.
<$macrocall $name=".note" _="""If a scrollable widget can't handle the `tm-scroll` message because the inner DIV fits within the outer DIV, then by default the message falls through to the parent widget. Setting the ''fallthrough'' attribute to `no` prevents this behaviour."""/>
! Examples
These examples require the following CSS definitions from [[$:/_tw5.com-styles]]:
This example requires the following CSS definitions from [[$:/_tw5.com-styles]]:
```
.tc-scrollable-demo {
@@ -36,8 +33,6 @@ These examples require the following CSS definitions from [[$:/_tw5.com-styles]]
}
```
!! Simple Usage
This wiki text shows how to display a list within the scrollable widget:
<<wikitext-example-without-html "<$scrollable class='tc-scrollable-demo'>
@@ -51,23 +46,3 @@ This wiki text shows how to display a list within the scrollable widget:
</$scrollable>
">>
!! Binding scroll position to a tiddler
[[Current scroll position|$:/my-scroll-position]]: {{$:/my-scroll-position!!scroll-left}}, {{$:/my-scroll-position!!scroll-top}}
<$button>
<$action-setfield $tiddler="$:/my-scroll-position" scroll-left="100" scroll-top="100"/>
Set current scroll position to 100,100
</$button>
<<wikitext-example-without-html "<$scrollable class='tc-scrollable-demo' bind='$:/my-scroll-position'>
<$list filter='[tag[Reference]]'>
<$view field='title'/>: <$list filter='[all[current]links[]sort[title]]' storyview='pop'>
<$link><$view field='title'/></$link>
</$list>
</$list>
</$scrollable>
">>

View File

@@ -1,6 +1,6 @@
caption: select
created: 20131024141900000
modified: 20231113093304323
modified: 20211108165037846
tags: TriggeringWidgets Widgets
title: SelectWidget
type: text/vnd.tiddlywiki
@@ -42,9 +42,6 @@ The content of the `<$select>` widget should be one or more HTML `<option>` or `
|actions |A string containing ActionWidgets to be triggered when the key combination is detected |
|focus |<<.from-version "5.2.4">> Optional. Set to "yes" to automatically focus the HTML select element after creation |
|tabindex |<<.from-version "5.3.1">> Optional. Sets the `tabindex` attribute of the HTML select element to the given value |
|data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element |
|style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element |
! Examples
!! Simple Lists

View File

@@ -6,8 +6,7 @@
"tiddlywiki/railroad",
"tiddlywiki/evernote",
"tiddlywiki/internals",
"tiddlywiki/menubar",
"tiddlywiki/qrcode"
"tiddlywiki/menubar"
],
"themes": [
"tiddlywiki/vanilla",
@@ -54,12 +53,7 @@
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"],
"external-js": [
"--render","$:/core/save/offline-external-js","[[external-]addsuffix<version>addsuffix[.html]]","text/plain",
"--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffix<version>addsuffix[.js]]","text/plain"],
"archive":[
"--render","$:/core/save/all","[[archive/TiddlyWiki-]addsuffix<version>addsuffix[.html]]","text/plain",
"--render","$:/editions/tw5.com/download-empty","[[archive/Empty-TiddlyWiki-]addsuffix<version>addsuffix[.html]]","text/plain",
"--render","[[TiddlyWiki Archive]]","archive/index.html","text/plain","$:/core/templates/static.tiddler.html",
"--render","$:/core/templates/static.template.css","archive/static.css","text/plain"]
"--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffix<version>addsuffix[.js]]","text/plain"]
},
"config": {
"retain-original-tiddler-path": true

View File

@@ -551,5 +551,3 @@ Eric Haberstroh, @pille1842, 2023/07/23
BuckarooBanzay, @BuckarooBanzay, 2023/09/01
Timur, @T1mL3arn, 2023/10/04
Wang Ke, @Gk0Wk, 2023/10/17

View File

@@ -32,9 +32,6 @@
"node": ">=0.8.2"
},
"scripts": {
"dev": "node ./tiddlywiki.js ./editions/tw5.com-server --listen",
"test": "node ./tiddlywiki.js ./editions/test --verbose --version --build index",
"lint:fix": "eslint . --fix",
"lint": "eslint ."
}
}

View File

@@ -1,46 +0,0 @@
const { defineConfig, devices } = require('@playwright/test');
/**
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: './editions/test/',
// Allow parallel tests
fullyParallel: true,
// Prevent accidentally committed "test.only" from wrecking havoc
forbidOnly: !!process.env.CI,
// Do not retry tests on failure
retries: 0,
// How many parallel workers
workers: process.env.CI ? 1 : undefined,
// Reporter to use. See https://playwright.dev/docs/test-reporters
reporter: 'html',
// Settings shared with all the tests
use: {
// Take a screenshot when the test fails
screenshot: {
mode: 'only-on-failure',
fullPage: true
}
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
}
],
});

View File

@@ -56,11 +56,6 @@ name: tiddlywiki
rendering-intent: auto;
}
.tc-tiddler-frame .tc-tiddler-editor .tc-edit-texteditor,
.tc-tiddler-frame .tc-tiddler-editor .tc-tiddler-preview-preview {
overflow: auto;
}
.cm-s-tiddlywiki.CodeMirror, .cm-s-tiddlywiki .CodeMirror-gutters { background-color: <<colour tiddler-editor-background>>; color: <<colour foreground>>; }
.cm-s-tiddlywiki .CodeMirror-gutters {background: <<colour tiddler-editor-background>>; border-right: 1px solid <<colour tiddler-editor-border>>;}
.cm-s-tiddlywiki .CodeMirror-linenumber {color: <<colour foreground>>;}

View File

@@ -23,7 +23,6 @@ The `<$dynannotate>` widget uses the selection tracker to support a popup that d
|filter |Filter identifying the annotation tiddlers applying to this content (see below) |
|actions |Action string to be executed when an annotation is clicked. The variable `annotationTiddler` contains the title of the tiddler corresponding to the annotation that was clicked, and the variable `modifierKey` contains "ctrl", "shift", "ctrl-shift", "normal" according to which modifier keys were pressed |
|popup |Popup state tiddler to be used to trigger a popup when an annotation is clicked |
|floating |Setting to `yes` makes the popup need to be closed explicitly. |
|search |Search text to be highlighted within the widget |
|searchDisplay |"overlay" or "snippet" (see below) |
|searchMode |"literal" (default), "regexp", "whitespace", "words" or "some" (see below) |

View File

@@ -65,7 +65,7 @@ DynannotateWidget.prototype.render = function(parent,nextSibling) {
this.domNodes.push(this.domWrapper);
// Apply the selection tracker data to the DOM
if(!isSnippetMode) {
this.applySelectionTrackerData();
this.applySelectionTrackerData();
}
// Render our child widgets
this.renderChildren(this.domContent,null);
@@ -79,7 +79,7 @@ DynannotateWidget.prototype.render = function(parent,nextSibling) {
// Apply annotations
this.applyAnnotations();
// Apply search overlays
this.applySearch();
this.applySearch();
}
}
// Save the width of the wrapper so that we can tell when it changes
@@ -205,11 +205,10 @@ DynannotateWidget.prototype.applyAnnotations = function() {
if(self.hasAttribute("popup")) {
$tw.popup.triggerPopup({
domNode: domOverlay,
title: self.getAttribute("popup"),
floating: self.getAttribute("floating"),
title: self.getAttribute("popup"),
wiki: self.wiki
});
}
}
};
};
// Draw the overlay for the "target" attribute
@@ -225,7 +224,7 @@ DynannotateWidget.prototype.applyAnnotations = function() {
className: "tc-dynannotation-annotation-overlay",
onclick: clickHandlerFn(null)
});
}
}
}
// Draw the overlays for each annotation tiddler
$tw.utils.each(this.annotationTiddlers,function(title) {
@@ -362,7 +361,7 @@ DynannotateWidget.prototype.applySnippets = function() {
if(!merged) {
container = null;
}
});
});
}
};
@@ -383,7 +382,7 @@ DynannotateWidget.prototype.refresh = function(changedTiddlers) {
var childrenDidRefresh = this.refreshChildren(changedTiddlers);
// Reapply the selection tracker data to the DOM
if(changedAttributes.selection || changedAttributes.selectionPrefix || changedAttributes.selectionSuffix || changedAttributes.selectionPopup) {
this.applySelectionTrackerData();
this.applySelectionTrackerData();
}
// Reapply the annotations if the children refreshed or the main wrapper resized
var wrapperWidth = this.domWrapper.offsetWidth,
@@ -391,14 +390,14 @@ DynannotateWidget.prototype.refresh = function(changedTiddlers) {
oldAnnotationTiddlers = this.annotationTiddlers;
this.getAnnotationTiddlers();
if(!isSnippetMode && (
childrenDidRefresh ||
hasResized ||
changedAttributes.target ||
changedAttributes.targetPrefix ||
changedAttributes.targetSuffix ||
changedAttributes.filter ||
changedAttributes.actions ||
changedAttributes.popup ||
childrenDidRefresh ||
hasResized ||
changedAttributes.target ||
changedAttributes.targetPrefix ||
changedAttributes.targetSuffix ||
changedAttributes.filter ||
changedAttributes.actions ||
changedAttributes.popup ||
!$tw.utils.isArrayEqual(oldAnnotationTiddlers,this.annotationTiddlers) ||
this.annotationTiddlers.find(function(title) {
return changedTiddlers[title];
@@ -407,23 +406,23 @@ DynannotateWidget.prototype.refresh = function(changedTiddlers) {
this.applyAnnotations();
}
if(!isSnippetMode && (
childrenDidRefresh ||
hasResized ||
changedAttributes.search ||
changedAttributes.searchMinLength ||
changedAttributes.searchClass ||
changedAttributes.searchMode ||
childrenDidRefresh ||
hasResized ||
changedAttributes.search ||
changedAttributes.searchMinLength ||
changedAttributes.searchClass ||
changedAttributes.searchMode ||
changedAttributes.searchCaseSensitive
)) {
this.applySearch();
}
if(isSnippetMode && (
childrenDidRefresh ||
hasResized ||
changedAttributes.search ||
changedAttributes.searchMinLength ||
changedAttributes.searchClass ||
changedAttributes.searchMode ||
childrenDidRefresh ||
hasResized ||
changedAttributes.search ||
changedAttributes.searchMinLength ||
changedAttributes.searchClass ||
changedAttributes.searchMode ||
changedAttributes.searchCaseSensitive
)) {
this.applySnippets();

View File

@@ -15,7 +15,6 @@ For details see: https://blog.evernote.com/tech/2013/08/08/evernote-export-forma
"use strict";
// DOMParser = require("$:/plugins/tiddlywiki/xmldom/dom-parser").DOMParser;
var illegalFilenameCharacters = /[\[\]<>;\:\"\/\\\|\?\*\^\?\$\(\)\s~]/g;
/*
Parse an ENEX file into tiddlers
@@ -24,13 +23,10 @@ exports["application/enex+xml"] = function(text,fields) {
// Collect output tiddlers in an array
var results = [];
// Parse the XML document
var doc = new DOMParser().parseFromString(text,"application/xml");
var parser = new DOMParser(),
doc = parser.parseFromString(text,"application/xml");
// Output a report tiddler with information about the import
var enex = doc.querySelector("en-export");
if(!enex) {
// Firefox's DOMParser have problem in some cases.
throw new Error('Failed to parse ENEX file, no "en-export" node found, try use Chrome/Edge to export again.');
}
results.push({
title: "Evernote Import Report",
text: "Evernote file imported on " + enex.getAttribute("export-date") + " from " + enex.getAttribute("application") + " (" + enex.getAttribute("version") + ")"
@@ -38,102 +34,47 @@ exports["application/enex+xml"] = function(text,fields) {
// Get all the "note" nodes
var noteNodes = doc.querySelectorAll("note");
$tw.utils.each(noteNodes,function(noteNode) {
var noteTitle = getTextContent(noteNode,"title");
// get real note content node
var contentNode = noteNode.querySelector("content")
var contentText = (contentNode.textContent || "").replace(/&nbsp;/g, ' ').trim();
if(contentText) {
// The final content will be HTML instead of xml. And we will save it as wikitext, to make wiki syntax work, and remaining HTML will also work.
try {
// may error if content is not valid XML
contentNode = new DOMParser().parseFromString(contentText,"application/xml").querySelector("en-note") || contentNode;
} catch(e) {
// ignore
}
}
// process main content and metadata, and save as wikitext tiddler.
var noteResult = {
title: noteTitle.replace(illegalFilenameCharacters,"_"),
var result = {
title: getTextContent(noteNode,"title"),
type: "text/html",
tags: [],
modified: convertDate(getTextContent(noteNode,"updated") || getTextContent(noteNode,"created")),
modifier: getTextContent(noteNode,"author"),
created: convertDate(getTextContent(noteNode,"created")),
creator: getTextContent(noteNode,"author")
text: getTextContent(noteNode,"content"),
modified: convertDate(getTextContent(noteNode,"created")),
created: convertDate(getTextContent(noteNode,"created"))
};
// process resources (images, PDFs, etc.)
$tw.utils.each(noteNode.querySelectorAll("resource"),function(resourceNode) {
// hash generated by applying https://github.com/vzhd1701/evernote-backup/pull/54
var hash = resourceNode.querySelector("data").getAttribute("hash");
var text = getTextContent(resourceNode,"data");
var mimeType = getTextContent(resourceNode,"mime");
var contentTypeInfo = $tw.config.contentTypeInfo[mimeType] || {extension:""};
var title = getTextContent(resourceNode,"resource-attributes>file-name")
// a few resources don't have title, use hash as fallback
title = title || (hash + contentTypeInfo.extension);
// replace all system reserved characters in title
title = title.replace(illegalFilenameCharacters,"_");
// prefix image title with note title, to avoid name conflicts which is quite common in web-clipped content
title = noteResult.title + "/" + title;
results.push({
title: title,
type: mimeType,
width: getTextContent(resourceNode,"width"),
height: getTextContent(resourceNode,"height"),
text: text,
// give image same modified and modifier as the note, so they can be grouped together in the "Recent"
modified: noteResult.modified,
modifier: noteResult.modifier,
created: noteResult.created,
creator: noteResult.creator
});
if(hash) {
fixAttachmentReference(contentNode, hash, mimeType, title);
}
});
// export mixed content of wikitext and HTML
noteResult.text = contentNode.innerHTML;
// remove all ` xmlns="http://www.w3.org/1999/xhtml"` attributes to save some space
noteResult.text = noteResult.text.replace(/ xmlns="http:\/\/www.w3.org\/1999\/xhtml"/g, "");
$tw.utils.each(noteNode.querySelectorAll("tag"),function(tagNode) {
noteResult.tags.push(tagNode.textContent);
result.tags.push(tagNode.textContent);
});
// If there's an update date, set modifiy date accordingly
var update = getTextContent(noteNode,"updated");
if(update) {
noteResult.modified = convertDate(update);
result.modified = convertDate(update);
}
$tw.utils.each(noteNode.querySelectorAll("note-attributes>*"),function(attrNode) {
noteResult[attrNode.tagName] = attrNode.textContent;
result[attrNode.tagName] = attrNode.textContent;
});
results.push(result);
$tw.utils.each(noteNode.querySelectorAll("resource"),function(resourceNode) {
results.push({
title: getTextContent(resourceNode,"resource-attributes>file-name"),
type: getTextContent(resourceNode,"mime"),
width: getTextContent(resourceNode,"width"),
height: getTextContent(resourceNode,"height"),
text: getTextContent(resourceNode,"data")
});
});
results.push(noteResult);
});
// Return the output tiddlers
return results;
};
function getTextContent(node,selector) {
return (node.querySelector(selector) || {}).textContent || "";
return (node.querySelector(selector) || {}).textContent;
}
function convertDate(isoDate) {
return (isoDate || "").replace("T","").replace("Z","") + "000"
}
function fixAttachmentReference(contentNode, md5Hash, mimeType, name) {
if(!contentNode) return;
var mediaNode = contentNode.querySelector('en-media[hash="' + md5Hash + '"]');
if(!name) {
throw new Error("name is empty for resource hash" + md5Hash);
}
if(!mediaNode) return;
if(mimeType.indexOf("image/") === 0) {
// find en-media node, replace with image syntax
mediaNode.parentNode.replaceChild($tw.utils.domMaker("p", {text: "[img["+ name + "]]"}), mediaNode);
} else {
// For other than image attachments, we make a link to the tiddler
mediaNode.parentNode.replaceChild($tw.utils.domMaker("p", {text: "[["+ name + "]]"}), mediaNode);
}
}
})();

View File

@@ -5,7 +5,6 @@ This plugin contains tool to assist migration of content from Evernote ENEX file
!! Instructions
# Download or save your ENEX file from Evernote
## Use [ext[evernote-backup|https://github.com/vzhd1701/evernote-backup]] to export ENEX file with resource hash, so images can be linked in the note
# Rename the file to have an `.enex` extension
# Drag the file into the TiddlyWiki browser window
## Alternatively, click the "Import" button in the "Tools" sidebar tab

File diff suppressed because one or more lines are too long

View File

@@ -51,7 +51,7 @@ describe("Wiki-based tests", function() {
});
function readMultipleTiddlersTiddler(title) {
var rawTiddlers = $tw.wiki.getTiddlerText(title).split(/\r?\n+\r?\n/mg);
var rawTiddlers = $tw.wiki.getTiddlerText(title).split("\n+\n");
var tiddlers = [];
$tw.utils.each(rawTiddlers,function(rawTiddler) {
var fields = Object.create(null),

View File

@@ -1,4 +1,4 @@
title: $:/plugins/tiddlywiki/qrcode/make/MakeContactQR
title: $:/plugins/tiddlywiki/qrcode/MakeContactQR
tags: $:/tags/MakeQR
caption: Contact

View File

@@ -1,4 +1,4 @@
title: $:/plugins/tiddlywiki/qrcode/make/MakeGenericQR
title: $:/plugins/tiddlywiki/qrcode/MakeGenericQR
tags: $:/tags/MakeQR
caption: Generic

View File

@@ -1,4 +1,4 @@
title: $:/plugins/tiddlywiki/qrcode/make/MakeWifiQR
title: $:/plugins/tiddlywiki/qrcode/MakeWifiQR
tags: $:/tags/MakeQR
caption: Wifi

View File

@@ -1,90 +0,0 @@
/*\
title: $:/plugins/tiddlywiki/qrcode/barcodereader.js
type: application/javascript
module-type: widget
barcodereader widget for reading barcodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var nextID = 0;
var BarCodeReaderWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
BarCodeReaderWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
BarCodeReaderWidget.prototype.render = function(parent,nextSibling) {
var self = this;
this.parentDomNode = parent;
this.computeAttributes();
// Make the child widgets
this.makeChildWidgets();
// Generate an ID for this element
var id = "capture-widget-internal-" + nextID;
nextID += 1;
// Create the DOM node and render children
var domNode = this.document.createElement("div");
domNode.className = "tc-readcode-widget";
domNode.setAttribute("width","300px");
domNode.setAttribute("height","300px");
domNode.id = id;
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
// Setup the qrcode library
if($tw.browser) {
var __Html5QrcodeLibrary__ = require("$:/plugins/tiddlywiki/qrcode/html5-qrcode/html5-qrcode.js").__Html5QrcodeLibrary__;
function onScanSuccess(decodedText, decodedResult) {
self.invokeActionString(self.getAttribute("actionsSuccess",""),self,{},{
format: decodedResult.result.format.formatName,
text: decodedText
});
console.log("Scan result",decodedResult,decodedText);
}
function onScanFailure(errorMessage) {
self.invokeActionString(self.getAttribute("actionsFailure",""),self,{},{
error: errorMessage
});
console.log("Scan error",errorMessage);
}
var html5QrcodeScanner = new __Html5QrcodeLibrary__.Html5QrcodeScanner(
id,
{
fps: 10,
qrbox: 250
}
);
html5QrcodeScanner.render(onScanSuccess,onScanFailure);
}
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
BarCodeReaderWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(),
hasChangedAttributes = $tw.utils.count(changedAttributes) > 0;
if(hasChangedAttributes) {
return this.refreshSelf();
}
return this.refreshChildren(changedTiddlers) || hasChangedAttributes;
};
exports.barcodereader = BarCodeReaderWidget;
})();

View File

@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/qrcode/examples
<<tabs "[all[shadows+tiddlers]tag[$:/tags/MakeQR]!has[draft.of]]" "$:/plugins/tiddlywiki/qrcode/MakeGenericQR">>

View File

@@ -0,0 +1,13 @@
title: $:/plugins/tiddlywiki/qrcode/readme
The QR code plugin provides a macro that enables any text to be rendered as a [[QR code|https://en.wikipedia.org/wiki/QR_code]]. QR codes are a type of 2-dimensional bar code that encodes arbitrary data: text, numbers, links. QR code readers are available or built-in for smartphones, making them a convenient means to transfer information between devices
The QR code plugin adds the following features to TiddlyWiki:
* A new [[makeqr Macro]] that renders specified text as a QR code image that can be displayed or printed
* A new toolbar button that can display several QR code renderings of the content of a tiddler:
** Raw content
** Rendered, formatted content
** URL of tiddler
The QR code plugin is based on the library [[qrcode.js by Zeno Zeng|https://github.com/zenozeng/node-yaqrcode]].

View File

@@ -1,8 +1,8 @@
title: $:/plugins/tiddlywiki/qrcode/docs/qrcode
tags: $:/tags/QRCodeDocs
caption: makeqr Macro
title: $:/plugins/tiddlywiki/qrcode/usage
The ''makeqr'' [[macro|Macros]] converts text data into an image of the corresponding QR code. The image is returned as [[base64-encoded data URI|https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs]].
! `makeqr` Macro
The <<.def makeqr>> [[macro|Macros]] converts text data into an image of the corresponding QR code. The image is returned as [[base64-encoded data URI|https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs]].
!! Parameters

View File

@@ -1,3 +0,0 @@
title: $:/plugins/tiddlywiki/qrcode/docs
<<tabs "[all[shadows+tiddlers]tag[$:/tags/QRCodeDocs]!has[draft.of]]" "$:/plugins/tiddlywiki/qrcode/docs/barcodereader">>

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