diff --git a/README.md b/README.md index 127452c..bd41afe 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,10 @@ TiddlyFox is an extension for Mozilla Firefox that enables TiddlyWiki to save changes directly to the file system. TiddlyWiki is complete wiki packaged as a standalone HTML file; see http://tiddlywiki.com/ for more details. -# Installation - -TiddlyFox can be installed from Mozilla's Add-ons page: - -https://addons.mozilla.org/en-US/firefox/addon/tiddlyfox/ - -However, due to the duration of Mozilla's approval process, you will often find a more up to date version here on Github: - -https://github.com/TiddlyWiki/TiddlyFox/raw/master/tiddlyfox.xpi +This personalised version keeps a one a day backup in the subfolder 'backup' relative to the tiddlywiki. -# Building the extension - -The shell script `build_mac.sh` builds `tiddlyfox.api` successfully on Mac OS X. - -# Development mode installation - -To make it easier to work on the extension, you can configure Firefox to use the unpacked, development version of the extension. To set it up, edit the file `tiddlyfox@tiddlywiki.org` to contain your local path to the TiddlyFox folder, and then drop the file in your `[firefox profile folder]\extensions\` folder. +# Installation +Open the addons page in firefox and select the 'Extensions' tab. Drag and drop the link below into the list of addons. -# Credits +https://github.com/buggyj/TiddlyFox/raw/saver/tiddlyfox.xpi -This extension started life as the sample code accompanying this MozillaZine Knowledge Base article: http://kb.mozillazine.org/Getting_started_with_extension_development diff --git a/content/inject.js b/content/inject.js index 3c99e97..b6c9666 100644 --- a/content/inject.js +++ b/content/inject.js @@ -15,6 +15,7 @@ The JavaScript in this file is injected into each TiddlyWiki page that loads var message = document.createElement("div"); message.setAttribute("data-tiddlyfox-path",path); message.setAttribute("data-tiddlyfox-content",content); + message.setAttribute("data-tiddlyfox-tw2","true"); messageBox.appendChild(message); // Create and dispatch the custom event to the extension var event = document.createEvent("Events"); @@ -52,4 +53,4 @@ The JavaScript in this file is injected into each TiddlyWiki page that loads window.convertUriToUTF8 = injectedConvertUriToUTF8; window.convertUnicodeToFileFormat = injectedConvertUnicodeToFileFormat; -})(); \ No newline at end of file +})(); diff --git a/content/overlay.js b/content/overlay.js index 030213a..03164b9 100644 --- a/content/overlay.js +++ b/content/overlay.js @@ -4,6 +4,186 @@ The JavaScript code in this file is executed via `overlay.xul` when Firefox star */ var TiddlyFox = (function () { +/* + * note that Date is not a 'real' object so you cannot extend it + * so instead we wrap it + */ +var util = { + + Date : function () { + this.date = new Date(); + }, + + CreateAPI : function (name, index, array) { + util.Date.prototype[name] =function () { + return(this.date[name]()); + } + } +}; +["getDate", "getFullYear", "getMonth", "getDay", "getHours", "getMinutes", "getSeconds", "getMilliseconds" ].forEach(util.CreateAPI); +["getUTCDate","getUTCFullYear","getUTCMonth","getUTCDay","getUTCHours","getUTCMinutes","getUTCSeconds","getUTCMilliseconds"].forEach(util.CreateAPI); +["getTime","getTimezoneOffset"].forEach(util.CreateAPI); + +// --------------------------------------------------------------------------------- +// Start of Utility functions copied from TiddlyWiki. +// --------------------------------------------------------------------------------- + +var zeroPad = function(n,d) +{ + var s = n.toString(); + if(s.length < d) + s = "000000000000000000000000000".substr(0,d-s.length) + s; + return(s); +} + +// Convert a date to UTC YYYYMMDDHHMM string format +util.Date.prototype.convertToYYYYMMDDHHMM = function() +{ + return(zeroPad(this.getUTCFullYear(),4) + zeroPad(this.getUTCMonth()+1,2) + zeroPad(this.getUTCDate(),2) + zeroPad(this.getUTCHours(),2) + zeroPad(this.getUTCMinutes(),2)); +} + +// Convert a date to UTC YYYYMMDD.HHMMSSMMM string format +util.Date.prototype.convertToYYYYMMDDHHMMSSMMM = function() +{ + return(zeroPad(this.getUTCFullYear(),4) + zeroPad(this.getUTCMonth()+1,2) + zeroPad(this.getUTCDate(),2) + "." + zeroPad(this.getUTCHours(),2) + zeroPad(this.getUTCMinutes(),2) + zeroPad(this.getUTCSeconds(),2) + zeroPad(this.getUTCMilliseconds(),4)); +} + +// Convert a date to UTC YYYY-MM-DD HH:MM string format +util.Date.prototype.convertToFullDate = function() +{ + return(zeroPad(this.getUTCFullYear(),4) + "-" + + zeroPad(this.getUTCMonth()+1,2) + "-" + + zeroPad(this.getUTCDate(),2) + " " + + zeroPad(this.getUTCHours(),2) + ":" + + zeroPad(this.getUTCMinutes(),2)); +} + +util.Date.prototype.formatString = function(template) +{ + var t = template.replace(/0hh12/g,zeroPad(this.getHours12(),2)); + t = t.replace(/hh12/g,this.getHours12()); + t = t.replace(/0hh/g,zeroPad(this.getHours(),2)); + t = t.replace(/hh/g,this.getHours()); + t = t.replace(/mmm/g,dateS.shortMonths[this.getMonth()]); + t = t.replace(/0mm/g,zeroPad(this.getMinutes(),2)); + t = t.replace(/mm/g,this.getMinutes()); + t = t.replace(/0ss/g,zeroPad(this.getSeconds(),2)); + t = t.replace(/ss/g,this.getSeconds()); + t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase()); + t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase()); + t = t.replace(/wYYYY/g,this.getYearForWeekNo()); + t = t.replace(/wYY/g,zeroPad(this.getYearForWeekNo()-2000,2)); + t = t.replace(/YYYY/g,this.getFullYear()); + t = t.replace(/YY/g,zeroPad(this.getFullYear()-2000,2)); + t = t.replace(/MMM/g,dateS.months[this.getMonth()]); + t = t.replace(/0MM/g,zeroPad(this.getMonth()+1,2)); + t = t.replace(/MM/g,this.getMonth()+1); + t = t.replace(/0WW/g,zeroPad(this.getWeek(),2)); + t = t.replace(/WW/g,this.getWeek()); + t = t.replace(/DDD/g,dateS.days[this.getDay()]); + t = t.replace(/ddd/g,dateS.shortDays[this.getDay()]); + t = t.replace(/0DD/g,zeroPad(this.getDate(),2)); + t = t.replace(/DDth/g,this.getDate()+this.daySuffix()); + t = t.replace(/DD/g,this.getDate()); + var tz = this.getTimezoneOffset(); + var atz = Math.abs(tz); + t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + zeroPad(Math.floor(atz / 60),2) + ':' + zeroPad(atz % 60,2)); + t = t.replace(/\\/g,""); + return t; +}; + +util.Date.prototype.getWeek = function() +{ + var dt = new Date(this.getTime()); + var d = dt.getDay(); + if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7 + dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo + var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000); + return Math.floor(n/7)+1; +}; + +util.Date.prototype.getYearForWeekNo = function() +{ + var dt = new Date(this.getTime()); + var d = dt.getDay(); + if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7 + dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week + return dt.getFullYear(); +}; + +util.Date.prototype.getHours12 = function() +{ + var h = this.getHours(); + return h > 12 ? h-12 : ( h > 0 ? h : 12 ); +}; + +util.Date.prototype.getAmPm = function() +{ + return this.getHours() >= 12 ? dateS.pm : dateS.am; +}; + +util.Date.prototype.daySuffix = function() +{ + return dateS.daySuffixes[this.getDate()-1]; +}; + +// Convert a date to local YYYYMMDDHHMM string format +util.Date.prototype.convertToLocalYYYYMMDDHHMM = function() +{ + return this.getFullYear() + zeroPad(this.getMonth()+1,2) + zeroPad(this.getDate(),2) + zeroPad(this.getHours(),2) + zeroPad(this.getMinutes(),2); +}; + +// Convert a date to UTC YYYYMMDDHHMM string format +util.Date.prototype.convertToYYYYMMDDHHMM = function() +{ + return this.getUTCFullYear() + zeroPad(this.getUTCMonth()+1,2) + zeroPad(this.getUTCDate(),2) + zeroPad(this.getUTCHours(),2) + zeroPad(this.getUTCMinutes(),2); +}; + +// Convert a date to UTC YYYYMMDD.HHMMSSMMM string format +util.Date.prototype.convertToYYYYMMDDHHMMSSMMM = function() +{ + return this.getUTCFullYear() + zeroPad(this.getUTCMonth()+1,2) + zeroPad(this.getUTCDate(),2) + "." + zeroPad(this.getUTCHours(),2) + zeroPad(this.getUTCMinutes(),2) + zeroPad(this.getUTCSeconds(),2) + zeroPad(this.getUTCMilliseconds(),3) +"0"; +}; + +// Static method to create a date from a UTC YYYYMMDDHHMM format string +util.Date.convertFromYYYYMMDDHHMM = function(d) +{ + d = d?d.replace(/[^0-9]/g, ""):""; + return util.Date.convertFromYYYYMMDDHHMMSSMMM(d.substr(0,12)); +}; + +// Static method to create a date from a UTC YYYYMMDDHHMMSS format string +util.Date.convertFromYYYYMMDDHHMMSS = function(d) +{ + d = d?d.replace(/[^0-9]/g, ""):""; + return util.Date.convertFromYYYYMMDDHHMMSSMMM(d.substr(0,14)); +}; + +// Static method to create a date from a UTC YYYYMMDDHHMMSSMMM format string +util.Date.convertFromYYYYMMDDHHMMSSMMM = function(d) +{ + d = d ? d.replace(/[^0-9]/g, "") : ""; + return new Date(Date.UTC(parseInt(d.substr(0,4),10), + parseInt(d.substr(4,2),10)-1, + parseInt(d.substr(6,2),10), + parseInt(d.substr(8,2)||"00",10), + parseInt(d.substr(10,2)||"00",10), + parseInt(d.substr(12,2)||"00",10), + parseInt(d.substr(14,3)||"000",10))); +}; +var dateS ={}; +dateS.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"]; +dateS.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; +dateS.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; +dateS.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +// suffixes for dates, eg "1st","2nd","3rd"..."30th","31st" +dateS.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th", + "th","th","th","th","th","th","th","th","th","th", + "st","nd","rd","th","th","th","th","th","th","th", + "st"]; +dateS.am = "am"; +dateS.pm = "pm"; + var TiddlyFox = { // Name of the permission for TiddlyFox used by Firefox @@ -80,8 +260,23 @@ var TiddlyFox = { // Attach the event handler to the message box messageBox.addEventListener("tiddlyfox-save-file",TiddlyFox.onSaveFile,false); }, + moveFile: function(sourcefile,destdir,onceaday) { + var aFile = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + if (!aFile) return false; + + var aDir = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + if (!aDir) return false; + + aFile.initWithPath(sourcefile); + aDir.initWithPath(TiddlyFox.getBackupPath(sourcefile,destdir)); - saveFile: function(filePath,content) { + + aFile.moveTo(aDir,TiddlyFox.getBackupFile(sourcefile,onceaday)); + }, + + saveFile: function(filePath,content,destdir,onceaday) { // Attempt to convert the filepath to a proper UTF-8 string try { var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService); @@ -90,8 +285,11 @@ var TiddlyFox = { } // Save the file try { + if (destdir) + TiddlyFox.moveFile(filePath,destdir,onceaday); var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile); file.initWithPath(filePath); + if(!file.exists()) file.create(0,0x01B4);// 0x01B4 = 0664 var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream); @@ -108,14 +306,47 @@ var TiddlyFox = { } }, +getBackupPath: function(localPath,destdir) { + var slash = "\\"; + var dirPathPos = localPath.lastIndexOf("\\"); + if(dirPathPos == -1) { + dirPathPos = localPath.lastIndexOf("/"); + slash = "/"; + } + var backupFolder = destdir;//config.options.txtBackupFolder; + if(!backupFolder || backupFolder == "") + backupFolder = "."; + backupFolder=localPath.substr(0,dirPathPos) + slash + backupFolder; + return backupFolder; +}, +getBackupFile: function(localPath,onceaday) { + var slash = "\\"; + var dirPathPos = localPath.lastIndexOf("\\"); + if(dirPathPos == -1) { + dirPathPos = localPath.lastIndexOf("/"); + slash = "/"; + } + var backupPath = localPath.substr(dirPathPos+1); + backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + "."; + if (onceaday) + backupPath += (new util.Date()).convertToYYYYMMDDHHMMSSMMM().replace (/(.*)\.(.*)/,"$1") + "." + "html"; + else + backupPath += (new util.Date()).convertToYYYYMMDDHHMMSSMMM() + "." + "html"; + return backupPath; +}, onSaveFile: function(event) { // Get the details from the message var message = event.target, path = message.getAttribute("data-tiddlyfox-path"), - content = message.getAttribute("data-tiddlyfox-content"); + content = message.getAttribute("data-tiddlyfox-content"), + backupdir = message.getAttribute("data-tiddlyfox-backupdir"), + onceaday = message.getAttribute("data-tiddlyfox-onceaday"), + tw2 =message.getAttribute("data-tiddlyfox-tw2"); // Save the file - TiddlyFox.saveFile(path,content); - // Remove the message element from the message box + if (tw2) + TiddlyFox.saveFile(path,content); + else + TiddlyFox.saveFile(path,content,"backup",true); message.parentNode.removeChild(message); // Send a confirmation message var event = document.createEvent("Events"); @@ -180,4 +411,5 @@ window.addEventListener("load",function load(event) { TiddlyFox.onLoad(event); },false); return TiddlyFox; + }()); diff --git a/tiddlyfox.xpi b/tiddlyfox.xpi index 9d3952c..051a3b6 100644 Binary files a/tiddlyfox.xpi and b/tiddlyfox.xpi differ