diff --git a/core/modules/saver-handler.js b/core/modules/saver-handler.js index 89b10c872..b03ce976c 100644 --- a/core/modules/saver-handler.js +++ b/core/modules/saver-handler.js @@ -18,64 +18,86 @@ wiki: wiki to be synced dirtyTracking: true if dirty tracking should be performed */ function SaverHandler(options) { - var self = this; - this.wiki = options.wiki; - this.dirtyTracking = options.dirtyTracking; - // Make a logger - this.logger = new $tw.utils.Logger("saver-handler"); - // Initialise our savers - if($tw.browser) { - this.initSavers(); - } - // Only do dirty tracking if required - if($tw.browser && this.dirtyTracking) { - // Compile the dirty tiddler filter - this.filterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(this.titleSyncFilter)); - // Count of tiddlers that have been changed but not yet saved - this.numTasksInQueue = 0; - // Listen out for changes to tiddlers - this.wiki.addEventListener("change",function(changes) { - var filteredChanges = self.filterFn.call(self.wiki,function(callback) { - $tw.utils.each(changes,function(change,title) { - var tiddler = self.wiki.getTiddler(title); - callback(tiddler,title); - }); - }); - self.numTasksInQueue += filteredChanges.length; - self.updateDirtyStatus(); - if(self.numTasksInQueue > 0) { - self.saveWiki({method: "autosave"}); - } - }); - // Browser event handlers - if($tw.browser) { - // 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" - }); - }); - } + var self = this; + this.wiki = options.wiki; + this.dirtyTracking = options.dirtyTracking; + this.pendingAutoSave = false; + // Make a logger + this.logger = new $tw.utils.Logger("saver-handler"); + // Initialise our savers + if($tw.browser) { + this.initSavers(); + } + // Only do dirty tracking if required + if($tw.browser && this.dirtyTracking) { + // Compile the dirty tiddler filter + this.filterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(this.titleSyncFilter)); + // Count of tiddlers that have been changed but not yet saved + this.numTasksInQueue = 0; + // Listen out for changes to tiddlers + this.wiki.addEventListener("change",function(changes) { + var filteredChanges = self.filterFn.call(self.wiki,function(callback) { + $tw.utils.each(changes,function(change,title) { + var tiddler = self.wiki.getTiddler(title); + callback(tiddler,title); + }); + }); + self.numTasksInQueue += filteredChanges.length; + self.updateDirtyStatus(); + // 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 + if(self.numTasksInQueue > 0) { + self.saveWiki({ + method: "autosave", + downloadType: "text/plain" + }); + } + self.pendingAutoSave = false; + } + }); + // 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) { + // Check if we're dirty + if(self.numTasksInQueue > 0) { + self.saveWiki({ + method: "autosave", + downloadType: "text/plain" + }); + } + } else { + // Otherwise put ourselves in the "pending autosave" state and wait for the change event before we do the autosave + self.pendingAutoSave = true; + } + }); + // 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"; @@ -86,86 +108,86 @@ SaverHandler.prototype.titleSavedNotification = "$:/language/Notifications/Save/ Select the appropriate saver modules and set them up */ SaverHandler.prototype.initSavers = function(moduleType) { - moduleType = moduleType || "saver"; - // Instantiate the available savers - this.savers = []; - var self = this; - $tw.modules.forEachModuleOfType(moduleType,function(title,module) { - if(module.canSave(self)) { - self.savers.push(module.create(self.wiki)); - } - }); - // Sort the savers into priority order - this.savers.sort(function(a,b) { - if(a.info.priority < b.info.priority) { - return -1; - } else { - if(a.info.priority > b.info.priority) { - return +1; - } else { - return 0; - } - } - }); + moduleType = moduleType || "saver"; + // Instantiate the available savers + this.savers = []; + var self = this; + $tw.modules.forEachModuleOfType(moduleType,function(title,module) { + if(module.canSave(self)) { + self.savers.push(module.create(self.wiki)); + } + }); + // Sort the savers into priority order + this.savers.sort(function(a,b) { + if(a.info.priority < b.info.priority) { + return -1; + } else { + if(a.info.priority > b.info.priority) { + return +1; + } else { + return 0; + } + } + }); }; /* Save the wiki contents. Options are: - method: "save", "autosave" or "download" - template: the tiddler containing the template to save - downloadType: the content type for the saved file + method: "save", "autosave" or "download" + template: the tiddler containing the template to save + downloadType: the content type for the saved file */ SaverHandler.prototype.saveWiki = function(options) { - options = options || {}; - var self = this, - method = options.method || "save", - template = options.template || "$:/core/save/all", - downloadType = options.downloadType || "text/plain", - text = this.wiki.renderTiddler(downloadType,template), - callback = function(err) { - if(err) { - alert("Error while saving:\n\n" + err); - } else { - // Clear the task queue if we're saving (rather than downloading) - if(method !== "download") { - self.numTasksInQueue = 0; - self.updateDirtyStatus(); - } - $tw.notifier.display(self.titleSavedNotification); - if(options.callback) { - options.callback(); - } - } - }; - // Ignore autosave if disabled - if(method === "autosave" && this.wiki.getTiddlerText(this.titleAutoSave,"yes") !== "yes") { - return false; - } - // Call the highest priority saver that supports this method - for(var t=this.savers.length-1; t>=0; t--) { - var saver = this.savers[t]; - 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); - return true; - } - } - return false; + options = options || {}; + var self = this, + method = options.method || "save", + template = options.template || "$:/core/save/all", + downloadType = options.downloadType || "text/plain", + text = this.wiki.renderTiddler(downloadType,template), + callback = function(err) { + if(err) { + alert("Error while saving:\n\n" + err); + } else { + // Clear the task queue if we're saving (rather than downloading) + if(method !== "download") { + self.numTasksInQueue = 0; + self.updateDirtyStatus(); + } + $tw.notifier.display(self.titleSavedNotification); + if(options.callback) { + options.callback(); + } + } + }; + // Ignore autosave if disabled + if(method === "autosave" && this.wiki.getTiddlerText(this.titleAutoSave,"yes") !== "yes") { + return false; + } + // Call the highest priority saver that supports this method + for(var t=this.savers.length-1; t>=0; t--) { + var saver = this.savers[t]; + 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); + return true; + } + } + return false; }; /* Checks whether the wiki is dirty (ie the window shouldn't be closed) */ 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 */ SaverHandler.prototype.updateDirtyStatus = function() { - if($tw.browser) { - $tw.utils.toggleClass(document.body,"tc-dirty",this.isDirty()); - } + if($tw.browser) { + $tw.utils.toggleClass(document.body,"tc-dirty",this.isDirty()); + } }; exports.SaverHandler = SaverHandler; diff --git a/core/modules/utils/pluginmaker.js b/core/modules/utils/pluginmaker.js index 5cce747a0..eadef8e43 100644 --- a/core/modules/utils/pluginmaker.js +++ b/core/modules/utils/pluginmaker.js @@ -71,6 +71,8 @@ exports.repackPlugin = function(title,additionalTiddlers,excludeTiddlers) { $tw.wiki.deleteTiddler(title); } }); + // Trigger an autosave + $tw.rootWidget.dispatchEvent({type: "tw-auto-save-wiki"}); // Return a heartwarming confirmation return "Plugin " + title + " successfully saved"; } diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 1ee5a42d3..e8edc321b 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -243,6 +243,8 @@ NavigatorWidget.prototype.handleDeleteTiddlerEvent = function(event) { // Remove the closed tiddler from the story this.removeTitleFromStory(storyList,title); this.saveStoryList(storyList); + // Trigger an autosave + $tw.rootWidget.dispatchEvent({type: "tw-auto-save-wiki"}); return false; }; @@ -331,6 +333,8 @@ NavigatorWidget.prototype.handleSaveTiddlerEvent = function(event) { if(draftTitle !== this.storyTitle) { 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 this.addToHistory([IMPORT_TITLE]); + // Trigger an autosave + $tw.rootWidget.dispatchEvent({type: "tw-auto-save-wiki"}); }; exports.navigator = NavigatorWidget; diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 1cc2c2fb8..f8d3c8323 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -152,6 +152,10 @@ exports.enqueueTiddlerEvent = function(title,isDeleted) { } }; +exports.getSizeOfTiddlerEventQueue = function() { + return $tw.utils.count(this.changedTiddlers); +}; + exports.clearTiddlerEventQueue = function() { this.changedTiddlers = Object.create(null); this.changeCount = Object.create(null) diff --git a/core/wiki/config/SaverFilter.tid b/core/wiki/config/SaverFilter.tid index 4b4fab9c2..b364b9c5c 100644 --- a/core/wiki/config/SaverFilter.tid +++ b/core/wiki/config/SaverFilter.tid @@ -1,3 +1,3 @@ title: $:/config/SaverFilter -[!is[shadow]] -[[$:/HistoryList]] -[[$:/StoryList]] -[[$:/Import]] -[[$:/isEncrypted]] -[[$:/UploadName]] -[prefix[$:/status]] -[prefix[$:/state]] -[prefix[$:/temp]] -[has[draft.of]] \ No newline at end of file +[all[]] -[[$:/HistoryList]] -[[$:/StoryList]] -[[$:/Import]] -[[$:/isEncrypted]] -[[$:/UploadName]] -[prefix[$:/state]] -[prefix[$:/temp]] -[has[draft.of]] \ No newline at end of file