1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-12-24 09:00:27 +00:00

First pass at upgrade mechanism

This commit is contained in:
Jermolene 2014-07-12 09:09:36 +01:00
parent f08f57c5d2
commit 1f6e16318a
11 changed files with 356 additions and 34 deletions

View File

@ -0,0 +1,11 @@
title: $:/language/Import/
Listing/Cancel/Caption: Cancel
Listing/Hint: These tiddlers are ready to import:
Listing/Import/Caption: Import
Listing/Select/Caption: Select
Listing/Status/Caption: Status
Listing/Title/Caption: Title
Upgrader/Plugins/Suppressed: Suppressed plugin (due to incoming <<incoming>> being older than existing <<existing>>)
Upgrader/Plugins/Upgraded: Upgraded plugin from <<incoming>> to <<upgraded>>
Upgrader/System/Suppressed: Suppressed system tiddler

View File

@ -0,0 +1,70 @@
/*\
title: $:/core/modules/commands/makelibrary.js
type: application/javascript
module-type: command
Command to pack all of the plugins in the library into a plugin tiddler of type "library"
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "makelibrary",
synchronous: true
};
var UPGRADE_LIBRARY_TITLE = "$:/UpgradeLibrary";
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var wiki = this.commander.wiki,
fs = require("fs"),
path = require("path"),
upgradeLibraryTitle = this.params[0] || UPGRADE_LIBRARY_TITLE,
tiddlers = {};
// Collect up the library plugins
var collectPlugins = function(folder) {
var pluginFolders = fs.readdirSync(folder);
for(var p=0; p<pluginFolders.length; p++) {
if(!$tw.boot.excludeRegExp.test(pluginFolders[p])) {
pluginFields = $tw.loadPluginFolder(path.resolve(folder,"./" + pluginFolders[p]));
if(pluginFields && pluginFields.title) {
tiddlers[pluginFields.title] = pluginFields;
}
}
}
},
collectPublisherPlugins = function(folder) {
var publisherFolders = fs.readdirSync(folder);
for(var t=0; t<publisherFolders.length; t++) {
if(!$tw.boot.excludeRegExp.test(publisherFolders[t])) {
collectPlugins(path.resolve(folder,"./" + publisherFolders[t]));
}
}
};
collectPublisherPlugins(path.resolve($tw.boot.corePath,$tw.config.pluginsPath));
collectPublisherPlugins(path.resolve($tw.boot.corePath,$tw.config.themesPath));
collectPlugins(path.resolve($tw.boot.corePath,$tw.config.languagesPath));
// Save the upgrade library tiddler
var pluginFields = {
title: upgradeLibraryTitle,
type: "application/json",
"plugin-type": "library",
"text": JSON.stringify({tiddlers: tiddlers},null,$tw.config.preferences.jsonSpaces)
};
wiki.addTiddler(new $tw.Tiddler(pluginFields));
return null;
};
exports.Command = Command;
})();

View File

@ -18,7 +18,7 @@ Export our filter function
exports.plugintiddlers = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var pluginInfo = options.wiki.getPluginInfo(title);
var pluginInfo = options.wiki.getPluginInfo(title) || options.wiki.getTiddlerData(title,{tiddlers:[]});
if(pluginInfo) {
$tw.utils.each(pluginInfo.tiddlers,function(fields,title) {
results.push(title);

View File

@ -0,0 +1,58 @@
/*\
title: $:/core/modules/upgraders/plugins.js
type: application/javascript
module-type: upgrader
Upgrader module that checks that plugins are newer than any already installed version
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var UPGRADE_LIBRARY_TITLE = "$:/UpgradeLibrary";
exports.upgrade = function(wiki,titles,tiddlers) {
var self = this,
messages = {},
upgradeLibrary,
getLibraryTiddler = function(title) {
if(!upgradeLibrary) {
upgradeLibrary = wiki.getTiddlerData(UPGRADE_LIBRARY_TITLE,{});
upgradeLibrary.tiddlers = upgradeLibrary.tiddlers || {};
}
return upgradeLibrary.tiddlers[title]
};
// Go through all the incoming tiddlers
$tw.utils.each(titles,function(title) {
var incomingTiddler = tiddlers[title];
// Check if we're dealing with a plugin
if(incomingTiddler && incomingTiddler["plugin-type"] && incomingTiddler["version"]) {
// Upgrade the incoming plugin if we've got a newer version in the upgrade library
var libraryTiddler = getLibraryTiddler(title);
if(libraryTiddler && libraryTiddler["plugin-type"] && libraryTiddler["version"]) {
if($tw.utils.checkVersions(libraryTiddler.version,incomingTiddler.version)) {
tiddlers[title] = libraryTiddler;
messages[title] = $tw.language.getString("Import/Upgrader/Plugins/Upgraded",{variables: {incoming: incomingTiddler.version, upgraded: libraryTiddler.version}});
return;
}
}
// Suppress the incoming plugin if it is older than the currently installed one
var existingTiddler = wiki.getTiddler(title);
if(existingTiddler && existingTiddler.hasField("plugin-type") && existingTiddler.hasField("version")) {
// Reject the incoming plugin by blanking all its fields
if($tw.utils.checkVersions(existingTiddler.fields.version,incomingTiddler.version)) {
tiddlers[title] = Object.create(null);
messages[title] = $tw.language.getString("Import/Upgrader/Plugins/Suppressed",{variables: {incoming: incomingTiddler.version, existing: existingTiddler.fields.version}});
return;
}
}
}
});
return messages;
};
})();

View File

@ -0,0 +1,30 @@
/*\
title: $:/core/modules/upgraders/system.js
type: application/javascript
module-type: upgrader
Upgrader module that suppresses certain system tiddlers that shouldn't be imported
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var DONT_IMPORT_LIST = ["$:/StoryList","$:/HistoryList"];
exports.upgrade = function(wiki,titles,tiddlers) {
var self = this,
messages = {};
// Check for tiddlers on our list
$tw.utils.each(titles,function(title) {
if(DONT_IMPORT_LIST.indexOf(title) !== -1) {
tiddlers[title] = Object.create(null);
messages[title] = $tw.language.getString("Import/Upgrader/System/Suppressed");
}
});
return messages;
};
})();

View File

@ -12,6 +12,8 @@ Navigator widget
/*global $tw: false */
"use strict";
var IMPORT_TITLE = "$:/Import";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var NavigatorWidget = function(parseTreeNode,options) {
@ -26,7 +28,8 @@ var NavigatorWidget = function(parseTreeNode,options) {
{type: "tw-close-all-tiddlers", handler: "handleCloseAllTiddlersEvent"},
{type: "tw-close-other-tiddlers", handler: "handleCloseOtherTiddlersEvent"},
{type: "tw-new-tiddler", handler: "handleNewTiddlerEvent"},
{type: "tw-import-tiddlers", handler: "handleImportTiddlersEvent"}
{type: "tw-import-tiddlers", handler: "handleImportTiddlersEvent"},
{type: "tw-perform-import", handler: "handlePerformImportEvent"}
]);
};
@ -402,7 +405,7 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) {
return false;
};
// Import JSON tiddlers
// Import JSON tiddlers into a pending import tiddler
NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
var self = this;
// Get the tiddlers
@ -411,54 +414,74 @@ NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) {
tiddlers = JSON.parse(event.param);
} catch(e) {
}
// Get the current $:/Import tiddler
var importTiddler = this.wiki.getTiddler(IMPORT_TITLE) || {fields: {}},
importData = this.wiki.getTiddlerData(IMPORT_TITLE,{}),
newFields = new Object({
title: IMPORT_TITLE,
type: "application/json",
"plugin-type": "import"
}),
incomingTiddlers = [];
// Process each tiddler
var importedTiddlers = [];
importData.tiddlers = importData.tiddlers || {};
$tw.utils.each(tiddlers,function(tiddlerFields) {
var title = tiddlerFields.title;
// Add it to the store
var imported = self.wiki.importTiddler(new $tw.Tiddler(
self.wiki.getCreationFields(),
self.wiki.getModificationFields(),
tiddlerFields
));
if(imported) {
importedTiddlers.push(title);
if(title) {
incomingTiddlers.push(title);
importData.tiddlers[title] = tiddlerFields;
}
});
// Give the active upgrader modules a chance to process the incoming tiddlers
var messages = this.wiki.invokeUpgraders(incomingTiddlers,importData.tiddlers);
$tw.utils.each(messages,function(message,title) {
newFields["message-" + title] = message;
});
// Deselect any suppressed tiddlers
$tw.utils.each(importData.tiddlers,function(tiddler,title) {
if($tw.utils.count(tiddler) === 0) {
newFields["selection-" + title] = "unchecked";
}
});
// Save the $:/Import tiddler
newFields.text = JSON.stringify(importData,null,$tw.config.preferences.jsonSpaces);
this.wiki.addTiddler(new $tw.Tiddler(importTiddler,newFields));
// Get the story and history details
var storyList = this.getStoryList(),
history = [];
// Create the import report tiddler
if(importedTiddlers.length === 0) {
return false;
}
var title;
if(importedTiddlers.length > 1) {
title = this.wiki.generateNewTitle("$:/temp/ImportReport");
var tiddlerFields = {
title: title,
text: "# [[" + importedTiddlers.join("]]\n# [[") + "]]\n"
};
this.wiki.addTiddler(new $tw.Tiddler(
self.wiki.getCreationFields(),
tiddlerFields,
self.wiki.getModificationFields()
));
} else {
title = importedTiddlers[0];
}
// Add it to the story
if(storyList.indexOf(title) === -1) {
storyList.unshift(title);
if(storyList.indexOf(IMPORT_TITLE) === -1) {
storyList.unshift(IMPORT_TITLE);
}
// And to history
history.push(title);
history.push(IMPORT_TITLE);
// Save the updated story and history
this.saveStoryList(storyList);
this.addToHistory(history);
return false;
};
//
NavigatorWidget.prototype.handlePerformImportEvent = function(event) {
var self = this,
importTiddler = this.wiki.getTiddler(event.param) || {fields: {}},
importData = this.wiki.getTiddlerData(event.param,{tiddlers: {}}),
importReport = [];
// Add the tiddlers to the store
importReport.push("The following tiddlers were imported:\n");
$tw.utils.each(importData.tiddlers,function(tiddlerFields) {
var title = tiddlerFields.title;
if(title && importTiddler.fields["selection-" + title] !== "unchecked") {
self.wiki.addTiddler(new $tw.Tiddler(tiddlerFields));
importReport.push("# [[" + tiddlerFields.title + "]]");
}
});
// Replace the $:/Import tiddler with an import report
this.wiki.addTiddler(new $tw.Tiddler({title: IMPORT_TITLE, text: importReport.join("\n")}));
// Navigate to the $:/Import tiddler
this.addToHistory([IMPORT_TITLE]);
};
exports.navigator = NavigatorWidget;
})();

