mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-17 23:34:50 +00:00
Merge branch 'master' into single-tiddler-mode
This commit is contained in:
commit
9d9b00bdb5
11
.travis.yml
11
.travis.yml
@ -1,5 +1,6 @@
|
||||
language: node_js
|
||||
- "node"
|
||||
node_js:
|
||||
- "12.4.0"
|
||||
|
||||
stages:
|
||||
- name: test
|
||||
@ -20,8 +21,8 @@ jobs:
|
||||
- export TW5_BUILD_DETAILS="Prerelease built from branch '$TRAVIS_BRANCH' at commit $(git rev-parse HEAD) of $(git remote get-url origin) at $(date +'%F %T %Z')"
|
||||
- export TW5_BUILD_MAIN_EDITION='./editions/prerelease'
|
||||
- export TW5_BUILD_OUTPUT='./output/prerelease'
|
||||
- ./bin/build-site.sh
|
||||
- ./bin/travis-push.sh
|
||||
- ./bin/build-site.sh || travis_terminate 1
|
||||
- ./bin/travis-push.sh || travis_terminate 1
|
||||
- stage: build-tiddlywiki-com
|
||||
script:
|
||||
- ./bin/travis-pre-build.sh
|
||||
@ -30,5 +31,5 @@ jobs:
|
||||
- export TW5_BUILD_DETAILS="Built from branch '$TRAVIS_BRANCH' at commit $(git rev-parse HEAD) of $(git remote get-url origin) at $(date +'%F %T %Z')"
|
||||
- export TW5_BUILD_MAIN_EDITION='./editions/tw5.com'
|
||||
- export TW5_BUILD_OUTPUT='./output'
|
||||
- ./bin/build-site.sh
|
||||
- ./bin/travis-push.sh
|
||||
- ./bin/build-site.sh || travis_terminate 1
|
||||
- ./bin/travis-push.sh || travis_terminate 1
|
||||
|
@ -136,6 +136,15 @@ node $TW5_BUILD_TIDDLYWIKI \
|
||||
--build index favicon static \
|
||||
|| exit 1
|
||||
|
||||
# /share.html Custom edition for sharing via the URL
|
||||
node $TW5_BUILD_TIDDLYWIKI \
|
||||
./editions/share \
|
||||
--verbose \
|
||||
--load $TW5_BUILD_OUTPUT/build.tid \
|
||||
--output $TW5_BUILD_OUTPUT \
|
||||
--build share \
|
||||
|| exit 1
|
||||
|
||||
# /upgrade.html Custom edition for performing upgrades
|
||||
node $TW5_BUILD_TIDDLYWIKI \
|
||||
./editions/upgrade \
|
||||
|
@ -2080,9 +2080,9 @@ $tw.loadWikiTiddlers = function(wikiPath,options) {
|
||||
for(var title in $tw.boot.files) {
|
||||
relativePath = path.relative(resolvedWikiPath,$tw.boot.files[title].filepath);
|
||||
output[title] =
|
||||
path.sep === path.posix.sep ?
|
||||
path.sep === "/" ?
|
||||
relativePath :
|
||||
relativePath.split(path.sep).join(path.posix.sep);
|
||||
relativePath.split(path.sep).join("/");
|
||||
}
|
||||
$tw.wiki.addTiddler({title: "$:/config/OriginalTiddlerPaths", type: "application/json", text: JSON.stringify(output)});
|
||||
}
|
||||
@ -2245,6 +2245,7 @@ $tw.boot.startup = function(options) {
|
||||
$tw.utils.registerFileType("application/json","utf8",".json");
|
||||
$tw.utils.registerFileType("application/pdf","base64",".pdf",{flags:["image"]});
|
||||
$tw.utils.registerFileType("application/zip","base64",".zip");
|
||||
$tw.utils.registerFileType("application/x-zip-compressed","base64",".zip");
|
||||
$tw.utils.registerFileType("image/jpeg","base64",[".jpg",".jpeg"],{flags:["image"]});
|
||||
$tw.utils.registerFileType("image/png","base64",".png",{flags:["image"]});
|
||||
$tw.utils.registerFileType("image/gif","base64",".gif",{flags:["image"]});
|
||||
|
@ -107,6 +107,8 @@ Saving/GitService/GitHub/Caption: ~GitHub Saver
|
||||
Saving/GitService/GitHub/Password: Password, OAUTH token, or personal access token (see [[GitHub help page|https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line]] for details)
|
||||
Saving/GitService/GitLab/Caption: ~GitLab Saver
|
||||
Saving/GitService/GitLab/Password: Personal access token for API (see [[GitLab help page|https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html]] for details)
|
||||
Saving/GitService/Gitea/Caption: Gitea Saver
|
||||
Saving/GitService/Gitea/Password: Personal access token for API (via Gitea’s web interface: `Settings | Applications | Generate New Token`)
|
||||
Saving/TiddlySpot/Advanced/Heading: Advanced Settings
|
||||
Saving/TiddlySpot/BackupDir: Backup Directory
|
||||
Saving/TiddlySpot/Backups: Backups
|
||||
|
@ -29,6 +29,8 @@ external-link-foreground-hover: External link foreground hover
|
||||
external-link-foreground-visited: External link foreground visited
|
||||
external-link-foreground: External link foreground
|
||||
foreground: General foreground
|
||||
menubar-background: Menu bar background
|
||||
menubar-foreground: Menu bar foreground
|
||||
message-background: Message box background
|
||||
message-border: Message box border
|
||||
message-foreground: Message box foreground
|
||||
|
@ -35,3 +35,4 @@ title: The unique name of a tiddler
|
||||
toc-link: Suppresses the tiddler's link in a Table of Contents tree if set to: ''no''
|
||||
type: The content type of a tiddler
|
||||
version: Version information for a plugin
|
||||
_is_skinny: If present, indicates that the tiddler text field must be loaded from the server
|
||||
|
@ -28,6 +28,7 @@ Error/Filter: Filter error
|
||||
Error/FilterSyntax: Syntax error in filter expression
|
||||
Error/IsFilterOperator: Filter Error: Unknown operand for the 'is' filter operator
|
||||
Error/LoadingPluginLibrary: Error loading plugin library
|
||||
Error/NetworkErrorAlert: `<h2>''Network Error''</h2>It looks like the connection to the server has been lost. This may indicate a problem with your network connection. Please attempt to restore network connectivity before continuing.<br><br>''Any unsaved changes will be automatically synchronised when connectivity is restored''.`
|
||||
Error/RecursiveTransclusion: Recursive transclusion error in transclude widget
|
||||
Error/RetrievingSkinny: Error retrieving skinny tiddler list
|
||||
Error/SavingToTWEdit: Error saving to TWEdit
|
||||
@ -66,6 +67,7 @@ SystemTiddlers/Include/Prompt: Include system tiddlers
|
||||
TagManager/Colour/Heading: Colour
|
||||
TagManager/Count/Heading: Count
|
||||
TagManager/Icon/Heading: Icon
|
||||
TagManager/Icons/None: None
|
||||
TagManager/Info/Heading: Info
|
||||
TagManager/Tag/Heading: Tag
|
||||
Tiddler/DateFormat: DDth MMM YYYY at hh12:0mmam
|
||||
|
86
core/modules/indexers/backlinks-index.js
Normal file
86
core/modules/indexers/backlinks-index.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*\
|
||||
title: $:/core/modules/indexers/backlinks-indexer.js
|
||||
type: application/javascript
|
||||
module-type: indexer
|
||||
|
||||
Indexes the tiddlers' backlinks
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global modules: false */
|
||||
"use strict";
|
||||
|
||||
|
||||
function BacklinksIndexer(wiki) {
|
||||
this.wiki = wiki;
|
||||
}
|
||||
|
||||
BacklinksIndexer.prototype.init = function() {
|
||||
this.index = null;
|
||||
}
|
||||
|
||||
BacklinksIndexer.prototype.rebuild = function() {
|
||||
this.index = null;
|
||||
}
|
||||
|
||||
BacklinksIndexer.prototype._getLinks = function(tiddler) {
|
||||
var parser = this.wiki.parseText(tiddler.fields.type, tiddler.fields.text, {});
|
||||
if(parser) {
|
||||
return this.wiki.extractLinks(parser.tree);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
BacklinksIndexer.prototype.update = function(updateDescriptor) {
|
||||
if(!this.index) {
|
||||
return;
|
||||
}
|
||||
var newLinks = [],
|
||||
oldLinks = [],
|
||||
self = this;
|
||||
if(updateDescriptor.old.exists) {
|
||||
oldLinks = this._getLinks(updateDescriptor.old.tiddler);
|
||||
}
|
||||
if(updateDescriptor.new.exists) {
|
||||
newLinks = this._getLinks(updateDescriptor.new.tiddler);
|
||||
}
|
||||
|
||||
$tw.utils.each(oldLinks,function(link) {
|
||||
if(self.index[link]) {
|
||||
delete self.index[link][updateDescriptor.old.tiddler.fields.title];
|
||||
}
|
||||
});
|
||||
$tw.utils.each(newLinks,function(link) {
|
||||
if(!self.index[link]) {
|
||||
self.index[link] = Object.create(null);
|
||||
}
|
||||
self.index[link][updateDescriptor.new.tiddler.fields.title] = true;
|
||||
});
|
||||
}
|
||||
|
||||
BacklinksIndexer.prototype.lookup = function(title) {
|
||||
if(!this.index) {
|
||||
this.index = Object.create(null);
|
||||
var self = this;
|
||||
this.wiki.forEachTiddler(function(title,tiddler) {
|
||||
var links = self._getLinks(tiddler);
|
||||
$tw.utils.each(links, function(link) {
|
||||
if(!self.index[link]) {
|
||||
self.index[link] = Object.create(null);
|
||||
}
|
||||
self.index[link][title] = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
if(this.index[title]) {
|
||||
return Object.keys(this.index[title]);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
exports.BacklinksIndexer = BacklinksIndexer;
|
||||
|
||||
})();
|
@ -19,14 +19,15 @@ Information about this macro
|
||||
exports.name = "jsontiddlers";
|
||||
|
||||
exports.params = [
|
||||
{name: "filter"}
|
||||
{name: "filter"},
|
||||
{name: "spaces"}
|
||||
];
|
||||
|
||||
/*
|
||||
Run the macro
|
||||
*/
|
||||
exports.run = function(filter) {
|
||||
return this.wiki.getTiddlersAsJson(filter);
|
||||
exports.run = function(filter,spaces) {
|
||||
return this.wiki.getTiddlersAsJson(filter,spaces);
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -22,14 +22,15 @@ exports.name = "makedatauri";
|
||||
|
||||
exports.params = [
|
||||
{name: "text"},
|
||||
{name: "type"}
|
||||
{name: "type"},
|
||||
{name: "_canonical_uri"}
|
||||
];
|
||||
|
||||
/*
|
||||
Run the macro
|
||||
*/
|
||||
exports.run = function(text,type) {
|
||||
return $tw.utils.makeDataUri(text,type);
|
||||
exports.run = function(text,type,_canonical_uri) {
|
||||
return $tw.utils.makeDataUri(text,type,_canonical_uri);
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -3,7 +3,7 @@ title: $:/core/modules/parsers/binaryparser.js
|
||||
type: application/javascript
|
||||
module-type: parser
|
||||
|
||||
The video parser parses a video tiddler into an embeddable HTML element
|
||||
The binary parser parses a binary tiddler into a warning message and download link
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
@ -13,14 +13,57 @@ The video parser parses a video tiddler into an embeddable HTML element
|
||||
"use strict";
|
||||
|
||||
var BINARY_WARNING_MESSAGE = "$:/core/ui/BinaryWarning";
|
||||
var EXPORT_BUTTON_IMAGE = "$:/core/images/export-button";
|
||||
|
||||
var BinaryParser = function(type,text,options) {
|
||||
this.tree = [{
|
||||
type: "transclude",
|
||||
// Transclude the binary data tiddler warning message
|
||||
var warn = {
|
||||
type: "element",
|
||||
tag: "p",
|
||||
children: [{
|
||||
type: "transclude",
|
||||
attributes: {
|
||||
tiddler: {type: "string", value: BINARY_WARNING_MESSAGE}
|
||||
}
|
||||
}]
|
||||
};
|
||||
// Create download link based on binary tiddler title
|
||||
var link = {
|
||||
type: "element",
|
||||
tag: "a",
|
||||
attributes: {
|
||||
tiddler: {type: "string", value: BINARY_WARNING_MESSAGE}
|
||||
}
|
||||
}];
|
||||
title: {type: "indirect", textReference: "!!title"},
|
||||
download: {type: "indirect", textReference: "!!title"}
|
||||
},
|
||||
children: [{
|
||||
type: "transclude",
|
||||
attributes: {
|
||||
tiddler: {type: "string", value: EXPORT_BUTTON_IMAGE}
|
||||
}
|
||||
}]
|
||||
};
|
||||
// Set the link href to external or internal data URI
|
||||
if(options._canonical_uri) {
|
||||
link.attributes.href = {
|
||||
type: "string",
|
||||
value: options._canonical_uri
|
||||
};
|
||||
} else if(text) {
|
||||
link.attributes.href = {
|
||||
type: "string",
|
||||
value: "data:" + type + ";base64," + text
|
||||
};
|
||||
}
|
||||
// Combine warning message and download link in a div
|
||||
var element = {
|
||||
type: "element",
|
||||
tag: "div",
|
||||
attributes: {
|
||||
class: {type: "string", value: "tc-binary-warning"}
|
||||
},
|
||||
children: [warn, link]
|
||||
}
|
||||
this.tree = [element];
|
||||
};
|
||||
|
||||
exports["application/octet-stream"] = BinaryParser;
|
||||
|
136
core/modules/savers/gitea.js
Normal file
136
core/modules/savers/gitea.js
Normal file
@ -0,0 +1,136 @@
|
||||
/*\
|
||||
title: $:/core/modules/savers/gitea.js
|
||||
type: application/javascript
|
||||
module-type: saver
|
||||
|
||||
Saves wiki by pushing a commit to the gitea
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Select the appropriate saver module and set it up
|
||||
*/
|
||||
var GiteaSaver = function(wiki) {
|
||||
this.wiki = wiki;
|
||||
};
|
||||
|
||||
GiteaSaver.prototype.save = function(text,method,callback) {
|
||||
var self = this,
|
||||
username = this.wiki.getTiddlerText("$:/Gitea/Username"),
|
||||
password = $tw.utils.getPassword("Gitea"),
|
||||
repo = this.wiki.getTiddlerText("$:/Gitea/Repo"),
|
||||
path = this.wiki.getTiddlerText("$:/Gitea/Path",""),
|
||||
filename = this.wiki.getTiddlerText("$:/Gitea/Filename"),
|
||||
branch = this.wiki.getTiddlerText("$:/Gitea/Branch") || "master",
|
||||
endpoint = this.wiki.getTiddlerText("$:/Gitea/ServerURL") || "https://gitea",
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
"Authorization": "Basic " + window.btoa(username + ":" + password)
|
||||
};
|
||||
// Bail if we don't have everything we need
|
||||
if(!username || !password || !repo || !path || !filename) {
|
||||
return false;
|
||||
}
|
||||
// Make sure the path start and ends with a slash
|
||||
if(path.substring(0,1) !== "/") {
|
||||
path = "/" + path;
|
||||
}
|
||||
if(path.substring(path.length - 1) !== "/") {
|
||||
path = path + "/";
|
||||
}
|
||||
// Compose the base URI
|
||||
var uri = endpoint + "/repos/" + repo + "/contents" + path;
|
||||
// Perform a get request to get the details (inc shas) of files in the same path as our file
|
||||
$tw.utils.httpRequest({
|
||||
url: uri,
|
||||
type: "GET",
|
||||
headers: headers,
|
||||
data: {
|
||||
ref: branch
|
||||
},
|
||||
callback: function(err,getResponseDataJson,xhr) {
|
||||
var getResponseData,sha = "";
|
||||
if(err && xhr.status !== 404) {
|
||||
return callback(err);
|
||||
}
|
||||
var use_put = true;
|
||||
if(xhr.status !== 404) {
|
||||
getResponseData = JSON.parse(getResponseDataJson);
|
||||
$tw.utils.each(getResponseData,function(details) {
|
||||
if(details.name === filename) {
|
||||
sha = details.sha;
|
||||
}
|
||||
});
|
||||
if(sha === ""){
|
||||
use_put = false;
|
||||
}
|
||||
}
|
||||
var data = {
|
||||
message: $tw.language.getRawString("ControlPanel/Saving/GitService/CommitMessage"),
|
||||
content: $tw.utils.base64Encode(text),
|
||||
sha: sha
|
||||
};
|
||||
$tw.utils.httpRequest({
|
||||
url: endpoint + "/repos/" + repo + "/branches/" + branch,
|
||||
type: "GET",
|
||||
headers: headers,
|
||||
callback: function(err,getResponseDataJson,xhr) {
|
||||
if(xhr.status === 404) {
|
||||
callback("Please ensure the branch in the Gitea repo exists");
|
||||
}else{
|
||||
data["branch"] = branch;
|
||||
self.upload(uri + filename, use_put?"PUT":"POST", headers, data, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
GiteaSaver.prototype.upload = function(uri,method,headers,data,callback) {
|
||||
$tw.utils.httpRequest({
|
||||
url: uri,
|
||||
type: method,
|
||||
headers: headers,
|
||||
data: JSON.stringify(data),
|
||||
callback: function(err,putResponseDataJson,xhr) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
var putResponseData = JSON.parse(putResponseDataJson);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Information about this saver
|
||||
*/
|
||||
GiteaSaver.prototype.info = {
|
||||
name: "Gitea",
|
||||
priority: 2000,
|
||||
capabilities: ["save", "autosave"]
|
||||
};
|
||||
|
||||
/*
|
||||
Static method that returns true if this saver is capable of working
|
||||
*/
|
||||
exports.canSave = function(wiki) {
|
||||
return true;
|
||||
};
|
||||
|
||||
/*
|
||||
Create an instance of this saver
|
||||
*/
|
||||
exports.create = function(wiki) {
|
||||
return new GiteaSaver(wiki);
|
||||
};
|
||||
|
||||
})();
|
@ -24,7 +24,7 @@ GitHubSaver.prototype.save = function(text,method,callback) {
|
||||
username = this.wiki.getTiddlerText("$:/GitHub/Username"),
|
||||
password = $tw.utils.getPassword("github"),
|
||||
repo = this.wiki.getTiddlerText("$:/GitHub/Repo"),
|
||||
path = this.wiki.getTiddlerText("$:/GitHub/Path"),
|
||||
path = this.wiki.getTiddlerText("$:/GitHub/Path",""),
|
||||
filename = this.wiki.getTiddlerText("$:/GitHub/Filename"),
|
||||
branch = this.wiki.getTiddlerText("$:/GitHub/Branch") || "master",
|
||||
endpoint = this.wiki.getTiddlerText("$:/GitHub/ServerURL") || "https://api.github.com",
|
||||
|
@ -25,7 +25,7 @@ GitLabSaver.prototype.save = function(text,method,callback) {
|
||||
username = this.wiki.getTiddlerText("$:/GitLab/Username"),
|
||||
password = $tw.utils.getPassword("gitlab"),
|
||||
repo = this.wiki.getTiddlerText("$:/GitLab/Repo"),
|
||||
path = this.wiki.getTiddlerText("$:/GitLab/Path"),
|
||||
path = this.wiki.getTiddlerText("$:/GitLab/Path",""),
|
||||
filename = this.wiki.getTiddlerText("$:/GitLab/Filename"),
|
||||
branch = this.wiki.getTiddlerText("$:/GitLab/Branch") || "master",
|
||||
endpoint = this.wiki.getTiddlerText("$:/GitLab/ServerURL") || "https://gitlab.com/api/v4",
|
||||
|
@ -19,22 +19,16 @@ exports.path = /^\/files\/(.+)$/;
|
||||
exports.handler = function(request,response,state) {
|
||||
var path = require("path"),
|
||||
fs = require("fs"),
|
||||
util = require("util");
|
||||
var filename = path.resolve($tw.boot.wikiPath,"files",decodeURIComponent(state.params[0])),
|
||||
util = require("util"),
|
||||
suppliedFilename = decodeURIComponent(state.params[0]),
|
||||
filename = path.resolve($tw.boot.wikiPath,"files",suppliedFilename),
|
||||
extension = path.extname(filename);
|
||||
fs.readFile(filename,function(err,content) {
|
||||
var status,content,type = "text/plain";
|
||||
if(err) {
|
||||
if(err.code === "ENOENT") {
|
||||
status = 404;
|
||||
content = "File '" + filename + "' not found";
|
||||
} else if(err.code === "EACCES") {
|
||||
status = 403;
|
||||
content = "You do not have permission to access the file '" + filename + "'";
|
||||
} else {
|
||||
status = 500;
|
||||
content = err.toString();
|
||||
}
|
||||
console.log("Error accessing file " + filename + ": " + err.toString());
|
||||
status = 404;
|
||||
content = "File '" + suppliedFilename + "' not found";
|
||||
} else {
|
||||
status = 200;
|
||||
content = content;
|
||||
|
@ -3,7 +3,7 @@ title: $:/core/modules/server/routes/get-tiddlers-json.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /recipes/default/tiddlers/tiddlers.json
|
||||
GET /recipes/default/tiddlers/tiddlers.json?filter=<filter>
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
@ -12,23 +12,34 @@ GET /recipes/default/tiddlers/tiddlers.json
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var DEFAULT_FILTER = "[all[tiddlers]!is[system]sort[title]]";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/recipes\/default\/tiddlers.json$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var filter = state.queryParameters.filter || DEFAULT_FILTER;
|
||||
if($tw.wiki.getTiddlerText("$:/config/Server/AllowAllExternalFilters") !== "yes") {
|
||||
if($tw.wiki.getTiddlerText("$:/config/Server/ExternalFilters/" + filter) !== "yes") {
|
||||
console.log("Blocked attempt to GET /recipes/default/tiddlers/tiddlers.json with filter: " + filter);
|
||||
response.writeHead(403);
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
var excludeFields = (state.queryParameters.exclude || "text").split(","),
|
||||
titles = state.wiki.filterTiddlers(filter);
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var tiddlers = [];
|
||||
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
|
||||
var tiddlerFields = {};
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
if(name !== "text") {
|
||||
tiddlerFields[name] = tiddler.getFieldString(name);
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
tiddlers.push(tiddlerFields);
|
||||
$tw.utils.each(titles,function(title) {
|
||||
var tiddler = state.wiki.getTiddler(title);
|
||||
if(tiddler) {
|
||||
var tiddlerFields = tiddler.getFieldStrings({exclude: excludeFields});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
tiddlers.push(tiddlerFields);
|
||||
}
|
||||
});
|
||||
var text = JSON.stringify(tiddlers);
|
||||
response.end(text,"utf8");
|
||||
|
@ -16,7 +16,8 @@ if($tw.node) {
|
||||
var util = require("util"),
|
||||
fs = require("fs"),
|
||||
url = require("url"),
|
||||
path = require("path");
|
||||
path = require("path"),
|
||||
querystring = require("querystring");
|
||||
}
|
||||
|
||||
/*
|
||||
@ -162,6 +163,7 @@ Server.prototype.requestHandler = function(request,response) {
|
||||
state.wiki = self.wiki;
|
||||
state.server = self;
|
||||
state.urlInfo = url.parse(request.url);
|
||||
state.queryParameters = querystring.parse(state.urlInfo.query);
|
||||
// Get the principals authorized to access this resource
|
||||
var authorizationType = this.methodMappings[request.method] || "readers";
|
||||
// Check for the CSRF header if this is a write
|
||||
@ -236,6 +238,7 @@ host: optional host address (falls back to value of "host" variable)
|
||||
prefix: optional prefix (falls back to value of "path-prefix" variable)
|
||||
*/
|
||||
Server.prototype.listen = function(port,host,prefix) {
|
||||
var self = this;
|
||||
// Handle defaults for port and host
|
||||
port = port || this.get("port");
|
||||
host = host || this.get("host");
|
||||
@ -244,19 +247,24 @@ Server.prototype.listen = function(port,host,prefix) {
|
||||
if(parseInt(port,10).toString() !== port) {
|
||||
port = process.env[port] || 8080;
|
||||
}
|
||||
$tw.utils.log("Serving on " + this.protocol + "://" + host + ":" + port + prefix,"brown/orange");
|
||||
$tw.utils.log("(press ctrl-C to exit)","red");
|
||||
// Warn if required plugins are missing
|
||||
if(!$tw.wiki.getTiddler("$:/plugins/tiddlywiki/tiddlyweb") || !$tw.wiki.getTiddler("$:/plugins/tiddlywiki/filesystem")) {
|
||||
$tw.utils.warning("Warning: Plugins required for client-server operation (\"tiddlywiki/filesystem\" and \"tiddlywiki/tiddlyweb\") are missing from tiddlywiki.info file");
|
||||
}
|
||||
// Listen
|
||||
// Create the server
|
||||
var server;
|
||||
if(this.listenOptions) {
|
||||
server = this.transport.createServer(this.listenOptions,this.requestHandler.bind(this));
|
||||
} else {
|
||||
server = this.transport.createServer(this.requestHandler.bind(this));
|
||||
}
|
||||
// Display the port number after we've started listening (the port number might have been specified as zero, in which case we will get an assigned port)
|
||||
server.on("listening",function() {
|
||||
var address = server.address();
|
||||
$tw.utils.log("Serving on " + self.protocol + "://" + address.address + ":" + address.port + prefix,"brown/orange");
|
||||
$tw.utils.log("(press ctrl-C to exit)","red");
|
||||
});
|
||||
// Listen
|
||||
return server.listen(port,host);
|
||||
};
|
||||
|
||||
|
@ -128,7 +128,7 @@ exports.startup = function() {
|
||||
// Set up the syncer object if we've got a syncadaptor
|
||||
if($tw.syncadaptor) {
|
||||
$tw.syncer = new $tw.Syncer({wiki: $tw.wiki, syncadaptor: $tw.syncadaptor});
|
||||
}
|
||||
}
|
||||
// Setup the saver handler
|
||||
$tw.saverHandler = new $tw.SaverHandler({
|
||||
wiki: $tw.wiki,
|
||||
|
@ -3,7 +3,7 @@ title: $:/core/modules/syncer.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
The syncer tracks changes to the store. If a syncadaptor is used then individual tiddlers are synchronised through it. If there is no syncadaptor then the entire wiki is saved via saver modules.
|
||||
The syncer tracks changes to the store and synchronises them to a remote data store represented as a "sync adaptor"
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
@ -23,8 +23,10 @@ Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
|
||||
Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
|
||||
Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
|
||||
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
|
||||
Syncer.prototype.titleSyncThrottleInterval = "$:/config/SyncThrottleInterval";
|
||||
Syncer.prototype.taskTimerInterval = 1 * 1000; // Interval for sync timer
|
||||
Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s...
|
||||
Syncer.prototype.errorRetryInterval = 5 * 1000; // Interval to retry after an error
|
||||
Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s
|
||||
Syncer.prototype.pollTimerInterval = 60 * 1000; // Interval for polling for changes from the adaptor
|
||||
|
||||
@ -36,6 +38,7 @@ wiki: wiki to be synced
|
||||
function Syncer(options) {
|
||||
var self = this;
|
||||
this.wiki = options.wiki;
|
||||
// Save parameters
|
||||
this.syncadaptor = options.syncadaptor;
|
||||
this.disableUI = !!options.disableUI;
|
||||
this.titleIsLoggedIn = options.titleIsLoggedIn || this.titleIsLoggedIn;
|
||||
@ -43,27 +46,60 @@ function Syncer(options) {
|
||||
this.titleSyncFilter = options.titleSyncFilter || this.titleSyncFilter;
|
||||
this.titleSavedNotification = options.titleSavedNotification || this.titleSavedNotification;
|
||||
this.taskTimerInterval = options.taskTimerInterval || this.taskTimerInterval;
|
||||
this.throttleInterval = options.throttleInterval || this.throttleInterval;
|
||||
this.throttleInterval = options.throttleInterval || parseInt(this.wiki.getTiddlerText(this.titleSyncThrottleInterval,""),10) || this.throttleInterval;
|
||||
this.errorRetryInterval = options.errorRetryInterval || this.errorRetryInterval;
|
||||
this.fallbackInterval = options.fallbackInterval || this.fallbackInterval;
|
||||
this.pollTimerInterval = options.pollTimerInterval || parseInt(this.wiki.getTiddlerText(this.titleSyncPollingInterval,""),10) || this.pollTimerInterval;
|
||||
this.logging = "logging" in options ? options.logging : true;
|
||||
// Make a logger
|
||||
this.logger = new $tw.utils.Logger("syncer" + ($tw.browser ? "-browser" : "") + ($tw.node ? "-server" : "") + (this.syncadaptor.name ? ("-" + this.syncadaptor.name) : ""),{
|
||||
colour: "cyan",
|
||||
enable: this.logging
|
||||
});
|
||||
colour: "cyan",
|
||||
enable: this.logging,
|
||||
saveHistory: true
|
||||
});
|
||||
// Make another logger for connection errors
|
||||
this.loggerConnection = new $tw.utils.Logger("syncer" + ($tw.browser ? "-browser" : "") + ($tw.node ? "-server" : "") + (this.syncadaptor.name ? ("-" + this.syncadaptor.name) : "") + "-connection",{
|
||||
colour: "cyan",
|
||||
enable: this.logging
|
||||
});
|
||||
// Ask the syncadaptor to use the main logger
|
||||
if(this.syncadaptor.setLoggerSaveBuffer) {
|
||||
this.syncadaptor.setLoggerSaveBuffer(this.logger);
|
||||
}
|
||||
// Compile the dirty tiddler filter
|
||||
this.filterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(this.titleSyncFilter));
|
||||
// Record information for known tiddlers
|
||||
this.readTiddlerInfo();
|
||||
// Tasks are {type: "load"/"save"/"delete", title:, queueTime:, lastModificationTime:}
|
||||
this.taskQueue = {}; // Hashmap of tasks yet to be performed
|
||||
this.taskInProgress = {}; // Hash of tasks in progress
|
||||
this.titlesToBeLoaded = {}; // Hashmap of titles of tiddlers that need loading from the server
|
||||
this.titlesHaveBeenLazyLoaded = {}; // Hashmap of titles of tiddlers that have already been lazily loaded from the server
|
||||
// Timers
|
||||
this.taskTimerId = null; // Timer for task dispatch
|
||||
this.pollTimerId = null; // Timer for polling server
|
||||
// Number of outstanding requests
|
||||
this.numTasksInProgress = 0;
|
||||
// Listen out for changes to tiddlers
|
||||
this.wiki.addEventListener("change",function(changes) {
|
||||
self.syncToServer(changes);
|
||||
// Filter the changes to just include ones that are being synced
|
||||
var filteredChanges = self.getSyncedTiddlers(function(callback) {
|
||||
$tw.utils.each(changes,function(change,title) {
|
||||
var tiddler = self.wiki.tiddlerExists(title) && self.wiki.getTiddler(title);
|
||||
callback(tiddler,title);
|
||||
});
|
||||
});
|
||||
if(filteredChanges.length > 0) {
|
||||
self.processTaskQueue();
|
||||
} else {
|
||||
// Look for deletions of tiddlers we're already syncing
|
||||
var outstandingDeletion = false
|
||||
$tw.utils.each(changes,function(change,title,object) {
|
||||
if(change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) {
|
||||
outstandingDeletion = true;
|
||||
}
|
||||
});
|
||||
if(outstandingDeletion) {
|
||||
self.processTaskQueue();
|
||||
}
|
||||
}
|
||||
});
|
||||
// Browser event handlers
|
||||
if($tw.browser && !this.disableUI) {
|
||||
@ -86,6 +122,9 @@ function Syncer(options) {
|
||||
$tw.rootWidget.addEventListener("tm-server-refresh",function() {
|
||||
self.handleRefreshEvent();
|
||||
});
|
||||
$tw.rootWidget.addEventListener("tm-copy-syncer-logs-to-clipboard",function() {
|
||||
$tw.utils.copyToClipboard($tw.utils.getSystemInfo() + "\n\nLog:\n" + self.logger.getBuffer());
|
||||
});
|
||||
}
|
||||
// Listen out for lazyLoad events
|
||||
if(!this.disableUI && $tw.wiki.getTiddlerText(this.titleSyncDisableLazyLoading) !== "yes") {
|
||||
@ -100,45 +139,87 @@ function Syncer(options) {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Show a generic network error alert
|
||||
*/
|
||||
Syncer.prototype.displayError = function(msg,err) {
|
||||
if(err === ($tw.language.getString("Error/XMLHttpRequest") + ": 0")) {
|
||||
this.loggerConnection.alert($tw.language.getString("Error/NetworkErrorAlert"));
|
||||
this.logger.log(msg + ":",err);
|
||||
} else {
|
||||
this.logger.alert(msg + ":",err);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Return an array of the tiddler titles that are subjected to syncing
|
||||
*/
|
||||
Syncer.prototype.getSyncedTiddlers = function(source) {
|
||||
return this.filterFn.call(this.wiki,source);
|
||||
};
|
||||
|
||||
/*
|
||||
Return an array of the tiddler titles that are subjected to syncing
|
||||
*/
|
||||
Syncer.prototype.getTiddlerRevision = function(title) {
|
||||
if(this.syncadaptor && this.syncadaptor.getTiddlerRevision) {
|
||||
return this.syncadaptor.getTiddlerRevision(title);
|
||||
} else {
|
||||
return this.wiki.getTiddler(title).fields.revision;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Read (or re-read) the latest tiddler info from the store
|
||||
*/
|
||||
Syncer.prototype.readTiddlerInfo = function() {
|
||||
// Hashmap by title of {revision:,changeCount:,adaptorInfo:}
|
||||
// "revision" is the revision of the tiddler last seen on the server, and "changecount" is the corresponding local changecount
|
||||
this.tiddlerInfo = {};
|
||||
// Record information for known tiddlers
|
||||
var self = this,
|
||||
tiddlers = this.filterFn.call(this.wiki);
|
||||
tiddlers = this.getSyncedTiddlers();
|
||||
$tw.utils.each(tiddlers,function(title) {
|
||||
var tiddler = self.wiki.getTiddler(title);
|
||||
var tiddler = self.wiki.tiddlerExists(title) && self.wiki.getTiddler(title);
|
||||
self.tiddlerInfo[title] = {
|
||||
revision: tiddler.fields.revision,
|
||||
revision: self.getTiddlerRevision(title),
|
||||
adaptorInfo: self.syncadaptor && self.syncadaptor.getTiddlerInfo(tiddler),
|
||||
changeCount: self.wiki.getChangeCount(title),
|
||||
hasBeenLazyLoaded: false
|
||||
changeCount: self.wiki.getChangeCount(title)
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Create an tiddlerInfo structure if it doesn't already exist
|
||||
*/
|
||||
Syncer.prototype.createTiddlerInfo = function(title) {
|
||||
if(!$tw.utils.hop(this.tiddlerInfo,title)) {
|
||||
this.tiddlerInfo[title] = {
|
||||
revision: null,
|
||||
adaptorInfo: {},
|
||||
changeCount: -1,
|
||||
hasBeenLazyLoaded: false
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Checks whether the wiki is dirty (ie the window shouldn't be closed)
|
||||
*/
|
||||
Syncer.prototype.isDirty = function() {
|
||||
return (this.numTasksInQueue() > 0) || (this.numTasksInProgress() > 0);
|
||||
this.logger.log("Checking dirty status");
|
||||
// Check tiddlers that are in the store and included in the filter function
|
||||
var titles = this.getSyncedTiddlers();
|
||||
for(var index=0; index<titles.length; index++) {
|
||||
var title = titles[index],
|
||||
tiddlerInfo = this.tiddlerInfo[title];
|
||||
if(this.wiki.tiddlerExists(title)) {
|
||||
if(tiddlerInfo) {
|
||||
// If the tiddler is known on the server and has been modified locally then it needs to be saved to the server
|
||||
if($tw.wiki.getChangeCount(title) > tiddlerInfo.changeCount) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// If the tiddler isn't known on the server then it needs to be saved to the server
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check tiddlers that are known from the server but not currently in the store
|
||||
titles = Object.keys(this.tiddlerInfo);
|
||||
for(index=0; index<titles.length; index++) {
|
||||
if(!this.wiki.tiddlerExists(titles[index])) {
|
||||
// There must be a pending delete
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -146,23 +227,26 @@ Update the document body with the class "tc-dirty" if the wiki has unsaved/unsyn
|
||||
*/
|
||||
Syncer.prototype.updateDirtyStatus = function() {
|
||||
if($tw.browser && !this.disableUI) {
|
||||
$tw.utils.toggleClass(document.body,"tc-dirty",this.isDirty());
|
||||
var dirty = this.isDirty();
|
||||
$tw.utils.toggleClass(document.body,"tc-dirty",dirty);
|
||||
if(!dirty) {
|
||||
this.loggerConnection.clearAlerts();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Save an incoming tiddler in the store, and updates the associated tiddlerInfo
|
||||
*/
|
||||
Syncer.prototype.storeTiddler = function(tiddlerFields,hasBeenLazyLoaded) {
|
||||
Syncer.prototype.storeTiddler = function(tiddlerFields) {
|
||||
// Save the tiddler
|
||||
var tiddler = new $tw.Tiddler(tiddlerFields);
|
||||
this.wiki.addTiddler(tiddler);
|
||||
// Save the tiddler revision and changeCount details
|
||||
this.tiddlerInfo[tiddlerFields.title] = {
|
||||
revision: tiddlerFields.revision,
|
||||
revision: this.getTiddlerRevision(tiddlerFields.title),
|
||||
adaptorInfo: this.syncadaptor.getTiddlerInfo(tiddler),
|
||||
changeCount: this.wiki.getChangeCount(tiddlerFields.title),
|
||||
hasBeenLazyLoaded: hasBeenLazyLoaded !== undefined ? hasBeenLazyLoaded : true
|
||||
changeCount: this.wiki.getChangeCount(tiddlerFields.title)
|
||||
};
|
||||
};
|
||||
|
||||
@ -176,14 +260,14 @@ Syncer.prototype.getStatus = function(callback) {
|
||||
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous) {
|
||||
if(err) {
|
||||
self.logger.alert(err);
|
||||
return;
|
||||
}
|
||||
// Set the various status tiddlers
|
||||
self.wiki.addTiddler({title: self.titleIsReadOnly,text: isReadOnly ? "yes" : "no"});
|
||||
self.wiki.addTiddler({title: self.titleIsAnonymous,text: isAnonymous ? "yes" : "no"});
|
||||
self.wiki.addTiddler({title: self.titleIsLoggedIn,text: isLoggedIn ? "yes" : "no"});
|
||||
if(isLoggedIn) {
|
||||
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
|
||||
} else {
|
||||
// Set the various status tiddlers
|
||||
self.wiki.addTiddler({title: self.titleIsReadOnly,text: isReadOnly ? "yes" : "no"});
|
||||
self.wiki.addTiddler({title: self.titleIsAnonymous,text: isAnonymous ? "yes" : "no"});
|
||||
self.wiki.addTiddler({title: self.titleIsLoggedIn,text: isLoggedIn ? "yes" : "no"});
|
||||
if(isLoggedIn) {
|
||||
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
|
||||
}
|
||||
}
|
||||
// Invoke the callback
|
||||
if(callback) {
|
||||
@ -199,91 +283,110 @@ Syncer.prototype.getStatus = function(callback) {
|
||||
Synchronise from the server by reading the skinny tiddler list and queuing up loads for any tiddlers that we don't already have up to date
|
||||
*/
|
||||
Syncer.prototype.syncFromServer = function() {
|
||||
if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
|
||||
this.logger.log("Retrieving skinny tiddler list");
|
||||
var self = this;
|
||||
if(this.pollTimerId) {
|
||||
clearTimeout(this.pollTimerId);
|
||||
this.pollTimerId = null;
|
||||
}
|
||||
this.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) {
|
||||
// Trigger the next sync
|
||||
var self = this,
|
||||
cancelNextSync = function() {
|
||||
if(self.pollTimerId) {
|
||||
clearTimeout(self.pollTimerId);
|
||||
self.pollTimerId = null;
|
||||
}
|
||||
},
|
||||
triggerNextSync = function() {
|
||||
self.pollTimerId = setTimeout(function() {
|
||||
self.pollTimerId = null;
|
||||
self.syncFromServer.call(self);
|
||||
},self.pollTimerInterval);
|
||||
// Check for errors
|
||||
};
|
||||
if(this.syncadaptor && this.syncadaptor.getUpdatedTiddlers) {
|
||||
this.logger.log("Retrieving updated tiddler list");
|
||||
cancelNextSync();
|
||||
this.syncadaptor.getUpdatedTiddlers(self,function(err,updates) {
|
||||
triggerNextSync();
|
||||
if(err) {
|
||||
self.logger.alert($tw.language.getString("Error/RetrievingSkinny") + ":",err);
|
||||
self.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
|
||||
return;
|
||||
}
|
||||
if(updates) {
|
||||
$tw.utils.each(updates.modifications,function(title) {
|
||||
self.titlesToBeLoaded[title] = true;
|
||||
});
|
||||
$tw.utils.each(updates.deletions,function(title) {
|
||||
delete self.tiddlerInfo[title];
|
||||
self.logger.log("Deleting tiddler missing from server:",title);
|
||||
self.wiki.deleteTiddler(title);
|
||||
});
|
||||
if(updates.modifications.length > 0 || updates.deletions.length > 0) {
|
||||
self.processTaskQueue();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
|
||||
this.logger.log("Retrieving skinny tiddler list");
|
||||
cancelNextSync();
|
||||
this.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) {
|
||||
triggerNextSync();
|
||||
// Check for errors
|
||||
if(err) {
|
||||
self.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
|
||||
return;
|
||||
}
|
||||
// Keep track of which tiddlers we already know about have been reported this time
|
||||
var previousTitles = Object.keys(self.tiddlerInfo);
|
||||
// Process each incoming tiddler
|
||||
for(var t=0; t<tiddlers.length; t++) {
|
||||
// Get the incoming tiddler fields, and the existing tiddler
|
||||
var tiddlerFields = tiddlers[t],
|
||||
incomingRevision = tiddlerFields.revision + "",
|
||||
tiddler = self.wiki.getTiddler(tiddlerFields.title),
|
||||
tiddler = self.wiki.tiddlerExists(tiddlerFields.title) && self.wiki.getTiddler(tiddlerFields.title),
|
||||
tiddlerInfo = self.tiddlerInfo[tiddlerFields.title],
|
||||
currRevision = tiddlerInfo ? tiddlerInfo.revision : null;
|
||||
currRevision = tiddlerInfo ? tiddlerInfo.revision : null,
|
||||
indexInPreviousTitles = previousTitles.indexOf(tiddlerFields.title);
|
||||
if(indexInPreviousTitles !== -1) {
|
||||
previousTitles.splice(indexInPreviousTitles,1);
|
||||
}
|
||||
// Ignore the incoming tiddler if it's the same as the revision we've already got
|
||||
if(currRevision !== incomingRevision) {
|
||||
// Do a full load if we've already got a fat version of the tiddler
|
||||
if(tiddler && tiddler.fields.text !== undefined) {
|
||||
// Do a full load of this tiddler
|
||||
self.enqueueSyncTask({
|
||||
type: "load",
|
||||
title: tiddlerFields.title
|
||||
});
|
||||
} else {
|
||||
// Load the skinny version of the tiddler
|
||||
self.storeTiddler(tiddlerFields,false);
|
||||
// Only load the skinny version if we don't already have a fat version of the tiddler
|
||||
if(!tiddler || tiddler.fields.text === undefined) {
|
||||
self.storeTiddler(tiddlerFields);
|
||||
}
|
||||
// Do a full load of this tiddler
|
||||
self.titlesToBeLoaded[tiddlerFields.title] = true;
|
||||
}
|
||||
}
|
||||
// Delete any tiddlers that were previously reported but missing this time
|
||||
$tw.utils.each(previousTitles,function(title) {
|
||||
delete self.tiddlerInfo[title];
|
||||
self.logger.log("Deleting tiddler missing from server:",title);
|
||||
self.wiki.deleteTiddler(title);
|
||||
});
|
||||
self.processTaskQueue();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Synchronise a set of changes to the server
|
||||
Force load a tiddler from the server
|
||||
*/
|
||||
Syncer.prototype.syncToServer = function(changes) {
|
||||
var self = this,
|
||||
now = Date.now(),
|
||||
filteredChanges = this.filterFn.call(this.wiki,function(callback) {
|
||||
$tw.utils.each(changes,function(change,title) {
|
||||
var tiddler = self.wiki.getTiddler(title);
|
||||
callback(tiddler,title);
|
||||
});
|
||||
});
|
||||
$tw.utils.each(changes,function(change,title,object) {
|
||||
// Process the change if it is a deletion of a tiddler we're already syncing, or is on the filtered change list
|
||||
if((change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) || filteredChanges.indexOf(title) !== -1) {
|
||||
// Queue a task to sync this tiddler
|
||||
self.enqueueSyncTask({
|
||||
type: change.deleted ? "delete" : "save",
|
||||
title: title
|
||||
});
|
||||
}
|
||||
});
|
||||
Syncer.prototype.enqueueLoadTiddler = function(title) {
|
||||
this.titlesToBeLoaded[title] = true;
|
||||
this.processTaskQueue();
|
||||
};
|
||||
|
||||
/*
|
||||
Lazily load a skinny tiddler if we can
|
||||
*/
|
||||
Syncer.prototype.handleLazyLoadEvent = function(title) {
|
||||
// Ignore if the syncadaptor doesn't handle it
|
||||
if(!this.syncadaptor.supportsLazyLoading) {
|
||||
return;
|
||||
}
|
||||
// Don't lazy load the same tiddler twice
|
||||
var info = this.tiddlerInfo[title];
|
||||
if(!info || !info.hasBeenLazyLoaded) {
|
||||
if(!this.titlesHaveBeenLazyLoaded[title]) {
|
||||
// Don't lazy load if the tiddler isn't included in the sync filter
|
||||
if(this.filterFn.call(this.wiki).indexOf(title) !== -1) {
|
||||
this.createTiddlerInfo(title);
|
||||
this.tiddlerInfo[title].hasBeenLazyLoaded = true;
|
||||
// Queue up a sync task to load this tiddler
|
||||
this.enqueueSyncTask({
|
||||
type: "load",
|
||||
title: title
|
||||
});
|
||||
if(this.getSyncedTiddlers().indexOf(title) !== -1) {
|
||||
// Mark the tiddler as needing loading, and having already been lazily loaded
|
||||
this.titlesToBeLoaded[title] = true;
|
||||
this.titlesHaveBeenLazyLoaded[title] = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -294,7 +397,7 @@ Dispay a password prompt and allow the user to login
|
||||
Syncer.prototype.handleLoginEvent = function() {
|
||||
var self = this;
|
||||
this.getStatus(function(err,isLoggedIn,username) {
|
||||
if(!isLoggedIn) {
|
||||
if(!err && !isLoggedIn) {
|
||||
$tw.passwordPrompt.createPrompt({
|
||||
serviceName: $tw.language.getString("LoginToTiddlySpace"),
|
||||
callback: function(data) {
|
||||
@ -324,7 +427,7 @@ Syncer.prototype.login = function(username,password,callback) {
|
||||
}
|
||||
self.getStatus(function(err,isLoggedIn,username) {
|
||||
if(callback) {
|
||||
callback(null,isLoggedIn);
|
||||
callback(err,isLoggedIn);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -358,189 +461,179 @@ Syncer.prototype.handleRefreshEvent = function() {
|
||||
};
|
||||
|
||||
/*
|
||||
Queue up a sync task. If there is already a pending task for the tiddler, just update the last modification time
|
||||
Process the next task
|
||||
*/
|
||||
Syncer.prototype.enqueueSyncTask = function(task) {
|
||||
var self = this,
|
||||
now = Date.now();
|
||||
// Set the timestamps on this task
|
||||
task.queueTime = now;
|
||||
task.lastModificationTime = now;
|
||||
// Fill in some tiddlerInfo if the tiddler is one we haven't seen before
|
||||
this.createTiddlerInfo(task.title);
|
||||
// Bail if this is a save and the tiddler is already at the changeCount that the server has
|
||||
if(task.type === "save" && this.wiki.getChangeCount(task.title) <= this.tiddlerInfo[task.title].changeCount) {
|
||||
return;
|
||||
}
|
||||
// Check if this tiddler is already in the queue
|
||||
if($tw.utils.hop(this.taskQueue,task.title)) {
|
||||
// this.logger.log("Re-queueing up sync task with type:",task.type,"title:",task.title);
|
||||
var existingTask = this.taskQueue[task.title];
|
||||
// If so, just update the last modification time
|
||||
existingTask.lastModificationTime = task.lastModificationTime;
|
||||
// If the new task is a save then we upgrade the existing task to a save. Thus a pending load is turned into a save if the tiddler changes locally in the meantime. But a pending save is not modified to become a load
|
||||
if(task.type === "save" || task.type === "delete") {
|
||||
existingTask.type = task.type;
|
||||
Syncer.prototype.processTaskQueue = function() {
|
||||
var self = this;
|
||||
// Only process a task if the sync adaptor is fully initialised and we're not already performing
|
||||
// a task. If we are already performing a task then we'll dispatch the next one when it completes
|
||||
if((!this.syncadaptor.isReady || this.syncadaptor.isReady()) && this.numTasksInProgress === 0) {
|
||||
// Choose the next task to perform
|
||||
var task = this.chooseNextTask();
|
||||
// Perform the task if we had one
|
||||
if(typeof task === "object" && task !== null) {
|
||||
this.numTasksInProgress += 1;
|
||||
task.run(function(err) {
|
||||
self.numTasksInProgress -= 1;
|
||||
if(err) {
|
||||
self.displayError("Sync error while processing " + task.type + " of '" + task.title + "'",err);
|
||||
self.updateDirtyStatus();
|
||||
self.triggerTimeout(self.errorRetryInterval);
|
||||
} else {
|
||||
self.updateDirtyStatus();
|
||||
// Process the next task
|
||||
self.processTaskQueue.call(self);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No task is ready so update the status
|
||||
this.updateDirtyStatus();
|
||||
// And trigger a timeout if there is a pending task
|
||||
if(task === true) {
|
||||
this.triggerTimeout();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this.logger.log("Queuing up sync task with type:",task.type,"title:",task.title);
|
||||
// If it is not in the queue, insert it
|
||||
this.taskQueue[task.title] = task;
|
||||
this.updateDirtyStatus();
|
||||
this.updateDirtyStatus();
|
||||
}
|
||||
// Process the queue
|
||||
$tw.utils.nextTick(function() {self.processTaskQueue.call(self);});
|
||||
};
|
||||
|
||||
/*
|
||||
Return the number of tasks in progress
|
||||
*/
|
||||
Syncer.prototype.numTasksInProgress = function() {
|
||||
return $tw.utils.count(this.taskInProgress);
|
||||
};
|
||||
|
||||
/*
|
||||
Return the number of tasks in the queue
|
||||
*/
|
||||
Syncer.prototype.numTasksInQueue = function() {
|
||||
return $tw.utils.count(this.taskQueue);
|
||||
};
|
||||
|
||||
/*
|
||||
Trigger a timeout if one isn't already outstanding
|
||||
*/
|
||||
Syncer.prototype.triggerTimeout = function() {
|
||||
Syncer.prototype.triggerTimeout = function(interval) {
|
||||
var self = this;
|
||||
if(!this.taskTimerId) {
|
||||
this.taskTimerId = setTimeout(function() {
|
||||
self.taskTimerId = null;
|
||||
self.processTaskQueue.call(self);
|
||||
},self.taskTimerInterval);
|
||||
},interval || self.taskTimerInterval);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Process the task queue, performing the next task if appropriate
|
||||
*/
|
||||
Syncer.prototype.processTaskQueue = function() {
|
||||
var self = this;
|
||||
// Only process a task if the sync adaptor is fully initialised and we're not already performing a task. If we are already performing a task then we'll dispatch the next one when it completes
|
||||
if((!this.syncadaptor.isReady || this.syncadaptor.isReady()) && this.numTasksInProgress() === 0) {
|
||||
// Choose the next task to perform
|
||||
var task = this.chooseNextTask();
|
||||
// Perform the task if we had one
|
||||
if(task) {
|
||||
// Remove the task from the queue and add it to the in progress list
|
||||
delete this.taskQueue[task.title];
|
||||
this.taskInProgress[task.title] = task;
|
||||
this.updateDirtyStatus();
|
||||
// Dispatch the task
|
||||
this.dispatchTask(task,function(err) {
|
||||
if(err) {
|
||||
self.logger.alert("Sync error while processing '" + task.title + "':\n" + err);
|
||||
}
|
||||
// Mark that this task is no longer in progress
|
||||
delete self.taskInProgress[task.title];
|
||||
self.updateDirtyStatus();
|
||||
// Process the next task
|
||||
self.processTaskQueue.call(self);
|
||||
});
|
||||
} else {
|
||||
// Make sure we've set a time if there wasn't a task to perform, but we've still got tasks in the queue
|
||||
if(this.numTasksInQueue() > 0) {
|
||||
this.triggerTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Choose the next sync task. We prioritise saves, then deletes, then loads from the server
|
||||
|
||||
/*
|
||||
Choose the next applicable task
|
||||
Returns either a task object, null if there's no upcoming tasks, or the boolean true if there are pending tasks that aren't yet due
|
||||
*/
|
||||
Syncer.prototype.chooseNextTask = function() {
|
||||
var self = this,
|
||||
candidateTask = null,
|
||||
now = Date.now();
|
||||
// Select the best candidate task
|
||||
$tw.utils.each(this.taskQueue,function(task,title) {
|
||||
// Exclude the task if there's one of the same name in progress
|
||||
if($tw.utils.hop(self.taskInProgress,title)) {
|
||||
return;
|
||||
var thresholdLastSaved = (new Date()) - this.throttleInterval,
|
||||
havePending = null;
|
||||
// First we look for tiddlers that have been modified locally and need saving back to the server
|
||||
var titles = this.getSyncedTiddlers();
|
||||
for(var index=0; index<titles.length; index++) {
|
||||
var title = titles[index],
|
||||
tiddler = this.wiki.tiddlerExists(title) && this.wiki.getTiddler(title),
|
||||
tiddlerInfo = this.tiddlerInfo[title];
|
||||
if(tiddler) {
|
||||
// If the tiddler is not known on the server, or has been modified locally no more recently than the threshold then it needs to be saved to the server
|
||||
var hasChanged = !tiddlerInfo || $tw.wiki.getChangeCount(title) > tiddlerInfo.changeCount,
|
||||
isReadyToSave = !tiddlerInfo || !tiddlerInfo.timestampLastSaved || tiddlerInfo.timestampLastSaved < thresholdLastSaved;
|
||||
if(hasChanged) {
|
||||
if(isReadyToSave) {
|
||||
return new SaveTiddlerTask(this,title);
|
||||
} else {
|
||||
havePending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Exclude the task if it is a save and the tiddler has been modified recently, but not hit the fallback time
|
||||
if(task.type === "save" && (now - task.lastModificationTime) < self.throttleInterval &&
|
||||
(now - task.queueTime) < self.fallbackInterval) {
|
||||
return;
|
||||
}
|
||||
// Second, we check tiddlers that are known from the server but not currently in the store, and so need deleting on the server
|
||||
titles = Object.keys(this.tiddlerInfo);
|
||||
for(index=0; index<titles.length; index++) {
|
||||
title = titles[index];
|
||||
tiddlerInfo = this.tiddlerInfo[title];
|
||||
tiddler = this.wiki.tiddlerExists(title) && this.wiki.getTiddler(title);
|
||||
if(!tiddler) {
|
||||
return new DeleteTiddlerTask(this,title);
|
||||
}
|
||||
// Exclude the task if it is newer than the current best candidate
|
||||
if(candidateTask && candidateTask.queueTime < task.queueTime) {
|
||||
return;
|
||||
}
|
||||
// Now this is our best candidate
|
||||
candidateTask = task;
|
||||
});
|
||||
return candidateTask;
|
||||
}
|
||||
// Check for tiddlers that need loading
|
||||
title = Object.keys(this.titlesToBeLoaded)[0];
|
||||
if(title) {
|
||||
delete this.titlesToBeLoaded[title];
|
||||
return new LoadTiddlerTask(this,title);
|
||||
}
|
||||
// No tasks are ready
|
||||
return havePending;
|
||||
};
|
||||
|
||||
/*
|
||||
Dispatch a task and invoke the callback
|
||||
*/
|
||||
Syncer.prototype.dispatchTask = function(task,callback) {
|
||||
var self = this;
|
||||
if(task.type === "save") {
|
||||
var changeCount = this.wiki.getChangeCount(task.title),
|
||||
tiddler = this.wiki.getTiddler(task.title);
|
||||
this.logger.log("Dispatching 'save' task:",task.title);
|
||||
if(tiddler) {
|
||||
this.syncadaptor.saveTiddler(tiddler,function(err,adaptorInfo,revision) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
// Adjust the info stored about this tiddler
|
||||
self.tiddlerInfo[task.title] = {
|
||||
changeCount: changeCount,
|
||||
adaptorInfo: adaptorInfo,
|
||||
revision: revision
|
||||
};
|
||||
// Invoke the callback
|
||||
callback(null);
|
||||
},{
|
||||
tiddlerInfo: self.tiddlerInfo[task.title]
|
||||
});
|
||||
} else {
|
||||
this.logger.log(" Not Dispatching 'save' task:",task.title,"tiddler does not exist");
|
||||
return callback(null);
|
||||
}
|
||||
} else if(task.type === "load") {
|
||||
// Load the tiddler
|
||||
this.logger.log("Dispatching 'load' task:",task.title);
|
||||
this.syncadaptor.loadTiddler(task.title,function(err,tiddlerFields) {
|
||||
function SaveTiddlerTask(syncer,title) {
|
||||
this.syncer = syncer;
|
||||
this.title = title;
|
||||
this.type = "save";
|
||||
}
|
||||
|
||||
SaveTiddlerTask.prototype.run = function(callback) {
|
||||
var self = this,
|
||||
changeCount = this.syncer.wiki.getChangeCount(this.title),
|
||||
tiddler = this.syncer.wiki.tiddlerExists(this.title) && this.syncer.wiki.getTiddler(this.title);
|
||||
this.syncer.logger.log("Dispatching 'save' task:",this.title);
|
||||
if(tiddler) {
|
||||
this.syncer.syncadaptor.saveTiddler(tiddler,function(err,adaptorInfo,revision) {
|
||||
// If there's an error, exit without changing any internal state
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
// Store the tiddler
|
||||
if(tiddlerFields) {
|
||||
self.storeTiddler(tiddlerFields,true);
|
||||
}
|
||||
// Adjust the info stored about this tiddler
|
||||
self.syncer.tiddlerInfo[self.title] = {
|
||||
changeCount: changeCount,
|
||||
adaptorInfo: adaptorInfo,
|
||||
revision: revision,
|
||||
timestampLastSaved: new Date()
|
||||
};
|
||||
// Invoke the callback
|
||||
callback(null);
|
||||
});
|
||||
} else if(task.type === "delete") {
|
||||
// Delete the tiddler
|
||||
this.logger.log("Dispatching 'delete' task:",task.title);
|
||||
this.syncadaptor.deleteTiddler(task.title,function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
delete self.tiddlerInfo[task.title];
|
||||
// Invoke the callback
|
||||
callback(null);
|
||||
},{
|
||||
tiddlerInfo: self.tiddlerInfo[task.title]
|
||||
});
|
||||
} else {
|
||||
this.syncer.logger.log(" Not Dispatching 'save' task:",this.title,"tiddler does not exist");
|
||||
$tw.utils.nextTick(callback(null));
|
||||
}
|
||||
};
|
||||
|
||||
function DeleteTiddlerTask(syncer,title) {
|
||||
this.syncer = syncer;
|
||||
this.title = title;
|
||||
this.type = "delete";
|
||||
}
|
||||
|
||||
DeleteTiddlerTask.prototype.run = function(callback) {
|
||||
var self = this;
|
||||
this.syncer.logger.log("Dispatching 'delete' task:",this.title);
|
||||
this.syncer.syncadaptor.deleteTiddler(this.title,function(err) {
|
||||
// If there's an error, exit without changing any internal state
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
// Remove the info stored about this tiddler
|
||||
delete self.syncer.tiddlerInfo[self.title];
|
||||
// Invoke the callback
|
||||
callback(null);
|
||||
},{
|
||||
tiddlerInfo: self.syncer.tiddlerInfo[this.title]
|
||||
});
|
||||
};
|
||||
|
||||
function LoadTiddlerTask(syncer,title) {
|
||||
this.syncer = syncer;
|
||||
this.title = title;
|
||||
this.type = "load";
|
||||
}
|
||||
|
||||
LoadTiddlerTask.prototype.run = function(callback) {
|
||||
var self = this;
|
||||
this.syncer.logger.log("Dispatching 'load' task:",this.title);
|
||||
this.syncer.syncadaptor.loadTiddler(this.title,function(err,tiddlerFields) {
|
||||
// If there's an error, exit without changing any internal state
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
// Update the info stored about this tiddler
|
||||
if(tiddlerFields) {
|
||||
self.syncer.storeTiddler(tiddlerFields);
|
||||
}
|
||||
// Invoke the callback
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
exports.Syncer = Syncer;
|
||||
|
||||
})();
|
||||
|
@ -22,6 +22,7 @@ A quick and dirty HTTP function; to be refactored later. Options are:
|
||||
*/
|
||||
exports.httpRequest = function(options) {
|
||||
var type = options.type || "GET",
|
||||
url = options.url,
|
||||
headers = options.headers || {accept: "application/json"},
|
||||
returnProp = options.returnProp || "responseText",
|
||||
request = new XMLHttpRequest(),
|
||||
@ -36,7 +37,11 @@ exports.httpRequest = function(options) {
|
||||
$tw.utils.each(options.data,function(dataItem,dataItemTitle) {
|
||||
results.push(dataItemTitle + "=" + encodeURIComponent(dataItem));
|
||||
});
|
||||
data = results.join("&");
|
||||
if(type === "GET" || type === "HEAD") {
|
||||
url += "?" + results.join("&");
|
||||
} else {
|
||||
data = results.join("&");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set up the state change handler
|
||||
@ -52,7 +57,7 @@ exports.httpRequest = function(options) {
|
||||
}
|
||||
};
|
||||
// Make the request
|
||||
request.open(type,options.url,true);
|
||||
request.open(type,url,true);
|
||||
if(headers) {
|
||||
$tw.utils.each(headers,function(header,headerTitle,object) {
|
||||
request.setRequestHeader(headerTitle,header);
|
||||
|
@ -58,6 +58,9 @@ Modal.prototype.display = function(title,options) {
|
||||
this.adjustPageClass();
|
||||
// Add classes
|
||||
$tw.utils.addClass(wrapper,"tc-modal-wrapper");
|
||||
if(tiddler.fields && tiddler.fields.class) {
|
||||
$tw.utils.addClass(wrapper,tiddler.fields.class);
|
||||
}
|
||||
$tw.utils.addClass(modalBackdrop,"tc-modal-backdrop");
|
||||
$tw.utils.addClass(modalWrapper,"tc-modal");
|
||||
$tw.utils.addClass(modalHeader,"tc-modal-header");
|
||||
@ -104,7 +107,7 @@ Modal.prototype.display = function(title,options) {
|
||||
modalBody.appendChild(modalLink);
|
||||
}
|
||||
// Render the footer of the message
|
||||
if(tiddler && tiddler.fields && tiddler.fields.help) {
|
||||
if(tiddler.fields && tiddler.fields.help) {
|
||||
var link = this.srcDocument.createElement("a");
|
||||
link.setAttribute("href",tiddler.fields.help);
|
||||
link.setAttribute("target","_blank");
|
||||
|
@ -22,15 +22,42 @@ function Logger(componentName,options) {
|
||||
this.componentName = componentName || "";
|
||||
this.colour = options.colour || "white";
|
||||
this.enable = "enable" in options ? options.enable : true;
|
||||
this.save = "save" in options ? options.save : true;
|
||||
this.saveLimit = options.saveLimit || 100 * 1024;
|
||||
this.saveBufferLogger = this;
|
||||
this.buffer = "";
|
||||
this.alertCount = 0;
|
||||
}
|
||||
|
||||
Logger.prototype.setSaveBuffer = function(logger) {
|
||||
this.saveBufferLogger = logger;
|
||||
};
|
||||
|
||||
/*
|
||||
Log a message
|
||||
*/
|
||||
Logger.prototype.log = function(/* args */) {
|
||||
if(this.enable && console !== undefined && console.log !== undefined) {
|
||||
return Function.apply.call(console.log, console, [$tw.utils.terminalColour(this.colour),this.componentName + ":"].concat(Array.prototype.slice.call(arguments,0)).concat($tw.utils.terminalColour()));
|
||||
}
|
||||
var self = this;
|
||||
if(this.enable) {
|
||||
if(this.saveBufferLogger.save) {
|
||||
this.saveBufferLogger.buffer += $tw.utils.formatDateString(new Date(),"YYYY MM DD 0hh:0mm:0ss.0XXX") + ":";
|
||||
$tw.utils.each(Array.prototype.slice.call(arguments,0),function(arg,index) {
|
||||
self.saveBufferLogger.buffer += " " + arg;
|
||||
});
|
||||
this.saveBufferLogger.buffer += "\n";
|
||||
this.saveBufferLogger.buffer = this.saveBufferLogger.buffer.slice(-this.saveBufferLogger.saveLimit);
|
||||
}
|
||||
if(console !== undefined && console.log !== undefined) {
|
||||
return Function.apply.call(console.log, console, [$tw.utils.terminalColour(this.colour),this.componentName + ":"].concat(Array.prototype.slice.call(arguments,0)).concat($tw.utils.terminalColour()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Read the message buffer
|
||||
*/
|
||||
Logger.prototype.getBuffer = function() {
|
||||
return this.saveBufferLogger.buffer;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -70,6 +97,7 @@ Logger.prototype.alert = function(/* args */) {
|
||||
component: this.componentName
|
||||
};
|
||||
existingCount = 0;
|
||||
this.alertCount += 1;
|
||||
}
|
||||
alertFields.modified = new Date();
|
||||
if(++existingCount > 1) {
|
||||
@ -87,6 +115,22 @@ Logger.prototype.alert = function(/* args */) {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Clear outstanding alerts
|
||||
*/
|
||||
Logger.prototype.clearAlerts = function() {
|
||||
var self = this;
|
||||
if($tw.browser && this.alertCount > 0) {
|
||||
$tw.utils.each($tw.wiki.getTiddlersWithTag(ALERT_TAG),function(title) {
|
||||
var tiddler = $tw.wiki.getTiddler(title);
|
||||
if(tiddler.fields.component === self.componentName) {
|
||||
$tw.wiki.deleteTiddler(title);
|
||||
}
|
||||
});
|
||||
this.alertCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
exports.Logger = Logger;
|
||||
|
||||
})();
|
||||
|
@ -728,16 +728,20 @@ exports.timer = function(base) {
|
||||
/*
|
||||
Convert text and content type to a data URI
|
||||
*/
|
||||
exports.makeDataUri = function(text,type) {
|
||||
exports.makeDataUri = function(text,type,_canonical_uri) {
|
||||
type = type || "text/vnd.tiddlywiki";
|
||||
var typeInfo = $tw.config.contentTypeInfo[type] || $tw.config.contentTypeInfo["text/plain"],
|
||||
isBase64 = typeInfo.encoding === "base64",
|
||||
parts = [];
|
||||
parts.push("data:");
|
||||
parts.push(type);
|
||||
parts.push(isBase64 ? ";base64" : "");
|
||||
parts.push(",");
|
||||
parts.push(isBase64 ? text : encodeURIComponent(text));
|
||||
if(_canonical_uri) {
|
||||
parts.push(_canonical_uri);
|
||||
} else {
|
||||
parts.push("data:");
|
||||
parts.push(type);
|
||||
parts.push(isBase64 ? ";base64" : "");
|
||||
parts.push(",");
|
||||
parts.push(isBase64 ? text : encodeURIComponent(text));
|
||||
}
|
||||
return parts.join("");
|
||||
};
|
||||
|
||||
@ -779,4 +783,22 @@ exports.strEndsWith = function(str,ending,position) {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Return system information useful for debugging
|
||||
*/
|
||||
exports.getSystemInfo = function(str,ending,position) {
|
||||
var results = [],
|
||||
save = function(desc,value) {
|
||||
results.push(desc + ": " + value);
|
||||
};
|
||||
if($tw.browser) {
|
||||
save("User Agent",navigator.userAgent);
|
||||
save("Online Status",window.navigator.onLine);
|
||||
}
|
||||
if($tw.node) {
|
||||
save("Node Version",process.version);
|
||||
}
|
||||
return results.join("\n");
|
||||
};
|
||||
|
||||
})();
|
||||
|
79
core/modules/widgets/action-popup.js
Normal file
79
core/modules/widgets/action-popup.js
Normal file
@ -0,0 +1,79 @@
|
||||
/*\
|
||||
title: $:/core/modules/widgets/action-popup.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
Action widget to trigger a popup.
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var ActionPopupWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
Inherit from the base widget class
|
||||
*/
|
||||
ActionPopupWidget.prototype = new Widget();
|
||||
|
||||
/*
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
ActionPopupWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
};
|
||||
|
||||
/*
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
ActionPopupWidget.prototype.execute = function() {
|
||||
this.actionState = this.getAttribute("$state");
|
||||
this.actionCoords = this.getAttribute("$coords");
|
||||
};
|
||||
|
||||
/*
|
||||
Refresh the widget by ensuring our attributes are up to date
|
||||
*/
|
||||
ActionPopupWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes["$state"] || changedAttributes["$coords"]) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
};
|
||||
|
||||
/*
|
||||
Invoke the action associated with this widget
|
||||
*/
|
||||
ActionPopupWidget.prototype.invokeAction = function(triggeringWidget,event) {
|
||||
// Trigger the popup
|
||||
var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/,
|
||||
match = popupLocationRegExp.exec(this.actionCoords);
|
||||
if(match) {
|
||||
$tw.popup.triggerPopup({
|
||||
domNode: null,
|
||||
domNodeRect: {
|
||||
left: parseFloat(match[1]),
|
||||
top: parseFloat(match[2]),
|
||||
width: parseFloat(match[3]),
|
||||
height: parseFloat(match[4])
|
||||
},
|
||||
title: this.actionState,
|
||||
wiki: this.wiki
|
||||
});
|
||||
}
|
||||
return true; // Action was invoked
|
||||
};
|
||||
|
||||
exports["action-popup"] = ActionPopupWidget;
|
||||
|
||||
})();
|
@ -64,6 +64,10 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
|
||||
if(this["aria-label"]) {
|
||||
domNode.setAttribute("aria-label",this["aria-label"]);
|
||||
}
|
||||
// Set the tabindex
|
||||
if(this.tabIndex) {
|
||||
domNode.setAttribute("tabindex",this.tabIndex);
|
||||
}
|
||||
// Add a click event handler
|
||||
domNode.addEventListener("click",function (event) {
|
||||
var handled = false;
|
||||
@ -205,6 +209,7 @@ ButtonWidget.prototype.execute = function() {
|
||||
this.setField = this.getAttribute("setField");
|
||||
this.setIndex = this.getAttribute("setIndex");
|
||||
this.popupTitle = this.getAttribute("popupTitle");
|
||||
this.tabIndex = this.getAttribute("tabindex");
|
||||
// Make child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
@ -214,7 +219,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||
*/
|
||||
ButtonWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.to || changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes["class"] || changedAttributes.selectedClass || changedAttributes.style || changedAttributes.dragFilter || changedAttributes.dragTiddler || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup]) || (this.popupTitle && changedTiddlers[this.popupTitle]) || changedAttributes.setTitle || changedAttributes.setField || changedAttributes.setIndex || changedAttributes.popupTitle) {
|
||||
if(changedAttributes.actions || changedAttributes.to || changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes["class"] || changedAttributes.selectedClass || changedAttributes.style || changedAttributes.dragFilter || changedAttributes.dragTiddler || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup]) || (this.popupTitle && changedTiddlers[this.popupTitle]) || changedAttributes.setTitle || changedAttributes.setField || changedAttributes.setIndex || changedAttributes.popupTitle) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||
*/
|
||||
DraggableWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedTiddlers.tag || changedTiddlers["class"]) {
|
||||
if(changedAttributes.tag || changedAttributes["class"]) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
|
@ -43,12 +43,14 @@ DroppableWidget.prototype.render = function(parent,nextSibling) {
|
||||
classes.push("tc-droppable");
|
||||
domNode.className = classes.join(" ");
|
||||
// Add event handlers
|
||||
$tw.utils.addEventListeners(domNode,[
|
||||
{name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"},
|
||||
{name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"},
|
||||
{name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"},
|
||||
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"}
|
||||
]);
|
||||
if(this.droppableEnable) {
|
||||
$tw.utils.addEventListeners(domNode,[
|
||||
{name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"},
|
||||
{name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"},
|
||||
{name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"},
|
||||
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"}
|
||||
]);
|
||||
}
|
||||
// Insert element
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
@ -142,6 +144,7 @@ DroppableWidget.prototype.execute = function() {
|
||||
this.droppableEffect = this.getAttribute("effect","copy");
|
||||
this.droppableTag = this.getAttribute("tag");
|
||||
this.droppableClass = this.getAttribute("class");
|
||||
this.droppableEnable = (this.getAttribute("enable") || "yes") === "yes";
|
||||
// Make child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
@ -151,7 +154,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||
*/
|
||||
DroppableWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes["class"] || changedAttributes.tag) {
|
||||
if(changedAttributes["class"] || changedAttributes.tag || changedAttributes.enable) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
|
@ -37,14 +37,16 @@ DropZoneWidget.prototype.render = function(parent,nextSibling) {
|
||||
var domNode = this.document.createElement("div");
|
||||
domNode.className = "tc-dropzone";
|
||||
// Add event handlers
|
||||
$tw.utils.addEventListeners(domNode,[
|
||||
{name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"},
|
||||
{name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"},
|
||||
{name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"},
|
||||
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"},
|
||||
{name: "paste", handlerObject: this, handlerMethod: "handlePasteEvent"},
|
||||
{name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"}
|
||||
]);
|
||||
if(this.dropzoneEnable) {
|
||||
$tw.utils.addEventListeners(domNode,[
|
||||
{name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"},
|
||||
{name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"},
|
||||
{name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"},
|
||||
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"},
|
||||
{name: "paste", handlerObject: this, handlerMethod: "handlePasteEvent"},
|
||||
{name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"}
|
||||
]);
|
||||
}
|
||||
domNode.addEventListener("click",function (event) {
|
||||
},false);
|
||||
// Insert element
|
||||
@ -189,6 +191,7 @@ Compute the internal state of the widget
|
||||
*/
|
||||
DropZoneWidget.prototype.execute = function() {
|
||||
this.dropzoneDeserializer = this.getAttribute("deserializer");
|
||||
this.dropzoneEnable = (this.getAttribute("enable") || "yes") === "yes";
|
||||
// Make child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
@ -197,6 +200,11 @@ DropZoneWidget.prototype.execute = function() {
|
||||
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
||||
*/
|
||||
DropZoneWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.enable) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
};
|
||||
|
||||
|
@ -13,6 +13,7 @@ Edit-binary widget; placeholder for editing binary tiddlers
|
||||
"use strict";
|
||||
|
||||
var BINARY_WARNING_MESSAGE = "$:/core/ui/BinaryWarning";
|
||||
var EXPORT_BUTTON_IMAGE = "$:/core/images/export-button";
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
@ -43,13 +44,55 @@ EditBinaryWidget.prototype.render = function(parent,nextSibling) {
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
EditBinaryWidget.prototype.execute = function() {
|
||||
// Construct the child widgets
|
||||
this.makeChildWidgets([{
|
||||
type: "transclude",
|
||||
// Get our parameters
|
||||
var editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
|
||||
var tiddler = this.wiki.getTiddler(editTitle);
|
||||
var type = tiddler.fields.type;
|
||||
var text = tiddler.fields.text;
|
||||
// Transclude the binary data tiddler warning message
|
||||
var warn = {
|
||||
type: "element",
|
||||
tag: "p",
|
||||
children: [{
|
||||
type: "transclude",
|
||||
attributes: {
|
||||
tiddler: {type: "string", value: BINARY_WARNING_MESSAGE}
|
||||
}
|
||||
}]
|
||||
};
|
||||
// Create download link based on draft tiddler title
|
||||
var link = {
|
||||
type: "element",
|
||||
tag: "a",
|
||||
attributes: {
|
||||
tiddler: {type: "string", value: BINARY_WARNING_MESSAGE}
|
||||
}
|
||||
}]);
|
||||
title: {type: "indirect", textReference: "!!draft.title"},
|
||||
download: {type: "indirect", textReference: "!!draft.title"}
|
||||
},
|
||||
children: [{
|
||||
type: "transclude",
|
||||
attributes: {
|
||||
tiddler: {type: "string", value: EXPORT_BUTTON_IMAGE}
|
||||
}
|
||||
}]
|
||||
};
|
||||
// Set the link href to internal data URI (no external)
|
||||
if(text) {
|
||||
link.attributes.href = {
|
||||
type: "string",
|
||||
value: "data:" + type + ";base64," + text
|
||||
};
|
||||
}
|
||||
// Combine warning message and download link in a div
|
||||
var element = {
|
||||
type: "element",
|
||||
tag: "div",
|
||||
attributes: {
|
||||
class: {type: "string", value: "tc-binary-warning"}
|
||||
},
|
||||
children: [warn, link]
|
||||
}
|
||||
// Construct the child widgets
|
||||
this.makeChildWidgets([element]);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -37,49 +37,52 @@ ImportVariablesWidget.prototype.render = function(parent,nextSibling) {
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
ImportVariablesWidget.prototype.execute = function(tiddlerList) {
|
||||
var self = this;
|
||||
var widgetPointer = this;
|
||||
// Get our parameters
|
||||
this.filter = this.getAttribute("filter");
|
||||
// Compute the filter
|
||||
this.tiddlerList = tiddlerList || this.wiki.filterTiddlers(this.filter,this);
|
||||
// Accumulate the <$set> widgets from each tiddler
|
||||
var widgetStackStart,widgetStackEnd;
|
||||
function addWidgetNode(widgetNode) {
|
||||
if(widgetNode) {
|
||||
if(!widgetStackStart && !widgetStackEnd) {
|
||||
widgetStackStart = widgetNode;
|
||||
widgetStackEnd = widgetNode;
|
||||
} else {
|
||||
widgetStackEnd.children = [widgetNode];
|
||||
widgetStackEnd = widgetNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
$tw.utils.each(this.tiddlerList,function(title) {
|
||||
var parser = self.wiki.parseTiddler(title);
|
||||
var parser = widgetPointer.wiki.parseTiddler(title);
|
||||
if(parser) {
|
||||
var parseTreeNode = parser.tree[0];
|
||||
while(parseTreeNode && parseTreeNode.type === "set") {
|
||||
addWidgetNode({
|
||||
var node = {
|
||||
type: "set",
|
||||
attributes: parseTreeNode.attributes,
|
||||
params: parseTreeNode.params,
|
||||
isMacroDefinition: parseTreeNode.isMacroDefinition
|
||||
});
|
||||
};
|
||||
if (parseTreeNode.isMacroDefinition) {
|
||||
// Macro definitions can be folded into
|
||||
// current widget instead of adding
|
||||
// another link to the chain.
|
||||
var widget = widgetPointer.makeChildWidget(node);
|
||||
widget.computeAttributes();
|
||||
widget.execute();
|
||||
// We SHALLOW copy over all variables
|
||||
// in widget. We can't use
|
||||
// $tw.utils.assign, because that copies
|
||||
// up the prototype chain, which we
|
||||
// don't want.
|
||||
$tw.utils.each(Object.keys(widget.variables), function(key) {
|
||||
widgetPointer.variables[key] = widget.variables[key];
|
||||
});
|
||||
} else {
|
||||
widgetPointer.makeChildWidgets([node]);
|
||||
widgetPointer = widgetPointer.children[0];
|
||||
}
|
||||
parseTreeNode = parseTreeNode.children && parseTreeNode.children[0];
|
||||
}
|
||||
}
|
||||
});
|
||||
// Add our own children to the end of the pile
|
||||
var parseTreeNodes;
|
||||
if(widgetStackStart && widgetStackEnd) {
|
||||
parseTreeNodes = [widgetStackStart];
|
||||
widgetStackEnd.children = this.parseTreeNode.children;
|
||||
|
||||
if (widgetPointer != this) {
|
||||
widgetPointer.parseTreeNode.children = this.parseTreeNode.children;
|
||||
} else {
|
||||
parseTreeNodes = this.parseTreeNode.children;
|
||||
widgetPointer.makeChildWidgets();
|
||||
}
|
||||
// Construct the child widgets
|
||||
this.makeChildWidgets(parseTreeNodes);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -414,6 +414,30 @@ exports.forEachTiddler = function(/* [options,]callback */) {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Return an array of tiddler titles that are directly linked within the given parse tree
|
||||
*/
|
||||
exports.extractLinks = function(parseTreeRoot) {
|
||||
// Count up the links
|
||||
var links = [],
|
||||
checkParseTree = function(parseTree) {
|
||||
for(var t=0; t<parseTree.length; t++) {
|
||||
var parseTreeNode = parseTree[t];
|
||||
if(parseTreeNode.type === "link" && parseTreeNode.attributes.to && parseTreeNode.attributes.to.type === "string") {
|
||||
var value = parseTreeNode.attributes.to.value;
|
||||
if(links.indexOf(value) === -1) {
|
||||
links.push(value);
|
||||
}
|
||||
}
|
||||
if(parseTreeNode.children) {
|
||||
checkParseTree(parseTreeNode.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
checkParseTree(parseTreeRoot);
|
||||
return links;
|
||||
};
|
||||
|
||||
/*
|
||||
Return an array of tiddler titles that are directly linked from the specified tiddler
|
||||
*/
|
||||
@ -423,26 +447,10 @@ exports.getTiddlerLinks = function(title) {
|
||||
return this.getCacheForTiddler(title,"links",function() {
|
||||
// Parse the tiddler
|
||||
var parser = self.parseTiddler(title);
|
||||
// Count up the links
|
||||
var links = [],
|
||||
checkParseTree = function(parseTree) {
|
||||
for(var t=0; t<parseTree.length; t++) {
|
||||
var parseTreeNode = parseTree[t];
|
||||
if(parseTreeNode.type === "link" && parseTreeNode.attributes.to && parseTreeNode.attributes.to.type === "string") {
|
||||
var value = parseTreeNode.attributes.to.value;
|
||||
if(links.indexOf(value) === -1) {
|
||||
links.push(value);
|
||||
}
|
||||
}
|
||||
if(parseTreeNode.children) {
|
||||
checkParseTree(parseTreeNode.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
if(parser) {
|
||||
checkParseTree(parser.tree);
|
||||
return self.extractLinks(parser.tree);
|
||||
}
|
||||
return links;
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
@ -451,13 +459,18 @@ Return an array of tiddler titles that link to the specified tiddler
|
||||
*/
|
||||
exports.getTiddlerBacklinks = function(targetTitle) {
|
||||
var self = this,
|
||||
backlinksIndexer = this.getIndexer("BacklinksIndexer"),
|
||||
backlinks = backlinksIndexer && backlinksIndexer.lookup(targetTitle);
|
||||
|
||||
if(!backlinks) {
|
||||
backlinks = [];
|
||||
this.forEachTiddler(function(title,tiddler) {
|
||||
var links = self.getTiddlerLinks(title);
|
||||
if(links.indexOf(targetTitle) !== -1) {
|
||||
backlinks.push(title);
|
||||
}
|
||||
});
|
||||
this.forEachTiddler(function(title,tiddler) {
|
||||
var links = self.getTiddlerLinks(title);
|
||||
if(links.indexOf(targetTitle) !== -1) {
|
||||
backlinks.push(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
return backlinks;
|
||||
};
|
||||
|
||||
@ -660,8 +673,9 @@ exports.getTiddlerAsJson = function(title) {
|
||||
}
|
||||
};
|
||||
|
||||
exports.getTiddlersAsJson = function(filter) {
|
||||
exports.getTiddlersAsJson = function(filter,spaces) {
|
||||
var tiddlers = this.filterTiddlers(filter),
|
||||
spaces = (spaces === undefined) ? $tw.config.preferences.jsonSpaces : spaces,
|
||||
data = [];
|
||||
for(var t=0;t<tiddlers.length; t++) {
|
||||
var tiddler = this.getTiddler(tiddlers[t]);
|
||||
@ -673,7 +687,7 @@ exports.getTiddlersAsJson = function(filter) {
|
||||
data.push(fields);
|
||||
}
|
||||
}
|
||||
return JSON.stringify(data,null,$tw.config.preferences.jsonSpaces);
|
||||
return JSON.stringify(data,null,spaces);
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1232,9 +1246,9 @@ exports.getTiddlerText = function(title,defaultText) {
|
||||
if(!tiddler) {
|
||||
return defaultText;
|
||||
}
|
||||
if(tiddler.fields.text !== undefined) {
|
||||
if(!tiddler.hasField("_is_skinny")) {
|
||||
// Just return the text if we've got it
|
||||
return tiddler.fields.text;
|
||||
return tiddler.fields.text || "";
|
||||
} else {
|
||||
// Tell any listeners about the need to lazily load this tiddler
|
||||
this.dispatchEvent("lazyLoad",title);
|
||||
@ -1398,10 +1412,8 @@ fromPageRect: page coordinates of the origin of the navigation
|
||||
historyTitle: title of history tiddler (defaults to $:/HistoryList)
|
||||
*/
|
||||
exports.addToHistory = function(title,fromPageRect,historyTitle) {
|
||||
if(historyTitle) {
|
||||
var story = new $tw.Story({wiki: this, historyTitle: historyTitle});
|
||||
story.addToHistory(title,fromPageRect);
|
||||
}
|
||||
var story = new $tw.Story({wiki: this, historyTitle: historyTitle});
|
||||
story.addToHistory(title,fromPageRect);
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1412,10 +1424,8 @@ storyTitle: title of story tiddler (defaults to $:/StoryList)
|
||||
options: see story.js
|
||||
*/
|
||||
exports.addToStory = function(title,fromTitle,storyTitle,options) {
|
||||
if(storyTitle) {
|
||||
var story = new $tw.Story({wiki: this, storyTitle: storyTitle});
|
||||
story.addToStory(title,fromTitle,options);
|
||||
}
|
||||
var story = new $tw.Story({wiki: this, storyTitle: storyTitle});
|
||||
story.addToStory(title,fromTitle,options);
|
||||
};
|
||||
|
||||
/*
|
||||
|
122
core/palettes/GruvBoxDark.tid
Normal file
122
core/palettes/GruvBoxDark.tid
Normal file
@ -0,0 +1,122 @@
|
||||
title: $:/palettes/GruvboxDark
|
||||
name: Gruvbox Dark
|
||||
description: Retro groove color scheme
|
||||
tags: $:/tags/Palette
|
||||
type: application/x-tiddler-dictionary
|
||||
license: https://github.com/morhetz/gruvbox
|
||||
|
||||
alert-background: #cc241d
|
||||
alert-border: #cc241d
|
||||
alert-highlight: #d79921
|
||||
alert-muted-foreground: #504945
|
||||
background: #3c3836
|
||||
blockquote-bar: <<colour muted-foreground>>
|
||||
button-background: #504945
|
||||
button-foreground: #fbf1c7
|
||||
button-border: transparent
|
||||
code-background: #504945
|
||||
code-border: #504945
|
||||
code-foreground: #fb4934
|
||||
diff-delete-background: #fb4934
|
||||
diff-delete-foreground: <<colour foreground>>
|
||||
diff-equal-background:
|
||||
diff-equal-foreground: <<colour foreground>>
|
||||
diff-insert-background: #b8bb26
|
||||
diff-insert-foreground: <<colour foreground>>
|
||||
diff-invisible-background:
|
||||
diff-invisible-foreground: <<colour muted-foreground>>
|
||||
dirty-indicator: #fb4934
|
||||
download-background: #b8bb26
|
||||
download-foreground: <<colour background>>
|
||||
dragger-background: <<colour foreground>>
|
||||
dragger-foreground: <<colour background>>
|
||||
dropdown-background: #665c54
|
||||
dropdown-border: <<colour background>>
|
||||
dropdown-tab-background-selected: #ebdbb2
|
||||
dropdown-tab-background: #665c54
|
||||
dropzone-background: #98971a
|
||||
external-link-background-hover: inherit
|
||||
external-link-background-visited: inherit
|
||||
external-link-background: inherit
|
||||
external-link-foreground-hover: inherit
|
||||
external-link-foreground-visited: #d3869b
|
||||
external-link-foreground: #8ec07c
|
||||
foreground: #fbf1c7
|
||||
menubar-background: #504945
|
||||
menubar-foreground: <<colour foreground>>
|
||||
message-background: #83a598
|
||||
message-border: #83a598
|
||||
message-foreground: #3c3836
|
||||
modal-backdrop: <<colour foreground>>
|
||||
modal-background: <<colour background>>
|
||||
modal-border: #504945
|
||||
modal-footer-background: #3c3836
|
||||
modal-footer-border: #3c3836
|
||||
modal-header-border: #3c3836
|
||||
muted-foreground: #d5c4a1
|
||||
notification-background: <<colour primary>>
|
||||
notification-border: <<colour primary>>
|
||||
page-background: #282828
|
||||
pre-background: #504945
|
||||
pre-border: #504945
|
||||
primary: #d79921
|
||||
select-tag-background: #665c54
|
||||
select-tag-foreground: <<colour foreground>>
|
||||
sidebar-button-foreground: <<colour foreground>>
|
||||
sidebar-controls-foreground-hover: #7c6f64
|
||||
sidebar-controls-foreground: #504945
|
||||
sidebar-foreground-shadow: transparent
|
||||
sidebar-foreground: #fbf1c7
|
||||
sidebar-muted-foreground-hover: #7c6f64
|
||||
sidebar-muted-foreground: #504945
|
||||
sidebar-tab-background-selected: #bdae93
|
||||
sidebar-tab-background: #3c3836
|
||||
sidebar-tab-border-selected: <<colour tab-border-selected>>
|
||||
sidebar-tab-border: #bdae93
|
||||
sidebar-tab-divider: <<colour page-background>>
|
||||
sidebar-tab-foreground-selected: #282828
|
||||
sidebar-tab-foreground: <<colour tab-foreground>>
|
||||
sidebar-tiddler-link-foreground-hover: #458588
|
||||
sidebar-tiddler-link-foreground: #98971a
|
||||
site-title-foreground: <<colour tiddler-title-foreground>>
|
||||
static-alert-foreground: #B48EAD
|
||||
tab-background-selected: #ebdbb2
|
||||
tab-background: #665c54
|
||||
tab-border-selected: #665c54
|
||||
tab-border: #665c54
|
||||
tab-divider: #bdae93
|
||||
tab-foreground-selected: #282828
|
||||
tab-foreground: #ebdbb2
|
||||
table-border: #7c6f64
|
||||
table-footer-background: #665c54
|
||||
table-header-background: #504945
|
||||
tag-background: #d3869b
|
||||
tag-foreground: #282828
|
||||
tiddler-background: <<colour background>>
|
||||
tiddler-border: <<colour background>>
|
||||
tiddler-controls-foreground-hover: #7c6f64
|
||||
tiddler-controls-foreground-selected: #7c6f64
|
||||
tiddler-controls-foreground: #665c54
|
||||
tiddler-editor-background: #282828
|
||||
tiddler-editor-border-image: #282828
|
||||
tiddler-editor-border: #282828
|
||||
tiddler-editor-fields-even: #504945
|
||||
tiddler-editor-fields-odd: #7c6f64
|
||||
tiddler-info-background: #32302f
|
||||
tiddler-info-border: #ebdbb2
|
||||
tiddler-info-tab-background: #ebdbb2
|
||||
tiddler-link-background: <<colour background>>
|
||||
tiddler-link-foreground: <<colour primary>>
|
||||
tiddler-subtitle-foreground: #7c6f64
|
||||
tiddler-title-foreground: #a89984
|
||||
toolbar-new-button:
|
||||
toolbar-options-button:
|
||||
toolbar-save-button:
|
||||
toolbar-info-button:
|
||||
toolbar-edit-button:
|
||||
toolbar-close-button:
|
||||
toolbar-delete-button:
|
||||
toolbar-cancel-button:
|
||||
toolbar-done-button:
|
||||
untagged-background: #504945
|
||||
very-muted-foreground: #bdae93
|
@ -42,6 +42,8 @@ external-link-foreground-hover: inherit
|
||||
external-link-foreground-visited: #5E81AC
|
||||
external-link-foreground: #8FBCBB
|
||||
foreground: #d8dee9
|
||||
menubar-background: #2E3440
|
||||
menubar-foreground: #d8dee9
|
||||
message-background: #2E3440
|
||||
message-border: #2E3440
|
||||
message-foreground: #547599
|
||||
@ -61,8 +63,8 @@ primary: #5E81AC
|
||||
select-tag-background: #3b4252
|
||||
select-tag-foreground: <<colour foreground>>
|
||||
sidebar-button-foreground: <<colour foreground>>
|
||||
sidebar-controls-foreground-hover: #4C566A
|
||||
sidebar-controls-foreground: #3B4252
|
||||
sidebar-controls-foreground-hover: #D8DEE9
|
||||
sidebar-controls-foreground: #4C566A
|
||||
sidebar-foreground-shadow: transparent
|
||||
sidebar-foreground: #D8DEE9
|
||||
sidebar-muted-foreground-hover: #4C566A
|
||||
|
@ -117,3 +117,20 @@ toolbar-cancel-button:
|
||||
toolbar-done-button:
|
||||
untagged-background: #999999
|
||||
very-muted-foreground: #888888
|
||||
wikilist-background: #e5e5e5
|
||||
wikilist-item: #fff
|
||||
wikilist-info: #000
|
||||
wikilist-title: #666
|
||||
wikilist-title-svg: <<colour wikilist-title>>
|
||||
wikilist-url: #aaa
|
||||
wikilist-button-open: #4fb82b
|
||||
wikilist-button-open-hover: green
|
||||
wikilist-button-reveal: #5778d8
|
||||
wikilist-button-reveal-hover: blue
|
||||
wikilist-button-remove: #d85778
|
||||
wikilist-button-remove-hover: red
|
||||
wikilist-toolbar-background: #d3d3d3
|
||||
wikilist-toolbar-foreground: #888
|
||||
wikilist-droplink-dragover: rgba(255,192,192,0.5)
|
||||
wikilist-button-background: #acacac
|
||||
wikilist-button-foreground: #000
|
||||
|
9
core/templates/html-div-skinny-tiddler.tid
Normal file
9
core/templates/html-div-skinny-tiddler.tid
Normal file
@ -0,0 +1,9 @@
|
||||
title: $:/core/templates/html-div-skinny-tiddler
|
||||
|
||||
<!--
|
||||
|
||||
This template is a variant of $:/core/templates/html-div-tiddler used for saving skinny tiddlers (with no text field)
|
||||
|
||||
-->`<div`<$fields template=' $name$="$encoded_value$"'></$fields>`>
|
||||
<pre></pre>
|
||||
</div>`
|
@ -3,4 +3,7 @@ title: $:/core/save/lazy-all
|
||||
\define saveTiddlerFilter()
|
||||
[is[system]] -[prefix[$:/state/popup/]] -[[$:/HistoryList]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] +[sort[title]]
|
||||
\end
|
||||
\define skinnySaveTiddlerFilter()
|
||||
[!is[system]]
|
||||
\end
|
||||
{{$:/core/templates/tiddlywiki5.html}}
|
||||
|
@ -3,4 +3,7 @@ title: $:/core/save/lazy-images
|
||||
\define saveTiddlerFilter()
|
||||
[is[tiddler]] -[prefix[$:/state/popup/]] -[[$:/HistoryList]] -[[$:/boot/boot.css]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] -[!is[system]is[image]] +[sort[title]]
|
||||
\end
|
||||
\define skinnySaveTiddlerFilter()
|
||||
[is[image]]
|
||||
\end
|
||||
{{$:/core/templates/tiddlywiki5.html}}
|
||||
|
@ -3,6 +3,7 @@ title: $:/core/templates/store.area.template.html
|
||||
<$reveal type="nomatch" state="$:/isEncrypted" text="yes">
|
||||
`<div id="storeArea" style="display:none;">`
|
||||
<$list filter=<<saveTiddlerFilter>> template="$:/core/templates/html-div-tiddler"/>
|
||||
<$list filter={{{ [<skinnySaveTiddlerFilter>] }}} template="$:/core/templates/html-div-skinny-tiddler"/>
|
||||
`</div>`
|
||||
</$reveal>
|
||||
<$reveal type="match" state="$:/isEncrypted" text="yes">
|
||||
|
@ -2,10 +2,12 @@ title: $:/core/ui/AlertTemplate
|
||||
|
||||
<div class="tc-alert">
|
||||
<div class="tc-alert-toolbar">
|
||||
<$button class="tc-btn-invisible"><$action-deletetiddler $tiddler=<<currentTiddler>>/>{{$:/core/images/delete-button}}</$button>
|
||||
<$button class="tc-btn-invisible"><$action-deletetiddler $tiddler=<<currentTiddler>>/>{{$:/core/images/cancel-button}}</$button>
|
||||
</div>
|
||||
<div class="tc-alert-subtitle">
|
||||
<$view field="component"/> - <$view field="modified" format="date" template="0hh:0mm:0ss DD MM YYYY"/> <$reveal type="nomatch" state="!!count" text=""><span class="tc-alert-highlight">({{$:/language/Count}}: <$view field="count"/>)</span></$reveal>
|
||||
<$wikify name="format" text=<<lingo Tiddler/DateFormat>>>
|
||||
<$view field="component"/> - <$view field="modified" format="date" template=<<format>>/> <$reveal type="nomatch" state="!!count" text=""><span class="tc-alert-highlight">({{$:/language/Count}}: <$view field="count"/>)</span></$reveal>
|
||||
</$wikify>
|
||||
</div>
|
||||
<div class="tc-alert-body">
|
||||
|
||||
|
@ -1,8 +1,4 @@
|
||||
title: $:/core/ui/BinaryWarning
|
||||
|
||||
\define lingo-base() $:/language/BinaryWarning/
|
||||
<div class="tc-binary-warning">
|
||||
|
||||
<<lingo Prompt>>
|
||||
|
||||
</div>
|
||||
|
16
core/ui/ControlPanel/Saving/gitea.tid
Normal file
16
core/ui/ControlPanel/Saving/gitea.tid
Normal file
@ -0,0 +1,16 @@
|
||||
title: $:/core/ui/ControlPanel/Saving/Gitea
|
||||
tags: $:/tags/ControlPanel/Saving
|
||||
caption: {{$:/language/ControlPanel/Saving/GitService/Gitea/Caption}}
|
||||
|
||||
\define lingo-base() $:/language/ControlPanel/Saving/GitService/
|
||||
\define service-name() ~Gitea
|
||||
|
||||
<<lingo Description>>
|
||||
|
||||
|<<lingo UserName>> |<$edit-text tiddler="$:/Gitea/Username" default="" tag="input"/> |
|
||||
|<<lingo Gitea/Password>> |<$password name="Gitea"/> |
|
||||
|<<lingo Repo>> |<$edit-text tiddler="$:/Gitea/Repo" default="" tag="input"/> |
|
||||
|<<lingo Branch>> |<$edit-text tiddler="$:/Gitea/Branch" default="master" tag="input"/> |
|
||||
|<<lingo Path>> |<$edit-text tiddler="$:/Gitea/Path" default="" tag="input"/> |
|
||||
|<<lingo Filename>> |<$edit-text tiddler="$:/Gitea/Filename" default="" tag="input"/> |
|
||||
|<<lingo ServerURL>> |<$edit-text tiddler="$:/Gitea/ServerURL" default="https://gitea/api/v1" tag="input"/> |
|
@ -69,7 +69,7 @@ $value={{{ [<newFieldValueTiddler>get[text]] }}}/>
|
||||
<$fieldmangler>
|
||||
<div class="tc-edit-field-add">
|
||||
<em class="tc-edit">
|
||||
<<lingo Fields/Add/Prompt>>
|
||||
<<lingo Fields/Add/Prompt>>
|
||||
</em>
|
||||
<span class="tc-edit-field-add-name">
|
||||
<$edit-text tiddler=<<newFieldNameTiddler>> tag="input" default="" placeholder={{$:/language/EditTemplate/Fields/Add/Name/Placeholder}} focusPopup=<<qualify "$:/state/popup/field-dropdown">> class="tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[fields]then[true]] ~[[false]] }}}/>
|
||||
|
@ -2,8 +2,9 @@ title: $:/core/ui/EditTemplate/type
|
||||
tags: $:/tags/EditTemplate
|
||||
|
||||
\define lingo-base() $:/language/EditTemplate/
|
||||
\whitespace trim
|
||||
<div class="tc-type-selector"><$fieldmangler>
|
||||
<em class="tc-edit"><<lingo Type/Prompt>></em> <$edit-text field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] ~[[false]] }}}/> <$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button> <$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}</$button>
|
||||
<em class="tc-edit"><<lingo Type/Prompt>></em> <$edit-text field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] ~[[false]] }}}/> <$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button> <$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}</$button>
|
||||
</$fieldmangler></div>
|
||||
|
||||
<div class="tc-block-dropdown-wrapper">
|
||||
|
@ -12,19 +12,21 @@ tc-page-container tc-page-view-$(storyviewTitle)$ tc-language-$(languageTitle)$
|
||||
|
||||
<$set name="tv-config-toolbar-class" value={{$:/config/Toolbar/ButtonClass}}>
|
||||
|
||||
<$set name="tv-enable-drag-and-drop" value={{$:/config/DragAndDrop/Enable}}>
|
||||
|
||||
<$set name="tv-show-missing-links" value={{$:/config/MissingLinks}}>
|
||||
|
||||
<$set name="storyviewTitle" value={{$:/view}}>
|
||||
|
||||
<$set name="tv-storyview-single-tiddler-mode" value={{{ [<storyviewTitle>getstoryviewmode[singletiddlermode]] }}}>
|
||||
|
||||
<$set name="tv-show-missing-links" value={{$:/config/MissingLinks}}>
|
||||
|
||||
<$set name="languageTitle" value={{{ [{$:/language}get[name]] }}}>
|
||||
|
||||
<div class=<<containerClasses>>>
|
||||
|
||||
<$navigator story="$:/StoryList" history="$:/HistoryList" singleTiddlerMode=<<tv-storyview-single-tiddler-mode>> openLinkFromInsideRiver={{$:/config/Navigation/openLinkFromInsideRiver}} openLinkFromOutsideRiver={{$:/config/Navigation/openLinkFromOutsideRiver}} relinkOnRename={{$:/config/RelinkOnRename}}>
|
||||
|
||||
<$dropzone>
|
||||
<$dropzone enable=<<tv-enable-drag-and-drop>>>
|
||||
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/PageTemplate]!has[draft.of]]" variable="listItem">
|
||||
|
||||
|
@ -15,7 +15,7 @@ caption: <$list filter="[<tv-storyview-single-tiddler-mode>prefix[yes]]" emptyMe
|
||||
|
||||
\define droppable-item(button)
|
||||
\whitespace trim
|
||||
<$droppable actions=<<drop-actions>>>
|
||||
<$droppable actions=<<drop-actions>> enable=<<tv-allow-drag-and-drop>>>
|
||||
<<placeholder>>
|
||||
<div>
|
||||
$button$
|
||||
|
@ -4,6 +4,8 @@ color: #bbb
|
||||
|
||||
\define lingo-base() $:/language/TagManager/
|
||||
\define iconEditorTab(type)
|
||||
\whitespace trim
|
||||
<$link to=""><<lingo Icons/None>></$link>
|
||||
<$list filter="[all[shadows+tiddlers]is[image]] [all[shadows+tiddlers]tag[$:/tags/Image]] -[type[application/pdf]] +[sort[title]] +[$type$is[system]]">
|
||||
<$link to={{!!title}}>
|
||||
<$transclude/> <$view field="title"/>
|
||||
@ -11,6 +13,7 @@ color: #bbb
|
||||
</$list>
|
||||
\end
|
||||
\define iconEditor(title)
|
||||
\whitespace trim
|
||||
<div class="tc-drop-down-wrapper">
|
||||
<$button popupTitle={{{ [[$:/state/popup/icon/]addsuffix<__title__>] }}} class="tc-btn-invisible tc-btn-dropdown">{{$:/core/images/down-arrow}}</$button>
|
||||
<$reveal stateTitle={{{ [[$:/state/popup/icon/]addsuffix<__title__>] }}} type="popup" position="belowleft" text="" default="">
|
||||
@ -25,6 +28,7 @@ color: #bbb
|
||||
</div>
|
||||
\end
|
||||
\define toggleButton(state)
|
||||
\whitespace trim
|
||||
<$reveal stateTitle=<<__state__>> type="match" text="closed" default="closed">
|
||||
<$button setTitle=<<__state__>> setTo="open" class="tc-btn-invisible tc-btn-dropdown" selectedClass="tc-selected">
|
||||
{{$:/core/images/info-button}}
|
||||
@ -36,6 +40,7 @@ color: #bbb
|
||||
</$button>
|
||||
</$reveal>
|
||||
\end
|
||||
\whitespace trim
|
||||
<table class="tc-tag-manager-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -1,9 +1,9 @@
|
||||
title: $:/core/ui/TopBar/menu
|
||||
tags: $:/tags/TopRightBar
|
||||
|
||||
<$reveal state="$:/state/sidebar" type="nomatch" text="no">
|
||||
<$list filter="[[$:/state/sidebar]get[text]] +[else[yes]!match[no]]" variable="ignore">
|
||||
<$button set="$:/state/sidebar" setTo="no" tooltip={{$:/language/Buttons/HideSideBar/Hint}} aria-label={{$:/language/Buttons/HideSideBar/Caption}} class="tc-btn-invisible">{{$:/core/images/chevron-right}}</$button>
|
||||
</$reveal>
|
||||
<$reveal state="$:/state/sidebar" type="match" text="no">
|
||||
</$list>
|
||||
<$list filter="[[$:/state/sidebar]get[text]] +[else[yes]match[no]]" variable="ignore">
|
||||
<$button set="$:/state/sidebar" setTo="yes" tooltip={{$:/language/Buttons/ShowSideBar/Hint}} aria-label={{$:/language/Buttons/ShowSideBar/Caption}} class="tc-btn-invisible">{{$:/core/images/chevron-left}}</$button>
|
||||
</$reveal>
|
||||
</$list>
|
||||
|
@ -4,6 +4,6 @@ title: $:/core/ui/ViewTemplate
|
||||
$:/state/folded/$(currentTiddler)$
|
||||
\end
|
||||
\import [all[shadows+tiddlers]tag[$:/tags/Macro/View]!has[draft.of]]
|
||||
<$vars storyTiddler=<<currentTiddler>> tiddlerInfoState=<<qualify "$:/state/popup/tiddler-info">>><div data-tiddler-title=<<currentTiddler>> data-tags={{!!tags}} class={{{ tc-tiddler-frame tc-tiddler-view-frame [<currentTiddler>is[tiddler]then[tc-tiddler-exists]] [<currentTiddler>is[missing]!is[shadow]then[tc-tiddler-missing]] [<currentTiddler>is[shadow]then[tc-tiddler-exists tc-tiddler-shadow]] [<currentTiddler>is[system]then[tc-tiddler-system]] [{!!class}] [<currentTiddler>tags[]encodeuricomponent[]addprefix[tc-tagged-]] +[join[ ]] }}}><$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewTemplate]!has[draft.of]]" variable="listItem"><$transclude tiddler=<<listItem>>/></$list>
|
||||
<$vars storyTiddler=<<currentTiddler>> tiddlerInfoState=<<qualify "$:/state/popup/tiddler-info">>><div data-tiddler-title=<<currentTiddler>> data-tags={{!!tags}} class={{{ tc-tiddler-frame tc-tiddler-view-frame [<currentTiddler>is[tiddler]then[tc-tiddler-exists]] [<currentTiddler>is[missing]!is[shadow]then[tc-tiddler-missing]] [<currentTiddler>is[shadow]then[tc-tiddler-exists tc-tiddler-shadow]] [<currentTiddler>is[shadow]is[tiddler]then[tc-tiddler-overridden-shadow]] [<currentTiddler>is[system]then[tc-tiddler-system]] [{!!class}] [<currentTiddler>tags[]encodeuricomponent[]addprefix[tc-tagged-]] +[join[ ]] }}}><$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewTemplate]!has[draft.of]]" variable="listItem"><$transclude tiddler=<<listItem>>/></$list>
|
||||
</div>
|
||||
</$vars>
|
||||
|
@ -16,7 +16,9 @@ $:/config/ViewToolbarButtons/Visibility/$(listItem)$
|
||||
<$link>
|
||||
<$set name="foregroundColor" value={{!!color}}>
|
||||
<span class="tc-tiddler-title-icon" style=<<title-styles>>>
|
||||
<$transclude tiddler={{!!icon}}/>
|
||||
<$transclude tiddler={{!!icon}}>
|
||||
<$transclude tiddler={{$:/config/DefaultTiddlerIcon}}/>
|
||||
</$transclude>
|
||||
</span>
|
||||
</$set>
|
||||
<$list filter="[all[current]removeprefix[$:/]]">
|
||||
|
2
core/wiki/config/ServerExternalFiltersDefault.tid
Normal file
2
core/wiki/config/ServerExternalFiltersDefault.tid
Normal file
@ -0,0 +1,2 @@
|
||||
title: $:/config/Server/ExternalFilters/[all[tiddlers]!is[system]sort[title]]
|
||||
text: yes
|
@ -2,7 +2,7 @@ title: $:/core/macros/CSS
|
||||
tags: $:/tags/Macro
|
||||
|
||||
\define colour(name)
|
||||
<$transclude tiddler={{$:/palette}} index="$name$"><$transclude tiddler="$:/palettes/Vanilla" index="$name$"/></$transclude>
|
||||
<$transclude tiddler={{$:/palette}} index="$name$"><$transclude tiddler="$:/palettes/Vanilla" index="$name$"><$transclude tiddler="$:/config/DefaultColourMappings/$name$"/></$transclude></$transclude>
|
||||
\end
|
||||
|
||||
\define color(name)
|
||||
@ -60,7 +60,7 @@ column-count: $columns$;
|
||||
\end
|
||||
|
||||
\define datauri(title)
|
||||
<$macrocall $name="makedatauri" type={{$title$!!type}} text={{$title$}}/>
|
||||
<$macrocall $name="makedatauri" type={{$title$!!type}} text={{$title$}} _canonical_uri={{$title$!!_canonical_uri}}/>
|
||||
\end
|
||||
|
||||
\define if-sidebar(text)
|
||||
|
@ -26,7 +26,7 @@ tags: $:/tags/Macro
|
||||
<$vars targetTiddler="""$tiddler$""" targetField="""$field$""">
|
||||
<$type$ class="$class$">
|
||||
<$list filter="[list[$tiddler$!!$field$]]">
|
||||
<$droppable actions=<<list-links-draggable-drop-actions>> tag="""$subtype$""">
|
||||
<$droppable actions=<<list-links-draggable-drop-actions>> tag="""$subtype$""" enable=<<tv-enable-drag-and-drop>>>
|
||||
<div class="tc-droppable-placeholder"/>
|
||||
<div>
|
||||
<$transclude tiddler="""$itemTemplate$""">
|
||||
@ -41,7 +41,7 @@ tags: $:/tags/Macro
|
||||
</$list>
|
||||
</$type$>
|
||||
<$tiddler tiddler="">
|
||||
<$droppable actions=<<list-links-draggable-drop-actions>> tag="div">
|
||||
<$droppable actions=<<list-links-draggable-drop-actions>> tag="div" enable=<<tv-enable-drag-and-drop>>>
|
||||
<div class="tc-droppable-placeholder">
|
||||
|
||||
</div>
|
||||
@ -74,13 +74,13 @@ tags: $:/tags/Macro
|
||||
</$set>
|
||||
\end
|
||||
|
||||
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div")
|
||||
\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__>>>
|
||||
<$list filter="[<__tag__>tagging[]$subFilter$]" emptyMessage=<<__emptyMessage__>> storyview=<<__storyview__>>>
|
||||
<$elementTag$ class="tc-menu-list-item">
|
||||
<$droppable actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""">
|
||||
<$droppable actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""" enable=<<tv-enable-drag-and-drop>>>
|
||||
<$elementTag$ class="tc-droppable-placeholder"/>
|
||||
<$elementTag$>
|
||||
<$transclude tiddler="""$itemTemplate$""">
|
||||
@ -93,7 +93,7 @@ tags: $:/tags/Macro
|
||||
</$elementTag$>
|
||||
</$list>
|
||||
<$tiddler tiddler="">
|
||||
<$droppable actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""">
|
||||
<$droppable actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""" enable=<<tv-enable-drag-and-drop>>>
|
||||
<$elementTag$ class="tc-droppable-placeholder"/>
|
||||
<$elementTag$ style="height:0.5em;">
|
||||
</$elementTag$>
|
||||
|
@ -15,12 +15,13 @@ tags: $:/tags/Macro
|
||||
\end
|
||||
|
||||
\define tag-picker-inner()
|
||||
\whitespace trim
|
||||
<div class="tc-edit-add-tag">
|
||||
<span class="tc-add-tag-name">
|
||||
<$keyboard key="ENTER" actions=<<add-tag-actions>>>
|
||||
<$edit-text tiddler=<<newTagNameTiddler>> tag="input" default="" placeholder={{$:/language/EditTemplate/Tags/Add/Placeholder}} focusPopup=<<qualify "$:/state/popup/tags-auto-complete">> class="tc-edit-texteditor tc-popup-handle" tabindex=<<tabIndex>> focus={{{ [{$:/config/AutoFocus}match[tags]then[true]] ~[[false]] }}}/>
|
||||
</$keyboard>
|
||||
</span> <$button popup=<<qualify "$:/state/popup/tags-auto-complete">> class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Tags/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Tags/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button> <span class="tc-add-tag-button">
|
||||
</span> <$button popup=<<qualify "$:/state/popup/tags-auto-complete">> class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Tags/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Tags/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button> <span class="tc-add-tag-button">
|
||||
<$set name="tag" value={{{ [<newTagNameTiddler>get[text]] }}}>
|
||||
<$button set="$:/temp/NewTagName" setTo="" class="">
|
||||
<<add-tag-actions>>
|
||||
@ -49,6 +50,7 @@ tags: $:/tags/Macro
|
||||
</div>
|
||||
\end
|
||||
\define tag-picker()
|
||||
\whitespace trim
|
||||
<$list filter="[<newTagNameTiddler>match[]]" emptyMessage=<<tag-picker-inner>>>
|
||||
<$set name="newTagNameTiddler" value=<<qualify "$:/temp/NewTagName">>>
|
||||
<<tag-picker-inner>>
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20130825162100000
|
||||
modified: 20140814094907624
|
||||
modified: 20200113094126878
|
||||
tags: dev moduletypes
|
||||
title: SyncAdaptorModules
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -14,6 +14,8 @@ SyncAdaptorModules encapsulate storage mechanisms that can be used by the SyncMe
|
||||
|
||||
SyncAdaptorModules are represented as JavaScript tiddlers with the field `module-type` set to `syncadaptor`.
|
||||
|
||||
See [[this pull request|https://github.com/Jermolene/TiddlyWiki5/pull/4373]] for background on the evolution of this API.
|
||||
|
||||
! Exports
|
||||
|
||||
The following properties should be exposed via the `exports` object:
|
||||
@ -25,7 +27,13 @@ Nothing should be exported if the adaptor detects that it isn't capable of opera
|
||||
|
||||
! Adaptor Module Methods
|
||||
|
||||
Adaptor modules must handle the following methods.
|
||||
Adaptor modules must handle the methods described below.
|
||||
|
||||
!!! Error Handling
|
||||
|
||||
The syncadaptor must invoke the provided callback with the ''err'' parameter containing a string describing the error.
|
||||
|
||||
The syncer has special handling for connection errors. For backwards compatibilty reasons, the syncer identifies connection errors as the string comprised of the content of the tiddler $:/language/Error/XMLHttpRequest with the string ": 0" appended to the end. For example, in English, the string is "XMLHttpRequest error code: 0" and in Brazilian Portuguese it is "Código de erro XMLHttpRequest: 0".
|
||||
|
||||
!! `Constructor(options)`
|
||||
|
||||
@ -47,12 +55,21 @@ Gets the supplemental information that the adaptor needs to keep track of for a
|
||||
|
||||
Returns an object storing any additional information required by the adaptor.
|
||||
|
||||
!! `getTiddlerRevision(title)`
|
||||
|
||||
Gets the revision ID associated with the specified tiddler title.
|
||||
|
||||
|!Parameter |!Description |
|
||||
|title |Tiddler title |
|
||||
|
||||
Returns a revision ID.
|
||||
|
||||
!! `getStatus(callback)`
|
||||
|
||||
Retrieves status information from the server. This method is optional.
|
||||
|
||||
|!Parameter |!Description |
|
||||
|callback |Callback function invoked with parameters `err,isLoggedIn,username` |
|
||||
|callback |Callback function invoked with parameters `err,isLoggedIn,username,isReadOnly` |
|
||||
|
||||
!! `login(username,password,callback)`
|
||||
|
||||
@ -70,16 +87,39 @@ Attempts to logout of the server. This method is optional.
|
||||
|!Parameter |!Description |
|
||||
|callback |Callback function invoked with parameter `err` |
|
||||
|
||||
!! `getUpdatedTiddlers(syncer,callback)`
|
||||
|
||||
Retrieves the titles of tiddlers that need to be updated from the server.
|
||||
|
||||
This method is optional. If an adaptor doesn't implement it then synchronisation will be unidirectional from the TiddlyWiki store to the adaptor, but not the other way.
|
||||
|
||||
The syncer will use the `getUpdatedTiddlers()` method in preference to the `getSkinnyTiddlers()` method.
|
||||
|
||||
|!Parameter |!Description |
|
||||
|syncer |Reference to the syncer object making the call |
|
||||
|callback |Callback function invoked with parameter `err,data` -- see below |
|
||||
|
||||
The data provided by the callback is as follows:
|
||||
|
||||
```
|
||||
{
|
||||
modifications: [<array of title>],
|
||||
deletions: [<array of title>],
|
||||
}
|
||||
```
|
||||
|
||||
!! `getSkinnyTiddlers(callback)`
|
||||
|
||||
Retrieves a list of skinny tiddlers from the server.
|
||||
|
||||
This method is optional. If an adaptor doesn't implement it then synchronisation will be unidirectional from the TiddlyWiki store to the adaptor, but not the other way.
|
||||
|
||||
The syncer will use the `getUpdatedTiddlers()` method in preference to the `getSkinnyTiddlers()` method.
|
||||
|
||||
|!Parameter |!Description |
|
||||
|callback |Callback function invoked with parameter `err,tiddlers`, where `tiddlers` is an array of tiddler field objects |
|
||||
|
||||
!! `saveTiddler(tiddler,callback,tiddlerInfo)`
|
||||
!! `saveTiddler(tiddler,callback)`
|
||||
|
||||
Saves a tiddler to the server.
|
||||
|
||||
@ -96,11 +136,16 @@ Loads a tiddler from the server.
|
||||
|title |Title of tiddler to be retrieved |
|
||||
|callback |Callback function invoked with parameter `err,tiddlerFields` |
|
||||
|
||||
!! `deleteTiddler(title,callback,tiddlerInfo)`
|
||||
!! `deleteTiddler(title,callback,options)`
|
||||
|
||||
Delete a tiddler from the server.
|
||||
|
||||
|!Parameter |!Description |
|
||||
|title |Title of tiddler to be deleted |
|
||||
|callback |Callback function invoked with parameter `err` |
|
||||
|options |See below |
|
||||
|
||||
The options parameter contains the following properties:
|
||||
|
||||
|!Property |!Description |
|
||||
|tiddlerInfo |The tiddlerInfo maintained by the syncer for this tiddler |
|
||||
|
@ -6,14 +6,9 @@ $(languagePluginTitle)$/icon
|
||||
|
||||
TiddlyWiki is currently available in <$count filter="[[$:/languages/en-GB]] [plugin-type[language]sort[title]] -[[$:/languages/de-AT]]"/> languages:
|
||||
|
||||
<div class="tc-drop-down-language-chooser" style="font-size: 10px; line-height: 12px; -moz-columns:2; -webkit-columns:2; columns:2;">
|
||||
<div class="tc-drop-down-language-chooser" style="font-size: 7px; line-height: 8px; -moz-columns:6; -webkit-columns:6; columns:6;">
|
||||
<$list filter="[[$:/languages/en-GB]] [plugin-type[language]sort[title]] -[[$:/languages/de-AT]]">
|
||||
<div class="tc-language-list">
|
||||
<span class="tc-language-list-greeting">
|
||||
<$view subtiddler="$:/language/ControlPanel/Basics/Language/Prompt">
|
||||
<$view tiddler="$:/language/ControlPanel/Basics/Language/Prompt"/>
|
||||
</$view>
|
||||
</span>
|
||||
<span class="tc-language-list-name tc-image-button">
|
||||
<$set name="languagePluginTitle" value=<<currentTiddler>>>
|
||||
<$transclude subtiddler=<<flag-title>>>
|
||||
@ -27,6 +22,9 @@ TiddlyWiki is currently available in <$count filter="[[$:/languages/en-GB]] [plu
|
||||
<$view field="title"/>
|
||||
</$view>
|
||||
</$view>
|
||||
<$view subtiddler="$:/language/ControlPanel/Basics/Language/Prompt">
|
||||
<$view tiddler="$:/language/ControlPanel/Basics/Language/Prompt"/>
|
||||
</$view>
|
||||
</span>
|
||||
</div>
|
||||
</$list>
|
||||
|
@ -19,7 +19,8 @@ html body .tc-subtitle {
|
||||
}
|
||||
|
||||
.tc-view-toolbar-tools {
|
||||
font-size: 0.7em;
|
||||
font-size: 0.5em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.tc-view-toolbar-tools p {
|
||||
|
@ -1,6 +1,6 @@
|
||||
caption: 5.1.22
|
||||
created: 20190910152413608
|
||||
modified: 20190910152413608
|
||||
created: 20200401124910939
|
||||
modified: 20200401124910939
|
||||
tags: ReleaseNotes
|
||||
title: Release 5.1.22
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -9,6 +9,8 @@ type: text/vnd.tiddlywiki
|
||||
|
||||
! Major Improvements
|
||||
|
||||
!! Dynamic Plugin Loading
|
||||
|
||||
Plugins [[can now|https://github.com/Jermolene/TiddlyWiki5/pull/4259]] be loaded or deleted dynamically, without requiring a reload -- as long as they don't contain any ~JavaScript modules. Plugins that require a reload are indicated in the plugin chooser in [[$:/ControlPanel]].
|
||||
|
||||
The [[plugin listing|$:/core/ui/ControlPanel/Plugins]] and the [[plugin chooser|$:/core/ui/ControlPanel/Modals/AddPlugins]] in the control panel feature several improvements:
|
||||
@ -17,25 +19,97 @@ The [[plugin listing|$:/core/ui/ControlPanel/Plugins]] and the [[plugin chooser|
|
||||
* Plugins may now declare dependencies that are automatically installed without user intervention
|
||||
* All plugins now feature a concise, informal name (in addition to the description and title)
|
||||
|
||||
!! Client-Server Improvements
|
||||
|
||||
This release sees several improvements to the client-server configuration:
|
||||
|
||||
* Improved resilience to connection errors. Errors encountered by the browser are automatically retried, and when they succeed the associated error messages are automatically removed
|
||||
* Changes to system tiddlers are now synchronised from the server to the browser
|
||||
* Deletions of tiddlers on the server are now synchronised to the browser
|
||||
* New "cloud" page control button with a dropdown menu showing status and additional actions:
|
||||
** "Refresh from server" to force an immediate check for changes from the server
|
||||
** "Logout"
|
||||
** "Save snapshot for offline use" to save a copy of the wiki without the client-server components
|
||||
** "Copy syncer logs to clipboard" to make it easier to ask end users for debugging information
|
||||
* Added "filter" and "exclude" parameters to the [[GET /recipes/default/tiddlers.json|WebServer API: Get All Tiddlers]] endpoint to allow the returned tiddlers and fields to be filtered
|
||||
* Enhanced LazyLoading to include skinny versions of tiddlers in the HTML file, and to avoid unnecessary loads from the server
|
||||
* The official plugin library is now disabled when the tiddlyweb adaptor is active
|
||||
* Internal file system details are no longer exposed to browsers when a file request fails (see https://github.com/Jermolene/TiddlyWiki5/issues/3724)
|
||||
|
||||
See the [[pull request on GitHub for more details|https://github.com/Jermolene/TiddlyWiki5/pull/4373]].
|
||||
|
||||
!! New Menu Bar Plugin
|
||||
|
||||
[[Added|https://github.com/Jermolene/TiddlyWiki5/commit/ad2d4503e584ce9f931dbd1e98f95738b2841e51]] new ''menubar'' plugin that creates a menu bar that is responsive on mobile devices, and supports search, dropdowns and links
|
||||
|
||||
!! New Freelinks Plugin
|
||||
|
||||
[[Added|https://github.com/Jermolene/TiddlyWiki5/commit/447cd56da9db2ee169607f32923081ac47e78354]] new ''freelinks'' plugin to automatically create links from any tiddler title, without having to type double square brackets or use CamelCase.
|
||||
|
||||
!! New Dynannotate Plugin
|
||||
|
||||
[[Added|https://github.com/Jermolene/TiddlyWiki5/commit/9b48a1c82955d1050c426fef559f42f4b8ec56e7]] new ''dynannotate'' plugin containing primitives for overlaying annotations or highlights over other content
|
||||
|
||||
!! New Share Plugin
|
||||
|
||||
[[Added|https://github.com/Jermolene/TiddlyWiki5/commit/005c584d85505b9477169ef822752eed39dde66a]] new ''share'' plugin for sharing tiddlers via the URL location hash
|
||||
|
||||
!! Improved Markdown Plugin
|
||||
|
||||
[[Switched|https://github.com/Jermolene/TiddlyWiki5/pull/3876]] to the newer and improved [[remarkable|https://github.com/jonschlinkert/remarkable]] library
|
||||
|
||||
! Translation Improvements
|
||||
|
||||
* Improved Chinese translation
|
||||
* Improved Dutch translation
|
||||
* Improved Portuguese translation
|
||||
|
||||
! Performance Improvements
|
||||
|
||||
* [[Optimised|https://github.com/Jermolene/TiddlyWiki5/pull/4108]] ImportVariablesWidget to improve performance of global macros
|
||||
* [[Optimised|https://github.com/Jermolene/TiddlyWiki5/commit/18f5333e01eb62ece65a86c457bf8a398f156930]] core SVG images to save approximately 100KB from the core plugin
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4421]] indexer for backlinks
|
||||
|
||||
! Usability Improvements
|
||||
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4238]] support for specifying the field that should receive focus when editing or creating a tiddler
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/96eca32b1105416c317101aa91dd284f835a8ba8]] download button for binary tiddlers
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/pull/4293]] ([[and here|https://github.com/Jermolene/TiddlyWiki5/pull/4296]]) spacing for page control buttons
|
||||
* [[Improved|https://github.com/Jermolene/TiddlyWiki5/commit/9395d7567179c436d0e8ac26fc976d717eae7f50]] display of icons in documentation
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4238]] support in control panel for specifying the field that should receive focus when editing or creating a tiddler
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4314]] support in control panel for specifying the tags for new tiddlers and journals
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/05a3e1ad36c2fb383dd4975a578f95e8a6c3f325]] language attribute to HTML document to improve accessibility
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/pull/3943]] scaling of embedded videos
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4361]] "none" option in icon dropdown in $:/TagManager
|
||||
* Fixed a number of layout issues relating to the edit template and tag picker (see [[18151cc1|https://github.com/Jermolene/TiddlyWiki5/commit/18151cc193e8c0d613663d3ecf6437c63e540c2d]], [[793d84bc|https://github.com/Jermolene/TiddlyWiki5/commit/793d84bcb1bc52ee77c49090268dd242017cdaa9]], [[1cf2d079|https://github.com/Jermolene/TiddlyWiki5/commit/1cf2d0799d7027d58f4bdca857bc342dd778a330]], [[3c365a25|https://github.com/Jermolene/TiddlyWiki5/commit/3c365a2567ebfe12d78b0aed77a40969cd38563e]], [[45def4de|https://github.com/Jermolene/TiddlyWiki5/commit/45def4def441554a06c3e17742adc29a17d2a13c]], [[89728d8a|https://github.com/Jermolene/TiddlyWiki5/commit/89728d8a9a9fd9b888deb78f420dd5758ee63909]], [[4cf96e73|https://github.com/Jermolene/TiddlyWiki5/commit/4cf96e7339b3b7d8e84a5b73d9871cdad50c5a84]], [[bfa3ddd0|https://github.com/Jermolene/TiddlyWiki5/commit/bfa3ddd077404ad4627b9161bcadf3d5a539c1dc]] and [[74172b35|https://github.com/Jermolene/TiddlyWiki5/commit/74172b35ceae84f254e20d6d7ec4dff2cca692b1]])
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4494]] new ''Gruvbox Dark'' palette
|
||||
* [[Improved|https://github.com/Jermolene/TiddlyWiki5/pull/4515]] (and [[https://github.com/Jermolene/TiddlyWiki5/pull/4517|here]])) Vanilla and Nord palettes
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/152125f53b9979bfd5511973038b4358d1ef48c4]] Railroad plugin to use standard palette colours
|
||||
* [[Improved|https://github.com/Jermolene/TiddlyWiki5/commit/16c1cbee292604b2b8be36a15d5828893d132b20]] Comment plugin to add a link on each comment to the original post
|
||||
|
||||
! Hackability Improvements
|
||||
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4491]] new saver for https://gitea.io/
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/24d2804799b09278c4bb83918d8b75dfa49dbed4]] new ActionPopupWidget for triggering popups
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4442]] ''tabindex'' attribute to the ButtonWidget
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/bfbd886a8bbf2f6ce147ea526eec2098357d3ae7]] ''index'' attribute to the RangeWidget enabling it to be used with DataTiddlers
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/6089c4de2921df0f76f605f1830fb2c04548f73c]] support for RefreshThrottling via the `throttle.refresh` field
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/1150c87edb7478af6cc943eb0ef52fdf3051c121]] (and [[here|https://github.com/Jermolene/TiddlyWiki5/commit/8c894612914e21cf941a1daa953538c28ce91d8e]]) new `[is[binary]]` operand for the [[is Operator]]
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/68163684a2e57108e160295e445c194268b873c5]] usage of `publishFilter` in save templates -- see SavingMechanism
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/89716bb81d68b9c68d3c0fd2f2de96afad1b086a]] CSS class identifying the tiddler body editor
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/e9211b0eee8e0b081f9f1597bde6673bf4c55d5c]] CSS classes to identify sidebar tabsets
|
||||
* [[Updated|https://github.com/Jermolene/TiddlyWiki5/pull/4208]] normalize.css from v3.0.0 to v8.0.1
|
||||
* [[Updated|https://github.com/Jermolene/TiddlyWiki5/pull/4502]] Highlight plugin to use highlight.js v9.18.1
|
||||
* [[Updated|https://github.com/Jermolene/TiddlyWiki5/pull/4226]] Jasmine to v3
|
||||
* [[Refactored|https://github.com/Jermolene/TiddlyWiki5/commit/7b66df688ae745537929a25799ef4a72d4437fcd]] AndTidWiki saver
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/5a5c967a3943beb6a4fa513cb34d231e46304452]] new [[SystemTag: $:/tags/Macro/View]] for creating macros that are only available within the view template
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4404]] support for embedding `.webm` and `.ogg` video files
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/3df1f9c9d0cc92b596262c0220ecf529c7fbb858]] ''spaces'' parameter to [[jsontiddlers Macro]] for controlling the formatting of the output
|
||||
* [[Updated|https://github.com/Jermolene/TiddlyWiki5/commit/4afde5a722afc91c826305800ba536c5fe8ef2e5]] the [[colour Macro]] to add support for specifying a fallback for colours not in the current colour palette
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/6091b013864af3d9918df69894f4aa05d1b8ffeb]] new [[Hidden Setting: Default Tiddler Icon]]
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/bc687e57834efa312cca126af222613ef241c585]] new [[Hidden Setting: Disable Drag and Drop]]
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/commit/ae9ce4f01c6048aeb5604a93b57c2f3e4f959162]] new ''tc-tiddler-overridden-shadow'' class to tiddler frames for tiddlers that override a shadow tiddler
|
||||
* [[Added|https://github.com/Jermolene/TiddlyWiki5/pull/4490]] support for a custom class to modal wrapper
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/091bcfce7d1a9c09140992e649f41ae17d27f307]] [[datauri Macro]] to work with tiddlers with a ''_canonical_uri'' field
|
||||
|
||||
! Bug Fixes
|
||||
|
||||
@ -44,12 +118,36 @@ The [[plugin listing|$:/core/ui/ControlPanel/Plugins]] and the [[plugin chooser|
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/issues/4237]] bug with permaview button when placed above the search box
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/a054d100e73db95071299e92c4321c2aa8e42382]] usage of ''count'' parameter of [[WidgetMessage: tm-edit-text-operation]]
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/issues/4284]] bug with toc-selective-expandable macro still showing disclosure arrow despite excluding tiddlers
|
||||
* [[Removed|https://github.com/Jermolene/TiddlyWiki5/commit/81f1e6af4e5920c6ff41e7f08171bfddc1b26dfc]] erroneous word break setting for vertical tabs
|
||||
* [[Removed|https://github.com/Jermolene/TiddlyWiki5/commit/81f1e6af4e5920c6ff41e7f08171bfddc1b26dfc]] (and [[here|https://github.com/Jermolene/TiddlyWiki5/commit/46c90af308015242fa0314d85f1524727e2aa7e5]]) erroneous word break setting for vertical tabs
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/d30eacc6520971c95bdabf24f4c4122534d9414a]] problem with programmatically deselecting entries from the SelectWidget in multiple selection mode
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/pull/4333]] usage of spans in colour picker
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/e84c422e5091c02f55db4027faa9ba840e2aee6c]] refreshing of RangeWidget when underlying value changes
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/e4eaae14faa1bf867c0f8168e221cf30ac6e2e0d]] problem with splash screen being shown when JavaScript is disabled
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/8f3da69f818940eb5f517da850fb3766b72c7d7d]] problem with millisecond 0XXX DateFormat
|
||||
* [[Improve|https://github.com/Jermolene/TiddlyWiki5/commit/174a36cda63127d19230bcfbe9a5fdde46e5b0ea]] compatibility of single tiddler window vs. main window
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/015306dfc9099f4d5d9415b64266d328a154b119]] problems with some core icons in Internet Explorer
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/3eacdc19fdb4ed7ce864a04dd87a5e1c6492daac]] GitHub and GitLab savers to use default path of `/` if none specified
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/pull/4432]] support for embedding ZIP files in some environments
|
||||
* [[Fixed|https://github.com/Jermolene/TiddlyWiki5/commit/665b63ec38b75dfe62009d2f5514682de60e953f]] lack of refresh when ButtonWidget ''actions'' attribute changes
|
||||
|
||||
! Contributors
|
||||
|
||||
[[@Jermolene|https://github.com/Jermolene]] would like to thank the contributors to this release who have generously given their time to help improve TiddlyWiki:
|
||||
|
||||
* [[@activescott|https://github.com/activescott]]
|
||||
* [[@BramChen|https://github.com/BramChen]]
|
||||
* [[@BurningTreeC|https://github.com/BurningTreeC]]
|
||||
* [[@donmor|https://github.com/donmor]]
|
||||
* [[@ento|https://github.com/ento]]
|
||||
* [[@flibbles|https://github.com/flibbles]]
|
||||
* [[@gernert|https://github.com/gernert]]
|
||||
* [[@heronils|https://github.com/heronils]]
|
||||
* [[@hoelzro|https://github.com/hoelzro]]
|
||||
* [[@Janno|https://github.com/Janno]]
|
||||
* [[@joshuafontany|https://github.com/joshuafontany]]
|
||||
* [[@m42e|https://github.com/m42e]]
|
||||
* [[@MidnightLightning|https://github.com/MidnightLightning]]
|
||||
* [[@pmario|https://github.com/pmario]]
|
||||
* [[@s-l-lee|https://github.com/s-l-lee]]
|
||||
* [[@s-light|https://github.com/s-light]]
|
||||
* [[@saqimtiaz|https://github.com/saqimtiaz]]
|
||||
|
@ -1,2 +1,2 @@
|
||||
title: $:/themes/tiddlywiki/vanilla/options/stickytitles
|
||||
text: yes
|
||||
text: no
|
||||
|
@ -8,14 +8,14 @@
|
||||
"tiddlywiki/powered-by-tiddlywiki",
|
||||
"tiddlywiki/internals",
|
||||
"tiddlywiki/highlight",
|
||||
"tiddlywiki/markdown",
|
||||
"tiddlywiki/bibtex",
|
||||
"tiddlywiki/savetrail",
|
||||
"tiddlywiki/external-attachments",
|
||||
"tiddlywiki/dynaview",
|
||||
"tiddlywiki/dynannotate",
|
||||
"tiddlywiki/codemirror",
|
||||
"tiddlywiki/comments",
|
||||
"tiddlywiki/browser-storage"
|
||||
"tiddlywiki/menubar"
|
||||
],
|
||||
"themes": [
|
||||
"tiddlywiki/vanilla",
|
||||
|
@ -12,11 +12,6 @@
|
||||
"build": {
|
||||
"index": [
|
||||
"--rendertiddler","$:/plugins/tiddlywiki/tiddlyweb/save/offline","index.html","text/plain"],
|
||||
"externalimages": [
|
||||
"--savetiddlers","[is[image]]","images",
|
||||
"--setfield","[is[image]]","_canonical_uri","$:/core/templates/canonical-uri-external-image","text/plain",
|
||||
"--setfield","[is[image]]","text","","text/plain",
|
||||
"--rendertiddler","$:/plugins/tiddlywiki/tiddlyweb/save/offline","externalimages.html","text/plain"],
|
||||
"static": [
|
||||
"--rendertiddler","$:/core/templates/static.template.html","static.html","text/plain",
|
||||
"--rendertiddler","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
||||
|
14
editions/share/tiddlywiki.info
Normal file
14
editions/share/tiddlywiki.info
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"description": "Sharing tiddlers via URLs",
|
||||
"plugins": [
|
||||
"tiddlywiki/share"
|
||||
],
|
||||
"themes": [
|
||||
"tiddlywiki/vanilla",
|
||||
"tiddlywiki/snowwhite"
|
||||
],
|
||||
"build": {
|
||||
"share": [
|
||||
"--rendertiddler","$:/core/save/all","share.html","text/plain"]
|
||||
}
|
||||
}
|
132
editions/test/tiddlers/tests/test-backlinks.js
Normal file
132
editions/test/tiddlers/tests/test-backlinks.js
Normal file
@ -0,0 +1,132 @@
|
||||
/*\
|
||||
title: test-backlinks.js
|
||||
type: application/javascript
|
||||
tags: [[$:/tags/test-spec]]
|
||||
|
||||
Tests the backlinks mechanism.
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
describe('Backlinks tests', function() {
|
||||
describe('a tiddler with no links to it', function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestIncoming',
|
||||
text: ''});
|
||||
|
||||
it('should have no backlinks', function() {
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('A tiddler added to the wiki with a link to it', function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestIncoming',
|
||||
text: ''});
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestOutgoing',
|
||||
text: 'A link to [[TestIncoming]]'});
|
||||
|
||||
it('should have a backlink', function() {
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('A tiddler that has a link added to it later', function() {
|
||||
it('should have an additional backlink', function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestIncoming',
|
||||
text: ''});
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestOutgoing',
|
||||
text: 'A link to [[TestIncoming]]'});
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestOutgoing2',
|
||||
text: 'Nothing yet!'});
|
||||
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestOutgoing2',
|
||||
text: 'Updated with link to [[TestIncoming]]'});
|
||||
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing,TestOutgoing2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('A tiddler that has a link remove from it later', function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestIncoming',
|
||||
text: ''});
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestOutgoing',
|
||||
text: 'A link to [[TestIncoming]]'});
|
||||
|
||||
it('should have one fewer backlink', function() {
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestOutgoing',
|
||||
text: 'No link to ~TestIncoming'});
|
||||
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('A tiddler linking to another that gets renamed', function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestIncoming',
|
||||
text: ''});
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestOutgoing',
|
||||
text: 'A link to [[TestIncoming]]'});
|
||||
|
||||
it('should have its name changed in the backlinks', function() {
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||
|
||||
wiki.renameTiddler('TestOutgoing', 'TestExtroverted');
|
||||
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestExtroverted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('A tiddler linking to another that gets deleted', function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestIncoming',
|
||||
text: ''});
|
||||
|
||||
wiki.addTiddler({
|
||||
title: 'TestOutgoing',
|
||||
text: 'A link to [[TestIncoming]]'});
|
||||
|
||||
it('should be removed from backlinks', function() {
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||
|
||||
wiki.deleteTiddler('TestOutgoing');
|
||||
|
||||
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
@ -8,8 +8,10 @@ Tests the filtering mechanism.
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false, describe: false, it: false, expect: false, require: false*/
|
||||
/* jslint node: true, browser: true */
|
||||
/* eslint-env node, browser, jasmine */
|
||||
/* eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/
|
||||
/* global $tw, require */
|
||||
"use strict";
|
||||
|
||||
describe("Filter tests", function() {
|
||||
@ -421,13 +423,8 @@ function runTests(wiki) {
|
||||
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
|
||||
var rootWidget = new widget.widget({
|
||||
type: "widget",
|
||||
children: [{type: "widget", children: []}]
|
||||
},{
|
||||
wiki: wiki,
|
||||
document: $tw.document
|
||||
});
|
||||
var rootWidget = new widget.widget({ type:"widget", children:[ {type:"widget", children:[]} ] },
|
||||
{ wiki:wiki, document:$tw.document});
|
||||
rootWidget.makeChildWidgets();
|
||||
var anchorWidget = rootWidget.children[0];
|
||||
rootWidget.setVariable("myVar","Tidd");
|
||||
@ -470,6 +467,8 @@ function runTests(wiki) {
|
||||
expect(wiki.filterTiddlers("1 2 3 4 +[min[2]]").join(",")).toBe("1,2,2,2");
|
||||
});
|
||||
|
||||
/* listops filters */
|
||||
|
||||
it("should handle the allafter operator", function() {
|
||||
expect(wiki.filterTiddlers("1 2 3 4 +[allafter[]]").join(",")).toBe("");
|
||||
expect(wiki.filterTiddlers("1 2 3 4 +[allafter:include[]]").join(",")).toBe("");
|
||||
@ -500,6 +499,164 @@ function runTests(wiki) {
|
||||
expect(wiki.filterTiddlers("1 2 3 4 +[allbefore:include[5]]").join(",")).toBe("");
|
||||
});
|
||||
|
||||
it("should handle the append operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c +[append[d e]]").join(",")).toBe("a,b,c,d,e");
|
||||
expect(wiki.filterTiddlers("a b c +[append:1[d e]]").join(",")).toBe("a,b,c,d");
|
||||
expect(wiki.filterTiddlers("a b c +[append{TiddlerSeventh!!list}]").join(",")).toBe("a,b,c,TiddlerOne,Tiddler Three,a fourth tiddler,MissingTiddler");
|
||||
expect(wiki.filterTiddlers("a b c +[append:2{TiddlerSeventh!!list}]").join(",")).toBe("a,b,c,TiddlerOne,Tiddler Three");
|
||||
|
||||
expect(wiki.filterTiddlers("a [[b c]] +[append{TiddlerSix!!filter}]").join(",")).toBe("a,b c,one,a a,[subfilter{hasList!!list}]");
|
||||
});
|
||||
|
||||
it("should handle the insertbefore operator", function() {
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
var rootWidget = new widget.widget({ type:"widget", children:[ {type:"widget", children:[]} ] },
|
||||
{ wiki:wiki, document:$tw.document});
|
||||
rootWidget.makeChildWidgets();
|
||||
var anchorWidget = rootWidget.children[0];
|
||||
rootWidget.setVariable("myVar","c");
|
||||
rootWidget.setVariable("tidTitle","e");
|
||||
rootWidget.setVariable("tidList","one tid with spaces");
|
||||
|
||||
expect(wiki.filterTiddlers("a b c d e f +[insertbefore:myVar[f]]",anchorWidget).join(",")).toBe("a,b,f,c,d,e");
|
||||
expect(wiki.filterTiddlers("a b c d e f +[insertbefore:myVar<tidTitle>]",anchorWidget).join(",")).toBe("a,b,e,c,d,f");
|
||||
expect(wiki.filterTiddlers("a b c d e f +[insertbefore:myVar[gg gg]]",anchorWidget).join(",")).toBe("a,b,gg gg,c,d,e,f");
|
||||
|
||||
expect(wiki.filterTiddlers("a b c d e +[insertbefore:myVar<tidList>]",anchorWidget).join(",")).toBe("a,b,one tid with spaces,c,d,e");
|
||||
expect(wiki.filterTiddlers("a b c d e f +[insertbefore:tidTitle{TiddlerOne!!tags}]",anchorWidget).join(",")).toBe("a,b,c,d,one,e,f");
|
||||
|
||||
// Next 2 tests do weired things, but will pass - there for compatibility reasons
|
||||
expect(wiki.filterTiddlers("a b c [[with space]] +[insertbefore[b]]").join(",")).toBe("a,c,with space,b");
|
||||
expect(wiki.filterTiddlers("a b c d e +[insertbefore:2[b]]").join(",")).toBe("a,c,d,e,b");
|
||||
});
|
||||
|
||||
it("should handle the move operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c d e +[move[c]]").join(",")).toBe("a,b,d,c,e");
|
||||
expect(wiki.filterTiddlers("a b c d e +[move:2[c]]").join(",")).toBe("a,b,d,e,c");
|
||||
expect(wiki.filterTiddlers("a b c d e +[move:10[c]]").join(",")).toBe("a,b,d,e,c");
|
||||
expect(wiki.filterTiddlers("a b c d e +[move:-1[c]]").join(",")).toBe("a,c,b,d,e");
|
||||
expect(wiki.filterTiddlers("a b c d e +[move:-5[c]]").join(",")).toBe("c,a,b,d,e");
|
||||
});
|
||||
|
||||
it("should handle the prepend operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c +[prepend[dd ee]]").join(",")).toBe("dd,ee,a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[prepend:3[ff gg]]").join(",")).toBe("ff,gg,a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[prepend:1[hh ii]]").join(",")).toBe("hh,a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[prepend:0[jj kk]]").join(",")).toBe("a,b,c");
|
||||
|
||||
expect(wiki.filterTiddlers("a b c +[prepend:-0[ll mm]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[prepend:-1[nn oo pp qq]]").join(",")).toBe("nn,oo,pp,a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[prepend:-2[rr ss tt uu]]").join(",")).toBe("rr,ss,a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[prepend:-4[rr ss tt uu]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[prepend:-5[vv ww xx yy]]").join(",")).toBe("a,b,c");
|
||||
});
|
||||
|
||||
it("should handle the putafter operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[putafter[b]]").join(",")).toBe("a,b,ee,c,dd");
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[putafter:1[b]]").join(",")).toBe("a,b,ee,c,dd");
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[putafter:2[b]]").join(",")).toBe("a,b,dd,ee,c");
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[putafter:3[b]]").join(",")).toBe("a,b,c,dd,ee");
|
||||
// It starts to duplicate elements
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[putafter:4[b]]").join(",")).toBe("a,b,b,c,dd,ee");
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[putafter:5[b]]").join(",")).toBe("a,b,a,b,c,dd,ee");
|
||||
// There are only 5 elements in the input
|
||||
expect(wiki.filterTiddlers("a b c ff gg +[putafter:6[b]]").join(",")).toBe("a,b,a,b,c,ff,gg");
|
||||
|
||||
// -1 starts to "eat" elements for the left and duplicates b
|
||||
expect(wiki.filterTiddlers("a b c hh ii +[putafter:-1[b]]").join(",")).toBe("a,b,b,c,hh,ii");
|
||||
// -2 moves c, hh, ii behind b, which is not visible
|
||||
expect(wiki.filterTiddlers("a b c hh ii +[putafter:-2[b]]").join(",")).toBe("a,b,c,hh,ii");
|
||||
// only ii is used from input and it's moved behind b
|
||||
expect(wiki.filterTiddlers("a b c hh ii +[putafter:-4[b]]").join(",")).toBe("a,b,ii,c,hh");
|
||||
// wasting time, because there are only 5 elements
|
||||
expect(wiki.filterTiddlers("a b c hh ii +[putafter:-5[b]]").join(",")).toBe("a,b,c,hh,ii");
|
||||
// there are only 5 elements
|
||||
expect(wiki.filterTiddlers("a b c hh ii +[putafter:-10[b]]").join(",")).toBe("a,b,c,hh,ii");
|
||||
|
||||
// use NAN uses default = 1
|
||||
expect(wiki.filterTiddlers("a b c jj kk +[putafter:NAN[b]]").join(",")).toBe("a,b,kk,c,jj");
|
||||
});
|
||||
|
||||
it("should handle the putbefore operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c dd +[putbefore[b]]").join(",")).toBe("a,dd,b,c");
|
||||
expect(wiki.filterTiddlers("a b c ff +[putbefore:1[b]]").join(",")).toBe("a,ff,b,c");
|
||||
expect(wiki.filterTiddlers("a b c gg +[putbefore:2[b]]").join(",")).toBe("a,c,gg,b");
|
||||
|
||||
expect(wiki.filterTiddlers("a b c [[g g]] +[putbefore:2[b]]").join(",")).toBe("a,c,g g,b");
|
||||
|
||||
// this one is strange
|
||||
expect(wiki.filterTiddlers("a b c ee +[putbefore:0[b]]").join(",")).toBe("a,a,b,c,ee");
|
||||
|
||||
// b is not part of the list anymore, so it will be appended at the end ???
|
||||
expect(wiki.filterTiddlers("a b c hh +[putbefore:3[b]]").join(",")).toBe("a,b,c,hh");
|
||||
expect(wiki.filterTiddlers("a b c ii +[putbefore:4[b]]").join(",")).toBe("a,a,b,c,ii");
|
||||
|
||||
// ????
|
||||
expect(wiki.filterTiddlers("a b c ii +[putbefore:10[b]]").join(",")).toBe("a,a,b,c,ii");
|
||||
|
||||
expect(wiki.filterTiddlers("a b c kk +[putbefore:-1[b]]").join(",")).toBe("a,b,c,kk");
|
||||
expect(wiki.filterTiddlers("a b c ll +[putbefore:-2[b]]").join(",")).toBe("a,c,ll,b");
|
||||
|
||||
expect(wiki.filterTiddlers("a b c mm +[putbefore:-3[b]]").join(",")).toBe("a,mm,b,c");
|
||||
|
||||
expect(wiki.filterTiddlers("a b c nn +[putbefore:-10[b]]").join(",")).toBe("a,b,c,nn");
|
||||
});
|
||||
|
||||
it("should handle the putfirst operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst[a b]]").join(",")).toBe("c,a,b");
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst[]]").join(",")).toBe("c,a,b");
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst:2[]]").join(",")).toBe("b,c,a");
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst:3[]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst:4[]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst:-0[]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst:-1[]]").join(",")).toBe("b,c,a");
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst:-2[]]").join(",")).toBe("c,a,b");
|
||||
expect(wiki.filterTiddlers("a b c +[putfirst:-4[]]").join(",")).toBe("a,b,c");
|
||||
});
|
||||
|
||||
it("should handle the putlast operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c +[putlast[d e]]").join(",")).toBe("b,c,a");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast[]]").join(",")).toBe("b,c,a");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:1[]]").join(",")).toBe("b,c,a");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:2[]]").join(",")).toBe("c,a,b");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:3[]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:4[]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:-0[]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:0[]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:-1[]]").join(",")).toBe("c,a,b");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:-2[]]").join(",")).toBe("b,c,a");
|
||||
expect(wiki.filterTiddlers("a b c +[putlast:-4[]]").join(",")).toBe("a,b,c");
|
||||
});
|
||||
|
||||
it("should handle the remove operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c +[remove[d e]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[remove[a]]").join(",")).toBe("b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[remove[c b a]]").join(",")).toBe("");
|
||||
});
|
||||
|
||||
it("should handle the replace operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c dd +[replace[a]]").join(",")).toBe("dd,b,c");
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[replace:2[a]]").join(",")).toBe("dd,ee,b,c");
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[replace:5[c]]").join(",")).toBe("a,b,a,b,c,dd,ee");
|
||||
|
||||
// strange things happen.
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[replace:-1[c]]").join(",")).toBe("a,b,b,c,dd,ee");
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[replace:-2[c]]").join(",")).toBe("a,b,c,dd,ee");
|
||||
expect(wiki.filterTiddlers("a b c dd ee +[replace:-2[ee]]").join(",")).toBe("a,b,c,dd,c,dd,ee");
|
||||
});
|
||||
|
||||
it("should handle the sortby operator", function() {
|
||||
expect(wiki.filterTiddlers("a b c +[sortby[d e]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("a b c +[sortby[b c a]]").join(",")).toBe("b,c,a");
|
||||
expect(wiki.filterTiddlers("aa a b c +[sortby[b c a cc]]").join(",")).toBe("aa,b,c,a");
|
||||
expect(wiki.filterTiddlers("a bb b c +[sortby[b c a cc]]").join(",")).toBe("bb,b,c,a");
|
||||
expect(wiki.filterTiddlers("a bb cc b c +[sortby[b c a cc]]").join(",")).toBe("bb,b,c,a,cc");
|
||||
|
||||
expect(wiki.filterTiddlers("b a b c +[sortby[]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("b a b c +[sortby[a b b c]]").join(",")).toBe("a,b,c");
|
||||
expect(wiki.filterTiddlers("b a b c +[sortby[b a c b]]").join(",")).toBe("b,a,c");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -465,6 +465,90 @@ describe("Widget module", function() {
|
||||
expect(wrapper.innerHTML).toBe("<p>nothing</p>");
|
||||
});
|
||||
|
||||
/**This test confirms that imported set variables properly refresh
|
||||
* if they use transclusion for their value. This relates to PR #4108.
|
||||
*/
|
||||
it("should refresh imported <$set> widgets", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "Raw", text: "Initial value"},
|
||||
{title: "Macro", text: "<$set name='test' value={{Raw}}>\n\ndummy text</$set>"},
|
||||
{title: "Caller", text: text}
|
||||
]);
|
||||
var text = "\\import Macro\n<<test>>";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
// Render the widget node to the DOM
|
||||
var wrapper = renderWidgetNode(widgetNode);
|
||||
// Test the rendering
|
||||
expect(wrapper.innerHTML).toBe("<p>Initial value</p>");
|
||||
wiki.addTiddler({title: "Raw", text: "New value"});
|
||||
// Refresh
|
||||
refreshWidgetNode(widgetNode,wrapper,["Raw"]);
|
||||
expect(wrapper.innerHTML).toBe("<p>New value</p>");
|
||||
});
|
||||
|
||||
it("should can mix setWidgets and macros when importing", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "A", text: "\\define A() Aval"},
|
||||
{title: "B", text: "<$set name='B' value='Bval'>\n\ndummy text</$set>"},
|
||||
{title: "C", text: "\\define C() Cval"}
|
||||
]);
|
||||
var text = "\\import A B C\n<<A>> <<B>> <<C>>";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
// Render the widget node to the DOM
|
||||
var wrapper = renderWidgetNode(widgetNode);
|
||||
// Test the rendering
|
||||
expect(wrapper.innerHTML).toBe("<p>Aval Bval Cval</p>");
|
||||
});
|
||||
|
||||
/** Special case. <$importvariables> has different handling if
|
||||
* it doesn't end up importing any variables. Make sure it
|
||||
* doesn't forget its childrenNodes.
|
||||
*/
|
||||
it("should work when import widget imports nothing", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var text = "\\import [prefix[XXX]]\nDon't forget me.";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
// Render the widget node to the DOM
|
||||
var wrapper = renderWidgetNode(widgetNode);
|
||||
// Test the rendering
|
||||
expect(wrapper.innerHTML).toBe("<p>Don't forget me.</p>");
|
||||
});
|
||||
|
||||
/** This test reproduces issue #4504.
|
||||
*
|
||||
* The importvariable widget was creating redundant copies into
|
||||
* itself of variables in widgets higher up in the tree. Normally,
|
||||
* this caused no errors, but it would mess up qualify-macros.
|
||||
* They would find multiple instances of the same transclusion
|
||||
* variable if a transclusion occured higher up in the widget tree
|
||||
* than an importvariables AND that importvariables was importing
|
||||
* at least ONE variable.
|
||||
*/
|
||||
it("adding imported variables doesn't change qualifyers", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
function wikiparse(text) {
|
||||
var tree = parseText(text,wiki);
|
||||
var widgetNode = createWidgetNode(tree,wiki);
|
||||
var wrapper = renderWidgetNode(widgetNode);
|
||||
return wrapper.innerHTML;
|
||||
};
|
||||
wiki.addTiddlers([
|
||||
{title: "body", text: "\\import A\n<<qualify this>>"},
|
||||
{title: "A", text: "\\define unused() ignored"}
|
||||
]);
|
||||
// This transclude wraps "body", which has an
|
||||
// importvariable widget in it.
|
||||
var withA = wikiparse("{{body}}");
|
||||
wiki.addTiddler({title: "A", text: ""});
|
||||
var withoutA = wikiparse("{{body}}");
|
||||
// Importing two different version of "A" shouldn't cause
|
||||
// the <<qualify>> widget to spit out something different.
|
||||
expect(withA).toBe(withoutA);
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
||||
|
5
editions/tw5.com/tiddlers/$__StoryList.tid
Normal file
5
editions/tw5.com/tiddlers/$__StoryList.tid
Normal file
@ -0,0 +1,5 @@
|
||||
created: 20191004112211823
|
||||
list: [[WidgetMessage: tm-unload-plugin-library]] [[WidgetMessage: tm-load-plugin-library]] [[WidgetMessage: tm-load-plugin-from-library]] HelloThere GettingStarted Community
|
||||
modified: 20191004113621710
|
||||
title: $:/StoryList
|
||||
type: text/vnd.tiddlywiki
|
@ -6,14 +6,14 @@ tags: About
|
||||
|
||||
TiddlyWiki is published under a [[permissive BSD 3-Clause License|https://opensource.org/licenses/BSD-3-Clause]] stored in the [[shadow tiddler|ShadowTiddlers]] [[$:/core/copyright.txt]]:
|
||||
|
||||
<div style="font-size: 0.5em;line-height:1.4;">
|
||||
<div style="font-size: 0.7em;line-height:1.4;">
|
||||
|
||||
{{$:/core/copyright.txt}}
|
||||
|
||||
</div>
|
||||
|
||||
In layman's terms, the license says that you can take TiddlyWiki and do anything you want with it without any license fee payment or other legal obligation to the creators of TiddlyWiki or anyone else. The quid pro quo is that there is no warranty or guarantee with open source projects like TiddlyWiki. You can't sue the contributors to TiddlyWiki for any loss or damage due to the use of TiddlyWiki: even if your data is lost due to a tragic chain of circumstances that involves TiddlyWiki.
|
||||
In layman's terms, the license says that you can take TiddlyWiki and do anything you want with it without any license fee payment or other legal obligation to the creators of ~TiddlyWiki or anyone else. The quid pro quo is that there is no warranty or guarantee with open source projects like ~TiddlyWiki. You can't sue the contributors to ~TiddlyWiki for any loss or damage due to the use of ~TiddlyWiki: even if your data is lost due to a tragic chain of circumstances that involves ~TiddlyWiki.
|
||||
|
||||
You are respectfully requested that to make an attribution to the project, but there's no obligation to do so.
|
||||
|
||||
For the avoidance of doubt, any information that you choose to store within your own copy of TiddlyWiki remains yours; using TiddlyWiki to publish content doesn't change whatever rights you may have to that content.
|
||||
For the avoidance of doubt, any information that you choose to store within your own copy of ~TiddlyWiki remains yours; using ~TiddlyWiki to publish content doesn't change whatever rights you may have to that content.
|
||||
|
@ -13,7 +13,8 @@ The ~TiddlyWiki discussion groups are mailing lists for talking about ~TiddlyWik
|
||||
** An enhanced group search facility is available on [[mail-archive.com|https://www.mail-archive.com/tiddlywiki@googlegroups.com/]]
|
||||
* Watch recordings of our regular [[TiddlyWiki Hangouts]]
|
||||
* Follow [[@TiddlyWiki on Twitter|http://twitter.com/TiddlyWiki]] for the latest news
|
||||
* ''New: Join us on our live chat at https://gitter.im/TiddlyWiki/public !''
|
||||
* New: Join us on our live chat at https://gitter.im/TiddlyWiki/public !
|
||||
* There is also a discord available at https://discord.gg/HFFZVQ8
|
||||
|
||||
|
||||
! Developers
|
||||
|
@ -7,6 +7,6 @@ Extend tiddlywiki to parse complex ("nested") json data tiddlers.
|
||||
|
||||
Json Mangler introduces a new path syntax for indexes of json data tiddlers , and includes many supporting tools, filters, widgets, etc.
|
||||
|
||||
Example Wiki: https://joshuafontany.github.io/TW5-JsonManglerPlugin/
|
||||
Example Wiki: https://joshuafontany.github.io/TW5-JsonMangler/
|
||||
|
||||
Source: https://github.com/joshuafontany/TW5-JsonManglerPlugin
|
||||
Source: https://github.com/joshuafontany/TW5-JsonMangler
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20130825213300000
|
||||
modified: 20191013093910961
|
||||
modified: 20191206152031468
|
||||
tags: Concepts
|
||||
title: TiddlerFields
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -42,11 +42,13 @@ Other fields used by the core are:
|
||||
|`subtitle` |<<lingo subtitle>> |
|
||||
|`throttle.refresh` |<<lingo throttle.refresh>> |
|
||||
|`toc-link`|<<lingo toc-link>>|
|
||||
|`_canonical_uri`|<<lingo _canonical_uri>>|
|
||||
|
||||
The TiddlyWebAdaptor uses a few more fields:
|
||||
|
||||
|!Field Name |!Description |
|
||||
|`bag` |<<lingo bag>> |
|
||||
|`revision` |<<lingo revision>> |
|
||||
|`_is_skinny` |<<lingo _is_skinny>> |
|
||||
|
||||
Details of the fields used in this ~TiddlyWiki are shown in the [[control panel|$:/ControlPanel]] {{$:/core/ui/Buttons/control-panel}} under the <<.controlpanel-tab Info>> tab >> <<.info-tab Advanced>> sub-tab >> Tiddler Fields
|
||||
|
@ -1,12 +1,12 @@
|
||||
created: 20130825161100000
|
||||
modified: 20171110094548887
|
||||
modified: 20200104111952539
|
||||
tags: Definitions
|
||||
title: TiddlyFox
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TiddlyFox is an extension for older versions of Firefox that allows standalone TiddlyWiki files to save their changes directly to the file system. TiddlyFox works on both desktop and smartphone versions of [[Firefox]]. See [[Saving with TiddlyFox]] or [[Saving with TiddlyFox on Android]] for detailed instructions.
|
||||
|
||||
TiddlyFox is now obsolete due to its incompatibility with the latest versions of Firefox - see [[Firefox Apocalypse]]. There are many alternatives to TiddlyFox, but none that work in precisely the same way -- see GettingStarted for details.
|
||||
TiddlyFox is now obsolete due to its incompatibility with the latest versions of Firefox - see [[TiddlyFox Apocalypse]]. There are many alternatives to TiddlyFox, but none that work in precisely the same way -- see GettingStarted for details.
|
||||
|
||||
TiddlyFox can be downloaded from the Mozilla Addons site:
|
||||
|
||||
|
@ -8,10 +8,10 @@ You can import tiddlers into a ~TiddlyWiki from external files or directly from
|
||||
|
||||
!! Importing content from external files
|
||||
|
||||
There are several ways to import content from external files:
|
||||
There are several ways to import content (including text, images, pdf documents, etc.) from external files:
|
||||
|
||||
* Use the <<.icon $:/core/images/import-button>> ''import'' button (under the ''Tools'' tab in the sidebar) to select a local file
|
||||
* Drag and drop files from Windows Explorer or OS X Finder etc. into the TiddlyWiki browser window
|
||||
* Drag and drop files from Windows Explorer or OS X Finder etc. into the ~TiddlyWiki browser window
|
||||
* Paste content directly from the clipboard using the menu or keyboard shortcut (<kbd>ctrl-V</kbd> or <kbd>cmd-V</kbd>)
|
||||
** Currently supported in Chrome, Firefox and Edge (but not Internet Explorer)
|
||||
|
||||
|
@ -22,7 +22,7 @@ The information above should be interpreted as follows:
|
||||
|
||||
As an example, try switching between the sidebar tabs to compare how long they take to render.
|
||||
|
||||
More detailed information on filter execution timings is also available. With performance instrumentation enabled, ype the following JavaScript command in the browser developer console:
|
||||
More detailed information on filter execution timings is also available. With performance instrumentation enabled, type the following JavaScript command in the browser developer console:
|
||||
|
||||
```
|
||||
$tw.perf.log()
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20140419082845576
|
||||
modified: 20160610082458079
|
||||
modified: 20190912093109517
|
||||
tags: Features
|
||||
title: SafeMode
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -8,6 +8,8 @@ type: text/vnd.tiddlywiki
|
||||
|
||||
Safe mode provides a way to disabling most customisations in TiddlyWiki. This is useful because if TiddlyWiki is customised incorrectly it can be rendered inoperable. A particular issue is that some customisations break when upgrading to a newer core version of TiddlyWiki (especially during the beta).
|
||||
|
||||
<<.warning "Safe mode should only be used with the single file configuration of TiddlyWiki, and then only with savers that do not autosave. Using safe mode in the client server configuration can lead to data loss.">>
|
||||
|
||||
! Enabling Safe Mode
|
||||
|
||||
Safe mode is enabled in the browser by starting TiddlyWiki with the URL hash set to the string `#:safe`. For example:
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20140410103123179
|
||||
modified: 20170103175939836
|
||||
modified: 20191209085901849
|
||||
tags: [[Filter Operators]] [[Group Operators]]
|
||||
title: each Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -20,6 +20,6 @@ Each input title is processed in turn. The value of field <<.place F>> in the co
|
||||
;each:value
|
||||
:As long as the title is unique it is appended to the output whether or not the corresponding tiddler exists.
|
||||
|
||||
If a tiddler does not contain field <<.place F>>, it is treated as if the value of the field were empty.
|
||||
Note that if a tiddler does not contain field <<.place F>>, it is treated as if the value of the field were empty. Thus, a filter expression such as `[each[motovun]]` will return one tiddler that doesn't have a `motovun` field, as well as one tiddler with each distinct value of that field, if any. To obtain just the tiddlers that have a non-blank value for the `motovun` field one can use `[each[motovun]has[motovun]]`.
|
||||
|
||||
<<.operator-examples "each">>
|
||||
|
@ -8,3 +8,4 @@ type: text/vnd.tiddlywiki
|
||||
|
||||
<<.operator-example 1 "[list[Days of the Week]allbefore[Wednesday]]">>
|
||||
<<.operator-example 2 "[list[Days of the Week]allbefore:include[Wednesday]]">>
|
||||
<<.operator-example 3 "A B C D E +[allbefore:include[C]count[]]">>
|
||||
|
@ -15,10 +15,12 @@ The following substitutions are made:
|
||||
|
||||
|!Character |!Replacement |
|
||||
|`\` |`\\` |
|
||||
|`"` |`\\` |
|
||||
|`'` |`\\` |
|
||||
|`"` |`\\"` |
|
||||
|`\r` (carriage return) |`\\r` |
|
||||
|`\n` (line feed) |`\\n` |
|
||||
|`\x08` (backpsace) |`\\b` |
|
||||
|`\x0c` (formfield) |`\\f` |
|
||||
|`\t` (tab) |`\\t` |
|
||||
|Characters from 0x00 to 0x1f |`\\x##` where ## is two hex digits |
|
||||
|Characters from 0x80 to 0xffff |`\\u####` where #### is four hex digits |
|
||||
|
||||
|
@ -3,7 +3,7 @@ created: 20190613153225735
|
||||
modified: 20190613153321546
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-output: replaces each input title with its length as an integer
|
||||
op-purpose: returns the lengths of each item in the list
|
||||
op-purpose: returns the number of characters of each item in the list
|
||||
tags: [[Filter Operators]]
|
||||
title: length Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
|
@ -2,8 +2,8 @@ caption: sign
|
||||
created: 20190613084919354
|
||||
modified: 20190613085044655
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-output: replaces each input number with -1, 0 or +1 according to whether the number is negative, zero, or positive
|
||||
op-purpose: return -1, 0 or +1 for a list of numbers according to whether each number is negative, zero, or positive
|
||||
op-output: replaces each input number with -1, 0 or 1 according to whether the number is negative, zero, or positive
|
||||
op-purpose: return -1, 0 or 1 for a list of numbers according to whether each number is negative, zero, or positive
|
||||
tags: [[Unary Mathematics Operators]] [[Filter Operators]] [[Mathematics Operators]]
|
||||
title: sign Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
|
@ -0,0 +1,7 @@
|
||||
created: 20200306145004925
|
||||
modified: 20200306145004925
|
||||
tags: [[Hidden Settings]]
|
||||
title: Hidden Setting: Default Tiddler Icon
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
A default tiddler icon can be specified by creating a tiddler called $:/config/DefaultTiddlerIcon containing the title of the tiddler containing the icon.
|
@ -0,0 +1,19 @@
|
||||
created: 20200315143638556
|
||||
modified: 20200315143638556
|
||||
tags: [[Hidden Settings]]
|
||||
title: Hidden Setting: Disable Drag and Drop
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
To disable all the drag and drop operations that are built into the core, set the following tiddler to "no":
|
||||
|
||||
$:/config/DragAndDrop/Enable
|
||||
|
||||
To selectively re-enable drag and drop for an instance of the [[list-tagged-draggable Macro]] or [[list-links-draggable Macro]] you must ensure that the variable `tv-enable-drag-and-drop` is set to `yes` for the scope of the macro invocation. For example, note how it is still possible to use drag and drop within this list even if $:/config/DragAndDrop/Enable is set to "no":
|
||||
|
||||
<$macrocall $name="wikitext-example-without-html" src="""<$set name="tv-enable-drag-and-drop" value="yes">
|
||||
|
||||
<<list-tagged-draggable tag:"HelloThere">>
|
||||
|
||||
</$set>"""/>
|
||||
|
||||
Note that when using the DropzoneWidget and the DroppableWidget directly the ''enable'' attribute works independently of the global setting.
|
@ -1,5 +1,5 @@
|
||||
created: 20150221154058000
|
||||
modified: 20150221154522000
|
||||
modified: 20200228142855357
|
||||
tags: Macros [[Core Macros]]
|
||||
title: colour Macro
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -7,7 +7,7 @@ caption: colour
|
||||
|
||||
The <<.def colour>> (or <<.def color>>) [[macro|Macros]] returns the [[CSS|Cascading Style Sheets]] value of one the colours in the current [[palette|ColourPalettes]].
|
||||
|
||||
If no such entry exists in the current palette, the [[vanilla palette|$:/palettes/Vanilla]] is used instead.
|
||||
If no such entry exists in the current palette, the [[vanilla palette|$:/palettes/Vanilla]] is used instead. If no such entry exists in the vanilla palette, the system looks for a configuration tiddler with the title `$:/config/DefaultColourMappings/<colour-name>` and transcludes the colour from the text field. This enables to plugins to ship defaults for colours that are not present in the core palettes.
|
||||
|
||||
!! Parameters
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20150221152226000
|
||||
modified: 20150221154213000
|
||||
modified: 20200204135513721
|
||||
tags: Macros [[Core Macros]]
|
||||
title: jsontiddlers Macro
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -13,3 +13,5 @@ An example can be seen in the [[template tiddler for JSON exports|$:/core/templa
|
||||
|
||||
;filter
|
||||
: A [[filter|Filters]] selecting which tiddlers to include
|
||||
;spaces
|
||||
: An optional number of spaces to use for formatting the JSON. Defaults to 4, with blank or zero resulting in packed JSON with no formatting spaces
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20131228163141555
|
||||
modified: 20150221223416000
|
||||
modified: 20200330105334133
|
||||
tags: Macros [[Core Macros]]
|
||||
title: makedatauri Macro
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -15,5 +15,7 @@ The <<.def makedatauri>> [[macro|Macros]] takes a piece of text and an associate
|
||||
: The text to be converted to a data URI
|
||||
;type
|
||||
: The ContentType of the text
|
||||
;_canonical_uri
|
||||
: The optional ''_canonical_uri'' address of the content
|
||||
|
||||
<<.macro-examples "makedatauri">>
|
||||
|
6
editions/tw5.com/tiddlers/menubar/gettingstarted.tid
Normal file
6
editions/tw5.com/tiddlers/menubar/gettingstarted.tid
Normal file
@ -0,0 +1,6 @@
|
||||
title: $:/editions/tw5.com/menuitems/gettingstarted
|
||||
caption: ~GettingStarted
|
||||
description: Link to ~GettingStarted
|
||||
tags: $:/tags/MenuBar
|
||||
target: GettingStarted
|
||||
|
2
editions/tw5.com/tiddlers/menubar/tagsMenuBar.tid
Normal file
2
editions/tw5.com/tiddlers/menubar/tagsMenuBar.tid
Normal file
@ -0,0 +1,2 @@
|
||||
title: $:/tags/MenuBar
|
||||
list:$:/plugins/tiddlywiki/menubar/items/hamburger $:/plugins/tiddlywiki/menubar/items/topleftbar $:/editions/tw5.com/menuitems/gettingstarted $:/plugins/tiddlywiki/menubar/items/contents $:/plugins/tiddlywiki/menubar/items/search $:/plugins/tiddlywiki/menubar/items/pagecontrols $:/plugins/tiddlywiki/menubar/items/server $:/plugins/tiddlywiki/menubar/items/toprightbar
|
@ -1,6 +1,6 @@
|
||||
caption: tm-fold-all-tiddlers
|
||||
created: 20160424230908388
|
||||
modified: 20190205154007291
|
||||
modified: 20191028113838596
|
||||
tags: Messages
|
||||
title: WidgetMessage: tm-fold-all-tiddlers
|
||||
type: text/vnd.tiddlywiki
|
||||
|
@ -1,6 +1,6 @@
|
||||
caption: tm-fold-other-tiddlers
|
||||
created: 20160424232355215
|
||||
modified: 20160424233338710
|
||||
modified: 20191028113932268
|
||||
tags: Messages
|
||||
title: WidgetMessage: tm-fold-other-tiddlers
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -11,8 +11,8 @@ The `tm-fold-other-tiddlers` message folds all tiddlers in the current story lis
|
||||
|param|Title of the tiddler that should be ignored by the fold operation. |
|
||||
|foldedStatePrefix |Prefix for the state tiddler in which the fold state is stored. |
|
||||
|
||||
<<.tip "The core uses a foldStatePrefix of '$:/state/folded/' to store the fold states for the default story view.">>
|
||||
<<.warning "The state tiddlers title is computed as 'foldStatePrefix + TiddlerTitle'. If the foldStatePrefix is not set, it will overwrite the text of the tiddler(s) itself, resulting in data loss. ">>
|
||||
<<.tip "The core uses a foldStatePrefix of `$:/state/folded/` to store the fold states for the default story view.">>
|
||||
<<.warning "The state tiddlers title is computed as `foldStatePrefix + TiddlerTitle`. If the foldStatePrefix is not set, it will overwrite the text of the tiddler(s) itself, resulting in data loss. ">>
|
||||
|
||||
|
||||
The `tm-fold-other-tiddlers` message is usually generated with the ButtonWidget and is handled by the surrounding NavigatorWidget.
|
||||
|
@ -1,16 +1,24 @@
|
||||
caption: tm-fold-tiddler
|
||||
created: 20160424232749223
|
||||
modified: 20160424233102003
|
||||
modified: 20191028113537119
|
||||
tags: Messages
|
||||
title: WidgetMessage: tm-fold-tiddler
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The `tm-fold-tiddler` message folds the tiddler specified in the `param` parameter. It does so by setting the text of a state tiddler to either "<<.value "show">>" or "<<.value "hide">>", according to the fold state.
|
||||
The `tm-fold-tiddler` message toggles the value of a state tiddler. It does so by setting the text of a state tiddler to either "<<.value "show">>" or "<<.value "hide">>", according to the fold state.
|
||||
|
||||
|!Name |!Description |
|
||||
|param|Title of the tiddler that should be folded. If the tiddler is already folded, it will be unfolded instead. |
|
||||
|foldedStatePrefix |Prefix for the state tiddler in which the fold state is stored. If no state prefix is provided, `tm-fold-tiddler` will do nothing. |
|
||||
|param |ignored ... foldedState must be used |
|
||||
|foldedState |State tiddler in which the fold state is stored |
|
||||
|
||||
<<.tip "The core uses a foldStatePrefix of '$:/state/folded/' to store the fold states for the default story view.">>
|
||||
<<.tip "The core uses a foldState prefix of `$:/state/folded/` + `tiddler title` to store the fold states for the default story view.">>
|
||||
|
||||
The `tm-fold-tiddler` message is usually generated with the ButtonWidget and is handled by the surrounding NavigatorWidget.
|
||||
|
||||
The core $:/core/ui/ViewTemplate defines a global variable `folded-state`, that is used with every tiddler. It's created like this:
|
||||
|
||||
```
|
||||
\define folded-state()
|
||||
$:/state/folded/$(currentTiddler)$
|
||||
\end
|
||||
```
|
||||
|
@ -1,16 +1,16 @@
|
||||
caption: tm-unfold-all-tiddlers
|
||||
created: 20160424233133261
|
||||
modified: 20160424233427308
|
||||
modified: 20191028113810219
|
||||
tags: Messages
|
||||
title: WidgetMessage: tm-unfold-all-tiddlers
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The `tm-unfold-all-tiddlers` message unfolds all tiddlers in the current story list. It does so by setting the text of a state tiddler to either "<<.value "show">>" or "<<.value "hide">>", according to the fold state.
|
||||
The `tm-unfold-all-tiddlers` message unfolds all tiddlers in the current story list. It does so by setting the text of all state tiddlers to: "<<.value "show">>".
|
||||
|
||||
|!Name |!Description |
|
||||
|foldedStatePrefix |Prefix for the state tiddler in which the fold state is stored. |
|
||||
|
||||
<<.tip "The core uses a foldStatePrefix of '$:/state/folded/' to store the fold states for the default story view.">>
|
||||
<<.warning "The state tiddlers title is computed as 'foldStatePrefix + TiddlerTitle'. If the foldStatePrefix is not set, it will overwrite the text of the tiddler(s) itself, resulting in data loss. ">>
|
||||
<<.tip "The core uses a foldStatePrefix of `$:/state/folded/` to store the fold states for the default story view.">>
|
||||
<<.warning "The state tiddlers title is computed as `foldStatePrefix + TiddlerTitle`. If the foldStatePrefix is not set, it will overwrite the text of the tiddler(s) itself, resulting in data loss. ">>
|
||||
|
||||
The `tm-unfold-all-tiddlers` message is usually generated with the ButtonWidget and is handled by the surrounding NavigatorWidget.
|
||||
|
@ -0,0 +1,18 @@
|
||||
caption: tm-unload-plugin-library
|
||||
created: 20191004112527669
|
||||
modified: 20191004113621714
|
||||
tags: Messages
|
||||
title: WidgetMessage: tm-unload-plugin-library
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The `tm-unload-plugin-library` message unloads the specified plugin library and deletes associated temporary tiddlers.
|
||||
|
||||
|!Name |!Description |
|
||||
|url |Url specifying the plugin library to be unloaded |
|
||||
|
||||
Unloading a plugin library deletes the following tiddlers:
|
||||
|
||||
* The tiddler titled `$:/temp/ServerConnection/` + `<url>`
|
||||
* All tiddlers with the prefix `$:/temp/RemoteAssetInfo/` + `<url>` + `/`
|
||||
|
||||
The `tm-unload-plugin-library` message is usually generated with the ButtonWidget and is handled by the core itself.
|
@ -1,6 +1,6 @@
|
||||
created: 20150926162849519
|
||||
modified: 20180701185329863
|
||||
tags: [[Installing TiddlyWiki on Node.js]]
|
||||
modified: 20191022095509822
|
||||
tags: [[TiddlyWiki on Node.js]]
|
||||
title: Installing TiddlyWiki Prerelease on Node.js
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
|
@ -0,0 +1,15 @@
|
||||
created: 20191022095653896
|
||||
modified: 20191102131824766
|
||||
tags: [[TiddlyWiki on Node.js]]
|
||||
title: Installing custom plugins on Node.js
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
There are several ways in which custom plugins that are not part of TiddlyWiki's plugin library can be installed when using TiddlyWiki under Node.js. (See [[Installing a plugin from the plugin library]] for instructions on installing plugins from the library).
|
||||
|
||||
* Arrange the PluginFolders containing the plugins in a convenient shared location and then use [[environment variables|Environment Variables on Node.js]] to tell TiddlyWiki to search those folders. The plugins can be referenced in `tiddlywiki.info` by their name (e.g. `tiddlytools/magic`)
|
||||
* Place the PluginFolders containing the plugins in a `plugins` folder within the [[wiki folder|TiddlyWikiFolders]]
|
||||
* Depending on how TiddlyWiki itself has been installed, plugins can also be installed by copying the plugin folders into the `plugins` folder of the repository. This is only recommended if working with a forked copy of the repo. It is not recommended if TiddlyWiki has been installed with npm because npm is liable to overwrite the installation when performing an update
|
||||
|
||||
Note that including a plugin as an ordinary tiddler (e.g. by dragging and dropping a plugin into the browser) will result in the plugin only being active in the browser, and not available under Node.js.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20131129094353704
|
||||
modified: 20150412185457193
|
||||
modified: 20200318115527226
|
||||
tags: Platforms
|
||||
title: TiddlyWiki on Node.js
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -10,14 +10,8 @@ Running TiddlyWiki on [[Node.js]] brings several important benefits over and abo
|
||||
* Individual tiddlers are stored in separate files, which you can organise as you wish
|
||||
* The ability to build multiple wikis that blend different combinations of shared and unique content
|
||||
|
||||
There are a few file system limitations you should be aware of that are related to how TiddlyWiki was designed:
|
||||
|
||||
* The best, most general way to interact with a running wiki is via the HTTP or JavaScript API, rather than manipulating the file store directly
|
||||
* Any modification to the contents of the wiki folder (e.g. images) might thus sometimes require that your restart the node.js server
|
||||
* TiddlyWiki might support manipulating the file store directly in the future
|
||||
<<.warning """Note that TiddlyWiki on Node.js doesn't currently support directly modifying the tiddler files via the file system while it is running. The server must be restarted in order to for changes to take effect. The recommended way to interact with a running wiki is via the HTTP or JavaScript APIs.""">>
|
||||
|
||||
For more information see:
|
||||
|
||||
* [[Installing TiddlyWiki on Node.js]]
|
||||
* [[Using TiddlyWiki on Node.js]]
|
||||
* [[Upgrading TiddlyWiki on Node.js]]
|
||||
<<list-links "[tag[TiddlyWiki on Node.js]]">>
|
||||
|
@ -1,6 +1,6 @@
|
||||
created: 20160107222352710
|
||||
modified: 20190927205622498
|
||||
tags: Plugins
|
||||
modified: 20191022095637710
|
||||
tags: [[TiddlyWiki on Node.js]] Plugins
|
||||
title: Installing a plugin from the plugin library
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
@ -47,3 +47,5 @@ Follow these instructions when using TiddlyWiki under Node.js:
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
See also [[Installing custom plugins on Node.js]].
|
@ -1,16 +1,16 @@
|
||||
caption: ~AndTidWiki
|
||||
caption: Tiddloid and Tiddloid Lite
|
||||
created: 20130825161400000
|
||||
delivery: App
|
||||
description: Android app for saving changes locally to device storage
|
||||
method: save
|
||||
modified: 20171113105950965
|
||||
modified: 20191013145728306
|
||||
tags: Saving Android
|
||||
title: Saving on Android
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The AndTidWiki app for Android devices makes it possible to edit and save changes to TiddlyWiki5, including working offline without a network connection. [[Download it here|https://play.google.com/store/apps/details?id=de.mgsimon.android.andtidwiki&hl=en]].
|
||||
The Tiddloid or Tiddloid Lite app for Android devices makes it possible to edit and save changes to TiddlyWiki. Get it from GitHub: [[Tiddloid|https://github.com/donmor/Tiddloid]] [[Tiddloid Lite|https://github.com/donmor/TiddloidLite]].
|
||||
|
||||
Instructions for use:
|
||||
''Instructions for use:''
|
||||
|
||||
# [[Download]] an empty TiddlyWiki on another web browser
|
||||
# Move the file you just downloaded to the directory `/sdcard/andtidwiki`
|
||||
@ -23,4 +23,5 @@ Instructions for use:
|
||||
|
||||
''Note:'' You can save your changes by clicking the <<.icon $:/core/images/save-button>> ''save changes'' button in the sidebar even if you have not clicked the <<.icon $:/core/images/done-button>> ''ok'' button to complete editing a tiddler
|
||||
|
||||
//Note that AndTidWiki is published independently of TiddlyWiki//
|
||||
* Tiddloid Lite supports new devices better. It also supports files on clouds like GDrive and ~OneDrive, while Tiddloid keeps the compatibility to TiddlyWikiClassic. For more difference between Tiddloid and Tiddloid Lite, please visit [[Tiddloid's homepage|https://github.com/donmor/Tiddloid]].
|
||||
* You should keep the `.html` or `.htm` extension of the files to be imported.
|
||||
|
@ -12,7 +12,7 @@ tags: $:/tags/Stylesheet
|
||||
border: 1px solid #ebefcd;
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 10px;
|
||||
background: #fcfdf3;
|
||||
background: <<colour code-background>>;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20181002131215403
|
||||
modified: 20190903094711346
|
||||
modified: 2020031109590546
|
||||
tags: [[WebServer API]]
|
||||
title: WebServer API: Get All Tiddlers
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -12,11 +12,23 @@ GET /recipes/default/tiddlers.json
|
||||
|
||||
Parameters:
|
||||
|
||||
* none
|
||||
* ''filter'' - filter identifying tiddlers to be returned (optional, defaults to "[all[tiddlers]!is[system]sort[title]]")
|
||||
* ''exclude'' - comma delimited list of fields to excluded from the returned tiddlers (optional, defaults to "text")
|
||||
|
||||
In order to avoid denial of service attacks with malformed filters in the default configuration the only filter that is accepted is the default filter "[all[tiddlers]!is[system]sort[title]]"; attempts to use any other filter will result in an HTTP 403 error.
|
||||
|
||||
To enable a particular filter, create a tiddler with the title "$:/config/Server/ExternalFilters/" concatenated with the filter text, and the text field set to "yes". For example, the TiddlyWeb plugin includes the following shadow tiddler to enable the filter that it requires:
|
||||
|
||||
```
|
||||
title: $:/config/Server/ExternalFilters/[all[tiddlers]] -[[$:/isEncrypted]] -[prefix[$:/temp/]] -[prefix[$:/status/]]
|
||||
text: yes
|
||||
```
|
||||
|
||||
It is also possible to configure the server to accept any filter by creating a tiddler titled $:/config/Server/AllowAllExternalFilters with the text "yes". This should not be done for public facing servers.
|
||||
|
||||
Response:
|
||||
|
||||
|
||||
* 200 OK
|
||||
*> `Content-Type: application/json`
|
||||
*> Body: array of all non-system tiddlers in [[TiddlyWeb JSON tiddler format]]
|
||||
* 403 Forbidden
|
||||
|
@ -1,6 +1,6 @@
|
||||
caption: path-prefix
|
||||
created: 20180630180514893
|
||||
modified: 20180702154716090
|
||||
modified: 20200311171847360
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: path-prefix
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -12,3 +12,5 @@ This example causes the server to serve from http://127.0.0.1/MyApp instead of t
|
||||
```
|
||||
tiddlywiki mywikifolder --listen "path-prefix=/MyApp"
|
||||
```
|
||||
|
||||
Note that further steps are required to configure the client-side components to use the prefix. See [[Using a custom path prefix with the client-server edition]].
|
@ -1,6 +1,6 @@
|
||||
caption: port
|
||||
created: 20180630180552254
|
||||
modified: 20180702155017130
|
||||
modified: 20191219123751824
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: port
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -10,6 +10,7 @@ The [[web server configuration parameter|WebServer Parameters]] ''port'' specifi
|
||||
The ''port'' parameter accepts two types of value:
|
||||
|
||||
* Numerical values are interpreted as a decimal port number
|
||||
** The special value 0 (zero) causes the operating system to assign an available port
|
||||
* Non-numeric values are interpreted as an environment variable from which the port should be read
|
||||
|
||||
This example configures the server to listen on port 8090:
|
||||
|
29
editions/tw5.com/tiddlers/widgets/ActionPopupWidget.tid
Normal file
29
editions/tw5.com/tiddlers/widgets/ActionPopupWidget.tid
Normal file
@ -0,0 +1,29 @@
|
||||
caption: action-popup
|
||||
created: 20200303114556528
|
||||
modified: 20200303114556528
|
||||
tags: Widgets ActionWidgets
|
||||
title: ActionPopupWidget
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
! Introduction
|
||||
|
||||
The ''action-popup'' widget is an [[action widget|ActionWidgets]] that triggers the display of a popup defined via a state tiddler. ActionWidgets are used within triggering widgets such as the ButtonWidget.
|
||||
|
||||
! Content and Attributes
|
||||
|
||||
The ''action-popup'' widget is invisible. Any content within it is ignored.
|
||||
|
||||
|!Attribute |!Description |
|
||||
|$state |The title of the state tiddler for the popup |
|
||||
|$coords |Optional coordinates for the handle to which popup is positioned (in the format `(x,y,w,h)`) |
|
||||
|
||||
! Examples
|
||||
|
||||
Here is an example of button that triggers the "more" button in the sidebar "Tools" tab. You may need to scroll to see the popup
|
||||
|
||||
<$macrocall $name='wikitext-example-without-html'
|
||||
src='<$button>
|
||||
<$action-setfield $tiddler="$:/state/tab/sidebar--595412856" $value="$:/core/ui/SideBar/Tools"/>
|
||||
<$action-popup $state="$:/state/popup/more-2053862905" $coords="(0,20,0,0)"/>
|
||||
Click me!
|
||||
</$button>'/>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user