From c99f927205252fd1bfa7b167cfa8d8e00e3060d5 Mon Sep 17 00:00:00 2001 From: benweet Date: Mon, 3 Jun 2013 23:19:52 +0100 Subject: [PATCH] Implement remember document position --- css/default.css | 2 +- index.html | 3 + js/core.js | 45 ++++++++--- js/extensions/document-selector.js | 16 +++- js/extensions/scroll-link.js | 44 ++++++----- js/file-manager.js | 93 +++++++++++++---------- js/helpers/google-helper.js | 2 +- js/libs/Markdown.Editor.js | 39 +++++----- js/libs/Markdown.Extra.js | 92 ++++++++++------------- js/libs/jquery.mousewheel.js | 117 +++++++++++++++++++++++++++++ js/main.js | 3 + js/providers/gdrive-provider.js | 3 +- js/providers/gplus-provider.js | 7 +- js/publisher.js | 40 +++++----- js/settings.js | 5 +- js/storage.js | 19 ++--- js/synchronizer.js | 28 ++++--- js/utils.js | 33 +++++++- 18 files changed, 394 insertions(+), 197 deletions(-) create mode 100644 js/libs/jquery.mousewheel.js diff --git a/css/default.css b/css/default.css index 60e0afbf..7fbdbb2e 100644 --- a/css/default.css +++ b/css/default.css @@ -598,7 +598,7 @@ dt, dd { } dd { - margin-left: 40px; + margin-left: 40px; } /* Table style */ diff --git a/index.html b/index.html index bb7e0b03..067786e3 100644 --- a/index.html +++ b/index.html @@ -736,6 +736,9 @@
jQuery
+
+ jQuery Mouse Wheel Plugin +
PageDown / Builds the "Open document" dropdown menu.

