/*\ 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 dirtyTracking: true if dirty tracking should be performed */ function SaverHandler(options) { var self = this; this.wiki = options.wiki; this.dirtyTracking = options.dirtyTracking; this.preloadDirty = options.preloadDirty || []; 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 changes that have not yet been saved var filteredChanges = self.filterFn.call(self.wiki,function(iterator) { $tw.utils.each(self.preloadDirty,function(title) { var tiddler = self.wiki.getTiddler(title); iterator(tiddler,title); }); }); this.numChanges = filteredChanges.length; // Listen out for changes to tiddlers this.wiki.addEventListener("change",function(changes) { // Filter the changes so that we only count changes to tiddlers that we care about var filteredChanges = self.filterFn.call(self.wiki,function(iterator) { $tw.utils.each(changes,function(change,title) { var tiddler = self.wiki.getTiddler(title); iterator(tiddler,title); }); }); // Adjust the number of changes self.numChanges += 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.numChanges > 0) { self.saveWiki({ method: "autosave", downloadType: "text/plain" }); } self.pendingAutoSave = false; } }); // Listen for the autosave event $tw.rootWidget.addEventListener("tm-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.numChanges > 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 $tw.addUnloadTask(function(event) { var confirmationMessage; 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", variables: event.paramObject }); }); $tw.rootWidget.addEventListener("tm-download-file",function(event) { self.saveWiki({ method: "download", template: event.param, downloadType: "text/plain", variables: event.paramObject }); }); } } SaverHandler.prototype.titleSyncFilter = "$:/config/SaverFilter"; 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", "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"; // Ignore autosave if disabled if(method === "autosave" && ($tw.config.disableAutoSave || this.wiki.getTiddlerText(this.titleAutoSave,"yes") !== "yes")) { return false; } var variables = options.variables || {}, template = (options.template || this.wiki.getTiddlerText("$:/config/SaveWikiButton/Template","$:/core/save/all")).trim(), downloadType = options.downloadType || "text/plain", text = this.wiki.renderTiddler(downloadType,template,options), callback = function(err) { if(err) { alert($tw.language.getString("Error/WhileSaving") + ":\n\n" + err); } else { // Clear the task queue if we're saving (rather than downloading) if(method !== "download") { self.numChanges = 0; self.updateDirtyStatus(); } $tw.notifier.display(self.titleSavedNotification); if(options.callback) { options.callback(); } } }; // 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,{variables: {filename: variables.filename}})) { 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.numChanges > 0; }; /* Update the document body with the class "tc-dirty" if the wiki has unsaved/unsynced changes */ SaverHandler.prototype.updateDirtyStatus = function() { var self = this; if($tw.browser) { $tw.utils.toggleClass(document.body,"tc-dirty",this.isDirty()); $tw.utils.each($tw.windows,function(win) { $tw.utils.toggleClass(win.document.body,"tc-dirty",self.isDirty()); }); } }; exports.SaverHandler = SaverHandler; })();