View File

@ -1125,4 +1125,31 @@ exports.addToHistory = function(title,fromPageRect,historyTitle) {
this.setTiddlerData(historyTitle,historyList,{"current-tiddler": titles[titles.length-1]});
};
/*
Invoke the available upgrader modules
titles: array of tiddler titles to be processed
tiddlers: hashmap by title of tiddler fields of pending import tiddlers. These can be modified by the upgraders. An entry with no fields indicates a tiddler that was pending import has been suppressed. When entries are added to the pending import the tiddlers hashmap may have entries that are not present in the titles array
Returns a hashmap of messages keyed by tiddler title.
*/
exports.invokeUpgraders = function(titles,tiddlers) {
// Collect up the available upgrader modules
var self = this;
if(!this.upgraderModules) {
this.upgraderModules = [];
$tw.modules.forEachModuleOfType("upgrader",function(title,module) {
if(module.upgrade) {
self.upgraderModules.push(module);
}
});
}
// Invoke each upgrader in turn
var messages = {};
for(var t=0; t<this.upgraderModules.length; t++) {
var upgrader = this.upgraderModules[t],
upgraderMessages = upgrader.upgrade(this,titles,tiddlers);
$tw.utils.extend(messages,upgraderMessages);
}
return messages;
};
})();

View File

