1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-22 19:04:38 +00:00

Compare commits

..

17 Commits

Author SHA1 Message Date
jeremy@jermolene.com
bbdf9bae89 Merge branch 'master' into publishing-framework 2021-07-14 15:48:54 +01:00
jeremy@jermolene.com
f1d76a1eee Merge branch 'master' into publishing-framework 2021-06-01 13:17:47 +01:00
jeremy@jermolene.com
160c154ef1 Merge branch 'master' into publishing-framework 2021-05-20 13:53:29 +01:00
jeremy@jermolene.com
184083ad1a Use variable definition in the job/sitemap/route text instead of from var-* fields 2021-04-10 17:42:35 +01:00
jeremy@jermolene.com
9c62bd8030 Fix typo in link widget 2021-04-10 17:41:41 +01:00
jeremy@jermolene.com
b7419dec3a Finish refactoring of server <-> sitemap interface
Continuing c4cdb1ed8c
2021-04-10 13:51:07 +01:00
jeremy@jermolene.com
02d390a673 Add support for slugify to the link widget's tv-wikilink-template handling 2021-04-10 10:58:55 +01:00
jeremy@jermolene.com
c4cdb1ed8c Refactor sitemap route handling
To avoid duplicating the rendering code
2021-04-10 10:58:26 +01:00
jeremy@jermolene.com
5fcff1f1a3 Refactor server route handling to handle default documents properly 2021-04-10 10:57:52 +01:00
jeremy@jermolene.com
a8770d7645 Revert changes to image widget 2021-04-08 14:27:30 +01:00
jeremy@jermolene.com
df2a3fdefd Refactor sitemap handling so we can reuse it in the webserver 2021-04-07 12:16:12 +01:00
jeremy@jermolene.com
a8c248eb3d Revert to using parameterised paths instead of filters
Because we will be able to run parameterised paths in reverse for HTTP serving, which we can't do with filters
2021-04-06 12:19:49 +01:00
jeremy@jermolene.com
96103d5d4c Link to the PR 2021-04-05 17:09:43 +01:00
jeremy@jermolene.com
a025bce21f Clean up before publishing a snapshot to TiddlyHost 2021-04-05 17:07:19 +01:00
jeremy@jermolene.com
45cdd7bdf7 Merge branch 'master' into publishing-framework 2021-04-05 12:31:46 +01:00
jeremy@jermolene.com
509356c696 Add Node.js support, refactor modelling of jobs and sitemaps, and use modals framework 2021-04-04 13:18:41 +01:00
jeremy@jermolene.com
42e10d030a First commit 2021-03-22 09:34:32 +00:00
668 changed files with 5801 additions and 10296 deletions

View File

@@ -5,7 +5,7 @@
# Default to the current version number for building the plugin library
if [ -z "$TW5_BUILD_VERSION" ]; then
TW5_BUILD_VERSION=v5.2.2
TW5_BUILD_VERSION=v5.2.0
fi
echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]"

View File

