1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-22 10:54:46 +00:00

Compare commits

...

26 Commits

Author SHA1 Message Date
Jeremy Ruston
c05400021c Merge branch 'master' into intrinsic-sub-plugins 2025-03-21 17:24:56 +00:00
Jeremy Ruston
5111be1953 Merge branch 'master' into intrinsic-sub-plugins 2025-02-27 17:36:41 +00:00
Jeremy Ruston
9dadd3fafb Display subplugins in control panel listing 2025-02-27 16:54:32 +00:00
Jeremy Ruston
12ced06d1d Add subplugins for codemirror languages
The French translation is just for demo purposes. It would be helpful to replace it with some real translations.
2025-02-27 12:49:24 +00:00
Jeremy Ruston
a4e8308e75 Add comment 2025-02-27 12:48:07 +00:00
Jeremy Ruston
9a2534fc5e Fix accidentally disabled test 2025-02-27 11:17:35 +00:00
Jeremy Ruston
2ae1e5aa09 Merge branch 'tiddlywiki-com' 2025-02-27 11:01:28 +00:00
Leilei332
378b9b3627 Allow building external core version of main site (#8953) 2025-02-27 11:00:58 +00:00
Jeremy Ruston
e01c428a17 Add support for add-on language and theme plugins 2025-02-26 21:17:53 +00:00
Jeremy Ruston
752c5cfbda Cleaning up 2025-02-26 20:01:32 +00:00
Jeremy Ruston
094428b1ab Remove debug code 2025-02-26 18:07:39 +00:00
Jeremy Ruston
41cd335cf4 Add test for dynamically loading a language plugin 2025-02-26 17:20:03 +00:00
Jeremy Ruston
26b2768294 Fix testcase export to include plugins 2025-02-26 17:18:37 +00:00
Jeremy Ruston
4e3d07b16a Code typo 2025-02-22 10:19:57 +00:00
Jeremy Ruston
01beac1d7c Test for loading language plugins at startup 2025-02-21 16:47:00 +00:00
Jeremy Ruston
0c80e1b35c Move subplugin tests into their own folder 2025-02-21 10:59:38 +00:00
Jeremy Ruston
55c634692b Deal with dynamic loading and unloading of plugins
More enhancements needed for the test framework too in order to allow events to be processed manually, without relying on the nexttick handler which is awkward in Jasmine
2025-02-21 09:36:40 +00:00
Jeremy Ruston
7e2a8238b5 Clarify comment 2025-02-18 21:17:34 +00:00
Jeremy Ruston
ee90ad2fa6 Merge branch 'master' into intrinsic-sub-plugins 2025-02-18 21:15:39 +00:00
Jeremy Ruston
afabe5b714 More efficient checks if a tiddler is a plugin 2025-02-18 17:30:11 +00:00
Jeremy Ruston
3a60e0eb17 Merge branch 'master' into intrinsic-sub-plugins 2025-02-18 16:54:47 +00:00
Jeremy Ruston
6b0a13b8cc Basic loading of bundled subplugins
Very basic. Doesn't cope with any changes to the plugins after startup, and doesn't remove sub-plugins when the parent plugin is removed.
2025-02-18 09:14:54 +00:00
Jeremy Ruston
c142bfc50d Use "bundled" rather than "intrinsic" 2025-02-18 09:14:05 +00:00
Jeremy Ruston
e8de413fba More refactoring for clarity 2025-02-17 16:31:42 +00:00
Jeremy Ruston
4cbf1b4d91 Refactor some symbols for clarity
All these symbols are inaccessible outside the wiki class closure, and so no backwards compatibility issues
2025-02-16 19:32:56 +00:00
Jeremy Ruston
778e544f1c Initial Commit 2025-02-16 18:20:00 +00:00
38 changed files with 853 additions and 227 deletions

View File

@@ -105,6 +105,7 @@ node $TW5_BUILD_TIDDLYWIKI \
fi
# /index.html Main site
# /external-(version).html External core version of main site
# /favicon.ico Favicon for main site
# /static.html Static rendering of default tiddlers
# /alltiddlers.html Static rendering of all tiddlers
@@ -117,7 +118,7 @@ node $TW5_BUILD_TIDDLYWIKI \
--version \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_OUTPUT \
--build favicon static index \
--build favicon static index external-js \
|| exit 1
# /empty.html Empty

View File

@@ -1068,6 +1068,10 @@ $tw.Tiddler.prototype.hasField = function(field) {
return $tw.utils.hop(this.fields,field);
};
$tw.Tiddler.prototype.isPlugin = function() {
return this.fields.type === "application/json" && this.hasField("plugin-type") && !this.hasField("draft.of");
};
/*
Compare two tiddlers for equality
tiddler: the tiddler to compare
@@ -1162,21 +1166,21 @@ enableIndexers - Array of indexer names to enable, or null to use all available
$tw.Wiki = function(options) {
options = options || {};
var self = this,
tiddlers = Object.create(null), // Hashmap of tiddlers
tiddlerTitles = null, // Array of tiddler titles
tiddlerStore = Object.create(null), // Hashmap of tiddlers
tiddlerTitles = null, // Array of tiddler titles, calculated and cached when needed
getTiddlerTitles = function() {
if(!tiddlerTitles) {
tiddlerTitles = Object.keys(tiddlers).sort(function(a,b) {return a.localeCompare(b);});
tiddlerTitles = Object.keys(tiddlerStore).sort(function(a,b) {return a.localeCompare(b);});
}
return tiddlerTitles;
},
pluginTiddlers = [], // Array of tiddlers containing registered plugins, ordered by priority
pluginInfo = Object.create(null), // Hashmap of parsed plugin content
shadowTiddlers = Object.create(null), // Hashmap by title of {source:, tiddler:}
shadowTiddlerTitles = null,
pluginTiddlersInfo = [], // Array of tiddler fields for all plugins and sub-plugins, ordered by priority
pluginContents = Object.create(null), // Hashmap by title of {contents:<parsed plugin contents>,fields:<fields>,subPluginTitles:<list>}
shadowTiddlerInfo = Object.create(null), // Hashmap by title of {source:, tiddler:}
shadowTiddlerTitles = null, // Array of tiddler titles, calculated and cached when needed
getShadowTiddlerTitles = function() {
if(!shadowTiddlerTitles) {
shadowTiddlerTitles = Object.keys(shadowTiddlers).sort(function(a,b) {return a.localeCompare(b);});
shadowTiddlerTitles = Object.keys(shadowTiddlerInfo).sort(function(a,b) {return a.localeCompare(b);});
}
return shadowTiddlerTitles;
},
@@ -1218,7 +1222,7 @@ $tw.Wiki = function(options) {
}
}
// Save the new tiddler
tiddlers[title] = tiddler;
tiddlerStore[title] = tiddler;
// Check we've got the title
tiddlerTitles = $tw.utils.insertSortedArray(tiddlerTitles || [],title);
// Record the new tiddler state
@@ -1243,7 +1247,7 @@ $tw.Wiki = function(options) {
this.deleteTiddler = function(title) {
// Uncomment the following line for detailed logs of all tiddler deletions
// console.log("Deleting",title)
if($tw.utils.hop(tiddlers,title)) {
if($tw.utils.hop(tiddlerStore,title)) {
// Record the old tiddler state
var updateDescriptor = {
old: {
@@ -1253,7 +1257,7 @@ $tw.Wiki = function(options) {
}
}
// Delete the tiddler
delete tiddlers[title];
delete tiddlerStore[title];
// Delete it from the list of titles
if(tiddlerTitles) {
var index = tiddlerTitles.indexOf(title);
@@ -1281,11 +1285,11 @@ $tw.Wiki = function(options) {
// Get a tiddler from the store
this.getTiddler = function(title) {
if(title) {
var t = tiddlers[title];
var t = tiddlerStore[title];
if(t !== undefined) {
return t;
} else {
var s = shadowTiddlers[title];
var s = shadowTiddlerInfo[title];
if(s !== undefined) {
return s.tiddler;
}
@@ -1305,7 +1309,7 @@ $tw.Wiki = function(options) {
index,titlesLength,title;
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
callback(tiddlers[title],title);
callback(tiddlerStore[title],title);
}
};
@@ -1320,10 +1324,10 @@ $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);
if(tiddlerStore[title]) {
callback(tiddlerStore[title],title);
} else {
var shadowInfo = shadowTiddlers[title];
var shadowInfo = shadowTiddlerInfo[title];
callback(shadowInfo.tiddler,title);
}
}
@@ -1335,13 +1339,13 @@ $tw.Wiki = function(options) {
titles = getTiddlerTitles();
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
callback(tiddlers[title],title);
callback(tiddlerStore[title],title);
}
titles = getShadowTiddlerTitles();
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
if(!tiddlers[title]) {
var shadowInfo = shadowTiddlers[title];
if(!tiddlerStore[title]) {
var shadowInfo = shadowTiddlerInfo[title];
callback(shadowInfo.tiddler,title);
}
}
@@ -1353,35 +1357,35 @@ $tw.Wiki = function(options) {
titles = getShadowTiddlerTitles();
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
if(tiddlers[title]) {
callback(tiddlers[title],title);
if(tiddlerStore[title]) {
callback(tiddlerStore[title],title);
} else {
var shadowInfo = shadowTiddlers[title];
var shadowInfo = shadowTiddlerInfo[title];
callback(shadowInfo.tiddler,title);
}
}
titles = getTiddlerTitles();
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
if(!shadowTiddlers[title]) {
callback(tiddlers[title],title);
if(!shadowTiddlerInfo[title]) {
callback(tiddlerStore[title],title);
}
}
};
// Test for the existence of a tiddler (excludes shadow tiddlers)
this.tiddlerExists = function(title) {
return !!$tw.utils.hop(tiddlers,title);
return !!$tw.utils.hop(tiddlerStore,title);
};
// Determines if a tiddler is a shadow tiddler, regardless of whether it has been overridden by a real tiddler
this.isShadowTiddler = function(title) {
return $tw.utils.hop(shadowTiddlers,title);
return $tw.utils.hop(shadowTiddlerInfo,title);
};
this.getShadowSource = function(title) {
if($tw.utils.hop(shadowTiddlers,title)) {
return shadowTiddlers[title].source;
if($tw.utils.hop(shadowTiddlerInfo,title)) {
return shadowTiddlerInfo[title].source;
}
return null;
};
@@ -1389,8 +1393,8 @@ $tw.Wiki = function(options) {
// Get an array of all the currently recognised plugin types
this.getPluginTypes = function() {
var types = [];
$tw.utils.each(pluginTiddlers,function(pluginTiddler) {
var pluginType = pluginTiddler.fields["plugin-type"];
$tw.utils.each(pluginTiddlersInfo,function(pluginTiddlerFields) {
var pluginType = pluginTiddlerFields["plugin-type"];
if(pluginType && types.indexOf(pluginType) === -1) {
types.push(pluginType);
}
@@ -1398,22 +1402,45 @@ $tw.Wiki = function(options) {
return types;
};
// Read plugin info for all plugins, or just an array of titles. Returns the number of plugins updated or deleted
// Read plugin contents for all plugins, or just an array of titles. Returns the list of updated plugin titles, the list of deleted plugin titles and
// a hashmap of the deleted plugin contents. Clears the pluginContents cache for any plugins that have been deleted
this.readPluginInfo = function(titles) {
var results = {
var self = this,
results = {
modifiedPlugins: [],
deletedPlugins: []
deletedPlugins: [],
deletedPluginContents: {}
};
$tw.utils.each(titles || getTiddlerTitles(),function(title) {
var tiddler = tiddlers[title];
var tiddler = self.getTiddler(title);
if(!tiddler) {
for(var t=0; t<pluginContents.length; t++) {
if(pluginContents[t].subPluginTitles.indexOf(title) !== -1) {
tiddler = new $tw.Tiddler(pluginContents[t].fields);
break;
}
}
}
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);
results.modifiedPlugins.push(tiddler.fields.title);
if(tiddler.isPlugin() && tiddler.fields.text) {
var contents = $tw.utils.parseJSONSafe(tiddler.fields.text).tiddlers;
var subPluginTitles = [];
// Read any sub-plugins
$tw.utils.each(contents,function(subPluginFields,subPluginTitle) {
if(subPluginFields["plugin-type"] && subPluginFields["type"] === "application/json") {
var contents = $tw.utils.parseJSONSafe(subPluginFields.text).tiddlers
pluginContents[subPluginTitle] = {contents: contents,fields: subPluginFields};
results.modifiedPlugins.push(subPluginTitle);
subPluginTitles.push(subPluginTitle)
}
});
pluginContents[title] = {contents: contents,fields: tiddler.fields,subPluginTitles: subPluginTitles};
results.modifiedPlugins.push(title);
}
} else {
if(pluginInfo[title]) {
delete pluginInfo[title];
if(pluginContents[title]) {
results.deletedPluginContents[title] = {tiddlers: pluginContents[title].contents};
delete pluginContents[title];
results.deletedPlugins.push(title);
}
}
@@ -1421,34 +1448,42 @@ $tw.Wiki = function(options) {
return results;
};
this.isSubPlugin = function(title) {
for(var t=0; t<pluginContents.length; t++) {
if(pluginContents[t].subPluginTitles.indexOf(title) !== -1) {
return true;
}
}
return false;
};
// Get plugin info for a plugin
this.getPluginInfo = function(title) {
return pluginInfo[title];
var pluginContent = pluginContents[title];
if(pluginContent) {
return {tiddlers: pluginContent.contents, subPluginTitles: pluginContent.subPluginTitles, fields: pluginContent.fields};
} else {
return null;
}
};
// Register the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting registration to an array of tiddler titles. Return the array of titles affected
this.registerPluginTiddlers = function(pluginType,titles) {
var self = this,
registeredTitles = [],
checkTiddler = function(tiddler,title) {
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType)) {
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title);
if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered
pluginTiddlers.push(tiddler);
registeredTitles.push(tiddler.fields.title);
registeredTitles = [];
$tw.utils.each(pluginContents,function(pluginContent,pluginTitle) {
var pluginFields = pluginContent.fields;
if(!titles || titles.indexOf(pluginTitle) !== -1) {
if(!pluginType || pluginFields["plugin-type"] === pluginType) {
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + pluginTitle);
if(pluginTitle === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[pluginTitle]); // Unregister the plugin if it's already registered
pluginTiddlersInfo.push(pluginFields);
registeredTitles.push(pluginTitle);
}
}
};
if(titles) {
$tw.utils.each(titles,function(title) {
checkTiddler(self.getTiddler(title),title);
});
} else {
this.each(function(tiddler,title) {
checkTiddler(tiddler,title);
});
}
}
});
return registeredTitles;
};
@@ -1457,11 +1492,11 @@ $tw.Wiki = function(options) {
var self = this,
unregisteredTitles = [];
// Remove any previous registered plugins of this type
for(var t=pluginTiddlers.length-1; t>=0; t--) {
var tiddler = pluginTiddlers[t];
if(tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType) && (!titles || titles.indexOf(tiddler.fields.title) !== -1)) {
unregisteredTitles.push(tiddler.fields.title);
pluginTiddlers.splice(t,1);
for(var t=pluginTiddlersInfo.length-1; t>=0; t--) {
var pluginFields = pluginTiddlersInfo[t];
if(pluginFields["plugin-type"] && (!pluginType || pluginFields["plugin-type"] === pluginType) && (!titles || titles.indexOf(pluginFields.title) !== -1)) {
unregisteredTitles.push(pluginFields.title);
pluginTiddlersInfo.splice(t,1);
}
}
return unregisteredTitles;
@@ -1471,36 +1506,33 @@ $tw.Wiki = function(options) {
this.unpackPluginTiddlers = function() {
var self = this;
// Sort the plugin titles by the `plugin-priority` field, if this field is missing, default to 1
pluginTiddlers.sort(function(a, b) {
var priorityA = "plugin-priority" in a.fields ? a.fields["plugin-priority"] : 1;
var priorityB = "plugin-priority" in b.fields ? b.fields["plugin-priority"] : 1;
pluginTiddlersInfo.sort(function(a, b) {
var priorityA = "plugin-priority" in a ? a["plugin-priority"] : 1,
priorityB = "plugin-priority" in b ? b["plugin-priority"] : 1;
if (priorityA !== priorityB) {
return priorityA - priorityB;
} else if (a.fields.title < b.fields.title) {
} else if (a.title < b.title) {
return -1;
} else if (a.fields.title === b.fields.title) {
} else if (a.title === b.title) {
return 0;
} else {
return +1;
}
});
// Now go through the plugins in ascending order and assign the shadows
shadowTiddlers = Object.create(null);
$tw.utils.each(pluginTiddlers,function(tiddler) {
shadowTiddlerInfo = Object.create(null);
$tw.utils.each(pluginTiddlersInfo,function(tiddlerFields) {
var contents = pluginContents[tiddlerFields.title].contents;
// Extract the constituent tiddlers
if($tw.utils.hop(pluginInfo,tiddler.fields.title)) {
$tw.utils.each(pluginInfo[tiddler.fields.title].tiddlers,function(constituentTiddler,constituentTitle) {
// Save the tiddler object
if(constituentTitle) {
shadowTiddlers[constituentTitle] = {
source: tiddler.fields.title,
tiddler: new $tw.Tiddler(constituentTiddler,{title: constituentTitle})
};
}
});
}
$tw.utils.each(contents,function(constituentTiddler,constituentTitle) {
// Save the tiddler object
shadowTiddlerInfo[constituentTitle] = {
source: tiddlerFields.title,
tiddler: new $tw.Tiddler(constituentTiddler,{title: constituentTitle})
};
});
});
shadowTiddlerTitles = null;
shadowTiddlerTitles = null; // Force regeneration of the shadow tiddler titles list
this.clearCache(null);
this.clearGlobalCache();
$tw.utils.each(indexers,function(indexer) {
@@ -1944,7 +1976,7 @@ $tw.boot.excludeRegExp = /^\.DS_Store$|^.*\.meta$|^\..*\.swp$|^\._.*$|^\.git$|^\
/*
Load all the tiddlers recursively from a directory, including honouring `tiddlywiki.files` files for drawing in external files. Returns an array of {filepath:,type:,tiddlers: [{..fields...}],hasMetaFile:}. Note that no file information is returned for externally loaded tiddlers, just the `tiddlers` property.
*/
$tw.loadTiddlersFromPath = function(filepath,excludeRegExp) {
$tw.loadTiddlersFromPath = function(filepath,excludeRegExp,excludePluginInfo) {
excludeRegExp = excludeRegExp || $tw.boot.excludeRegExp;
var tiddlers = [];
if(fs.existsSync(filepath)) {
@@ -1954,11 +1986,17 @@ $tw.loadTiddlersFromPath = function(filepath,excludeRegExp) {
// Look for a tiddlywiki.files file
if(files.indexOf("tiddlywiki.files") !== -1) {
Array.prototype.push.apply(tiddlers,$tw.loadTiddlersFromSpecification(filepath,excludeRegExp));
} else if(files.indexOf("plugin.info") !== -1 && !excludePluginInfo) {
// Load sub-plugin
var tiddler = $tw.loadPluginFolder(filepath);
if(tiddler) {
tiddlers.push({tiddlers: [tiddler]});
}
} else {
// If not, read all the files in the directory
$tw.utils.each(files,function(file) {
if(!excludeRegExp.test(file) && file !== "plugin.info") {
tiddlers.push.apply(tiddlers,$tw.loadTiddlersFromPath(filepath + path.sep + file,excludeRegExp));
tiddlers.push.apply(tiddlers,$tw.loadTiddlersFromPath(path.join(filepath,file),excludeRegExp));
}
});
}
@@ -2105,54 +2143,69 @@ Load the tiddlers from a plugin folder, and package them up into a proper JSON p
*/
$tw.loadPluginFolder = function(filepath,excludeRegExp) {
excludeRegExp = excludeRegExp || $tw.boot.excludeRegExp;
var infoPath = filepath + path.sep + "plugin.info";
if(fs.existsSync(filepath) && fs.statSync(filepath).isDirectory()) {
// Read the plugin information
if(!fs.existsSync(infoPath) || !fs.statSync(infoPath).isFile()) {
console.log("Warning: missing plugin.info file in " + filepath);
function readPluginFields(filepath) {
if(!(fs.existsSync(filepath) && fs.statSync(filepath).isDirectory())) {
return null;
}
var pluginInfo = $tw.utils.parseJSONSafe(fs.readFileSync(infoPath,"utf8"),function() {return null;});
if(!pluginInfo) {
console.log("warning: invalid JSON in plugin.info file at " + infoPath);
pluginInfo = {};
var infoPath = path.join(filepath,"plugin.info");
// Read the plugin information file
if(!fs.existsSync(infoPath) || !fs.statSync(infoPath).isFile()) {
return null;
}
// Read the plugin files
var pluginFiles = $tw.loadTiddlersFromPath(filepath,excludeRegExp);
// Save the plugin tiddlers into the plugin info
pluginInfo.tiddlers = pluginInfo.tiddlers || Object.create(null);
var pluginFields = $tw.utils.parseJSONSafe(fs.readFileSync(infoPath,"utf8"),{});
if(!(pluginFields.title && pluginFields.name)) {
return null;
}
// Give the plugin the same version number as the core if it doesn't have one
if(!("version" in pluginFields)) {
pluginFields.version = $tw.packageInfo.version;
}
// Use "plugin" as the plugin-type if we don't have one
if(!("plugin-type" in pluginFields)) {
pluginFields["plugin-type"] = "plugin";
}
// Set the dependents and type
pluginFields.dependents = pluginFields.dependents || [];
pluginFields.type = "application/json";
// Deserialise array fields (currently required for the dependents field)
for(var field in pluginFields) {
if($tw.utils.isArray(pluginFields[field])) {
pluginFields[field] = $tw.utils.stringifyList(pluginFields[field]);
}
}
return pluginFields;
}
function readPluginTiddlers(tiddlersPath) {
var pluginFiles = $tw.loadTiddlersFromPath(tiddlersPath,excludeRegExp,true),
pluginTiddlers = {};
// Save the plugin tiddlers into the plugin payload
for(var f=0; f<pluginFiles.length; f++) {
var tiddlers = pluginFiles[f].tiddlers;
if(!tiddlers) {
console.log(`Gosh ${JSON.stringify(pluginFiles[f])}`)
}
for(var t=0; t<tiddlers.length; t++) {
var tiddler= tiddlers[t];
if(tiddler.title) {
pluginInfo.tiddlers[tiddler.title] = tiddler;
pluginTiddlers[tiddler.title] = tiddler;
}
}
}
// Give the plugin the same version number as the core if it doesn't have one
if(!("version" in pluginInfo)) {
pluginInfo.version = $tw.packageInfo.version;
}
// Use "plugin" as the plugin-type if we don't have one
if(!("plugin-type" in pluginInfo)) {
pluginInfo["plugin-type"] = "plugin";
}
pluginInfo.dependents = pluginInfo.dependents || [];
pluginInfo.type = "application/json";
// Set plugin text
pluginInfo.text = JSON.stringify({tiddlers: pluginInfo.tiddlers});
delete pluginInfo.tiddlers;
// Deserialise array fields (currently required for the dependents field)
for(var field in pluginInfo) {
if($tw.utils.isArray(pluginInfo[field])) {
pluginInfo[field] = $tw.utils.stringifyList(pluginInfo[field]);
}
}
return pluginInfo;
} else {
return null;
return pluginTiddlers;
}
// Get the primary plugin fields
var pluginFields = readPluginFields(filepath);
if(!pluginFields) {
console.log("Warning: missing or invalid plugin.info file in " + filepath);
return null;
}
// The payload to be stored in the plugin text field in JSON
var pluginPayload = {tiddlers: {}};
// Get the constituent tiddlers of the plugin
pluginPayload.tiddlers = readPluginTiddlers(filepath);
// Set plugin text
pluginFields.text = JSON.stringify(pluginPayload);
return pluginFields;
};
/*

View File

@@ -3,5 +3,8 @@ name: en-GB
description: English (British)
author: JeremyRuston
core-version: >=5.0.0"
plugin-type: language
type: application/json
hidden: yes
Stub pseudo-plugin for the default language
{tidddlers:{}}

View File

@@ -0,0 +1,36 @@
/*\
title: $:/core/modules/filters/is/plugin.js
type: application/javascript
module-type: isfilteroperator
Filter function for [is[plugin]]
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.plugin = function(source,prefix,options) {
var results = [];
if(prefix === "!") {
source(function(tiddler,title) {
if(!tiddler.isPlugin()) {
results.push(title);
}
});
} else {
source(function(tiddler,title) {
if(tiddler.isPlugin()) {
results.push(title);
}
});
}
return results;
};
})();

View File

@@ -35,6 +35,7 @@ function PluginSwitcher(options) {
}
PluginSwitcher.prototype.switchPlugins = function() {
var self = this;
// Get the name of the current theme
var selectedPluginTitle = this.wiki.getTiddlerText(this.controllerTitle);
// If it doesn't exist, then fallback to one of the default themes
@@ -49,22 +50,51 @@ 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)),
dependents = $tw.utils.parseStringArray(tiddler.fields.dependents || "");
var dependents = $tw.utils.parseStringArray(tiddler.fields.dependents || "");
$tw.utils.each(dependents,function(title) {
accumulatePlugin(title);
});
}
};
accumulatePlugin(selectedPluginTitle);
var selectedPluginTiddler = this.wiki.getTiddler(selectedPluginTitle);
// Accumulate any other plugins of the same type with the same name
this.wiki.eachTiddlerPlusShadows(function(tiddler,title) {
if(tiddler.isPlugin() && tiddler.fields["plugin-type"] === self.pluginType && tiddler.fields.name === selectedPluginTiddler.fields.name) {
accumulatePlugin(title);
}
});
// Read the plugin info for the incoming plugins
var changes = $tw.wiki.readPluginInfo(plugins);
// Unregister any existing theme tiddlers
var unregisteredTiddlers = $tw.wiki.unregisterPluginTiddlers(this.pluginType);
// Register any new theme tiddlers
var registeredTiddlers = $tw.wiki.registerPluginTiddlers(this.pluginType,plugins);
// Unpack the current theme tiddlers
$tw.wiki.unpackPluginTiddlers();
var changedPluginInfo = this.wiki.readPluginInfo(plugins);
// Collect the shadow tiddlers of any deleted plugins
var changedShadowTiddlers = {};
$tw.utils.each(changedPluginInfo.deletedPlugins,function(pluginTitle) {
var contents = changedPluginInfo.deletedPluginContents[pluginTitle];
if(contents && contents.tiddlers) {
$tw.utils.each(Object.keys(contents.tiddlers),function(title) {
changedShadowTiddlers[title] = true;
});
}
});
// Collect the shadow tiddlers of any modified plugins
$tw.utils.each(changedPluginInfo.modifiedPlugins,function(pluginTitle) {
var pluginInfo = self.wiki.getPluginInfo(pluginTitle);
if(pluginInfo && pluginInfo.tiddlers) {
$tw.utils.each(Object.keys(pluginInfo.tiddlers),function(title) {
changedShadowTiddlers[title] = false;
});
}
});
// Unregister any existing theme/language tiddlers
var unregisteredTiddlers = this.wiki.unregisterPluginTiddlers(this.pluginType);
// Register any new theme/language tiddlers
var registeredTiddlers = this.wiki.registerPluginTiddlers(this.pluginType,plugins);
// Unpack the current theme/language tiddlers
this.wiki.unpackPluginTiddlers();
// Queue change events for the changed shadow tiddlers
$tw.utils.each(changedShadowTiddlers,function(status,title) {
self.wiki.enqueueTiddlerEvent(title,changedShadowTiddlers[title], true);
});
// Call the switch handler
if(this.onSwitch) {
this.onSwitch(plugins);

View File

@@ -15,67 +15,7 @@ exports.after = ["load-modules"];
exports.before = ["startup"];
exports.synchronous = true;
var TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE = "$:/status/RequireReloadDueToPluginChange";
var PREFIX_CONFIG_REGISTER_PLUGIN_TYPE = "$:/config/RegisterPluginType/";
exports.startup = function() {
$tw.wiki.addTiddler({title: TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE,text: "no"});
$tw.wiki.addEventListener("change",function(changes) {
// Work out which of the changed tiddlers are plugins that we need to reregister
var changesToProcess = [],
requireReloadDueToPluginChange = false;
$tw.utils.each(Object.keys(changes),function(title) {
var tiddler = $tw.wiki.getTiddler(title),
requiresReload = $tw.wiki.doesPluginRequireReload(title);
if(requiresReload) {
requireReloadDueToPluginChange = true;
} else if(tiddler) {
var pluginType = tiddler.fields["plugin-type"];
if($tw.wiki.getTiddlerText(PREFIX_CONFIG_REGISTER_PLUGIN_TYPE + (tiddler.fields["plugin-type"] || ""),"no") === "yes") {
changesToProcess.push(title);
}
}
});
// Issue warning if any of the tiddlers require a reload
if(requireReloadDueToPluginChange) {
$tw.wiki.addTiddler({title: TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE,text: "yes"});
}
// Read or delete the plugin info of the changed tiddlers
if(changesToProcess.length > 0) {
var changes = $tw.wiki.readPluginInfo(changesToProcess);
if(changes.modifiedPlugins.length > 0 || changes.deletedPlugins.length > 0) {
var changedShadowTiddlers = {};
// Collect the shadow tiddlers of any deleted plugins
$tw.utils.each(changes.deletedPlugins,function(pluginTitle) {
var pluginInfo = $tw.wiki.getPluginInfo(pluginTitle);
if(pluginInfo) {
$tw.utils.each(Object.keys(pluginInfo.tiddlers),function(title) {
changedShadowTiddlers[title] = true;
});
}
});
// Collect the shadow tiddlers of any modified plugins
$tw.utils.each(changes.modifiedPlugins,function(pluginTitle) {
var pluginInfo = $tw.wiki.getPluginInfo(pluginTitle);
if(pluginInfo && pluginInfo.tiddlers) {
$tw.utils.each(Object.keys(pluginInfo.tiddlers),function(title) {
changedShadowTiddlers[title] = false;
});
}
});
// (Re-)register any modified plugins
$tw.wiki.registerPluginTiddlers(null,changes.modifiedPlugins);
// Unregister any deleted plugins
$tw.wiki.unregisterPluginTiddlers(null,changes.deletedPlugins);
// Unpack the shadow tiddlers
$tw.wiki.unpackPluginTiddlers();
// Queue change events for the changed shadow tiddlers
$tw.utils.each(Object.keys(changedShadowTiddlers),function(title) {
$tw.wiki.enqueueTiddlerEvent(title,changedShadowTiddlers[title], true);
});
}
}
});
$tw.utils.installPluginChangeHandler($tw.wiki);
};

View File

@@ -13,10 +13,6 @@ exports.hasTag = function(tag) {
return this.fields.tags && this.fields.tags.indexOf(tag) !== -1;
};
exports.isPlugin = function() {
return this.fields.type === "application/json" && this.hasField("plugin-type");
};
exports.isDraft = function() {
return this.hasField("draft.of");
};

View File

@@ -0,0 +1,95 @@
/*\
title: $:/core/modules/utils/plugins.js
type: application/javascript
module-type: utils
Plugin utilities
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE = "$:/status/RequireReloadDueToPluginChange";
var PREFIX_CONFIG_REGISTER_PLUGIN_TYPE = "$:/config/RegisterPluginType/";
exports.installPluginChangeHandler = function(wiki) {
wiki.addTiddler({title: TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE,text: "no"});
wiki.addEventListener("change",function(changes) {
// Work out which of the changed tiddlers are plugins that we need to (re)register
var changesToProcess = [];
$tw.utils.each(Object.keys(changes),function(title) {
var tiddler = wiki.getTiddler(title);
if(tiddler) {
// It is a plugin that has been added or modified and is of a type that we need to register
if(tiddler.isPlugin() && wiki.getTiddlerText(PREFIX_CONFIG_REGISTER_PLUGIN_TYPE + (tiddler.fields["plugin-type"] || ""),"no") === "yes") {
changesToProcess.push(title);
}
} else {
if(wiki.isSubPlugin(title)) {
// It is a sub-plugin
changesToProcess.push(title);
} else {
// It is a plugin that has been deleted
var pluginInfo = wiki.getPluginInfo(title)
if(pluginInfo) {
changesToProcess.push(title);
}
}
}
});
if(changesToProcess.length > 0) {
// Read the plugin info of the changed tiddlers
var changedPluginInfo = wiki.readPluginInfo(changesToProcess);
if(changedPluginInfo.modifiedPlugins.length > 0 || changedPluginInfo.deletedPlugins.length > 0) {
var changedShadowTiddlers = {},
requireReloadDueToPluginChange = false;
// Collect the shadow tiddlers of any deleted plugins
$tw.utils.each(changedPluginInfo.deletedPlugins,function(pluginTitle) {
var contents = changedPluginInfo.deletedPluginContents[pluginTitle];
if(contents && contents.tiddlers) {
$tw.utils.each(Object.keys(contents.tiddlers),function(title) {
changedShadowTiddlers[title] = true;
if(contents.tiddlers[title].type === "application/javascript") {
requireReloadDueToPluginChange = true;
}
});
}
});
// Collect the shadow tiddlers of any modified plugins
$tw.utils.each(changedPluginInfo.modifiedPlugins,function(pluginTitle) {
var pluginInfo = wiki.getPluginInfo(pluginTitle);
if(pluginInfo && pluginInfo.tiddlers) {
$tw.utils.each(Object.keys(pluginInfo.tiddlers),function(title) {
changedShadowTiddlers[title] = false;
if(pluginInfo.tiddlers[title].type === "application/javascript") {
requireReloadDueToPluginChange = true;
}
});
}
});
// (Re-)register any modified plugins
wiki.registerPluginTiddlers(null,changedPluginInfo.modifiedPlugins);
// Unregister any deleted plugins
wiki.unregisterPluginTiddlers(null,changedPluginInfo.deletedPlugins);
// Unpack the shadow tiddlers
wiki.unpackPluginTiddlers();
// Queue change events for the changed shadow tiddlers
$tw.utils.each(changedShadowTiddlers,function(status,title) {
wiki.enqueueTiddlerEvent(title,changedShadowTiddlers[title], true);
});
// Issue warning if any of the tiddlers require a reload
if(requireReloadDueToPluginChange) {
wiki.addTiddler({title: TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE,text: "yes"});
}
}
}
});
};
})();

View File

@@ -66,11 +66,24 @@ TestCaseWidget.prototype.render = function(parent,nextSibling) {
});
var jsonPayload = JSON.stringify(tiddlers);
this.testcaseWiki.addTiddlers(tiddlers);
// Suppress next tick dispatch for wiki change events
this.testcaseWiki.setDispatchMode(true);
// Unpack plugin tiddlers
this.testcaseWiki.readPluginInfo();
this.testcaseWiki.registerPluginTiddlers("plugin");
this.testcaseWiki.unpackPluginTiddlers();
this.testcaseWiki.addIndexersToWiki();
// Install the plugin change event handler
$tw.utils.installPluginChangeHandler(this.testcaseWiki);
// Install the language switcher
var languageSwitcher = new $tw.PluginSwitcher({
wiki: this.testcaseWiki,
pluginType: "language",
controllerTitle: "$:/language",
defaultPlugins: [
"$:/languages/en-GB"
]
});
// Generate a `transclusion` variable that depends on the values of the payload tiddlers so that the template can easily make unique state tiddlers
this.setVariable("transclusion",$tw.utils.hashString(jsonPayload));
// Generate a `payloadTiddlers` variable that contains the payload in JSON format
@@ -92,13 +105,20 @@ TestCaseWidget.prototype.render = function(parent,nextSibling) {
}
});
testcaseOutputWidget.render(testcaseOutputContainer);
// Install the wiki change event handler
this.testcaseWiki.addEventListener("change",function(changes) {
testcaseOutputWidget.refresh(changes,testcaseOutputContainer);
});
}
// Clear changes queue
this.testcaseWiki.clearTiddlerEventQueue();
// Run the actions if provided
if(this.testcaseWiki.tiddlerExists(this.testcaseTestActions)) {
testcaseOutputWidget.invokeActionString(this.testcaseWiki.getTiddlerText(this.testcaseTestActions));
testcaseOutputWidget.refresh(this.testcaseWiki.changedTiddlers,testcaseOutputContainer);
// Make sure all wiki events have been cleared
while(this.testcaseWiki.eventsTriggered) {
this.testcaseWiki.processOutstandingTiddlerEvents();
}
}
// Set up the test result variables
var testResult = "",
@@ -128,6 +148,8 @@ TestCaseWidget.prototype.render = function(parent,nextSibling) {
parentWidget: this
});
rootWidget.render(domNode);
// Re-enable next tick dispatch for wiki change events
this.testcaseWiki.setDispatchMode(false);
// Trap changes in the wiki and refresh the rendering
this.testcaseWiki.addEventListener("change",function(changes) {
rootWidget.refresh(changes,domNode);

View File

@@ -132,6 +132,14 @@ exports.dispatchEvent = function(type /*, args */) {
}
};
/*
false (default) to dispatch event tick as usual
true to suppress dispatching the event tick and allow outstanding events to be processed manually
*/
exports.setDispatchMode = function(mode) {
this.dispatchMode = mode;
}
/*
Causes a tiddler to be marked as changed, incrementing the change count, and triggers event handlers.
This method should be called after the changes it describes have been made to the wiki.tiddlers[] array.
@@ -156,17 +164,18 @@ exports.enqueueTiddlerEvent = function(title,isDeleted,isShadow) {
}
// Trigger events
this.eventListeners = this.eventListeners || {};
if(!this.eventsTriggered) {
var self = this;
$tw.utils.nextTick(function() {
var changes = self.changedTiddlers;
self.changedTiddlers = Object.create(null);
self.eventsTriggered = false;
if($tw.utils.count(changes) > 0) {
self.dispatchEvent("change",changes);
}
});
this.eventsTriggered = true;
if(!this.eventsTriggered && !this.dispatchMode) {
$tw.utils.nextTick(this.processOutstandingTiddlerEvents.bind(this));
}
this.eventsTriggered = true;
};
exports.processOutstandingTiddlerEvents = function() {
var changes = this.changedTiddlers;
this.changedTiddlers = Object.create(null);
this.eventsTriggered = false;
if($tw.utils.count(changes) > 0) {
this.dispatchEvent("change",changes);
}
};
@@ -1740,7 +1749,7 @@ exports.invokeUpgraders = function(titles,tiddlers) {
// Determine whether a plugin by title is dynamically loadable
exports.doesPluginRequireReload = function(title) {
var tiddler = this.getTiddler(title);
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"]) {
if(tiddler && tiddler.isPlugin()) {
if(tiddler.fields["plugin-type"] === "import") {
// The import plugin never requires reloading
return false;

View File

@@ -8,7 +8,7 @@ caption: {{$:/language/ControlPanel/Plugins/Caption}}
\whitespace trim
<$set name="plugin-type" value="""$type$""">
<$set name="qualified-state" value=<<qualify "$:/state/plugin-info">>>
<$list filter="[!has[draft.of]plugin-type[$type$]sort[name]]" emptyMessage=<<lingo "Empty/Hint">> template="$:/core/ui/Components/plugin-info"/>
<$list filter="[all[tiddlers+shadows]!has[draft.of]plugin-type[$type$]!field:hidden[yes]sort[name]]" emptyMessage=<<lingo "Empty/Hint">> template="$:/core/ui/Components/plugin-info"/>
</$set>
</$set>
\end

View File

@@ -1,5 +1,5 @@
title: $:/core/ui/ControlPanel/Plugins/Installed/Languages
tags: $:/tags/ControlPanel/Plugins
caption: {{$:/language/ControlPanel/Plugins/Languages/Caption}} (<$count filter="[!has[draft.of]plugin-type[language]]"/>)
caption: {{$:/language/ControlPanel/Plugins/Languages/Caption}} (<$count filter="[all[tiddlers+shadows]is[plugin]!field:hidden[yes]plugin-type[language]]"/>)
<<plugin-table language>>

View File

@@ -1,5 +1,5 @@
title: $:/core/ui/ControlPanel/Plugins/Installed/Plugins
tags: $:/tags/ControlPanel/Plugins
caption: {{$:/language/ControlPanel/Plugins/Plugins/Caption}} (<$count filter="[!has[draft.of]plugin-type[plugin]]"/>)
caption: {{$:/language/ControlPanel/Plugins/Plugins/Caption}} (<$count filter="[all[tiddlers+shadows]is[plugin]plugin-type[plugin]]"/>)
<<plugin-table plugin>>

View File

@@ -1,5 +1,5 @@
title: $:/core/ui/ControlPanel/Plugins/Installed/Themes
tags: $:/tags/ControlPanel/Plugins
caption: {{$:/language/ControlPanel/Plugins/Themes/Caption}} (<$count filter="[!has[draft.of]plugin-type[theme]]"/>)
caption: {{$:/language/ControlPanel/Plugins/Themes/Caption}} (<$count filter="[all[tiddlers+shadows]is[plugin]plugin-type[theme]]"/>)
<<plugin-table theme>>

View File

@@ -103,7 +103,8 @@ code-body: yes
-Description
-Narrative
-[[$:/temp/testcase/draft-title]]
-[has[plugin-type]]
-[[$:/status/RequireReloadDueToPluginChange]]
-[[$:/core]]
-[prefix<tf.state>]
-[prefix[$:/state/popup/export]]
-[prefix[$:/HistoryList]]

View File

@@ -1,4 +1,4 @@
title: $:/core/ui/testcases/actions/Export
tags: $:/tags/TestCase/Actions
<$macrocall $name="exportButton" exportFilter="[all[tiddlers]sort[]] -[prefix[$:/state/]] -Description -Narrative -ExpectedResult -[has[plugin-type]]" lingoBase="$:/language/Buttons/ExportTiddlers/"/>
<$macrocall $name="exportButton" exportFilter="[all[tiddlers]sort[]] -[prefix[$:/state/]] -Description -Narrative -ExpectedResult -[[$:/core]]" lingoBase="$:/language/Buttons/ExportTiddlers/"/>

View File

@@ -0,0 +1,36 @@
title: Plugins/DynamicJSPluginLoad
description: Dynamic JS plugin loading
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{$:/status/RequireReloadDueToPluginChange}}
+
title: Actions
<$action-createtiddler $basetitle="$:/plugins/tiddlywiki/test-js-plugin" $template="$:/plugins/tiddlywiki/test-js-plugin/raw" type="application/json"/>
+
title: $:/plugins/tiddlywiki/test-js-plugin/raw
list: readme
name: bundled-subplugin-tests
plugin-type: plugin
type: text/plain
{
"tiddlers": {
"$:/plugins/tiddlywiki/test-js-plugin/readme": {
"title": "$:/plugins/tiddlywiki/test-js-plugin/readme",
"text": "Readme from $:/plugins/tiddlywiki/test-js-plugin\n\n"
},
"$:/plugins/tiddlywiki/test-js-plugin/js": {
"title": "$:/plugins/tiddlywiki/test-js-plugin/js",
"text": "2+2",
"type": "application/javascript"
}
}
}
+
title: ExpectedResult
<p>yes</p>

View File

@@ -0,0 +1,47 @@
title: Plugins/DynamicLoadLanguage
description: Dynamically loading of language plugin
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{First}}
+
title: Actions
<$action-createtiddler $basetitle="$:/languages/fr-FR" $template="$:/languages/fr-FR/raw" type="application/json"/>
<$action-setfield $tiddler="$:/languages/fr-FR" plugin-type="language"/>
<$action-setfield $tiddler="$:/language" text="$:/languages/fr-FR"/>
+
title: $:/languages/en-GB
list: readme
name: en-GB
plugin-type: language
type: application/json
{
"tiddlers": {
"First": {
"title": "First",
"text": "This is English"
}
}
}
+
title: $:/languages/fr-FR/raw
list: readme
name: fr-FR
type: plain/text
{
"tiddlers": {
"First": {
"title": "First",
"text": "This is French"
}
}
}
+
title: ExpectedResult
<p>This is French</p>

View File

@@ -0,0 +1,46 @@
title: Plugins/Language
description: Loading of correct language plugin at startup
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{First}}
+
title: $:/language
$:/languages/fr-FR
+
title: $:/languages/en-GB
list: readme
name: en-GB
plugin-type: language
type: application/json
{
"tiddlers": {
"First": {
"title": "First",
"text": "This is English"
}
}
}
+
title: $:/languages/fr-FR
list: readme
name: fr-FR
plugin-type: language
type: application/json
{
"tiddlers": {
"First": {
"title": "First",
"text": "This is French"
}
}
}
+
title: ExpectedResult
<p>This is French</p>

View File

@@ -0,0 +1,57 @@
title: Plugins/LanguageAddonSubPlugin
description: Loading of correct language subplugin at startup
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{First}}
{{Second}}
+
title: $:/language
$:/languages/fr-FR-subplugin
+
title: $:/languages/fr-FR-container
list: readme
name: fr-FR
plugin-type: plugin
type: application/json
{
"tiddlers": {
"$:/languages/fr-FR-container/readme": {
"title": "$:/languages/fr-FR-container/readme",
"text": "Readme from ~$:/languages/fr-FR-container\n\n"
},
"$:/languages/fr-FR-subplugin": {
"title": "$:/languages/fr-FR-subplugin",
"name": "fr-FR",
"description": "fr-FR subplugin",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL",
"version": "5.3.7-prerelease",
"plugin-type": "language",
"dependents": "",
"type": "application/json",
"text": "{\"tiddlers\":{\"First\":{\"title\":\"First\",\"text\":\"First from ~$:/languages/fr-FR-subplugin\"}}}"
},
"$:/languages/fr-FR-subplugin2": {
"title": "$:/languages/fr-FR-subplugin2",
"name": "fr-FR",
"description": "fr-FR subplugin2",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL",
"version": "5.3.7-prerelease",
"plugin-type": "language",
"dependents": "",
"type": "application/json",
"text": "{\"tiddlers\":{\"Second\":{\"title\":\"Second\",\"text\":\"Second from ~$:/languages/fr-FR-subplugin2\"}}}"
}
}
}
+
title: ExpectedResult
<p>First from $:/languages/fr-FR-subplugin</p><p>Second from $:/languages/fr-FR-subplugin2</p>

View File

@@ -0,0 +1,45 @@
title: Plugins/LanguageSubPlugin
description: Loading of correct language subplugin at startup
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{First}}
{{$:/languages/fr-FR-container/readme}}
+
title: $:/language
$:/languages/fr-FR-subplugin
+
title: $:/languages/fr-FR-container
list: readme
name: fr-FR
plugin-type: plugin
type: application/json
{
"tiddlers": {
"$:/languages/fr-FR-container/readme": {
"title": "$:/languages/fr-FR-container/readme",
"text": "Readme from ~$:/languages/fr-FR-container\n\n"
},
"$:/languages/fr-FR-subplugin": {
"title": "$:/languages/fr-FR-subplugin",
"name": "fr-FR",
"description": "fr-FR subplugin",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL",
"version": "5.3.7-prerelease",
"plugin-type": "language",
"dependents": "",
"type": "application/json",
"text": "{\"tiddlers\":{\"First\":{\"title\":\"First\",\"text\":\"First from ~$:/languages/fr-FR-subplugin\"}}}"
}
}
}
+
title: ExpectedResult
<p>First from $:/languages/fr-FR-subplugin</p><p>Readme from $:/languages/fr-FR-container</p>

View File

@@ -0,0 +1,43 @@
title: SubPlugins/DynamicSubPluginLoad
description: Dynamic sub-plugin loading
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{$:/plugins/tiddlywiki/test-subplugin/readme}}
+
title: Actions
<$action-createtiddler $basetitle="$:/plugins/tiddlywiki/bundled-subplugin-tests" $template="$:/plugins/tiddlywiki/bundled-subplugin-tests/raw" type="application/json"/>
+
title: $:/plugins/tiddlywiki/bundled-subplugin-tests/raw
list: readme
name: bundled-subplugin-tests
plugin-type: plugin
type: text/plain
{
"tiddlers": {
"$:/plugins/tiddlywiki/bundled-subplugin-tests/readme": {
"title": "$:/plugins/tiddlywiki/bundled-subplugin-tests/readme",
"text": "Readme from $:/plugins/tiddlywiki/bundled-subplugin-tests\n\n"
},
"$:/plugins/tiddlywiki/test-subplugin": {
"title": "$:/plugins/tiddlywiki/test-subplugin",
"name": "test-subplugin",
"description": "Test subplugin",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL",
"version": "5.3.7-prerelease",
"plugin-type": "plugin",
"dependents": "",
"type": "application/json",
"text": "{\"tiddlers\":{\"$:/plugins/tiddlywiki/test-subplugin/readme\":{\"title\":\"$:/plugins/tiddlywiki/test-subplugin/readme\",\"text\":\"Readme from $:/plugins/tiddlywiki/test-subplugin\"},\"$:/plugins/tiddlywiki/test-subsubplugin\":{\"title\":\"$:/plugins/tiddlywiki/test-subsubplugin\",\"name\":\"test-subsubplugin\",\"description\":\"Test subsubplugin\",\"list\":\"readme readme2\",\"stability\":\"STABILITY_1_EXPERIMENTAL\",\"version\":\"5.3.7-prerelease\",\"plugin-type\":\"plugin\",\"dependents\":\"\",\"type\":\"application/json\",\"text\":\"{\\\"tiddlers\\\":{\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\":{\\\"title\\\":\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\",\\\"text\\\":\\\"Readme from $:/plugins/tiddlywiki/test-subsubplugin\\\"}}}\"}}}"
}
}
}
+
title: ExpectedResult
<p>Readme from <a class="tc-tiddlylink tc-tiddlylink-shadow" href="#%24%3A%2Fplugins%2Ftiddlywiki%2Ftest-subplugin">$:/plugins/tiddlywiki/test-subplugin</a></p>

View File

@@ -0,0 +1,47 @@
title: SubPlugins/DynamicSubPluginUnload
description: Dynamic sub-plugin unloading
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
<$transclude $tiddler="$:/plugins/tiddlywiki/test-subplugin/readme">
NO
</$transclude>
+
title: Actions
<$action-deletetiddler $tiddler="$:/plugins/tiddlywiki/bundled-subplugin-tests"/>
+
title: $:/plugins/tiddlywiki/bundled-subplugin-tests
list: readme
name: bundled-subplugin-tests
plugin-type: plugin
type: application/json
{
"tiddlers": {
"$:/plugins/tiddlywiki/bundled-subplugin-tests/readme": {
"title": "$:/plugins/tiddlywiki/bundled-subplugin-tests/readme",
"text": "Readme from $:/plugins/tiddlywiki/bundled-subplugin-tests\n\n"
},
"$:/plugins/tiddlywiki/test-subplugin": {
"title": "$:/plugins/tiddlywiki/test-subplugin",
"name": "test-subplugin",
"description": "Test subplugin",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL",
"version": "5.3.7-prerelease",
"plugin-type": "plugin",
"dependents": "",
"type": "application/json",
"text": "{\"tiddlers\":{\"$:/plugins/tiddlywiki/test-subplugin/readme\":{\"title\":\"$:/plugins/tiddlywiki/test-subplugin/readme\",\"text\":\"Readme from $:/plugins/tiddlywiki/test-subplugin\"},\"$:/plugins/tiddlywiki/test-subsubplugin\":{\"title\":\"$:/plugins/tiddlywiki/test-subsubplugin\",\"name\":\"test-subsubplugin\",\"description\":\"Test subsubplugin\",\"list\":\"readme readme2\",\"stability\":\"STABILITY_1_EXPERIMENTAL\",\"version\":\"5.3.7-prerelease\",\"plugin-type\":\"plugin\",\"dependents\":\"\",\"type\":\"application/json\",\"text\":\"{\\\"tiddlers\\\":{\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\":{\\\"title\\\":\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\",\\\"text\\\":\\\"Readme from $:/plugins/tiddlywiki/test-subsubplugin\\\"}}}\"}}}"
}
}
}
+
title: ExpectedResult
<p>
NO
</p>

View File

@@ -0,0 +1,21 @@
title: SubPlugins/SimpleSubPlugin
description: Simple sub-plugin
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{$:/plugins/tiddlywiki/test-subplugin/readme}}
+
title: $:/plugins/tiddlywiki/bundled-subplugin-tests
list: readme
name: bundled-subplugin-tests
plugin-type: plugin
type: application/json
{"tiddlers":{"$:/plugins/tiddlywiki/bundled-subplugin-tests/readme":{"title":"$:/plugins/tiddlywiki/bundled-subplugin-tests/readme","text":"Readme from $:/plugins/tiddlywiki/bundled-subplugin-tests\n\n"},"$:/plugins/tiddlywiki/test-subplugin":{"title":"$:/plugins/tiddlywiki/test-subplugin","name":"test-subplugin","description":"Test subplugin","list":"readme","stability":"STABILITY_1_EXPERIMENTAL","version":"5.3.7-prerelease","plugin-type":"plugin","dependents":"","type":"application/json","text":"{\"tiddlers\":{\"$:/plugins/tiddlywiki/test-subplugin/readme\":{\"title\":\"$:/plugins/tiddlywiki/test-subplugin/readme\",\"text\":\"Readme from $:/plugins/tiddlywiki/test-subplugin\"},\"$:/plugins/tiddlywiki/test-subsubplugin\":{\"title\":\"$:/plugins/tiddlywiki/test-subsubplugin\",\"name\":\"test-subsubplugin\",\"description\":\"Test subsubplugin\",\"list\":\"readme readme2\",\"stability\":\"STABILITY_1_EXPERIMENTAL\",\"version\":\"5.3.7-prerelease\",\"plugin-type\":\"plugin\",\"dependents\":\"\",\"type\":\"application/json\",\"text\":\"{\\\"tiddlers\\\":{\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\":{\\\"title\\\":\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\",\\\"text\\\":\\\"Readme from $:/plugins/tiddlywiki/test-subsubplugin\\\"}}}\"}}}"}}}
+
title: ExpectedResult
<p>Readme from <a class="tc-tiddlylink tc-tiddlylink-shadow" href="#%24%3A%2Fplugins%2Ftiddlywiki%2Ftest-subplugin">$:/plugins/tiddlywiki/test-subplugin</a></p>

View File

@@ -7,7 +7,9 @@
"tiddlywiki/menubar",
"tiddlywiki/confetti",
"tiddlywiki/dynannotate",
"tiddlywiki/tour"
"tiddlywiki/tour",
"tiddlywiki/codemirror",
"tiddlywiki/bundled-subplugin-tests"
],
"themes": [
"tiddlywiki/vanilla",
@@ -21,6 +23,7 @@
"tiddlywiki/readonly"
],
"languages": [
"fr-FR"
],
"build": {
"index": [

View File

@@ -0,0 +1,7 @@
{
"title": "$:/plugins/tiddlywiki/bundled-subplugin-tests",
"name": "bundled-subplugin-tests",
"description": "Bundled subplugin tests",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL"
}

View File

@@ -0,0 +1,4 @@
title: $:/plugins/tiddlywiki/bundled-subplugin-tests/readme
Readme from $:/plugins/tiddlywiki/bundled-subplugin-tests

View File

@@ -0,0 +1,7 @@
{
"title": "$:/plugins/tiddlywiki/test-subplugin",
"name": "test-subplugin",
"description": "Test subplugin",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL"
}

View File

@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/test-subplugin/readme
Readme from $:/plugins/tiddlywiki/test-subplugin

View File

@@ -0,0 +1,7 @@
{
"title": "$:/plugins/tiddlywiki/test-subsubplugin",
"name": "test-subsubplugin",
"description": "Test subsubplugin",
"list": "readme readme2",
"stability": "STABILITY_1_EXPERIMENTAL"
}

View File

@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/test-subsubplugin/readme
Readme from $:/plugins/tiddlywiki/test-subsubplugin

View File

@@ -0,0 +1,7 @@
{
"title": "$:/language/codemirror/en-GB",
"name": "en-GB",
"plugin-type": "language",
"description": "English (UK) translations for CodeMirror",
"list": "readme"
}

View File

@@ -0,0 +1,3 @@
title: $:/language/codemirror/en-GB/readme
English (UK) translations for CodeMirror

View File

@@ -0,0 +1,29 @@
title: $:/language/codemirror/
homeUrl: http://codemirror.net
addOnUrl: http://codemirror.net/doc/manual.html#addons
configUrl: http://codemirror.net/doc/manual.html#config
controlPanel/hint: Tʜᴇᴇ ꜱᴇᴛᴛɪɴɢꜱ ʟᴇᴛ ʏ ᴄᴜꜱᴛᴏᴍɪꜱᴇ ᴛʜᴇ ʙᴇʜᴀᴠɪᴏᴜʀ ᴏꜰ [[CᴅᴇMɪʀʀʀ|$:/plugins/tiddlywiki/codemirror]].
controlPanel/keyboard: Kᴇʏʙᴀʀᴅ ꜱʜᴏʀᴛᴄᴜᴛꜱ
controlPanel/usage: Uᴀɢᴇ ɪɴꜰᴏʀᴍᴀᴛɪᴏɴ
cursorBlinkRate/hint: Cʀʀ ʙʟɪɴᴋ ʀᴀᴛᴇ
editorFont/hint: Eᴅɪʀ ꜰᴏɴᴛ ꜰᴀᴍɪʟʏ
editorFont/info: Sᴇᴛ ᴛʜᴇ ꜰᴏɴᴛ ꜰᴀᴍɪʟʏ ꜰᴏʀ ᴛʜᴇ ~CᴅᴇMɪʀʀʀ ᴛᴇxᴛ-ᴇᴅɪᴛᴏʀ
indentUnit/hint: H ᴍᴀɴʏ ꜱᴘᴀᴄᴇꜱ ᴀ ʙʟᴏᴄᴋ ꜱʜᴏᴜʟᴅ ʙᴇ ɪɴᴅᴇɴᴛᴇᴅ
indentWithTabs/hint: Eɴᴀʙʟᴇ ɪɴᴅᴇɴᴛɪɴɢ ᴡɪᴛʜ ᴛᴀʙꜱ
indentWithTabs/info: Wʜᴇᴛʜᴇʀ, ᴡʜᴇɴ ɪɴᴅᴇɴᴛɪɴɢ, ᴛʜᴇ ꜰɪʀꜱᴛ N*`ᴛᴀʙSɪᴇ` ꜱᴘᴀᴄᴇꜱ ꜱʜᴏᴜʟᴅ ʙᴇ ʀᴇᴘʟᴀᴄᴇᴅ ʙʏ N ᴛᴀʙꜱ.
keyMap/hint: ~CᴅᴇMɪʀʀʀ ᴋᴇʏᴍᴀᴘ
keyMap/info: ~Tʜᴇ Kᴇʏʙᴀʀᴅ KᴇʏMᴀᴘ ᴜꜱᴇᴅ ᴡɪᴛʜɪɴ ᴛʜᴇ ~CᴅᴇMɪʀʀʀ ᴛᴇxᴛ-ᴇᴅɪᴛᴏʀ
lineNumbers/hint: Eɴᴀʙʟᴇ ʟɪɴᴇ ɴᴜᴍʙᴇʀꜱ
lineNumbers/info: Wʜᴇᴛʜᴇʀ ᴛᴏ ꜱʜᴏᴡ ʟɪɴᴇ ɴᴜᴍʙᴇʀꜱ ᴛᴏ ᴛʜᴇ ʟᴇꜰᴛ ᴏꜰ ᴛʜᴇ ᴇᴅɪᴛᴏʀ.
lineWrapping/hint: Eɴᴀʙʟᴇ ʟɪɴᴇ ᴡʀᴀᴘᴘɪɴɢ
lineWrapping/info: Wʜᴇᴛʜᴇʀ CᴅᴇMɪʀʀʀ ꜱʜᴏᴜʟᴅ ꜱᴄʀᴏʟʟ ᴏʀ ᴡʀᴀᴘ ꜰᴏʀ ʟᴏɴɢ ʟɪɴᴇꜱ. Dᴇꜰᴀʟᴛ ᴛᴏ `ꜰᴀʟꜱᴇ` (ꜱᴄʀᴏʟʟ).
showCursorWhenSelecting/hint: Sʜ ᴄᴜʀꜱᴏʀ, ᴡʜᴇɴ ꜱᴇʟᴇᴄᴛɪɴɢ
showCursorWhenSelecting/info: Wʜᴇᴛʜᴇʀ ᴛʜᴇ ᴄᴜʀꜱᴏʀ ꜱʜᴏᴜʟᴅ ʙᴇ ᴅʀᴀᴡɴ ᴡʜᴇɴ ᴀ ꜱᴇʟᴇᴄᴛɪᴏɴ ɪ ᴀᴄᴛɪᴠᴇ.
smartIndent/hint: Eɴᴀʙʟᴇ ꜱᴍᴀʀᴛ ɪɴᴅᴇɴᴛ
smartIndent/info: Wʜᴇᴛʜᴇʀ ᴛᴏ ᴜꜱᴇ ᴛʜᴇ ɴᴛᴇxᴛ-ꜱᴇɴꜱɪᴛɪᴠᴇ ɪɴᴅᴇɴᴛᴀᴛɪᴏɴ ᴛʜᴀᴛ ᴛʜᴇ ᴍᴏᴅᴇ ᴘʀᴏᴠɪᴅᴇꜱ (ᴏʀ ᴊᴜꜱᴛ ɪɴᴅᴇɴᴛ ᴛʜᴇ ꜱᴀᴍᴇ ᴀꜱ ᴛʜᴇ ʟɪɴᴇ ʙᴇꜰᴏʀᴇ). Dᴇꜰᴀʟᴛ ᴛᴏ `ᴛʀᴜᴇ`.
styleActiveLine/hint: Hɪɢʜʟɪɢʜᴛ ᴀᴄᴛɪᴠᴇ ʟɪɴᴇ
styleActiveLine/info: Wʜᴇᴛʜᴇʀ ᴏʀ ɴᴏᴛ ᴛᴏ ʜɪɢʜʟɪɢʜᴛ ᴛʜᴇ ᴀᴄᴛɪᴠᴇ ᴛᴇxᴛ-ᴇᴅɪᴛᴏʀ ʟɪɴᴇ
tabSize/hint: Wɪᴅᴛʜ ᴏꜰ ᴀ ᴛᴀʙ ᴄʜᴀʀᴀᴄᴛᴇʀ
theme/hint: Sᴇʟᴇᴛ ᴀ ᴛʜᴇᴍᴇ
theme/info: Cʜᴇ ʙᴇᴛᴡᴇᴇɴ ~CᴅᴇMɪʀʀʀ ᴛʜᴇᴍᴇꜱ

View File

@@ -0,0 +1,7 @@
{
"title": "$:/language/codemirror/fr-FR",
"name": "fr-FR",
"plugin-type": "language",
"description": "French translations for CodeMirror",
"list": "readme"
}

View File

@@ -0,0 +1,3 @@
title: $:/language/codemirror/fr-FR/readme
French translations for CodeMirror

View File

@@ -21,17 +21,32 @@ describe("Wiki-based tests", function() {
var tiddler = $tw.wiki.getTiddler(title);
it(tiddler.fields.title + ": " + tiddler.fields.description, function() {
// Add our tiddlers
var wiki = new $tw.Wiki(),
coreTiddler = $tw.wiki.getTiddler("$:/core");
var wiki = new $tw.Wiki();
// Suppress next tick dispatch for wiki change events
wiki.setDispatchMode(true);
// Add the core plugin
var coreTiddler = $tw.wiki.getTiddler("$:/core")
if(coreTiddler) {
wiki.addTiddler(coreTiddler);
}
// Add other tiddlers
wiki.addTiddlers(readMultipleTiddlersTiddler(title));
// Unpack plugin tiddlers
wiki.readPluginInfo();
wiki.registerPluginTiddlers("plugin");
wiki.unpackPluginTiddlers();
wiki.addIndexersToWiki();
// Install the language switcher
var languageSwitcher = new $tw.PluginSwitcher({
wiki: wiki,
pluginType: "language",
controllerTitle: "$:/language",
defaultPlugins: [
"$:/languages/en-GB"
]
});
// Install the plugin change event handler
$tw.utils.installPluginChangeHandler(wiki);
// Clear changes queue
wiki.clearTiddlerEventQueue();
// Complain if we don't have the ouput and expected results
@@ -44,12 +59,17 @@ describe("Wiki-based tests", function() {
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
// Render the widget node to the DOM
var wrapper = renderWidgetNode(widgetNode);
// Clear changes queue
wiki.clearTiddlerEventQueue();
// Install the wiki change event handler
wiki.addEventListener("change",function(changes) {
widgetNode.refresh(changes,wrapper);
});
// Run the actions if provided
if(wiki.tiddlerExists("Actions")) {
widgetNode.invokeActionString(wiki.getTiddlerText("Actions"));
refreshWidgetNode(widgetNode,wrapper);
}
// Make sure all wiki events have been cleared
while(wiki.eventsTriggered) {
wiki.processOutstandingTiddlerEvents();
}
// Test the rendering
expect(wrapper.innerHTML).toBe(wiki.getTiddlerText("ExpectedResult"));
@@ -90,9 +110,4 @@ describe("Wiki-based tests", function() {
return wrapper;
}
function refreshWidgetNode(widgetNode,wrapper) {
widgetNode.refresh(widgetNode.wiki.changedTiddlers,wrapper);
// console.log(require("util").inspect(wrapper,{depth: 8}));
}
});