' }; @@ -56,7 +61,8 @@ define([ $("#file-selector li:not(.stick)").removeClass("disabled"); var li = liMap[fileDesc.fileIndex]; if(li === undefined) { - // It means that we are showing a temporary file (not in the selector) + // It means that we are showing a temporary file (not in the + // selector) return; } liMap[fileDesc.fileIndex].addClass("disabled"); @@ -69,7 +75,7 @@ define([ documentSelector.onSyncRemoved = buildSelector; documentSelector.onNewPublishSuccess = buildSelector; documentSelector.onPublishRemoved = buildSelector; - + // Filter for search input in file selector function filterFileSelector(filter) { var liList = $("#file-selector li:not(.stick)"); @@ -104,6 +110,12 @@ define([ }).click(function(event) { event.stopPropagation(); }); + /* + $("#wmd-input").keydown(function(event) { + if(event.ctrlKey && event.keyCode == documentSelector.config.keyShortcut) { + console.log(event.keyCode); + } + });*/ }; return documentSelector; diff --git a/js/extensions/scroll-link.js b/js/extensions/scroll-link.js index 6bee7008..3157cfb5 100644 --- a/js/extensions/scroll-link.js +++ b/js/extensions/scroll-link.js @@ -1,7 +1,8 @@ define([ "jquery", "underscore", - "libs/css_browser_selector" + "libs/css_browser_selector", + "libs/jquery.mousewheel" ], function($, _) { var scrollLink = { @@ -95,13 +96,14 @@ define([ // apply Scroll Link lastEditorScrollTop = -10; - isScrollPreview = false; + lastPreviewScrollTop = -10; runScrollLink(); }, 500); - // -10 to be sure the gap is > 9 + // -10 to be sure the gap is more than 9px var lastEditorScrollTop = -10; var lastPreviewScrollTop = -10; + var isScrollEditor = false; var isScrollPreview = false; var runScrollLink = _.debounce(function() { if(mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) { @@ -120,7 +122,8 @@ define([ }); if(srcSection === undefined) { // Something wrong in the algorithm... - return -10; + callback(-10); + return; } var posInSection = (srcScrollTop - srcSection.startOffset) / srcSection.height; var destSection = destSectionList[sectionIndex]; @@ -131,50 +134,58 @@ define([ ]); if(Math.abs(destScrollTop - lastDestScrollTop) < 9) { // Skip the animation in case it's not necessary + callback(lastDestScrollTop); return; } destElt.animate({ scrollTop: destScrollTop - }, 600, function() { + }, 500, function() { callback(destScrollTop); }); } // Perform the animation if diff > 9px - if(isScrollPreview === false && Math.abs(editorScrollTop - lastEditorScrollTop) > 9) { + if(isScrollEditor === true && Math.abs(editorScrollTop - lastEditorScrollTop) > 9) { + isScrollEditor = false; // Animate the preview lastEditorScrollTop = editorScrollTop; animate(editorScrollTop, mdSectionList, previewElt, htmlSectionList, lastPreviewScrollTop, function(destScrollTop) { lastPreviewScrollTop = destScrollTop; }); } - else if(Math.abs(previewScrollTop - lastPreviewScrollTop) > 9) { + else if(isScrollPreview === true && Math.abs(previewScrollTop - lastPreviewScrollTop) > 9) { + isScrollPreview = false; // Animate the editor lastPreviewScrollTop = previewScrollTop; animate(previewScrollTop, htmlSectionList, editorElt, mdSectionList, lastEditorScrollTop, function(destScrollTop) { lastEditorScrollTop = destScrollTop; }); } - }, 600); + }, 500); scrollLink.onLayoutConfigure = function(layoutConfig) { - layoutConfig.onresize = buildSections; + layoutConfig.onresize = function() { + isScrollEditor = true; + buildSections(); + }; }; scrollLink.onLayoutCreated = function() { - $(".preview-container").scroll(function() { + $(".preview-container").bind("keydown click focus mousewheel", function() { isScrollPreview = true; + isScrollEditor = false; runScrollLink(); }); - $("#wmd-input").scroll(function() { + $("#wmd-input").bind("keydown click focus mousewheel", function() { + isScrollEditor = true; isScrollPreview = false; runScrollLink(); }); }; scrollLink.onEditorConfigure = function(editor) { - lastPreviewScrollTop = 0; editor.getConverter().hooks.chain("postConversion", function(text) { - // To avoid losing scrolling position before elements are fully loaded + // To avoid losing scrolling position before elements are fully + // loaded $("#wmd-preview").height($("#wmd-preview").height()); return text; }); @@ -183,11 +194,8 @@ define([ scrollLink.onPreviewFinished = function() { // Now set the correct height $("#wmd-preview").height("auto"); - _.defer(function() { - // Modify scroll position of the preview not the editor - lastEditorScrollTop = -10; - buildSections(); - }); + isScrollEditor = true; + buildSections(); }; return scrollLink; diff --git a/js/file-manager.js b/js/file-manager.js index e4d6fcf1..4eb4361d 100644 --- a/js/file-manager.js +++ b/js/file-manager.js @@ -11,10 +11,16 @@ define([ var fileMgr = {}; - // Defines a file descriptor in the file system (fileDesc objects) + // Defines a file descriptor (fileDesc objects) function FileDescriptor(fileIndex, title, syncLocations, publishLocations) { this.fileIndex = fileIndex; this._title = title; + this._editorScrollTop = parseInt(localStorage[fileIndex + ".editorScrollTop"]) || 0; + this._editorStart = parseInt(localStorage[fileIndex + ".editorStart"]) || 0; + this._editorEnd = parseInt(localStorage[fileIndex + ".editorEnd"]) || 0; + this._previewScrollTop = parseInt(localStorage[fileIndex + ".previewScrollTop"]) || 0; + this.syncLocations = syncLocations || {}; + this.publishLocations = publishLocations || {}; this.__defineGetter__("title", function() { return this._title; }); @@ -30,12 +36,38 @@ define([ localStorage[this.fileIndex + ".content"] = content; extensionMgr.onContentChanged(this); }); - this.syncLocations = syncLocations || {}; - this.publishLocations = publishLocations || {}; + this.__defineGetter__("editorScrollTop", function() { + return this._editorScrollTop; + }); + this.__defineSetter__("editorScrollTop", function(editorScrollTop) { + this._editorScrollTop = editorScrollTop; + localStorage[this.fileIndex + ".editorScrollTop"] = editorScrollTop; + }); + this.__defineGetter__("editorStart", function() { + return this._editorStart; + }); + this.__defineSetter__("editorStart", function(editorStart) { + this._editorStart = editorStart; + localStorage[this.fileIndex + ".editorStart"] = editorStart; + }); + this.__defineGetter__("editorEnd", function() { + return this._editorEnd; + }); + this.__defineSetter__("editorEnd", function(editorEnd) { + this._editorEnd = editorEnd; + localStorage[this.fileIndex + ".editorEnd"] = editorEnd; + }); + this.__defineGetter__("previewScrollTop", function() { + return this._previewScrollTop; + }); + this.__defineSetter__("previewScrollTop", function(previewScrollTop) { + this._previewScrollTop = previewScrollTop; + localStorage[this.fileIndex + ".previewScrollTop"] = previewScrollTop; + }); } - // Load file descriptors from localStorage - _.chain(localStorage["file.list"].split(";")).compact().each(function(fileIndex) { + // Retrieve file descriptors from localStorage and populate fileSystem + _.each(utils.retrieveIndexArray("file.list"), function(fileIndex) { fileSystem[fileIndex] = new FileDescriptor(fileIndex, localStorage[fileIndex + ".title"]); }); @@ -57,7 +89,6 @@ define([ } }; - // Caution: this function recreates the editor (reset undo operations) fileMgr.selectFile = function(fileDesc) { fileDesc = fileDesc || fileMgr.getCurrentFile(); @@ -92,12 +123,8 @@ define([ } } - // Recreate the editor - $("#wmd-input").val(fileDesc.content); - core.createEditor(function() { - // Callback to save content when textarea changes - fileMgr.saveFile(); - }); + // Refresh the editor + core.createEditor(fileDesc); }; fileMgr.createFile = function(title, content, syncLocations, isTemporary) { @@ -137,7 +164,7 @@ define([ // Add the index to the file list if(!isTemporary) { - localStorage["file.list"] += fileIndex + ";"; + utils.appendIndexToArray("file.list", fileIndex); fileSystem[fileIndex] = fileDesc; extensionMgr.onFileCreated(fileDesc); } @@ -148,7 +175,7 @@ define([ fileDesc = fileDesc || fileMgr.getCurrentFile(); // Remove the index from the file list - localStorage["file.list"] = localStorage["file.list"].replace(";" + fileDesc.fileIndex + ";", ";"); + utils.removeIndexFromArray("file.list", fileDesc.fileIndex); delete fileSystem[fileDesc.fileIndex]; if(fileMgr.isCurrentFile(fileDesc) === true) { @@ -160,12 +187,12 @@ define([ // Remove synchronized locations _.each(fileDesc.syncLocations, function(syncAttributes) { - fileMgr.removeSync(syncAttributes, true); + fileMgr.removeSync(syncAttributes); }); // Remove publish locations _.each(fileDesc.publishLocations, function(publishAttributes) { - fileMgr.removePublish(publishAttributes, true); + fileMgr.removePublish(publishAttributes); }); localStorage.removeItem(fileDesc.fileIndex + ".title"); @@ -176,32 +203,24 @@ define([ extensionMgr.onFileDeleted(fileDesc); }; - // Save current file in localStorage - fileMgr.saveFile = function() { - var fileDesc = fileMgr.getCurrentFile(); - fileDesc.content = $("#wmd-input").val(); - }; - // Add a synchronized location to a file fileMgr.addSync = function(fileDesc, syncAttributes) { - localStorage[fileDesc.fileIndex + ".sync"] += syncAttributes.syncIndex + ";"; + utils.appendIndexToArray(fileDesc.fileIndex + ".sync", syncAttributes.syncIndex); fileDesc.syncLocations[syncAttributes.syncIndex] = syncAttributes; // addSync is only used for export, not for import extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes); }; // Remove a synchronized location - fileMgr.removeSync = function(syncAttributes, skipExtensions) { + fileMgr.removeSync = function(syncAttributes) { var fileDesc = fileMgr.getFileFromSyncIndex(syncAttributes.syncIndex); if(fileDesc !== undefined) { - localStorage[fileDesc.fileIndex + ".sync"] = localStorage[fileDesc.fileIndex + ".sync"].replace(";" + syncAttributes.syncIndex + ";", ";"); - } - // Remove sync attributes - localStorage.removeItem(syncAttributes.syncIndex); - delete fileDesc.syncLocations[syncAttributes.syncIndex]; - if(!skipExtensions) { + utils.removeIndexFromArray(fileDesc.fileIndex + ".sync", syncAttributes.syncIndex); + delete fileDesc.syncLocations[syncAttributes.syncIndex]; extensionMgr.onSyncRemoved(fileDesc, syncAttributes); } + // Remove sync attributes from localStorage + localStorage.removeItem(syncAttributes.syncIndex); }; // Get the file descriptor associated to a syncIndex @@ -228,23 +247,21 @@ define([ // Add a publishIndex (publish location) to a file fileMgr.addPublish = function(fileDesc, publishAttributes) { - localStorage[fileDesc.fileIndex + ".publish"] += publishAttributes.publishIndex + ";"; + utils.appendIndexToArray(fileDesc.fileIndex + ".publish", publishAttributes.publishIndex); fileDesc.publishLocations[publishAttributes.publishIndex] = publishAttributes; extensionMgr.onNewPublishSuccess(fileDesc, publishAttributes); }; // Remove a publishIndex (publish location) - fileMgr.removePublish = function(publishAttributes, skipExtensions) { + fileMgr.removePublish = function(publishAttributes) { var fileDesc = fileMgr.getFileFromPublishIndex(publishAttributes.publishIndex); if(fileDesc !== undefined) { - localStorage[fileDesc.fileIndex + ".publish"] = localStorage[fileDesc.fileIndex + ".publish"].replace(";" + publishAttributes.publishIndex + ";", ";"); - } - // Remove publish attributes - localStorage.removeItem(publishAttributes.publishIndex); - delete fileDesc.publishLocations[publishAttributes.publishIndex]; - if(!skipExtensions) { + utils.removeIndexFromArray(fileDesc.fileIndex + ".publish", publishAttributes.publishIndex); + delete fileDesc.publishLocations[publishAttributes.publishIndex]; extensionMgr.onPublishRemoved(fileDesc, publishAttributes); } + // Remove publish attributes from localStorage + localStorage.removeItem(publishAttributes.publishIndex); }; // Get the file descriptor associated to a publishIndex diff --git a/js/helpers/google-helper.js b/js/helpers/google-helper.js index 403b9cea..3cf3a684 100644 --- a/js/helpers/google-helper.js +++ b/js/helpers/google-helper.js @@ -362,7 +362,7 @@ define([ task.retry(new Error(errorMsg), 1); return; } - else if(error.code <= 0) { + else if(error.code === 0 || error.code === -1) { connected = false; authenticated = false; core.setOffline(); diff --git a/js/libs/Markdown.Editor.js b/js/libs/Markdown.Editor.js index debd0630..660e1df6 100644 --- a/js/libs/Markdown.Editor.js +++ b/js/libs/Markdown.Editor.js @@ -111,14 +111,13 @@ * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used. */ - hooks.addFalse("insertLinkDialog"); // benweet + hooks.addFalse("insertLinkDialog"); this.getConverter = function () { return markdownConverter; } var that = this, panels; - // benweet var undoManager; this.run = function (previewWrapper) { if (panels) @@ -126,7 +125,7 @@ panels = new PanelCollection(idPostfix); var commandManager = new CommandManager(hooks, getString); - var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }, previewWrapper); // benweet + var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }, previewWrapper); var uiManager; if (!/\?noundo/.test(doc.location.href)) { @@ -147,13 +146,9 @@ var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); }; - forceRefresh(); - }; - - // benweet - this.restart = function() { - undoManager.reinit(); - that.refreshPreview(); + //Not necessary + //forceRefresh(); + return undoManager; }; } @@ -681,18 +676,21 @@ var init = function () { setEventHandlers(); refreshState(true); - saveState(); + //Not necessary + //saveState(); }; - // benweet - this.reinit = function() { + this.reinit = function(content, start, end, scrollTop) { undoStack = []; stackPtr = 0; mode = "none"; lastState = undefined; timer = undefined; - inputStateObj = undefined; refreshState(); + inputStateObj.text = content; + inputStateObj.start = start; + inputStateObj.end = end; + inputStateObj.scrollTop = scrollTop; inputStateObj.setInputAreaSelection(); saveState(); }; @@ -842,7 +840,7 @@ this.init(); }; - function PreviewManager(converter, panels, previewRefreshCallback, previewWrapper) { // benweet + function PreviewManager(converter, panels, previewRefreshCallback, previewWrapper) { var managerObj = this; var timeout; @@ -908,7 +906,7 @@ pushPreviewHtml(text); }; - if(previewWrapper !== undefined) { // benweet + if(previewWrapper !== undefined) { makePreviewHtml = previewWrapper(makePreviewHtml); } @@ -1030,7 +1028,8 @@ var init = function () { setupEvents(panels.input, applyTimeout); - makePreviewHtml(); + //Not necessary + //makePreviewHtml(); if (panels.preview) { panels.preview.scrollTop = 0; @@ -1437,12 +1436,12 @@ return false; } } - button.className = button.className.replace(/ disabled/g, ""); // benweet + button.className = button.className.replace(/ disabled/g, ""); } else { image.style.backgroundPosition = button.XShift + " " + disabledYShift; button.onmouseover = button.onmouseout = button.onclick = function () { }; - button.className += " disabled"; // benweet + button.className += " disabled"; } } @@ -1807,7 +1806,7 @@ ui.prompt(this.getString("imagedialog"), imageDefaultText, linkEnteredCallback); } else { - if (!this.hooks.insertLinkDialog(linkEnteredCallback)) // benweet + if (!this.hooks.insertLinkDialog(linkEnteredCallback)) ui.prompt(this.getString("linkdialog"), linkDefaultText, linkEnteredCallback); } return true; diff --git a/js/libs/Markdown.Extra.js b/js/libs/Markdown.Extra.js index 8204799d..b9c7f56b 100644 --- a/js/libs/Markdown.Extra.js +++ b/js/libs/Markdown.Extra.js @@ -24,7 +24,7 @@ } } return -1; - } + }; } function trim(str) { @@ -113,21 +113,17 @@ // Duplicated from PageDown converter function unescapeSpecialChars(text) { - // // Swap back in all the special characters we've hidden. - // - text = text.replace(/~E(\d+)E/g, - function (wholeMatch, m1) { - var charCodeToReplace = parseInt(m1); - return String.fromCharCode(charCodeToReplace); - } - ); + text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + }); return text; } - /****************************************************************** - * Markdown.Extra * - *****************************************************************/ + /***************************************************************************** + * Markdown.Extra * + ****************************************************************************/ Markdown.Extra = function() { // For converting internal markdown (in tables for instance). @@ -159,36 +155,47 @@ var extra = new Markdown.Extra(); var postNormalizationTransformations = []; var preBlockGamutTransformations = []; + var postConversionTransformations = ["unHashExtraBlocks"]; options = options || {}; options.extensions = options.extensions || ["all"]; - if (contains(options.extensions, "all")) + if (contains(options.extensions, "all")) { options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list"]; - if (contains(options.extensions, "tables")) - preBlockGamutTransformations.push("tables"); - if (contains(options.extensions, "fenced_code_gfm")) + } + if (contains(options.extensions, "attr_list")) { + postNormalizationTransformations.push("hashFcbAttributeBlocks"); + preBlockGamutTransformations.push("hashHeaderAttributeBlocks"); + postConversionTransformations.push("applyAttributeBlocks"); + extra.attributeBlocks = true; + } + if (contains(options.extensions, "tables")) { + preBlockGamutTransformations.push("tables"); + } + if (contains(options.extensions, "fenced_code_gfm")) { postNormalizationTransformations.push("fencedCodeBlocks"); - if (contains(options.extensions, "def_list")) - preBlockGamutTransformations.push("definitionLists"); - if (contains(options.extensions, "attr_list")) - extra.attributeBlocks = true; + } + if (contains(options.extensions, "def_list")) { + preBlockGamutTransformations.push("definitionLists"); + } - converter.hooks.chain("postNormalization", function(text) { - return extra.doTransform(postNormalizationTransformations, text); + return extra.doTransform(postNormalizationTransformations, text) + '\n'; }); - // preBlockGamut also gives us access to a hook so we can run the - // block gamut recursively, however we don't need it at this point converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) { + // Keep a reference to the block gamut callback to run recursively extra.blockGamutHookCallback = blockGamutHookCallback; - return extra.doConversion(preBlockGamutTransformations, text); + text = processEscapes(text); + return extra.doTransform(preBlockGamutTransformations, text) + '\n'; }); - // Keep a reference to the hook chain running before finishConversion to apply on hashed extra blocks + // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks extra.previousPostConversion = converter.hooks.postConversion; converter.hooks.chain("postConversion", function(text) { - return extra.finishConversion(text); + text = extra.doTransform(postConversionTransformations, text); + // Clear state vars that may use unnecessary memory + this.hashBlocks = []; + return text; }); if ("highlighter" in options) { @@ -208,35 +215,9 @@ // Do transformations Markdown.Extra.prototype.doTransform = function(transformations, text) { - if (this.attributeBlocks) - text = this.hashFcbAttributeBlocks(text); - for(var i = 0; i < transformations.length; i++) text = this[transformations[i]](text); - - return text + '\n'; - }; - - // Setup state vars, do conversion - Markdown.Extra.prototype.doConversion = function(transformations, text) { - text = processEscapes(text); - - if (this.attributeBlocks) - text = this.hashHeaderAttributeBlocks(text); - - return this.doTransform(transformations, text); - }; - - // Clear state vars that may use unnecessary memory. Unhash blocks we - // stored, apply attribute blocks if necessary, and return converted text. - Markdown.Extra.prototype.finishConversion = function(text) { - text = this.unHashExtraBlocks(text); - - if (this.attributeBlocks) - text = this.applyAttributeBlocks(text); - - this.hashBlocks = []; - return text; + return text; }; // Return a placeholder containing a key, which is the block's index in the @@ -466,6 +447,9 @@ code = code.replace(/&/g, "&"); code = code.replace(//g, ">"); + // These were escaped by PageDown before postNormalization + code = code.replace(/~D/g, "$$"); + code = code.replace(/~T/g, "~"); return code; } diff --git a/js/libs/jquery.mousewheel.js b/js/libs/jquery.mousewheel.js new file mode 100644 index 00000000..9d65c716 --- /dev/null +++ b/js/libs/jquery.mousewheel.js @@ -0,0 +1,117 @@ +/*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net) + * Licensed under the MIT License (LICENSE.txt). + * + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * Thanks to: Seamus Leahy for adding deltaX and deltaY + * + * Version: 3.1.3 + * + * Requires: 1.2.2+ + */ + +(function (factory) { + if ( typeof define === 'function' && define.amd ) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS style for Browserify + module.exports = factory; + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + + var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll']; + var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll']; + var lowestDelta, lowestDeltaXY; + + if ( $.event.fixHooks ) { + for ( var i = toFix.length; i; ) { + $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks; + } + } + + $.event.special.mousewheel = { + setup: function() { + if ( this.addEventListener ) { + for ( var i = toBind.length; i; ) { + this.addEventListener( toBind[--i], handler, false ); + } + } else { + this.onmousewheel = handler; + } + }, + + teardown: function() { + if ( this.removeEventListener ) { + for ( var i = toBind.length; i; ) { + this.removeEventListener( toBind[--i], handler, false ); + } + } else { + this.onmousewheel = null; + } + } + }; + + $.fn.extend({ + mousewheel: function(fn) { + return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); + }, + + unmousewheel: function(fn) { + return this.unbind("mousewheel", fn); + } + }); + + + function handler(event) { + var orgEvent = event || window.event, + args = [].slice.call(arguments, 1), + delta = 0, + deltaX = 0, + deltaY = 0, + absDelta = 0, + absDeltaXY = 0, + fn; + event = $.event.fix(orgEvent); + event.type = "mousewheel"; + + // Old school scrollwheel delta + if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; } + if ( orgEvent.detail ) { delta = orgEvent.detail * -1; } + + // New school wheel delta (wheel event) + if ( orgEvent.deltaY ) { + deltaY = orgEvent.deltaY * -1; + delta = deltaY; + } + if ( orgEvent.deltaX ) { + deltaX = orgEvent.deltaX; + delta = deltaX * -1; + } + + // Webkit + if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; } + if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; } + + // Look for lowest delta to normalize the delta values + absDelta = Math.abs(delta); + if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; } + absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX)); + if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; } + + // Get a whole value for the deltas + fn = delta > 0 ? 'floor' : 'ceil'; + delta = Math[fn](delta / lowestDelta); + deltaX = Math[fn](deltaX / lowestDeltaXY); + deltaY = Math[fn](deltaY / lowestDeltaXY); + + // Add event and delta to the front of the arguments + args.unshift(event, delta, deltaX, deltaY); + + return ($.event.dispatch || $.event.handle).apply(this, args); + } + +})); diff --git a/js/main.js b/js/main.js index e1df25f5..f016b838 100644 --- a/js/main.js +++ b/js/main.js @@ -26,6 +26,9 @@ requirejs.config({ 'libs/jquery.waitforimages': [ 'jquery' ], + 'libs/jquery.mousewheel': [ + 'jquery' + ], 'libs/layout': [ 'libs/jquery-ui' ], diff --git a/js/providers/gdrive-provider.js b/js/providers/gdrive-provider.js index 0f5db6a1..2747d561 100644 --- a/js/providers/gdrive-provider.js +++ b/js/providers/gdrive-provider.js @@ -244,12 +244,11 @@ define([ }; core.onReady(function() { - var state = localStorage[PROVIDER_GDRIVE + ".state"]; + var state = utils.retrieveIgnoreError(PROVIDER_GDRIVE + ".state"); if(state === undefined) { return; } localStorage.removeItem(PROVIDER_GDRIVE + ".state"); - state = JSON.parse(state); if(state.action == "create") { googleHelper.upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE, settings.defaultContent, undefined, function(error, file) { if(error) { diff --git a/js/providers/gplus-provider.js b/js/providers/gplus-provider.js index 26bd3bea..fa1593fc 100644 --- a/js/providers/gplus-provider.js +++ b/js/providers/gplus-provider.js @@ -28,6 +28,7 @@ define([ var importImageCallback = undefined; var imageDoc = undefined; + var importImagePreferences = utils.retrieveIgnoreError(PROVIDER_GPLUS + ".importImagePreferences"); gplusProvider.importImage = function(callback) { importImageCallback = callback; googleHelper.picker(function(error, docs) { @@ -46,9 +47,7 @@ define([ utils.setInputValue("#input-import-image-title", imageDoc.name); // Load preferences - var serializedPreferences = localStorage[PROVIDER_GPLUS + ".importImagePreferences"]; - if(serializedPreferences) { - var importImagePreferences = JSON.parse(serializedPreferences); + if(importImagePreferences) { utils.setInputValue("#input-import-image-size", importImagePreferences.size); } @@ -67,7 +66,7 @@ define([ importImageCallback(undefined, image); // Store import preferences for next time - var importImagePreferences = {}; + importImagePreferences = {}; if(size) { importImagePreferences.size = size; } diff --git a/js/publisher.js b/js/publisher.js index dc78a962..75517d52 100644 --- a/js/publisher.js +++ b/js/publisher.js @@ -30,13 +30,22 @@ define([ // Retrieve publish locations from localStorage _.each(fileSystem, function(fileDesc) { - _.chain(localStorage[fileDesc.fileIndex + ".publish"].split(";")).compact().each(function(publishIndex) { - var publishAttributes = JSON.parse(localStorage[publishIndex]); - // Store publishIndex - publishAttributes.publishIndex = publishIndex; - // Replace provider ID by provider module in attributes - publishAttributes.provider = providerMap[publishAttributes.provider]; - fileDesc.publishLocations[publishIndex] = publishAttributes; + _.each(utils.retrieveIndexArray(fileDesc.fileIndex + ".publish"), function(publishIndex) { + try { + var publishAttributes = JSON.parse(localStorage[publishIndex]); + // Store publishIndex + publishAttributes.publishIndex = publishIndex; + // Replace provider ID by provider module in attributes + publishAttributes.provider = providerMap[publishAttributes.provider]; + fileDesc.publishLocations[publishIndex] = publishAttributes; + } + catch(e) { + // localStorage can be corrupted + extensionMgr.onError(e); + // Remove publish location + utils.removeIndexFromArray(fileDesc.fileIndex + ".publish", publishIndex); + localStorage.removeItem(publishIndex); + } }); }); @@ -151,9 +160,8 @@ define([ $("input:radio[name=radio-publish-format][value=" + defaultPublishFormat + "]").prop("checked", true); // Load preferences - var serializedPreferences = localStorage[provider.providerId + ".publishPreferences"]; - if(serializedPreferences) { - var publishPreferences = JSON.parse(serializedPreferences); + var publishPreferences = utils.retrieveIgnoreError(provider.providerId + ".publishPreferences"); + if(publishPreferences) { _.each(provider.publishPreferencesInputIds, function(inputId) { utils.setInputValue("#input-publish-" + inputId, publishPreferences[inputId]); }); @@ -195,18 +203,6 @@ define([ localStorage[provider.providerId + ".publishPreferences"] = JSON.stringify(publishPreferences); } - // Retrieve file's publish locations from localStorage - publisher.populatePublishLocations = function(fileDesc) { - _.chain(localStorage[fileDesc.fileIndex + ".publish"].split(";")).compact().each(function(publishIndex) { - var publishAttributes = JSON.parse(localStorage[publishIndex]); - // Store publishIndex - publishAttributes.publishIndex = publishIndex; - // Replace provider ID by provider module in attributes - publishAttributes.provider = providerMap[publishAttributes.provider]; - fileDesc.publishLocations[publishIndex] = publishAttributes; - }); - }; - core.onReady(function() { // Add every provider var publishMenu = $("#publish-menu"); diff --git a/js/settings.js b/js/settings.js index 03d99b58..8c7ee974 100644 --- a/js/settings.js +++ b/js/settings.js @@ -26,9 +26,12 @@ define([ extensionSettings: {} }; - if(_.has(localStorage, "settings")) { + try { _.extend(settings, JSON.parse(localStorage.settings)); } + catch(e) { + // Ignore parsing error + } return settings; }); \ No newline at end of file diff --git a/js/storage.js b/js/storage.js index cb8072eb..0e5da2ed 100644 --- a/js/storage.js +++ b/js/storage.js @@ -1,13 +1,10 @@ // Setup an empty localStorage or upgrade an existing one define([ - "underscore" -], function(_) { + "underscore", + "utils" +], function(_, utils) { - // Create the file system if not exist - if(localStorage["file.list"] === undefined) { - localStorage["file.list"] = ";"; - } - var fileIndexList = _.compact(localStorage["file.list"].split(";")); + var fileIndexList = utils.retrieveIndexArray("file.list"); // localStorage versioning var version = localStorage["version"]; @@ -22,7 +19,7 @@ define([ _.each(fileIndexList, function(fileIndex) { localStorage[fileIndex + ".publish"] = ";"; - var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); + var syncIndexList = utils.retrieveIndexArray(fileIndex + ".sync"); _.each(syncIndexList, function(syncIndex) { localStorage[syncIndex + ".contentCRC"] = "0"; // We store title CRC only for Google Drive synchronization @@ -52,7 +49,7 @@ define([ var SYNC_PROVIDER_GDRIVE = "sync." + PROVIDER_GDRIVE + "."; var SYNC_PROVIDER_DROPBOX = "sync." + PROVIDER_DROPBOX + "."; _.each(fileIndexList, function(fileIndex) { - var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); + var syncIndexList = utils.retrieveIndexArray(fileIndex + ".sync"); _.each(syncIndexList, function(syncIndex) { var syncAttributes = {}; if(syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { @@ -85,7 +82,7 @@ define([ localStorage.removeItem(fileIndex + ".title"); localStorage.removeItem(fileIndex + ".publish"); localStorage.removeItem(fileIndex + ".content"); - localStorage["file.list"] = localStorage["file.list"].replace(";" + fileIndex + ";", ";"); + utils.removeIndexFromArray("file.list", fileIndex); } }); version = "v3"; @@ -110,7 +107,7 @@ define([ // Upgrade from v5 to v6 if(version == "v5") { _.each(fileIndexList, function(fileIndex) { - var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); + var publishIndexList = utils.retrieveIndexArray(fileIndex + ".publish"); _.each(publishIndexList, function(publishIndex) { var publishAttributes = JSON.parse(localStorage[publishIndex]); if(publishAttributes.provider == "gdrive") { diff --git a/js/synchronizer.js b/js/synchronizer.js index d6915a22..20c814a6 100644 --- a/js/synchronizer.js +++ b/js/synchronizer.js @@ -22,13 +22,22 @@ define([ // Retrieve sync locations from localStorage _.each(fileSystem, function(fileDesc) { - _.chain(localStorage[fileDesc.fileIndex + ".sync"].split(";")).compact().each(function(syncIndex) { - var syncAttributes = JSON.parse(localStorage[syncIndex]); - // Store syncIndex - syncAttributes.syncIndex = syncIndex; - // Replace provider ID by provider module in attributes - syncAttributes.provider = providerMap[syncAttributes.provider]; - fileDesc.syncLocations[syncIndex] = syncAttributes; + _.each(utils.retrieveIndexArray(fileDesc.fileIndex + ".sync"), function(syncIndex) { + try { + var syncAttributes = JSON.parse(localStorage[syncIndex]); + // Store syncIndex + syncAttributes.syncIndex = syncIndex; + // Replace provider ID by provider module in attributes + syncAttributes.provider = providerMap[syncAttributes.provider]; + fileDesc.syncLocations[syncIndex] = syncAttributes; + } + catch(e) { + // localStorage can be corrupted + extensionMgr.onError(e); + // Remove sync location + utils.removeIndexFromArray(fileDesc.fileIndex + ".sync", syncIndex); + localStorage.removeItem(syncIndex); + } }); }); @@ -191,9 +200,8 @@ define([ utils.resetModalInputs(); // Load preferences - var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"]; - if(serializedPreferences) { - var exportPreferences = JSON.parse(serializedPreferences); + var exportPreferences = utils.retrieveIgnoreError(provider.providerId + ".exportPreferences"); + if(exportPreferences) { _.each(provider.exportPreferencesInputIds, function(inputId) { utils.setInputValue("#input-sync-export-" + inputId, exportPreferences[inputId]); }); diff --git a/js/utils.js b/js/utils.js index 6d9b3658..5d7483f4 100644 --- a/js/utils.js +++ b/js/utils.js @@ -196,7 +196,7 @@ define([ }; utils.updateCurrentTime(); - // Serialize sync/publish attributes and store it in the fileStorage + // Serialize sync/publish attributes and store it in the localStorage utils.storeAttributes = function(attributes) { var storeIndex = attributes.syncIndex || attributes.publishIndex; // Don't store sync/publish index @@ -205,7 +205,38 @@ define([ attributes.provider = attributes.provider.providerId; localStorage[storeIndex] = JSON.stringify(attributes); }; + + // Retrieve/parse an index array from localStorage + utils.retrieveIndexArray = function(storeIndex) { + try { + return _.compact(localStorage[storeIndex].split(";")); + } + catch(e) { + localStorage[storeIndex] = ";"; + return []; + } + }; + + // Append an index to an array in localStorage + utils.appendIndexToArray = function(storeIndex, index) { + localStorage[storeIndex] += index + ";"; + }; + + // Remove an index from an array in localStorage + utils.removeIndexFromArray = function(storeIndex, index) { + localStorage[storeIndex] = localStorage[storeIndex].replace(";" + index + ";", ";"); + }; + // Retrieve/parse an object from localStorage. Returns undefined if error. + utils.retrieveIgnoreError = function(storeIndex) { + try { + return JSON.parse(localStorage[storeIndex]); + } + catch(e) { + return undefined; + } + }; + // Base64 conversion utils.encodeBase64 = function(str) { if(str.length === 0) {