@ -3,10 +3,20 @@ tags: $:/tags/ViewTemplate
<div class="body">
<$list filter="[all[current]has[plugin-type]] -[all[current]field:plugin-type[import]]">
This tiddler is a plugin
</$list>
<$list filter="[all[current]!has[plugin-type]]">
<$transclude>
<$transclude tiddler="$:/language/MissingTiddler/Hint"/>
</$transclude>
</$list>
</div>

View File

@ -0,0 +1,53 @@
title: $:/core/ui/ViewTemplate/import
tags: $:/tags/ViewTemplate
\define lingo-base() $:/language/Import/
\define messageField()
message-$(currentTiddler)$
\end
\define selectionField()
selection-$(currentTiddler)$
\end
<$list filter="[all[current]field:plugin-type[import]]">
<div class="tw-import">
<<lingo Listing/Hint>>
<table>
<tbody>
<tr>
<th>
<<lingo Listing/Select/Caption>>
</th>
<th>
<<lingo Listing/Title/Caption>>
</th>
<th>
<<lingo Listing/Status/Caption>>
</th>
</tr>
<$list filter="[all[current]plugintiddlers[]sort[title]]">
<tr>
<td>
<$checkbox tiddler=<<storyTiddler>> field=<<selectionField>> checked="checked" unchecked="unchecked" default="checked"/>
</td>
<td>
<$link to={{!!title}}>
<$view field="title"/>
</$link>
</td>
<td>
<$view tiddler=<<storyTiddler>> field=<<messageField>>/>
</td>
</tr>
</$list>
</tbody>
</table>
<$button message="tw-delete-tiddler" param=<<currentTiddler>>><<lingo Listing/Cancel/Caption>></$button>
<$button message="tw-perform-import" param=<<currentTiddler>>><<lingo Listing/Import/Caption>></$button>
</div>
</$list>