@@ -256,28 +256,6 @@ $tw.utils.deepDefaults = function(object /*, sourceObjectList */) {
return object;
};
/*
Convert a URIComponent encoded string to a string safely
*/
$tw.utils.decodeURIComponentSafe = function(s) {
var v = s;
try {
v = decodeURIComponent(s);
} catch(e) {}
return v;
};
/*
Convert a URI encoded string to a string safely
*/
$tw.utils.decodeURISafe = function(s) {
var v = s;
try {
v = decodeURI(s);
} catch(e) {}
return v;
};
/*
Convert "&amp;" to &, "&nbsp;" to nbsp, "&lt;" to <, "&gt;" to > and "&quot;" to "
*/
@@ -409,19 +387,6 @@ $tw.utils.parseFields = function(text,fields) {
return fields;
};
// Safely parse a string as JSON
$tw.utils.parseJSONSafe = function(text,defaultJSON) {
try {
return JSON.parse(text);
} catch(e) {
if(typeof defaultJSON === "function") {
return defaultJSON(e);
} else {
return defaultJSON || {};
}
}
};
/*
Resolves a source filepath delimited with `/` relative to a specified absolute root filepath.
In relative paths, the special folder name `..` refers to immediate parent directory, and the
@@ -792,7 +757,12 @@ $tw.modules.execute = function(moduleName,moduleRoot) {
tiddler = $tw.wiki.getTiddler(name),
_exports = {},
sandbox = {
module: {exports: _exports},
module: {
exports: _exports,
setStringHandler: function(handler) {
moduleInfo.stringHandler = handler;
}
},
//moduleInfo: moduleInfo,
exports: _exports,
console: console,
@@ -856,7 +826,7 @@ $tw.modules.execute = function(moduleName,moduleRoot) {
moduleInfo.definition(moduleInfo,moduleInfo.exports,sandbox.require);
} else if(typeof moduleInfo.definition === "string") { // String
moduleInfo.exports = _exports;
$tw.utils.evalSandboxed(moduleInfo.definition,sandbox,tiddler.fields.title);
$tw.utils.evalSandboxed(moduleInfo.definition,sandbox,name);
if(sandbox.module.exports) {
moduleInfo.exports = sandbox.module.exports; //more codemirror workaround
}
@@ -953,6 +923,20 @@ $tw.modules.createClassesFromModules = function(moduleType,subType,baseClass) {
return classes;
};
/*
Return a specified module string for a module, null if the module or string is missing
*/
$tw.modules.getModuleString = function(moduleName,stringName,language) {
if(moduleName in $tw.modules.titles) {
$tw.modules.execute(moduleName);
var stringHandler = $tw.modules.titles[moduleName].stringHandler;
if(stringHandler) {
return stringHandler(stringName,language);
}
}
return null;
};
/////////////////////////// Barebones tiddler object
/*
@@ -1247,12 +1231,8 @@ $tw.Wiki = function(options) {
index,titlesLength,title;
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
if(tiddlers[title]) {
callback(tiddlers[title],title);
} else {
var shadowInfo = shadowTiddlers[title];
callback(shadowInfo.tiddler,title);
}
var shadowInfo = shadowTiddlers[title];
callback(shadowInfo.tiddler,title);
}
};
@@ -1335,7 +1315,7 @@ $tw.Wiki = function(options) {
var tiddler = tiddlers[title];
if(tiddler) {
if(tiddler.fields.type === "application/json" && tiddler.hasField("plugin-type") && tiddler.fields.text) {
pluginInfo[tiddler.fields.title] = $tw.utils.parseJSONSafe(tiddler.fields.text);
pluginInfo[tiddler.fields.title] = JSON.parse(tiddler.fields.text);
results.modifiedPlugins.push(tiddler.fields.title);
}
} else {
@@ -1468,7 +1448,7 @@ $tw.Wiki.prototype.defineTiddlerModules = function() {
}
break;
case "application/json":
$tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],$tw.utils.parseJSONSafe(tiddler.fields.text));
$tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],JSON.parse(tiddler.fields.text));
break;
case "application/x-tiddler-dictionary":
$tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],$tw.utils.parseFields(tiddler.fields.text));
@@ -1641,8 +1621,8 @@ $tw.modules.define("$:/boot/tiddlerdeserializer/json","tiddlerdeserializer",{
}
for(var f in data) {
if($tw.utils.hop(data,f)) {
// Check field name doesn't contain control characters
if(typeof(data[f]) !== "string" || /[\x00-\x1F]/.test(f)) {
// Check field name doesn't contain whitespace or control characters
if(typeof(data[f]) !== "string" || /[\x00-\x1F\s]/.test(f)) {
return false;
}
}
@@ -1657,7 +1637,7 @@ $tw.modules.define("$:/boot/tiddlerdeserializer/json","tiddlerdeserializer",{
}
return true;
},
data = $tw.utils.parseJSONSafe(text);
data = JSON.parse(text);
if($tw.utils.isArray(data) && isTiddlerArrayValid(data)) {
return data;
} else if(isTiddlerValid(data)) {
@@ -1697,7 +1677,7 @@ $tw.boot.decryptEncryptedTiddlers = function(callback) {
$tw.crypto.setPassword(data.password);
var decryptedText = $tw.crypto.decrypt(encryptedText);
if(decryptedText) {
var json = $tw.utils.parseJSONSafe(decryptedText);
var json = JSON.parse(decryptedText);
for(var title in json) {
$tw.preloadTiddler(json[title]);
}
@@ -1897,7 +1877,7 @@ filepath: pathname of the directory containing the specification file
$tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
var tiddlers = [];
// Read the specification
var filesInfo = $tw.utils.parseJSONSafe(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8"));
var filesInfo = JSON.parse(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8"));
// Helper to process a file
var processFile = function(filename,isTiddlerFile,fields,isEditableFile) {
var extInfo = $tw.config.fileExtensionInfo[path.extname(filename)],
@@ -1924,13 +1904,13 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
value = path.basename(filename);
break;
case "filename-uri-decoded":
value = $tw.utils.decodeURIComponentSafe(path.basename(filename));
value = decodeURIComponent(path.basename(filename));
break;
case "basename":
value = path.basename(filename,path.extname(filename));
break;
case "basename-uri-decoded":
value = $tw.utils.decodeURIComponentSafe(path.basename(filename,path.extname(filename)));
value = decodeURIComponent(path.basename(filename,path.extname(filename)));
break;
case "extname":
value = path.extname(filename);
@@ -1958,20 +1938,6 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
tiddlers.push({tiddlers: fileTiddlers});
}
};
// Helper to recursively search subdirectories
var getAllFiles = function(dirPath, recurse, arrayOfFiles) {
recurse = recurse || false;
arrayOfFiles = arrayOfFiles || [];
var files = fs.readdirSync(dirPath);
files.forEach(function(file) {
if (recurse && fs.statSync(dirPath + path.sep + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + path.sep + file, recurse, arrayOfFiles);
} else if(fs.statSync(dirPath + path.sep + file).isFile()){
arrayOfFiles.push(path.join(dirPath, path.sep, file));
}
});
return arrayOfFiles;
}
// Process the listed tiddlers
$tw.utils.each(filesInfo.tiddlers,function(tidInfo) {
if(tidInfo.prefix && tidInfo.suffix) {
@@ -1995,14 +1961,13 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
// Process directory specifier
var dirPath = path.resolve(filepath,dirSpec.path);
if(fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
var files = getAllFiles(dirPath, dirSpec.searchSubdirectories),
var files = fs.readdirSync(dirPath),
fileRegExp = new RegExp(dirSpec.filesRegExp || "^.*$"),
metaRegExp = /^.*\.meta$/;
for(var t=0; t<files.length; t++) {
var thisPath = path.relative(filepath, files[t]),
filename = path.basename(thisPath);
var filename = files[t];
if(filename !== "tiddlywiki.files" && !metaRegExp.test(filename) && fileRegExp.test(filename)) {
processFile(thisPath,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile);
processFile(dirPath + path.sep + filename,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile);
}
}
} else {
@@ -2027,7 +1992,7 @@ $tw.loadPluginFolder = function(filepath,excludeRegExp) {
console.log("Warning: missing plugin.info file in " + filepath);
return null;
}
var pluginInfo = $tw.utils.parseJSONSafe(fs.readFileSync(infoPath,"utf8"));
var pluginInfo = JSON.parse(fs.readFileSync(infoPath,"utf8"));
// Read the plugin files
var pluginFiles = $tw.loadTiddlersFromPath(filepath,excludeRegExp);
// Save the plugin tiddlers into the plugin info
@@ -2144,7 +2109,7 @@ $tw.loadWikiTiddlers = function(wikiPath,options) {
pluginFields;
// Bail if we don't have a wiki info file
if(fs.existsSync(wikiInfoPath)) {
wikiInfo = $tw.utils.parseJSONSafe(fs.readFileSync(wikiInfoPath,"utf8"));
wikiInfo = JSON.parse(fs.readFileSync(wikiInfoPath,"utf8"));
} else {
return null;
}
@@ -2657,4 +2622,3 @@ if(typeof(exports) !== "undefined") {
} else {
_boot(window.$tw);
}
//# sourceURL=$:/boot/boot.js

View File

@@ -117,4 +117,3 @@ if(typeof(exports) === "undefined") {
// Export functionality as a module
exports.bootprefix = _bootprefix;
}
//# sourceURL=$:/boot/bootprefix.js

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@ type: text/plain
TiddlyWiki created by Jeremy Ruston, (jeremy [at] jermolene [dot] com)
Copyright (c) 2004-2007, Jeremy Ruston
Copyright (c) 2007-2022, UnaMesa Association
Copyright (c) 2007-2021, UnaMesa Association
All rights reserved.
Redistribution and use in source and binary forms, with or without

7
core/images/publish.tid Normal file
View File

@@ -0,0 +1,7 @@
title: $:/core/images/publish
tags: $:/tags/Image
<svg width="22pt" height="22pt" class="tc-image-publish tc-image-button" viewBox="0 0 128 128"><g fill-rule="evenodd">
<path d="M64.0434107,46.2358498 C65.8048912,45.8955184 67.6195684,46.7809274 68.4102078,48.4458716 L68.4937877,48.6340507 L98.8972485,122.034498 C99.7426494,124.075476 98.7734424,126.415349 96.7324641,127.26075 C94.7552664,128.079732 92.4975633,127.195747 91.5897922,125.284145 L91.5062123,125.095966 L88.403,117.605598 L79.5048497,126.50485 C78.775871,127.233828 77.8355254,127.622617 76.8810886,127.671216 L76.6764226,127.676423 C75.6527333,127.676423 74.6290441,127.285898 73.8479955,126.50485 L63.999,116.656598 L54.1520045,126.50485 C53.4230259,127.233828 52.4826802,127.622617 51.5282434,127.671216 L51.3235774,127.676423 C50.2998881,127.676423 49.2761989,127.285898 48.4951503,126.50485 L39.596,117.605598 L36.4937877,125.095966 C35.6483868,127.136944 33.3085142,128.106151 31.2675359,127.26075 C29.2265576,126.415349 28.2573506,124.075476 29.1027515,122.034498 L59.5062123,48.6340507 C60.2518688,46.8338766 62.1601511,45.867488 64.0005148,46.2445933 L64.0434107,46.2358498 Z M77.679,102.976598 L69.656,110.999598 L76.676,118.018598 L84.699,109.996598 L77.679,102.976598 Z M50.326,102.983598 L43.307,110.002598 L51.323,118.018598 L58.342,110.999598 L50.326,102.983598 Z M64.006,89.3035977 L55.983,97.3265977 L63.999,105.342598 L72.022,97.3195977 L64.006,89.3035977 Z M55.035,80.3325977 L50.348,91.6475977 L58.349,83.6465977 L55.035,80.3325977 Z M72.968,80.3415977 L69.663,83.6465977 L77.642,91.6255977 L72.968,80.3415977 Z M64,58.6895977 L58.3344072,72.3699713 C58.4295481,72.4423583 58.5221874,72.5195778 58.6119911,72.6016299 L58.7445283,72.7283325 L64.006,77.9895977 L69.2684411,72.7283325 C69.3957231,72.6010505 69.5294489,72.4841397 69.6685683,72.3775999 L64,58.6895977 Z M38.5026635,2.59571369 C40.7118025,2.59571369 42.5026635,4.38657469 42.5026635,6.59571369 C42.5026635,7.7643162 42.0015346,8.8158738 41.2024517,9.54721162 L41.372583,9.372583 C35.581722,15.163444 32,23.163444 32,32 C32,40.8370609 35.5821314,48.8374703 41.3735757,54.6284097 C42.0884306,55.3520538 42.531226,56.3471171 42.531226,57.4456008 C42.531226,59.6547398 40.740365,61.4456008 38.531226,61.4456008 C37.4327422,61.4456008 36.4376789,61.0028055 35.7147885,60.2859673 L35.7048234,60.2963403 C28.472656,53.0586276 24,43.0631255 24,32.0229786 C24,21.1046577 28.3744907,11.2080539 35.4664167,3.99022262 C36.2004724,3.13678606 37.2883808,2.59571369 38.5026635,2.59571369 Z M89.3688013,2.48959773 C90.5097745,2.48959773 91.5391719,2.96731026 92.2678917,3.73363348 L92.2733617,3.72780197 C99.5183488,10.9672382 104,20.9717355 104,32.0229786 C104,42.8488024 99.6993143,52.6701472 92.7132398,59.8717162 C91.9816831,60.8152368 90.8371876,61.4223982 89.5508819,61.4223982 C87.3417429,61.4223982 85.5508819,59.6315372 85.5508819,57.4223982 C85.5508819,56.3502862 85.9726717,55.3766885 86.6593633,54.6584932 L86.627417,54.627417 C92.418278,48.836556 96,40.836556 96,32 C96,23.2702193 92.5043135,15.3568999 86.8363661,9.5834674 C85.9399594,8.85136737 85.3688013,7.73718311 85.3688013,6.48959773 C85.3688013,4.28045873 87.1596623,2.48959773 89.3688013,2.48959773 Z M50.0871028,13.6297119 C52.2962418,13.6297119 54.0871028,15.4205729 54.0871028,17.6297119 C54.0871028,18.8506134 53.5401157,19.9437593 52.6778337,20.6774573 L52.6862915,20.6862915 C49.790861,23.581722 48,27.581722 48,32 C48,36.4187838 49.791271,40.4191937 52.6872859,43.3147028 L52.6777105,43.3251493 C53.4423082,44.0537041 53.918794,45.0819221 53.918794,46.2214294 C53.918794,48.4305684 52.127933,50.2214294 49.918794,50.2214294 C48.6900999,50.2214294 47.5907975,49.6674376 46.8570439,48.7956109 L47.0304281,48.9715536 C42.6867001,44.6283295 40,38.627921 40,32 C40,25.372583 42.6862915,19.372583 47.0294373,15.0294373 L47.0395567,15.0387468 C47.7732533,14.1766 48.8663118,13.6297119 50.0871028,13.6297119 Z M77.9823819,13.700452 C79.1679628,13.700452 80.2330731,14.2162483 80.9655529,15.0356811 L80.9715536,15.0304281 C85.314117,19.3734956 88,25.373087 88,32 C88,38.4619387 85.4461804,44.3274009 81.2927766,48.6421512 L80.9933974,48.9476975 C80.2638884,49.7186971 79.2309977,50.2000547 78.08568,50.2000547 C75.876541,50.2000547 74.08568,48.4091937 74.08568,46.2000547 C74.08568,45.0665777 74.557136,44.0432152 75.314649,43.3153662 L75.3137085,43.3137085 C78.209139,40.418278 80,36.418278 80,32 C80,27.5822253 78.209547,23.5826334 75.314698,20.6872811 C74.4981782,19.9511432 73.9823819,18.8860329 73.9823819,17.700452 C73.9823819,15.491313 75.7732429,13.700452 77.9823819,13.700452 Z M64,24 C68.418278,24 72,27.581722 72,32 C72,36.418278 68.418278,40 64,40 C59.581722,40 56,36.418278 56,32 C56,27.581722 59.581722,24 64,24 Z"></path>
</g>
</svg>

View File

@@ -32,6 +32,8 @@ ExportTiddler/Caption: export tiddler
ExportTiddler/Hint: Export tiddler
ExportTiddlers/Caption: export tiddlers
ExportTiddlers/Hint: Export tiddlers
ExportTiddlyWikiCore/Caption: export TiddlyWiki core
ExportTiddlyWikiCore/Hint: Export the ~TiddlyWiki core code for running with external ~JavaScript
SidebarSearch/Hint: Select the sidebar search field
Fold/Caption: fold tiddler
Fold/Hint: Fold the body of this tiddler
@@ -83,6 +85,8 @@ Permaview/Caption: permaview
Permaview/Hint: Set browser address bar to a direct link to all the tiddlers in this story
Print/Caption: print page
Print/Hint: Print the current page
Publish/Caption: publish
Publish/Hint: Publish from the wiki
Refresh/Caption: refresh
Refresh/Hint: Perform a full refresh of the wiki
Save/Caption: ok

View File

@@ -27,15 +27,10 @@ Basics/Tiddlers/Prompt: Number of tiddlers
Basics/Title/Prompt: Title of this ~TiddlyWiki
Basics/Username/Prompt: Username for signing edits
Basics/Version/Prompt: ~TiddlyWiki version
Cascades/Caption: Cascades
Cascades/Hint: These global rules are used to dynamically choose certain templates. The result of the cascade is the result of the first filter in the sequence that returns a result
Cascades/TagPrompt: Filters tagged <$macrocall $name="tag" tag=<<currentTiddler>>/>
EditorTypes/Caption: Editor Types
EditorTypes/Editor/Caption: Editor
EditorTypes/Hint: These tiddlers determine which editor is used to edit specific tiddler types.
EditorTypes/Type/Caption: Type
EditTemplateBody/Caption: Edit Template Body
EditTemplateBody/Hint: This rule cascade is used by the default edit template to dynamically choose the template for editing the body of a tiddler.
Info/Caption: Info
Info/Hint: Information about this TiddlyWiki
KeyboardShortcuts/Add/Prompt: Type shortcut here
@@ -101,6 +96,8 @@ Plugins/Updates/Caption: Updates
Plugins/Updates/Hint: Available updates to installed plugins
Plugins/Updates/UpdateAll/Caption: Update <<update-count>> plugins
Plugins/SubPluginPrompt: With <<count>> sub-plugins available
Publishing/Caption: Publishing
Publishing/Hint: Settings used for publishing extracts from this TiddlyWiki as separate files through a "publisher" module
Saving/Caption: Saving
Saving/DownloadSaver/AutoSave/Description: Permit automatic saving for the download saver
Saving/DownloadSaver/AutoSave/Hint: Enable Autosave for Download Saver
@@ -196,8 +193,6 @@ Settings/TitleLinks/Yes/Description: Display tiddler titles as links
Settings/MissingLinks/Caption: Wiki Links
Settings/MissingLinks/Hint: Choose whether to link to tiddlers that do not exist yet
Settings/MissingLinks/Description: Enable links to missing tiddlers
StoryTiddler/Caption: Story Tiddler
StoryTiddler/Hint: This rule cascade is used to dynamically choose the template for displaying a tiddler in the story river.
StoryView/Caption: Story View
StoryView/Prompt: Current view:
Stylesheets/Caption: Stylesheets
@@ -208,10 +203,6 @@ Theme/Caption: Theme
Theme/Prompt: Current theme:
TiddlerFields/Caption: Tiddler Fields
TiddlerFields/Hint: This is the full set of TiddlerFields in use in this wiki (including system tiddlers but excluding shadow tiddlers).
TiddlerColour/Caption: Tiddler Colour
TiddlerColour/Hint: This rules cascade is used to dynamically choose the colour for a tiddler (used for the icon and the associated tag pill).
TiddlerIcon/Caption: Tiddler Icon
TiddlerIcon/Hint: This rules cascade is used to dynamically choose the icon for a tiddler.
Toolbars/Caption: Toolbars
Toolbars/EditToolbar/Caption: Edit Toolbar
Toolbars/EditToolbar/Hint: Choose which buttons are displayed for tiddlers in edit mode. Drag and drop to change the ordering
@@ -223,7 +214,3 @@ Toolbars/EditorToolbar/Hint: Choose which buttons are displayed in the editor to
Toolbars/ViewToolbar/Caption: View Toolbar
Toolbars/ViewToolbar/Hint: Choose which buttons are displayed for tiddlers in view mode. Drag and drop to change the ordering
Tools/Download/Full/Caption: Download full wiki
ViewTemplateBody/Caption: View Template Body
ViewTemplateBody/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the body of a tiddler.
ViewTemplateTitle/Caption: View Template Title
ViewTemplateTitle/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the title of a tiddler.

View File

@@ -3,7 +3,6 @@ title: $:/language/Docs/Fields/
_canonical_uri: The full URI of an external image tiddler
bag: The name of the bag from which a tiddler came
caption: The text to be displayed on a tab or button
code-body: The view template will display the tiddler as code if set to ''yes''
color: The CSS color value associated with a tiddler
component: The name of the component responsible for an [[alert tiddler|AlertMechanism]]
current-tiddler: Used to cache the top tiddler in a [[history list|HistoryMechanism]]
@@ -14,9 +13,9 @@ description: The descriptive text for a plugin, or a modal dialogue
draft.of: For draft tiddlers, contains the title of the tiddler of which this is a draft
draft.title: For draft tiddlers, contains the proposed new title of the tiddler
footer: The footer text for a wizard
hide-body: The view template will hide bodies of tiddlers if set to ''yes''
hide-body: The view template will hide bodies of tiddlers if set to: ''yes''
icon: The title of the tiddler containing the icon associated with a tiddler
library: Indicates that a tiddler should be saved as a JavaScript library if set to ''yes''
library: Indicates that a tiddler should be saved as a JavaScript library if set to: ''yes''
list: An ordered list of tiddler titles associated with a tiddler
list-before: If set, the title of a tiddler before which this tiddler should be added to the ordered list of tiddler titles, or at the start of the list if this field is present but empty
list-after: If set, the title of the tiddler after which this tiddler should be added to the ordered list of tiddler titles, or at the end of the list if this field is present but empty
@@ -33,7 +32,7 @@ tags: A list of tags associated with a tiddler
text: The body text of a tiddler
throttle.refresh: If present, throttles refreshes of this tiddler
title: The unique name of a tiddler
toc-link: Suppresses the tiddler's link in a Table of Contents tree if set to ''no''
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

@@ -22,6 +22,8 @@ All parameters are optional with safe defaults, and can be specified in any orde
* ''readers'' - comma-separated list of principals allowed to read from this wiki
* ''writers'' - comma-separated list of principals allowed to write to this wiki
* ''csrf-disable'' - set to "yes" to disable CSRF checks (defaults to "no")
* ''sse-enabled'' - set to "yes" to enable Server-sent events (defaults to "no")
* ''sitemap'' - optional sitemap describing how the tiddlers will be served. See [[Publishing]] for more details
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
* ''root-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")

View File

@@ -30,5 +30,5 @@ Upgrader/System/Warning: Core module tiddler.
Upgrader/System/Alert: You are about to import a tiddler that will overwrite a core module tiddler. This is not recommended as it may make the system unstable.
Upgrader/ThemeTweaks/Created: Migrated theme tweak from <$text text=<<from>>/>.
Upgrader/Tiddler/Disabled: Disabled tiddler.
Upgrader/Tiddler/Selected: Selected tiddler.
Upgrader/Tiddler/Selected: User selected.
Upgrader/Tiddler/Unselected: Unselected tiddler.

View File

@@ -0,0 +1,5 @@
title: $:/language/Publishing/Modal
subtitle: Publishing: ''<$transclude field="caption"><$view field="title"/></$transclude>''
footer: <$button message="tm-close-tiddler">Cancel</$button>
Publishing <$text text=<<totalFiles>>/> files via the "{{!!publisher}}" publisher.

View File

@@ -48,7 +48,7 @@ Command.prototype.execute = function() {
}
// Tweak the tiddlywiki.info to remove any included wikis
var packagePath = $tw.boot.wikiPath + "/tiddlywiki.info",
packageJson = $tw.utils.parseJSONSafe(fs.readFileSync(packagePath));
packageJson = JSON.parse(fs.readFileSync(packagePath));
delete packageJson.includeWikis;
fs.writeFileSync(packagePath,JSON.stringify(packageJson,null,$tw.config.preferences.jsonSpaces));
return null;

View File

@@ -0,0 +1,46 @@
/*\
title: $:/core/modules/commands/publish.js
type: application/javascript
module-type: command
Publish static files
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "publish",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
if(this.params.length < 1) {
return "Missing filename filter";
}
var self = this,
wiki = this.commander.wiki,
jobTiddler = this.params[0],
variableList = this.params.slice(1),
variables = Object.create(null);
while(variableList.length >= 2) {
variables[variableList[0]] = variableList[1];
variableList = variableList.slice(2);
}
$tw.publisherHandler.publish(jobTiddler,this.callback,{commander: this.commander,variables: variables});
return null;
};
exports.Command = Command;
})();

View File

@@ -8,59 +8,58 @@ Render individual tiddlers and save the results to the specified files
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/widgets/widget.js");
exports.info = {
name: "render",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
if(this.params.length < 1) {
return "Missing tiddler filter";
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/widgets/widget.js");
exports.info = {
name: "render",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
if(this.params.length < 1) {
return "Missing tiddler filter";
}
var self = this,
fs = require("fs"),
path = require("path"),
wiki = this.commander.wiki,
tiddlerFilter = this.params[0],
filenameFilter = this.params[1] || "[is[tiddler]addsuffix[.html]]",
type = this.params[2] || "text/html",
template = this.params[3],
variableList = this.params.slice(4),
tiddlers = wiki.filterTiddlers(tiddlerFilter),
variables = Object.create(null);
while(variableList.length >= 2) {
variables[variableList[0]] = variableList[1];
variableList = variableList.slice(2);
}
$tw.utils.each(tiddlers,function(title) {
var parser = wiki.parseTiddler(template || title);
var widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title})}),
container = $tw.fakeDocument.createElement("div");
widgetNode.render(container,null);
var text = type === "text/html" ? container.innerHTML : container.textContent,
filepath = path.resolve(self.commander.outputPath,wiki.filterTiddlers(filenameFilter,$tw.rootWidget,wiki.makeTiddlerIterator([title]))[0]);
if(self.commander.verbose) {
console.log("Rendering \"" + title + "\" to \"" + filepath + "\"");
}
var self = this,
fs = require("fs"),
path = require("path"),
wiki = this.commander.wiki,
tiddlerFilter = this.params[0],
filenameFilter = this.params[1] || "[is[tiddler]addsuffix[.html]]",
type = this.params[2] || "text/html",
template = this.params[3],
variableList = this.params.slice(4),
tiddlers = wiki.filterTiddlers(tiddlerFilter),
variables = Object.create(null);
while(variableList.length >= 2) {
variables[variableList[0]] = variableList[1];
variableList = variableList.slice(2);
}
$tw.utils.each(tiddlers,function(title) {
var filepath = path.resolve(self.commander.outputPath,wiki.filterTiddlers(filenameFilter,$tw.rootWidget,wiki.makeTiddlerIterator([title]))[0]);
if(self.commander.verbose) {
console.log("Rendering \"" + title + "\" to \"" + filepath + "\"");
}
var parser = wiki.parseTiddler(template || title),
widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title})}),
container = $tw.fakeDocument.createElement("div");
widgetNode.render(container,null);
var text = type === "text/html" ? container.innerHTML : container.textContent;
$tw.utils.createFileDirectories(filepath);
fs.writeFileSync(filepath,text,"utf8");
});
return null;
};
exports.Command = Command;
})();
$tw.utils.createFileDirectories(filepath);
fs.writeFileSync(filepath,text,"utf8");
});
return null;
};
exports.Command = Command;
})();

View File

@@ -69,7 +69,7 @@ Command.prototype.execute = function() {
$tw.utils.createFileDirectories(pathname);
fs.writeFileSync(pathname,JSON.stringify(tiddler),"utf8");
// Collect the skinny list data
var pluginTiddlers = $tw.utils.parseJSONSafe(tiddler.text),
var pluginTiddlers = JSON.parse(tiddler.text),
readmeContent = (pluginTiddlers.tiddlers[title + "/readme"] || {}).text,
doesRequireReload = !!self.commander.wiki.doesPluginInfoRequireReload(pluginTiddlers),
iconTiddler = pluginTiddlers.tiddlers[title + "/icon"] || {},

View File

@@ -151,7 +151,7 @@ WikiFolderMaker.prototype.saveCustomPlugin = function(pluginTiddler) {
pluginInfo = pluginTiddler.getFieldStrings({exclude: ["text","type"]});
this.saveJSONFile(directory + path.sep + "plugin.info",pluginInfo);
self.log("Writing " + directory + path.sep + "plugin.info: " + JSON.stringify(pluginInfo,null,$tw.config.preferences.jsonSpaces));
var pluginTiddlers = $tw.utils.parseJSONSafe(pluginTiddler.fields.text).tiddlers; // A hashmap of tiddlers in the plugin
var pluginTiddlers = JSON.parse(pluginTiddler.fields.text).tiddlers; // A hashmap of tiddlers in the plugin
$tw.utils.each(pluginTiddlers,function(tiddler) {
self.saveTiddler(directory,new $tw.Tiddler(tiddler));
});

View File

@@ -17,13 +17,16 @@ exports["application/x-tiddler-html-div"] = function(text,fields) {
};
exports["application/json"] = function(text,fields) {
var results = [],
incoming = $tw.utils.parseJSONSafe(text,function(err) {
return [{
title: "JSON error: " + err,
text: ""
}];
});
var incoming,
results = [];
try {
incoming = JSON.parse(text);
} catch(e) {
incoming = [{
title: "JSON error: " + e,
text: ""
}]
}
if(!$tw.utils.isArray(incoming)) {
incoming = [incoming];
}

View File

@@ -135,11 +135,7 @@ FramedEngine.prototype.setText = function(text,type) {
Update the DomNode with the new text
*/
FramedEngine.prototype.updateDomNodeText = function(text) {
try {
this.domNode.value = text;
} catch(e) {
// Ignore
}
this.domNode.value = text;
};
/*
@@ -205,7 +201,7 @@ FramedEngine.prototype.handleInputEvent = function(event) {
this.widget.saveChanges(this.getText());
this.fixHeight();
if(this.widget.editInputActions) {
this.widget.invokeActionString(this.widget.editInputActions,this,event,{actionValue: this.getText()});
this.widget.invokeActionString(this.widget.editInputActions);
}
return true;
};

View File

@@ -85,11 +85,7 @@ SimpleEngine.prototype.setText = function(text,type) {
Update the DomNode with the new text
*/
SimpleEngine.prototype.updateDomNodeText = function(text) {
try {
this.domNode.value = text;
} catch(e) {
// Ignore
}
this.domNode.value = text;
};
/*
@@ -133,7 +129,7 @@ SimpleEngine.prototype.handleInputEvent = function(event) {
this.widget.saveChanges(this.getText());
this.fixHeight();
if(this.widget.editInputActions) {
this.widget.invokeActionString(this.widget.editInputActions,this,event,{actionValue: this.getText()});
this.widget.invokeActionString(this.widget.editInputActions);
}
return true;
};

View File

@@ -324,7 +324,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
If there are no Files, let the browser handle it.
*/
EditTextWidget.prototype.handleDropEvent = function(event) {
if($tw.utils.dragEventContainsFiles(event)) {
if(event.dataTransfer.files.length) {
event.preventDefault();
event.stopPropagation();
this.dispatchDOMEvent(this.cloneEvent(event,["dataTransfer"]));
@@ -332,7 +332,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
};
EditTextWidget.prototype.handlePasteEvent = function(event) {
if(event.clipboardData && event.clipboardData.files && event.clipboardData.files.length) {
if(event.clipboardData.files.length) {
event.preventDefault();
event.stopPropagation();
this.dispatchDOMEvent(this.cloneEvent(event,["clipboardData"]));

View File

@@ -1,17 +0,0 @@
/*\
title: $:/core/modules/editor/operations/text/focus-editor.js
type: application/javascript
module-type: texteditoroperation
Simply focus the Text editor
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports["focus-editor"] = function(event,operation) {
operation = null;
};
})();

View File

@@ -13,17 +13,15 @@ Text editor operation to wrap the selected lines with a prefix and suffix
"use strict";
exports["wrap-lines"] = function(event,operation) {
var prefix = operation.paramObject.prefix || "",
suffix = operation.paramObject.suffix || "";
// Cut just past the preceding line break, or the start of the text
operation.cutStart = $tw.utils.findPrecedingLineBreak(operation.text,operation.selStart);
// Cut to just past the following line break, or to the end of the text
operation.cutEnd = $tw.utils.findFollowingLineBreak(operation.text,operation.selEnd);
// Add the prefix and suffix
operation.replacement = prefix + "\n" +
operation.replacement = event.paramObject.prefix + "\n" +
operation.text.substring(operation.cutStart,operation.cutEnd) + "\n" +
suffix + "\n";
operation.newSelStart = operation.cutStart + prefix.length + 1;
event.paramObject.suffix + "\n";
operation.newSelStart = operation.cutStart + event.paramObject.prefix.length + 1;
operation.newSelEnd = operation.newSelStart + (operation.cutEnd - operation.cutStart);
};

View File

@@ -1,53 +0,0 @@
/*\
title: $:/core/modules/filterrunprefixes/cascade.js
type: application/javascript
module-type: filterrunprefix
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter prefix function
*/
exports.cascade = function(operationSubFunction,options) {
return function(results,source,widget) {
if(results.length !== 0) {
var filterList = operationSubFunction(source,widget),
filterFnList = [];
var inputResults = results.toArray();
results.clear();
$tw.utils.each(inputResults,function(title) {
var result = ""; // If no filter matches, we return an empty string
$tw.utils.each(filterList,function(filter,index) {
if(!filterFnList[index]) {
filterFnList[index] = options.wiki.compileFilter(filter);
}
var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name,opts) {
opts = opts || {};
opts.variables = {
"currentTiddler": "" + title,
"..currentTiddler": widget.getVariable("currentTiddler")
};
if(name in opts.variables) {
return opts.variables[name];
} else {
return widget.getVariable(name,opts);
}
}
});
if(output.length !== 0) {
result = output[0];
return false;
}
});
results.push(result);
});
}
}
};
})();

