1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-08-07 22:33:50 +00:00

Fix for autosave

Re-introduces the “tw-auto-save-wiki” message. The previous approach of
automatically triggering autosave whenever a tiddler changed meant that
changing configuration changes in control panel was triggering an
autosave. Using the explicit message gives us better control of the
situations in which we’ll autosave.

Now we solve the earlier problem of there being outstanding tiddler
change events at the time that we process the “tw-auto-save-wiki” by
deferring the autosave until the outstanding change event comes in.
This commit is contained in:
Jermolene 2014-08-29 09:58:30 +01:00
parent 48312272ad
commit 2952afe7af
5 changed files with 155 additions and 121 deletions

View File

@ -18,64 +18,86 @@ wiki: wiki to be synced
dirtyTracking: true if dirty tracking should be performed dirtyTracking: true if dirty tracking should be performed
*/ */
function SaverHandler(options) { function SaverHandler(options) {
var self = this; var self = this;
this.wiki = options.wiki; this.wiki = options.wiki;
this.dirtyTracking = options.dirtyTracking; this.dirtyTracking = options.dirtyTracking;
// Make a logger this.pendingAutoSave = false;
this.logger = new $tw.utils.Logger("saver-handler"); // Make a logger
// Initialise our savers this.logger = new $tw.utils.Logger("saver-handler");
if($tw.browser) { // Initialise our savers
this.initSavers(); if($tw.browser) {
} this.initSavers();
// Only do dirty tracking if required }
if($tw.browser && this.dirtyTracking) { // Only do dirty tracking if required
// Compile the dirty tiddler filter if($tw.browser && this.dirtyTracking) {
this.filterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(this.titleSyncFilter)); // Compile the dirty tiddler filter
// Count of tiddlers that have been changed but not yet saved this.filterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(this.titleSyncFilter));
this.numTasksInQueue = 0; // Count of tiddlers that have been changed but not yet saved
// Listen out for changes to tiddlers this.numTasksInQueue = 0;
this.wiki.addEventListener("change",function(changes) { // Listen out for changes to tiddlers
var filteredChanges = self.filterFn.call(self.wiki,function(callback) { this.wiki.addEventListener("change",function(changes) {
$tw.utils.each(changes,function(change,title) { var filteredChanges = self.filterFn.call(self.wiki,function(callback) {
var tiddler = self.wiki.getTiddler(title); $tw.utils.each(changes,function(change,title) {
callback(tiddler,title); var tiddler = self.wiki.getTiddler(title);
}); callback(tiddler,title);
}); });
self.numTasksInQueue += filteredChanges.length; });
self.updateDirtyStatus(); self.numTasksInQueue += filteredChanges.length;
if(self.numTasksInQueue > 0) { self.updateDirtyStatus();
self.saveWiki({method: "autosave"}); // Do any autosave if one is pending and there's no more change events
} if(self.pendingAutoSave && self.wiki.getSizeOfTiddlerEventQueue() === 0) {
}); // Check if we're dirty
// Browser event handlers if(self.numTasksInQueue > 0) {
if($tw.browser) { self.saveWiki({
// Set up our beforeunload handler method: "autosave",
window.addEventListener("beforeunload",function(event) { downloadType: "text/plain"
var confirmationMessage = undefined; });
if(self.isDirty()) { }
confirmationMessage = $tw.language.getString("UnsavedChangesWarning"); self.pendingAutoSave = false;
event.returnValue = confirmationMessage; // Gecko }
} });
return confirmationMessage; // Listen for the autosave event
}); $tw.rootWidget.addEventListener("tw-auto-save-wiki",function(event) {
} // Do the autosave unless there are outstanding tiddler change events
} if(self.wiki.getSizeOfTiddlerEventQueue() === 0) {
// Install the save action handlers // Check if we're dirty
if($tw.browser) { if(self.numTasksInQueue > 0) {
$tw.rootWidget.addEventListener("tm-save-wiki",function(event) { self.saveWiki({
self.saveWiki({ method: "autosave",
template: event.param, downloadType: "text/plain"
downloadType: "text/plain" });
}); }
}); } else {
$tw.rootWidget.addEventListener("tm-download-file",function(event) { // Otherwise put ourselves in the "pending autosave" state and wait for the change event before we do the autosave
self.saveWiki({ self.pendingAutoSave = true;
method: "download", }
template: event.param, });
downloadType: "text/plain" // Set up our beforeunload handler
}); window.addEventListener("beforeunload",function(event) {
}); var confirmationMessage = undefined;
} if(self.isDirty()) {
confirmationMessage = $tw.language.getString("UnsavedChangesWarning");
event.returnValue = confirmationMessage; // Gecko
}
return confirmationMessage;
});
}
// Install the save action handlers
if($tw.browser) {
$tw.rootWidget.addEventListener("tm-save-wiki",function(event) {
self.saveWiki({
template: event.param,
downloadType: "text/plain"
});
});
$tw.rootWidget.addEventListener("tm-download-file",function(event) {
self.saveWiki({
method: "download",
template: event.param,
downloadType: "text/plain"
});
});
}
} }
SaverHandler.prototype.titleSyncFilter = "$:/config/SaverFilter"; SaverHandler.prototype.titleSyncFilter = "$:/config/SaverFilter";
@ -86,86 +108,86 @@ SaverHandler.prototype.titleSavedNotification = "$:/language/Notifications/Save/
Select the appropriate saver modules and set them up Select the appropriate saver modules and set them up
*/ */
SaverHandler.prototype.initSavers = function(moduleType) { SaverHandler.prototype.initSavers = function(moduleType) {
moduleType = moduleType || "saver"; moduleType = moduleType || "saver";
// Instantiate the available savers // Instantiate the available savers
this.savers = []; this.savers = [];
var self = this; var self = this;
$tw.modules.forEachModuleOfType(moduleType,function(title,module) { $tw.modules.forEachModuleOfType(moduleType,function(title,module) {
if(module.canSave(self)) { if(module.canSave(self)) {
self.savers.push(module.create(self.wiki)); self.savers.push(module.create(self.wiki));
} }
}); });
// Sort the savers into priority order // Sort the savers into priority order
this.savers.sort(function(a,b) { this.savers.sort(function(a,b) {
if(a.info.priority < b.info.priority) { if(a.info.priority < b.info.priority) {
return -1; return -1;
} else { } else {
if(a.info.priority > b.info.priority) { if(a.info.priority > b.info.priority) {
return +1; return +1;
} else { } else {
return 0; return 0;
} }
} }
}); });
}; };
/* /*
Save the wiki contents. Options are: Save the wiki contents. Options are:
method: "save", "autosave" or "download" method: "save", "autosave" or "download"
template: the tiddler containing the template to save template: the tiddler containing the template to save
downloadType: the content type for the saved file downloadType: the content type for the saved file
*/ */
SaverHandler.prototype.saveWiki = function(options) { SaverHandler.prototype.saveWiki = function(options) {
options = options || {}; options = options || {};
var self = this, var self = this,
method = options.method || "save", method = options.method || "save",
template = options.template || "$:/core/save/all", template = options.template || "$:/core/save/all",
downloadType = options.downloadType || "text/plain", downloadType = options.downloadType || "text/plain",
text = this.wiki.renderTiddler(downloadType,template), text = this.wiki.renderTiddler(downloadType,template),
callback = function(err) { callback = function(err) {
if(err) { if(err) {
alert("Error while saving:\n\n" + err); alert("Error while saving:\n\n" + err);
} else { } else {
// Clear the task queue if we're saving (rather than downloading) // Clear the task queue if we're saving (rather than downloading)
if(method !== "download") { if(method !== "download") {
self.numTasksInQueue = 0; self.numTasksInQueue = 0;
self.updateDirtyStatus(); self.updateDirtyStatus();
} }
$tw.notifier.display(self.titleSavedNotification); $tw.notifier.display(self.titleSavedNotification);
if(options.callback) { if(options.callback) {
options.callback(); options.callback();
} }
} }
}; };
// Ignore autosave if disabled // Ignore autosave if disabled
if(method === "autosave" && this.wiki.getTiddlerText(this.titleAutoSave,"yes") !== "yes") { if(method === "autosave" && this.wiki.getTiddlerText(this.titleAutoSave,"yes") !== "yes") {
return false; return false;
} }
// Call the highest priority saver that supports this method // Call the highest priority saver that supports this method
for(var t=this.savers.length-1; t>=0; t--) { for(var t=this.savers.length-1; t>=0; t--) {
var saver = this.savers[t]; var saver = this.savers[t];
if(saver.info.capabilities.indexOf(method) !== -1 && saver.save(text,method,callback)) { if(saver.info.capabilities.indexOf(method) !== -1 && saver.save(text,method,callback)) {
this.logger.log("Saving wiki with method",method,"through saver",saver.info.name); this.logger.log("Saving wiki with method",method,"through saver",saver.info.name);
return true; return true;
} }
} }
return false; return false;
}; };
/* /*
Checks whether the wiki is dirty (ie the window shouldn't be closed) Checks whether the wiki is dirty (ie the window shouldn't be closed)
*/ */
SaverHandler.prototype.isDirty = function() { SaverHandler.prototype.isDirty = function() {
return this.numTasksInQueue > 0; return this.numTasksInQueue > 0;
}; };
/* /*
Update the document body with the class "tc-dirty" if the wiki has unsaved/unsynced changes Update the document body with the class "tc-dirty" if the wiki has unsaved/unsynced changes
*/ */
SaverHandler.prototype.updateDirtyStatus = function() { SaverHandler.prototype.updateDirtyStatus = function() {
if($tw.browser) { if($tw.browser) {
$tw.utils.toggleClass(document.body,"tc-dirty",this.isDirty()); $tw.utils.toggleClass(document.body,"tc-dirty",this.isDirty());
} }
}; };
exports.SaverHandler = SaverHandler; exports.SaverHandler = SaverHandler;