View File

@ -9,6 +9,9 @@
"build": {
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"upgrade": [
"--makelibrary",
"--rendertiddler","$:/core/save/all","upgrade.html","text/plain"],
"externalimages": [
"--savetiddlers","[is[image]]","images",
"--setfield","[is[image]]","_canonical_uri","$:/core/templates/canonical-uri-external-image","text/plain",

View File

@ -0,0 +1,37 @@
title: UpgradeMechanism
modified: 20140711090154150
created: 20140711090154150
tags: mechanism
# Open upgrade.html
# Includes a data tiddler called `$:/UpgradeLibrary` that contains the latest compatible versions of all plugins in the library
# Drag in old wiki file
# Place tiddlers into a data tiddler `$:/Import` that is typed as a "pending import"
# Kick off import processing for each tiddler
## Give each "upgrader" module a chance to inspect the incoming tiddlers
## Upgrader modules can trigger actions for each tiddler:
##* Display a warning message
##* Don't import
##* Replace with another tiddler from the upgrade library
##* Disable incompatible plugins
# Display the newly created pending import tiddler through a new view template segment
## Displays the payload tiddlers as a list of titles and checkboxes, with a dropdown showing the full details of the tiddler
## Perhaps we also suppress the usual JSON display for data tiddlers behind a reveal widget
# The user can adjust the selection checkboxes
# Clicking "done" performs the following actions:
## Unpack the selected tiddlers from the pending import tiddler
## Delete the pending import tiddler
## Delete the upgrade library tiddler
##> Or we could just exclude those tiddlers from the subsequent save operation
! ToDo
* Add upgrade plugin containing instructions and one-shot UI
* Grouping plugins
* Incoming tiddler preview
* Better display text for plugins
* Suppressing $:/UpgradeLibrary and upgrade plugin from save
* Finish UpgradeMechanism docs
* ~~Checkboxes~~
* ~~Visual difference for suppressed tiddlers~~
* ~~Translation~~