1
0
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:
Jeremy Ruston 2020-04-02 11:45:01 +01:00
commit 9d9b00bdb5
212 changed files with 4380 additions and 614 deletions

View File

@ -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

View File

@ -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 \

View File

@ -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"]});

View File

@ -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 Giteas web interface: `Settings | Applications | Generate New Token`)
Saving/TiddlySpot/Advanced/Heading: Advanced Settings
Saving/TiddlySpot/BackupDir: Backup Directory
Saving/TiddlySpot/Backups: Backups

View File

@ -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

View File

@ -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

View File

@ -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

View 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;
})();

View File

@ -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);
};
})();

View File

@ -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);
};
})();

View File

@ -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;

View 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);
};
})();

View File

@ -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",

View File

@ -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",

View File

@ -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;

View File

@ -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");

View File

@ -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);
};

View File

@ -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,

View File

@ -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;
})();

View File

@ -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);

View File

@ -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");

View File

@ -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;
})();

View File

@ -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");
};
})();

View 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;
})();

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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]);
};
/*

View File

@ -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);
};
/*

View File

@ -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);
};
/*

View 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

View File

@ -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

View File

@ -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

View 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>`

View File

@ -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}}

View File

@ -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}}

View File

@ -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">

View File

@ -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">

View File

@ -1,8 +1,4 @@
title: $:/core/ui/BinaryWarning
\define lingo-base() $:/language/BinaryWarning/
<div class="tc-binary-warning">
<<lingo Prompt>>
</div>

View 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"/> |

View File

@ -69,7 +69,7 @@ $value={{{ [<newFieldValueTiddler>get[text]] }}}/>
<$fieldmangler>
<div class="tc-edit-field-add">
<em class="tc-edit">
<<lingo Fields/Add/Prompt>>&nbsp;
<<lingo Fields/Add/Prompt>>&nbsp;&nbsp;
</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]] }}}/>

View File

@ -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>&nbsp;&nbsp;<$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]] }}}/>&nbsp;<$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>&nbsp;<$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">

View File

@ -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">

View File

@ -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$

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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[$:/]]">

View File

@ -0,0 +1,2 @@
title: $:/config/Server/ExternalFilters/[all[tiddlers]!is[system]sort[title]]
text: yes

View File

@ -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)

View File

@ -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">
&nbsp;
</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$>

View File

@ -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>&nbsp;<$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>&nbsp;<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>>

View File

@ -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 |

View File

@ -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>

View File

@ -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 {

View File

@ -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]]

View File

@ -1,2 +1,2 @@
title: $:/themes/tiddlywiki/vanilla/options/stickytitles
text: yes
text: no

View File

@ -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",

View File

@ -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",

View 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"]
}
}

View 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('');
});
});
});
})();

View File

@ -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");
});
}
});

View File

@ -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);
});
});
})();

View 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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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()

View File

@ -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:

View File

@ -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">>

View File

@ -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[]]">>

View File

@ -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 |

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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">>

View File

@ -0,0 +1,6 @@
title: $:/editions/tw5.com/menuitems/gettingstarted
caption: ~GettingStarted
description: Link to ~GettingStarted
tags: $:/tags/MenuBar
target: GettingStarted

View 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

View File

@ -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

View File

@ -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.

View File

@ -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
```

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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]]">>

View File

@ -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]].

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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]].

View File

@ -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:

View 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