View File

@@ -16,30 +16,23 @@ Export our filter function
exports.filter = function(operationSubFunction,options) {
return function(results,source,widget) {
if(results.length > 0) {
var resultsToRemove = [],
index = 0;
var resultsToRemove = [];
results.each(function(title) {
var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name,opts) {
opts = opts || {};
opts.variables = {
"currentTiddler": "" + title,
"..currentTiddler": widget.getVariable("currentTiddler"),
"index": "" + index,
"revIndex": "" + (results.length - 1 - index),
"length": "" + results.length
};
if(name in opts.variables) {
return opts.variables[name];
} else {
return widget.getVariable(name,opts);
getVariable: function(name) {
switch(name) {
case "currentTiddler":
return "" + title;
case "..currentTiddler":
return widget.getVariable("currentTiddler");
default:
return widget.getVariable(name);
}
}
});
if(filtered.length === 0) {
resultsToRemove.push(title);
}
++index;
});
results.remove(resultsToRemove);
}

View File

@@ -15,29 +15,22 @@ Export our filter prefix function
exports.map = function(operationSubFunction,options) {
return function(results,source,widget) {
if(results.length > 0) {
var inputTitles = results.toArray(),
index = 0;
var inputTitles = results.toArray();
results.clear();
$tw.utils.each(inputTitles,function(title) {
var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name,opts) {
opts = opts || {};
opts.variables = {
"currentTiddler": "" + title,
"..currentTiddler": widget.getVariable("currentTiddler"),
"index": "" + index,
"revIndex": "" + (inputTitles.length - 1 - index),
"length": "" + inputTitles.length
};
if(name in opts.variables) {
return opts.variables[name];
} else {
return widget.getVariable(name,opts);
getVariable: function(name) {
switch(name) {
case "currentTiddler":
return "" + title;
case "..currentTiddler":
return widget.getVariable("currentTiddler");
default:
return widget.getVariable(name);
}
}
});
results.push(filtered[0] || "");
++index;
});
}
}

View File

@@ -15,24 +15,26 @@ Export our filter prefix function
exports.reduce = function(operationSubFunction,options) {
return function(results,source,widget) {
if(results.length > 0) {
var accumulator = "",
index = 0;
var accumulator = "";
var index = 0;
results.each(function(title) {
var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name,opts) {
opts = opts || {};
opts.variables = {
"currentTiddler": "" + title,
"..currentTiddler": widget.getVariable("currentTiddler"),
"index": "" + index,
"revIndex": "" + (results.length - 1 - index),
"length": "" + results.length,
"accumulator": "" + accumulator
};
if(name in opts.variables) {
return opts.variables[name];
} else {
return widget.getVariable(name,opts);
getVariable: function(name) {
switch(name) {
case "currentTiddler":
return "" + title;
case "..currentTiddler":
return widget.getVariable("currentTiddler");
case "accumulator":
return "" + accumulator;
case "index":
return "" + index;
case "revIndex":
return "" + (results.length - 1 - index);
case "length":
return "" + results.length;
default:
return widget.getVariable(name);
}
}
});

View File

@@ -26,16 +26,14 @@ exports.sort = function(operationSubFunction,options) {
compareFn;
results.each(function(title) {
var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name,opts) {
opts = opts || {};
opts.variables = {
"currentTiddler": "" + title,
"..currentTiddler": widget.getVariable("currentTiddler")
};
if(name in opts.variables) {
return opts.variables[name];
} else {
return widget.getVariable(name,opts);
getVariable: function(name) {
switch(name) {
case "currentTiddler":
return "" + title;
case "..currentTiddler":
return widget.getVariable("currentTiddler");
default:
return widget.getVariable(name);
}
}
});

View File

@@ -52,7 +52,7 @@ exports.all = function(source,operator,options) {
results.pushTop(subop(source,operator.prefix,options));
}
}
return results.makeTiddlerIterator(options.wiki);
return results.toArray();
};
})();

View File

@@ -16,11 +16,11 @@ Filter operator for returning all the backlinks from a tiddler
Export our filter function
*/
exports.backlinks = function(source,operator,options) {
var results = new $tw.utils.LinkedList();
var results = [];
source(function(tiddler,title) {
results.pushTop(options.wiki.getTiddlerBacklinks(title));
$tw.utils.pushTop(results,options.wiki.getTiddlerBacklinks(title));
});
return results.makeTiddlerIterator(options.wiki);
return results;
};
})();

View File

@@ -19,7 +19,12 @@ Export our filter functions
exports.decodeuricomponent = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
results.push($tw.utils.decodeURIComponentSafe(title));
var value = title;
try {
value = decodeURIComponent(title);
} catch(e) {
}
results.push(value);
});
return results;
};
@@ -35,7 +40,12 @@ exports.encodeuricomponent = function(source,operator,options) {
exports.decodeuri = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
results.push($tw.utils.decodeURISafe(title));
var value = title;
try {
value = decodeURI(title);
} catch(e) {
}
results.push(value);
});
return results;
};

View File

@@ -20,7 +20,7 @@ exports.links = function(source,operator,options) {
source(function(tiddler,title) {
results.pushTop(options.wiki.getTiddlerLinks(title));
});
return results.makeTiddlerIterator(options.wiki);
return results.toArray();
};
})();

View File

@@ -103,16 +103,4 @@ exports.nth = function(source,operator,options) {
return results.slice(count - 1,count);
};
/*
The zero based nth member of the list
*/
exports.zth = function(source,operator,options) {
var count = $tw.utils.getInt(operator.operand,0),
results = [];
source(function(tiddler,title) {
results.push(title);
});
return results.slice(count,count + 1);
};
})();

View File

@@ -165,35 +165,6 @@ exports["standard-deviation"] = makeNumericReducingOperator(
}
);
//trigonometry
exports.cos = makeNumericBinaryOperator(
function(a) {return Math.cos(a)}
);
exports.sin = makeNumericBinaryOperator(
function(a) {return Math.sin(a)}
);
exports.tan = makeNumericBinaryOperator(
function(a) {return Math.tan(a)}
);
exports.acos = makeNumericBinaryOperator(
function(a) {return Math.acos(a)}
);
exports.asin = makeNumericBinaryOperator(
function(a) {return Math.asin(a)}
);
exports.atan = makeNumericBinaryOperator(
function(a) {return Math.atan(a)}
);
exports.atan2 = makeNumericBinaryOperator(
function(a,b) {return Math.atan2(a,b)}
);
//Calculate the variance of a population of numbers in an array given its mean
function getVarianceFromArray(values,mean) {
var deviationTotal = values.reduce(function(accumulator,value) {

View File

@@ -17,7 +17,14 @@ Export our filter function
*/
exports.modules = function(source,operator,options) {
var results = [];
if(operator.operands.length >= 2) {
if(operator.operands.length === 1) {
// Return all the module names without filtering
source(function(tiddler,title) {
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
results.push(moduleName);
});
});
} else if(operator.operands.length >= 2) {
// Return the modules that have the module property specified in the first operand with the value in the second operand
source(function(tiddler,title) {
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
@@ -26,13 +33,6 @@ exports.modules = function(source,operator,options) {
}
});
});
} else {
// Return all the module names without filtering
source(function(tiddler,title) {
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
results.push(moduleName);
});
});
}
results.sort();
return results;

View File

