kopia lustrzana https://github.com/miklobit/TiddlyWiki5
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.fix-syncer
rodzic
b44dc39299
commit
1c23059204
55
boot/boot.js
55
boot/boot.js
|
@ -1237,15 +1237,39 @@ $tw.Wiki = function(options) {
|
|||
return null;
|
||||
};
|
||||
|
||||
// Read plugin info for all plugins
|
||||
this.readPluginInfo = function() {
|
||||
for(var title in tiddlers) {
|
||||
var tiddler = tiddlers[title];
|
||||
if(tiddler.fields.type === "application/json" && tiddler.hasField("plugin-type")) {
|
||||
pluginInfo[tiddler.fields.title] = JSON.parse(tiddler.fields.text);
|
||||
// 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"];
|
||||
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
|
||||
|
@ -1253,14 +1277,15 @@ $tw.Wiki = function(options) {
|
|||
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) {
|
||||
var self = this,
|
||||
registeredTitles = [],
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
@ -1278,19 +1303,19 @@ $tw.Wiki = function(options) {
|
|||
return registeredTitles;
|
||||
};
|
||||
|
||||
// Unregister the plugin tiddlers of a particular type, returning an array of the titles affected
|
||||
this.unregisterPluginTiddlers = function(pluginType) {
|
||||
// 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,titles) {
|
||||
var self = this,
|
||||
titles = [];
|
||||
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) {
|
||||
titles.push(tiddler.fields.title);
|
||||
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);
|
||||
}
|
||||
}
|
||||
return titles;
|
||||
return unregisteredTitles;
|
||||
};
|
||||
|
||||
// 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/OpenPluginLibrary: open plugin library
|
||||
Plugins/ClosePluginLibrary: close plugin library
|
||||
Plugins/PluginWillRequireReload: (requires reload)
|
||||
Plugins/Plugins/Caption: Plugins
|
||||
Plugins/Plugins/Hint: Plugins
|
||||
Plugins/Reinstall/Caption: reinstall
|
||||
|
|
|
@ -59,7 +59,7 @@ MissingTiddler/Hint: Missing tiddler "<$text text=<<currentTiddler>>/>" -- click
|
|||
No: No
|
||||
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.
|
||||
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
|
||||
SystemTiddler/Tooltip: This is a system tiddler
|
||||
SystemTiddlers/Include/Prompt: Include system tiddlers
|
||||
|
|
|
@ -59,7 +59,7 @@ Command.prototype.execute = function() {
|
|||
title: upgradeLibraryTitle,
|
||||
type: "application/json",
|
||||
"plugin-type": "library",
|
||||
"text": JSON.stringify({tiddlers: tiddlers},null,$tw.config.preferences.jsonSpaces)
|
||||
"text": JSON.stringify({tiddlers: tiddlers})
|
||||
};
|
||||
wiki.addTiddler(new $tw.Tiddler(pluginFields));
|
||||
return null;
|
||||
|
|
|
@ -65,10 +65,11 @@ Command.prototype.execute = function() {
|
|||
// Save each JSON file and collect the skinny data
|
||||
var pathname = path.resolve(self.commander.outputPath,basepath + encodeURIComponent(title) + ".json");
|
||||
$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
|
||||
var pluginTiddlers = JSON.parse(tiddler.text),
|
||||
readmeContent = (pluginTiddlers.tiddlers[title + "/readme"] || {}).text,
|
||||
doesContainJavaScript = !!$tw.wiki.doesPluginInfoContainModules(pluginTiddlers),
|
||||
iconTiddler = pluginTiddlers.tiddlers[title + "/icon"] || {},
|
||||
iconType = iconTiddler.type,
|
||||
iconText = iconTiddler.text,
|
||||
|
@ -76,7 +77,12 @@ Command.prototype.execute = function() {
|
|||
if(iconType && iconText) {
|
||||
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
|
||||
if(skinnyListTitle) {
|
||||
|
|
|
@ -18,6 +18,8 @@ exports.before = ["startup"];
|
|||
exports.after = ["load-modules"];
|
||||
exports.synchronous = true;
|
||||
|
||||
var TITLE_INFO_PLUGIN = "$:/temp/info-plugin";
|
||||
|
||||
exports.startup = function() {
|
||||
// Collect up the info tiddlers
|
||||
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 = {
|
||||
title: "$:/temp/info-plugin",
|
||||
title: TITLE_INFO_PLUGIN,
|
||||
type: "application/json",
|
||||
"plugin-type": "info",
|
||||
text: JSON.stringify({tiddlers: infoTiddlerFields},null,$tw.config.preferences.jsonSpaces)
|
||||
};
|
||||
$tw.wiki.addTiddler(new $tw.Tiddler(fields));
|
||||
$tw.wiki.readPluginInfo();
|
||||
$tw.wiki.readPluginInfo([TITLE_INFO_PLUGIN]);
|
||||
$tw.wiki.registerPluginTiddlers("info");
|
||||
$tw.wiki.unpackPluginTiddlers();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
// 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}}""">
|
||||
{{$:/language/ControlPanel/Plugins/Reinstall/Caption}}
|
||||
</$list>
|
||||
<$reveal stateTitle=<<assetInfo>> stateField="contains-javascript" type="match" text="yes">{{$:/language/ControlPanel/Plugins/PluginWillRequireReload}}</$reveal>
|
||||
</$button>
|
||||
\end
|
||||
|
||||
|
@ -35,7 +36,7 @@ $:/state/add-plugin-info/$(connectionTiddler)$/$(assetInfo)$
|
|||
</$list>
|
||||
</div>
|
||||
<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>
|
||||
<div><em><$view tiddler=<<assetInfo>> field="version"/></em></div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ tags: $:/tags/PageTemplate
|
|||
|
||||
\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">
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
title: $:/config/RegisterPluginType/
|
||||
|
||||
plugin: yes
|
||||
theme: yes
|
||||
language: yes
|
||||
info: no
|
||||
import: no
|
Ładowanie…
Reference in New Issue