View File

@ -71,6 +71,8 @@ exports.repackPlugin = function(title,additionalTiddlers,excludeTiddlers) {
$tw.wiki.deleteTiddler(title); $tw.wiki.deleteTiddler(title);
} }
}); });
// Trigger an autosave
$tw.rootWidget.dispatchEvent({type: "tw-auto-save-wiki"});
// Return a heartwarming confirmation // Return a heartwarming confirmation
return "Plugin " + title + " successfully saved"; return "Plugin " + title + " successfully saved";
} }

View File

@ -243,6 +243,8 @@ NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) {
// Remove the closed tiddler from the story // Remove the closed tiddler from the story
this.removeTitleFromStory(storyList,title); this.removeTitleFromStory(storyList,title);
this.saveStoryList(storyList); this.saveStoryList(storyList);
// Trigger an autosave
$tw.rootWidget.dispatchEvent({type: "tw-auto-save-wiki"});
return false; return false;
}; };
@ -331,6 +333,8 @@ NavigatorWidget.prototype.handleSaveTiddlerEvent = function(event) {
if(draftTitle !== this.storyTitle) { if(draftTitle !== this.storyTitle) {
this.saveStoryList(storyList); this.saveStoryList(storyList);
} }
// Trigger an autosave
$tw.rootWidget.dispatchEvent({type: "tw-auto-save-wiki"});
} }
} }
} }
@ -478,6 +482,8 @@ NavigatorWidget.prototype.handlePerformImportEvent = function(event) {
})); }));
// Navigate to the $:/Import tiddler // Navigate to the $:/Import tiddler
this.addToHistory([IMPORT_TITLE]); this.addToHistory([IMPORT_TITLE]);
// Trigger an autosave
$tw.rootWidget.dispatchEvent({type: "tw-auto-save-wiki"});
}; };
exports.navigator = NavigatorWidget; exports.navigator = NavigatorWidget;

View File

@ -152,6 +152,10 @@ exports.enqueueTiddlerEvent = function(title,isDeleted) {
} }
}; };
exports.getSizeOfTiddlerEventQueue = function() {
return $tw.utils.count(this.changedTiddlers);
};
exports.clearTiddlerEventQueue = function() { exports.clearTiddlerEventQueue = function() {
this.changedTiddlers = Object.create(null); this.changedTiddlers = Object.create(null);
this.changeCount = Object.create(null) this.changeCount = Object.create(null)

View File

@ -1,3 +1,3 @@
title: $:/config/SaverFilter title: $:/config/SaverFilter
[!is[shadow]] -[[$:/HistoryList]] -[[$:/StoryList]] -[[$:/Import]] -[[$:/isEncrypted]] -[[$:/UploadName]] -[prefix[$:/status]] -[prefix[$:/state]] -[prefix[$:/temp]] -[has[draft.of]] [all[]] -[[$:/HistoryList]] -[[$:/StoryList]] -[[$:/Import]] -[[$:/isEncrypted]] -[[$:/UploadName]] -[prefix[$:/state]] -[prefix[$:/temp]] -[has[draft.of]]