@@ -0,0 +1,30 @@
/*\
title: $:/core/modules/filters/modulestring.js
type: application/javascript
module-type: filteroperator
Filter [[module-name]modulestring[en-gb]] retrieve a module strings in a particular language
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.modulestring = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var s = $tw.modules.getModuleString(title,operator.operands[0] || "",operator.operands[1] || "");
if(s !== null) {
results.push(s);
}
});
results.sort();
return results;
};
})();

View File

@@ -17,13 +17,9 @@ Export our filter function
*/
exports.range = function(source,operator,options) {
var results = [];
// For backwards compatibility, if there is only one operand, try to split it using one of the delimiters
var parts = operator.operands || [];
if(parts.length === 1) {
parts = operator.operand.split(/[,:;]/g);
}
// Process the parts
var beg, end, inc, i, fixed = 0;
// Split the operand into numbers delimited by these symbols
var parts = operator.operand.split(/[,:;]/g),
beg, end, inc, i, fixed = 0;
for (i=0; i<parts.length; i++) {
// Validate real number
if(!/^\s*[+-]?((\d+(\.\d*)?)|(\.\d+))\s*$/.test(parts[i])) {

View File

@@ -119,25 +119,23 @@ exports["search-replace"] = function(source,operator,options) {
var results = [],
suffixes = operator.suffixes || [],
flagSuffix = (suffixes[0] ? (suffixes[0][0] || "") : ""),
flags = (flagSuffix.indexOf("g") !== -1 ? "g" : "") + (flagSuffix.indexOf("i") !== -1 ? "i" : "") + (flagSuffix.indexOf("m") !== -1 ? "m" : ""),
flags = (flagSuffix.indexOf("g") !== -1 ? "g" : "") + (flagSuffix.indexOf("i") !== -1 ? "i" : ""),
isRegExp = (suffixes[1] && suffixes[1][0] === "regexp") ? true : false,
//Escape regexp characters if the operand is not a regular expression
searchTerm = isRegExp ? operator.operand : $tw.utils.escapeRegExp(operator.operand),
//Escape $ character in replacement string if not in regular expression mode
replacement = isRegExp ? operator.operands[1] : (operator.operands[1]||"").replace(/\$/g,"$$$$"),
searchTerm,
regExp;
try {
regExp = new RegExp(searchTerm,flags);
} catch(ex) {
return ["RegExp error: " + ex];
}
source(function(tiddler,title) {
if(title && (operator.operands.length > 1)) {
//Escape regexp characters if the operand is not a regular expression
searchTerm = isRegExp ? operator.operand : $tw.utils.escapeRegExp(operator.operand);
try {
regExp = new RegExp(searchTerm,flags);
} catch(ex) {
return ["RegExp error: " + ex];
}
results.push(
title.replace(regExp,replacement)
title.replace(regExp,operator.operands[1])
);
regExp.lastIndex = 0;
} else {
results.push(title);
}

View File

@@ -16,13 +16,20 @@ Filter operator returning all the selected tiddlers that are untagged
Export our filter function
*/
exports.untagged = function(source,operator,options) {
var results = [],
expected = (operator.prefix === "!");
source(function(tiddler,title) {
if((tiddler && $tw.utils.isArray(tiddler.fields.tags) && tiddler.fields.tags.length > 0) === expected) {
results.push(title);
}
});
var results = [];
if(operator.prefix === "!") {
source(function(tiddler,title) {
if(tiddler && $tw.utils.isArray(tiddler.fields.tags) && tiddler.fields.tags.length > 0) {
$tw.utils.pushTop(results,title);
}
});
} else {
source(function(tiddler,title) {
if(!tiddler || !tiddler.hasField("tags") || ($tw.utils.isArray(tiddler.fields.tags) && tiddler.fields.tags.length === 0)) {
$tw.utils.pushTop(results,title);
}
});
}
return results;
};

View File

@@ -66,7 +66,7 @@ exports.parse = function() {
};
/*
Look for an HTML tag. Returns null if not found, otherwise returns {type: "element", name:, attributes: {}, orderedAttributes: [], isSelfClosing:, start:, end:,}
Look for an HTML tag. Returns null if not found, otherwise returns {type: "element", name:, attributes: [], isSelfClosing:, start:, end:,}
*/
exports.parseTag = function(source,pos,options) {
options = options || {};
@@ -74,8 +74,7 @@ exports.parseTag = function(source,pos,options) {
node = {
type: "element",
start: pos,
attributes: {},
orderedAttributes: []
attributes: {}
};
// Define our regexps
var reTagName = /([a-zA-Z0-9\-\$]+)/g;
@@ -107,7 +106,6 @@ exports.parseTag = function(source,pos,options) {
// Process attributes
var attribute = $tw.utils.parseAttribute(source,pos);
while(attribute) {
node.orderedAttributes.push(attribute);
node.attributes[attribute.name] = attribute;
pos = attribute.end;
// Get the next attribute

View File

@@ -231,10 +231,7 @@ WikiParser.prototype.parseBlock = function(terminatorRegExpString) {
return nextMatch.rule.parse();
}
// Treat it as a paragraph if we didn't find a block rule
var start = this.pos;
var children = this.parseInlineRun(terminatorRegExp);
var end = this.pos;
return [{type: "element", tag: "p", children: children, start: start, end: end }];
return [{type: "element", tag: "p", children: this.parseInlineRun(terminatorRegExp)}];
};
/*
@@ -310,7 +307,7 @@ WikiParser.prototype.parseInlineRunUnterminated = function(options) {
while(this.pos < this.sourceLength && nextMatch) {
// Process the text preceding the run rule
if(nextMatch.matchIndex > this.pos) {
this.pushTextWidget(tree,this.source.substring(this.pos,nextMatch.matchIndex),this.pos,nextMatch.matchIndex);
this.pushTextWidget(tree,this.source.substring(this.pos,nextMatch.matchIndex));
this.pos = nextMatch.matchIndex;
}
// Process the run rule
@@ -320,7 +317,7 @@ WikiParser.prototype.parseInlineRunUnterminated = function(options) {
}
// Process the remaining text
if(this.pos < this.sourceLength) {
this.pushTextWidget(tree,this.source.substr(this.pos),this.pos,this.sourceLength);
this.pushTextWidget(tree,this.source.substr(this.pos));
}
this.pos = this.sourceLength;
return tree;
@@ -340,7 +337,7 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
if(terminatorMatch) {
if(!inlineRuleMatch || inlineRuleMatch.matchIndex >= terminatorMatch.index) {
if(terminatorMatch.index > this.pos) {
this.pushTextWidget(tree,this.source.substring(this.pos,terminatorMatch.index),this.pos,terminatorMatch.index);
this.pushTextWidget(tree,this.source.substring(this.pos,terminatorMatch.index));
}
this.pos = terminatorMatch.index;
if(options.eatTerminator) {
@@ -353,7 +350,7 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
if(inlineRuleMatch) {
// Preceding text
if(inlineRuleMatch.matchIndex > this.pos) {
this.pushTextWidget(tree,this.source.substring(this.pos,inlineRuleMatch.matchIndex),this.pos,inlineRuleMatch.matchIndex);
this.pushTextWidget(tree,this.source.substring(this.pos,inlineRuleMatch.matchIndex));
this.pos = inlineRuleMatch.matchIndex;
}
// Process the inline rule
@@ -367,7 +364,7 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
}
// Process the remaining text
if(this.pos < this.sourceLength) {
this.pushTextWidget(tree,this.source.substr(this.pos),this.pos,this.sourceLength);
this.pushTextWidget(tree,this.source.substr(this.pos));
}
this.pos = this.sourceLength;
return tree;
@@ -376,12 +373,12 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
/*
Push a text widget onto an array, respecting the configTrimWhiteSpace setting
*/
WikiParser.prototype.pushTextWidget = function(array,text,start,end) {
WikiParser.prototype.pushTextWidget = function(array,text) {
if(this.configTrimWhiteSpace) {
text = $tw.utils.trim(text);
}
if(text) {
array.push({type: "text", text: text, start: start, end: end});
array.push({type: "text", text: text});
}
};

View File

@@ -52,7 +52,7 @@ PluginSwitcher.prototype.switchPlugins = function() {
var tiddler = self.wiki.getTiddler(title);
if(tiddler && tiddler.isPlugin() && plugins.indexOf(title) === -1) {
plugins.push(title);
var pluginInfo = $tw.utils.parseJSONSafe(self.wiki.getTiddlerText(title)),
var pluginInfo = JSON.parse(self.wiki.getTiddlerText(title)),
dependents = $tw.utils.parseStringArray(tiddler.fields.dependents || "");
$tw.utils.each(dependents,function(title) {
accumulatePlugin(title);

View File

@@ -0,0 +1,181 @@
/*\
title: $:/core/modules/publisherhandler.js
type: application/javascript
module-type: global
The publisher manages publishing extracts of wikis as external files
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var PUBLISHING_MODAL_TITLE = "$:/language/Publishing/Modal";
/*
Instantiate the publisher manager with the following options
wiki: wiki object to be used
commander: commander object to be used for output
*/
function PublisherHandler(options) {
this.wiki = options.wiki;
this.commander = options.commander;
}
/*
Publish a job
jobTitle: title of tiddler containing details of the job
callback: completion callback invoked callback(err)
options: Include:
commander: commander object associated with publishing under Node.js
variables: hashmap of variables to be passed to renderings
*/
PublisherHandler.prototype.publish = function(jobTitle,callback,options) {
if(jobTitle) {
var job = new PublishingJob(jobTitle,this,options);
job.publish(callback);
}
};
function PublishingJob(jobTitle,publisherHandler,options) {
options = options || {};
// Save params
this.jobTitle = jobTitle;
this.publisherHandler = publisherHandler;
this.commander = options.commander;
this.publishVariables = options.variables || Object.create(null);
}
/*
Start publishing
*/
PublishingJob.prototype.publish = function(callback) {
var self = this;
// Get the job tiddler and check it is enabled
this.jobTiddler = this.publisherHandler.wiki.getTiddler(this.jobTitle);
if(this.jobTiddler && this.jobTiddler.fields.enabled === "yes") {
// Get the list of tiddlers to be exported, defaulting to all non-system tiddlers
this.exportList = this.publisherHandler.wiki.filterTiddlers(this.jobTiddler.fields["export-filter"] || "[!is[system]]");
// Get publisher
this.publisher = this.getPublisher(this.jobTiddler.fields.publisher);
if(this.publisher) {
// Get the sitemap
this.sitemap = new $tw.Sitemap(this.jobTiddler.fields.sitemap,{
wiki: this.publisherHandler.wiki,
variables: this.publishVariables
});
this.sitemap.load();
// Get the output operations
this.operations = this.sitemap.getAllFileDetails(this.exportList);
// Display the progress modal
if($tw.modal) {
this.progressModal = $tw.modal.display(PUBLISHING_MODAL_TITLE,{
progress: true,
variables: {
currentTiddler: this.jobTitle,
totalFiles: this.operations.length + ""
},
onclose: function(event) {
if(event !== self) {
// The modal was closed other than by us programmatically
self.isCancelled = true;
}
}
});
}
// Send the operations to the publisher
this.executeOperations(function(err) {
if(self.progressModal) {
self.progressModal.closeHandler(self);
}
callback(err);
});
} else {
return callback("Unrecognised publisher");
}
} else {
return callback("Missing or disabled job tiddler");
}
};
/*
Instantiate the required publisher object
*/
PublishingJob.prototype.getPublisher = function(publisherName) {
var publisher;
$tw.modules.forEachModuleOfType("publisher",function(title,module) {
if(module.name === publisherName) {
publisher = module;
}
});
return publisher && publisher.create(this.jobTiddler.fields,this.publisherHandler,this);
};
/*
Execute the operations for this job
*/
PublishingJob.prototype.executeOperations = function(callback) {
var self = this,
report = {overwrites: []},
nextOperation = 0,
performNextOperation = function() {
// Check for having been cancelled
if(self.isCancelled) {
if(self.publisher.publishCancel) {
self.publisher.publishCancel();
}
return callback("CANCELLED");
}
// Update progress
if(self.progressModal) {
self.progressModal.setProgress(nextOperation,self.operations.length);
}
// Check for having finished
if(nextOperation >= self.operations.length) {
$tw.utils.nextTick(function() {
self.publisher.publishEnd(callback);
});
} else {
// Execute this operation
var fileDetails = self.operations[nextOperation]();
nextOperation += 1;
self.publisher.publishFile(fileDetails,function() {
$tw.utils.nextTick(performNextOperation);
});
}
};
// Tell the publisher to start, and get back an array of the existing paths
self.publisher.publishStart(function(existingPaths) {
var paths = {};
$tw.utils.each(self.operations,function(operation) {
if(operation.path in paths) {
report.overwrites.push(operation.path);
}
paths[operation.path] = true;
});
// Run the operations
performNextOperation();
});
};
PublishingJob.prototype.saveReport = function(report) {
// Create the report tiddler
var reportTitle = this.wiki.generateNewTitle("$:/temp/publish-report");
$tw.wiki.addTiddler({
title: reportTitle,
text: "* " + report.overwrites.join("\n* ")
});
// Add the report tiddler title to the list field of the publisher parameters tiddler
var paramsTiddler = $tw.wiki.getTiddler(this.publisherParamsTitle),
list = (paramsTiddler.fields.list || []).slice(0);
list.unshift(reportTitle);
$tw.wiki.addTiddler(new $tw.Tiddler(paramsTiddler,{list: list}));
};
exports.PublisherHandler = PublisherHandler;
})();

View File

@@ -0,0 +1,52 @@
/*\
title: $:/core/modules/publishers/filesystem.js
type: application/javascript
module-type: publisher
Handles publishing to the Node.js filesystem
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.name = "filesystem";
exports.create = function(params,publisherHandler,publishingJob) {
return new FileSystemPublisher(params,publisherHandler,publishingJob);
};
function FileSystemPublisher(params,publisherHandler,publishingJob) {
this.params = params;
this.publisherHandler = publisherHandler;
this.publishingJob = publishingJob;
};
FileSystemPublisher.prototype.publishStart = function(callback) {
console.log("publishStart");
// Returns a list of the previously published files
callback([]);
};
FileSystemPublisher.prototype.publishFile = function(item,callback) {
var fs = require("fs"),
path = require("path"),
filepath = path.resolve(this.publishingJob.commander.outputPath,item.path);
$tw.utils.createFileDirectories(filepath);
fs.writeFile(filepath,item.text,item.isBase64 ? "base64" : "utf8",function(err) {
if(err) {
console.log("File writing error",err)
}
callback(err);
});
};
FileSystemPublisher.prototype.publishEnd = function(callback) {
console.log("publishEnd");
callback(null);
};
})();

View File

@@ -42,7 +42,7 @@ AndTidWiki.prototype.save = function(text,method,callback,options) {
window.twi.saveWiki(text);
} else {
// Get the pathname of this document
var pathname = $tw.utils.decodeURIComponentSafe(document.location.toString().split("#")[0]);
var pathname = decodeURIComponent(document.location.toString().split("#")[0]);
// Strip the file://
if(pathname.indexOf("file://") === 0) {
pathname = pathname.substr(7);

View File

@@ -26,7 +26,7 @@ DownloadSaver.prototype.save = function(text,method,callback,options) {
var p = document.location.pathname.lastIndexOf("/");
if(p !== -1) {
// We decode the pathname because document.location is URL encoded by the browser
filename = $tw.utils.decodeURIComponentSafe(document.location.pathname.substr(p+1));
filename = decodeURIComponent(document.location.pathname.substr(p+1));
}
}
if(!filename) {

View File

@@ -61,7 +61,7 @@ GiteaSaver.prototype.save = function(text,method,callback) {
}
var use_put = true;
if(xhr.status !== 404) {
getResponseData = $tw.utils.parseJSONSafe(getResponseDataJson);
getResponseData = JSON.parse(getResponseDataJson);
$tw.utils.each(getResponseData,function(details) {
if(details.name === filename) {
sha = details.sha;
@@ -72,7 +72,7 @@ GiteaSaver.prototype.save = function(text,method,callback) {
}
}
var data = {
message: $tw.language.getString("ControlPanel/Saving/GitService/CommitMessage"),
message: $tw.language.getRawString("ControlPanel/Saving/GitService/CommitMessage"),
content: $tw.utils.base64Encode(text),
sha: sha
};
@@ -104,7 +104,7 @@ GiteaSaver.prototype.upload = function(uri,method,headers,data,callback) {
if(err) {
return callback(err);
}
var putResponseData = $tw.utils.parseJSONSafe(putResponseDataJson);
var putResponseData = JSON.parse(putResponseDataJson);
callback(null);
}
});

View File

@@ -61,7 +61,7 @@ GitHubSaver.prototype.save = function(text,method,callback) {
return callback(err);
}
if(xhr.status !== 404) {
getResponseData = $tw.utils.parseJSONSafe(getResponseDataJson);
getResponseData = JSON.parse(getResponseDataJson);
$tw.utils.each(getResponseData,function(details) {
if(details.name === filename) {
sha = details.sha;
@@ -69,7 +69,7 @@ GitHubSaver.prototype.save = function(text,method,callback) {
});
}
var data = {
message: $tw.language.getString("ControlPanel/Saving/GitService/CommitMessage"),
message: $tw.language.getRawString("ControlPanel/Saving/GitService/CommitMessage"),
content: $tw.utils.base64Encode(text),
branch: branch,
sha: sha
@@ -84,7 +84,7 @@ GitHubSaver.prototype.save = function(text,method,callback) {
if(err) {
return callback(err);
}
var putResponseData = $tw.utils.parseJSONSafe(putResponseDataJson);
var putResponseData = JSON.parse(putResponseDataJson);
callback(null);
}
});

View File

@@ -58,7 +58,7 @@ GitLabSaver.prototype.save = function(text,method,callback) {
}
var requestType = "POST";
if(xhr.status !== 404) {
getResponseData = $tw.utils.parseJSONSafe(getResponseDataJson);
getResponseData = JSON.parse(getResponseDataJson);
$tw.utils.each(getResponseData,function(details) {
if(details.name === filename) {
requestType = "PUT";
@@ -67,7 +67,7 @@ GitLabSaver.prototype.save = function(text,method,callback) {
});
}
var data = {
commit_message: $tw.language.getString("ControlPanel/Saving/GitService/CommitMessage"),
commit_message: $tw.language.getRawString("ControlPanel/Saving/GitService/CommitMessage"),
content: text,
branch: branch,
sha: sha
@@ -82,7 +82,7 @@ GitLabSaver.prototype.save = function(text,method,callback) {
if(err) {
return callback(err);
}
var putResponseData = $tw.utils.parseJSONSafe(putResponseDataJson);
var putResponseData = JSON.parse(putResponseDataJson);
callback(null);
}
});

View File

@@ -43,7 +43,7 @@ TiddlyFoxSaver.prototype.save = function(text,method,callback) {
}
// Create the message element and put it in the message box
var message = document.createElement("div");
message.setAttribute("data-tiddlyfox-path",$tw.utils.decodeURIComponentSafe(pathname));
message.setAttribute("data-tiddlyfox-path",decodeURIComponent(pathname));
message.setAttribute("data-tiddlyfox-content",text);
messageBox.appendChild(message);
// Add an event handler for when the file has been saved

View File

@@ -21,7 +21,7 @@ TWEditSaver.prototype.save = function(text,method,callback) {
return false;
}
// Get the pathname of this document
var pathname = $tw.utils.decodeURIComponentSafe(document.location.pathname);
var pathname = decodeURIComponent(document.location.pathname);
// Strip any query or location part
var p = pathname.indexOf("?");
if(p !== -1) {

View File

@@ -17,7 +17,7 @@ exports.method = "DELETE";
exports.path = /^\/bags\/default\/tiddlers\/(.+)$/;
exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]);
var title = decodeURIComponent(state.params[0]);
state.wiki.deleteTiddler(title);
response.writeHead(204, "OK", {
"Content-Type": "text/plain"

View File

@@ -20,29 +20,22 @@ exports.handler = function(request,response,state) {
var path = require("path"),
fs = require("fs"),
util = require("util"),
suppliedFilename = $tw.utils.decodeURIComponentSafe(state.params[0]),
baseFilename = path.resolve(state.boot.wikiPath,"files"),
filename = path.resolve(baseFilename,suppliedFilename),
suppliedFilename = decodeURIComponent(state.params[0]),
filename = path.resolve(state.boot.wikiPath,"files",suppliedFilename),
extension = path.extname(filename);
// Check that the filename is inside the wiki files folder
if(path.relative(baseFilename,filename).indexOf("..") !== 0) {
// Send the file
fs.readFile(filename,function(err,content) {
var status,content,type = "text/plain";
if(err) {
console.log("Error accessing file " + filename + ": " + err.toString());
status = 404;
content = "File '" + suppliedFilename + "' not found";
} else {
status = 200;
content = content;
type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream");
}
state.sendResponse(status,{"Content-Type": type},content);
});
} else {
state.sendResponse(404,{"Content-Type": "text/plain"},"File '" + suppliedFilename + "' not found");
}
fs.readFile(filename,function(err,content) {
var status,content,type = "text/plain";
if(err) {
console.log("Error accessing file " + filename + ": " + err.toString());
status = 404;
content = "File '" + suppliedFilename + "' not found";
} else {
status = 200;
content = content;
type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream");
}
state.sendResponse(status,{"Content-Type": type},content);
});
};
}());

View File

@@ -14,7 +14,7 @@ GET /
exports.method = "GET";
exports.path = /^\/$/;
exports.path = /^\/index.html$/;
exports.handler = function(request,response,state) {
var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")),

View File

@@ -21,6 +21,7 @@ exports.handler = function(request,response,state) {
username: state.authenticatedUsername || state.server.get("anon-username") || "",
anonymous: !state.authenticatedUsername,
read_only: !state.server.isAuthorized("writers",state.authenticatedUsername),
sse_enabled: state.server.get("sse-enabled") === "yes",
space: {
recipe: "default"
},

View File

@@ -17,7 +17,7 @@ exports.method = "GET";
exports.path = /^\/([^\/]+)$/;
exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
var title = decodeURIComponent(state.params[0]),
tiddler = state.wiki.getTiddler(title);
if(tiddler) {
var renderType = tiddler.getFieldString("_render_type"),

View File

@@ -17,7 +17,7 @@ exports.method = "GET";
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
var title = decodeURIComponent(state.params[0]),
tiddler = state.wiki.getTiddler(title),
tiddlerFields = {},
knownFields = [

View File

@@ -17,8 +17,8 @@ exports.method = "PUT";
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
fields = $tw.utils.parseJSONSafe(state.data);
var title = decodeURIComponent(state.params[0]),
fields = JSON.parse(state.data);
// Pull up any subfields in the `fields` object
if(fields.fields) {
$tw.utils.each(fields.fields,function(field,name) {
@@ -30,7 +30,7 @@ exports.handler = function(request,response,state) {
if(fields.revision) {
delete fields.revision;
}
state.wiki.addTiddler(new $tw.Tiddler(fields,{title: title}));
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
var changeCount = state.wiki.getChangeCount(title).toString();
response.writeHead(204, "OK",{
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",

View File

@@ -0,0 +1,70 @@
/*\
title: $:/core/modules/server/server-sent-events.js
type: application/javascript
module-type: library
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
parameters:
prefix - usually the plugin path, such as `plugins/tiddlywiki/tiddlyweb`. The
route will match `/events/${prefix}` exactly.
handler - a function that will be called each time a request comes in with the
request and state from the route and an emit function to call.
*/
var ServerSentEvents = function ServerSentEvents(prefix, handler) {
this.handler = handler;
this.prefix = prefix;
};
ServerSentEvents.prototype.getExports = function() {
return {
bodyFormat: "stream",
method: "GET",
path: new RegExp("^/events/" + this.prefix + "$"),
handler: this.handleEventRequest.bind(this)
};
};
ServerSentEvents.prototype.handleEventRequest = function(request,response,state) {
if(ServerSentEvents.prototype.isEventStreamRequest(request)) {
response.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
});
this.handler(request,state,this.emit.bind(this,response),this.end.bind(this,response));
} else {
response.writeHead(406,"Not Acceptable",{});
response.end();
}
};
ServerSentEvents.prototype.isEventStreamRequest = function(request) {
return request.headers.accept &&
request.headers.accept.match(/^text\/event-stream/);
};
ServerSentEvents.prototype.emit = function(response,event,data) {
if(typeof event !== "string" || event.indexOf("\n") !== -1) {
throw new Error("Type must be a single-line string");
}
if(typeof data !== "string" || data.indexOf("\n") !== -1) {
throw new Error("Data must be a single-line string");
}
response.write("event: " + event + "\ndata: " + data + "\n\n", "utf8");
};
ServerSentEvents.prototype.end = function(response) {
response.end();
};
exports.ServerSentEvents = ServerSentEvents;
})();

View File

@@ -63,10 +63,7 @@ function Server(options) {
self.addAuthenticator(authenticatorDefinition.AuthenticatorClass);
});
// Load route handlers
$tw.modules.forEachModuleOfType("route", function(title,routeDefinition) {
// console.log("Loading server route " + title);
self.addRoute(routeDefinition);
});
this.addRouteHandlers();
// Initialise the http vs https
this.listenOptions = null;
this.protocol = "http";
@@ -182,11 +179,43 @@ Server.prototype.addAuthenticator = function(AuthenticatorClass) {
}
};
Server.prototype.findMatchingRoute = function(request,state) {
Server.prototype.addRouteHandlers = function() {
var self = this;
// Load route handlers from sitemap if present, or just load all route modules
if(this.variables.sitemap) {
this.sitemap = new $tw.Sitemap(this.variables.sitemap,{
wiki: this.wiki,
variables: {}
});
this.sitemap.load();
$tw.utils.each(this.sitemap.getServerRoutes(),function(routeInfo) {
self.addRoute({
method: "GET",
path: routeInfo.regexp,
handler: function(request,response,state) {
var fileDetails = routeInfo.handler(state.params);
if(fileDetails) {
response.writeHead(200, {"Content-Type": fileDetails.type});
response.end(fileDetails.text,fileDetails.isBase64 ? "base64" : "utf8");
} else {
response.writeHead(404);
response.end();
}
}
});
});
} else {
$tw.modules.forEachModuleOfType("route",function(title,routeDefinition) {
self.addRoute(routeDefinition);
});
}
};
Server.prototype.findMatchingRoute = function(request,state,options) {
options = options || {};
for(var t=0; t<this.routes.length; t++) {
var potentialRoute = this.routes[t],
pathRegExp = potentialRoute.path,
pathname = state.urlInfo.pathname,
pathname = options.pathname || state.urlInfo.pathname,
match;
if(state.pathPrefix) {
if(pathname.substr(0,state.pathPrefix.length) === state.pathPrefix) {
@@ -263,6 +292,15 @@ Server.prototype.requestHandler = function(request,response,options) {
}
// Find the route that matches this path
var route = self.findMatchingRoute(request,state);
if(!route) {
// Try with the default document
var defaultDocumentPathname = state.urlInfo.pathname;
if(defaultDocumentPathname.substr(-1) !== "/") {
defaultDocumentPathname = defaultDocumentPathname + "/";
}
defaultDocumentPathname = defaultDocumentPathname + "index.html";
route = self.findMatchingRoute(request,state,{pathname: defaultDocumentPathname});
}
// Optionally output debug info
if(self.get("debug-level") !== "none") {
console.log("Request path:",JSON.stringify(state.urlInfo));

236
core/modules/sitemap.js Normal file
View File

@@ -0,0 +1,236 @@
/*\
title: $:/core/modules/sitemap.js
type: application/javascript
module-type: global
Sitemaps are used for static publishing and web serving
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function Sitemap(sitemapTitle,options) {
options = options || {};
this.sitemapTitle = sitemapTitle;
this.wiki = options.wiki;
this.routes = [];
this.variables = $tw.utils.extend({},options.variables);
}
Sitemap.prototype.load = function(sitemapTitle) {
var self = this;
// Get the sitemap
var sitemapTiddler = this.wiki.getTiddler(this.sitemapTitle);
if(sitemapTiddler) {
// Collect each route
$tw.utils.each(sitemapTiddler.fields.list,function(routeTitle) {
var routeTiddler = self.wiki.getTiddler(routeTitle);
if(routeTiddler) {
// Convert the path into a regexp and an array of {field:,function:} for each capture group
var regexpurgatedParameterisedPath = self.regexpurgateParameterisedPath(routeTiddler.fields["route-path"]);
self.routes.push({
title: routeTitle,
params: routeTiddler.getFieldStrings({prefix: "route-"}),
variables: routeTiddler.getFieldStrings({prefix: "var-"}),
regexp: regexpurgatedParameterisedPath.regexp,
captureGroups: regexpurgatedParameterisedPath.captureGroups
});
}
});
}
};
Sitemap.prototype.renderRoute = function(title,route) {
var tiddler = this.wiki.getTiddler(title);
switch(route.params.type) {
case "raw":
return {
path: this.resolveParameterisedPath(route.params.path,title),
text: tiddler.fields.text || "",
type: tiddler.fields.type || "",
isBase64: ($tw.config.contentTypeInfo[tiddler.fields.type] || {}).encoding === "base64"
};
break;
case "render":
var parser = {
tree: [
{
"type": "importvariables",
"attributes": {
"tiddler": {
"name": "tiddler",
"type": "string",
"value": this.sitemapTitle,
}
},
"tag": "$importvariables",
"isBlock": false,
"children": [
{
"type": "importvariables",
"attributes": {
"tiddler": {
"name": "tiddler",
"type": "string",
"value": route.title,
}
},
"tag": "$importvariables",
"isBlock": false,
"children": this.wiki.parseTiddler(route.params.template,{parseAsInline: true}).tree
}
]
}
]
},
widgetNode = this.wiki.makeWidget(parser,{
variables: $tw.utils.extend({},this.variables,{currentTiddler: title})
}),
container = $tw.fakeDocument.createElement("div");
widgetNode.render(container,null);
return {
path: this.resolveParameterisedPath(route.params.path,title),
text: container.textContent,
type: route.params["output-type"] || "text/html"
};
break;
}
};
/*
Returns an array of functions that return {path:,text:,type:,isBase64:} for each path
*/
Sitemap.prototype.getAllFileDetails = function(exportTiddlers) {
var self = this,
output = [];
$tw.utils.each(this.routes,function(route) {
var routeFilter = route.params["tiddler-filter"] || "DUMMY_RESULT", // If no filter is provided, use a dummy filter that returns a single result
routeTiddlers = self.wiki.filterTiddlers(routeFilter,null,self.wiki.makeTiddlerIterator(exportTiddlers));
$tw.utils.each(routeTiddlers,function(title) {
output.push(self.renderRoute.bind(self,title,route));
});
});
return output;
};
/*
Returns an array of server routes {regexp:, handler:}
*/
Sitemap.prototype.getServerRoutes = function() {
var self = this,
output = [];
$tw.utils.each(this.routes,function(route) {
output.push({
regexp: route.regexp,
handler: function(params) {
// Locate the tiddler identified by the capture groups, if any
var title = null,
nextParam = 0;
$tw.utils.each(route.captureGroups,function(captureGroup) {
var param = params[nextParam++];
if(captureGroup.field === "title") {
switch(captureGroup.function) {
case "slugify":
var titles = self.wiki.unslugify(param);
if(titles && titles.length > 0) {
title = titles[0];
}
break;
}
}
})
// Check that the tiddler passes the route filter
if(route.params["tiddler-filter"]) {
if(!title) {
return null;
}
var routeTiddlers = self.wiki.filterTiddlers(route.params["tiddler-filter"],null,self.wiki.makeTiddlerIterator([title]));
if(routeTiddlers.indexOf(title) === -1) {
return null;
}
}
// Return the rendering or raw tiddler
return self.renderRoute(title,route);
}
});
});
return output;
};
/*
Apply a tiddler to a parameterised path to create a usable path
*/
Sitemap.prototype.resolveParameterisedPath = function(parameterisedPath,title) {
var self = this;
// Split the path on $*_*$ markers
var tiddler = this.wiki.getTiddler(title),
output = [];
$tw.utils.each(parameterisedPath.split(/(\$[a-z_]+\$)/),function(part) {
var match = part.match(/\$([a-z]+)_([a-z]+)\$/);
if(match) {
var value;
// Get the base value
switch(match[1]) {
case "uri":
case "title":
value = title;
break;
case "type":
value = tiddler.fields.type || "text/vnd.tiddlywiki";
break;
}
// Apply the encoding function
switch(match[2]) {
case "encoded":
value = encodeURIComponent(value);
break;
case "doubleencoded":
value = encodeURIComponent(encodeURIComponent(value));
break;
case "slugify":
value = self.wiki.slugify(value);
break;
case "extension":
value = ($tw.config.contentTypeInfo[value] || {extension: "."}).extension.slice(1);
break;
}
output.push(value);
} else {
output.push(part);
}
});
return output.join("");
};
/*
// Convert the path into a regexp and an array of {field:,function:} for each capture group
*/
Sitemap.prototype.regexpurgateParameterisedPath = function(parameterisedPath) {
var regexpParts = ["\\/"],
captureGroups = [];
$tw.utils.each(parameterisedPath.split(/(\$[a-z_]+\$)/),function(part) {
var match = part.match(/\$([a-z]+)_([a-z]+)\$/);
if(match) {
regexpParts.push("(.+)");
captureGroups.push({
field: match[1],
function: match[2]
});
} else {
regexpParts.push($tw.utils.escapeRegExp(part));
}
});
return {
regexp: new RegExp("^" + regexpParts.join("") + "$"),
captureGroups: captureGroups
};
};
exports.Sitemap = Sitemap;
})();

View File

@@ -152,7 +152,7 @@ exports.startup = function() {
if(event.data.status.charAt(0) === "2") {
if(event.data.cookies) {
if(event.data.cookies.type === "save-info") {
var tiddlers = $tw.utils.parseJSONSafe(event.data.body);
var tiddlers = JSON.parse(event.data.body);
$tw.utils.each(tiddlers,function(tiddler) {
$tw.wiki.addTiddler(new $tw.Tiddler($tw.wiki.getCreationFields(),tiddler,{
title: event.data.cookies.infoTitlePrefix + event.data.cookies.url + "/" + tiddler.title,
@@ -170,7 +170,7 @@ exports.startup = function() {
},$tw.wiki.getModificationFields()));
});
} else if(event.data.cookies.type === "save-tiddler") {
var tiddler = $tw.utils.parseJSONSafe(event.data.body);
var tiddler = JSON.parse(event.data.body);
$tw.wiki.addTiddler(new $tw.Tiddler(tiddler));
}
}

View File

@@ -73,6 +73,12 @@ exports.startup = function() {
}
});
}
// Hook up events for the publisher handler
$tw.rootWidget.addEventListener("tm-publish",function(event) {
$tw.publisherHandler.publish(event.paramObject.job,function(err) {
console.log("Finished publishing with result:",err);
});
});
// If we're being viewed on a data: URI then give instructions for how to save
if(document.location.protocol === "data:") {
$tw.rootWidget.dispatchEvent({

View File

@@ -129,6 +129,10 @@ exports.startup = function() {
dirtyTracking: !$tw.syncadaptor,
preloadDirty: $tw.boot.preloadDirty || []
});
// Install the publisher handler
$tw.publisherHandler = new $tw.PublisherHandler({
wiki: $tw.wiki
});
// Host-specific startup
if($tw.browser) {
// Install the popup manager

View File

@@ -120,10 +120,10 @@ function openStartupTiddlers(options) {
var hash = $tw.locationHash.substr(1),
split = hash.indexOf(":");
if(split === -1) {
target = $tw.utils.decodeURIComponentSafe(hash.trim());
target = decodeURIComponent(hash.trim());
} else {
target = $tw.utils.decodeURIComponentSafe(hash.substr(0,split).trim());
storyFilter = $tw.utils.decodeURIComponentSafe(hash.substr(split + 1).trim());
target = decodeURIComponent(hash.substr(0,split).trim());
storyFilter = decodeURIComponent(hash.substr(split + 1).trim());
}
}
// If the story wasn't specified use the current tiddlers or a blank story

View File

@@ -20,6 +20,7 @@ Syncer.prototype.titleIsAnonymous = "$:/status/IsAnonymous";
Syncer.prototype.titleIsReadOnly = "$:/status/IsReadOnly";
Syncer.prototype.titleUserName = "$:/status/UserName";
Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
Syncer.prototype.titleSyncDisablePolling = "$:/config/SyncDisablePolling";
Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
@@ -89,7 +90,7 @@ function Syncer(options) {
if(filteredChanges.length > 0) {
self.processTaskQueue();
} else {
// Look for deletions of tiddlers we're already syncing
// 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)) {
@@ -121,7 +122,7 @@ function Syncer(options) {
self.login(username,password,function() {});
} else {
// No username and password, so we display a prompt
self.handleLoginEvent();
self.handleLoginEvent();
}
});
$tw.rootWidget.addEventListener("tm-logout",function() {
@@ -138,7 +139,7 @@ function Syncer(options) {
if(!this.disableUI && this.wiki.getTiddlerText(this.titleSyncDisableLazyLoading) !== "yes") {
this.wiki.addEventListener("lazyLoad",function(title) {
self.handleLazyLoadEvent(title);
});
});
}
// Get the login status
this.getStatus(function(err,isLoggedIn) {
@@ -173,8 +174,8 @@ Syncer.prototype.getTiddlerRevision = function(title) {
if(this.syncadaptor && this.syncadaptor.getTiddlerRevision) {
return this.syncadaptor.getTiddlerRevision(title);
} else {
return this.wiki.getTiddler(title).fields.revision;
}
return this.wiki.getTiddler(title).fields.revision;
}
};
/*
@@ -267,9 +268,9 @@ Syncer.prototype.getStatus = function(callback) {
// Mark us as not logged in
this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
// Get login status
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous) {
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous,isPollingDisabled) {
if(err) {
self.displayError("Get Status Error",err);
self.logger.alert(err);
} else {
// Set the various status tiddlers
self.wiki.addTiddler({title: self.titleIsReadOnly,text: isReadOnly ? "yes" : "no"});
@@ -278,6 +279,9 @@ Syncer.prototype.getStatus = function(callback) {
if(isLoggedIn) {
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
}
if(isPollingDisabled) {
self.wiki.addTiddler({title: self.titleSyncDisablePolling, text: "yes"});
}
}
// Invoke the callback
if(callback) {
@@ -301,12 +305,15 @@ Syncer.prototype.syncFromServer = function() {
}
},
triggerNextSync = function() {
self.pollTimerId = setTimeout(function() {
self.pollTimerId = null;
self.syncFromServer.call(self);
},self.pollTimerInterval);
if(pollingEnabled) {
self.pollTimerId = setTimeout(function() {
self.pollTimerId = null;
self.syncFromServer.call(self);
},self.pollTimerInterval);
}
},
syncSystemFromServer = (self.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes" ? true : false);
syncSystemFromServer = (self.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes"),
pollingEnabled = (self.wiki.getTiddlerText(self.titleSyncDisablePolling) !== "yes");
if(this.syncadaptor && this.syncadaptor.getUpdatedTiddlers) {
this.logger.log("Retrieving updated tiddler list");
cancelNextSync();
@@ -329,7 +336,7 @@ Syncer.prototype.syncFromServer = function() {
});
if(updates.modifications.length > 0 || updates.deletions.length > 0) {
self.processTaskQueue();
}
}
}
});
} else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
@@ -472,7 +479,7 @@ Syncer.prototype.handleLogoutEvent = function() {
if(this.syncadaptor.logout) {
this.syncadaptor.logout(function(err) {
if(err) {
self.displayError("Logout Error",err);
self.logger.alert(err);
} else {
self.getStatus();
}
@@ -509,7 +516,7 @@ Syncer.prototype.processTaskQueue = function() {
} else {
self.updateDirtyStatus();
// Process the next task
self.processTaskQueue.call(self);
self.processTaskQueue.call(self);
}
});
} else {
@@ -517,11 +524,11 @@ Syncer.prototype.processTaskQueue = function() {
this.updateDirtyStatus();
// And trigger a timeout if there is a pending task
if(task === true) {
this.triggerTimeout();
this.triggerTimeout();
}
}
} else {
this.updateDirtyStatus();
this.updateDirtyStatus();
}
};
@@ -555,7 +562,7 @@ Syncer.prototype.chooseNextTask = function() {
isReadyToSave = !tiddlerInfo || !tiddlerInfo.timestampLastSaved || tiddlerInfo.timestampLastSaved < thresholdLastSaved;
if(hasChanged) {
if(isReadyToSave) {
return new SaveTiddlerTask(this,title);
return new SaveTiddlerTask(this,title);
} else {
havePending = true;
}

View File

@@ -24,13 +24,13 @@ exports.isDraft = function() {
return this.hasField("draft.of");
};
exports.getFieldString = function(field,defaultValue) {
exports.getFieldString = function(field) {
var value = this.fields[field];
// Check for a missing field
if(value === undefined || value === null) {
return defaultValue || "";
return "";
}
// Stringify the field with the associated tiddler field module (if any)
// Parse the field with the associated module (if any)
var fieldModule = $tw.Tiddler.fieldModules[field];
if(fieldModule && fieldModule.stringify) {
return fieldModule.stringify.call(this,value);
@@ -54,15 +54,27 @@ exports.getFieldList = function(field) {
/*
Get all the fields as a hashmap of strings. Options:
exclude: an array of field names to exclude
prefix: an optional field name prefix. Only fields with the prefix are included, and the prefix is stripped from the name
*/
exports.getFieldStrings = function(options) {
options = options || {};
var exclude = options.exclude || [];
var fields = {};
for(var field in this.fields) {
if($tw.utils.hop(this.fields,field)) {
if(exclude.indexOf(field) === -1) {
fields[field] = this.getFieldString(field);
var exclude = options.exclude || [],
fields = {},
field;
if(options.prefix) {
for(field in this.fields) {
if($tw.utils.hop(this.fields,field)) {
if(exclude.indexOf(field) === -1 && field.substring(0,options.prefix.length) === options.prefix) {
fields[field.substring(options.prefix.length)] = this.getFieldString(field);
}
}
}
} else {
for(field in this.fields) {
if($tw.utils.hop(this.fields,field)) {
if(exclude.indexOf(field) === -1) {
fields[field] = this.getFieldString(field);
}
}
}
}

View File

@@ -41,7 +41,7 @@ exports.upgrade = function(wiki,titles,tiddlers) {
// Check if we're dealing with a plugin
if(incomingTiddler && incomingTiddler["plugin-type"]) {
// Check whether the plugin contains JS modules
var requiresReload = wiki.doesPluginInfoRequireReload($tw.utils.parseJSONSafe(incomingTiddler.text)) ? (wiki.getTiddlerText("$:/language/ControlPanel/Plugins/PluginWillRequireReload") + " ") : "";
var requiresReload = wiki.doesPluginInfoRequireReload(JSON.parse(incomingTiddler.text)) ? (wiki.getTiddlerText("$:/language/ControlPanel/Plugins/PluginWillRequireReload") + " ") : "";
messages[title] = requiresReload;
if(incomingTiddler.version) {
// Upgrade the incoming plugin if it is in the upgrade library

View File

@@ -21,7 +21,7 @@ exports.extractEncryptedStoreArea = function(text) {
if(encryptedStoreAreaStart !== -1) {
var encryptedStoreAreaEnd = text.indexOf("</pre>",encryptedStoreAreaStart);
if(encryptedStoreAreaEnd !== -1) {
return $tw.utils.htmlDecode(text.substring(encryptedStoreAreaStart + encryptedStoreAreaStartMarker.length,encryptedStoreAreaEnd));
return $tw.utils.htmlDecode(text.substring(encryptedStoreAreaStart + encryptedStoreAreaStartMarker.length,encryptedStoreAreaEnd-1));
}
}
return null;
@@ -33,7 +33,7 @@ Attempt to extract the tiddlers from an encrypted store area using the current p
exports.decryptStoreArea = function(encryptedStoreArea,password) {
var decryptedText = $tw.crypto.decrypt(encryptedStoreArea,password);
if(decryptedText) {
var json = $tw.utils.parseJSONSafe(decryptedText),
var json = JSON.parse(decryptedText),
tiddlers = [];
for(var title in json) {
if(title !== "$:/isEncrypted") {

View File

@@ -16,7 +16,7 @@ Browser data transfer utilities, used with the clipboard and drag and drop
Options:
domNode: dom node to make draggable
dragImageType: "pill", "blank" or "dom" (the default)
dragImageType: "pill" or "dom"
dragTiddlerFn: optional function to retrieve the title of tiddler to drag
dragFilterFn: optional function to retreive the filter defining a list of tiddlers to drag
widget: widget to use as the contect for the filter
@@ -73,9 +73,6 @@ exports.makeDraggable = function(options) {
if(dataTransfer.setDragImage) {
if(dragImageType === "pill") {
dataTransfer.setDragImage(dragImage.firstChild,-16,-16);
} else if (dragImageType === "blank") {
dragImage.removeChild(dragImage.firstChild);
dataTransfer.setDragImage(dragImage,0,0);
} else {
var r = domNode.getBoundingClientRect();
dataTransfer.setDragImage(domNode,event.clientX-r.left,event.clientY-r.top);
@@ -167,7 +164,7 @@ var importDataTypes = [
}},
{type: "URL", IECompatible: true, toTiddlerFieldsArray: function(data,fallbackTitle) {
// Check for tiddler data URI
var match = $tw.utils.decodeURIComponentSafe(data).match(/^data\:text\/vnd\.tiddler,(.*)/i);
var match = decodeURIComponent(data).match(/^data\:text\/vnd\.tiddler,(.*)/i);
if(match) {
return parseJSONTiddlers(match[1],fallbackTitle);
} else {
@@ -176,7 +173,7 @@ var importDataTypes = [
}},
{type: "text/x-moz-url", IECompatible: false, toTiddlerFieldsArray: function(data,fallbackTitle) {
// Check for tiddler data URI
var match = $tw.utils.decodeURIComponentSafe(data).match(/^data\:text\/vnd\.tiddler,(.*)/i);
var match = decodeURIComponent(data).match(/^data\:text\/vnd\.tiddler,(.*)/i);
if(match) {
return parseJSONTiddlers(match[1],fallbackTitle);
} else {
@@ -198,7 +195,7 @@ var importDataTypes = [
];
function parseJSONTiddlers(json,fallbackTitle) {
var data = $tw.utils.parseJSONSafe(json);
var data = JSON.parse(json);
if(!$tw.utils.isArray(data)) {
data = [data];
}
@@ -208,10 +205,10 @@ function parseJSONTiddlers(json,fallbackTitle) {
return data;
};
function dragEventContainsType(event,targetType) {
exports.dragEventContainsFiles = function(event) {
if(event.dataTransfer.types) {
for(var i=0; i<event.dataTransfer.types.length; i++) {
if(event.dataTransfer.types[i] === targetType) {
if(event.dataTransfer.types[i] === "Files") {
return true;
break;
}
@@ -220,10 +217,4 @@ function dragEventContainsType(event,targetType) {
return false;
};
exports.dragEventContainsFiles = function(event) {
return (dragEventContainsType(event,"Files") && !dragEventContainsType(event,"text/plain"));
};
exports.dragEventContainsType = dragEventContainsType;
})();

View File

@@ -34,23 +34,6 @@ exports.httpRequest = function(options) {
});
return result;
},
getHeader = function(targetHeader) {
return headers[targetHeader] || headers[targetHeader.toLowerCase()];
},
isSimpleRequest = function(type,headers) {
if(["GET","HEAD","POST"].indexOf(type) === -1) {
return false;
}
for(var header in headers) {
if(["accept","accept-language","content-language","content-type"].indexOf(header.toLowerCase()) === -1) {
return false;
}
}
if(hasHeader("Content-Type") && ["application/x-www-form-urlencoded","multipart/form-data","text/plain"].indexOf(getHeader["Content-Type"]) === -1) {
return false;
}
return true;
},
returnProp = options.returnProp || "responseText",
request = new XMLHttpRequest(),
data = "",
@@ -93,7 +76,7 @@ exports.httpRequest = function(options) {
if(data && !hasHeader("Content-Type")) {
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
}
if(!hasHeader("X-Requested-With") && !isSimpleRequest(type,headers)) {
if(!hasHeader("X-Requested-With")) {
request.setRequestHeader("X-Requested-With","TiddlyWiki");
}
try {

View File

@@ -12,8 +12,9 @@ Modal message mechanism
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/widgets/widget.js");
var navigator = require("$:/core/modules/widgets/navigator.js");
var widget = require("$:/core/modules/widgets/widget.js"),
navigator = require("$:/core/modules/widgets/navigator.js"),
dm = $tw.utils.domMaker;
var Modal = function(wiki) {
this.wiki = wiki;
@@ -26,6 +27,10 @@ Display a modal dialogue
options: see below
Options include:
downloadLink: Text of a big download link to include
variables: variables to be passed to the modal
event: optional DOM event that initiated the modal
progress: set to true to add a progress bar
onclose: callback for when the modal is closed
*/
Modal.prototype.display = function(title,options) {
options = options || {};
@@ -47,7 +52,6 @@ Modal.prototype.display = function(title,options) {
"tv-story-list": (options.event && options.event.widget ? options.event.widget.getVariable("tv-story-list") : ""),
"tv-history-list": (options.event && options.event.widget ? options.event.widget.getVariable("tv-history-list") : "")
},options.variables);
// Create the wrapper divs
var wrapper = this.srcDocument.createElement("div"),
modalBackdrop = this.srcDocument.createElement("div"),
@@ -55,6 +59,7 @@ Modal.prototype.display = function(title,options) {
modalHeader = this.srcDocument.createElement("div"),
headerTitle = this.srcDocument.createElement("h3"),
modalBody = this.srcDocument.createElement("div"),
modalProgress = this.srcDocument.createElement("div"),
modalLink = this.srcDocument.createElement("a"),
modalFooter = this.srcDocument.createElement("div"),
modalFooterHelp = this.srcDocument.createElement("span"),
@@ -71,6 +76,7 @@ Modal.prototype.display = function(title,options) {
$tw.utils.addClass(modalWrapper,"tc-modal");
$tw.utils.addClass(modalHeader,"tc-modal-header");
$tw.utils.addClass(modalBody,"tc-modal-body");
$tw.utils.addClass(modalProgress,"tc-modal-progress");
$tw.utils.addClass(modalFooter,"tc-modal-footer");
// Join them together
wrapper.appendChild(modalBackdrop);
@@ -78,6 +84,15 @@ Modal.prototype.display = function(title,options) {
modalHeader.appendChild(headerTitle);
modalWrapper.appendChild(modalHeader);
modalWrapper.appendChild(modalBody);
if(options.progress) {
var modalProgressBar = this.srcDocument.createElement("div");
modalProgressBar.className = "tc-modal-progress-bar";
modalProgress.appendChild(modalProgressBar);
var modalProgressPercent = this.srcDocument.createElement("div");
modalProgressPercent.className = "tc-modal-progress-percent";
modalProgress.appendChild(modalProgressPercent);
modalWrapper.appendChild(modalProgress);
}
modalFooter.appendChild(modalFooterHelp);
modalFooter.appendChild(modalFooterButtons);
modalWrapper.appendChild(modalFooter);
@@ -105,7 +120,6 @@ Modal.prototype.display = function(title,options) {
parentWidget: $tw.rootWidget
});
navigatorWidgetNode.render(modalBody,null);
// Render the title of the message
var headerWidgetNode = this.wiki.makeTranscludeWidget(title,{
field: "subtitle",
@@ -182,6 +196,10 @@ Modal.prototype.display = function(title,options) {
this.wiki.addEventListener("change",refreshHandler);
// Add the close event handler
var closeHandler = function(event) {
// Call the onclose handler
if(options.onclose) {
options.onclose(event);
}
// Remove our refresh handler
self.wiki.removeEventListener("change",refreshHandler);
// Decrease the modal count and adjust the body class
@@ -236,6 +254,22 @@ Modal.prototype.display = function(title,options) {
$tw.utils.setStyle(modalWrapper,[
{transform: "translateY(0px)"}
]);
// Return the wrapper node
return {
domNode: wrapper,
closeHandler: closeHandler,
setProgress: function(numerator,denominator) {
// Remove old progress
while(modalProgressPercent.hasChildNodes()) {
modalProgressPercent.removeChild(modalProgressPercent.firstChild);
}
// Set new text
var percent = (numerator * 100 /denominator).toFixed(2) + "%";
modalProgressPercent.appendChild(self.srcDocument.createTextNode(percent));
// Set bar width
modalProgressBar.style.width = percent;
}
};
};
Modal.prototype.adjustPageClass = function() {

View File

@@ -30,7 +30,11 @@ exports.getEditionInfo = function() {
var entry = entries[entryIndex];
// Check if directories have a valid tiddlywiki.info
if(!editionInfo[entry] && $tw.utils.isDirectory(path.resolve(editionPath,entry))) {
var info = $tw.utils.parseJSONSafe(fs.readFileSync(path.resolve(editionPath,entry,"tiddlywiki.info"),"utf8"),null);
var info;
try {
info = JSON.parse(fs.readFileSync(path.resolve(editionPath,entry,"tiddlywiki.info"),"utf8"));
} catch(ex) {
}
if(info) {
editionInfo[entry] = info;
}

View File

@@ -235,7 +235,7 @@ Object.defineProperty(TW_Element.prototype, "innerHTML", {
if(node instanceof TW_Element) {
b.push(node.outerHTML);
} else if(node instanceof TW_TextNode) {
b.push($tw.utils.htmlTextEncode(node.textContent));
b.push($tw.utils.htmlEncode(node.textContent));
}
});
return b.join("");

View File

@@ -95,15 +95,6 @@ LinkedList.prototype.toArray = function() {
return output;
};
LinkedList.prototype.makeTiddlerIterator = function(wiki) {
var self = this;
return function(callback) {
self.each(function(title) {
callback(wiki.getTiddler(title),title);
});
};
};
function _removeOne(list,value) {
var prevEntry = list.prev[value],
nextEntry = list.next[value],

View File

@@ -24,8 +24,10 @@ exports.repackPlugin = function(title,additionalTiddlers,excludeTiddlers) {
throw "No such tiddler as " + title;
}
// Extract the JSON
var jsonPluginTiddler = $tw.utils.parseJSONSafe(pluginTiddler.fields.text,null);
if(!jsonPluginTiddler) {
var jsonPluginTiddler;
try {
jsonPluginTiddler = JSON.parse(pluginTiddler.fields.text);
} catch(e) {
throw "Cannot parse plugin tiddler " + title + "\n" + $tw.language.getString("Error/Caption") + ": " + e;
}
// Get the list of tiddlers

View File

@@ -199,8 +199,6 @@ exports.transliterationPairs = {
"Nj":"N",
"Ñ":"N",
"NJ":"NJ",
"ð":"d",
"Ð":"D",
"Ó":"O",
"Ŏ":"O",
"Ǒ":"O",
@@ -267,8 +265,6 @@ exports.transliterationPairs = {
"Ɽ":"R",
"Ꜿ":"C",
"Ǝ":"E",
"ß":"ss",
"ẞ":"SS",
"Ś":"S",
"Ṥ":"S",
"Š":"S",
@@ -279,8 +275,6 @@ exports.transliterationPairs = {
"Ṡ":"S",
"Ṣ":"S",
"Ṩ":"S",
"þ": "th",
"Þ": "TH",
"Ť":"T",
"Ţ":"T",
"Ṱ":"T",
@@ -913,8 +907,7 @@ exports.transliterationPairs = {
"т":"t",
"ь":"'",
"б":"b",
"ю":"yu",
"…":"..."
"ю":"yu"
};
exports.transliterate = function(str) {

View File

@@ -383,15 +383,6 @@ exports.formatDateString = function(date,template) {
[/^0WW/, function() {
return $tw.utils.pad($tw.utils.getWeek(date));
}],
[/^0ddddd/, function() {
return $tw.utils.pad(Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24),3);
}],
[/^ddddd/, function() {
return Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
}],
[/^dddd/, function() {
return [7,1,2,3,4,5,6][date.getDay()];
}],
[/^ddd/, function() {
return $tw.language.getString("Date/Short/Day/" + date.getDay());
}],

View File

@@ -59,7 +59,7 @@ Invoke the action associated with this widget
ConfirmWidget.prototype.invokeAction = function(triggeringWidget,event) {
var invokeActions = true,
handled = true,
win = event && event.event && event.event.view ? event.event.view : window;
win = event.event && event.event.view ? event.event.view : window;
if(this.prompt) {
invokeActions = win.confirm(this.message);
}

View File

@@ -39,8 +39,6 @@ SendMessageWidget.prototype.execute = function() {
this.actionParam = this.getAttribute("$param");
this.actionName = this.getAttribute("$name");
this.actionValue = this.getAttribute("$value","");
this.actionNames = this.getAttribute("$names");
this.actionValues = this.getAttribute("$values");
};
/*
@@ -61,20 +59,13 @@ Invoke the action associated with this widget
SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) {
// Get the string parameter
var param = this.actionParam;
// Assemble the parameters as a hashmap
// Assemble the attributes as a hashmap
var paramObject = Object.create(null);
// Add names/values pairs if present
if(this.actionNames && this.actionValues) {
var names = this.wiki.filterTiddlers(this.actionNames,this),
values = this.wiki.filterTiddlers(this.actionValues,this);
$tw.utils.each(names,function(name,index) {
paramObject[name] = values[index] || "";
});
}
// Add raw parameters
var count = 0;
$tw.utils.each(this.attributes,function(attribute,name) {
if(name.charAt(0) !== "$") {
paramObject[name] = attribute;
count++;
}
});
// Add name/value pair if present
@@ -82,15 +73,14 @@ SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) {
paramObject[this.actionName] = this.actionValue;
}
// Dispatch the message
var params = {
this.dispatchEvent({
type: this.actionMessage,
param: param,
paramObject: paramObject,
event: event,
tiddlerTitle: this.getVariable("currentTiddler"),
navigateFromTitle: this.getVariable("storyTiddler")
};
this.dispatchEvent(params);
navigateFromTitle: this.getVariable("storyTiddler"),
event: event
});
return true; // Action was invoked
};

View File

@@ -1,86 +0,0 @@
/*\
title: $:/core/modules/widgets/action-setmultiplefields.js
type: application/javascript
module-type: widget
Action widget to set multiple fields or indexes on a tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var SetMultipleFieldsWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
SetMultipleFieldsWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
SetMultipleFieldsWidget.prototype.render = function(parent,nextSibling) {
this.computeAttributes();
this.execute();
};
/*
Compute the internal state of the widget
*/
SetMultipleFieldsWidget.prototype.execute = function() {
this.actionTiddler = this.getAttribute("$tiddler",this.getVariable("currentTiddler"));
this.actionFields = this.getAttribute("$fields");
this.actionIndexes = this.getAttribute("$indexes");
this.actionValues = this.getAttribute("$values");
this.actionTimestamp = this.getAttribute("$timestamp","yes") === "yes";
};
/*
Refresh the widget by ensuring our attributes are up to date
*/
SetMultipleFieldsWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes["$tiddler"] || changedAttributes["$fields"] || changedAttributes["$indexes"] || changedAttributes["$values"] || changedAttributes["$timestamp"]) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
/*
Invoke the action associated with this widget
*/
SetMultipleFieldsWidget.prototype.invokeAction = function(triggeringWidget,event) {
var tiddler = this.wiki.getTiddler(this.actionTiddler),
names, values = this.wiki.filterTiddlers(this.actionValues,this);
if(this.actionFields) {
var additions = {};
names = this.wiki.filterTiddlers(this.actionFields,this);
$tw.utils.each(names,function(fieldname,index) {
additions[fieldname] = values[index] || "";
});
var creationFields = this.actionTimestamp ? this.wiki.getCreationFields() : undefined,
modificationFields = this.actionTimestamp ? this.wiki.getModificationFields() : undefined;
this.wiki.addTiddler(new $tw.Tiddler(creationFields,tiddler,{title: this.actionTiddler},modificationFields,additions));
} else if(this.actionIndexes) {
var data = this.wiki.getTiddlerData(this.actionTiddler,Object.create(null));
names = this.wiki.filterTiddlers(this.actionIndexes,this);
$tw.utils.each(names,function(name,index) {
data[name] = values[index] || "";
});
this.wiki.setTiddlerData(this.actionTiddler,data,{},{suppressTimestamp: !this.actionTimestamp});
}
return true; // Action was invoked
};
exports["action-setmultiplefields"] = SetMultipleFieldsWidget;
})();

View File

@@ -52,13 +52,7 @@ CodeBlockWidget.prototype.execute = function() {
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
CodeBlockWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.code || changedAttributes.language) {
this.refreshSelf();
return true;
} else {
return false;
}
return false;
};
exports.codeblock = CodeBlockWidget;

View File

@@ -54,7 +54,6 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
dragFilterFn: function() {return self.getAttribute("filter");},
startActions: self.startActions,
endActions: self.endActions,
dragImageType: self.dragImageType,
widget: this
});
// Insert the link into the DOM and render any children
@@ -72,7 +71,6 @@ DraggableWidget.prototype.execute = function() {
this.draggableClasses = this.getAttribute("class");
this.startActions = this.getAttribute("startactions");
this.endActions = this.getAttribute("endactions");
this.dragImageType = this.getAttribute("dragimagetype");
// Make the child widgets
this.makeChildWidgets();
};

View File

@@ -198,8 +198,7 @@ DropZoneWidget.prototype.handleDropEvent = function(event) {
this.resetState();
// Import any files in the drop
var numFiles = 0;
// If we have type text/vnd.tiddlywiki then skip trying to import files
if(dataTransfer.files && !$tw.utils.dragEventContainsType(event,"text/vnd.tiddler")) {
if(dataTransfer.files) {
numFiles = this.wiki.readFiles(dataTransfer.files,{
callback: readFileCallback,
deserializer: this.dropzoneDeserializer

View File

@@ -76,22 +76,16 @@ EventWidget.prototype.render = function(parent,nextSibling) {
variables["tv-selectednode-posy"] = selectedNode.offsetTop.toString();
variables["tv-selectednode-width"] = selectedNode.offsetWidth.toString();
variables["tv-selectednode-height"] = selectedNode.offsetHeight.toString();
if(event.clientX && event.clientY) {
//Add variables for event X and Y position relative to selected node
selectedNodeRect = selectedNode.getBoundingClientRect();
variables["event-fromselected-posx"] = (event.clientX - selectedNodeRect.left).toString();
variables["event-fromselected-posy"] = (event.clientY - selectedNodeRect.top).toString();
//Add variables for event X and Y position relative to event catcher node
catcherNodeRect = self.domNode.getBoundingClientRect();
variables["event-fromcatcher-posx"] = (event.clientX - catcherNodeRect.left).toString();
variables["event-fromcatcher-posy"] = (event.clientY - catcherNodeRect.top).toString();
//Add variables for event X and Y position relative to selected node
selectedNodeRect = selectedNode.getBoundingClientRect();
variables["event-fromselected-posx"] = (event.clientX - selectedNodeRect.left).toString();
variables["event-fromselected-posy"] = (event.clientY - selectedNodeRect.top).toString();
//Add variables for event X and Y position relative to the viewport
variables["event-fromviewport-posx"] = event.clientX.toString();
variables["event-fromviewport-posy"] = event.clientY.toString();
}
//Add variables for event X and Y position relative to event catcher node
catcherNodeRect = self.domNode.getBoundingClientRect();
variables["event-fromcatcher-posx"] = (event.clientX - catcherNodeRect.left).toString();
variables["event-fromcatcher-posy"] = (event.clientY - catcherNodeRect.top).toString();
}
} else {
return false;

View File

@@ -111,16 +111,6 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
if(this.imageAlt) {
domNode.setAttribute("alt",this.imageAlt);
}
// Add classes when the image loads or fails
$tw.utils.addClass(domNode,"tc-image-loading");
domNode.addEventListener("load",function() {
$tw.utils.removeClass(domNode,"tc-image-loading");
$tw.utils.addClass(domNode,"tc-image-loaded");
},false);
domNode.addEventListener("error",function() {
$tw.utils.removeClass(domNode,"tc-image-loading");
$tw.utils.addClass(domNode,"tc-image-error");
},false);
// Insert element
parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);

View File

@@ -40,10 +40,8 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
var widgetPointer = this;
// Got to flush all the accumulated variables
this.variables = new this.variablesConstructor();
// Get our parameters
this.filter = this.getAttribute("filter");
// Compute the filter
this.tiddlerList = tiddlerList || this.wiki.filterTiddlers(this.filter,this);
this.tiddlerList = tiddlerList || this.getTiddlerList();
// Accumulate the <$set> widgets from each tiddler
$tw.utils.each(this.tiddlerList,function(title) {
var parser = widgetPointer.wiki.parseTiddler(title);
@@ -86,7 +84,6 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
}
}
});
if (widgetPointer != this) {
widgetPointer.parseTreeNode.children = this.parseTreeNode.children;
} else {
@@ -94,13 +91,21 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
}
};
ImportVariablesWidget.prototype.getTiddlerList = function() {
var filter = this.getAttribute("filter"),
title = this.getAttribute("tiddler");
return (filter && this.wiki.filterTiddlers(filter,this)) || (title && [title]) || [];
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ImportVariablesWidget.prototype.refresh = function(changedTiddlers) {
// Recompute our attributes and the filter list
var changedAttributes = this.computeAttributes(),
tiddlerList = this.wiki.filterTiddlers(this.getAttribute("filter"),this);
filter = this.getAttribute("filter"),
title = this.getAttribute("tiddler"),
tiddlerList = this.getTiddlerList();
// Refresh if the filter has changed, or the list of tiddlers has changed, or any of the tiddlers in the list has changed
function haveListedTiddlersChanged() {
var changed = false;
@@ -111,7 +116,7 @@ ImportVariablesWidget.prototype.refresh = function(changedTiddlers) {
});
return changed;
}
if(changedAttributes.filter || !$tw.utils.isArrayEqual(this.tiddlerList,tiddlerList) || haveListedTiddlersChanged()) {
if(changedAttributes.filter || changedAttributes.tiddler || !$tw.utils.isArrayEqual(this.tiddlerList,tiddlerList) || haveListedTiddlersChanged()) {
// Compute the filter
this.removeChildDomNodes();
this.execute(tiddlerList);

View File

@@ -32,7 +32,13 @@ JSONTiddlerWidget.prototype.render = function(parent,nextSibling) {
this.computeAttributes();
this.execute();
// Collect the fields from the optional base tiddler
var fields = this.getTiddlerFields();
var fields = {};
if(this.attTiddler) {
var tiddler = this.wiki.getTiddler(this.attTiddler);
if(tiddler) {
fields = tiddler.getFieldStrings({exclude: this.attExclude.split(" ")});
}
}
// Add custom fields specified in attributes starting with $
$tw.utils.each(this.attributes,function(attribute,name) {
if(name.charAt(0) === "$") {
@@ -73,19 +79,6 @@ JSONTiddlerWidget.prototype.refresh = function(changedTiddlers) {
}
};
JSONTiddlerWidget.prototype.getTiddlerFields = function() {
var fields = {};
if(this.attTiddler) {
var tiddler = this.wiki.getTiddler(this.attTiddler);
if(tiddler) {
fields = tiddler.getFieldStrings({exclude: this.attExclude.split(" ")});
} else {
fields = {title: this.attTiddler};
}
}
return fields;
};
exports.jsontiddler = JSONTiddlerWidget;
})();

View File

@@ -1,96 +0,0 @@
/*\
title: $:/core/modules/widgets/let.js
type: application/javascript
module-type: widget
This widget allows defining multiple variables at once, while allowing
the later variables to depend upon the earlier ones.
```
\define helloworld() Hello world!
<$let currentTiddler="target" value={{!!value}} currentTiddler="different">
{{!!value}} will be different from <<value>>
</$let>
```
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var LetWidget = function(parseTreeNode,options) {
// Initialise
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
LetWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
LetWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
LetWidget.prototype.computeAttributes = function() {
// Before computing attributes, we must make clear that none of the
// existing attributes are staged for lookup, even on a refresh
var changedAttributes = {},
self = this;
this.currentValueFor = Object.create(null);
$tw.utils.each(this.parseTreeNode.orderedAttributes,function(attribute,index) {
var value = self.computeAttribute(attribute),
name = attribute.name;
if(name.charAt(0) !== "$") {
// Now that it's prepped, we're allowed to look this variable up
// when defining later variables
self.currentValueFor[name] = value;
}
});
// Run through again, setting variables and looking for differences
$tw.utils.each(this.currentValueFor,function(value,name) {
if (self.attributes[name] !== value) {
self.attributes[name] = value;
self.setVariable(name,value);
changedAttributes[name] = true;
}
});
return changedAttributes;
};
LetWidget.prototype.getVariableInfo = function(name,options) {
// Special handling: If this variable exists in this very $let, we can
// use it, but only if it's been staged.
if ($tw.utils.hop(this.currentValueFor,name)) {
return {
text: this.currentValueFor[name]
};
}
return Widget.prototype.getVariableInfo.call(this,name,options);
};
/*
Refresh the widget by ensuring our attributes are up to date
*/
LetWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if($tw.utils.count(changedAttributes) > 0) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports["let"] = LetWidget;
})();

View File

@@ -98,6 +98,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
var wikiLinkTemplateMacro = this.getVariable("tv-wikilink-template"),
wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.trim() : "#$uri_encoded$";
wikiLinkText = $tw.utils.replaceString(wikiLinkTemplate,"$uri_encoded$",encodeURIComponent(this.to));
wikiLinkText = $tw.utils.replaceString(wikiLinkText,"$title_slugify$",this.wiki.slugify(this.to));
wikiLinkText = $tw.utils.replaceString(wikiLinkText,"$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to)));
}
// Override with the value of tv-get-export-link if defined
@@ -207,8 +208,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
LinkWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip ||
changedAttributes["class"] || changedAttributes.tabindex || changedAttributes.draggable || changedAttributes.tag) {
if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip) {
this.refreshSelf();
return true;
}

View File

@@ -236,11 +236,6 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
hasRefreshed = hasRefreshed || refreshed;
}
}
// If there are items to remove and we have not refreshed then recreate the item that will now be at the last position
if(!hasRefreshed && this.children.length > this.list.length) {
this.removeListItem(this.list.length-1);
this.insertListItem(this.list.length-1,this.list[this.list.length-1]);
}
} else {
// Cycle through the list, inserting and removing list items as needed
for(t=0; t<this.list.length; t++) {

View File

@@ -36,38 +36,28 @@ MessageCatcherWidget.prototype.render = function(parent,nextSibling) {
// Helper to add an event handler
var addEventHandler = function(type,actions) {
if(type && actions) {
var isActionStringExecuting = false;
self.addEventListener(
type,
function(event) {
// Don't trap the event if it came from one of our action handlers
if(isActionStringExecuting) {
return true;
}
// Collect all the event properties into variables
var collectProps = function(obj,prefix) {
prefix = prefix || "";
var props = {},
names = [];
var props = {};
$tw.utils.each(obj,function(value,name) {
if(["string","boolean","number"].indexOf(typeof value) !== -1) {
names.push(name);
props[prefix + "-" + name] = value.toString();
props[prefix + name] = value.toString();
}
});
props["list-" + prefix] = $tw.utils.stringifyList(names);
return props;
};
var variables = $tw.utils.extend(
{},
collectProps(event.paramObject,"event-paramObject"),
collectProps(event,"event"),
collectProps(event.paramObject,"event-paramObject-"),
collectProps(event,"event-"),
{
modifier: $tw.keyboardManager.getEventModifierKeyDescriptor(event)
});
isActionStringExecuting = true;
self.invokeActionString(actions,self,event,variables);
isActionStringExecuting = false;
return false;
}
);

View File

@@ -499,7 +499,11 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
// Import JSON tiddlers into a pending import tiddler
NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
// Get the tiddlers
var tiddlers = $tw.utils.parseJSONSafe(event.param,[]);
var tiddlers = [];
try {
tiddlers = JSON.parse(event.param);
} catch(e) {
}
// Get the current $:/Import tiddler
var importTitle = event.importTitle ? event.importTitle : IMPORT_TITLE,
importTiddler = this.wiki.getTiddler(importTitle),

View File

@@ -64,9 +64,9 @@ RadioWidget.prototype.getValue = function() {
tiddler = this.wiki.getTiddler(this.radioTitle);
if(tiddler) {
if(this.radioIndex) {
value = this.wiki.extractTiddlerDataItem(this.radioTitle,this.radioIndex,this.radioDefault);
value = this.wiki.extractTiddlerDataItem(this.radioTitle,this.radioIndex);
} else {
value = tiddler.getFieldString(this.radioField,this.radioDefault);
value = tiddler.getFieldString(this.radioField);
}
} else {
value = this.radioDefault;
@@ -122,7 +122,6 @@ RadioWidget.prototype.refresh = function(changedTiddlers) {
return true;
} else if(changedTiddlers[this.radioTitle]) {
this.inputDomNode.checked = this.getValue() === this.radioValue;
$tw.utils.toggleClass(this.labelDomNode,"tc-radio-selected",this.inputDomNode.checked);
return this.refreshChildren(changedTiddlers);
} else {
return this.refreshChildren(changedTiddlers);

View File

@@ -226,7 +226,7 @@ RevealWidget.prototype.refresh = function(changedTiddlers) {
this.refreshSelf();
return true;
}
} else if(this.type === "popup" && this.isOpen && this.updatePopupPosition && (changedTiddlers[this.state] || changedTiddlers[this.stateTitle])) {
} else if(this.type === "popup" && this.updatePopupPosition && (changedTiddlers[this.state] || changedTiddlers[this.stateTitle])) {
this.positionPopup(this.domNode);
}
if(changedAttributes.style) {

View File

@@ -1,81 +0,0 @@
/*\
title: $:/core/modules/widgets/setmultiplevariables.js
type: application/javascript
module-type: widget
Widget to set multiple variables at once from a list of names and a list of values
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var SetMultipleVariablesWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
SetMultipleVariablesWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
SetMultipleVariablesWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
SetMultipleVariablesWidget.prototype.execute = function() {
// Setup our variables
this.setVariables();
// Construct the child widgets
this.makeChildWidgets();
};
SetMultipleVariablesWidget.prototype.setVariables = function() {
// Set the variables
var self = this,
filterNames = this.getAttribute("$names",""),
filterValues = this.getAttribute("$values","");
this.variableNames = [];
this.variableValues = [];
if(filterNames && filterValues) {
this.variableNames = this.wiki.filterTiddlers(filterNames,this);
this.variableValues = this.wiki.filterTiddlers(filterValues,this);
$tw.utils.each(this.variableNames,function(varname,index) {
self.setVariable(varname,self.variableValues[index]);
});
}
};
/*
Refresh the widget by ensuring our attributes are up to date
*/
SetMultipleVariablesWidget.prototype.refresh = function(changedTiddlers) {
var filterNames = this.getAttribute("$names",""),
filterValues = this.getAttribute("$values",""),
variableNames = this.wiki.filterTiddlers(filterNames,this),
variableValues = this.wiki.filterTiddlers(filterValues,this);
if(!$tw.utils.isArrayEqual(this.variableNames,variableNames) || !$tw.utils.isArrayEqual(this.variableValues,variableValues)) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports["setmultiplevariables"] = SetMultipleVariablesWidget;
})();

View File

@@ -29,12 +29,14 @@ var VarsWidget = function(parseTreeNode,options) {
/*
Inherit from the base widget class
*/
VarsWidget.prototype = new Widget();
VarsWidget.prototype = Object.create(Widget.prototype);
/*
Render this widget into the DOM
*/
VarsWidget.prototype.render = function(parent,nextSibling) {
// Call the constructor
Widget.call(this);
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
@@ -61,7 +63,7 @@ Refresh the widget by ensuring our attributes are up to date
*/
VarsWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if($tw.utils.count(changedAttributes) > 0) {
if(Object.keys(changedAttributes).length) {
this.refreshSelf();
return true;
}

View File

@@ -122,7 +122,7 @@ Widget.prototype.getVariableInfo = function(name,options) {
});
// Only substitute variable references if this variable was defined with the \define pragma
if(variable.isMacroDefinition) {
value = this.substituteVariableReferences(value,options);
value = this.substituteVariableReferences(value);
}
return {
text: value,
@@ -175,10 +175,10 @@ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams)
return results;
};
Widget.prototype.substituteVariableReferences = function(text,options) {
Widget.prototype.substituteVariableReferences = function(text) {
var self = this;
return (text || "").replace(/\$\(([^\)\$]+)\)\$/g,function(match,p1,offset,string) {
return options.variables && options.variables[p1] || (self.getVariable(p1,{defaultValue: ""}));
return self.getVariable(p1,{defaultValue: ""});
});
};
@@ -263,9 +263,19 @@ Compute the current values of the attributes of the widget. Returns a hashmap of
*/
Widget.prototype.computeAttributes = function() {
var changedAttributes = {},
self = this;
self = this,
value;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
var value = self.computeAttribute(attribute);
if(attribute.type === "filtered") {
value = self.wiki.filterTiddlers(attribute.filter,self)[0] || "";
} else if(attribute.type === "indirect") {
value = self.wiki.getTextReference(attribute.textReference,"",self.getVariable("currentTiddler"));
} else if(attribute.type === "macro") {
value = self.getVariable(attribute.value.name,{params: attribute.value.params});
} else { // String attribute
value = attribute.value;
}
// Check whether the attribute has changed
if(self.attributes[name] !== value) {
self.attributes[name] = value;
changedAttributes[name] = true;
@@ -274,20 +284,6 @@ Widget.prototype.computeAttributes = function() {
return changedAttributes;
};
Widget.prototype.computeAttribute = function(attribute) {
var value;
if(attribute.type === "filtered") {
value = this.wiki.filterTiddlers(attribute.filter,this)[0] || "";
} else if(attribute.type === "indirect") {
value = this.wiki.getTextReference(attribute.textReference,"",this.getVariable("currentTiddler"));
} else if(attribute.type === "macro") {
value = this.getVariable(attribute.value.name,{params: attribute.value.params});
} else { // String attribute
value = attribute.value;
}
return value;
};
/*
Check for the presence of an attribute
*/
@@ -574,7 +570,7 @@ Widget.prototype.invokeActions = function(triggeringWidget,event) {
for(var t=0; t<this.children.length; t++) {
var child = this.children[t],
childIsActionWidget = !!child.invokeAction,
actionRefreshPolicy = child.getVariable("tv-action-refresh-policy"); // Default is "once"
actionRefreshPolicy = child.getVariable("tv-action-refresh-policy");
// Refresh the child if required
if(childIsActionWidget || actionRefreshPolicy === "always") {
child.refreshSelf();

View File

@@ -46,31 +46,26 @@ function relinkTiddler(fromTitle,toTitle,options) {
if(!tiddler.fields["plugin-type"] && type !== "application/javascript") {
var tags = tiddler.fields.tags ? tiddler.fields.tags.slice(0) : undefined,
list = tiddler.fields.list ? tiddler.fields.list.slice(0) : undefined,
isModified = false,
processList = function(listField) {
if(listField && listField.indexOf(fromTitle) !== -1) {
// Remove any existing instances of the toTitle
var p = listField.indexOf(toTitle);
while(p !== -1) {
listField.splice(p,1);
p = listField.indexOf(toTitle);
}
// Replace the fromTitle with toTitle
$tw.utils.each(listField,function (title,index) {
if(title === fromTitle) {
listField[index] = toTitle;
isModified = true;
}
});
}
};
isModified = false;
if(!options.dontRenameInTags) {
// Rename tags
processList(tags);
$tw.utils.each(tags,function (title,index) {
if(title === fromTitle) {
console.log("Renaming tag '" + tags[index] + "' to '" + toTitle + "' of tiddler '" + tiddler.fields.title + "'");
tags[index] = toTitle;
isModified = true;
}
});
}
if(!options.dontRenameInLists) {
// Rename lists
processList(list);
$tw.utils.each(list,function (title,index) {
if(title === fromTitle) {
console.log("Renaming list item '" + list[index] + "' to '" + toTitle + "' of tiddler '" + tiddler.fields.title + "'");
list[index] = toTitle;
isModified = true;
}
});
}
if(isModified) {
var newTiddler = new $tw.Tiddler(tiddler,{tags: tags, list: list},self.getModificationFields())

View File

@@ -81,7 +81,7 @@ exports.setText = function(title,field,index,value,options) {
} else {
delete data[index];
}
this.setTiddlerData(title,data,{},{suppressTimestamp: options.suppressTimestamp});
this.setTiddlerData(title,data,modificationFields);
} else {
var tiddler = this.getTiddler(title),
fields = {title: title};
@@ -365,108 +365,47 @@ Sort an array of tiddler titles by a specified field
*/
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric) {
var self = this;
if(sortField === "title") {
if(!isNumeric && !isAlphaNumeric) {
if(isCaseSensitive) {
if(isDescending) {
titles.sort(function(a,b) {
return b.localeCompare(a);
});
} else {
titles.sort(function(a,b) {
return a.localeCompare(b);
});
}
titles.sort(function(a,b) {
var x,y,
compareNumbers = function(x,y) {
var result =
isNaN(x) && !isNaN(y) ? (isDescending ? -1 : 1) :
!isNaN(x) && isNaN(y) ? (isDescending ? 1 : -1) :
(isDescending ? y - x : x - y);
return result;
};
if(sortField !== "title") {
var tiddlerA = self.getTiddler(a),
tiddlerB = self.getTiddler(b);
if(tiddlerA) {
a = tiddlerA.getFieldString(sortField) || "";
} else {
if(isDescending) {
titles.sort(function(a,b) {
return b.toLowerCase().localeCompare(a.toLowerCase());
});
} else {
titles.sort(function(a,b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
}
a = "";
}
if(tiddlerB) {
b = tiddlerB.getFieldString(sortField) || "";
} else {
b = "";
}
} else {
titles.sort(function(a,b) {
var x,y;
if(isNumeric) {
x = Number(a);
y = Number(b);
if(isNaN(x)) {
if(isNaN(y)) {
// If neither value is a number then fall through to a textual comparison
} else {
return isDescending ? -1 : 1;
}
} else {
if(isNaN(y)) {
return isDescending ? 1 : -1;
} else {
return isDescending ? y - x : x - y;
}
}
}
if(isAlphaNumeric) {
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
}
if(!isCaseSensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
});
}
} else {
titles.sort(function(a,b) {
var x,y;
if(sortField !== "title") {
var tiddlerA = self.getTiddler(a),
tiddlerB = self.getTiddler(b);
if(tiddlerA) {
a = tiddlerA.fields[sortField] || "";
} else {
a = "";
}
if(tiddlerB) {
b = tiddlerB.fields[sortField] || "";
} else {
b = "";
}
}
if(isNumeric) {
x = Number(a);
y = Number(b);
if(isNaN(x)) {
if(isNaN(y)) {
// If neither value is a number then fall through to a textual comparison
} else {
return isDescending ? -1 : 1;
}
} else {
if(isNaN(y)) {
return isDescending ? 1 : -1;
} else {
return isDescending ? y - x : x - y;
}
}
}
if(Object.prototype.toString.call(a) === "[object Date]" && Object.prototype.toString.call(b) === "[object Date]") {
return isDescending ? b - a : a - b;
}
x = Number(a);
y = Number(b);
if(isNumeric && (!isNaN(x) || !isNaN(y))) {
return compareNumbers(x,y);
} else if($tw.utils.isDate(a) && $tw.utils.isDate(b)) {
return isDescending ? b - a : a - b;
} else if(isAlphaNumeric) {
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
} else {
a = String(a);
b = String(b);
if(isAlphaNumeric) {
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
}
if(!isCaseSensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
});
}
}
});
};
/*
@@ -822,7 +761,12 @@ exports.getTiddlerData = function(titleOrTiddler,defaultData) {
switch(tiddler.fields.type) {
case "application/json":
// JSON tiddler
return $tw.utils.parseJSONSafe(tiddler.fields.text,defaultData);
try {
data = JSON.parse(tiddler.fields.text);
} catch(ex) {
return defaultData;
}
return data;
case "application/x-tiddler-dictionary":
return $tw.utils.parseFields(tiddler.fields.text);
}
@@ -851,24 +795,19 @@ Set a tiddlers content to a JavaScript object. Currently this is done by setting
title: title of tiddler
data: object that can be serialised to JSON
fields: optional hashmap of additional tiddler fields to be set
options: optional hashmap of options including:
suppressTimestamp: if true, don't set the creation/modification timestamps
*/
exports.setTiddlerData = function(title,data,fields,options) {
options = options || {};
exports.setTiddlerData = function(title,data,fields) {
var existingTiddler = this.getTiddler(title),
creationFields = options.suppressTimestamp ? {} : this.getCreationFields(),
modificationFields = options.suppressTimestamp ? {} : this.getModificationFields(),
newFields = {
title: title
};
};
if(existingTiddler && existingTiddler.fields.type === "application/x-tiddler-dictionary") {
newFields.text = $tw.utils.makeTiddlerDictionary(data);
} else {
newFields.type = "application/json";
newFields.text = JSON.stringify(data,null,$tw.config.preferences.jsonSpaces);
}
this.addTiddler(new $tw.Tiddler(creationFields,existingTiddler,fields,newFields,modificationFields));
this.addTiddler(new $tw.Tiddler(this.getCreationFields(),existingTiddler,fields,newFields,this.getModificationFields()));
};
/*
@@ -905,7 +844,7 @@ exports.clearGlobalCache = function() {
exports.getCacheForTiddler = function(title,cacheName,initializer) {
this.caches = this.caches || Object.create(null);
var caches = this.caches[title];
if(caches && caches[cacheName] !== undefined) {
if(caches && caches[cacheName]) {
return caches[cacheName];
} else {
if(!caches) {
@@ -1636,4 +1575,23 @@ exports.slugify = function(title,options) {
return slug;
};
/*
Return an array of the titles that would generate a specified slug, if any. Options include:
*/
exports.unslugify = function(slug) {
var self = this,
slugToTitle = this.getGlobalCache("slugs",function() {
var map = {};
$tw.utils.each($tw.wiki.allTitles(),function(title) {
var slug = self.slugify(title);
if(!(slug in map)) {
map[slug] = [];
}
map[slug].push(title);
});
return map;
});
return slugToTitle[slug];
};
})();

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