mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-30 05:19:57 +00:00
Add automatic saving and warning on exit with unsaved changes
We re-use some of the existing syncer mechanism. It was already keeping track of changes to tiddlers in the store when working with a tiddler syncadaptor. Now it also tracks changes when there is no syncadaptor, allowing us to provide a warning if there are unsaved changes.
This commit is contained in:
parent
8e8e31fb9f
commit
a01bbd4b9c
@ -56,6 +56,15 @@ exports.startup = function() {
|
|||||||
$tw.wiki.addTiddler({title: storyTitle, text: "", list: story},$tw.wiki.getModificationFields());
|
$tw.wiki.addTiddler({title: storyTitle, text: "", list: story},$tw.wiki.getModificationFields());
|
||||||
// Host-specific startup
|
// Host-specific startup
|
||||||
if($tw.browser) {
|
if($tw.browser) {
|
||||||
|
// Set up our beforeunload handler
|
||||||
|
window.addEventListener("beforeunload",function(event) {
|
||||||
|
var confirmationMessage = null;
|
||||||
|
if($tw.syncer.isDirty()) {
|
||||||
|
confirmationMessage = "You have unsaved changes in TiddlyWiki";
|
||||||
|
event.returnValue = confirmationMessage; // Gecko
|
||||||
|
}
|
||||||
|
return confirmationMessage; // Webkit, Safari, Chrome etc.
|
||||||
|
});
|
||||||
// Install the popup manager
|
// Install the popup manager
|
||||||
$tw.popup = new $tw.utils.Popup({
|
$tw.popup = new $tw.utils.Popup({
|
||||||
rootElement: document.body
|
rootElement: document.body
|
||||||
@ -86,22 +95,21 @@ exports.startup = function() {
|
|||||||
$tw.pageScroller.handleEvent(event);
|
$tw.pageScroller.handleEvent(event);
|
||||||
});
|
});
|
||||||
// Install the save action handler
|
// Install the save action handler
|
||||||
$tw.wiki.initSavers();
|
|
||||||
$tw.rootWidget.addEventListener("tw-save-wiki",function(event) {
|
$tw.rootWidget.addEventListener("tw-save-wiki",function(event) {
|
||||||
$tw.wiki.saveWiki({
|
$tw.syncer.saveWiki({
|
||||||
template: event.param,
|
template: event.param,
|
||||||
downloadType: "text/plain"
|
downloadType: "text/plain"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$tw.rootWidget.addEventListener("tw-auto-save-wiki",function(event) {
|
$tw.rootWidget.addEventListener("tw-auto-save-wiki",function(event) {
|
||||||
$tw.wiki.saveWiki({
|
$tw.syncer.saveWiki({
|
||||||
method: "autosave",
|
method: "autosave",
|
||||||
template: event.param,
|
template: event.param,
|
||||||
downloadType: "text/plain"
|
downloadType: "text/plain"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$tw.rootWidget.addEventListener("tw-download-file",function(event) {
|
$tw.rootWidget.addEventListener("tw-download-file",function(event) {
|
||||||
$tw.wiki.saveWiki({
|
$tw.syncer.saveWiki({
|
||||||
method: "download",
|
method: "download",
|
||||||
template: event.param,
|
template: event.param,
|
||||||
downloadType: "text/plain"
|
downloadType: "text/plain"
|
||||||
|
@ -3,7 +3,7 @@ title: $:/core/modules/syncer.js
|
|||||||
type: application/javascript
|
type: application/javascript
|
||||||
module-type: global
|
module-type: global
|
||||||
|
|
||||||
The syncer transfers content to and from data sources using syncadaptor modules.
|
The syncer tracks changes to the store. If a syncadaptor is used then individual tiddlers are synchronised through it. If there is no syncadaptor then the entire wiki is saved via saver modules.
|
||||||
|
|
||||||
\*/
|
\*/
|
||||||
(function(){
|
(function(){
|
||||||
@ -28,46 +28,16 @@ function Syncer(options) {
|
|||||||
self.syncadaptor = new module.adaptorClass(self);
|
self.syncadaptor = new module.adaptorClass(self);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Only do anything if we've got a syncadaptor
|
// Initialise our savers
|
||||||
if(this.syncadaptor) {
|
if($tw.browser) {
|
||||||
this.init();
|
this.initSavers();
|
||||||
}
|
}
|
||||||
}
|
// Compile the dirty tiddler filter
|
||||||
|
this.filterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(this.titleSyncFilter));
|
||||||
/*
|
|
||||||
Error handling
|
|
||||||
*/
|
|
||||||
Syncer.prototype.showError = function(error) {
|
|
||||||
this.log("Error: " + error);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Constants
|
|
||||||
*/
|
|
||||||
Syncer.prototype.titleIsLoggedIn = "$:/status/IsLoggedIn";
|
|
||||||
Syncer.prototype.titleUserName = "$:/status/UserName";
|
|
||||||
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...
|
|
||||||
Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s
|
|
||||||
Syncer.prototype.pollTimerInterval = 60 * 1000; // Interval for polling for changes from the adaptor
|
|
||||||
|
|
||||||
/*
|
|
||||||
Initialise the syncer
|
|
||||||
*/
|
|
||||||
Syncer.prototype.init = function() {
|
|
||||||
var self = this;
|
|
||||||
// Hashmap by title of {revision:,changeCount:,adaptorInfo:}
|
|
||||||
this.tiddlerInfo = {};
|
|
||||||
// Record information for known tiddlers
|
// Record information for known tiddlers
|
||||||
this.wiki.forEachTiddler({includeSystem: true},function(title,tiddler) {
|
this.readTiddlerInfo();
|
||||||
self.tiddlerInfo[title] = {
|
|
||||||
revision: tiddler.fields["revision"],
|
|
||||||
adaptorInfo: self.syncadaptor.getTiddlerInfo(tiddler),
|
|
||||||
changeCount: self.wiki.getChangeCount(title)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Tasks are {type: "load"/"save"/"delete", title:, queueTime:, lastModificationTime:}
|
// Tasks are {type: "load"/"save"/"delete", title:, queueTime:, lastModificationTime:}
|
||||||
this.taskQueue = {}; // Hashmap of tasks to be performed
|
this.taskQueue = {}; // Hashmap of tasks yet to be performed
|
||||||
this.taskInProgress = {}; // Hash of tasks in progress
|
this.taskInProgress = {}; // Hash of tasks in progress
|
||||||
this.taskTimerId = null; // Timer for task dispatch
|
this.taskTimerId = null; // Timer for task dispatch
|
||||||
this.pollTimerId = null; // Timer for polling server
|
this.pollTimerId = null; // Timer for polling server
|
||||||
@ -76,9 +46,11 @@ Syncer.prototype.init = function() {
|
|||||||
self.syncToServer(changes);
|
self.syncToServer(changes);
|
||||||
});
|
});
|
||||||
// Listen out for lazyLoad events
|
// Listen out for lazyLoad events
|
||||||
this.wiki.addEventListener("lazyLoad",function(title) {
|
if(this.syncadaptor) {
|
||||||
self.handleLazyLoadEvent(title);
|
this.wiki.addEventListener("lazyLoad",function(title) {
|
||||||
});
|
self.handleLazyLoadEvent(title);
|
||||||
|
});
|
||||||
|
}
|
||||||
// Listen out for login/logout/refresh events in the browser
|
// Listen out for login/logout/refresh events in the browser
|
||||||
if($tw.browser) {
|
if($tw.browser) {
|
||||||
document.addEventListener("tw-login",function(event) {
|
document.addEventListener("tw-login",function(event) {
|
||||||
@ -98,6 +70,120 @@ Syncer.prototype.init = function() {
|
|||||||
self.syncFromServer();
|
self.syncFromServer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Error handling
|
||||||
|
*/
|
||||||
|
Syncer.prototype.showError = function(error) {
|
||||||
|
this.log("Error: " + error);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constants
|
||||||
|
*/
|
||||||
|
Syncer.prototype.titleIsLoggedIn = "$:/status/IsLoggedIn";
|
||||||
|
Syncer.prototype.titleUserName = "$:/status/UserName";
|
||||||
|
Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
|
||||||
|
Syncer.prototype.titleAutoSave = "$:/config/AutoSave";
|
||||||
|
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...
|
||||||
|
Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s
|
||||||
|
Syncer.prototype.pollTimerInterval = 60 * 1000; // Interval for polling for changes from the adaptor
|
||||||
|
|
||||||
|
/*
|
||||||
|
Read (or re-read) the latest tiddler info from the store
|
||||||
|
*/
|
||||||
|
Syncer.prototype.readTiddlerInfo = function() {
|
||||||
|
// Hashmap by title of {revision:,changeCount:,adaptorInfo:}
|
||||||
|
this.tiddlerInfo = {};
|
||||||
|
// Record information for known tiddlers
|
||||||
|
var self = this;
|
||||||
|
this.wiki.forEachTiddler({includeSystem: true},function(title,tiddler) {
|
||||||
|
self.tiddlerInfo[title] = {
|
||||||
|
revision: tiddler.fields["revision"],
|
||||||
|
adaptorInfo: self.syncadaptor && self.syncadaptor.getTiddlerInfo(tiddler),
|
||||||
|
changeCount: self.wiki.getChangeCount(title)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 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("$:/messages/Saved");
|
||||||
|
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.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 = {};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Checks whether the wiki is dirty (ie the window shouldn't be closed)
|
||||||
|
*/
|
||||||
|
Syncer.prototype.isDirty = function() {
|
||||||
|
return (this.numTasksInQueue() > 0) || (this.numTasksInProgress() > 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -118,7 +204,7 @@ Syncer.prototype.storeTiddler = function(tiddlerFields) {
|
|||||||
Syncer.prototype.getStatus = function(callback) {
|
Syncer.prototype.getStatus = function(callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
// Check if the adaptor supports getStatus()
|
// Check if the adaptor supports getStatus()
|
||||||
if(this.syncadaptor.getStatus) {
|
if(this.syncadaptor && this.syncadaptor.getStatus) {
|
||||||
// Mark us as not logged in
|
// Mark us as not logged in
|
||||||
this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
|
this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
|
||||||
// Get login status
|
// Get login status
|
||||||
@ -130,7 +216,7 @@ Syncer.prototype.getStatus = function(callback) {
|
|||||||
// Set the various status tiddlers
|
// Set the various status tiddlers
|
||||||
self.wiki.addTiddler({title: self.titleIsLoggedIn,text: isLoggedIn ? "yes" : "no"});
|
self.wiki.addTiddler({title: self.titleIsLoggedIn,text: isLoggedIn ? "yes" : "no"});
|
||||||
if(isLoggedIn) {
|
if(isLoggedIn) {
|
||||||
self.wiki.addTiddler({title: self.titleUserName,text: username});
|
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
|
||||||
} else {
|
} else {
|
||||||
self.wiki.deleteTiddler(self.titleUserName);
|
self.wiki.deleteTiddler(self.titleUserName);
|
||||||
}
|
}
|
||||||
@ -148,7 +234,7 @@ Syncer.prototype.getStatus = function(callback) {
|
|||||||
Synchronise from the server by reading the skinny tiddler list and queuing up loads for any tiddlers that we don't already have up to date
|
Synchronise from the server by reading the skinny tiddler list and queuing up loads for any tiddlers that we don't already have up to date
|
||||||
*/
|
*/
|
||||||
Syncer.prototype.syncFromServer = function() {
|
Syncer.prototype.syncFromServer = function() {
|
||||||
if(this.syncadaptor.getSkinnyTiddlers) {
|
if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
|
||||||
this.log("Retrieving skinny tiddler list");
|
this.log("Retrieving skinny tiddler list");
|
||||||
var self = this;
|
var self = this;
|
||||||
if(this.pollTimerId) {
|
if(this.pollTimerId) {
|
||||||
@ -198,10 +284,11 @@ Synchronise a set of changes to the server
|
|||||||
*/
|
*/
|
||||||
Syncer.prototype.syncToServer = function(changes) {
|
Syncer.prototype.syncToServer = function(changes) {
|
||||||
var self = this,
|
var self = this,
|
||||||
now = new Date();
|
now = new Date(),
|
||||||
|
filteredChanges = this.filterFn.call(this.wiki,changes);
|
||||||
$tw.utils.each(changes,function(change,title,object) {
|
$tw.utils.each(changes,function(change,title,object) {
|
||||||
// Ignore the change if it is a shadow tiddler
|
// Ignore the change if it is a shadow tiddler
|
||||||
if((change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) || (!change.deleted && self.wiki.tiddlerExists(title))) {
|
if((change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) || (!change.deleted && filteredChanges.indexOf(title) !== -1)) {
|
||||||
// Queue a task to sync this tiddler
|
// Queue a task to sync this tiddler
|
||||||
self.enqueueSyncTask({
|
self.enqueueSyncTask({
|
||||||
type: change.deleted ? "delete" : "save",
|
type: change.deleted ? "delete" : "save",
|
||||||
@ -215,6 +302,7 @@ Syncer.prototype.syncToServer = function(changes) {
|
|||||||
Lazily load a skinny tiddler if we can
|
Lazily load a skinny tiddler if we can
|
||||||
*/
|
*/
|
||||||
Syncer.prototype.handleLazyLoadEvent = function(title) {
|
Syncer.prototype.handleLazyLoadEvent = function(title) {
|
||||||
|
console.log("Lazy loading",title)
|
||||||
// Queue up a sync task to load this tiddler
|
// Queue up a sync task to load this tiddler
|
||||||
this.enqueueSyncTask({
|
this.enqueueSyncTask({
|
||||||
type: "load",
|
type: "load",
|
||||||
@ -314,7 +402,7 @@ Syncer.prototype.enqueueSyncTask = function(task) {
|
|||||||
}
|
}
|
||||||
// Check if this tiddler is already in the queue
|
// Check if this tiddler is already in the queue
|
||||||
if($tw.utils.hop(this.taskQueue,task.title)) {
|
if($tw.utils.hop(this.taskQueue,task.title)) {
|
||||||
this.log("Re-queueing up sync task with type:",task.type,"title:",task.title);
|
// this.log("Re-queueing up sync task with type:",task.type,"title:",task.title);
|
||||||
var existingTask = this.taskQueue[task.title];
|
var existingTask = this.taskQueue[task.title];
|
||||||
// If so, just update the last modification time
|
// If so, just update the last modification time
|
||||||
existingTask.lastModificationTime = task.lastModificationTime;
|
existingTask.lastModificationTime = task.lastModificationTime;
|
||||||
@ -323,12 +411,14 @@ Syncer.prototype.enqueueSyncTask = function(task) {
|
|||||||
existingTask.type = task.type;
|
existingTask.type = task.type;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.log("Queuing up sync task with type:",task.type,"title:",task.title);
|
// this.log("Queuing up sync task with type:",task.type,"title:",task.title);
|
||||||
// If it is not in the queue, insert it
|
// If it is not in the queue, insert it
|
||||||
this.taskQueue[task.title] = task;
|
this.taskQueue[task.title] = task;
|
||||||
}
|
}
|
||||||
// Process the queue
|
// Process the queue
|
||||||
$tw.utils.nextTick(function() {self.processTaskQueue.call(self);});
|
if(this.syncadaptor) {
|
||||||
|
$tw.utils.nextTick(function() {self.processTaskQueue.call(self);});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -52,6 +52,7 @@ NavigatorWidget.prototype.execute = function() {
|
|||||||
// Get our parameters
|
// Get our parameters
|
||||||
this.storyTitle = this.getAttribute("story");
|
this.storyTitle = this.getAttribute("story");
|
||||||
this.historyTitle = this.getAttribute("history");
|
this.historyTitle = this.getAttribute("history");
|
||||||
|
this.autosave = this.getAttribute("autosave","yes");
|
||||||
// Construct the child widgets
|
// Construct the child widgets
|
||||||
this.makeChildWidgets();
|
this.makeChildWidgets();
|
||||||
};
|
};
|
||||||
@ -61,7 +62,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
|||||||
*/
|
*/
|
||||||
NavigatorWidget.prototype.refresh = function(changedTiddlers) {
|
NavigatorWidget.prototype.refresh = function(changedTiddlers) {
|
||||||
var changedAttributes = this.computeAttributes();
|
var changedAttributes = this.computeAttributes();
|
||||||
if(changedAttributes.story || changedAttributes.history) {
|
if(changedAttributes.story || changedAttributes.history || changedAttributes.autosave) {
|
||||||
this.refreshSelf();
|
this.refreshSelf();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -290,7 +291,9 @@ NavigatorWidget.prototype.handleSaveTiddlerEvent = function(event) {
|
|||||||
this.saveStoryList(storyList);
|
this.saveStoryList(storyList);
|
||||||
}
|
}
|
||||||
// Send a notification event
|
// Send a notification event
|
||||||
this.dispatchEvent({type: "tw-auto-save-wiki"});
|
if(this.autosave === "yes") {
|
||||||
|
this.dispatchEvent({type: "tw-auto-save-wiki"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -877,62 +877,6 @@ exports.renderTiddler = function(outputType,title,options) {
|
|||||||
return outputType === "text/html" ? container.innerHTML : container.textContent;
|
return outputType === "text/html" ? container.innerHTML : container.textContent;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
Select the appropriate saver modules and set them up
|
|
||||||
*/
|
|
||||||
exports.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));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 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
|
|
||||||
*/
|
|
||||||
exports.saveWiki = function(options) {
|
|
||||||
options = options || {};
|
|
||||||
var method = options.method || "save",
|
|
||||||
template = options.template || "$:/core/save/all",
|
|
||||||
downloadType = options.downloadType || "text/plain",
|
|
||||||
text = this.renderTiddler(downloadType,template),
|
|
||||||
callback = function(err) {
|
|
||||||
if(err) {
|
|
||||||
alert("Error while saving:\n\n" + err);
|
|
||||||
} else {
|
|
||||||
$tw.notifier.display("$:/messages/Saved");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// 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)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Return an array of tiddler titles that match a search string
|
Return an array of tiddler titles that match a search string
|
||||||
text: The text string to search for
|
text: The text string to search for
|
||||||
|
@ -12,6 +12,11 @@ http://$(userName)$.tiddlyspot.com/backup/
|
|||||||
</$set>
|
</$set>
|
||||||
</$reveal>
|
</$reveal>
|
||||||
\end
|
\end
|
||||||
|
! Saving
|
||||||
|
|
||||||
|
|[[Autosave|$:/config/AutoSave]] |{{$:/snippets/autosavestatus}} |
|
||||||
|
|
||||||
|
|
||||||
! TiddlySpot
|
! TiddlySpot
|
||||||
|
|
||||||
|[[Wiki name|$:/UploadName]] |<$edit-text tiddler="$:/UploadName" default="" tag="input"/> |
|
|[[Wiki name|$:/UploadName]] |<$edit-text tiddler="$:/UploadName" default="" tag="input"/> |
|
||||||
|
10
core/wiki/autosavestatus.tid
Normal file
10
core/wiki/autosavestatus.tid
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
title: $:/snippets/autosavestatus
|
||||||
|
|
||||||
|
<$reveal type="match" state="$:/config/AutoSave" text="yes">
|
||||||
|
Autosave is currently enabled
|
||||||
|
<$linkcatcher to="$:/config/AutoSave"><$link to="no">Disable</$link></$linkcatcher>
|
||||||
|
</$reveal>
|
||||||
|
<$reveal type="nomatch" state="$:/config/AutoSave" text="yes">
|
||||||
|
Autosave is currently disabled
|
||||||
|
<$linkcatcher to="$:/config/AutoSave"><$link to="yes">Enable</$link></$linkcatcher>
|
||||||
|
</$reveal>
|
3
core/wiki/config/AutoSave.tid
Normal file
3
core/wiki/config/AutoSave.tid
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
title: $:/config/AutoSave
|
||||||
|
|
||||||
|
yes
|
3
core/wiki/config/SyncFilter.tid
Normal file
3
core/wiki/config/SyncFilter.tid
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
title: $:/config/SyncFilter
|
||||||
|
|
||||||
|
[is[tiddler]] -[[$:/HistoryList]] -[[$:/StoryList]] -[[$:/isEncrypted]] -[prefix[$:/status]] -[prefix[$:/state]] -[prefix[$:/temp]]
|
11
editions/clientserver/tiddlers/AutoSave.tid
Normal file
11
editions/clientserver/tiddlers/AutoSave.tid
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
created: 20140206211715540
|
||||||
|
modified: 20140206212334833
|
||||||
|
tags: features
|
||||||
|
title: AutoSave
|
||||||
|
type: text/vnd.tiddlywiki
|
||||||
|
|
||||||
|
If there is a SaverModule available that supports it, TiddlyWiki will automatically trigger a save of the current document on clicking {{$:/core/images/done-button}} ''tick'' after editing a tiddler.
|
||||||
|
|
||||||
|
You should see a yellow notification at the top right of the window to confirm that an automatic save has taken place.
|
||||||
|
|
||||||
|
Automatic saving can be enabled or disabled through the ''Saving'' tab of the [[control panel|$:/ControlPanel]]. Behind the scenes, it is controlled through the configuration tiddler [[$:/config/AutoSave]], which must have the value ''yes'' to enable automatic saving.
|
@ -9,11 +9,6 @@
|
|||||||
"tiddlywiki/vanilla",
|
"tiddlywiki/vanilla",
|
||||||
"tiddlywiki/snowwhite"
|
"tiddlywiki/snowwhite"
|
||||||
],
|
],
|
||||||
"doNotSave": [
|
|
||||||
"$:/HistoryList",
|
|
||||||
"$:/status/IsLoggedIn",
|
|
||||||
"$:/status/UserName"
|
|
||||||
],
|
|
||||||
"includeWikis": [
|
"includeWikis": [
|
||||||
"../tw5.com"
|
"../tw5.com"
|
||||||
]
|
]
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
"tiddlywiki/vanilla",
|
"tiddlywiki/vanilla",
|
||||||
"tiddlywiki/snowwhite"
|
"tiddlywiki/snowwhite"
|
||||||
],
|
],
|
||||||
"doNotSave": [
|
|
||||||
],
|
|
||||||
"includeWikis": [
|
"includeWikis": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,6 @@
|
|||||||
"tiddlywiki/vanilla",
|
"tiddlywiki/vanilla",
|
||||||
"tiddlywiki/snowwhite"
|
"tiddlywiki/snowwhite"
|
||||||
],
|
],
|
||||||
"doNotSave": [
|
|
||||||
"$:/StoryList",
|
|
||||||
"$:/HistoryList",
|
|
||||||
"$:/status/IsLoggedIn",
|
|
||||||
"$:/status/UserName"
|
|
||||||
],
|
|
||||||
"includeWikis": [
|
"includeWikis": [
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -6,8 +6,6 @@
|
|||||||
"tiddlywiki/vanilla",
|
"tiddlywiki/vanilla",
|
||||||
"tiddlywiki/snowwhite"
|
"tiddlywiki/snowwhite"
|
||||||
],
|
],
|
||||||
"doNotSave": [
|
|
||||||
],
|
|
||||||
"includeWikis": [
|
"includeWikis": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
"tiddlywiki/vanilla",
|
"tiddlywiki/vanilla",
|
||||||
"tiddlywiki/snowwhite"
|
"tiddlywiki/snowwhite"
|
||||||
],
|
],
|
||||||
"doNotSave": [
|
|
||||||
],
|
|
||||||
"includeWikis": [
|
"includeWikis": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
created: 20131129090249275
|
created: 20131129090249275
|
||||||
modified: 20140126130155435
|
modified: 20140206212022007
|
||||||
tags: introduction
|
tags: introduction
|
||||||
title: GettingStarted
|
title: GettingStarted
|
||||||
type: text/vnd.tiddlywiki
|
type: text/vnd.tiddlywiki
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
created: 20140127143652456
|
created: 20140127143652456
|
||||||
modified: 20140127151028534
|
modified: 20140206191028534
|
||||||
tags: releasenote
|
tags: releasenote
|
||||||
title: Release 5.0.8-beta
|
title: Release 5.0.8-beta
|
||||||
type: text/vnd.tiddlywiki
|
type: text/vnd.tiddlywiki
|
||||||
@ -18,7 +18,8 @@ These are changes that might affect users upgrading from previous betas.
|
|||||||
|
|
||||||
!! Improvements
|
!! Improvements
|
||||||
|
|
||||||
*
|
* Added [[automatic saving|AutoSave]] on editing a tiddler
|
||||||
|
* Added a warning when attempting to close the window with unsaved changes
|
||||||
|
|
||||||
!! Bug Fixes
|
!! Bug Fixes
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
created: 20130825214200000
|
created: 20130825214200000
|
||||||
modified: 20140128194546203
|
modified: 20140206174804112
|
||||||
tags: dev
|
tags: dev
|
||||||
title: TiddlyWikiFolders
|
title: TiddlyWikiFolders
|
||||||
type: text/vnd.tiddlywiki
|
type: text/vnd.tiddlywiki
|
||||||
@ -21,7 +21,6 @@ Only the ''tiddlywiki.info'' file is required, the ''tiddlers'' and ''plugins''
|
|||||||
The `tiddlywiki.info` file in a wiki folder contains a JSON object comprising the following fields:
|
The `tiddlywiki.info` file in a wiki folder contains a JSON object comprising the following fields:
|
||||||
|
|
||||||
* ''plugins'' - an array of plugin names to be included in the wiki
|
* ''plugins'' - an array of plugin names to be included in the wiki
|
||||||
* ''doNotSave'' - an array of tiddler titles that should not be saved by the FileSystemAdaptorPlugin
|
|
||||||
* ''includeWikis'' - an array of relative paths to external wiki folders to be included in the wiki
|
* ''includeWikis'' - an array of relative paths to external wiki folders to be included in the wiki
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
@ -32,11 +31,6 @@ For example:
|
|||||||
"tiddlywiki/tiddlyweb",
|
"tiddlywiki/tiddlyweb",
|
||||||
"tiddlywiki/filesystem"
|
"tiddlywiki/filesystem"
|
||||||
],
|
],
|
||||||
"doNotSave": [
|
|
||||||
"$:/HistoryList",
|
|
||||||
"$:/status/IsLoggedIn",
|
|
||||||
"$:/status/UserName"
|
|
||||||
],
|
|
||||||
"includeWikis": [
|
"includeWikis": [
|
||||||
"../tw5.com"
|
"../tw5.com"
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
created: 20130823203800000
|
created: 20130823203800000
|
||||||
modified: 20140124204012349
|
modified: 20140206174012349
|
||||||
tags: planning
|
tags: planning
|
||||||
title: RoadMap
|
title: RoadMap
|
||||||
type: text/vnd.tiddlywiki
|
type: text/vnd.tiddlywiki
|
||||||
@ -12,10 +12,6 @@ During the beta TiddlyWiki will be practical for cautious everyday use but as we
|
|||||||
|
|
||||||
The following additional features are planned or under consideration for implementation during the beta:
|
The following additional features are planned or under consideration for implementation during the beta:
|
||||||
|
|
||||||
* Features affecting user data integrity
|
|
||||||
** Warning when attempting to close window without saving
|
|
||||||
** Use browser local storage to preserve changes in case browser crashes before saving/sync
|
|
||||||
** Automatic saving for saver modules that can support it
|
|
||||||
* Features required for large scale adoption
|
* Features required for large scale adoption
|
||||||
** Multilanguage support
|
** Multilanguage support
|
||||||
** Proper use of ARIA roles
|
** Proper use of ARIA roles
|
||||||
|
@ -140,10 +140,6 @@ FileSystemAdaptor.prototype.saveTiddler = function(tiddler,callback) {
|
|||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
if($tw.boot.wikiInfo.doNotSave && $tw.boot.wikiInfo.doNotSave.indexOf(tiddler.fields.title) !== -1) {
|
|
||||||
// Don't save the tiddler if it is on the blacklist
|
|
||||||
return callback(null,{},0);
|
|
||||||
}
|
|
||||||
if(self.watchers[fileInfo.filepath]) {
|
if(self.watchers[fileInfo.filepath]) {
|
||||||
self.watchers[fileInfo.filepath].close();
|
self.watchers[fileInfo.filepath].close();
|
||||||
delete self.watchers[fileInfo.filepath];
|
delete self.watchers[fileInfo.filepath];
|
||||||
@ -197,34 +193,29 @@ FileSystemAdaptor.prototype.deleteTiddler = function(title,callback) {
|
|||||||
fileInfo = $tw.boot.files[title];
|
fileInfo = $tw.boot.files[title];
|
||||||
// Only delete the tiddler if we have writable information for the file
|
// Only delete the tiddler if we have writable information for the file
|
||||||
if(fileInfo) {
|
if(fileInfo) {
|
||||||
if($tw.boot.wikiInfo.doNotSave && $tw.boot.wikiInfo.doNotSave.indexOf(title) !== -1) {
|
if(this.watchers[fileInfo.filepath]) {
|
||||||
// Don't delete the tiddler if it is on the blacklist
|
this.watchers[fileInfo.filepath].close();
|
||||||
callback(null);
|
delete this.watchers[fileInfo.filepath];
|
||||||
} else {
|
|
||||||
if(this.watchers[fileInfo.filepath]) {
|
|
||||||
this.watchers[fileInfo.filepath].close();
|
|
||||||
delete this.watchers[fileInfo.filepath];
|
|
||||||
}
|
|
||||||
delete this.pending[fileInfo.filepath];
|
|
||||||
// Delete the file
|
|
||||||
fs.unlink(fileInfo.filepath,function(err) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
self.log("Deleted file",fileInfo.filepath);
|
|
||||||
// Delete the metafile if present
|
|
||||||
if(fileInfo.hasMetaFile) {
|
|
||||||
fs.unlink(fileInfo.filepath + ".meta",function(err) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback(null);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
delete this.pending[fileInfo.filepath];
|
||||||
|
// Delete the file
|
||||||
|
fs.unlink(fileInfo.filepath,function(err) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
self.log("Deleted file",fileInfo.filepath);
|
||||||
|
// Delete the metafile if present
|
||||||
|
if(fileInfo.hasMetaFile) {
|
||||||
|
fs.unlink(fileInfo.filepath + ".meta",function(err) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user