diff --git a/core/modules/saver-handler.js b/core/modules/saver-handler.js new file mode 100644 index 000000000..9d5f9a973 --- /dev/null +++ b/core/modules/saver-handler.js @@ -0,0 +1,170 @@ +/*\ +title: $:/core/modules/saver-handler.js +type: application/javascript +module-type: global + +The saver handler tracks changes to the store and handles saving the entire wiki via saver modules. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Instantiate the saver handler with the following options: +wiki: wiki to be synced +*/ +function SaverHandler(options) { + var self = this; + this.wiki = options.wiki; + // Make a logger + this.logger = new $tw.utils.Logger("saver-handler"); + // Initialise our savers + if($tw.browser) { + this.initSavers(); + } + // 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(); + }); + // 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 + $tw.rootWidget.addEventListener("tw-save-wiki",function(event) { + self.saveWiki({ + template: event.param, + downloadType: "text/plain" + }); + }); + $tw.rootWidget.addEventListener("tw-auto-save-wiki",function(event) { + self.saveWiki({ + method: "autosave", + template: event.param, + downloadType: "text/plain" + }); + }); + $tw.rootWidget.addEventListener("tw-download-file",function(event) { + self.saveWiki({ + method: "download", + template: event.param, + downloadType: "text/plain" + }); + }); + } +} + +SaverHandler.prototype.titleSyncFilter = "$:/config/SyncFilter"; +SaverHandler.prototype.titleAutoSave = "$:/config/AutoSave"; +SaverHandler.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done"; + +/* +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; + } + } + }); +}; + +/* +Save the wiki contents. Options are: + method: "save" 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 { + $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); + // Clear the task queue if we're saving (rather than downloading) + if(method !== "download") { + this.numTasksInQueue = 0; + this.updateDirtyStatus(); + } + 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; +}; + +/* +Update the document body with the class "tw-dirty" if the wiki has unsaved/unsynced changes +*/ +SaverHandler.prototype.updateDirtyStatus = function() { + if($tw.browser) { + $tw.utils.toggleClass(document.body,"tw-dirty",this.isDirty()); + } +}; + +exports.SaverHandler = SaverHandler; + +})(); diff --git a/core/modules/startup/startup.js b/core/modules/startup/startup.js index b60e1fcb4..67c3415c1 100755 --- a/core/modules/startup/startup.js +++ b/core/modules/startup/startup.js @@ -69,9 +69,11 @@ exports.startup = function() { $tw.syncadaptor = new module.adaptorClass({wiki: $tw.wiki}); } }); - // Set up the syncer object + // Set up the syncer object if we've got a syncadaptor, otherwise setup the saverhandler if($tw.syncadaptor) { $tw.syncer = new $tw.Syncer({wiki: $tw.wiki, syncadaptor: $tw.syncadaptor}); + } else { + $tw.saverHandler = new $tw.SaverHandler({wiki: $tw.wiki}); } // Host-specific startup if($tw.browser) { diff --git a/core/modules/syncer.js b/core/modules/syncer.js index 66e84b8dc..b92d7a478 100644 --- a/core/modules/syncer.js +++ b/core/modules/syncer.js @@ -23,10 +23,6 @@ function Syncer(options) { this.syncadaptor = options.syncadaptor // Make a logger this.logger = new $tw.utils.Logger("syncer" + ($tw.browser ? "-browser" : "") + ($tw.node ? "-server" : "")); - // Initialise our savers - if($tw.browser) { - this.initSavers(); - } // Compile the dirty tiddler filter this.filterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(this.titleSyncFilter)); // Record information for known tiddlers @@ -61,34 +57,11 @@ function Syncer(options) { $tw.rootWidget.addEventListener("tw-server-refresh",function() { self.handleRefreshEvent(); }); - // Install the save action handlers - $tw.rootWidget.addEventListener("tw-save-wiki",function(event) { - self.saveWiki({ - template: event.param, - downloadType: "text/plain" - }); - }); - $tw.rootWidget.addEventListener("tw-auto-save-wiki",function(event) { - self.saveWiki({ - method: "autosave", - template: event.param, - downloadType: "text/plain" - }); - }); - $tw.rootWidget.addEventListener("tw-download-file",function(event) { - self.saveWiki({ - method: "download", - template: event.param, - downloadType: "text/plain" - }); - }); } // Listen out for lazyLoad events - if(this.syncadaptor) { - this.wiki.addEventListener("lazyLoad",function(title) { - self.handleLazyLoadEvent(title); - }); - } + this.wiki.addEventListener("lazyLoad",function(title) { + self.handleLazyLoadEvent(title); + }); // Get the login status this.getStatus(function (err,isLoggedIn) { // Do a sync from the server @@ -102,7 +75,6 @@ Constants Syncer.prototype.titleIsLoggedIn = "$:/status/IsLoggedIn"; Syncer.prototype.titleUserName = "$:/status/UserName"; Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter"; -Syncer.prototype.titleAutoSave = "$:/config/AutoSave"; Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done"; Syncer.prototype.taskTimerInterval = 1 * 1000; // Interval for sync timer Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s... @@ -129,79 +101,6 @@ Syncer.prototype.readTiddlerInfo = function() { }); }; -/* -Select the appropriate saver modules and set them up -*/ -Syncer.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; - } - } - }); -}; - -/* -Save the wiki contents. Options are: - method: "save" or "download" - template: the tiddler containing the template to save - downloadType: the content type for the saved file -*/ -Syncer.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 { - $tw.notifier.display(self.titleSavedNotification); - if(options.callback) { - options.callback(); - } - } - }; - // Ignore autosave if we've got a syncadaptor or autosave is disabled - if(method === "autosave") { - if(this.syncadaptor || 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); - // Clear the task queue if we're saving (rather than downloading) - if(method !== "download") { - this.readTiddlerInfo(); - this.taskQueue = {}; - this.updateDirtyStatus(); - } - return true; - } - } - return false; -}; - /* Checks whether the wiki is dirty (ie the window shouldn't be closed) */ @@ -453,9 +352,7 @@ Syncer.prototype.enqueueSyncTask = function(task) { this.updateDirtyStatus(); } // Process the queue - if(this.syncadaptor) { - $tw.utils.nextTick(function() {self.processTaskQueue.call(self);}); - } + $tw.utils.nextTick(function() {self.processTaskQueue.call(self);}); }; /*