mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-23 10:07:19 +00:00
Dynamic loading/unloading of plugins (#4259)
* First pass at dynamic loading/unloading * Show warning for changes to plugins containing JS modules * Use $:/config/RegisterPluginType/* for configuring whether a plugin type is automatically registered Where "registered" means "the constituent shadows are loaded". * Fix the info plugin The previous mechanism re-read all plugin info during startup * Don't prettify JSON in the plugin library * Indicate in plugin library whether a plugin requires reloading * Display the highlighted plugin name in the plugin chooser And if there's no name field fall back to the part of the title after the final slash.
This commit is contained in:
parent
b44dc39299
commit
1c23059204
55
boot/boot.js
55
boot/boot.js
@ -1237,15 +1237,39 @@ $tw.Wiki = function(options) {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read plugin info for all plugins
|
// Get an array of all the currently recognised plugin types
|
||||||
this.readPluginInfo = function() {
|
this.getPluginTypes = function() {
|
||||||
for(var title in tiddlers) {
|
var types = [];
|
||||||
var tiddler = tiddlers[title];
|
$tw.utils.each(pluginTiddlers,function(pluginTiddler) {
|
||||||
if(tiddler.fields.type === "application/json" && tiddler.hasField("plugin-type")) {
|
var pluginType = pluginTiddler.fields["plugin-type"];
|
||||||
pluginInfo[tiddler.fields.title] = JSON.parse(tiddler.fields.text);
|
if(pluginType && types.indexOf(pluginType) === -1) {
|
||||||
|
types.push(pluginType);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
return types;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
// Read plugin info for all plugins, or just an array of titles. Returns the number of plugins updated or deleted
|
||||||
|
this.readPluginInfo = function(titles) {
|
||||||
|
var results = {
|
||||||
|
modifiedPlugins: [],
|
||||||
|
deletedPlugins: []
|
||||||
|
};
|
||||||
|
$tw.utils.each(titles || getTiddlerTitles(),function(title) {
|
||||||
|
var tiddler = tiddlers[title];
|
||||||
|
if(tiddler) {
|
||||||
|
if(tiddler.fields.type === "application/json" && tiddler.hasField("plugin-type")) {
|
||||||
|
pluginInfo[tiddler.fields.title] = JSON.parse(tiddler.fields.text);
|
||||||
|
results.modifiedPlugins.push(tiddler.fields.title);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(pluginInfo[title]) {
|
||||||
|
delete pluginInfo[title];
|
||||||
|
results.deletedPlugins.push(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get plugin info for a plugin
|
// Get plugin info for a plugin
|
||||||
@ -1253,14 +1277,15 @@ $tw.Wiki = function(options) {
|
|||||||
return pluginInfo[title];
|
return pluginInfo[title];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register the plugin tiddlers of a particular type, optionally restricting registration to an array of tiddler titles. Return the array of titles affected
|
// 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) {
|
this.registerPluginTiddlers = function(pluginType,titles) {
|
||||||
var self = this,
|
var self = this,
|
||||||
registeredTitles = [],
|
registeredTitles = [],
|
||||||
checkTiddler = function(tiddler,title) {
|
checkTiddler = function(tiddler,title) {
|
||||||
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"] === pluginType) {
|
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);
|
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title);
|
||||||
if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
|
if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
|
||||||
|
self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered
|
||||||
pluginTiddlers.push(tiddler);
|
pluginTiddlers.push(tiddler);
|
||||||
registeredTitles.push(tiddler.fields.title);
|
registeredTitles.push(tiddler.fields.title);
|
||||||
}
|
}
|
||||||
@ -1278,19 +1303,19 @@ $tw.Wiki = function(options) {
|
|||||||
return registeredTitles;
|
return registeredTitles;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unregister the plugin tiddlers of a particular type, returning an array of the titles affected
|
// Unregister the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting unregistering to an array of tiddler titles. Returns an array of the titles affected
|
||||||
this.unregisterPluginTiddlers = function(pluginType) {
|
this.unregisterPluginTiddlers = function(pluginType,titles) {
|
||||||
var self = this,
|
var self = this,
|
||||||
titles = [];
|
unregisteredTitles = [];
|
||||||
// Remove any previous registered plugins of this type
|
// Remove any previous registered plugins of this type
|
||||||
for(var t=pluginTiddlers.length-1; t>=0; t--) {
|
for(var t=pluginTiddlers.length-1; t>=0; t--) {
|
||||||
var tiddler = pluginTiddlers[t];
|
var tiddler = pluginTiddlers[t];
|
||||||
if(tiddler.fields["plugin-type"] === pluginType) {
|
if(tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType) && (!titles || titles.indexOf(tiddler.fields.title) !== -1)) {
|
||||||
titles.push(tiddler.fields.title);
|
unregisteredTitles.push(tiddler.fields.title);
|
||||||
pluginTiddlers.splice(t,1);
|
pluginTiddlers.splice(t,1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return titles;
|
return unregisteredTitles;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unpack the currently registered plugins, creating shadow tiddlers for their constituent tiddlers
|
// Unpack the currently registered plugins, creating shadow tiddlers for their constituent tiddlers
|
||||||
|
@ -78,6 +78,7 @@ Plugins/NoInfoFound/Hint: No ''"<$text text=<<currentTab>>/>"'' found
|
|||||||
Plugins/NotInstalled/Hint: This plugin is not currently installed
|
Plugins/NotInstalled/Hint: This plugin is not currently installed
|
||||||
Plugins/OpenPluginLibrary: open plugin library
|
Plugins/OpenPluginLibrary: open plugin library
|
||||||
Plugins/ClosePluginLibrary: close plugin library
|
Plugins/ClosePluginLibrary: close plugin library
|
||||||
|
Plugins/PluginWillRequireReload: (requires reload)
|
||||||
Plugins/Plugins/Caption: Plugins
|
Plugins/Plugins/Caption: Plugins
|
||||||
Plugins/Plugins/Hint: Plugins
|
Plugins/Plugins/Hint: Plugins
|
||||||
Plugins/Reinstall/Caption: reinstall
|
Plugins/Reinstall/Caption: reinstall
|
||||||
|
@ -59,7 +59,7 @@ MissingTiddler/Hint: Missing tiddler "<$text text=<<currentTiddler>>/>" -- click
|
|||||||
No: No
|
No: No
|
||||||
OfficialPluginLibrary: Official ~TiddlyWiki Plugin Library
|
OfficialPluginLibrary: Official ~TiddlyWiki Plugin Library
|
||||||
OfficialPluginLibrary/Hint: The official ~TiddlyWiki plugin library at tiddlywiki.com. Plugins, themes and language packs are maintained by the core team.
|
OfficialPluginLibrary/Hint: The official ~TiddlyWiki plugin library at tiddlywiki.com. Plugins, themes and language packs are maintained by the core team.
|
||||||
PluginReloadWarning: Please save {{$:/core/ui/Buttons/save-wiki}} and reload {{$:/core/ui/Buttons/refresh}} to allow changes to plugins to take effect
|
PluginReloadWarning: Please save {{$:/core/ui/Buttons/save-wiki}} and reload {{$:/core/ui/Buttons/refresh}} to allow changes to ~JavaScript plugins to take effect
|
||||||
RecentChanges/DateFormat: DDth MMM YYYY
|
RecentChanges/DateFormat: DDth MMM YYYY
|
||||||
SystemTiddler/Tooltip: This is a system tiddler
|
SystemTiddler/Tooltip: This is a system tiddler
|
||||||
SystemTiddlers/Include/Prompt: Include system tiddlers
|
SystemTiddlers/Include/Prompt: Include system tiddlers
|
||||||
|
@ -59,7 +59,7 @@ Command.prototype.execute = function() {
|
|||||||
title: upgradeLibraryTitle,
|
title: upgradeLibraryTitle,
|
||||||
type: "application/json",
|
type: "application/json",
|
||||||
"plugin-type": "library",
|
"plugin-type": "library",
|
||||||
"text": JSON.stringify({tiddlers: tiddlers},null,$tw.config.preferences.jsonSpaces)
|
"text": JSON.stringify({tiddlers: tiddlers})
|
||||||
};
|
};
|
||||||
wiki.addTiddler(new $tw.Tiddler(pluginFields));
|
wiki.addTiddler(new $tw.Tiddler(pluginFields));
|
||||||
return null;
|
return null;
|
||||||
|
@ -65,10 +65,11 @@ Command.prototype.execute = function() {
|
|||||||
// Save each JSON file and collect the skinny data
|
// Save each JSON file and collect the skinny data
|
||||||
var pathname = path.resolve(self.commander.outputPath,basepath + encodeURIComponent(title) + ".json");
|
var pathname = path.resolve(self.commander.outputPath,basepath + encodeURIComponent(title) + ".json");
|
||||||
$tw.utils.createFileDirectories(pathname);
|
$tw.utils.createFileDirectories(pathname);
|
||||||
fs.writeFileSync(pathname,JSON.stringify(tiddler,null,$tw.config.preferences.jsonSpaces),"utf8");
|
fs.writeFileSync(pathname,JSON.stringify(tiddler),"utf8");
|
||||||
// Collect the skinny list data
|
// Collect the skinny list data
|
||||||
var pluginTiddlers = JSON.parse(tiddler.text),
|
var pluginTiddlers = JSON.parse(tiddler.text),
|
||||||
readmeContent = (pluginTiddlers.tiddlers[title + "/readme"] || {}).text,
|
readmeContent = (pluginTiddlers.tiddlers[title + "/readme"] || {}).text,
|
||||||
|
doesContainJavaScript = !!$tw.wiki.doesPluginInfoContainModules(pluginTiddlers),
|
||||||
iconTiddler = pluginTiddlers.tiddlers[title + "/icon"] || {},
|
iconTiddler = pluginTiddlers.tiddlers[title + "/icon"] || {},
|
||||||
iconType = iconTiddler.type,
|
iconType = iconTiddler.type,
|
||||||
iconText = iconTiddler.text,
|
iconText = iconTiddler.text,
|
||||||
@ -76,7 +77,12 @@ Command.prototype.execute = function() {
|
|||||||
if(iconType && iconText) {
|
if(iconType && iconText) {
|
||||||
iconContent = $tw.utils.makeDataUri(iconText,iconType);
|
iconContent = $tw.utils.makeDataUri(iconText,iconType);
|
||||||
}
|
}
|
||||||
skinnyList.push($tw.utils.extend({},tiddler,{text: undefined, readme: readmeContent, icon: iconContent}));
|
skinnyList.push($tw.utils.extend({},tiddler,{
|
||||||
|
text: undefined,
|
||||||
|
readme: readmeContent,
|
||||||
|
"contains-javascript": doesContainJavaScript ? "yes" : "no",
|
||||||
|
icon: iconContent
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
// Save the catalogue tiddler
|
// Save the catalogue tiddler
|
||||||
if(skinnyListTitle) {
|
if(skinnyListTitle) {
|
||||||
|
@ -18,6 +18,8 @@ exports.before = ["startup"];
|
|||||||
exports.after = ["load-modules"];
|
exports.after = ["load-modules"];
|
||||||
exports.synchronous = true;
|
exports.synchronous = true;
|
||||||
|
|
||||||
|
var TITLE_INFO_PLUGIN = "$:/temp/info-plugin";
|
||||||
|
|
||||||
exports.startup = function() {
|
exports.startup = function() {
|
||||||
// Collect up the info tiddlers
|
// Collect up the info tiddlers
|
||||||
var infoTiddlerFields = {};
|
var infoTiddlerFields = {};
|
||||||
@ -32,15 +34,15 @@ exports.startup = function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Bake the info tiddlers into a plugin
|
// Bake the info tiddlers into a plugin. We use the non-standard plugin-type "info" because ordinary plugins are only registered asynchronously after being loaded dynamically
|
||||||
var fields = {
|
var fields = {
|
||||||
title: "$:/temp/info-plugin",
|
title: TITLE_INFO_PLUGIN,
|
||||||
type: "application/json",
|
type: "application/json",
|
||||||
"plugin-type": "info",
|
"plugin-type": "info",
|
||||||
text: JSON.stringify({tiddlers: infoTiddlerFields},null,$tw.config.preferences.jsonSpaces)
|
text: JSON.stringify({tiddlers: infoTiddlerFields},null,$tw.config.preferences.jsonSpaces)
|
||||||
};
|
};
|
||||||
$tw.wiki.addTiddler(new $tw.Tiddler(fields));
|
$tw.wiki.addTiddler(new $tw.Tiddler(fields));
|
||||||
$tw.wiki.readPluginInfo();
|
$tw.wiki.readPluginInfo([TITLE_INFO_PLUGIN]);
|
||||||
$tw.wiki.registerPluginTiddlers("info");
|
$tw.wiki.registerPluginTiddlers("info");
|
||||||
$tw.wiki.unpackPluginTiddlers();
|
$tw.wiki.unpackPluginTiddlers();
|
||||||
};
|
};
|
||||||
|
59
core/modules/startup/plugins.js
Normal file
59
core/modules/startup/plugins.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*\
|
||||||
|
title: $:/core/modules/startup/plugins.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: startup
|
||||||
|
|
||||||
|
Startup logic concerned with managing plugins
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Export name and synchronous status
|
||||||
|
exports.name = "plugins";
|
||||||
|
exports.after = ["load-modules"];
|
||||||
|
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) {
|
||||||
|
var changesToProcess = [],
|
||||||
|
requireReloadDueToPluginChange = false;
|
||||||
|
$tw.utils.each(Object.keys(changes),function(title) {
|
||||||
|
var tiddler = $tw.wiki.getTiddler(title),
|
||||||
|
containsModules = $tw.wiki.doesPluginContainModules(title);
|
||||||
|
if(containsModules) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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) {
|
||||||
|
// (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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
@ -1450,5 +1450,25 @@ exports.invokeUpgraders = function(titles,tiddlers) {
|
|||||||
return messages;
|
return messages;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Determine whether a plugin by title contains JS modules.
|
||||||
|
exports.doesPluginContainModules = function(title) {
|
||||||
|
return this.doesPluginInfoContainModules(this.getPluginInfo(title) || this.getTiddlerDataCached(title));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine whether a plugin info structure contains JS modules.
|
||||||
|
exports.doesPluginInfoContainModules = function(pluginInfo) {
|
||||||
|
if(pluginInfo) {
|
||||||
|
var foundModule = false;
|
||||||
|
$tw.utils.each(pluginInfo.tiddlers,function(tiddler) {
|
||||||
|
if(tiddler.type === "application/javascript" && $tw.utils.hop(tiddler,"module-type")) {
|
||||||
|
foundModule = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return foundModule;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ subtitle: {{$:/core/images/download-button}} {{$:/language/ControlPanel/Plugins/
|
|||||||
<$list filter="[<assetInfo>get[original-title]get[version]]" variable="installedVersion" emptyMessage="""{{$:/language/ControlPanel/Plugins/Install/Caption}}""">
|
<$list filter="[<assetInfo>get[original-title]get[version]]" variable="installedVersion" emptyMessage="""{{$:/language/ControlPanel/Plugins/Install/Caption}}""">
|
||||||
{{$:/language/ControlPanel/Plugins/Reinstall/Caption}}
|
{{$:/language/ControlPanel/Plugins/Reinstall/Caption}}
|
||||||
</$list>
|
</$list>
|
||||||
|
<$reveal stateTitle=<<assetInfo>> stateField="contains-javascript" type="match" text="yes">{{$:/language/ControlPanel/Plugins/PluginWillRequireReload}}</$reveal>
|
||||||
</$button>
|
</$button>
|
||||||
\end
|
\end
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ $:/state/add-plugin-info/$(connectionTiddler)$/$(assetInfo)$
|
|||||||
</$list>
|
</$list>
|
||||||
</div>
|
</div>
|
||||||
<div class="tc-plugin-info-chunk">
|
<div class="tc-plugin-info-chunk">
|
||||||
<h1><$view tiddler=<<assetInfo>> field="description"/></h1>
|
<h1><strong><$text text={{{ [<assetInfo>get[name]] ~[<assetInfo>get[original-title]split[/]last[1]] }}}/></strong>: <$view tiddler=<<assetInfo>> field="description"/></h1>
|
||||||
<h2><$view tiddler=<<assetInfo>> field="original-title"/></h2>
|
<h2><$view tiddler=<<assetInfo>> field="original-title"/></h2>
|
||||||
<div><em><$view tiddler=<<assetInfo>> field="version"/></em></div>
|
<div><em><$view tiddler=<<assetInfo>> field="version"/></em></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ tags: $:/tags/PageTemplate
|
|||||||
|
|
||||||
\define lingo-base() $:/language/
|
\define lingo-base() $:/language/
|
||||||
|
|
||||||
<$list filter="[has[plugin-type]haschanged[]!plugin-type[import]limit[1]]">
|
<$list filter="[{$:/status/RequireReloadDueToPluginChange}match[yes]]">
|
||||||
|
|
||||||
<$reveal type="nomatch" state="$:/temp/HidePluginWarning" text="yes">
|
<$reveal type="nomatch" state="$:/temp/HidePluginWarning" text="yes">
|
||||||
|
|
||||||
|
7
core/wiki/config/RegisterPluginTypes.multids
Normal file
7
core/wiki/config/RegisterPluginTypes.multids
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
title: $:/config/RegisterPluginType/
|
||||||
|
|
||||||
|
plugin: yes
|
||||||
|
theme: yes
|
||||||
|
language: yes
|
||||||
|
info: no
|
||||||
|
import: no
|
Loading…
Reference in New Issue
Block a user