diff --git a/css/default.css b/css/default.css index 8f13ff58..249510e7 100644 --- a/css/default.css +++ b/css/default.css @@ -327,7 +327,6 @@ hr { div.dropdown-menu { padding: 5px 20px; - white-space: normal; } div.dropdown-menu p, @@ -335,12 +334,17 @@ div.dropdown-menu blockquote { margin: 10px 0; } +div.dropdown-menu .stat { + margin-bottom: 10px; +} + div.dropdown-menu i { margin-right: 0; } #link-container { min-width: 210px; + white-space: normal; } #link-container .link-list { @@ -518,6 +522,10 @@ div.dropdown-menu i { text-align: left; } +#modal-settings .accordion-inner .form-inline .label-text { + margin: 0 10px; +} + .accordion-toggle { cursor: help; } diff --git a/doc/img/architecture.png b/doc/img/architecture.png new file mode 100644 index 00000000..812278e0 Binary files /dev/null and b/doc/img/architecture.png differ diff --git a/img/glyphicons-halflings-white.png b/img/glyphicons-halflings-white.png index 82022a63..25df1419 100644 Binary files a/img/glyphicons-halflings-white.png and b/img/glyphicons-halflings-white.png differ diff --git a/js/async-runner.js b/js/async-runner.js index 972ea156..4e8edffb 100644 --- a/js/async-runner.js +++ b/js/async-runner.js @@ -9,184 +9,185 @@ define([ "extension-manager" ], function(_, core, utils, extensionMgr) { - var asyncRunner = {}; + var asyncRunner = {}; - var taskQueue = []; - var asyncRunning = false; - var currentTask = undefined; - var currentTaskRunning = false; - var currentTaskStartTime = 0; + var taskQueue = []; + var asyncRunning = false; + var currentTask = undefined; + var currentTaskRunning = false; + var currentTaskStartTime = 0; - asyncRunner.createTask = function() { - var task = {}; - task.finished = false; - task.timeout = ASYNC_TASK_DEFAULT_TIMEOUT; - task.retryCounter = 0; - /** - * onRun callbacks are called by chain(). These callbacks have to call - * chain() themselves to chain with next onRun callback or error() to - * throw an exception or retry() to restart the task. - */ - // Run callbacks - task.runCallbacks = []; - task.onRun = function(callback) { - task.runCallbacks.push(callback); - }; - /** - * onSuccess callbacks are called when every onRun callbacks have - * succeed. - */ - task.successCallbacks = []; - task.onSuccess = function(callback) { - task.successCallbacks.push(callback); - }; - /** - * onError callbacks are called when error() is called in a onRun - * callback. - */ - task.errorCallbacks = []; - task.onError = function(callback) { - task.errorCallbacks.push(callback); - }; - /** - * chain() calls the next onRun callback or the onSuccess callbacks when - * finished. The optional callback parameter can be used to pass an onRun - * callback during execution. - */ - task.chain = function(callback) { - if (task.finished === true) { - return; - } - // If first execution - if (task.queue === undefined) { - // Create a copy of the onRun callbacks - task.queue = task.runCallbacks.slice(); - } - // If a callback is passed as a parameter - if(callback !== undefined) { - callback(); - return; - } - // If all callbacks have been run - if (task.queue.length === 0) { - // Run the onSuccess callbacks - runSafe(task, task.successCallbacks); - return; - } - // Run the next callback - var runCallback = task.queue.shift(); - runCallback(); - }; - /** - * error() calls the onError callbacks passing the error parameter and ends - * the task by throwing an exception. - */ - task.error = function(error) { - if (task.finished === true) { - return; - } - error = error || new Error("Unknown error"); - if(error.message) { - extensionMgr.onError(error); - } - runSafe(task, task.errorCallbacks, error); - // Exit the current call stack - throw error; - }; - /** - * retry() can be called in an onRun callback to restart the task - */ - task.retry = function(error, maxRetryCounter) { - if (task.finished === true) { - return; - } - maxRetryCounter = maxRetryCounter || 5; - task.queue = undefined; - if (task.retryCounter >= maxRetryCounter) { - task.error(error); - return; - } - // Implement an exponential backoff - var delay = Math.pow(2, task.retryCounter++) * 1000; - currentTaskStartTime = utils.currentTime + delay; - currentTaskRunning = false; - asyncRunner.runTask(); - }; - return task; - }; + asyncRunner.createTask = function() { + var task = {}; + task.finished = false; + task.timeout = ASYNC_TASK_DEFAULT_TIMEOUT; + task.retryCounter = 0; + /** + * onRun callbacks are called by chain(). These callbacks have to call + * chain() themselves to chain with next onRun callback or error() to + * throw an exception or retry() to restart the task. + */ + // Run callbacks + task.runCallbacks = []; + task.onRun = function(callback) { + task.runCallbacks.push(callback); + }; + /** + * onSuccess callbacks are called when every onRun callbacks have + * succeed. + */ + task.successCallbacks = []; + task.onSuccess = function(callback) { + task.successCallbacks.push(callback); + }; + /** + * onError callbacks are called when error() is called in a onRun + * callback. + */ + task.errorCallbacks = []; + task.onError = function(callback) { + task.errorCallbacks.push(callback); + }; + /** + * chain() calls the next onRun callback or the onSuccess callbacks when + * finished. The optional callback parameter can be used to pass an + * onRun callback during execution. + */ + task.chain = function(callback) { + if(task.finished === true) { + return; + } + // If first execution + if(task.queue === undefined) { + // Create a copy of the onRun callbacks + task.queue = task.runCallbacks.slice(); + } + // If a callback is passed as a parameter + if(callback !== undefined) { + callback(); + return; + } + // If all callbacks have been run + if(task.queue.length === 0) { + // Run the onSuccess callbacks + runSafe(task, task.successCallbacks); + return; + } + // Run the next callback + var runCallback = task.queue.shift(); + runCallback(); + }; + /** + * error() calls the onError callbacks passing the error parameter and + * ends the task by throwing an exception. + */ + task.error = function(error) { + if(task.finished === true) { + return; + } + error = error || new Error("Unknown error"); + if(error.message) { + extensionMgr.onError(error); + } + runSafe(task, task.errorCallbacks, error); + // Exit the current call stack + throw error; + }; + /** + * retry() can be called in an onRun callback to restart the task + */ + task.retry = function(error, maxRetryCounter) { + if(task.finished === true) { + return; + } + maxRetryCounter = maxRetryCounter || 5; + task.queue = undefined; + if(task.retryCounter >= maxRetryCounter) { + task.error(error); + return; + } + // Implement an exponential backoff + var delay = Math.pow(2, task.retryCounter++) * 1000; + currentTaskStartTime = utils.currentTime + delay; + currentTaskRunning = false; + asyncRunner.runTask(); + }; + return task; + }; - // Run the next task in the queue if any and no other running - asyncRunner.runTask = function() { - // Use defer to avoid stack overflow - _.defer(function() { + // Run the next task in the queue if any and no other running + asyncRunner.runTask = function() { + // Use defer to avoid stack overflow + _.defer(function() { - // If there is a task currently running - if (currentTaskRunning === true) { - // If the current task takes too long - if (currentTaskStartTime + currentTask.timeout < utils.currentTime) { - currentTask.error(new Error("A timeout occurred.")); - } - return; - } + // If there is a task currently running + if(currentTaskRunning === true) { + // If the current task takes too long + if(currentTaskStartTime + currentTask.timeout < utils.currentTime) { + currentTask.error(new Error("A timeout occurred.")); + } + return; + } - if (currentTask === undefined) { - // If no task in the queue - if (taskQueue.length === 0) { - return; - } + if(currentTask === undefined) { + // If no task in the queue + if(taskQueue.length === 0) { + return; + } - // Dequeue an enqueued task - currentTask = taskQueue.shift(); - currentTaskStartTime = utils.currentTime; - if(asyncRunning === false) { - asyncRunning = true; - extensionMgr.onAsyncRunning(true); - } - } + // Dequeue an enqueued task + currentTask = taskQueue.shift(); + currentTaskStartTime = utils.currentTime; + if(asyncRunning === false) { + asyncRunning = true; + extensionMgr.onAsyncRunning(true); + } + } - // Run the task - if (currentTaskStartTime <= utils.currentTime) { - currentTaskRunning = true; - currentTask.chain(); - } - }); - }; - // Run runTask function periodically - core.addPeriodicCallback(asyncRunner.runTask); + // Run the task + if(currentTaskStartTime <= utils.currentTime) { + currentTaskRunning = true; + currentTask.chain(); + } + }); + }; + // Run runTask function periodically + core.addPeriodicCallback(asyncRunner.runTask); - function runSafe(task, callbacks, param) { - try { - _.each(callbacks, function(callback) { - callback(param); - }); - } - finally { - task.finished = true; - if (currentTask === task) { - currentTask = undefined; - currentTaskRunning = false; - } - if (taskQueue.length === 0) { - asyncRunning = false; - extensionMgr.onAsyncRunning(false); - } else { - asyncRunner.runTask(); - } - } - } + function runSafe(task, callbacks, param) { + try { + _.each(callbacks, function(callback) { + callback(param); + }); + } + finally { + task.finished = true; + if(currentTask === task) { + currentTask = undefined; + currentTaskRunning = false; + } + if(taskQueue.length === 0) { + asyncRunning = false; + extensionMgr.onAsyncRunning(false); + } + else { + asyncRunner.runTask(); + } + } + } - // Add a task to the queue - asyncRunner.addTask = function(task) { - taskQueue.push(task); - asyncRunner.runTask(); - }; + // Add a task to the queue + asyncRunner.addTask = function(task) { + taskQueue.push(task); + asyncRunner.runTask(); + }; - // Change current task timeout - asyncRunner.setCurrentTaskTimeout = function(timeout) { - if (currentTask !== undefined) { - currentTask.timeout = timeout; - } - }; + // Change current task timeout + asyncRunner.setCurrentTaskTimeout = function(timeout) { + if(currentTask !== undefined) { + currentTask.timeout = timeout; + } + }; - return asyncRunner; + return asyncRunner; }); diff --git a/js/blogger-provider.js b/js/blogger-provider.js index 8e65ce81..c63ae202 100644 --- a/js/blogger-provider.js +++ b/js/blogger-provider.js @@ -1,59 +1,51 @@ define([ - "underscore", + "underscore", "utils", "google-helper" ], function(_, utils, googleHelper) { - - var PROVIDER_BLOGGER = "blogger"; - var bloggerProvider = { - providerId: PROVIDER_BLOGGER, - providerName: "Blogger", - defaultPublishFormat: "html", - publishPreferencesInputIds: ["blogger-url"] - }; - - bloggerProvider.publish = function(publishAttributes, title, content, callback) { - googleHelper.uploadBlogger( - publishAttributes.blogUrl, - publishAttributes.blogId, - publishAttributes.postId, - publishAttributes.labelList, - title, - content, - function(error, blogId, postId) { - if(error) { - callback(error); - return; - } - publishAttributes.blogId = blogId; - publishAttributes.postId = postId; - callback(); - } - ); - }; - - bloggerProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - var blogUrl = utils.getInputTextValue("#input-publish-blogger-url", event); - if(blogUrl !== undefined) { - publishAttributes.blogUrl = utils.checkUrl(blogUrl); - } - publishAttributes.postId = utils.getInputTextValue("#input-publish-postid"); - publishAttributes.labelList = []; - var labels = utils.getInputTextValue("#input-publish-labels"); - if(labels !== undefined) { - publishAttributes.labelList = _.chain( - labels.split(",") - ).map(function(label) { - return utils.trim(label); - }).compact().value(); - } - if(event.isPropagationStopped()) { - return undefined; - } - return publishAttributes; - }; + var PROVIDER_BLOGGER = "blogger"; - return bloggerProvider; + var bloggerProvider = { + providerId: PROVIDER_BLOGGER, + providerName: "Blogger", + defaultPublishFormat: "html", + publishPreferencesInputIds: [ + "blogger-url" + ] + }; + + bloggerProvider.publish = function(publishAttributes, title, content, callback) { + googleHelper.uploadBlogger(publishAttributes.blogUrl, publishAttributes.blogId, publishAttributes.postId, publishAttributes.labelList, title, content, function(error, blogId, postId) { + if(error) { + callback(error); + return; + } + publishAttributes.blogId = blogId; + publishAttributes.postId = postId; + callback(); + }); + }; + + bloggerProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + var blogUrl = utils.getInputTextValue("#input-publish-blogger-url", event); + if(blogUrl !== undefined) { + publishAttributes.blogUrl = utils.checkUrl(blogUrl); + } + publishAttributes.postId = utils.getInputTextValue("#input-publish-postid"); + publishAttributes.labelList = []; + var labels = utils.getInputTextValue("#input-publish-labels"); + if(labels !== undefined) { + publishAttributes.labelList = _.chain(labels.split(",")).map(function(label) { + return utils.trim(label); + }).compact().value(); + } + if(event.isPropagationStopped()) { + return undefined; + } + return publishAttributes; + }; + + return bloggerProvider; }); \ No newline at end of file diff --git a/js/config.js b/js/config.js index e73fa82c..03b54de6 100644 --- a/js/config.js +++ b/js/config.js @@ -1,8 +1,10 @@ var MAIN_URL = "http://benweet.github.io/stackedit/"; var GOOGLE_API_KEY = "AIzaSyAeCU8CGcSkn0z9js6iocHuPBX4f_mMWkw"; -var GOOGLE_SCOPES = [ "https://www.googleapis.com/auth/drive.install", - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/blogger" ]; +var GOOGLE_SCOPES = [ + "https://www.googleapis.com/auth/drive.install", + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/blogger" +]; var GOOGLE_DRIVE_APP_ID = "241271498917"; var DROPBOX_APP_KEY = "lq6mwopab8wskas"; var DROPBOX_APP_SECRET = "851fgnucpezy84t"; @@ -14,7 +16,7 @@ var AJAX_TIMEOUT = 30000; var ASYNC_TASK_DEFAULT_TIMEOUT = 60000; var ASYNC_TASK_LONG_TIMEOUT = 120000; var SYNC_PERIOD = 180000; -var USER_IDLE_THRESHOLD = 300000; +var USER_IDLE_THRESHOLD = 300000; var TEMPORARY_FILE_INDEX = "file.tempIndex"; var WELCOME_DOCUMENT_TITLE = "Welcome document"; var DOWNLOAD_PROXY_URL = "http://stackedit-download-proxy.herokuapp.com/"; @@ -25,9 +27,9 @@ var SSH_PROXY_URL = "http://stackedit-ssh-proxy.herokuapp.com/"; // Use by Google's client.js var delayedFunction = undefined; function runDelayedFunction() { - if (delayedFunction !== undefined) { - delayedFunction(); - } + if(delayedFunction !== undefined) { + delayedFunction(); + } } // Site dependent @@ -38,15 +40,15 @@ var GATEKEEPER_URL = "http://stackedit-gatekeeper-localhost.herokuapp.com/"; var TUMBLR_PROXY_URL = "http://stackedit-tumblr-proxy-local.herokuapp.com/"; if(location.hostname.indexOf("benweet.github.io") === 0) { - BASE_URL = MAIN_URL; - GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com'; - GITHUB_CLIENT_ID = 'fa0d09514da8377ee32e'; - GATEKEEPER_URL = "http://stackedit-gatekeeper.herokuapp.com/"; - TUMBLR_PROXY_URL = "http://stackedit-tumblr-proxy.herokuapp.com/"; + BASE_URL = MAIN_URL; + GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com'; + GITHUB_CLIENT_ID = 'fa0d09514da8377ee32e'; + GATEKEEPER_URL = "http://stackedit-gatekeeper.herokuapp.com/"; + TUMBLR_PROXY_URL = "http://stackedit-tumblr-proxy.herokuapp.com/"; } var THEME_LIST = { - "": "Default", - "blue-gray": "Blue-Gray", - "night": "Night" + "": "Default", + "blue-gray": "Blue-Gray", + "night": "Night" }; diff --git a/js/core.js b/js/core.js index 4d748c3c..b406b6f2 100644 --- a/js/core.js +++ b/js/core.js @@ -1,6 +1,6 @@ define([ "jquery", - "underscore", + "underscore", "utils", "settings", "extension-manager", @@ -10,461 +10,446 @@ define([ "lib/layout", "lib/Markdown.Editor" ], function($, _, utils, settings, extensionMgr) { - - var core = {}; - - // Used for periodic tasks - var intervalId = undefined; - var periodicCallbacks = []; - core.addPeriodicCallback = function(callback) { - periodicCallbacks.push(callback); - }; - - // Used to detect user activity - var userReal = false; - var userActive = false; - var windowUnique = true; - var userLastActivity = 0; - function setUserActive() { - userReal = true; - userActive = true; - userLastActivity = utils.currentTime; - }; - function isUserActive() { - if(userActive === true - && utils.currentTime - userLastActivity > USER_IDLE_THRESHOLD) { - userActive = false; - } - return userActive && windowUnique; - } - - // Used to only have 1 window of the application in the same browser - var windowId = undefined; - function checkWindowUnique() { - if(userReal === false || windowUnique === false) { - return; - } - if(windowId === undefined) { - windowId = utils.randomString(); - localStorage["frontWindowId"] = windowId; - } - var frontWindowId = localStorage["frontWindowId"]; - if(frontWindowId != windowId) { - windowUnique = false; - if(intervalId !== undefined) { - clearInterval(intervalId); - } - $(".modal").modal("hide"); - $('#modal-non-unique').modal({ - backdrop: "static", - keyboard: false - }); - } - } - - // Offline management - core.isOffline = false; - var offlineTime = utils.currentTime; - core.setOffline = function() { - offlineTime = utils.currentTime; - if(core.isOffline === false) { - core.isOffline = true; - extensionMgr.onOfflineChanged(true); - } - }; - function setOnline() { - if(core.isOffline === true) { - core.isOffline = false; - extensionMgr.onOfflineChanged(false); - } - } - function checkOnline() { - // Try to reconnect if we are offline but we have some network - if (core.isOffline === true && navigator.onLine === true - && offlineTime + CHECK_ONLINE_PERIOD < utils.currentTime) { - offlineTime = utils.currentTime; - // Try to download anything to test the connection - $.ajax({ - url : "//www.google.com/jsapi", - timeout : AJAX_TIMEOUT, dataType : "script" - }).done(function() { - setOnline(); - }); - } - } - - // Load settings in settings dialog - function loadSettings() { - - // Layout orientation - utils.setInputRadio("radio-layout-orientation", settings.layoutOrientation); - // Theme - utils.setInputValue("#input-settings-theme", localStorage.theme); - // Lazy rendering - utils.setInputChecked("#input-settings-lazy-rendering", settings.lazyRendering); - // Editor font size - utils.setInputValue("#input-settings-editor-font-size", settings.editorFontSize); - // Default content - utils.setInputValue("#textarea-settings-default-content", settings.defaultContent); - // Commit message - utils.setInputValue("#input-settings-publish-commit-msg", settings.commitMsg); - // Template - utils.setInputValue("#textarea-settings-publish-template", settings.template); - // SSH proxy - utils.setInputValue("#input-settings-ssh-proxy", settings.sshProxy); - - // Load extension settings - extensionMgr.onLoadSettings(); - } - // Save settings from settings dialog - function saveSettings(event) { - var newSettings = {}; - - // Layout orientation - newSettings.layoutOrientation = utils.getInputRadio("radio-layout-orientation"); - // Theme - var theme = utils.getInputValue("#input-settings-theme"); - // Lazy Rendering - newSettings.lazyRendering = utils.getInputChecked("#input-settings-lazy-rendering"); - // Editor font size - newSettings.editorFontSize = utils.getInputIntValue("#input-settings-editor-font-size", event, 1, 99); - // Default content - newSettings.defaultContent = utils.getInputValue("#textarea-settings-default-content"); - // Commit message - newSettings.commitMsg = utils.getInputTextValue("#input-settings-publish-commit-msg", event); - // Template - newSettings.template = utils.getInputTextValue("#textarea-settings-publish-template", event); - // SSH proxy - newSettings.sshProxy = utils.checkUrl(utils.getInputTextValue("#input-settings-ssh-proxy", event), true); - - // Save extension settings - newSettings.extensionSettings = {}; - extensionMgr.onSaveSettings(newSettings.extensionSettings, event); - - if(!event.isPropagationStopped()) { - $.extend(settings, newSettings); - localStorage.settings = JSON.stringify(settings); - localStorage.theme = theme; - } - } - - // Create the layout - var layout = undefined; - core.createLayout = function() { - if(viewerMode === true) { - return; - } - var layoutGlobalConfig = { - closable : true, - resizable : false, - slidable : false, - livePaneResizing : true, - enableCursorHotkey : false, - spacing_open : 15, - spacing_closed : 15, - togglerLength_open : 90, - togglerLength_closed : 90, - stateManagement__enabled : false, - center__minWidth : 200, - center__minHeight : 200 - }; - extensionMgr.onLayoutConfigure(layoutGlobalConfig); - if (settings.layoutOrientation == "horizontal") { - $(".ui-layout-south").remove(); - $(".ui-layout-east").addClass("well").prop("id", "wmd-preview"); - layout = $('body').layout( - $.extend(layoutGlobalConfig, { - east__resizable : true, - east__size : .5, - east__minSize : 200 - }) - ); - } else if (settings.layoutOrientation == "vertical") { - $(".ui-layout-east").remove(); - $(".ui-layout-south").addClass("well").prop("id", "wmd-preview"); - layout = $('body').layout( - $.extend(layoutGlobalConfig, { - south__resizable : true, - south__size : .5, - south__minSize : 200 - }) - ); - } - $(".ui-layout-toggler-north").addClass("btn").append( - $("").addClass("caret")); - $(".ui-layout-toggler-south").addClass("btn").append( - $("").addClass("caret")); - $(".ui-layout-toggler-east").addClass("btn").append( - $("").addClass("caret")); - $("#navbar").click(function() { - layout.allowOverflow('north'); - }); - - extensionMgr.onLayoutCreated(layout); - }; - - // Create the PageDown editor - var insertLinkCallback = undefined; - core.createEditor = function(onTextChange) { - var converter = new Markdown.Converter(); - var editor = new Markdown.Editor(converter); - // Custom insert link dialog - editor.hooks.set("insertLinkDialog", function (callback) { - insertLinkCallback = callback; - utils.resetModalInputs(); - $("#modal-insert-link").modal(); - _.defer(function() { - $("#input-insert-link").focus(); - }); - return true; - }); - // Custom insert image dialog - editor.hooks.set("insertImageDialog", function (callback) { - insertLinkCallback = callback; - utils.resetModalInputs(); - $("#modal-insert-image").modal(); - _.defer(function() { - $("#input-insert-image").focus(); - }); - return true; - }); - - var documentContent = undefined; - function checkDocumentChanges() { - var newDocumentContent = $("#wmd-input").val(); - if(documentContent !== undefined && documentContent != newDocumentContent) { - onTextChange(); - } - documentContent = newDocumentContent; - } - var previewWrapper = undefined; - if(settings.lazyRendering === true) { - previewWrapper = function(makePreview) { - var debouncedMakePreview = _.debounce(makePreview, 500); - return function() { - if(documentContent === undefined) { - makePreview(); - } - else { - debouncedMakePreview(); - } - checkDocumentChanges(); - }; - }; - } - else { - previewWrapper = function(makePreview) { - return function() { - checkDocumentChanges(); - makePreview(); - }; - }; - } - editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview); - extensionMgr.onEditorConfigure(editor); - - $("#wmd-input, #wmd-preview").scrollTop(0); - $("#wmd-button-bar").empty(); - editor.run(previewWrapper); - firstChange = false; + var core = {}; - // Hide default buttons - $(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)") - .addClass("btn").css("left", 0).find("span").hide(); - - // Add customized buttons - $("#wmd-bold-button").append($("").addClass("icon-bold")); - $("#wmd-italic-button").append($("").addClass("icon-italic")); - $("#wmd-link-button").append($("").addClass("icon-globe")); - $("#wmd-quote-button").append($("").addClass("icon-indent-left")); - $("#wmd-code-button").append($("").addClass("icon-code")); - $("#wmd-image-button").append($("").addClass("icon-picture")); - $("#wmd-olist-button").append($("").addClass("icon-numbered-list")); - $("#wmd-ulist-button").append($("").addClass("icon-list")); - $("#wmd-heading-button").append($("").addClass("icon-text-height")); - $("#wmd-hr-button").append($("").addClass("icon-hr")); - $("#wmd-undo-button").append($("").addClass("icon-undo")); - $("#wmd-redo-button").append($("").addClass("icon-share-alt")); - }; + // Used for periodic tasks + var intervalId = undefined; + var periodicCallbacks = []; + core.addPeriodicCallback = function(callback) { + periodicCallbacks.push(callback); + }; - // onReady event callbacks - var readyCallbacks = []; - core.onReady = function(callback) { - readyCallbacks.push(callback); - runReadyCallbacks(); - }; - var ready = false; - core.setReady = function() { - ready = true; - runReadyCallbacks(); - }; - function runReadyCallbacks() { - if(ready === true) { - _.each(readyCallbacks, function(callback) { - callback(); - }); - readyCallbacks = []; - } - } - - core.onReady(extensionMgr.onReady); - core.onReady(function() { - - // Load theme list - _.each(THEME_LIST, function(name, value) { - $("#input-settings-theme").append($('')); - }); - - // listen to online/offline events - $(window).on('offline', core.setOffline); - $(window).on('online', setOnline); - if (navigator.onLine === false) { - core.setOffline(); - } - - // Detect user activity - $(document).mousemove(setUserActive).keypress(setUserActive); - - // Avoid dropdown to close when clicking on submenu - $(".dropdown-submenu > a").click(function(e) { - e.stopPropagation(); - }); - - // Click events on "insert link" and "insert image" dialog buttons - $(".action-insert-link").click(function(e) { - var value = utils.getInputTextValue($("#input-insert-link"), e); - if(value !== undefined) { - insertLinkCallback(value); - } - }); - $(".action-insert-image").click(function(e) { - var value = utils.getInputTextValue($("#input-insert-image"), e); - if(value !== undefined) { - insertLinkCallback(value); - } - }); - $(".action-close-insert-link").click(function(e) { - insertLinkCallback(null); - }); + // Used to detect user activity + var userReal = false; + var userActive = false; + var windowUnique = true; + var userLastActivity = 0; + function setUserActive() { + userReal = true; + userActive = true; + userLastActivity = utils.currentTime; + } - // Settings loading/saving - $(".action-load-settings").click(function() { - loadSettings(); - }); - $(".action-apply-settings").click(function(e) { - saveSettings(e); - if(!e.isPropagationStopped()) { - window.location.reload(); - } - }); - - $(".action-default-settings").click(function() { - localStorage.removeItem("settings"); - localStorage.removeItem("theme"); - window.location.reload(); - }); - - $(".action-app-reset").click(function() { - localStorage.clear(); - window.location.reload(); - }); - - // UI layout - $("#menu-bar, .ui-layout-center, .ui-layout-east, .ui-layout-south").removeClass("hide"); - core.createLayout(); + function isUserActive() { + if(userActive === true && utils.currentTime - userLastActivity > USER_IDLE_THRESHOLD) { + userActive = false; + } + return userActive && windowUnique; + } - // Editor's textarea - $("#wmd-input, #md-section-helper").css({ - // Apply editor font size - "font-size": settings.editorFontSize + "px", - "line-height": Math.round(settings.editorFontSize * (20/14)) + "px" - }); - - // Manage tab key - $("#wmd-input").keydown(function(e) { - if(e.keyCode === 9) { - var value = $(this).val(); - var start = this.selectionStart; - var end = this.selectionEnd; - // IE8 does not support selection attributes - if(start === undefined || end === undefined) { - return; - } - $(this).val(value.substring(0, start) + "\t" + value.substring(end)); - this.selectionStart = this.selectionEnd = start + 1; - e.preventDefault(); - } - }); + // Used to only have 1 window of the application in the same browser + var windowId = undefined; + function checkWindowUnique() { + if(userReal === false || windowUnique === false) { + return; + } + if(windowId === undefined) { + windowId = utils.randomString(); + localStorage["frontWindowId"] = windowId; + } + var frontWindowId = localStorage["frontWindowId"]; + if(frontWindowId != windowId) { + windowUnique = false; + if(intervalId !== undefined) { + clearInterval(intervalId); + } + $(".modal").modal("hide"); + $('#modal-non-unique').modal({ + backdrop: "static", + keyboard: false + }); + } + } - // Tooltips - $(".tooltip-scroll-link").tooltip({ - html: true, - container: '#modal-settings', - placement: 'right', - title: ['Scroll Link is a feature that binds together editor and preview scrollbars. ', - 'It allows you to keep an eye on the preview while scrolling the editor and vice versa. ', - '

', - 'The mapping between Markdown and HTML is based on the position of the title elements (h1, h2, ...) in the page. ', - 'Therefore, if your document does not contain any title, the mapping will be linear and consequently less efficient.', - ].join("") - }); - $(".tooltip-lazy-rendering").tooltip({ - container: '#modal-settings', - placement: 'right', - title: 'Disable preview rendering while typing in order to offload CPU. Refresh preview after 500 ms of inactivity.' - }); - $(".tooltip-default-content").tooltip({ - html: true, - container: '#modal-settings', - placement: 'right', - title: 'Thanks for supporting StackEdit by adding a backlink in your documents!' - }); - $(".tooltip-template").tooltip({ - html: true, - container: '#modal-settings', - placement: 'right', - trigger: 'manual', - title: ['Available variables:
', - '
  • documentTitle: document title
  • ', - '
  • documentMarkdown: document in Markdown format
  • ', - '
  • documentHTML: document in HTML format
  • ', - '
  • publishAttributes: attributes of the publish location (undefined when using "Save")
', - 'Examples:
', - _.escape('<%= documentTitle %>'), - '
', - _.escape('
<%- documentHTML %>
'), - '
', - _.escape('<% if(publishAttributes.provider == "github") print(documentMarkdown); %>'), - '

More info', - ].join("") - }).click(function(e) { - $(this).tooltip('show'); - e.stopPropagation(); - }); - - $(document).click(function(e) { - $(".tooltip-template").tooltip('hide'); - }); + // Offline management + core.isOffline = false; + var offlineTime = utils.currentTime; + core.setOffline = function() { + offlineTime = utils.currentTime; + if(core.isOffline === false) { + core.isOffline = true; + extensionMgr.onOfflineChanged(true); + } + }; + function setOnline() { + if(core.isOffline === true) { + core.isOffline = false; + extensionMgr.onOfflineChanged(false); + } + } + function checkOnline() { + // Try to reconnect if we are offline but we have some network + if(core.isOffline === true && navigator.onLine === true && offlineTime + CHECK_ONLINE_PERIOD < utils.currentTime) { + offlineTime = utils.currentTime; + // Try to download anything to test the connection + $.ajax({ + url: "//www.google.com/jsapi", + timeout: AJAX_TIMEOUT, + dataType: "script" + }).done(function() { + setOnline(); + }); + } + } - // Reset inputs - $(".action-reset-input").click(function() { - utils.resetModalInputs(); - }); - - // Do periodic tasks - intervalId = window.setInterval(function() { - utils.updateCurrentTime(); - checkWindowUnique(); - if(isUserActive() === true || viewerMode === true) { - _.each(periodicCallbacks, function(callback) { - callback(); - }); - checkOnline(); - } - }, 1000); - }); + // Load settings in settings dialog + function loadSettings() { - return core; + // Layout orientation + utils.setInputRadio("radio-layout-orientation", settings.layoutOrientation); + // Theme + utils.setInputValue("#input-settings-theme", localStorage.theme); + // Lazy rendering + utils.setInputChecked("#input-settings-lazy-rendering", settings.lazyRendering); + // Editor font size + utils.setInputValue("#input-settings-editor-font-size", settings.editorFontSize); + // Default content + utils.setInputValue("#textarea-settings-default-content", settings.defaultContent); + // Commit message + utils.setInputValue("#input-settings-publish-commit-msg", settings.commitMsg); + // Template + utils.setInputValue("#textarea-settings-publish-template", settings.template); + // SSH proxy + utils.setInputValue("#input-settings-ssh-proxy", settings.sshProxy); + + // Load extension settings + extensionMgr.onLoadSettings(); + } + + // Save settings from settings dialog + function saveSettings(event) { + var newSettings = {}; + + // Layout orientation + newSettings.layoutOrientation = utils.getInputRadio("radio-layout-orientation"); + // Theme + var theme = utils.getInputValue("#input-settings-theme"); + // Lazy Rendering + newSettings.lazyRendering = utils.getInputChecked("#input-settings-lazy-rendering"); + // Editor font size + newSettings.editorFontSize = utils.getInputIntValue("#input-settings-editor-font-size", event, 1, 99); + // Default content + newSettings.defaultContent = utils.getInputValue("#textarea-settings-default-content"); + // Commit message + newSettings.commitMsg = utils.getInputTextValue("#input-settings-publish-commit-msg", event); + // Template + newSettings.template = utils.getInputTextValue("#textarea-settings-publish-template", event); + // SSH proxy + newSettings.sshProxy = utils.checkUrl(utils.getInputTextValue("#input-settings-ssh-proxy", event), true); + + // Save extension settings + newSettings.extensionSettings = {}; + extensionMgr.onSaveSettings(newSettings.extensionSettings, event); + + if(!event.isPropagationStopped()) { + $.extend(settings, newSettings); + localStorage.settings = JSON.stringify(settings); + localStorage.theme = theme; + } + } + + // Create the layout + var layout = undefined; + core.createLayout = function() { + if(viewerMode === true) { + return; + } + var layoutGlobalConfig = { + closable: true, + resizable: false, + slidable: false, + livePaneResizing: true, + enableCursorHotkey: false, + spacing_open: 15, + spacing_closed: 15, + togglerLength_open: 90, + togglerLength_closed: 90, + stateManagement__enabled: false, + center__minWidth: 200, + center__minHeight: 200 + }; + extensionMgr.onLayoutConfigure(layoutGlobalConfig); + if(settings.layoutOrientation == "horizontal") { + $(".ui-layout-south").remove(); + $(".ui-layout-east").addClass("well").prop("id", "wmd-preview"); + layout = $('body').layout($.extend(layoutGlobalConfig, { + east__resizable: true, + east__size: .5, + east__minSize: 200 + })); + } + else if(settings.layoutOrientation == "vertical") { + $(".ui-layout-east").remove(); + $(".ui-layout-south").addClass("well").prop("id", "wmd-preview"); + layout = $('body').layout($.extend(layoutGlobalConfig, { + south__resizable: true, + south__size: .5, + south__minSize: 200 + })); + } + $(".ui-layout-toggler-north").addClass("btn").append($("").addClass("caret")); + $(".ui-layout-toggler-south").addClass("btn").append($("").addClass("caret")); + $(".ui-layout-toggler-east").addClass("btn").append($("").addClass("caret")); + $("#navbar").click(function() { + layout.allowOverflow('north'); + }); + + extensionMgr.onLayoutCreated(layout); + }; + + // Create the PageDown editor + var insertLinkCallback = undefined; + core.createEditor = function(onTextChange) { + var converter = new Markdown.Converter(); + var editor = new Markdown.Editor(converter); + // Custom insert link dialog + editor.hooks.set("insertLinkDialog", function(callback) { + insertLinkCallback = callback; + utils.resetModalInputs(); + $("#modal-insert-link").modal(); + _.defer(function() { + $("#input-insert-link").focus(); + }); + return true; + }); + // Custom insert image dialog + editor.hooks.set("insertImageDialog", function(callback) { + insertLinkCallback = callback; + utils.resetModalInputs(); + $("#modal-insert-image").modal(); + _.defer(function() { + $("#input-insert-image").focus(); + }); + return true; + }); + + var documentContent = undefined; + function checkDocumentChanges() { + var newDocumentContent = $("#wmd-input").val(); + if(documentContent !== undefined && documentContent != newDocumentContent) { + onTextChange(); + } + documentContent = newDocumentContent; + } + var previewWrapper = undefined; + if(settings.lazyRendering === true) { + previewWrapper = function(makePreview) { + var debouncedMakePreview = _.debounce(makePreview, 500); + return function() { + if(documentContent === undefined) { + makePreview(); + } + else { + debouncedMakePreview(); + } + checkDocumentChanges(); + }; + }; + } + else { + previewWrapper = function(makePreview) { + return function() { + checkDocumentChanges(); + makePreview(); + }; + }; + } + editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview); + extensionMgr.onEditorConfigure(editor); + + $("#wmd-input, #wmd-preview").scrollTop(0); + $("#wmd-button-bar").empty(); + editor.run(previewWrapper); + firstChange = false; + + // Hide default buttons + $(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)").addClass("btn").css("left", 0).find("span").hide(); + + // Add customized buttons + $("#wmd-bold-button").append($("").addClass("icon-bold")); + $("#wmd-italic-button").append($("").addClass("icon-italic")); + $("#wmd-link-button").append($("").addClass("icon-globe")); + $("#wmd-quote-button").append($("").addClass("icon-indent-left")); + $("#wmd-code-button").append($("").addClass("icon-code")); + $("#wmd-image-button").append($("").addClass("icon-picture")); + $("#wmd-olist-button").append($("").addClass("icon-numbered-list")); + $("#wmd-ulist-button").append($("").addClass("icon-list")); + $("#wmd-heading-button").append($("").addClass("icon-text-height")); + $("#wmd-hr-button").append($("").addClass("icon-hr")); + $("#wmd-undo-button").append($("").addClass("icon-undo")); + $("#wmd-redo-button").append($("").addClass("icon-share-alt")); + }; + + // onReady event callbacks + var readyCallbacks = []; + core.onReady = function(callback) { + readyCallbacks.push(callback); + runReadyCallbacks(); + }; + var ready = false; + core.setReady = function() { + ready = true; + runReadyCallbacks(); + }; + function runReadyCallbacks() { + if(ready === true) { + _.each(readyCallbacks, function(callback) { + callback(); + }); + readyCallbacks = []; + } + } + + core.onReady(extensionMgr.onReady); + core.onReady(function() { + + // Load theme list + _.each(THEME_LIST, function(name, value) { + $("#input-settings-theme").append($('')); + }); + + // listen to online/offline events + $(window).on('offline', core.setOffline); + $(window).on('online', setOnline); + if(navigator.onLine === false) { + core.setOffline(); + } + + // Detect user activity + $(document).mousemove(setUserActive).keypress(setUserActive); + + // Avoid dropdown to close when clicking on submenu + $(".dropdown-submenu > a").click(function(e) { + e.stopPropagation(); + }); + + // Click events on "insert link" and "insert image" dialog buttons + $(".action-insert-link").click(function(e) { + var value = utils.getInputTextValue($("#input-insert-link"), e); + if(value !== undefined) { + insertLinkCallback(value); + } + }); + $(".action-insert-image").click(function(e) { + var value = utils.getInputTextValue($("#input-insert-image"), e); + if(value !== undefined) { + insertLinkCallback(value); + } + }); + $(".action-close-insert-link").click(function(e) { + insertLinkCallback(null); + }); + + // Settings loading/saving + $(".action-load-settings").click(function() { + loadSettings(); + }); + $(".action-apply-settings").click(function(e) { + saveSettings(e); + if(!e.isPropagationStopped()) { + window.location.reload(); + } + }); + + $(".action-default-settings").click(function() { + localStorage.removeItem("settings"); + localStorage.removeItem("theme"); + window.location.reload(); + }); + + $(".action-app-reset").click(function() { + localStorage.clear(); + window.location.reload(); + }); + + // UI layout + $("#menu-bar, .ui-layout-center, .ui-layout-east, .ui-layout-south").removeClass("hide"); + core.createLayout(); + + // Editor's textarea + $("#wmd-input, #md-section-helper").css({ + // Apply editor font size + "font-size": settings.editorFontSize + "px", + "line-height": Math.round(settings.editorFontSize * (20 / 14)) + "px" + }); + + // Manage tab key + $("#wmd-input").keydown(function(e) { + if(e.keyCode === 9) { + var value = $(this).val(); + var start = this.selectionStart; + var end = this.selectionEnd; + // IE8 does not support selection attributes + if(start === undefined || end === undefined) { + return; + } + $(this).val(value.substring(0, start) + "\t" + value.substring(end)); + this.selectionStart = this.selectionEnd = start + 1; + e.preventDefault(); + } + }); + + // Tooltips + $(".tooltip-lazy-rendering").tooltip({ + container: '#modal-settings', + placement: 'right', + title: 'Disable preview rendering while typing in order to offload CPU. Refresh preview after 500 ms of inactivity.' + }); + $(".tooltip-default-content").tooltip({ + html: true, + container: '#modal-settings', + placement: 'right', + title: 'Thanks for supporting StackEdit by adding a backlink in your documents!' + }); + $(".tooltip-template").tooltip({ + html: true, + container: '#modal-settings', + placement: 'right', + trigger: 'manual', + title: [ + 'Available variables:
', + '
    ', + '
  • documentTitle: document title
  • ', + '
  • documentMarkdown: document in Markdown format
  • ', + '
  • documentHTML: document in HTML format
  • ', + '
  • publishAttributes: attributes of the publish location (undefined when using "Save")
  • ', + '
', + 'Examples:
', + _.escape('<%= documentTitle %>'), + '
', + _.escape('
<%- documentHTML %>
'), + '
', + _.escape('<% if(publishAttributes.provider == "github") print(documentMarkdown); %>'), + '

', + 'More info', + ].join("") + }).click(function(e) { + $(this).tooltip('show'); + e.stopPropagation(); + }); + + $(document).click(function(e) { + $(".tooltip-template").tooltip('hide'); + }); + + // Reset inputs + $(".action-reset-input").click(function() { + utils.resetModalInputs(); + }); + + // Do periodic tasks + intervalId = window.setInterval(function() { + utils.updateCurrentTime(); + checkWindowUnique(); + if(isUserActive() === true || viewerMode === true) { + _.each(periodicCallbacks, function(callback) { + callback(); + }); + checkOnline(); + } + }, 1000); + }); + + return core; }); - diff --git a/js/download-provider.js b/js/download-provider.js index 20ee65b3..c3973a36 100644 --- a/js/download-provider.js +++ b/js/download-provider.js @@ -3,46 +3,48 @@ define([ "core", "async-runner" ], function($, core, asyncRunner) { - - var PROVIDER_DOWNLOAD = "download"; - - var downloadProvider = { - providerId: PROVIDER_DOWNLOAD, - sharingAttributes: ["url"] - }; - - downloadProvider.importPublic = function(importParameters, callback) { - var task = asyncRunner.createTask(); - var title = undefined; - var content = undefined; - task.onRun(function() { - var url = importParameters.url; - var slashUrl = url.lastIndexOf("/"); - if(slashUrl === -1) { - task.error(new Error("Invalid URL parameter.")); - return; - } - title = url.substring(slashUrl + 1); - $.ajax({ - url : DOWNLOAD_PROXY_URL + "download?url=" + url, - type: "GET", - dataType : "text", - timeout : AJAX_TIMEOUT - }).done(function(result, textStatus, jqXHR) { - content = result; - task.chain(); - }).fail(function(jqXHR) { - task.error(new Error("Unable to access URL " + url)); - }); - }); - task.onSuccess(function() { - callback(undefined, title, content); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - return downloadProvider; + var PROVIDER_DOWNLOAD = "download"; + + var downloadProvider = { + providerId: PROVIDER_DOWNLOAD, + sharingAttributes: [ + "url" + ] + }; + + downloadProvider.importPublic = function(importParameters, callback) { + var task = asyncRunner.createTask(); + var title = undefined; + var content = undefined; + task.onRun(function() { + var url = importParameters.url; + var slashUrl = url.lastIndexOf("/"); + if(slashUrl === -1) { + task.error(new Error("Invalid URL parameter.")); + return; + } + title = url.substring(slashUrl + 1); + $.ajax({ + url: DOWNLOAD_PROXY_URL + "download?url=" + url, + type: "GET", + dataType: "text", + timeout: AJAX_TIMEOUT + }).done(function(result, textStatus, jqXHR) { + content = result; + task.chain(); + }).fail(function(jqXHR) { + task.error(new Error("Unable to access URL " + url)); + }); + }); + task.onSuccess(function() { + callback(undefined, title, content); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; + + return downloadProvider; }); \ No newline at end of file diff --git a/js/dropbox-helper.js b/js/dropbox-helper.js index a522eabd..55f49f8b 100644 --- a/js/dropbox-helper.js +++ b/js/dropbox-helper.js @@ -6,323 +6,330 @@ define([ "async-runner" ], function($, _, core, extensionMgr, asyncRunner) { - var client = undefined; - var authenticated = false; + var client = undefined; + var authenticated = false; - var dropboxHelper = {}; + var dropboxHelper = {}; - // Try to connect dropbox by downloading client.js - function connect(task) { - task.onRun(function() { - if(core.isOffline === true) { - client = undefined; - task.error(new Error("Operation not available in offline mode.|stopPublish")); - return; - } - if (client !== undefined) { - task.chain(); - return; - } - $.ajax({ - url : "lib/dropbox.min.js", - dataType : "script", timeout : AJAX_TIMEOUT - }).done(function() { - client = new Dropbox.Client({ - key: DROPBOX_APP_KEY, - secret: DROPBOX_APP_SECRET - }); - client.authDriver(new Dropbox.Drivers.Popup({ - receiverUrl: BASE_URL + "dropbox-oauth-receiver.html", - rememberUser: true - })); - task.chain(); - }).fail(function(jqXHR) { - var error = { - status: jqXHR.status, - responseText: jqXHR.statusText - }; - handleError(error, task); - }); - }); - } + // Try to connect dropbox by downloading client.js + function connect(task) { + task.onRun(function() { + if(core.isOffline === true) { + client = undefined; + task.error(new Error("Operation not available in offline mode.|stopPublish")); + return; + } + if(client !== undefined) { + task.chain(); + return; + } + $.ajax({ + url: "lib/dropbox.min.js", + dataType: "script", + timeout: AJAX_TIMEOUT + }).done(function() { + client = new Dropbox.Client({ + key: DROPBOX_APP_KEY, + secret: DROPBOX_APP_SECRET + }); + client.authDriver(new Dropbox.Drivers.Popup({ + receiverUrl: BASE_URL + "dropbox-oauth-receiver.html", + rememberUser: true + })); + task.chain(); + }).fail(function(jqXHR) { + var error = { + status: jqXHR.status, + responseText: jqXHR.statusText + }; + handleError(error, task); + }); + }); + } - // Try to authenticate with Oauth - function authenticate(task) { - task.onRun(function() { - if (authenticated === true) { - task.chain(); - return; - } - var immediate = true; - function localAuthenticate() { - if (immediate === false) { - extensionMgr.onMessage("Please make sure the Dropbox authorization popup is not blocked by your browser."); - // If not immediate we add time for user to enter his credentials - task.timeout = ASYNC_TASK_LONG_TIMEOUT; - } - client.reset(); - client.authenticate({interactive: !immediate}, function(error, client) { - // Success - if (client.authState === Dropbox.Client.DONE) { - authenticated = true; - task.chain(); - return; - } - // If immediate did not work retry without immediate flag - if (immediate === true) { - immediate = false; - task.chain(localAuthenticate); - return; - } - // Error - task.error(new Error("Access to Dropbox account is not authorized.")); - }); - } - task.chain(localAuthenticate); - }); - } + // Try to authenticate with Oauth + function authenticate(task) { + task.onRun(function() { + if(authenticated === true) { + task.chain(); + return; + } + var immediate = true; + function localAuthenticate() { + if(immediate === false) { + extensionMgr.onMessage("Please make sure the Dropbox authorization popup is not blocked by your browser."); + // If not immediate we add time for user to enter his + // credentials + task.timeout = ASYNC_TASK_LONG_TIMEOUT; + } + client.reset(); + client.authenticate({ + interactive: !immediate + }, function(error, client) { + // Success + if(client.authState === Dropbox.Client.DONE) { + authenticated = true; + task.chain(); + return; + } + // If immediate did not work retry without immediate flag + if(immediate === true) { + immediate = false; + task.chain(localAuthenticate); + return; + } + // Error + task.error(new Error("Access to Dropbox account is not authorized.")); + }); + } + task.chain(localAuthenticate); + }); + } - dropboxHelper.upload = function(path, content, callback) { - var result = undefined; - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - client.writeFile(path, content, function(error, stat) { - if (!error) { - result = stat; - task.chain(); - return; - } - // Handle error - if(error.status === 400) { - error = 'Could not upload document into path "' + path + '".'; - } - handleError(error, task); - }); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; + dropboxHelper.upload = function(path, content, callback) { + var result = undefined; + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + client.writeFile(path, content, function(error, stat) { + if(!error) { + result = stat; + task.chain(); + return; + } + // Handle error + if(error.status === 400) { + error = 'Could not upload document into path "' + path + '".'; + } + handleError(error, task); + }); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - dropboxHelper.checkChanges = function(lastChangeId, callback) { - var changes = []; - var newChangeId = lastChangeId || 0; - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - function retrievePageOfChanges() { - client.pullChanges(newChangeId, function(error, pullChanges) { - if (error) { - handleError(error, task); - return; - } - // Retrieve success - newChangeId = pullChanges.cursor(); - if(pullChanges.changes !== undefined) { - changes = changes.concat(pullChanges.changes); - } - if (pullChanges.shouldPullAgain) { - task.chain(retrievePageOfChanges); - } else { - task.chain(); - } - }); - } - task.chain(retrievePageOfChanges); - }); - task.onSuccess(function() { - callback(undefined, changes, newChangeId); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; + dropboxHelper.checkChanges = function(lastChangeId, callback) { + var changes = []; + var newChangeId = lastChangeId || 0; + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + function retrievePageOfChanges() { + client.pullChanges(newChangeId, function(error, pullChanges) { + if(error) { + handleError(error, task); + return; + } + // Retrieve success + newChangeId = pullChanges.cursor(); + if(pullChanges.changes !== undefined) { + changes = changes.concat(pullChanges.changes); + } + if(pullChanges.shouldPullAgain) { + task.chain(retrievePageOfChanges); + } + else { + task.chain(); + } + }); + } + task.chain(retrievePageOfChanges); + }); + task.onSuccess(function() { + callback(undefined, changes, newChangeId); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - dropboxHelper.downloadMetadata = function(paths, callback) { - var result = []; - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - function recursiveDownloadMetadata() { - if(paths.length === 0) { - task.chain(); - return; - } - var path = paths[0]; - client.stat(path, function(error, stat) { - if(stat) { - result.push(stat); - paths.shift(); - task.chain(recursiveDownloadMetadata); - return; - } - handleError(error, task); - }); - } - task.chain(recursiveDownloadMetadata); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; + dropboxHelper.downloadMetadata = function(paths, callback) { + var result = []; + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + function recursiveDownloadMetadata() { + if(paths.length === 0) { + task.chain(); + return; + } + var path = paths[0]; + client.stat(path, function(error, stat) { + if(stat) { + result.push(stat); + paths.shift(); + task.chain(recursiveDownloadMetadata); + return; + } + handleError(error, task); + }); + } + task.chain(recursiveDownloadMetadata); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - dropboxHelper.downloadContent = function(objects, callback) { - var result = []; - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - function recursiveDownloadContent() { - if(objects.length === 0) { - task.chain(); - return; - } - var object = objects[0]; - result.push(object); - var file = undefined; - // object may be a file - if(object.isFile === true) { - file = object; - } - // object may be a change - else if(object.wasRemoved !== undefined) { - file = object.stat; - } - if(!file) { - objects.shift(); - task.chain(recursiveDownloadContent); - return; - } - client.readFile(file.path, function(error, data) { - if(data) { - file.content = data; - objects.shift(); - task.chain(recursiveDownloadContent); - return; - } - handleError(error, task); - }); - } - task.chain(recursiveDownloadContent); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - - function handleError(error, task) { - var errorMsg = true; - if (error) { - logger.error(error); - // Try to analyze the error - if (typeof error === "string") { - errorMsg = error; - } - else { - errorMsg = "Dropbox error (" - + error.status + ": " + error.responseText + ")."; + dropboxHelper.downloadContent = function(objects, callback) { + var result = []; + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + function recursiveDownloadContent() { + if(objects.length === 0) { + task.chain(); + return; + } + var object = objects[0]; + result.push(object); + var file = undefined; + // object may be a file + if(object.isFile === true) { + file = object; + } + // object may be a change + else if(object.wasRemoved !== undefined) { + file = object.stat; + } + if(!file) { + objects.shift(); + task.chain(recursiveDownloadContent); + return; + } + client.readFile(file.path, function(error, data) { + if(data) { + file.content = data; + objects.shift(); + task.chain(recursiveDownloadContent); + return; + } + handleError(error, task); + }); + } + task.chain(recursiveDownloadContent); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - if (error.status === 401 || error.status === 403) { - authenticated = false; - errorMsg = "Access to Dropbox account is not authorized."; - task.retry(new Error(errorMsg), 1); - return; - } else if(error.status === 400 && error.responseText - .indexOf("oauth_nonce") !== -1) { - // A bug I guess... - _.each(_.keys(localStorage), function(key) { - // We have to remove the Oauth cache from the localStorage - if(key.indexOf("dropbox-auth") === 0) { - localStorage.removeItem(key); - } - }); - authenticated = false; - task.retry(new Error(errorMsg), 1); - return; - } else if (error.status <= 0) { - client = undefined; - authenticated = false; - core.setOffline(); - errorMsg = "|stopPublish"; - } - } - } - task.error(new Error(errorMsg)); - } + function handleError(error, task) { + var errorMsg = true; + if(error) { + logger.error(error); + // Try to analyze the error + if(typeof error === "string") { + errorMsg = error; + } + else { + errorMsg = "Dropbox error (" + error.status + ": " + error.responseText + ")."; - var pickerLoaded = false; - function loadPicker(task) { - task.onRun(function() { - if (pickerLoaded === true) { - task.chain(); - return; - } - $.ajax({ - url : "https://www.dropbox.com/static/api/1/dropbox.js", - dataType : "script", timeout : AJAX_TIMEOUT - }).done(function() { - pickerLoaded = true; - task.chain(); - }).fail(function(jqXHR) { - var error = { - status: jqXHR.status, - responseText: jqXHR.statusText - }; - handleError(error, task); - }); - }); - } - - dropboxHelper.picker = function(callback) { - var paths = []; - var task = asyncRunner.createTask(); - // Add some time for user to choose his files - task.timeout = ASYNC_TASK_LONG_TIMEOUT; - connect(task); - loadPicker(task); - task.onRun(function() { - var options = {}; - options.multiselect = true; - options.linkType = "direct"; - options.success = function(files) { - for(var i=0; i:"\|?\*]+$/)) { - extensionMgr.onError('"' + path + '" contains invalid characters.'); - return undefined; - } - if(path.indexOf("/") !== 0) { - return "/" + path; - } - return path; - } - - function createSyncIndex(path) { - return "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(path.toLowerCase()); - } - - function createSyncAttributes(path, versionTag, content) { - var syncAttributes = {}; - syncAttributes.provider = dropboxProvider; - syncAttributes.path = path; - syncAttributes.version = versionTag; - syncAttributes.contentCRC = utils.crc32(content); - syncAttributes.syncIndex = createSyncIndex(path); - utils.storeAttributes(syncAttributes); - return syncAttributes; - } - - function importFilesFromPaths(paths) { - dropboxHelper.downloadMetadata(paths, function(error, result) { - if(error) { - return; - } - dropboxHelper.downloadContent(result, function(error, result) { - if(error) { - return; - } - var fileDescList = []; - _.each(result, function(file) { - var syncAttributes = createSyncAttributes(file.path, file.versionTag, file.content); - var syncLocations = {}; - syncLocations[syncAttributes.syncIndex] = syncAttributes; - var fileDesc = fileMgr.createFile(file.name, file.content, syncLocations); - fileMgr.selectFile(fileDesc); - fileDescList.push(fileDesc); - }); - extensionMgr.onSyncImportSuccess(fileDescList, dropboxProvider); - }); - }); - } - dropboxProvider.importFiles = function() { - dropboxHelper.picker(function(error, paths) { - if(error || paths.length === 0) { - return; - } - var importPaths = []; - _.each(paths, function(path) { - var syncIndex = createSyncIndex(path); - var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); - if(fileDesc !== undefined) { - extensionMgr.onError('"' + fileDesc.title + '" was already imported'); - return; - } - importPaths.push(path); - }); - importFilesFromPaths(importPaths); - }); - }; - - function exportFileToPath(path, title, content, callback) { - path = checkPath(path); - if(path === undefined) { - callback(true); - return; - } - // Check that file is not synchronized with an other one - var syncIndex = createSyncIndex(path); - var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); - if(fileDesc !== undefined) { - var existingTitle = fileDesc.title; - extensionMgr.onError('File path is already synchronized with "' + existingTitle + '"'); - callback(true); - return; - } - dropboxHelper.upload(path, content, function(error, result) { - if (error) { - callback(error); - return; - } - var syncAttributes = createSyncAttributes(result.path, result.versionTag, content); - callback(undefined, syncAttributes); - }); - } - - dropboxProvider.exportFile = function(event, title, content, callback) { - var path = utils.getInputTextValue("#input-sync-export-dropbox-path", event); - exportFileToPath(path, title, content, callback); - }; + var PROVIDER_DROPBOX = "dropbox"; - dropboxProvider.exportManual = function(event, title, content, callback) { - var path = utils.getInputTextValue("#input-sync-manual-dropbox-path", event); - exportFileToPath(path, title, content, callback); - }; - - dropboxProvider.syncUp = function(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, callback) { - var syncContentCRC = syncAttributes.contentCRC; - // Skip if CRC has not changed - if(uploadContentCRC == syncContentCRC) { - callback(undefined, false); - return; - } - dropboxHelper.upload(syncAttributes.path, uploadContent, function(error, result) { - if(error) { - callback(error, true); - return; - } - syncAttributes.version = result.versionTag; - syncAttributes.contentCRC = uploadContentCRC; - callback(undefined, true); - }); - }; - - dropboxProvider.syncDown = function(callback) { - var lastChangeId = localStorage[PROVIDER_DROPBOX + ".lastChangeId"]; - dropboxHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) { - if (error) { - callback(error); - return; - } - var interestingChanges = []; - _.each(changes, function(change) { - var syncIndex = createSyncIndex(change.path); - var syncAttributes = fileMgr.getSyncAttributes(syncIndex); - if(syncAttributes === undefined) { - return; - } - // Store syncAttributes to avoid 2 times searching - change.syncAttributes = syncAttributes; - // Delete - if(change.wasRemoved === true) { - interestingChanges.push(change); - return; - } - // Modify - if(syncAttributes.version != change.stat.versionTag) { - interestingChanges.push(change); - } - }); - dropboxHelper.downloadContent(interestingChanges, function(error, changes) { - if (error) { - callback(error); - return; - } - _.each(changes, function(change) { - var syncAttributes = change.syncAttributes; - var syncIndex = syncAttributes.syncIndex; - var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); - // No file corresponding (file may have been deleted locally) - if(fileDesc === undefined) { - return; - } - var localTitle = fileDesc.title; - // File deleted - if (change.wasRemoved === true) { - extensionMgr.onError('"' + localTitle + '" has been removed from Dropbox.'); - fileMgr.removeSync(syncAttributes); - return; - } - var localContent = fileDesc.getContent(); - var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); - var file = change.stat; + var dropboxProvider = { + providerId: PROVIDER_DROPBOX, + providerName: "Dropbox", + defaultPublishFormat: "template" + }; + + function checkPath(path) { + if(path === undefined) { + return undefined; + } + if(!path.match(/^[^\\<>:"\|?\*]+$/)) { + extensionMgr.onError('"' + path + '" contains invalid characters.'); + return undefined; + } + if(path.indexOf("/") !== 0) { + return "/" + path; + } + return path; + } + + function createSyncIndex(path) { + return "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(path.toLowerCase()); + } + + function createSyncAttributes(path, versionTag, content) { + var syncAttributes = {}; + syncAttributes.provider = dropboxProvider; + syncAttributes.path = path; + syncAttributes.version = versionTag; + syncAttributes.contentCRC = utils.crc32(content); + syncAttributes.syncIndex = createSyncIndex(path); + utils.storeAttributes(syncAttributes); + return syncAttributes; + } + + function importFilesFromPaths(paths) { + dropboxHelper.downloadMetadata(paths, function(error, result) { + if(error) { + return; + } + dropboxHelper.downloadContent(result, function(error, result) { + if(error) { + return; + } + var fileDescList = []; + _.each(result, function(file) { + var syncAttributes = createSyncAttributes(file.path, file.versionTag, file.content); + var syncLocations = {}; + syncLocations[syncAttributes.syncIndex] = syncAttributes; + var fileDesc = fileMgr.createFile(file.name, file.content, syncLocations); + fileMgr.selectFile(fileDesc); + fileDescList.push(fileDesc); + }); + extensionMgr.onSyncImportSuccess(fileDescList, dropboxProvider); + }); + }); + } + + dropboxProvider.importFiles = function() { + dropboxHelper.picker(function(error, paths) { + if(error || paths.length === 0) { + return; + } + var importPaths = []; + _.each(paths, function(path) { + var syncIndex = createSyncIndex(path); + var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); + if(fileDesc !== undefined) { + extensionMgr.onError('"' + fileDesc.title + '" was already imported'); + return; + } + importPaths.push(path); + }); + importFilesFromPaths(importPaths); + }); + }; + + function exportFileToPath(path, title, content, callback) { + path = checkPath(path); + if(path === undefined) { + callback(true); + return; + } + // Check that file is not synchronized with an other one + var syncIndex = createSyncIndex(path); + var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); + if(fileDesc !== undefined) { + var existingTitle = fileDesc.title; + extensionMgr.onError('File path is already synchronized with "' + existingTitle + '"'); + callback(true); + return; + } + dropboxHelper.upload(path, content, function(error, result) { + if(error) { + callback(error); + return; + } + var syncAttributes = createSyncAttributes(result.path, result.versionTag, content); + callback(undefined, syncAttributes); + }); + } + + dropboxProvider.exportFile = function(event, title, content, callback) { + var path = utils.getInputTextValue("#input-sync-export-dropbox-path", event); + exportFileToPath(path, title, content, callback); + }; + + dropboxProvider.exportManual = function(event, title, content, callback) { + var path = utils.getInputTextValue("#input-sync-manual-dropbox-path", event); + exportFileToPath(path, title, content, callback); + }; + + dropboxProvider.syncUp = function(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, callback) { + var syncContentCRC = syncAttributes.contentCRC; + // Skip if CRC has not changed + if(uploadContentCRC == syncContentCRC) { + callback(undefined, false); + return; + } + dropboxHelper.upload(syncAttributes.path, uploadContent, function(error, result) { + if(error) { + callback(error, true); + return; + } + syncAttributes.version = result.versionTag; + syncAttributes.contentCRC = uploadContentCRC; + callback(undefined, true); + }); + }; + + dropboxProvider.syncDown = function(callback) { + var lastChangeId = localStorage[PROVIDER_DROPBOX + ".lastChangeId"]; + dropboxHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) { + if(error) { + callback(error); + return; + } + var interestingChanges = []; + _.each(changes, function(change) { + var syncIndex = createSyncIndex(change.path); + var syncAttributes = fileMgr.getSyncAttributes(syncIndex); + if(syncAttributes === undefined) { + return; + } + // Store syncAttributes to avoid 2 times searching + change.syncAttributes = syncAttributes; + // Delete + if(change.wasRemoved === true) { + interestingChanges.push(change); + return; + } + // Modify + if(syncAttributes.version != change.stat.versionTag) { + interestingChanges.push(change); + } + }); + dropboxHelper.downloadContent(interestingChanges, function(error, changes) { + if(error) { + callback(error); + return; + } + _.each(changes, function(change) { + var syncAttributes = change.syncAttributes; + var syncIndex = syncAttributes.syncIndex; + var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); + // No file corresponding (file may have been deleted + // locally) + if(fileDesc === undefined) { + return; + } + var localTitle = fileDesc.title; + // File deleted + if(change.wasRemoved === true) { + extensionMgr.onError('"' + localTitle + '" has been removed from Dropbox.'); + fileMgr.removeSync(syncAttributes); + return; + } + var localContent = fileDesc.content; + var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); + var file = change.stat; var remoteContentCRC = utils.crc32(file.content); var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC; - var fileContentChanged = localContent != file.content; - // Conflict detection - if (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true) { - fileMgr.createFile(localTitle + " (backup)", localContent); - extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.'); - } - // If file content changed - if(fileContentChanged && remoteContentChanged === true) { - fileDesc.setContent(file.content); - extensionMgr.onMessage('"' + localTitle + '" has been updated from Dropbox.'); - if(fileMgr.isCurrentFile(fileDesc)) { - fileMgr.selectFile(); // Refresh editor - } - } - // Update syncAttributes - syncAttributes.version = file.versionTag; - syncAttributes.contentCRC = remoteContentCRC; - utils.storeAttributes(syncAttributes); - }); - localStorage[PROVIDER_DROPBOX + ".lastChangeId"] = newChangeId; - callback(); - }); - }); - }; - - dropboxProvider.publish = function(publishAttributes, title, content, callback) { - var path = checkPath(publishAttributes.path); - if(path === undefined) { - callback(true); - return; - } - dropboxHelper.upload(path, content, callback); - }; + var fileContentChanged = localContent != file.content; + // Conflict detection + if(fileContentChanged === true && localContentChanged === true && remoteContentChanged === true) { + fileMgr.createFile(localTitle + " (backup)", localContent); + extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.'); + } + // If file content changed + if(fileContentChanged && remoteContentChanged === true) { + fileDesc.content = file.content; + extensionMgr.onMessage('"' + localTitle + '" has been updated from Dropbox.'); + if(fileMgr.isCurrentFile(fileDesc)) { + fileMgr.selectFile(); // Refresh editor + } + } + // Update syncAttributes + syncAttributes.version = file.versionTag; + syncAttributes.contentCRC = remoteContentCRC; + utils.storeAttributes(syncAttributes); + }); + localStorage[PROVIDER_DROPBOX + ".lastChangeId"] = newChangeId; + callback(); + }); + }); + }; - dropboxProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - publishAttributes.path = utils.getInputTextValue("#input-publish-dropbox-path", event); - if(event.isPropagationStopped()) { - return undefined; - } - return publishAttributes; - }; + dropboxProvider.publish = function(publishAttributes, title, content, callback) { + var path = checkPath(publishAttributes.path); + if(path === undefined) { + callback(true); + return; + } + dropboxHelper.upload(path, content, callback); + }; - return dropboxProvider; + dropboxProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + publishAttributes.path = utils.getInputTextValue("#input-publish-dropbox-path", event); + if(event.isPropagationStopped()) { + return undefined; + } + return publishAttributes; + }; + + return dropboxProvider; }); \ No newline at end of file diff --git a/js/extension-manager.js b/js/extension-manager.js index bcd22f82..42ebc592 100644 --- a/js/extension-manager.js +++ b/js/extension-manager.js @@ -1,4 +1,4 @@ -define( [ +define([ "jquery", "underscore", "utils", @@ -20,167 +20,162 @@ define( [ "extensions/scroll-link", "lib/bootstrap" ], function($, _, utils, settings) { - - var extensionMgr = {}; - - // Create a list of extensions - var extensionList = _.chain( - arguments - ).map(function(argument) { - return _.isObject(argument) && argument.extensionId && argument; - }).compact().value(); - // Return every named callbacks implemented in extensions - function getExtensionCallbackList(hookName) { - return _.chain( - extensionList - ).map(function(extension) { - return extension.config.enabled && extension[hookName]; - }).compact().value(); - } - - // Return a function that calls every callbacks from extensions - function createHook(hookName) { - var callbackList = getExtensionCallbackList(hookName); - return function() { - logger.debug(hookName, arguments); - var callbackArguments = arguments; - _.each(callbackList, function(callback) { - callback.apply(null, callbackArguments); - }); - }; - } - - // Add a Hook to the extensionMgr - function addHook(hookName) { - extensionMgr[hookName] = createHook(hookName); - } - - // Set extension config - extensionSettings = settings.extensionSettings || {}; - _.each(extensionList, function(extension) { - extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]); - extension.config.enabled = !extension.optional || extension.config.enabled === undefined || extension.config.enabled === true; - }); - - // Load/Save extension config from/to settings - extensionMgr["onLoadSettings"] = function() { - logger.debug("onLoadSettings"); - _.each(extensionList, function(extension) { - utils.setInputChecked("#input-enable-extension-" + extension.extensionId, extension.config.enabled); - var onLoadSettingsCallback = extension.onLoadSettings; - onLoadSettingsCallback && onLoadSettingsCallback(); - }); - }; - extensionMgr["onSaveSettings"] = function(newExtensionSettings, event) { - logger.debug("onSaveSettings"); - _.each(extensionList, function(extension) { - var newExtensionConfig = extension.defaultConfig || {}; - newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId); - var onSaveSettingsCallback = extension.onSaveSettings; - onSaveSettingsCallback && onSaveSettingsCallback(newExtensionConfig, event); - newExtensionSettings[extension.extensionId] = newExtensionConfig; - }); - }; - - addHook("onReady"); - addHook("onMessage"); - addHook("onError"); - addHook("onOfflineChanged"); - addHook("onAsyncRunning"); - - // To access modules that are loaded after extensions - addHook("onFileMgrCreated"); - addHook("onSynchronizerCreated"); - addHook("onPublisherCreated"); - - // Operations on files - addHook("onFileCreated"); - addHook("onFileDeleted"); - addHook("onFileSelected"); - addHook("onContentChanged"); - addHook("onTitleChanged"); - - // Sync events - addHook("onSyncRunning"); - addHook("onSyncSuccess"); - addHook("onSyncImportSuccess"); - addHook("onSyncExportSuccess"); - addHook("onSyncRemoved"); - - // Publish events - addHook("onPublishRunning"); - addHook("onPublishSuccess"); - addHook("onNewPublishSuccess"); - addHook("onPublishRemoved"); - - // Operations on Layout - addHook("onLayoutConfigure"); - addHook("onLayoutCreated"); - - // Operations on PageDown - addHook("onEditorConfigure"); - - var onPreviewFinished = createHook("onPreviewFinished"); - var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview"); - extensionMgr["onAsyncPreview"] = function() { - logger.debug("onAsyncPreview"); - // Call onPreviewFinished callbacks when all async preview are finished - var counter = 0; - function tryFinished() { - if(counter === onAsyncPreviewCallbackList.length) { - onPreviewFinished(); - } - } - _.each(onAsyncPreviewCallbackList, function(asyncPreviewCallback) { - asyncPreviewCallback(function() { - counter++; - tryFinished(); - }); - }); - tryFinished(); - }; - - var accordionTmpl = [ + var extensionMgr = {}; + + // Create a list of extensions + var extensionList = _.chain(arguments).map(function(argument) { + return _.isObject(argument) && argument.extensionId && argument; + }).compact().value(); + + // Return every named callbacks implemented in extensions + function getExtensionCallbackList(hookName) { + return _.chain(extensionList).map(function(extension) { + return extension.config.enabled && extension[hookName]; + }).compact().value(); + } + + // Return a function that calls every callbacks from extensions + function createHook(hookName) { + var callbackList = getExtensionCallbackList(hookName); + return function() { + logger.debug(hookName, arguments); + var callbackArguments = arguments; + _.each(callbackList, function(callback) { + callback.apply(null, callbackArguments); + }); + }; + } + + // Add a Hook to the extensionMgr + function addHook(hookName) { + extensionMgr[hookName] = createHook(hookName); + } + + // Set extension config + extensionSettings = settings.extensionSettings || {}; + _.each(extensionList, function(extension) { + extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]); + extension.config.enabled = !extension.optional || extension.config.enabled === undefined || extension.config.enabled === true; + }); + + // Load/Save extension config from/to settings + extensionMgr["onLoadSettings"] = function() { + logger.debug("onLoadSettings"); + _.each(extensionList, function(extension) { + utils.setInputChecked("#input-enable-extension-" + extension.extensionId, extension.config.enabled); + var onLoadSettingsCallback = extension.onLoadSettings; + onLoadSettingsCallback && onLoadSettingsCallback(); + }); + }; + extensionMgr["onSaveSettings"] = function(newExtensionSettings, event) { + logger.debug("onSaveSettings"); + _.each(extensionList, function(extension) { + var newExtensionConfig = _.extend({}, extension.defaultConfig); + newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId); + var onSaveSettingsCallback = extension.onSaveSettings; + onSaveSettingsCallback && onSaveSettingsCallback(newExtensionConfig, event); + newExtensionSettings[extension.extensionId] = newExtensionConfig; + }); + }; + + addHook("onReady"); + addHook("onMessage"); + addHook("onError"); + addHook("onOfflineChanged"); + addHook("onAsyncRunning"); + + // To access modules that are loaded after extensions + addHook("onFileMgrCreated"); + addHook("onSynchronizerCreated"); + addHook("onPublisherCreated"); + + // Operations on files + addHook("onFileCreated"); + addHook("onFileDeleted"); + addHook("onFileSelected"); + addHook("onContentChanged"); + addHook("onTitleChanged"); + + // Sync events + addHook("onSyncRunning"); + addHook("onSyncSuccess"); + addHook("onSyncImportSuccess"); + addHook("onSyncExportSuccess"); + addHook("onSyncRemoved"); + + // Publish events + addHook("onPublishRunning"); + addHook("onPublishSuccess"); + addHook("onNewPublishSuccess"); + addHook("onPublishRemoved"); + + // Operations on Layout + addHook("onLayoutConfigure"); + addHook("onLayoutCreated"); + + // Operations on PageDown + addHook("onEditorConfigure"); + + var onPreviewFinished = createHook("onPreviewFinished"); + var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview"); + extensionMgr["onAsyncPreview"] = function() { + logger.debug("onAsyncPreview"); + // Call onPreviewFinished callbacks when all async preview are finished + var counter = 0; + function tryFinished() { + if(counter === onAsyncPreviewCallbackList.length) { + onPreviewFinished(); + } + } + _.each(onAsyncPreviewCallbackList, function(asyncPreviewCallback) { + asyncPreviewCallback(function() { + counter++; + tryFinished(); + }); + }); + tryFinished(); + }; + + var accordionTmpl = [ '
', - '
', - '', - '', - '<%= extensionName %>', - '', - '
', - '
', - '
<%= settingsBloc %>
', - '
', - '
'].join(""); - - function createSettings(extension) { - $("#accordion-extensions").append($(_.template(accordionTmpl, { - extensionId: extension.extensionId, - extensionName: extension.extensionName, - optional: extension.optional, - settingsBloc: extension.settingsBloc - }))); - } + '
', + ' ', + ' ', + ' <%= extensionName %>', + ' ', + '
', + '
', + '
<%= settingsBloc %>
', + '
', + '' + ].join(""); - $(function() { - // Create accordion in settings dialog - _.chain( - extensionList - ).sortBy(function(extension) { - return extension.extensionName.toLowerCase(); - }).each(createSettings); - - // Create extension buttons - logger.debug("onCreateButton"); - var onCreateButtonCallbackList = getExtensionCallbackList("onCreateButton"); - _.each(onCreateButtonCallbackList, function(callback) { - $("#extension-buttons").append($('
').append(callback())); - }); + function createSettings(extension) { + $("#accordion-extensions").append($(_.template(accordionTmpl, { + extensionId: extension.extensionId, + extensionName: extension.extensionName, + optional: extension.optional, + settingsBloc: extension.settingsBloc + }))); + } - }); - - return extensionMgr; + $(function() { + // Create accordion in settings dialog + _.chain(extensionList).sortBy(function(extension) { + return extension.extensionName.toLowerCase(); + }).each(createSettings); + + // Create extension buttons + logger.debug("onCreateButton"); + var onCreateButtonCallbackList = getExtensionCallbackList("onCreateButton"); + _.each(onCreateButtonCallbackList, function(callback) { + $("#extension-buttons").append($('
').append(callback())); + }); + + }); + + return extensionMgr; }); \ No newline at end of file diff --git a/js/extensions/button-publish.js b/js/extensions/button-publish.js index fe2f92d8..64810558 100644 --- a/js/extensions/button-publish.js +++ b/js/extensions/button-publish.js @@ -2,78 +2,79 @@ define([ "jquery", "underscore" ], function($, _) { - - var buttonPublish = { - extensionId: "buttonPublish", - extensionName: 'Button "Publish"', - settingsBloc: '

Adds a "Publish document" button in the navigation bar.

' - }; - - var button = undefined; - var currentFileDesc = undefined; - var publishRunning = false; - var hasPublications = false; - var isOffline = false; - // Enable/disable the button - function updateButtonState() { - if(button === undefined) { - return; - } - if(publishRunning === true || hasPublications === false || isOffline === true) { - button.addClass("disabled"); - } - else { - button.removeClass("disabled"); - } - }; - - var publisher = undefined; - buttonPublish.onPublisherCreated = function(publisherParameter) { - publisher = publisherParameter; - }; - - buttonPublish.onCreateButton = function() { - button = $([ - ''].join("") - ).click(function() { - if(!$(this).hasClass("disabled")) { - publisher.publish(); - } - }); - return button; - }; - - buttonPublish.onPublishRunning = function(isRunning) { - publishRunning = isRunning; - updateButtonState(); - }; - - buttonPublish.onOfflineChanged = function(isOfflineParameter) { - isOffline = isOfflineParameter; - updateButtonState(); - }; - - // Check that current file has publications - var checkPublication = function() { - if(_.size(currentFileDesc.publishLocations) === 0) { - hasPublications = false; - } - else { - hasPublications = true; - } - updateButtonState(); - }; - - buttonPublish.onFileSelected = function(fileDesc) { - currentFileDesc = fileDesc; - checkPublication(); - }; - - buttonPublish.onPublishRemoved = checkPublication; - buttonPublish.onNewPublishSuccess = checkPublication; - - return buttonPublish; - + + var buttonPublish = { + extensionId: "buttonPublish", + extensionName: 'Button "Publish"', + settingsBloc: '

Adds a "Publish document" button in the navigation bar.

' + }; + + var button = undefined; + var currentFileDesc = undefined; + var publishRunning = false; + var hasPublications = false; + var isOffline = false; + // Enable/disable the button + function updateButtonState() { + if(button === undefined) { + return; + } + if(publishRunning === true || hasPublications === false || isOffline === true) { + button.addClass("disabled"); + } + else { + button.removeClass("disabled"); + } + } + ; + + var publisher = undefined; + buttonPublish.onPublisherCreated = function(publisherParameter) { + publisher = publisherParameter; + }; + + buttonPublish.onCreateButton = function() { + button = $([ + '' + ].join("")).click(function() { + if(!$(this).hasClass("disabled")) { + publisher.publish(); + } + }); + return button; + }; + + buttonPublish.onPublishRunning = function(isRunning) { + publishRunning = isRunning; + updateButtonState(); + }; + + buttonPublish.onOfflineChanged = function(isOfflineParameter) { + isOffline = isOfflineParameter; + updateButtonState(); + }; + + // Check that current file has publications + var checkPublication = function() { + if(_.size(currentFileDesc.publishLocations) === 0) { + hasPublications = false; + } + else { + hasPublications = true; + } + updateButtonState(); + }; + + buttonPublish.onFileSelected = function(fileDesc) { + currentFileDesc = fileDesc; + checkPublication(); + }; + + buttonPublish.onPublishRemoved = checkPublication; + buttonPublish.onNewPublishSuccess = checkPublication; + + return buttonPublish; + }); \ No newline at end of file diff --git a/js/extensions/button-share.js b/js/extensions/button-share.js index 55b21b73..5963f786 100644 --- a/js/extensions/button-share.js +++ b/js/extensions/button-share.js @@ -2,71 +2,73 @@ define([ "jquery", "underscore" ], function($, _) { - - var buttonShare = { - extensionId: "buttonShare", - extensionName: 'Button "Share"', + + var buttonShare = { + extensionId: "buttonShare", + extensionName: 'Button "Share"', optional: true, - settingsBloc: '

Adds a "Share document" button in the navigation bar.

' - }; - - buttonShare.onCreateButton = function() { - return $([ - '', - ''].join("") - ); - }; - - var fileDesc = undefined; - var lineTemplate = [ + settingsBloc: '

Adds a "Share document" button in the navigation bar.

' + }; + + buttonShare.onCreateButton = function() { + return $([ + '', + '' + ].join("")); + }; + + var fileDesc = undefined; + var lineTemplate = [ '
', - '', - '', - '
'].join(""); - var refreshDocumentSharing = function(fileDescParameter) { - if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { - return; - } - - var linkList = $("#link-container .link-list").empty(); - $("#link-container .no-link").show(); - - var attributesList = _.values(fileDesc.publishLocations); - _.each(attributesList, function(attributes) { - if(attributes.sharingLink) { - var lineElement = $(_.template(lineTemplate, { - link: attributes.sharingLink - })); - lineElement.click(function(event) { - event.stopPropagation(); - }); - linkList.append(lineElement); - $("#link-container .no-link").hide(); - } - }); - }; - - buttonShare.onFileSelected = function(fileDescParameter) { - fileDesc = fileDescParameter; - refreshDocumentSharing(fileDescParameter); - }; - - buttonShare.onNewPublishSuccess = refreshDocumentSharing; - buttonShare.onPublishRemoved = refreshDocumentSharing; - - return buttonShare; - + ' ', + ' ', + '
' + ].join(""); + var refreshDocumentSharing = function(fileDescParameter) { + if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { + return; + } + + var linkList = $("#link-container .link-list").empty(); + $("#link-container .no-link").show(); + + var attributesList = _.values(fileDesc.publishLocations); + _.each(attributesList, function(attributes) { + if(attributes.sharingLink) { + var lineElement = $(_.template(lineTemplate, { + link: attributes.sharingLink + })); + lineElement.click(function(event) { + event.stopPropagation(); + }); + linkList.append(lineElement); + $("#link-container .no-link").hide(); + } + }); + }; + + buttonShare.onFileSelected = function(fileDescParameter) { + fileDesc = fileDescParameter; + refreshDocumentSharing(fileDescParameter); + }; + + buttonShare.onNewPublishSuccess = refreshDocumentSharing; + buttonShare.onPublishRemoved = refreshDocumentSharing; + + return buttonShare; + }); \ No newline at end of file diff --git a/js/extensions/button-stat.js b/js/extensions/button-stat.js index 0a0872cd..4ec914df 100644 --- a/js/extensions/button-stat.js +++ b/js/extensions/button-stat.js @@ -1,72 +1,78 @@ define([ "jquery", - "underscore" -], function($, _) { - - var buttonStat = { - extensionId: "buttonStat", - extensionName: 'Button "Statistics"', + "underscore", + "utils" +], function($, _, utils) { + + var buttonStat = { + extensionId: "buttonStat", + extensionName: 'Button "Statistics"', optional: true, - settingsBloc: '

Adds a "Document statistics" button in the navigation bar.

' - }; - - buttonStat.onCreateButton = function() { - return $([ - '', - ''].join("") - ); - }; - - var fileDesc = undefined; - var lineTemplate = [ - '
', - '', - '', - '
'].join(""); - var refreshDocumentSharing = function(fileDescParameter) { - if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { - return; - } - - var linkList = $("#link-container .link-list").empty(); - $("#link-container .no-link").show(); - - var attributesList = _.values(fileDesc.publishLocations); - _.each(attributesList, function(attributes) { - if(attributes.sharingLink) { - var lineElement = $(_.template(lineTemplate, { - link: attributes.sharingLink - })); - lineElement.click(function(event) { - event.stopPropagation(); - }); - linkList.append(lineElement); - $("#link-container .no-link").hide(); - } - }); - }; - - buttonStat.onFileSelected = function(fileDescParameter) { - fileDesc = fileDescParameter; - refreshDocumentSharing(fileDescParameter); - }; - - buttonStat.onNewPublishSuccess = refreshDocumentSharing; - buttonStat.onPublishRemoved = refreshDocumentSharing; - - return buttonStat; - + defaultConfig: { + name1: "Words", + value1: "\\S+", + name2: "Characters", + value2: "\\S", + name3: "Paragraphs", + value3: ".+", + }, + settingsBloc: [ + '

Adds a "Document statistics" button in the navigation bar.

', + '

', + ' ', + ' ', + ' ', + ' ', + '

', + '

', + ' ', + ' ', + ' ', + ' ', + '

', + '

', + ' ', + ' ', + ' ', + ' ', + '

'].join("") + }; + + buttonStat.onLoadSettings = function() { + _.each(buttonStat.defaultConfig, function(value, key) { + utils.setInputValue("#input-stat-" + key, buttonStat.config[key]); + }); + }; + + buttonStat.onSaveSettings = function(newConfig, event) { + _.each(buttonStat.defaultConfig, function(value, key) { + newConfig[key] = utils.getInputTextValue("#input-stat-" + key, event); + }); + }; + + buttonStat.onCreateButton = function() { + return $([ + '', + '' + ].join("")); + }; + + buttonStat.onPreviewFinished = function() { + var text = $("#wmd-preview").text(); + $("#span-stat-value1").text(text.match(new RegExp(buttonStat.config.value1, "g")).length); + $("#span-stat-value2").text(text.match(new RegExp(buttonStat.config.value2, "g")).length); + $("#span-stat-value3").text(text.match(new RegExp(buttonStat.config.value3, "g")).length); + }; + + return buttonStat; + }); \ No newline at end of file diff --git a/js/extensions/button-sync.js b/js/extensions/button-sync.js index 3d9d3dbe..07938733 100644 --- a/js/extensions/button-sync.js +++ b/js/extensions/button-sync.js @@ -2,77 +2,77 @@ define([ "jquery", "underscore" ], function($, _) { - - var buttonSync = { - extensionId: "buttonSync", - extensionName: 'Button "Synchronize"', - settingsBloc: '

Adds a "Synchronize documents" button in the navigation bar.

' - }; - - var button = undefined; - var syncRunning = false; - var uploadPending = false; - var isOffline = false; - // Enable/disable the button - var updateButtonState = function() { - if(button === undefined) { - return; - } - if(syncRunning === true || uploadPending === false || isOffline) { - button.addClass("disabled"); - } - else { - button.removeClass("disabled"); - } - }; - - var synchronizer = undefined; - buttonSync.onSynchronizerCreated = function(synchronizerParameter) { - synchronizer = synchronizerParameter; - }; - - buttonSync.onCreateButton = function() { - button = $([ - ''].join("") - ).click(function() { - if(!$(this).hasClass("disabled")) { - synchronizer.forceSync(); - } - }); - return button; - }; - - buttonSync.onReady = updateButtonState; - - buttonSync.onSyncRunning = function(isRunning) { - syncRunning = isRunning; - uploadPending = true; - updateButtonState(); - }; - - buttonSync.onSyncSuccess = function() { - uploadPending = false; - updateButtonState(); - }; - - buttonSync.onOfflineChanged = function(isOfflineParameter) { - isOffline = isOfflineParameter; - updateButtonState(); - }; - - // Check that a file has synchronized locations - var checkSynchronization = function(fileDesc) { - if(_.size(fileDesc.syncLocations) !== 0) { - uploadPending = true; - updateButtonState(); - } - }; - - buttonSync.onContentChanged = checkSynchronization; - buttonSync.onTitleChanged = checkSynchronization; - - return buttonSync; - + + var buttonSync = { + extensionId: "buttonSync", + extensionName: 'Button "Synchronize"', + settingsBloc: '

Adds a "Synchronize documents" button in the navigation bar.

' + }; + + var button = undefined; + var syncRunning = false; + var uploadPending = false; + var isOffline = false; + // Enable/disable the button + var updateButtonState = function() { + if(button === undefined) { + return; + } + if(syncRunning === true || uploadPending === false || isOffline) { + button.addClass("disabled"); + } + else { + button.removeClass("disabled"); + } + }; + + var synchronizer = undefined; + buttonSync.onSynchronizerCreated = function(synchronizerParameter) { + synchronizer = synchronizerParameter; + }; + + buttonSync.onCreateButton = function() { + button = $([ + '' + ].join("")).click(function() { + if(!$(this).hasClass("disabled")) { + synchronizer.forceSync(); + } + }); + return button; + }; + + buttonSync.onReady = updateButtonState; + + buttonSync.onSyncRunning = function(isRunning) { + syncRunning = isRunning; + uploadPending = true; + updateButtonState(); + }; + + buttonSync.onSyncSuccess = function() { + uploadPending = false; + updateButtonState(); + }; + + buttonSync.onOfflineChanged = function(isOfflineParameter) { + isOffline = isOfflineParameter; + updateButtonState(); + }; + + // Check that a file has synchronized locations + var checkSynchronization = function(fileDesc) { + if(_.size(fileDesc.syncLocations) !== 0) { + uploadPending = true; + updateButtonState(); + } + }; + + buttonSync.onContentChanged = checkSynchronization; + buttonSync.onTitleChanged = checkSynchronization; + + return buttonSync; + }); \ No newline at end of file diff --git a/js/extensions/document-selector.js b/js/extensions/document-selector.js index 727cbab5..0ac99ea0 100644 --- a/js/extensions/document-selector.js +++ b/js/extensions/document-selector.js @@ -3,70 +3,99 @@ define([ "underscore", "file-system" ], function($, _, fileSystem) { - - var documentSelector = { - extensionId: "documentSelector", - extensionName: "Document selector", - settingsBloc: '

Builds the "Open document" dropdown menu.

' - }; - - var fileMgr = undefined; - documentSelector.onFileMgrCreated = function(fileMgrParameter) { - fileMgr = fileMgrParameter; - }; - - var liMap = undefined; - var buildSelector = function() { - - function composeTitle(fileDesc) { - var result = []; - var syncAttributesList = _.values(fileDesc.syncLocations); - var publishAttributesList = _.values(fileDesc.publishLocations); - var attributesList = syncAttributesList.concat(publishAttributesList); - _.chain(attributesList).sortBy(function(attributes) { - return attributes.provider.providerId; - }).each(function(attributes) { - result.push(''); - }); - result.push(" "); - result.push(fileDesc.title); - return result.join(""); - } - liMap = {}; - $("#file-selector li:not(.stick)").empty(); - _.chain( - fileSystem - ).sortBy(function(fileDesc) { - return fileDesc.title.toLowerCase(); - }).each(function(fileDesc) { - var a = $('').html(composeTitle(fileDesc)).click(function() { - if(!liMap[fileDesc.fileIndex].is(".disabled")) { - fileMgr.selectFile(fileDesc); - } - }); - var li = $("
  • ").append(a); - liMap[fileDesc.fileIndex] = li; - $("#file-selector").append(li); - }); - }; - - documentSelector.onFileSelected = function(fileDesc) { - if(liMap === undefined) { - buildSelector(); - } - $("#file-selector li:not(.stick)").removeClass("disabled"); - liMap[fileDesc.fileIndex].addClass("disabled"); - }; - - documentSelector.onFileCreated = buildSelector; - documentSelector.onFileDeleted = buildSelector; - documentSelector.onTitleChanged = buildSelector; - documentSelector.onSyncExportSuccess = buildSelector; - documentSelector.onSyncRemoved = buildSelector; - documentSelector.onNewPublishSuccess = buildSelector; - documentSelector.onPublishRemoved = buildSelector; - - return documentSelector; - + var documentSelector = { + extensionId: "documentSelector", + extensionName: "Document selector", + settingsBloc: '

    Builds the "Open document" dropdown menu.

    ' + }; + + var fileMgr = undefined; + documentSelector.onFileMgrCreated = function(fileMgrParameter) { + fileMgr = fileMgrParameter; + }; + + var liMap = undefined; + var buildSelector = function() { + + function composeTitle(fileDesc) { + var result = []; + var syncAttributesList = _.values(fileDesc.syncLocations); + var publishAttributesList = _.values(fileDesc.publishLocations); + var attributesList = syncAttributesList.concat(publishAttributesList); + _.chain(attributesList).sortBy(function(attributes) { + return attributes.provider.providerId; + }).each(function(attributes) { + result.push(''); + }); + result.push(" "); + result.push(fileDesc.title); + return result.join(""); + } + + liMap = {}; + $("#file-selector li:not(.stick)").empty(); + _.chain(fileSystem).sortBy(function(fileDesc) { + return fileDesc.title.toLowerCase(); + }).each(function(fileDesc) { + var a = $('
    ').html(composeTitle(fileDesc)).click(function() { + if(!liMap[fileDesc.fileIndex].is(".disabled")) { + fileMgr.selectFile(fileDesc); + } + }); + var li = $("
  • ").append(a); + liMap[fileDesc.fileIndex] = li; + $("#file-selector").append(li); + }); + }; + + documentSelector.onFileSelected = function(fileDesc) { + if(liMap === undefined) { + buildSelector(); + } + $("#file-selector li:not(.stick)").removeClass("disabled"); + liMap[fileDesc.fileIndex].addClass("disabled"); + }; + + documentSelector.onFileCreated = buildSelector; + documentSelector.onFileDeleted = buildSelector; + documentSelector.onTitleChanged = buildSelector; + documentSelector.onSyncExportSuccess = buildSelector; + 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)"); + liList.show(); + if(filter) { + var words = filter.toLowerCase().split(/\s+/); + liList.each(function() { + var fileTitle = $(this).text().toLowerCase(); + if(_.some(words, function(word) { + return fileTitle.indexOf(word) === -1; + })) { + $(this).hide(); + } + }); + } + } + + documentSelector.onReady = function() { + $(".action-open-file").click(function() { + filterFileSelector(); + _.defer(function() { + $("#file-search").val("").focus(); + }); + }); + $("#file-search").keyup(function() { + filterFileSelector($(this).val()); + }).click(function(event) { + event.stopPropagation(); + }); + }; + + return documentSelector; + }); \ No newline at end of file diff --git a/js/extensions/document-title.js b/js/extensions/document-title.js index 56987ab1..80932d8b 100644 --- a/js/extensions/document-title.js +++ b/js/extensions/document-title.js @@ -2,62 +2,62 @@ define([ "jquery", "underscore" ], function($, _) { - - var documentTitle = { - extensionId: "documentTitle", - extensionName: "Document title", - settingsBloc: '

    Responsible for showing the document title in the navigation bar.

    ' - }; - - var layout = undefined; - documentTitle.onLayoutCreated = function(layoutParameter) { - layout = layoutParameter; - }; - - var fileDesc = undefined; - var updateTitle = function(fileDescParameter) { - if(fileDescParameter !== fileDesc) { - return; - } - - function composeTitle(fileDesc) { - var result = []; - var syncAttributesList = _.values(fileDesc.syncLocations); - var publishAttributesList = _.values(fileDesc.publishLocations); - var attributesList = syncAttributesList.concat(publishAttributesList); - _.chain(attributesList).sortBy(function(attributes) { - return attributes.provider.providerId; - }).each(function(attributes) { - result.push(''); - }); - result.push(" "); - result.push(fileDesc.title); - return result.join(""); - } - var title = fileDesc.title; - document.title = "StackEdit - " + title; - $("#file-title").html(composeTitle(fileDesc)); - $(".file-title").text(title); - $("#file-title-input").val(title); - - if(layout !== undefined) { - // Use defer to make sure UI has been updated - _.defer(layout.resizeAll); - } - }; - - documentTitle.onFileSelected = function(fileDescParameter) { - fileDesc = fileDescParameter; - updateTitle(fileDescParameter); - }; - - documentTitle.onTitleChanged = updateTitle; - documentTitle.onSyncExportSuccess = updateTitle; - documentTitle.onSyncRemoved = updateTitle; - documentTitle.onNewPublishSuccess = updateTitle; - documentTitle.onPublishRemoved = updateTitle; - - return documentTitle; - + var documentTitle = { + extensionId: "documentTitle", + extensionName: "Document title", + settingsBloc: '

    Responsible for showing the document title in the navigation bar.

    ' + }; + + var layout = undefined; + documentTitle.onLayoutCreated = function(layoutParameter) { + layout = layoutParameter; + }; + + var fileDesc = undefined; + var updateTitle = function(fileDescParameter) { + if(fileDescParameter !== fileDesc) { + return; + } + + function composeTitle(fileDesc) { + var result = []; + var syncAttributesList = _.values(fileDesc.syncLocations); + var publishAttributesList = _.values(fileDesc.publishLocations); + var attributesList = syncAttributesList.concat(publishAttributesList); + _.chain(attributesList).sortBy(function(attributes) { + return attributes.provider.providerId; + }).each(function(attributes) { + result.push(''); + }); + result.push(" "); + result.push(fileDesc.title); + return result.join(""); + } + + var title = fileDesc.title; + document.title = "StackEdit - " + title; + $("#file-title").html(composeTitle(fileDesc)); + $(".file-title").text(title); + $("#file-title-input").val(title); + + if(layout !== undefined) { + // Use defer to make sure UI has been updated + _.defer(layout.resizeAll); + } + }; + + documentTitle.onFileSelected = function(fileDescParameter) { + fileDesc = fileDescParameter; + updateTitle(fileDescParameter); + }; + + documentTitle.onTitleChanged = updateTitle; + documentTitle.onSyncExportSuccess = updateTitle; + documentTitle.onSyncRemoved = updateTitle; + documentTitle.onNewPublishSuccess = updateTitle; + documentTitle.onPublishRemoved = updateTitle; + + return documentTitle; + }); \ No newline at end of file diff --git a/js/extensions/email-converter.js b/js/extensions/email-converter.js index f4223425..ab516316 100644 --- a/js/extensions/email-converter.js +++ b/js/extensions/email-converter.js @@ -1,20 +1,19 @@ define(function() { - - var emailConverter = { - extensionId: "emailConverter", - extensionName: "Email Converter", + + var emailConverter = { + extensionId: "emailConverter", + extensionName: "Email Converter", optional: true, - settingsBloc: '

    Converts email adresses in the form <email@example.com> into a clickable links.

    ' - }; - - emailConverter.onEditorConfigure = function(editor) { - editor.getConverter().hooks.chain("postConversion", function(text) { - return text.replace(/<(mailto\:)?([^\s>]+@[^\s>]+\.\S+?)>/g, function(match, mailto, email) { - return '
    ' + email + ''; - }); - }); - }; - - return emailConverter; + settingsBloc: '

    Converts email adresses in the form <email@example.com> into a clickable links.

    ' + }; + + emailConverter.onEditorConfigure = function(editor) { + editor.getConverter().hooks.chain("postConversion", function(text) { + return text.replace(/<(mailto\:)?([^\s>]+@[^\s>]+\.\S+?)>/g, function(match, mailto, email) { + return '' + email + ''; + }); + }); + }; + + return emailConverter; }); - diff --git a/js/extensions/manage-publication.js b/js/extensions/manage-publication.js index fe68bca2..08dee00d 100644 --- a/js/extensions/manage-publication.js +++ b/js/extensions/manage-publication.js @@ -2,65 +2,67 @@ define([ "jquery", "underscore" ], function($, _) { - - var managePublication = { - extensionId: "managePublication", - extensionName: "Manage publication", - settingsBloc: '

    Populates the "Manage publication" dialog box.

    ' - }; - - var fileMgr = undefined; - managePublication.onFileMgrCreated = function(fileMgrParameter) { - fileMgr = fileMgrParameter; - }; - - var fileDesc = undefined; - var lineTemplate = [ + + var managePublication = { + extensionId: "managePublication", + extensionName: "Manage publication", + settingsBloc: '

    Populates the "Manage publication" dialog box.

    ' + }; + + var fileMgr = undefined; + managePublication.onFileMgrCreated = function(fileMgrParameter) { + fileMgr = fileMgrParameter; + }; + + var fileDesc = undefined; + var lineTemplate = [ '
    ', - '', - '', - '', - '', - '
    '].join(""); - var removeButtonTemplate = ''; - var refreshDialog = function(fileDescParameter) { - if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { - return; - } - - var publishAttributesList = _.values(fileDesc.publishLocations); - $(".msg-no-publish, .msg-publish-list").addClass("hide"); - var publishList = $("#manage-publish-list").empty(); - if (publishAttributesList.length > 0) { - $(".msg-publish-list").removeClass("hide"); - } else { - $(".msg-no-publish").removeClass("hide"); - } - _.each(publishAttributesList, function(publishAttributes) { - formattedAttributes = _.omit(publishAttributes, "provider", "publishIndex", "sharingLink"); - if(formattedAttributes.password) { - formattedAttributes.password = "********"; - } - var publishDesc = JSON.stringify(formattedAttributes).replace(/{|}|"/g, "").replace(/,/g, ", "); - var lineElement = $(_.template(lineTemplate, { - provider: publishAttributes.provider, - publishDesc: publishDesc - })); - lineElement.append($(removeButtonTemplate).click(function() { - fileMgr.removePublish(publishAttributes); - })); - publishList.append(lineElement); - }); - }; - - managePublication.onFileSelected = function(fileDescParameter) { - fileDesc = fileDescParameter; - refreshDialog(fileDescParameter); - }; - - managePublication.onNewPublishSuccess = refreshDialog; - managePublication.onPublishRemoved = refreshDialog; - - return managePublication; - + ' ', + ' ', + ' ', + ' ', + '
  • ' + ].join(""); + var removeButtonTemplate = ''; + var refreshDialog = function(fileDescParameter) { + if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { + return; + } + + var publishAttributesList = _.values(fileDesc.publishLocations); + $(".msg-no-publish, .msg-publish-list").addClass("hide"); + var publishList = $("#manage-publish-list").empty(); + if(publishAttributesList.length > 0) { + $(".msg-publish-list").removeClass("hide"); + } + else { + $(".msg-no-publish").removeClass("hide"); + } + _.each(publishAttributesList, function(publishAttributes) { + formattedAttributes = _.omit(publishAttributes, "provider", "publishIndex", "sharingLink"); + if(formattedAttributes.password) { + formattedAttributes.password = "********"; + } + var publishDesc = JSON.stringify(formattedAttributes).replace(/{|}|"/g, "").replace(/,/g, ", "); + var lineElement = $(_.template(lineTemplate, { + provider: publishAttributes.provider, + publishDesc: publishDesc + })); + lineElement.append($(removeButtonTemplate).click(function() { + fileMgr.removePublish(publishAttributes); + })); + publishList.append(lineElement); + }); + }; + + managePublication.onFileSelected = function(fileDescParameter) { + fileDesc = fileDescParameter; + refreshDialog(fileDescParameter); + }; + + managePublication.onNewPublishSuccess = refreshDialog; + managePublication.onPublishRemoved = refreshDialog; + + return managePublication; + }); \ No newline at end of file diff --git a/js/extensions/manage-synchronization.js b/js/extensions/manage-synchronization.js index 79e9bbba..84578673 100644 --- a/js/extensions/manage-synchronization.js +++ b/js/extensions/manage-synchronization.js @@ -2,61 +2,63 @@ define([ "jquery", "underscore" ], function($, _) { - - var manageSynchronization = { - extensionId: "manageSynchronization", - extensionName: "Manage synchronization", - settingsBloc: '

    Populates the "Manage synchronization" dialog box.

    ' - }; - - var fileMgr = undefined; - manageSynchronization.onFileMgrCreated = function(fileMgrParameter) { - fileMgr = fileMgrParameter; - }; - - var fileDesc = undefined; - var lineTemplate = [ + + var manageSynchronization = { + extensionId: "manageSynchronization", + extensionName: "Manage synchronization", + settingsBloc: '

    Populates the "Manage synchronization" dialog box.

    ' + }; + + var fileMgr = undefined; + manageSynchronization.onFileMgrCreated = function(fileMgrParameter) { + fileMgr = fileMgrParameter; + }; + + var fileDesc = undefined; + var lineTemplate = [ '
    ', - '', - '', - '', - '', - '
    '].join(""); - var removeButtonTemplate = ''; - var refreshDialog = function(fileDescParameter) { - if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { - return; - } - - var syncAttributesList = _.values(fileDesc.syncLocations); - $(".msg-no-sync, .msg-sync-list").addClass("hide"); - var syncList = $("#manage-sync-list").empty(); - if (syncAttributesList.length > 0) { - $(".msg-sync-list").removeClass("hide"); - } else { - $(".msg-no-sync").removeClass("hide"); - } - _.each(syncAttributesList, function(syncAttributes) { - var syncDesc = syncAttributes.id || syncAttributes.path; - var lineElement = $(_.template(lineTemplate, { - provider: syncAttributes.provider, - syncDesc: syncDesc - })); - lineElement.append($(removeButtonTemplate).click(function() { - fileMgr.removeSync(syncAttributes); - })); - syncList.append(lineElement); - }); - }; - - manageSynchronization.onFileSelected = function(fileDescParameter) { - fileDesc = fileDescParameter; - refreshDialog(fileDescParameter); - }; - - manageSynchronization.onSyncExportSuccess = refreshDialog; - manageSynchronization.onSyncRemoved = refreshDialog; - - return manageSynchronization; - + ' ', + ' ', + ' ', + ' ', + '' + ].join(""); + var removeButtonTemplate = ''; + var refreshDialog = function(fileDescParameter) { + if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { + return; + } + + var syncAttributesList = _.values(fileDesc.syncLocations); + $(".msg-no-sync, .msg-sync-list").addClass("hide"); + var syncList = $("#manage-sync-list").empty(); + if(syncAttributesList.length > 0) { + $(".msg-sync-list").removeClass("hide"); + } + else { + $(".msg-no-sync").removeClass("hide"); + } + _.each(syncAttributesList, function(syncAttributes) { + var syncDesc = syncAttributes.id || syncAttributes.path; + var lineElement = $(_.template(lineTemplate, { + provider: syncAttributes.provider, + syncDesc: syncDesc + })); + lineElement.append($(removeButtonTemplate).click(function() { + fileMgr.removeSync(syncAttributes); + })); + syncList.append(lineElement); + }); + }; + + manageSynchronization.onFileSelected = function(fileDescParameter) { + fileDesc = fileDescParameter; + refreshDialog(fileDescParameter); + }; + + manageSynchronization.onSyncExportSuccess = refreshDialog; + manageSynchronization.onSyncRemoved = refreshDialog; + + return manageSynchronization; + }); \ No newline at end of file diff --git a/js/extensions/markdown-extra.js b/js/extensions/markdown-extra.js index a567b480..e909d21e 100644 --- a/js/extensions/markdown-extra.js +++ b/js/extensions/markdown-extra.js @@ -2,44 +2,44 @@ define([ "utils", "lib/Markdown.Extra" ], function(utils) { - + var markdownExtra = { extensionId: "markdownExtra", extensionName: "Markdown Extra", optional: true, defaultConfig: { - prettify: true - }, + prettify: true + }, settingsBloc: [ - '

    Adds extra features to the original Markdown syntax.

    ', - '
    ', - '
    ', - '', - '
    ', - '', - '
    ', - '
    ', - '
    ' - ].join("") + '

    Adds extra features to the original Markdown syntax.

    ', + '
    ', + '
    ', + ' ', + '
    ', + ' ', + '
    ', + '
    ', + '
    ' + ].join("") }; - + markdownExtra.onLoadSettings = function() { - utils.setInputChecked("#input-markdownextra-prettify", markdownExtra.config.prettify); + utils.setInputChecked("#input-markdownextra-prettify", markdownExtra.config.prettify); }; - + markdownExtra.onSaveSettings = function(newConfig, event) { - newConfig.prettify = utils.getInputChecked("#input-markdownextra-prettify"); + newConfig.prettify = utils.getInputChecked("#input-markdownextra-prettify"); }; - + markdownExtra.onEditorConfigure = function(editor) { - var converter = editor.getConverter(); - var options = {}; - if(markdownExtra.config.prettify === true) { - options.highlighter = "prettify"; - editor.hooks.chain("onPreviewRefresh", prettyPrint); - } - Markdown.Extra.init(converter, options); - }; - + var converter = editor.getConverter(); + var options = {}; + if(markdownExtra.config.prettify === true) { + options.highlighter = "prettify"; + editor.hooks.chain("onPreviewRefresh", prettyPrint); + } + Markdown.Extra.init(converter, options); + }; + return markdownExtra; }); \ No newline at end of file diff --git a/js/extensions/notifications.js b/js/extensions/notifications.js index 5dedce51..ca2c16a5 100644 --- a/js/extensions/notifications.js +++ b/js/extensions/notifications.js @@ -4,117 +4,117 @@ define([ "utils", "jgrowl" ], function($, _, utils, jGrowl) { - - var notifications = { - extensionId: "notifications", - extensionName: "Notifications", - defaultConfig: { - timeout: 5000 - }, + + var notifications = { + extensionId: "notifications", + extensionName: "Notifications", + defaultConfig: { + timeout: 5000 + }, settingsBloc: [ - '

    Shows notification messages in the bottom-right corner of the screen.

    ', - '
    ', - '
    ', - '', - '
    ', - '', - 'ms', - '
    ', - '
    ', - '
    ' - ].join("") - }; - - notifications.onLoadSettings = function() { - utils.setInputValue("#input-notifications-timeout", notifications.config.timeout); + '

    Shows notification messages in the bottom-right corner of the screen.

    ', + '
    ', + '
    ', + ' ', + '
    ', + ' ', + ' ms', + '
    ', + '
    ', + '
    ' + ].join("") }; - + + notifications.onLoadSettings = function() { + utils.setInputValue("#input-notifications-timeout", notifications.config.timeout); + }; + notifications.onSaveSettings = function(newConfig, event) { - newConfig.timeout = utils.getInputIntValue("#input-notifications-timeout", event, 1, 60000); + newConfig.timeout = utils.getInputIntValue("#input-notifications-timeout", event, 1, 60000); }; - - notifications.onReady = function() { - // jGrowl configuration - jGrowl.defaults.life = notifications.config.timeout; - jGrowl.defaults.closer = false; - jGrowl.defaults.closeTemplate = ''; - jGrowl.defaults.position = 'bottom-right'; - }; - - function showMessage(msg, iconClass, options) { - if(!msg) { - return; - } - var endOfMsg = msg.indexOf("|"); - if(endOfMsg !== -1) { - msg = msg.substring(0, endOfMsg); - if(!msg) { - return; - } - } - options = options || {}; - iconClass = iconClass || "icon-info-sign"; - jGrowl(" " + _.escape(msg), options); - } - - notifications.onMessage = function(message) { - logger.log(message); - showMessage(message); - }; - - notifications.onError = function(error) { - logger.error(error); - if(_.isString(error)) { - showMessage(error, "icon-warning-sign"); - } - else if(_.isObject(error)) { - showMessage(error.message, "icon-warning-sign"); - } - }; - - notifications.onOfflineChanged = function(isOffline) { - if(isOffline === true) { - showMessage("You are offline.", "icon-exclamation-sign msg-offline", { - sticky : true, - close : function() { - showMessage("You are back online!", "icon-signal"); - } - }); - } else { - $(".msg-offline").parents(".jGrowl-notification").trigger( - 'jGrowl.beforeClose'); - } - }; - - notifications.onSyncImportSuccess = function(fileDescList, provider) { - if(!fileDescList) { - return; - } - var titles = _.map(fileDescList, function(fileDesc) { - return fileDesc.title; - }).join(", "); - showMessage(titles + ' imported successfully from ' + provider.providerName + '.'); - }; - - notifications.onSyncExportSuccess = function(fileDesc, syncAttributes) { - showMessage('"' + fileDesc.title + '" will now be synchronized on ' + syncAttributes.provider.providerName + '.'); - }; - - notifications.onSyncRemoved = function(fileDesc, syncAttributes) { - showMessage(syncAttributes.provider.providerName + " synchronized location has been removed."); - }; - - notifications.onPublishSuccess = function(fileDesc) { - showMessage('"' + fileDesc.title + '" successfully published.'); - }; - - notifications.onNewPublishSuccess = function(fileDesc, publishIndex, publishAttributes) { - showMessage('"' + fileDesc.title + '" is now published on ' + publishAttributes.provider.providerName + '.'); - }; - - notifications.onPublishRemoved = function(fileDesc, publishAttributes) { - showMessage(publishAttributes.provider.providerName + " publish location has been removed."); - }; - - return notifications; + + notifications.onReady = function() { + // jGrowl configuration + jGrowl.defaults.life = notifications.config.timeout; + jGrowl.defaults.closer = false; + jGrowl.defaults.closeTemplate = ''; + jGrowl.defaults.position = 'bottom-right'; + }; + + function showMessage(msg, iconClass, options) { + if(!msg) { + return; + } + var endOfMsg = msg.indexOf("|"); + if(endOfMsg !== -1) { + msg = msg.substring(0, endOfMsg); + if(!msg) { + return; + } + } + options = options || {}; + iconClass = iconClass || "icon-info-sign"; + jGrowl(" " + _.escape(msg), options); + } + + notifications.onMessage = function(message) { + logger.log(message); + showMessage(message); + }; + + notifications.onError = function(error) { + logger.error(error); + if(_.isString(error)) { + showMessage(error, "icon-warning-sign"); + } + else if(_.isObject(error)) { + showMessage(error.message, "icon-warning-sign"); + } + }; + + notifications.onOfflineChanged = function(isOffline) { + if(isOffline === true) { + showMessage("You are offline.", "icon-exclamation-sign msg-offline", { + sticky: true, + close: function() { + showMessage("You are back online!", "icon-signal"); + } + }); + } + else { + $(".msg-offline").parents(".jGrowl-notification").trigger('jGrowl.beforeClose'); + } + }; + + notifications.onSyncImportSuccess = function(fileDescList, provider) { + if(!fileDescList) { + return; + } + var titles = _.map(fileDescList, function(fileDesc) { + return fileDesc.title; + }).join(", "); + showMessage(titles + ' imported successfully from ' + provider.providerName + '.'); + }; + + notifications.onSyncExportSuccess = function(fileDesc, syncAttributes) { + showMessage('"' + fileDesc.title + '" will now be synchronized on ' + syncAttributes.provider.providerName + '.'); + }; + + notifications.onSyncRemoved = function(fileDesc, syncAttributes) { + showMessage(syncAttributes.provider.providerName + " synchronized location has been removed."); + }; + + notifications.onPublishSuccess = function(fileDesc) { + showMessage('"' + fileDesc.title + '" successfully published.'); + }; + + notifications.onNewPublishSuccess = function(fileDesc, publishIndex, publishAttributes) { + showMessage('"' + fileDesc.title + '" is now published on ' + publishAttributes.provider.providerName + '.'); + }; + + notifications.onPublishRemoved = function(fileDesc, publishAttributes) { + showMessage(publishAttributes.provider.providerName + " publish location has been removed."); + }; + + return notifications; }); \ No newline at end of file diff --git a/js/extensions/scroll-link.js b/js/extensions/scroll-link.js index 644c3975..09e62912 100644 --- a/js/extensions/scroll-link.js +++ b/js/extensions/scroll-link.js @@ -1,199 +1,202 @@ define([ "jquery", "underscore", - "lib/css_browser_selector" + "lib/css_browser_selector" ], function($, _) { - - var scrollLink = { - extensionId: "scrollLink", - extensionName: "Scroll Link", - optional: true, - settingsBloc: [ - '

    Binds together editor and preview scrollbars.

    ', - '
    NOTE: ', - 'The mapping between Markdown and HTML is based on the position of the title elements (h1, h2, ...) in the page. ', - 'Therefore, if your document does not contain any title, the mapping will be linear and consequently less accurate.', - '' - ].join("") - }; - - var mdSectionList = []; - var htmlSectionList = []; - function pxToFloat(px) { - return parseFloat(px.substring(0, px.length-2)); - } - var buildSections = _.debounce(function() { - - // Try to find Markdown sections by looking for titles - var editorElt = $("#wmd-input"); - mdSectionList = []; - // This textarea is used to measure sections height - var textareaElt = $("#md-section-helper"); - // It has to be the same width than wmd-input - textareaElt.width(editorElt.width()); - // Consider wmd-input top padding - var padding = pxToFloat(editorElt.css('padding-top')); - var offset = 0, mdSectionOffset = 0; - function addMdSection(sectionText) { - var sectionHeight = padding; - if(sectionText !== undefined) { - textareaElt.val(sectionText); - sectionHeight += textareaElt.prop('scrollHeight'); - } - var newSectionOffset = mdSectionOffset + sectionHeight; - mdSectionList.push({ - startOffset: mdSectionOffset, - endOffset: newSectionOffset, - height: sectionHeight - }); - mdSectionOffset = newSectionOffset; - padding = 0; - } - // Create MD sections by finding title patterns (excluding gfm blocs) - var text = editorElt.val() + "\n\n"; - text.replace(/^```.*\n[\s\S]*?\n```|(^.+[ \t]*\n=+[ \t]*\n+|^.+[ \t]*\n-+[ \t]*\n+|^\#{1,6}[ \t]*.+?[ \t]*\#*\n+)/gm, - function(match, title, matchOffset) { - if(title) { - // We just found a title which means end of the previous section - // Exclude last \n of the section - var sectionText = undefined; - if(matchOffset > offset) { - sectionText = text.substring(offset, matchOffset-1); - } - addMdSection(sectionText); - offset = matchOffset; - } - return ""; - } - ); - // Last section - // Consider wmd-input bottom padding and exclude \n\n previously added - padding += pxToFloat(editorElt.css('padding-bottom')); - addMdSection(text.substring(offset, text.length-2)); - - // Try to find corresponding sections in the preview - var previewElt = $("#wmd-preview"); - htmlSectionList = []; - var htmlSectionOffset = 0; - var previewScrollTop = previewElt.scrollTop(); - // Each title element is a section separator - previewElt.children("h1,h2,h3,h4,h5,h6").each(function() { - // Consider div scroll position and header element top margin - var newSectionOffset = $(this).position().top + previewScrollTop + pxToFloat($(this).css('margin-top')); - htmlSectionList.push({ - startOffset: htmlSectionOffset, - endOffset: newSectionOffset, - height: newSectionOffset - htmlSectionOffset - }); - htmlSectionOffset = newSectionOffset; - }); - // Last section - var scrollHeight = previewElt.prop('scrollHeight'); - htmlSectionList.push({ - startOffset: htmlSectionOffset, - endOffset: scrollHeight, - height: scrollHeight - htmlSectionOffset - }); - - // apply Scroll Link - lastEditorScrollTop = -9; - skipScrollLink = false; - isScrollPreview = false; - runScrollLink(); - }, 500); - - // -9 is less than -5 - var lastEditorScrollTop = -9; - var lastPreviewScrollTop = -9; - var skipScrollLink = false; - var isScrollPreview = false; - var runScrollLink = _.debounce(function() { - if(skipScrollLink === true || mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) { - return; - } - var editorElt = $("#wmd-input"); - var editorScrollTop = editorElt.scrollTop(); - var previewElt = $("#wmd-preview"); - var previewScrollTop = previewElt.scrollTop(); - function animate(srcScrollTop, srcSectionList, destElt, destSectionList, lastDestScrollTop, callback) { - // Find the section corresponding to the offset - var sectionIndex = undefined; - var srcSection = _.find(srcSectionList, function(section, index) { - sectionIndex = index; - return srcScrollTop < section.endOffset; - }); - if(srcSection === undefined) { - // Something wrong in the algorithm... - return -9; - } - var posInSection = (srcScrollTop - srcSection.startOffset) / srcSection.height; - var destSection = destSectionList[sectionIndex]; - var destScrollTop = destSection.startOffset + destSection.height * posInSection; - destScrollTop = _.min([destScrollTop, destElt.prop('scrollHeight') - destElt.outerHeight()]); - if(Math.abs(destScrollTop - lastDestScrollTop) < 5) { - // Skip the animation in case it's not necessary - return; - } - destElt.animate({scrollTop: destScrollTop}, 600, function() { - callback(destScrollTop); - }); - } - // Perform the animation if diff > 5px - if(isScrollPreview === false && Math.abs(editorScrollTop - lastEditorScrollTop) > 5) { - // Animate the preview - lastEditorScrollTop = editorScrollTop; - animate(editorScrollTop, mdSectionList, previewElt, htmlSectionList, lastPreviewScrollTop, function(destScrollTop) { - lastPreviewScrollTop = destScrollTop; - }); - } - else if(Math.abs(previewScrollTop - lastPreviewScrollTop) > 5) { - // Animate the editor - lastPreviewScrollTop = previewScrollTop; - animate(previewScrollTop, htmlSectionList, editorElt, mdSectionList, lastEditorScrollTop, function(destScrollTop) { - lastEditorScrollTop = destScrollTop; - }); - } - }, 600); - scrollLink.onLayoutConfigure = function(layoutConfig) { - layoutConfig.onresize = buildSections; - }; - - scrollLink.onLayoutCreated = function() { - $("#wmd-preview").scroll(function() { - isScrollPreview = true; - runScrollLink(); - }); - $("#wmd-input").scroll(function() { - isScrollPreview = false; - runScrollLink(); - }); - }; - - scrollLink.onEditorConfigure = function(editor) { - skipScrollLink = true; - lastPreviewScrollTop = 0; - editor.hooks.chain("onPreviewRefresh", function() { - skipScrollLink = true; - }); - }; - - scrollLink.onPreviewFinished = function() { - // MathJax may have change the scrolling position. Restore it. - if(lastPreviewScrollTop >= 0) { - $("#wmd-preview").scrollTop(lastPreviewScrollTop); - } - _.defer(function() { - // Modify scroll position of the preview not the editor - lastEditorScrollTop = -9; - buildSections(); - // Preview may change if images are loading - $("#wmd-preview img").load(function() { - lastEditorScrollTop = -9; - buildSections(); - }); - }); - }; - - return scrollLink; + var scrollLink = { + extensionId: "scrollLink", + extensionName: "Scroll Link", + optional: true, + settingsBloc: [ + '

    Binds together editor and preview scrollbars.

    ', + '
    NOTE: ', + ' The mapping between Markdown and HTML is based on the position of the title elements (h1, h2, ...) in the page. ', + ' Therefore, if your document does not contain any title, the mapping will be linear and consequently less accurate.', + '' + ].join("") + }; + + var mdSectionList = []; + var htmlSectionList = []; + function pxToFloat(px) { + return parseFloat(px.substring(0, px.length - 2)); + } + var buildSections = _.debounce(function() { + + // Try to find Markdown sections by looking for titles + var editorElt = $("#wmd-input"); + mdSectionList = []; + // This textarea is used to measure sections height + var textareaElt = $("#md-section-helper"); + // It has to be the same width than wmd-input + textareaElt.width(editorElt.width()); + // Consider wmd-input top padding + var padding = pxToFloat(editorElt.css('padding-top')); + var offset = 0, mdSectionOffset = 0; + function addMdSection(sectionText) { + var sectionHeight = padding; + if(sectionText !== undefined) { + textareaElt.val(sectionText); + sectionHeight += textareaElt.prop('scrollHeight'); + } + var newSectionOffset = mdSectionOffset + sectionHeight; + mdSectionList.push({ + startOffset: mdSectionOffset, + endOffset: newSectionOffset, + height: sectionHeight + }); + mdSectionOffset = newSectionOffset; + padding = 0; + } + // Create MD sections by finding title patterns (excluding gfm blocs) + var text = editorElt.val() + "\n\n"; + text.replace(/^```.*\n[\s\S]*?\n```|(^.+[ \t]*\n=+[ \t]*\n+|^.+[ \t]*\n-+[ \t]*\n+|^\#{1,6}[ \t]*.+?[ \t]*\#*\n+)/gm, function(match, title, matchOffset) { + if(title) { + // We just found a title which means end of the previous section + // Exclude last \n of the section + var sectionText = undefined; + if(matchOffset > offset) { + sectionText = text.substring(offset, matchOffset - 1); + } + addMdSection(sectionText); + offset = matchOffset; + } + return ""; + }); + // Last section + // Consider wmd-input bottom padding and exclude \n\n previously added + padding += pxToFloat(editorElt.css('padding-bottom')); + addMdSection(text.substring(offset, text.length - 2)); + + // Try to find corresponding sections in the preview + var previewElt = $("#wmd-preview"); + htmlSectionList = []; + var htmlSectionOffset = 0; + var previewScrollTop = previewElt.scrollTop(); + // Each title element is a section separator + previewElt.children("h1,h2,h3,h4,h5,h6").each(function() { + // Consider div scroll position and header element top margin + var newSectionOffset = $(this).position().top + previewScrollTop + pxToFloat($(this).css('margin-top')); + htmlSectionList.push({ + startOffset: htmlSectionOffset, + endOffset: newSectionOffset, + height: newSectionOffset - htmlSectionOffset + }); + htmlSectionOffset = newSectionOffset; + }); + // Last section + var scrollHeight = previewElt.prop('scrollHeight'); + htmlSectionList.push({ + startOffset: htmlSectionOffset, + endOffset: scrollHeight, + height: scrollHeight - htmlSectionOffset + }); + + // apply Scroll Link + lastEditorScrollTop = -9; + skipScrollLink = false; + isScrollPreview = false; + runScrollLink(); + }, 500); + + // -9 is less than -5 + var lastEditorScrollTop = -9; + var lastPreviewScrollTop = -9; + var skipScrollLink = false; + var isScrollPreview = false; + var runScrollLink = _.debounce(function() { + if(skipScrollLink === true || mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) { + return; + } + var editorElt = $("#wmd-input"); + var editorScrollTop = editorElt.scrollTop(); + var previewElt = $("#wmd-preview"); + var previewScrollTop = previewElt.scrollTop(); + function animate(srcScrollTop, srcSectionList, destElt, destSectionList, lastDestScrollTop, callback) { + // Find the section corresponding to the offset + var sectionIndex = undefined; + var srcSection = _.find(srcSectionList, function(section, index) { + sectionIndex = index; + return srcScrollTop < section.endOffset; + }); + if(srcSection === undefined) { + // Something wrong in the algorithm... + return -9; + } + var posInSection = (srcScrollTop - srcSection.startOffset) / srcSection.height; + var destSection = destSectionList[sectionIndex]; + var destScrollTop = destSection.startOffset + destSection.height * posInSection; + destScrollTop = _.min([ + destScrollTop, + destElt.prop('scrollHeight') - destElt.outerHeight() + ]); + if(Math.abs(destScrollTop - lastDestScrollTop) < 5) { + // Skip the animation in case it's not necessary + return; + } + destElt.animate({ + scrollTop: destScrollTop + }, 600, function() { + callback(destScrollTop); + }); + } + // Perform the animation if diff > 5px + if(isScrollPreview === false && Math.abs(editorScrollTop - lastEditorScrollTop) > 5) { + // Animate the preview + lastEditorScrollTop = editorScrollTop; + animate(editorScrollTop, mdSectionList, previewElt, htmlSectionList, lastPreviewScrollTop, function(destScrollTop) { + lastPreviewScrollTop = destScrollTop; + }); + } + else if(Math.abs(previewScrollTop - lastPreviewScrollTop) > 5) { + // Animate the editor + lastPreviewScrollTop = previewScrollTop; + animate(previewScrollTop, htmlSectionList, editorElt, mdSectionList, lastEditorScrollTop, function(destScrollTop) { + lastEditorScrollTop = destScrollTop; + }); + } + }, 600); + + scrollLink.onLayoutConfigure = function(layoutConfig) { + layoutConfig.onresize = buildSections; + }; + + scrollLink.onLayoutCreated = function() { + $("#wmd-preview").scroll(function() { + isScrollPreview = true; + runScrollLink(); + }); + $("#wmd-input").scroll(function() { + isScrollPreview = false; + runScrollLink(); + }); + }; + + scrollLink.onEditorConfigure = function(editor) { + skipScrollLink = true; + lastPreviewScrollTop = 0; + editor.hooks.chain("onPreviewRefresh", function() { + skipScrollLink = true; + }); + }; + + scrollLink.onPreviewFinished = function() { + // MathJax may have change the scrolling position. Restore it. + if(lastPreviewScrollTop >= 0) { + $("#wmd-preview").scrollTop(lastPreviewScrollTop); + } + _.defer(function() { + // Modify scroll position of the preview not the editor + lastEditorScrollTop = -9; + buildSections(); + // Preview may change if images are loading + $("#wmd-preview img").load(function() { + lastEditorScrollTop = -9; + buildSections(); + }); + }); + }; + + return scrollLink; }); \ No newline at end of file diff --git a/js/extensions/toc.js b/js/extensions/toc.js index dbac74a0..c4973e33 100644 --- a/js/extensions/toc.js +++ b/js/extensions/toc.js @@ -3,116 +3,106 @@ define([ "underscore", "utils" ], function($, _, utils) { - - var toc = { - extensionId: "toc", - extensionName: "Table of content", + + var toc = { + extensionId: "toc", + extensionName: "Table of content", optional: true, - settingsBloc: '

    Generates a table of content when a [TOC] marker is found.

    ' - }; - - // TOC element description - function TocElement(tagName, anchor, text) { - this.tagName = tagName; - this.anchor = anchor; - this.text = text; - this.children = []; - } - TocElement.prototype.childrenToString = function() { - if(this.children.length === 0) { - return ""; - } - var result = "
      "; - _.each(this.children, function(child) { - result += child.toString(); - }); - result += "
    "; - return result; - }; - TocElement.prototype.toString = function() { - var result = "
  • "; - if(this.anchor && this.text) { - result += '' + this.text + ''; - } - result += this.childrenToString() + "
  • "; - return result; - }; - - // Transform flat list of TocElement into a tree - function groupTags(array, level) { - level = level || 1; - var tagName = "H" + level; - var result = []; - - var currentElement = undefined; - function pushCurrentElement() { - if(currentElement !== undefined) { - if(currentElement.children.length > 0) { - currentElement.children = groupTags(currentElement.children, level + 1); - } - result.push(currentElement); - } - } - - _.each(array, function(element, index) { - if(element.tagName != tagName) { - if(currentElement === undefined) { - currentElement = new TocElement(); - } - currentElement.children.push(element); - } - else { - pushCurrentElement(); - currentElement = element; - } - }); - pushCurrentElement(); - return result; - } - - // Build the TOC - function buildToc() { - var anchorList = {}; - function createAnchor(element) { - var id = element.prop("id") || utils.slugify(element.text()); - var anchor = id; - var index = 0; - while(_.has(anchorList, anchor)) { - anchor = id + "-" + (++index); - } - anchorList[anchor] = true; - // Update the id of the element - element.prop("id", anchor); - return anchor; - } - - var elementList = []; - $("#wmd-preview > h1," + - "#wmd-preview > h2," + - "#wmd-preview > h3," + - "#wmd-preview > h4," + - "#wmd-preview > h5," + - "#wmd-preview > h6").each(function() { - elementList.push(new TocElement( - $(this).prop("tagName"), - createAnchor($(this)), - $(this).text() - )); - }); - elementList = groupTags(elementList); - return '
      ' + elementList.toString() + '
    '; - } - - toc.onEditorConfigure = function(editor) { - // Run TOC generation when conversion is finished directly on HTML - editor.hooks.chain("onPreviewRefresh", function() { - var toc = buildToc(); - var html = $("#wmd-preview").html(); - html = html.replace(/

    \[TOC\]<\/p>/g, toc); - $("#wmd-preview").html(html); - }); - }; - - return toc; + settingsBloc: '

    Generates a table of content when a [TOC] marker is found.

    ' + }; + + // TOC element description + function TocElement(tagName, anchor, text) { + this.tagName = tagName; + this.anchor = anchor; + this.text = text; + this.children = []; + } + TocElement.prototype.childrenToString = function() { + if(this.children.length === 0) { + return ""; + } + var result = "
      "; + _.each(this.children, function(child) { + result += child.toString(); + }); + result += "
    "; + return result; + }; + TocElement.prototype.toString = function() { + var result = "
  • "; + if(this.anchor && this.text) { + result += '' + this.text + ''; + } + result += this.childrenToString() + "
  • "; + return result; + }; + + // Transform flat list of TocElement into a tree + function groupTags(array, level) { + level = level || 1; + var tagName = "H" + level; + var result = []; + + var currentElement = undefined; + function pushCurrentElement() { + if(currentElement !== undefined) { + if(currentElement.children.length > 0) { + currentElement.children = groupTags(currentElement.children, level + 1); + } + result.push(currentElement); + } + } + + _.each(array, function(element, index) { + if(element.tagName != tagName) { + if(currentElement === undefined) { + currentElement = new TocElement(); + } + currentElement.children.push(element); + } + else { + pushCurrentElement(); + currentElement = element; + } + }); + pushCurrentElement(); + return result; + } + + // Build the TOC + function buildToc() { + var anchorList = {}; + function createAnchor(element) { + var id = element.prop("id") || utils.slugify(element.text()); + var anchor = id; + var index = 0; + while (_.has(anchorList, anchor)) { + anchor = id + "-" + (++index); + } + anchorList[anchor] = true; + // Update the id of the element + element.prop("id", anchor); + return anchor; + } + + var elementList = []; + $("#wmd-preview > h1," + "#wmd-preview > h2," + "#wmd-preview > h3," + "#wmd-preview > h4," + "#wmd-preview > h5," + "#wmd-preview > h6").each(function() { + elementList.push(new TocElement($(this).prop("tagName"), createAnchor($(this)), $(this).text())); + }); + elementList = groupTags(elementList); + return '
      ' + elementList.toString() + '
    '; + } + + toc.onEditorConfigure = function(editor) { + // Run TOC generation when conversion is finished directly on HTML + editor.hooks.chain("onPreviewRefresh", function() { + var toc = buildToc(); + var html = $("#wmd-preview").html(); + html = html.replace(/

    \[TOC\]<\/p>/g, toc); + $("#wmd-preview").html(html); + }); + }; + + return toc; }); - diff --git a/js/extensions/working-indicator.js b/js/extensions/working-indicator.js index 74b472e8..31acb065 100644 --- a/js/extensions/working-indicator.js +++ b/js/extensions/working-indicator.js @@ -1,24 +1,25 @@ define([ - "jquery", - "underscore" + "jquery", + "underscore" ], function($, _) { - - var workingIndicator = { - extensionId: "workingIndicator", - extensionName: "Working indicator", - settingsBloc: '

    Displays an animated image when a network operation is running.

    ' - }; - - workingIndicator.onAsyncRunning = function(isRunning) { - if (isRunning === false) { - $(".working-indicator").removeClass("show"); - $("body").removeClass("working"); - } else { - $(".working-indicator").addClass("show"); - $("body").addClass("working"); - } - }; - - return workingIndicator; - + + var workingIndicator = { + extensionId: "workingIndicator", + extensionName: "Working indicator", + settingsBloc: '

    Displays an animated image when a network operation is running.

    ' + }; + + workingIndicator.onAsyncRunning = function(isRunning) { + if(isRunning === false) { + $(".working-indicator").removeClass("show"); + $("body").removeClass("working"); + } + else { + $(".working-indicator").addClass("show"); + $("body").addClass("working"); + } + }; + + return workingIndicator; + }); \ No newline at end of file diff --git a/js/file-manager.js b/js/file-manager.js index 1714ca83..3f1a6216 100644 --- a/js/file-manager.js +++ b/js/file-manager.js @@ -8,347 +8,316 @@ define([ "file-system", "lib/text!../WELCOME.md" ], function($, _, core, utils, settings, extensionMgr, fileSystem, welcomeContent) { - - var fileMgr = {}; - - // Defines a file descriptor in the file system (fileDesc objects) - function FileDescriptor(fileIndex, title, syncLocations, publishLocations) { - this.fileIndex = fileIndex; - this.title = title; - this.syncLocations = syncLocations || {}; - this.publishLocations = publishLocations || {}; - } - FileDescriptor.prototype.getContent = function() { - return localStorage[this.fileIndex + ".content"]; - }; - FileDescriptor.prototype.setContent = function(content) { - localStorage[this.fileIndex + ".content"] = content; - extensionMgr.onContentChanged(this); - }; - FileDescriptor.prototype.setTitle = function(title) { - this.title = title; - localStorage[this.fileIndex + ".title"] = title; - extensionMgr.onTitleChanged(this); - }; - // Load file descriptors from localStorage - _.chain( - localStorage["file.list"].split(";") - ).compact().each(function(fileIndex) { - fileSystem[fileIndex] = new FileDescriptor(fileIndex, localStorage[fileIndex + ".title"]); - }); - - // Defines the current file - var currentFile = undefined; - fileMgr.getCurrentFile = function() { - return currentFile; - }; - fileMgr.isCurrentFile = function(fileDesc) { - return fileDesc === currentFile; - }; - fileMgr.setCurrentFile = function(fileDesc) { - currentFile = fileDesc; - if(fileDesc === undefined) { - localStorage.removeItem("file.current"); - } - else if(fileDesc.fileIndex != TEMPORARY_FILE_INDEX) { - localStorage["file.current"] = fileDesc.fileIndex; - } - }; - - // Caution: this function recreates the editor (reset undo operations) - fileMgr.selectFile = function(fileDesc) { - fileDesc = fileDesc || fileMgr.getCurrentFile(); - - if(fileDesc === undefined) { - var fileSystemSize = _.size(fileSystem); - // If fileSystem empty create one file - if (fileSystemSize === 0) { - fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); - } - else { - var fileIndex = localStorage["file.current"]; - // If no file is selected take the last created - if(fileIndex === undefined) { - fileIndex = _.keys(fileSystem)[fileSystemSize - 1]; - } - fileDesc = fileSystem[fileIndex]; - } - } - - if(fileMgr.isCurrentFile(fileDesc) === false) { - fileMgr.setCurrentFile(fileDesc); - - // Notify extensions - extensionMgr.onFileSelected(fileDesc); + var fileMgr = {}; - // Hide the viewer pencil button - if(fileDesc.fileIndex == TEMPORARY_FILE_INDEX) { - $(".action-edit-document").removeClass("hide"); - } - else { - $(".action-edit-document").addClass("hide"); - } - } - - // Recreate the editor - $("#wmd-input").val(fileDesc.getContent()); - core.createEditor(function() { - // Callback to save content when textarea changes - fileMgr.saveFile(); - }); - }; - - fileMgr.createFile = function(title, content, syncLocations, isTemporary) { - content = content !== undefined ? content : settings.defaultContent; - if (!title) { - // Create a file title - title = DEFAULT_FILE_TITLE; - var indicator = 2; - while(_.some(fileSystem, function(fileDesc) { - return fileDesc.title == title; - })) { - title = DEFAULT_FILE_TITLE + indicator++; - } - } - - // Generate a unique fileIndex - var fileIndex = TEMPORARY_FILE_INDEX; - if(!isTemporary) { - do { - fileIndex = "file." + utils.randomString(); - } while(_.has(fileSystem, fileIndex)); - } - - // syncIndex associations - syncLocations = syncLocations || {}; - var sync = _.reduce(syncLocations, function(sync, syncAttributes, syncIndex) { - return sync + syncIndex + ";"; - }, ";"); - - localStorage[fileIndex + ".title"] = title; - localStorage[fileIndex + ".content"] = content; - localStorage[fileIndex + ".sync"] = sync; - localStorage[fileIndex + ".publish"] = ";"; - - // Create the file descriptor - var fileDesc = new FileDescriptor(fileIndex, title, syncLocations); - - // Add the index to the file list - if(!isTemporary) { - localStorage["file.list"] += fileIndex + ";"; - fileSystem[fileIndex] = fileDesc; - extensionMgr.onFileCreated(fileDesc); - } - return fileDesc; - }; + // Defines a file descriptor in the file system (fileDesc objects) + function FileDescriptor(fileIndex, title, syncLocations, publishLocations) { + this.fileIndex = fileIndex; + this._title = title; + this.__defineGetter__("title", function() { + return this._title; + }); + this.__defineSetter__("title", function(title) { + this._title = title; + localStorage[this.fileIndex + ".title"] = title; + extensionMgr.onTitleChanged(this); + }); + this.__defineGetter__("content", function() { + return localStorage[this.fileIndex + ".content"]; + }); + this.__defineSetter__("content", function(content) { + localStorage[this.fileIndex + ".content"] = content; + extensionMgr.onContentChanged(this); + }); + this.syncLocations = syncLocations || {}; + this.publishLocations = publishLocations || {}; + } - fileMgr.deleteFile = function(fileDesc) { - fileDesc = fileDesc || fileMgr.getCurrentFile(); - if(fileMgr.isCurrentFile(fileDesc) === true) { - // Unset the current fileDesc - fileMgr.setCurrentFile(); - // Refresh the editor with an other file - fileMgr.selectFile(); - } + // Load file descriptors from localStorage + _.chain(localStorage["file.list"].split(";")).compact().each(function(fileIndex) { + fileSystem[fileIndex] = new FileDescriptor(fileIndex, localStorage[fileIndex + ".title"]); + }); - // Remove synchronized locations - _.each(fileDesc.syncLocations, function(syncAttributes) { - fileMgr.removeSync(syncAttributes, true); - }); - - // Remove publish locations - _.each(fileDesc.publishLocations, function(publishAttributes) { - fileMgr.removePublish(publishAttributes, true); - }); + // Defines the current file + var currentFile = undefined; + fileMgr.getCurrentFile = function() { + return currentFile; + }; + fileMgr.isCurrentFile = function(fileDesc) { + return fileDesc === currentFile; + }; + fileMgr.setCurrentFile = function(fileDesc) { + currentFile = fileDesc; + if(fileDesc === undefined) { + localStorage.removeItem("file.current"); + } + else if(fileDesc.fileIndex != TEMPORARY_FILE_INDEX) { + localStorage["file.current"] = fileDesc.fileIndex; + } + }; - // Remove the index from the file list - var fileIndex = fileDesc.fileIndex; - localStorage["file.list"] = localStorage["file.list"].replace(";" - + fileIndex + ";", ";"); - - localStorage.removeItem(fileIndex + ".title"); - localStorage.removeItem(fileIndex + ".content"); - localStorage.removeItem(fileIndex + ".sync"); - localStorage.removeItem(fileIndex + ".publish"); - - fileSystem.removeItem(fileIndex); - extensionMgr.onFileDeleted(fileDesc); - }; + // Caution: this function recreates the editor (reset undo operations) + fileMgr.selectFile = function(fileDesc) { + fileDesc = fileDesc || fileMgr.getCurrentFile(); - // Save current file in localStorage - fileMgr.saveFile = function() { - var content = $("#wmd-input").val(); - var fileDesc = fileMgr.getCurrentFile(); - fileDesc.setContent(content); - }; + if(fileDesc === undefined) { + var fileSystemSize = _.size(fileSystem); + // If fileSystem empty create one file + if(fileSystemSize === 0) { + fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); + } + else { + var fileIndex = localStorage["file.current"]; + // If no file is selected take the last created + if(fileIndex === undefined) { + fileIndex = _.keys(fileSystem)[fileSystemSize - 1]; + } + fileDesc = fileSystem[fileIndex]; + } + } - // Add a synchronized location to a file - fileMgr.addSync = function(fileDesc, syncAttributes) { - localStorage[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) { - 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); - fileDesc.syncLocations.removeItem(syncAttributes.syncIndex); - if(!skipExtensions) { - extensionMgr.onSyncRemoved(fileDesc, syncAttributes); - } - }; - - // Get the file descriptor associated to a syncIndex - fileMgr.getFileFromSyncIndex = function(syncIndex) { - return _.find(fileSystem, function(fileDesc) { - return _.has(fileDesc.syncLocations, syncIndex); - }); - }; - - // Get syncAttributes from syncIndex - fileMgr.getSyncAttributes = function(syncIndex) { - var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); - return fileDesc && fileDesc.syncLocations[syncIndex]; - }; - - // Returns true if provider has locations to synchronize - fileMgr.hasSync = function(provider) { - return _.some(fileSystem, function(fileDesc) { - return _.some(fileDesc.syncLocations, function(syncAttributes) { - return syncAttributes.provider === provider; - }); - }); - }; + if(fileMgr.isCurrentFile(fileDesc) === false) { + fileMgr.setCurrentFile(fileDesc); - // Add a publishIndex (publish location) to a file - fileMgr.addPublish = function(fileDesc, publishAttributes) { - localStorage[fileDesc.fileIndex + ".publish"] += publishAttributes.publishIndex + ";"; - fileDesc.publishLocations[publishAttributes.publishIndex] = publishAttributes; - extensionMgr.onNewPublishSuccess(fileDesc, publishAttributes); - }; - - // Remove a publishIndex (publish location) - fileMgr.removePublish = function(publishAttributes, skipExtensions) { - 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); - fileDesc.publishLocations.removeItem(publishAttributes.publishIndex); - if(!skipExtensions) { - extensionMgr.onPublishRemoved(fileDesc, publishAttributes); - } - }; - - // Get the file descriptor associated to a publishIndex - fileMgr.getFileFromPublishIndex = function(publishIndex) { - return _.find(fileSystem, function(fileDesc) { - return _.has(fileDesc.publishLocations, publishIndex); - }); - }; - - // Filter for search input in file selector - function filterFileSelector(filter) { - var liList = $("#file-selector li:not(.stick)"); - liList.show(); - if(filter) { - var words = filter.toLowerCase().split(/\s+/); - liList.each(function() { - var fileTitle = $(this).text().toLowerCase(); - if(_.some(words, function(word) { - return fileTitle.indexOf(word) === -1; - })) { - $(this).hide(); - } - }); - } - } - - core.onReady(function() { - - fileMgr.selectFile(); + // Notify extensions + extensionMgr.onFileSelected(fileDesc); - $(".action-create-file").click(function() { - var fileDesc = fileMgr.createFile(); - fileMgr.selectFile(fileDesc); - var wmdInput = $("#wmd-input").focus().get(0); - if(wmdInput.setSelectionRange) { - wmdInput.setSelectionRange(0, 0); - } - $("#file-title").click(); - }); - $(".action-remove-file").click(function() { - fileMgr.deleteFile(); - }); - $("#file-title").click(function() { - if(viewerMode === true) { - return; - } - $(this).hide(); - var fileTitleInput = $("#file-title-input").show(); - _.defer(function() { - fileTitleInput.focus().get(0).select(); - }); - }); - function applyTitle(input) { - input.hide(); - $("#file-title").show(); - var title = $.trim(input.val()); - var fileDesc = fileMgr.getCurrentFile(); - if (title && title != fileDesc.title) { - fileDesc.setTitle(title); - } - input.val(fileDesc.title); - $("#wmd-input").focus(); - } - $("#file-title-input").blur(function() { - applyTitle($(this)); - }).keyup(function(e) { - if (e.keyCode == 13) { - applyTitle($(this)); - } - if (e.keyCode == 27) { - $(this).val(""); - applyTitle($(this)); - } - }); - $(".action-open-file").click(function() { - filterFileSelector(); - _.defer(function() { - $("#file-search").val("").focus(); - }); - }); - $("#file-search").keyup(function() { - filterFileSelector($(this).val()); - }).click(function(event) { - event.stopPropagation(); - }); - $(".action-open-stackedit").click(function() { - window.location.href = "."; - }); - $(".action-edit-document").click(function() { - var content = $("#wmd-input").val(); - var title = fileMgr.getCurrentFile().title; - var fileDesc = fileMgr.createFile(title, content); - fileMgr.selectFile(fileDesc); - window.location.href = "."; - }); - $(".action-welcome-file").click(function() { - var fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); - fileMgr.selectFile(fileDesc); - }); - }); + // Hide the viewer pencil button + if(fileDesc.fileIndex == TEMPORARY_FILE_INDEX) { + $(".action-edit-document").removeClass("hide"); + } + else { + $(".action-edit-document").addClass("hide"); + } + } - extensionMgr.onFileMgrCreated(fileMgr); - return fileMgr; + // Recreate the editor + $("#wmd-input").val(fileDesc.content); + core.createEditor(function() { + // Callback to save content when textarea changes + fileMgr.saveFile(); + }); + }; + + fileMgr.createFile = function(title, content, syncLocations, isTemporary) { + content = content !== undefined ? content : settings.defaultContent; + if(!title) { + // Create a file title + title = DEFAULT_FILE_TITLE; + var indicator = 2; + while (_.some(fileSystem, function(fileDesc) { + return fileDesc.title == title; + })) { + title = DEFAULT_FILE_TITLE + indicator++; + } + } + + // Generate a unique fileIndex + var fileIndex = TEMPORARY_FILE_INDEX; + if(!isTemporary) { + do { + fileIndex = "file." + utils.randomString(); + } while (_.has(fileSystem, fileIndex)); + } + + // syncIndex associations + syncLocations = syncLocations || {}; + var sync = _.reduce(syncLocations, function(sync, syncAttributes, syncIndex) { + return sync + syncIndex + ";"; + }, ";"); + + localStorage[fileIndex + ".title"] = title; + localStorage[fileIndex + ".content"] = content; + localStorage[fileIndex + ".sync"] = sync; + localStorage[fileIndex + ".publish"] = ";"; + + // Create the file descriptor + var fileDesc = new FileDescriptor(fileIndex, title, syncLocations); + + // Add the index to the file list + if(!isTemporary) { + localStorage["file.list"] += fileIndex + ";"; + fileSystem[fileIndex] = fileDesc; + extensionMgr.onFileCreated(fileDesc); + } + return fileDesc; + }; + + fileMgr.deleteFile = function(fileDesc) { + fileDesc = fileDesc || fileMgr.getCurrentFile(); + if(fileMgr.isCurrentFile(fileDesc) === true) { + // Unset the current fileDesc + fileMgr.setCurrentFile(); + // Refresh the editor with an other file + fileMgr.selectFile(); + } + + // Remove synchronized locations + _.each(fileDesc.syncLocations, function(syncAttributes) { + fileMgr.removeSync(syncAttributes, true); + }); + + // Remove publish locations + _.each(fileDesc.publishLocations, function(publishAttributes) { + fileMgr.removePublish(publishAttributes, true); + }); + + // Remove the index from the file list + var fileIndex = fileDesc.fileIndex; + localStorage["file.list"] = localStorage["file.list"].replace(";" + fileIndex + ";", ";"); + + localStorage.removeItem(fileIndex + ".title"); + localStorage.removeItem(fileIndex + ".content"); + localStorage.removeItem(fileIndex + ".sync"); + localStorage.removeItem(fileIndex + ".publish"); + + fileSystem.removeItem(fileIndex); + 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 + ";"; + 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) { + 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); + fileDesc.syncLocations.removeItem(syncAttributes.syncIndex); + if(!skipExtensions) { + extensionMgr.onSyncRemoved(fileDesc, syncAttributes); + } + }; + + // Get the file descriptor associated to a syncIndex + fileMgr.getFileFromSyncIndex = function(syncIndex) { + return _.find(fileSystem, function(fileDesc) { + return _.has(fileDesc.syncLocations, syncIndex); + }); + }; + + // Get syncAttributes from syncIndex + fileMgr.getSyncAttributes = function(syncIndex) { + var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); + return fileDesc && fileDesc.syncLocations[syncIndex]; + }; + + // Returns true if provider has locations to synchronize + fileMgr.hasSync = function(provider) { + return _.some(fileSystem, function(fileDesc) { + return _.some(fileDesc.syncLocations, function(syncAttributes) { + return syncAttributes.provider === provider; + }); + }); + }; + + // Add a publishIndex (publish location) to a file + fileMgr.addPublish = function(fileDesc, publishAttributes) { + localStorage[fileDesc.fileIndex + ".publish"] += publishAttributes.publishIndex + ";"; + fileDesc.publishLocations[publishAttributes.publishIndex] = publishAttributes; + extensionMgr.onNewPublishSuccess(fileDesc, publishAttributes); + }; + + // Remove a publishIndex (publish location) + fileMgr.removePublish = function(publishAttributes, skipExtensions) { + 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); + fileDesc.publishLocations.removeItem(publishAttributes.publishIndex); + if(!skipExtensions) { + extensionMgr.onPublishRemoved(fileDesc, publishAttributes); + } + }; + + // Get the file descriptor associated to a publishIndex + fileMgr.getFileFromPublishIndex = function(publishIndex) { + return _.find(fileSystem, function(fileDesc) { + return _.has(fileDesc.publishLocations, publishIndex); + }); + }; + + core.onReady(function() { + + fileMgr.selectFile(); + + $(".action-create-file").click(function() { + var fileDesc = fileMgr.createFile(); + fileMgr.selectFile(fileDesc); + var wmdInput = $("#wmd-input").focus().get(0); + if(wmdInput.setSelectionRange) { + wmdInput.setSelectionRange(0, 0); + } + $("#file-title").click(); + }); + $(".action-remove-file").click(function() { + fileMgr.deleteFile(); + }); + $("#file-title").click(function() { + if(viewerMode === true) { + return; + } + $(this).hide(); + var fileTitleInput = $("#file-title-input").show(); + _.defer(function() { + fileTitleInput.focus().get(0).select(); + }); + }); + function applyTitle(input) { + input.hide(); + $("#file-title").show(); + var title = $.trim(input.val()); + var fileDesc = fileMgr.getCurrentFile(); + if(title && title != fileDesc.title) { + fileDesc.title = title; + } + input.val(fileDesc.title); + $("#wmd-input").focus(); + } + $("#file-title-input").blur(function() { + applyTitle($(this)); + }).keyup(function(e) { + if(e.keyCode == 13) { + applyTitle($(this)); + } + if(e.keyCode == 27) { + $(this).val(""); + applyTitle($(this)); + } + }); + $(".action-open-stackedit").click(function() { + window.location.href = "."; + }); + $(".action-edit-document").click(function() { + var content = $("#wmd-input").val(); + var title = fileMgr.getCurrentFile().title; + var fileDesc = fileMgr.createFile(title, content); + fileMgr.selectFile(fileDesc); + window.location.href = "."; + }); + $(".action-welcome-file").click(function() { + var fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); + fileMgr.selectFile(fileDesc); + }); + }); + + extensionMgr.onFileMgrCreated(fileMgr); + return fileMgr; }); diff --git a/js/gdrive-provider.js b/js/gdrive-provider.js index a80da023..c8eeb275 100644 --- a/js/gdrive-provider.js +++ b/js/gdrive-provider.js @@ -6,280 +6,275 @@ define([ "file-manager", "google-helper" ], function(_, core, utils, extensionMgr, fileMgr, googleHelper) { - - var PROVIDER_GDRIVE = "gdrive"; - - var gdriveProvider = { - providerId: PROVIDER_GDRIVE, - providerName: "Google Drive", - defaultPublishFormat: "template", - exportPreferencesInputIds: ["gdrive-parentid"] - }; - - function createSyncIndex(id) { - return "sync." + PROVIDER_GDRIVE + "." + id; - } - - function createSyncAttributes(id, etag, content, title) { - var syncAttributes = {}; - syncAttributes.provider = gdriveProvider; - syncAttributes.id = id; - syncAttributes.etag = etag; - syncAttributes.contentCRC = utils.crc32(content); - syncAttributes.titleCRC = utils.crc32(title); - syncAttributes.syncIndex = createSyncIndex(id); - utils.storeAttributes(syncAttributes); - return syncAttributes; - } - - function importFilesFromIds(ids) { - googleHelper.downloadMetadata(ids, function(error, result) { - if(error) { - return; - } - googleHelper.downloadContent(result, function(error, result) { - if(error) { - return; - } - var fileDescList = []; - _.each(result, function(file) { - var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title); - var syncLocations = {}; - syncLocations[syncAttributes.syncIndex] = syncAttributes; - var fileDesc = fileMgr.createFile(file.title, file.content, syncLocations); - fileMgr.selectFile(fileDesc); - fileDescList.push(fileDesc); - }); - extensionMgr.onSyncImportSuccess(fileDescList, gdriveProvider); - }); - }); - }; - gdriveProvider.importFiles = function() { - googleHelper.picker(function(error, ids) { - if(error || ids.length === 0) { - return; - } - var importIds = []; - _.each(ids, function(id) { - var syncIndex = createSyncIndex(id); - var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); - if(fileDesc !== undefined) { - extensionMgr.onError('"' + fileDesc.title + '" was already imported'); - return; - } - importIds.push(id); - }); - importFilesFromIds(importIds); - }); - }; - - gdriveProvider.exportFile = function(event, title, content, callback) { - var parentId = utils.getInputTextValue("#input-sync-export-gdrive-parentid"); - googleHelper.upload(undefined, parentId, title, content, undefined, function(error, result) { - if (error) { - callback(error); - return; - } - var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); - callback(undefined, syncAttributes); - }); - }; + var PROVIDER_GDRIVE = "gdrive"; - gdriveProvider.exportManual = function(event, title, content, callback) { - var id = utils.getInputTextValue("#input-sync-manual-gdrive-id", event); - if(!id) { - return; - } - // Check that file is not synchronized with another an existing document - var syncIndex = createSyncIndex(id); - var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); - if(fileDesc !== undefined) { - extensionMgr.onError('File ID is already synchronized with "' + fileDesc.title + '"'); - callback(true); - return; - } - googleHelper.upload(id, undefined, title, content, undefined, function(error, result) { - if (error) { - callback(error); - return; - } - var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); - callback(undefined, syncAttributes); - }); - }; - - gdriveProvider.syncUp = function(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, callback) { - var syncContentCRC = syncAttributes.contentCRC; - var syncTitleCRC = syncAttributes.titleCRC; - // Skip if CRC has not changed - if(uploadContentCRC == syncContentCRC && uploadTitleCRC == syncTitleCRC) { - callback(undefined, false); - return; - } - googleHelper.upload(syncAttributes.id, undefined, uploadTitle, uploadContent, syncAttributes.etag, function(error, result) { - if(error) { - callback(error, true); - return; - } - syncAttributes.etag = result.etag; - syncAttributes.contentCRC = uploadContentCRC; - syncAttributes.titleCRC = uploadTitleCRC; - callback(undefined, true); - }); - }; - - gdriveProvider.syncDown = function(callback) { - var lastChangeId = parseInt(localStorage[PROVIDER_GDRIVE + ".lastChangeId"]); - googleHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) { - if (error) { - callback(error); - return; - } - var interestingChanges = []; - _.each(changes, function(change) { - var syncIndex = createSyncIndex(change.fileId); - var syncAttributes = fileMgr.getSyncAttributes(syncIndex); - if(syncAttributes === undefined) { - return; - } - // Store syncAttributes to avoid 2 times searching - change.syncAttributes = syncAttributes; - // Delete - if(change.deleted === true) { - interestingChanges.push(change); - return; - } - // Modify - if(syncAttributes.etag != change.file.etag) { - interestingChanges.push(change); - } - }); - googleHelper.downloadContent(interestingChanges, function(error, changes) { - if (error) { - callback(error); - return; - } - _.each(changes, function(change) { - var syncAttributes = change.syncAttributes; - var syncIndex = syncAttributes.syncIndex; - var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); - // No file corresponding (file may have been deleted locally) - if(fileDesc === undefined) { - return; - } - var localTitle = fileDesc.title; - // File deleted - if (change.deleted === true) { - extensionMgr.onError('"' + localTitle + '" has been removed from Google Drive.'); - fileMgr.removeSync(syncAttributes); - return; - } - var localTitleChanged = syncAttributes.titleCRC != utils.crc32(localTitle); - var localContent = fileDesc.getContent(); - var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); - var file = change.file; + var gdriveProvider = { + providerId: PROVIDER_GDRIVE, + providerName: "Google Drive", + defaultPublishFormat: "template", + exportPreferencesInputIds: [ + "gdrive-parentid" + ] + }; + + function createSyncIndex(id) { + return "sync." + PROVIDER_GDRIVE + "." + id; + } + + function createSyncAttributes(id, etag, content, title) { + var syncAttributes = {}; + syncAttributes.provider = gdriveProvider; + syncAttributes.id = id; + syncAttributes.etag = etag; + syncAttributes.contentCRC = utils.crc32(content); + syncAttributes.titleCRC = utils.crc32(title); + syncAttributes.syncIndex = createSyncIndex(id); + utils.storeAttributes(syncAttributes); + return syncAttributes; + } + + function importFilesFromIds(ids) { + googleHelper.downloadMetadata(ids, function(error, result) { + if(error) { + return; + } + googleHelper.downloadContent(result, function(error, result) { + if(error) { + return; + } + var fileDescList = []; + _.each(result, function(file) { + var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title); + var syncLocations = {}; + syncLocations[syncAttributes.syncIndex] = syncAttributes; + var fileDesc = fileMgr.createFile(file.title, file.content, syncLocations); + fileMgr.selectFile(fileDesc); + fileDescList.push(fileDesc); + }); + extensionMgr.onSyncImportSuccess(fileDescList, gdriveProvider); + }); + }); + } + ; + + gdriveProvider.importFiles = function() { + googleHelper.picker(function(error, ids) { + if(error || ids.length === 0) { + return; + } + var importIds = []; + _.each(ids, function(id) { + var syncIndex = createSyncIndex(id); + var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); + if(fileDesc !== undefined) { + extensionMgr.onError('"' + fileDesc.title + '" was already imported'); + return; + } + importIds.push(id); + }); + importFilesFromIds(importIds); + }); + }; + + gdriveProvider.exportFile = function(event, title, content, callback) { + var parentId = utils.getInputTextValue("#input-sync-export-gdrive-parentid"); + googleHelper.upload(undefined, parentId, title, content, undefined, function(error, result) { + if(error) { + callback(error); + return; + } + var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); + callback(undefined, syncAttributes); + }); + }; + + gdriveProvider.exportManual = function(event, title, content, callback) { + var id = utils.getInputTextValue("#input-sync-manual-gdrive-id", event); + if(!id) { + return; + } + // Check that file is not synchronized with another an existing document + var syncIndex = createSyncIndex(id); + var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); + if(fileDesc !== undefined) { + extensionMgr.onError('File ID is already synchronized with "' + fileDesc.title + '"'); + callback(true); + return; + } + googleHelper.upload(id, undefined, title, content, undefined, function(error, result) { + if(error) { + callback(error); + return; + } + var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); + callback(undefined, syncAttributes); + }); + }; + + gdriveProvider.syncUp = function(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, callback) { + var syncContentCRC = syncAttributes.contentCRC; + var syncTitleCRC = syncAttributes.titleCRC; + // Skip if CRC has not changed + if(uploadContentCRC == syncContentCRC && uploadTitleCRC == syncTitleCRC) { + callback(undefined, false); + return; + } + googleHelper.upload(syncAttributes.id, undefined, uploadTitle, uploadContent, syncAttributes.etag, function(error, result) { + if(error) { + callback(error, true); + return; + } + syncAttributes.etag = result.etag; + syncAttributes.contentCRC = uploadContentCRC; + syncAttributes.titleCRC = uploadTitleCRC; + callback(undefined, true); + }); + }; + + gdriveProvider.syncDown = function(callback) { + var lastChangeId = parseInt(localStorage[PROVIDER_GDRIVE + ".lastChangeId"]); + googleHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) { + if(error) { + callback(error); + return; + } + var interestingChanges = []; + _.each(changes, function(change) { + var syncIndex = createSyncIndex(change.fileId); + var syncAttributes = fileMgr.getSyncAttributes(syncIndex); + if(syncAttributes === undefined) { + return; + } + // Store syncAttributes to avoid 2 times searching + change.syncAttributes = syncAttributes; + // Delete + if(change.deleted === true) { + interestingChanges.push(change); + return; + } + // Modify + if(syncAttributes.etag != change.file.etag) { + interestingChanges.push(change); + } + }); + googleHelper.downloadContent(interestingChanges, function(error, changes) { + if(error) { + callback(error); + return; + } + _.each(changes, function(change) { + var syncAttributes = change.syncAttributes; + var syncIndex = syncAttributes.syncIndex; + var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); + // No file corresponding (file may have been deleted + // locally) + if(fileDesc === undefined) { + return; + } + var localTitle = fileDesc.title; + // File deleted + if(change.deleted === true) { + extensionMgr.onError('"' + localTitle + '" has been removed from Google Drive.'); + fileMgr.removeSync(syncAttributes); + return; + } + var localTitleChanged = syncAttributes.titleCRC != utils.crc32(localTitle); + var localContent = fileDesc.content; + var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); + var file = change.file; var remoteTitleCRC = utils.crc32(file.title); var remoteTitleChanged = syncAttributes.titleCRC != remoteTitleCRC; var fileTitleChanged = localTitle != file.title; var remoteContentCRC = utils.crc32(file.content); var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC; - var fileContentChanged = localContent != file.content; - // Conflict detection - if ((fileTitleChanged === true && localTitleChanged === true && remoteTitleChanged === true) - || (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true)) { - fileMgr.createFile(localTitle + " (backup)", localContent); - extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.'); - } - // If file title changed - if(fileTitleChanged && remoteTitleChanged === true) { - fileDesc.setTitle(file.title); - extensionMgr.onMessage('"' + localTitle + '" has been renamed to "' + file.title + '" on Google Drive.'); - } - // If file content changed - if(fileContentChanged && remoteContentChanged === true) { - fileDesc.setContent(file.content); - extensionMgr.onMessage('"' + file.title + '" has been updated from Google Drive.'); - if(fileMgr.isCurrentFile(fileDesc)) { - fileMgr.selectFile(); // Refresh editor - } - } - // Update syncAttributes - syncAttributes.etag = file.etag; - syncAttributes.contentCRC = remoteContentCRC; - syncAttributes.titleCRC = remoteTitleCRC; - utils.storeAttributes(syncAttributes); - }); - localStorage[PROVIDER_GDRIVE + ".lastChangeId"] = newChangeId; - callback(); - }); - }); - }; - - gdriveProvider.publish = function(publishAttributes, title, content, callback) { - googleHelper.upload( - publishAttributes.id, - undefined, - publishAttributes.fileName || title, - content, - undefined, - function(error, result) { - if(error) { - callback(error); - return; - } - publishAttributes.id = result.id; - callback(); - } - ); - }; + var fileContentChanged = localContent != file.content; + // Conflict detection + if((fileTitleChanged === true && localTitleChanged === true && remoteTitleChanged === true) || (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true)) { + fileMgr.createFile(localTitle + " (backup)", localContent); + extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.'); + } + // If file title changed + if(fileTitleChanged && remoteTitleChanged === true) { + fileDesc.title = file.title; + extensionMgr.onMessage('"' + localTitle + '" has been renamed to "' + file.title + '" on Google Drive.'); + } + // If file content changed + if(fileContentChanged && remoteContentChanged === true) { + fileDesc.content = file.content; + extensionMgr.onMessage('"' + file.title + '" has been updated from Google Drive.'); + if(fileMgr.isCurrentFile(fileDesc)) { + fileMgr.selectFile(); // Refresh editor + } + } + // Update syncAttributes + syncAttributes.etag = file.etag; + syncAttributes.contentCRC = remoteContentCRC; + syncAttributes.titleCRC = remoteTitleCRC; + utils.storeAttributes(syncAttributes); + }); + localStorage[PROVIDER_GDRIVE + ".lastChangeId"] = newChangeId; + callback(); + }); + }); + }; - gdriveProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - publishAttributes.id = utils.getInputTextValue("#input-publish-gdrive-fileid"); - publishAttributes.fileName = utils.getInputTextValue("#input-publish-gdrive-filename"); - if(event.isPropagationStopped()) { - return undefined; - } - return publishAttributes; - }; - - core.onReady(function() { - var state = localStorage[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, - "", undefined, function(error, file) { - if(error) { - return; - } - var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title); - var syncLocations = {}; - syncLocations[syncAttributes.syncIndex] = syncAttributes; - var fileDesc = fileMgr.createFile(file.title, file.content, syncAttributes); - fileMgr.selectFile(fileDesc); - extensionMgr.onMessage('"' + file.title + '" created successfully on Google Drive.'); - }); - } - else if (state.action == "open") { - var importIds = []; - _.each(state.ids, function(id) { - var syncIndex = createSyncIndex(id); - var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); - if(fileDesc !== undefined) { - fileMgr.selectFile(fileDesc); - } - else { - importIds.push(id); - } - }); - importFilesFromIds(importIds); - } - }); + gdriveProvider.publish = function(publishAttributes, title, content, callback) { + googleHelper.upload(publishAttributes.id, undefined, publishAttributes.fileName || title, content, undefined, function(error, result) { + if(error) { + callback(error); + return; + } + publishAttributes.id = result.id; + callback(); + }); + }; - return gdriveProvider; + gdriveProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + publishAttributes.id = utils.getInputTextValue("#input-publish-gdrive-fileid"); + publishAttributes.fileName = utils.getInputTextValue("#input-publish-gdrive-filename"); + if(event.isPropagationStopped()) { + return undefined; + } + return publishAttributes; + }; + + core.onReady(function() { + var state = localStorage[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, "", undefined, function(error, file) { + if(error) { + return; + } + var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title); + var syncLocations = {}; + syncLocations[syncAttributes.syncIndex] = syncAttributes; + var fileDesc = fileMgr.createFile(file.title, file.content, syncAttributes); + fileMgr.selectFile(fileDesc); + extensionMgr.onMessage('"' + file.title + '" created successfully on Google Drive.'); + }); + } + else if(state.action == "open") { + var importIds = []; + _.each(state.ids, function(id) { + var syncIndex = createSyncIndex(id); + var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); + if(fileDesc !== undefined) { + fileMgr.selectFile(fileDesc); + } + else { + importIds.push(id); + } + }); + importFilesFromIds(importIds); + } + }); + + return gdriveProvider; }); \ No newline at end of file diff --git a/js/gist-provider.js b/js/gist-provider.js index 6355d460..9e19a7a7 100644 --- a/js/gist-provider.js +++ b/js/gist-provider.js @@ -2,42 +2,43 @@ define([ "utils", "github-helper" ], function(utils, githubHelper) { - - var PROVIDER_GIST = "gist"; - - var gistProvider = { - providerId: PROVIDER_GIST, - providerName: "Gist", - sharingAttributes: ["gistId", "filename"] - }; - - gistProvider.publish = function(publishAttributes, title, content, callback) { - githubHelper.uploadGist(publishAttributes.gistId, publishAttributes.filename, publishAttributes.isPublic, - title, content, function(error, gistId) { - if(error) { - callback(error); - return; - } - publishAttributes.gistId = gistId; - callback(); - } - ); - }; - gistProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - publishAttributes.gistId = utils.getInputTextValue("#input-publish-gist-id"); - publishAttributes.filename = utils.getInputTextValue("#input-publish-filename", event); - publishAttributes.isPublic = utils.getInputChecked("#input-publish-gist-public"); - if(event.isPropagationStopped()) { - return undefined; - } - return publishAttributes; - }; - - gistProvider.importPublic = function(importParameters, callback) { - githubHelper.downloadGist(importParameters.gistId, importParameters.filename, callback); - }; + var PROVIDER_GIST = "gist"; - return gistProvider; + var gistProvider = { + providerId: PROVIDER_GIST, + providerName: "Gist", + sharingAttributes: [ + "gistId", + "filename" + ] + }; + + gistProvider.publish = function(publishAttributes, title, content, callback) { + githubHelper.uploadGist(publishAttributes.gistId, publishAttributes.filename, publishAttributes.isPublic, title, content, function(error, gistId) { + if(error) { + callback(error); + return; + } + publishAttributes.gistId = gistId; + callback(); + }); + }; + + gistProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + publishAttributes.gistId = utils.getInputTextValue("#input-publish-gist-id"); + publishAttributes.filename = utils.getInputTextValue("#input-publish-filename", event); + publishAttributes.isPublic = utils.getInputChecked("#input-publish-gist-public"); + if(event.isPropagationStopped()) { + return undefined; + } + return publishAttributes; + }; + + gistProvider.importPublic = function(importParameters, callback) { + githubHelper.downloadGist(importParameters.gistId, importParameters.filename, callback); + }; + + return gistProvider; }); \ No newline at end of file diff --git a/js/github-helper.js b/js/github-helper.js index ec49c7d0..642e9c89 100644 --- a/js/github-helper.js +++ b/js/github-helper.js @@ -6,247 +6,249 @@ define([ "async-runner" ], function($, core, utils, extensionMgr, asyncRunner) { - var connected = undefined; - var github = undefined; + var connected = undefined; + var github = undefined; - var githubHelper = {}; + var githubHelper = {}; - // Try to connect github by downloading js file - function connect(task) { - task.onRun(function() { - if(core.isOffline === true) { - connected = false; - task.error(new Error("Operation not available in offline mode.|stopPublish")); - return; - } - if (connected === true) { - task.chain(); - return; - } - $.ajax({ - url : "lib/github.js", - dataType : "script", timeout : AJAX_TIMEOUT - }).done(function() { - connected = true; - task.chain(); - }).fail(function(jqXHR) { - var error = { - error: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - }); - } + // Try to connect github by downloading js file + function connect(task) { + task.onRun(function() { + if(core.isOffline === true) { + connected = false; + task.error(new Error("Operation not available in offline mode.|stopPublish")); + return; + } + if(connected === true) { + task.chain(); + return; + } + $.ajax({ + url: "lib/github.js", + dataType: "script", + timeout: AJAX_TIMEOUT + }).done(function() { + connected = true; + task.chain(); + }).fail(function(jqXHR) { + var error = { + error: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + }); + } - // Try to authenticate with Oauth - function authenticate(task) { - var authWindow = undefined; - var intervalId = undefined; - task.onRun(function() { - if (github !== undefined) { - task.chain(); - return; - } - var token = localStorage["githubToken"]; - if(token !== undefined) { - github = new Github({ - token: token, - auth: "oauth" - }); - task.chain(); - return; - } - extensionMgr.onMessage("Please make sure the Github authorization popup is not blocked by your browser."); - var errorMsg = "Failed to retrieve a token from GitHub."; - // We add time for user to enter his credentials - task.timeout = ASYNC_TASK_LONG_TIMEOUT; - var code = undefined; - function getCode() { - localStorage.removeItem("githubCode"); - authWindow = utils.popupWindow( - 'github-oauth-client.html?client_id=' + GITHUB_CLIENT_ID, - 'stackedit-github-oauth', 960, 600); - authWindow.focus(); - intervalId = setInterval(function() { - if(authWindow.closed === true) { - clearInterval(intervalId); - authWindow = undefined; - intervalId = undefined; - code = localStorage["githubCode"]; - if(code === undefined) { - task.error(new Error(errorMsg)); - return; - } - localStorage.removeItem("githubCode"); - task.chain(getToken); - } - }, 500); - } - function getToken() { - $.getJSON(GATEKEEPER_URL + "authenticate/" + code, function(data) { - if(data.token !== undefined) { - token = data.token; - localStorage["githubToken"] = token; - github = new Github({ - token: token, - auth: "oauth" - }); - task.chain(); - } - else { - task.error(new Error(errorMsg)); - } - }); - } - task.chain(getCode); - }); - task.onError(function() { - if(intervalId !== undefined) { - clearInterval(intervalId); - } - if(authWindow !== undefined) { - authWindow.close(); - } - }); - } + // Try to authenticate with Oauth + function authenticate(task) { + var authWindow = undefined; + var intervalId = undefined; + task.onRun(function() { + if(github !== undefined) { + task.chain(); + return; + } + var token = localStorage["githubToken"]; + if(token !== undefined) { + github = new Github({ + token: token, + auth: "oauth" + }); + task.chain(); + return; + } + extensionMgr.onMessage("Please make sure the Github authorization popup is not blocked by your browser."); + var errorMsg = "Failed to retrieve a token from GitHub."; + // We add time for user to enter his credentials + task.timeout = ASYNC_TASK_LONG_TIMEOUT; + var code = undefined; + function getCode() { + localStorage.removeItem("githubCode"); + authWindow = utils.popupWindow('github-oauth-client.html?client_id=' + GITHUB_CLIENT_ID, 'stackedit-github-oauth', 960, 600); + authWindow.focus(); + intervalId = setInterval(function() { + if(authWindow.closed === true) { + clearInterval(intervalId); + authWindow = undefined; + intervalId = undefined; + code = localStorage["githubCode"]; + if(code === undefined) { + task.error(new Error(errorMsg)); + return; + } + localStorage.removeItem("githubCode"); + task.chain(getToken); + } + }, 500); + } + function getToken() { + $.getJSON(GATEKEEPER_URL + "authenticate/" + code, function(data) { + if(data.token !== undefined) { + token = data.token; + localStorage["githubToken"] = token; + github = new Github({ + token: token, + auth: "oauth" + }); + task.chain(); + } + else { + task.error(new Error(errorMsg)); + } + }); + } + task.chain(getCode); + }); + task.onError(function() { + if(intervalId !== undefined) { + clearInterval(intervalId); + } + if(authWindow !== undefined) { + authWindow.close(); + } + }); + } - githubHelper.upload = function(reponame, branch, path, content, commitMsg, callback) { - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - var userLogin = undefined; - function getUserLogin() { - var user = github.getUser(); - user.show(undefined, function(err, result) { - if(err) { - handleError(err, task); - return; - } - userLogin = result.login; - task.chain(write); - }); - } - function write() { - var repo = github.getRepo(userLogin, reponame); - repo.write(branch, path, content, commitMsg, function(err) { - if(err) { - handleError(err, task); - return; - } - task.chain(); - }); - } - task.chain(getUserLogin); - }); - task.onSuccess(function() { - callback(); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - - githubHelper.uploadGist = function(gistId, filename, isPublic, title, content, callback) { - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - var gist = github.getGist(gistId); - var files = {}; - files[filename] = {content: content}; - githubFunction = gist.update; - if(gistId === undefined) { - githubFunction = gist.create; - } - githubFunction({ - description: title, - "public": isPublic, - files: files - }, function(err, gist) { - if(err) { - // Handle error - if(err.error === 404 && gistId !== undefined) { - err = 'Gist ' + gistId + ' not found on GitHub.|removePublish'; - } - handleError(err, task); - return; - } - gistId = gist.id; - task.chain(); - }); - }); - task.onSuccess(function() { - callback(undefined, gistId); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - - githubHelper.downloadGist = function(gistId, filename, callback) { - var task = asyncRunner.createTask(); - connect(task); - // No need for authentication - var title = undefined; - var content = undefined; - task.onRun(function() { - var github = new Github({}); - var gist = github.getGist(gistId); - gist.read(function(err, gist) { - if(err) { - // Handle error - task.error(new Error('Error trying to access Gist ' + gistId + '.')); - return; - } - title = gist.description; - var file = gist.files[filename]; - if(file === undefined) { - task.error(new Error('Gist ' + gistId + ' does not contain "' + filename + '".')); - return; - } - content = file.content; - task.chain(); - }); - }); - task.onSuccess(function() { - callback(undefined, title, content); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - - function handleError(error, task) { - var errorMsg = undefined; - if (error) { - logger.error(error); - // Try to analyze the error - if (typeof error === "string") { - errorMsg = error; - } - else { - errorMsg = "Could not publish on GitHub."; - if (error.error === 401 || error.error === 403) { - github = undefined; - localStorage.removeItem("githubToken"); - errorMsg = "Access to GitHub account is not authorized."; - task.retry(new Error(errorMsg), 1); - return; - } else if (error.error <= 0) { - connected = false; - github = undefined; - core.setOffline(); - errorMsg = "|stopPublish"; - } - } - } - task.error(new Error(errorMsg)); - } + githubHelper.upload = function(reponame, branch, path, content, commitMsg, callback) { + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + var userLogin = undefined; + function getUserLogin() { + var user = github.getUser(); + user.show(undefined, function(err, result) { + if(err) { + handleError(err, task); + return; + } + userLogin = result.login; + task.chain(write); + }); + } + function write() { + var repo = github.getRepo(userLogin, reponame); + repo.write(branch, path, content, commitMsg, function(err) { + if(err) { + handleError(err, task); + return; + } + task.chain(); + }); + } + task.chain(getUserLogin); + }); + task.onSuccess(function() { + callback(); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - return githubHelper; + githubHelper.uploadGist = function(gistId, filename, isPublic, title, content, callback) { + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + var gist = github.getGist(gistId); + var files = {}; + files[filename] = { + content: content + }; + githubFunction = gist.update; + if(gistId === undefined) { + githubFunction = gist.create; + } + githubFunction({ + description: title, + "public": isPublic, + files: files + }, function(err, gist) { + if(err) { + // Handle error + if(err.error === 404 && gistId !== undefined) { + err = 'Gist ' + gistId + ' not found on GitHub.|removePublish'; + } + handleError(err, task); + return; + } + gistId = gist.id; + task.chain(); + }); + }); + task.onSuccess(function() { + callback(undefined, gistId); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; + + githubHelper.downloadGist = function(gistId, filename, callback) { + var task = asyncRunner.createTask(); + connect(task); + // No need for authentication + var title = undefined; + var content = undefined; + task.onRun(function() { + var github = new Github({}); + var gist = github.getGist(gistId); + gist.read(function(err, gist) { + if(err) { + // Handle error + task.error(new Error('Error trying to access Gist ' + gistId + '.')); + return; + } + title = gist.description; + var file = gist.files[filename]; + if(file === undefined) { + task.error(new Error('Gist ' + gistId + ' does not contain "' + filename + '".')); + return; + } + content = file.content; + task.chain(); + }); + }); + task.onSuccess(function() { + callback(undefined, title, content); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; + + function handleError(error, task) { + var errorMsg = undefined; + if(error) { + logger.error(error); + // Try to analyze the error + if(typeof error === "string") { + errorMsg = error; + } + else { + errorMsg = "Could not publish on GitHub."; + if(error.error === 401 || error.error === 403) { + github = undefined; + localStorage.removeItem("githubToken"); + errorMsg = "Access to GitHub account is not authorized."; + task.retry(new Error(errorMsg), 1); + return; + } + else if(error.error <= 0) { + connected = false; + github = undefined; + core.setOffline(); + errorMsg = "|stopPublish"; + } + } + } + task.error(new Error(errorMsg)); + } + + return githubHelper; }); diff --git a/js/github-provider.js b/js/github-provider.js index 32d7c7fb..30efe559 100644 --- a/js/github-provider.js +++ b/js/github-provider.js @@ -3,31 +3,33 @@ define([ "settings", "github-helper" ], function(utils, settings, githubHelper) { - - var PROVIDER_GITHUB = "github"; - - var githubProvider = { - providerId: PROVIDER_GITHUB, - providerName: "GitHub", - publishPreferencesInputIds: ["github-reponame", "github-branch"] - }; - - githubProvider.publish = function(publishAttributes, title, content, callback) { - var commitMsg = settings.commitMsg; - githubHelper.upload(publishAttributes.repository, publishAttributes.branch, - publishAttributes.path, content, commitMsg, callback); - }; - githubProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - publishAttributes.repository = utils.getInputTextValue("#input-publish-github-reponame", event); - publishAttributes.branch = utils.getInputTextValue("#input-publish-github-branch", event); - publishAttributes.path = utils.getInputTextValue("#input-publish-file-path", event); - if(event.isPropagationStopped()) { - return undefined; - } - return publishAttributes; - }; + var PROVIDER_GITHUB = "github"; - return githubProvider; + var githubProvider = { + providerId: PROVIDER_GITHUB, + providerName: "GitHub", + publishPreferencesInputIds: [ + "github-reponame", + "github-branch" + ] + }; + + githubProvider.publish = function(publishAttributes, title, content, callback) { + var commitMsg = settings.commitMsg; + githubHelper.upload(publishAttributes.repository, publishAttributes.branch, publishAttributes.path, content, commitMsg, callback); + }; + + githubProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + publishAttributes.repository = utils.getInputTextValue("#input-publish-github-reponame", event); + publishAttributes.branch = utils.getInputTextValue("#input-publish-github-branch", event); + publishAttributes.path = utils.getInputTextValue("#input-publish-file-path", event); + if(event.isPropagationStopped()) { + return undefined; + } + return publishAttributes; + }; + + return githubProvider; }); \ No newline at end of file diff --git a/js/google-helper.js b/js/google-helper.js index e92f3bbc..f7d95551 100644 --- a/js/google-helper.js +++ b/js/google-helper.js @@ -6,518 +6,543 @@ define([ "async-runner" ], function($, core, utils, extensionMgr, asyncRunner) { - var connected = false; - var authenticated = false; + var connected = false; + var authenticated = false; - var googleHelper = {}; + var googleHelper = {}; - // Try to connect Gdrive by downloading client.js - function connect(task) { - task.onRun(function() { - if(core.isOffline === true) { - connected = false; - task.error(new Error("Operation not available in offline mode.|stopPublish")); - return; - } - if (connected === true) { - task.chain(); - return; - } - delayedFunction = function() { - connected = true; - task.chain(); - }; - $.ajax({ - url : "https://apis.google.com/js/client.js?onload=runDelayedFunction", - dataType : "script", timeout : AJAX_TIMEOUT - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - }); - } + // Try to connect Gdrive by downloading client.js + function connect(task) { + task.onRun(function() { + if(core.isOffline === true) { + connected = false; + task.error(new Error("Operation not available in offline mode.|stopPublish")); + return; + } + if(connected === true) { + task.chain(); + return; + } + delayedFunction = function() { + connected = true; + task.chain(); + }; + $.ajax({ + url: "https://apis.google.com/js/client.js?onload=runDelayedFunction", + dataType: "script", + timeout: AJAX_TIMEOUT + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + }); + } - // Try to authenticate with Oauth - function authenticate(task) { - task.onRun(function() { - if (authenticated === true) { - task.chain(); - return; - } - var immediate = true; - function localAuthenticate() { - if (immediate === false) { - extensionMgr.onMessage("Please make sure the Google authorization popup is not blocked by your browser."); - // If not immediate we add time for user to enter his credentials - task.timeout = ASYNC_TASK_LONG_TIMEOUT; - } - gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID, - 'scope' : GOOGLE_SCOPES, 'immediate' : immediate }, function( - authResult) { - gapi.client.load('drive', 'v2', function() { - if (!authResult || authResult.error) { - // If immediate did not work retry without immediate flag - if (connected === true && immediate === true) { - immediate = false; - task.chain(localAuthenticate); - return; - } - // Error - task.error(new Error("Access to Google account is not authorized.")); - return; - } - // Success - authenticated = true; - task.chain(); - }); - }); - } - task.chain(localAuthenticate); - }); - } + // Try to authenticate with Oauth + function authenticate(task) { + task.onRun(function() { + if(authenticated === true) { + task.chain(); + return; + } + var immediate = true; + function localAuthenticate() { + if(immediate === false) { + extensionMgr.onMessage("Please make sure the Google authorization popup is not blocked by your browser."); + // If not immediate we add time for user to enter his + // credentials + task.timeout = ASYNC_TASK_LONG_TIMEOUT; + } + gapi.auth.authorize({ + 'client_id': GOOGLE_CLIENT_ID, + 'scope': GOOGLE_SCOPES, + 'immediate': immediate + }, function(authResult) { + gapi.client.load('drive', 'v2', function() { + if(!authResult || authResult.error) { + // If immediate did not work retry without immediate + // flag + if(connected === true && immediate === true) { + immediate = false; + task.chain(localAuthenticate); + return; + } + // Error + task.error(new Error("Access to Google account is not authorized.")); + return; + } + // Success + authenticated = true; + task.chain(); + }); + }); + } + task.chain(localAuthenticate); + }); + } - googleHelper.upload = function(fileId, parentId, title, content, etag, callback) { - var result = undefined; - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - var boundary = '-------314159265358979323846'; - var delimiter = "\r\n--" + boundary + "\r\n"; - var close_delim = "\r\n--" + boundary + "--"; - var contentType = 'text/x-markdown'; - var metadata = { title : title, mimeType : contentType }; - if (parentId !== undefined) { - // Specify the directory - metadata.parents = [ { kind : 'drive#fileLink', - id : parentId } ]; - } - var path = '/upload/drive/v2/files'; - var method = 'POST'; - if (fileId !== undefined) { - // If it's an update - path += "/" + fileId; - method = 'PUT'; - } - var headers = { 'Content-Type' : 'multipart/mixed; boundary="' - + boundary + '"', }; - if(etag !== undefined) { - // Sometimes we have error 412 from Google even with the correct etag - //headers["If-Match"] = etag; - } + googleHelper.upload = function(fileId, parentId, title, content, etag, callback) { + var result = undefined; + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + var boundary = '-------314159265358979323846'; + var delimiter = "\r\n--" + boundary + "\r\n"; + var close_delim = "\r\n--" + boundary + "--"; + var contentType = 'text/x-markdown'; + var metadata = { + title: title, + mimeType: contentType + }; + if(parentId !== undefined) { + // Specify the directory + metadata.parents = [ + { + kind: 'drive#fileLink', + id: parentId + } + ]; + } + var path = '/upload/drive/v2/files'; + var method = 'POST'; + if(fileId !== undefined) { + // If it's an update + path += "/" + fileId; + method = 'PUT'; + } + var headers = { + 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"', + }; + if(etag !== undefined) { + // Sometimes we have error 412 from Google even with the correct + // etag + // headers["If-Match"] = etag; + } - var base64Data = utils.encodeBase64(content); - var multipartRequestBody = delimiter - + 'Content-Type: application/json\r\n\r\n' - + JSON.stringify(metadata) + delimiter + 'Content-Type: ' - + contentType + '\r\n' - + 'Content-Transfer-Encoding: base64\r\n' + '\r\n' - + base64Data + close_delim; + var base64Data = utils.encodeBase64(content); + var multipartRequestBody = delimiter + 'Content-Type: application/json\r\n\r\n' + JSON.stringify(metadata) + delimiter + 'Content-Type: ' + contentType + '\r\n' + 'Content-Transfer-Encoding: base64\r\n' + '\r\n' + base64Data + close_delim; - var request = gapi.client - .request({ - 'path' : path, - 'method' : method, - 'params' : { 'uploadType' : 'multipart', }, - 'headers' : headers, - 'body' : multipartRequestBody, }); - request.execute(function(response) { - if (response && response.id) { - // Upload success - result = response; - task.chain(); - return; - } - var error = response.error; - // Handle error - if(error !== undefined && fileId !== undefined) { - if(error.code === 404) { - error = 'File ID "' + fileId + '" not found on Google Drive.|removePublish'; - } - else if(error.code === 412) { - // We may have missed a file update - localStorage.removeItem("gdrive.lastChangeId"); - error = 'Conflict on file ID "' + fileId + '". Please restart the synchronization.'; - } - } - handleError(error, task); - }); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; + var request = gapi.client.request({ + 'path': path, + 'method': method, + 'params': { + 'uploadType': 'multipart', + }, + 'headers': headers, + 'body': multipartRequestBody, + }); + request.execute(function(response) { + if(response && response.id) { + // Upload success + result = response; + task.chain(); + return; + } + var error = response.error; + // Handle error + if(error !== undefined && fileId !== undefined) { + if(error.code === 404) { + error = 'File ID "' + fileId + '" not found on Google Drive.|removePublish'; + } + else if(error.code === 412) { + // We may have missed a file update + localStorage.removeItem("gdrive.lastChangeId"); + error = 'Conflict on file ID "' + fileId + '". Please restart the synchronization.'; + } + } + handleError(error, task); + }); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - googleHelper.checkChanges = function(lastChangeId, callback) { - var changes = []; - var newChangeId = lastChangeId || 0; - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - var nextPageToken = undefined; - function retrievePageOfChanges() { - var request = undefined; - if(nextPageToken === undefined) { - request = gapi.client.drive.changes - .list({ 'startChangeId' : newChangeId + 1 }); - } - else { - request = gapi.client.drive.changes - .list({ 'pageToken' : nextPageToken }); - } + googleHelper.checkChanges = function(lastChangeId, callback) { + var changes = []; + var newChangeId = lastChangeId || 0; + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + var nextPageToken = undefined; + function retrievePageOfChanges() { + var request = undefined; + if(nextPageToken === undefined) { + request = gapi.client.drive.changes.list({ + 'startChangeId': newChangeId + 1 + }); + } + else { + request = gapi.client.drive.changes.list({ + 'pageToken': nextPageToken + }); + } - request.execute(function(response) { - if (!response || !response.largestChangeId) { - // Handle error - handleError(response.error, task); - return; - } - // Retrieve success - newChangeId = response.largestChangeId; - nextPageToken = response.nextPageToken; - if (response.items !== undefined) { - changes = changes.concat(response.items); - } - if (nextPageToken !== undefined) { - task.chain(retrievePageOfChanges); - } - else { - task.chain(); - } - }); - } - task.chain(retrievePageOfChanges); - }); - task.onSuccess(function() { - callback(undefined, changes, newChangeId); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; + request.execute(function(response) { + if(!response || !response.largestChangeId) { + // Handle error + handleError(response.error, task); + return; + } + // Retrieve success + newChangeId = response.largestChangeId; + nextPageToken = response.nextPageToken; + if(response.items !== undefined) { + changes = changes.concat(response.items); + } + if(nextPageToken !== undefined) { + task.chain(retrievePageOfChanges); + } + else { + task.chain(); + } + }); + } + task.chain(retrievePageOfChanges); + }); + task.onSuccess(function() { + callback(undefined, changes, newChangeId); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - googleHelper.downloadMetadata = function(ids, callback, skipAuth) { - var result = []; - var task = asyncRunner.createTask(); - connect(task); - if(!skipAuth) { - authenticate(task); - } - task.onRun(function() { - function recursiveDownloadMetadata() { - if(ids.length === 0) { - task.chain(); - return; - } - var id = ids[0]; - var headers = {}; - var token = gapi.auth.getToken(); - if(token) { - headers.Authorization = "Bearer " + token.access_token; - } - $.ajax({ - url : "https://www.googleapis.com/drive/v2/files/" + id, - headers : headers, - data : {key: GOOGLE_API_KEY}, - dataType : "json", - timeout : AJAX_TIMEOUT - }).done(function(data, textStatus, jqXHR) { - result.push(data); - ids.shift(); - task.chain(recursiveDownloadMetadata); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404) { - error = 'File ID "' + id + '" not found on Google Drive.'; - } - handleError(error, task); - }); - } - task.chain(recursiveDownloadMetadata); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; + googleHelper.downloadMetadata = function(ids, callback, skipAuth) { + var result = []; + var task = asyncRunner.createTask(); + connect(task); + if(!skipAuth) { + authenticate(task); + } + task.onRun(function() { + function recursiveDownloadMetadata() { + if(ids.length === 0) { + task.chain(); + return; + } + var id = ids[0]; + var headers = {}; + var token = gapi.auth.getToken(); + if(token) { + headers.Authorization = "Bearer " + token.access_token; + } + $.ajax({ + url: "https://www.googleapis.com/drive/v2/files/" + id, + headers: headers, + data: { + key: GOOGLE_API_KEY + }, + dataType: "json", + timeout: AJAX_TIMEOUT + }).done(function(data, textStatus, jqXHR) { + result.push(data); + ids.shift(); + task.chain(recursiveDownloadMetadata); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404) { + error = 'File ID "' + id + '" not found on Google Drive.'; + } + handleError(error, task); + }); + } + task.chain(recursiveDownloadMetadata); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - googleHelper.downloadContent = function(objects, callback, skipAuth) { - var result = []; - var task = asyncRunner.createTask(); - // Add some time for user to choose his files - task.timeout = ASYNC_TASK_LONG_TIMEOUT; - connect(task); - if(!skipAuth) { - authenticate(task); - } - task.onRun(function() { - function recursiveDownloadContent() { - if(objects.length === 0) { - task.chain(); - return; - } - var object = objects[0]; - result.push(object); - var file = undefined; - // object may be a file - if(object.kind == "drive#file") { - file = object; - } - // object may be a change - else if(object.kind == "drive#change") { - file = object.file; - } - if(!file) { - objects.shift(); - task.chain(recursiveDownloadContent); - return; - } - var headers = {}; - var token = gapi.auth.getToken(); - if(token) { - headers.Authorization = "Bearer " + token.access_token; - } - $.ajax({ - url : file.downloadUrl, - headers : headers, - data : {key: GOOGLE_API_KEY}, - dataType : "text", - timeout : AJAX_TIMEOUT - }).done(function(data, textStatus, jqXHR) { - file.content = data; - objects.shift(); - task.chain(recursiveDownloadContent); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - handleError(error, task); - }); - } - task.chain(recursiveDownloadContent); - }); - task.onSuccess(function() { - callback(undefined, result); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - - function handleError(error, task) { - var errorMsg = undefined; - if (error) { - logger.error(error); - // Try to analyze the error - if (typeof error === "string") { - errorMsg = error; - } - else { - errorMsg = "Google error (" + error.code + ": " - + error.message + ")."; - if (error.code >= 500 && error.code < 600) { - // Retry as described in Google's best practices - task.retry(new Error(errorMsg)); - return; - } else if (error.code === 401 || error.code === 403) { - authenticated = false; - errorMsg = "Access to Google account is not authorized."; - task.retry(new Error(errorMsg), 1); - return; - } else if (error.code <= 0) { - connected = false; - authenticated = false; - core.setOffline(); - errorMsg = "|stopPublish"; - } - } - } - task.error(new Error(errorMsg)); - } + googleHelper.downloadContent = function(objects, callback, skipAuth) { + var result = []; + var task = asyncRunner.createTask(); + // Add some time for user to choose his files + task.timeout = ASYNC_TASK_LONG_TIMEOUT; + connect(task); + if(!skipAuth) { + authenticate(task); + } + task.onRun(function() { + function recursiveDownloadContent() { + if(objects.length === 0) { + task.chain(); + return; + } + var object = objects[0]; + result.push(object); + var file = undefined; + // object may be a file + if(object.kind == "drive#file") { + file = object; + } + // object may be a change + else if(object.kind == "drive#change") { + file = object.file; + } + if(!file) { + objects.shift(); + task.chain(recursiveDownloadContent); + return; + } + var headers = {}; + var token = gapi.auth.getToken(); + if(token) { + headers.Authorization = "Bearer " + token.access_token; + } + $.ajax({ + url: file.downloadUrl, + headers: headers, + data: { + key: GOOGLE_API_KEY + }, + dataType: "text", + timeout: AJAX_TIMEOUT + }).done(function(data, textStatus, jqXHR) { + file.content = data; + objects.shift(); + task.chain(recursiveDownloadContent); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + handleError(error, task); + }); + } + task.chain(recursiveDownloadContent); + }); + task.onSuccess(function() { + callback(undefined, result); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - var pickerLoaded = false; - function loadPicker(task) { - task.onRun(function() { - if (pickerLoaded === true) { - task.chain(); - return; - } - $.ajax({ - url : "//www.google.com/jsapi", - data : {key: GOOGLE_API_KEY}, - dataType : "script", - timeout : AJAX_TIMEOUT - }).done(function() { - google.load('picker', '1', {callback: task.chain}); - pickerLoaded = true; - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - }); - } - - googleHelper.picker = function(callback) { - var ids = []; - var picker = undefined; - function hidePicker() { - if(picker !== undefined) { - picker.setVisible(false); - $(".modal-backdrop, .picker").remove(); - } - } - var task = asyncRunner.createTask(); - connect(task); - loadPicker(task); - task.onRun(function() { - var view = new google.picker.View(google.picker.ViewId.DOCS); - view.setMimeTypes("text/x-markdown,text/plain,application/octet-stream"); - var pickerBuilder = new google.picker.PickerBuilder(); - pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN); - pickerBuilder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED); - pickerBuilder.setAppId(GOOGLE_DRIVE_APP_ID); - var token = gapi.auth.getToken(); - if(token) { - pickerBuilder.setOAuthToken(token.access_token); - } - pickerBuilder.addView(view); - pickerBuilder.addView(new google.picker.DocsUploadView()); - pickerBuilder.setCallback(function(data) { - if (data.action == google.picker.Action.PICKED || - data.action == google.picker.Action.CANCEL) { - if(data.action == google.picker.Action.PICKED) { - for(var i=0; i").addClass("modal-backdrop").click(function() { - hidePicker(); - task.chain(); - })); - picker.setVisible(true); - }); - task.onSuccess(function() { - callback(undefined, ids); - }); - task.onError(function(error) { - hidePicker(); - callback(error); - }); - asyncRunner.addTask(task); - }; + function handleError(error, task) { + var errorMsg = undefined; + if(error) { + logger.error(error); + // Try to analyze the error + if(typeof error === "string") { + errorMsg = error; + } + else { + errorMsg = "Google error (" + error.code + ": " + error.message + ")."; + if(error.code >= 500 && error.code < 600) { + // Retry as described in Google's best practices + task.retry(new Error(errorMsg)); + return; + } + else if(error.code === 401 || error.code === 403) { + authenticated = false; + errorMsg = "Access to Google account is not authorized."; + task.retry(new Error(errorMsg), 1); + return; + } + else if(error.code <= 0) { + connected = false; + authenticated = false; + core.setOffline(); + errorMsg = "|stopPublish"; + } + } + } + task.error(new Error(errorMsg)); + } - googleHelper.uploadBlogger = function(blogUrl, blogId, postId, labelList, title, content, callback) { - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - var headers = {}; - var token = gapi.auth.getToken(); - if(token) { - headers.Authorization = "Bearer " + token.access_token; - } - function publish() { - var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/"; - var data = { - kind: "blogger#post", - blog: { id: blogId }, - labels: labelList, - title: title, - content: content - }; - var type = "POST"; - // If it's an update - if(postId !== undefined) { - url += postId; - data.id = postId; - type = "PUT"; - } - $.ajax({ - url : url, - data: JSON.stringify(data), - headers : headers, - type: type, - contentType: "application/json", - dataType : "json", - timeout : AJAX_TIMEOUT - }).done(function(post, textStatus, jqXHR) { - postId = post.id; - task.chain(); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404 && postId !== undefined) { - error = 'Post ' + postId + ' not found on Blogger.|removePublish'; - } - handleError(error, task); - }); - } - function getBlogId() { - if(blogId !== undefined) { - task.chain(publish); - return; - } - $.ajax({ - url : "https://www.googleapis.com/blogger/v3/blogs/byurl", - data: { url: blogUrl }, - headers : headers, - dataType : "json", - timeout : AJAX_TIMEOUT - }).done(function(blog, textStatus, jqXHR) { - blogId = blog.id; - task.chain(publish); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404) { - error = 'Blog "' + blogUrl + '" not found on Blogger.|removePublish'; - } - handleError(error, task); - }); - } - task.chain(getBlogId); - }); - task.onSuccess(function() { - callback(undefined, blogId, postId); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; + var pickerLoaded = false; + function loadPicker(task) { + task.onRun(function() { + if(pickerLoaded === true) { + task.chain(); + return; + } + $.ajax({ + url: "//www.google.com/jsapi", + data: { + key: GOOGLE_API_KEY + }, + dataType: "script", + timeout: AJAX_TIMEOUT + }).done(function() { + google.load('picker', '1', { + callback: task.chain + }); + pickerLoaded = true; + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + }); + } - return googleHelper; + googleHelper.picker = function(callback) { + var ids = []; + var picker = undefined; + function hidePicker() { + if(picker !== undefined) { + picker.setVisible(false); + $(".modal-backdrop, .picker").remove(); + } + } + var task = asyncRunner.createTask(); + connect(task); + loadPicker(task); + task.onRun(function() { + var view = new google.picker.View(google.picker.ViewId.DOCS); + view.setMimeTypes("text/x-markdown,text/plain,application/octet-stream"); + var pickerBuilder = new google.picker.PickerBuilder(); + pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN); + pickerBuilder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED); + pickerBuilder.setAppId(GOOGLE_DRIVE_APP_ID); + var token = gapi.auth.getToken(); + if(token) { + pickerBuilder.setOAuthToken(token.access_token); + } + pickerBuilder.addView(view); + pickerBuilder.addView(new google.picker.DocsUploadView()); + pickerBuilder.setCallback(function(data) { + if(data.action == google.picker.Action.PICKED || data.action == google.picker.Action.CANCEL) { + if(data.action == google.picker.Action.PICKED) { + for ( var i = 0; i < data.docs.length; i++) { + ids.push(data.docs[i].id); + } + } + hidePicker(); + task.chain(); + } + }); + picker = pickerBuilder.build(); + $("body").append($("
    ").addClass("modal-backdrop").click(function() { + hidePicker(); + task.chain(); + })); + picker.setVisible(true); + }); + task.onSuccess(function() { + callback(undefined, ids); + }); + task.onError(function(error) { + hidePicker(); + callback(error); + }); + asyncRunner.addTask(task); + }; + + googleHelper.uploadBlogger = function(blogUrl, blogId, postId, labelList, title, content, callback) { + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + var headers = {}; + var token = gapi.auth.getToken(); + if(token) { + headers.Authorization = "Bearer " + token.access_token; + } + function publish() { + var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/"; + var data = { + kind: "blogger#post", + blog: { + id: blogId + }, + labels: labelList, + title: title, + content: content + }; + var type = "POST"; + // If it's an update + if(postId !== undefined) { + url += postId; + data.id = postId; + type = "PUT"; + } + $.ajax({ + url: url, + data: JSON.stringify(data), + headers: headers, + type: type, + contentType: "application/json", + dataType: "json", + timeout: AJAX_TIMEOUT + }).done(function(post, textStatus, jqXHR) { + postId = post.id; + task.chain(); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404 && postId !== undefined) { + error = 'Post ' + postId + ' not found on Blogger.|removePublish'; + } + handleError(error, task); + }); + } + function getBlogId() { + if(blogId !== undefined) { + task.chain(publish); + return; + } + $.ajax({ + url: "https://www.googleapis.com/blogger/v3/blogs/byurl", + data: { + url: blogUrl + }, + headers: headers, + dataType: "json", + timeout: AJAX_TIMEOUT + }).done(function(blog, textStatus, jqXHR) { + blogId = blog.id; + task.chain(publish); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404) { + error = 'Blog "' + blogUrl + '" not found on Blogger.|removePublish'; + } + handleError(error, task); + }); + } + task.chain(getBlogId); + }); + task.onSuccess(function() { + callback(undefined, blogId, postId); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; + + return googleHelper; }); diff --git a/js/publisher.js b/js/publisher.js index 5ca8376f..decd980b 100644 --- a/js/publisher.js +++ b/js/publisher.js @@ -18,236 +18,229 @@ define([ "wordpress-provider" ], function($, _, core, utils, settings, extensionMgr, fileSystem, fileMgr, sharing) { - var publisher = {}; - - // Create a map with providerId: providerModule - var providerMap = _.chain( - arguments - ).map(function(argument) { - return argument && argument.providerId && [argument.providerId, argument]; - }).compact().object().value(); - - // 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; - }); - }); + var publisher = {}; - // Apply template to the current document - publisher.applyTemplate = function(publishAttributes) { - var fileDesc = fileMgr.getCurrentFile(); - try { - return _.template(settings.template, { - documentTitle: fileDesc.title, - documentMarkdown: $("#wmd-input").val(), - documentHTML: $("#wmd-preview").html(), - publishAttributes: publishAttributes - }); - } catch(e) { - extensionMgr.onError(e); - throw e; - } - }; - - // Used to get content to publish - function getPublishContent(publishAttributes) { - if(publishAttributes.format === undefined) { - publishAttributes.format = $("input:radio[name=radio-publish-format]:checked").prop("value"); - } - if(publishAttributes.format == "markdown") { - return $("#wmd-input").val(); - } - else if(publishAttributes.format == "html") { - return $("#wmd-preview").html(); - } - else { - return publisher.applyTemplate(publishAttributes); - } - } - - // Recursive function to publish a file on multiple locations - var publishAttributesList = []; - var publishTitle = undefined; - function publishLocation(callback, errorFlag) { - - // No more publish location for this document - if (publishAttributesList.length === 0) { - callback(errorFlag); - return; - } - - // Dequeue a synchronized location - var publishAttributes = publishAttributesList.pop(); - var content = getPublishContent(publishAttributes); - - // Call the provider - publishAttributes.provider.publish(publishAttributes, publishTitle, content, function(error) { - if(error !== undefined) { - var errorMsg = error.toString(); - if(errorMsg.indexOf("|removePublish") !== -1) { - fileMgr.removePublish(publishAttributes); - } - if(errorMsg.indexOf("|stopPublish") !== -1) { - callback(error); - return; - } - } - publishLocation(callback, errorFlag || error ); - }); - } - - var publishRunning = false; - publisher.publish = function() { - // If publish is running or offline - if(publishRunning === true || core.isOffline) { - return; - } - - publishRunning = true; - extensionMgr.onPublishRunning(true); - var fileDesc = fileMgr.getCurrentFile(); - publishTitle = fileDesc.title; - publishAttributesList = _.values(fileDesc.publishLocations); - publishLocation(function(errorFlag) { - publishRunning = false; - extensionMgr.onPublishRunning(false); - if(errorFlag === undefined) { - extensionMgr.onPublishSuccess(fileDesc); - } - }); - }; - - // Generate a publishIndex associated to a file and store publishAttributes - function createPublishIndex(fileDesc, publishAttributes) { - var publishIndex = undefined; - do { - publishIndex = "publish." + utils.randomString(); - } while(_.has(localStorage, publishIndex)); - publishAttributes.publishIndex = publishIndex; - utils.storeAttributes(publishAttributes); - fileMgr.addPublish(fileDesc, publishAttributes); - } - - // Initialize the "New publication" dialog - var newLocationProvider = undefined; - function initNewLocation(provider) { - var defaultPublishFormat = provider.defaultPublishFormat || "markdown"; - newLocationProvider = provider; - $(".publish-provider-name").text(provider.providerName); - - // Show/hide controls depending on provider - $('div[class*=" modal-publish-"]').hide().filter(".modal-publish-" + provider.providerId).show(); - - // Reset fields - utils.resetModalInputs(); - $("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); - _.each(provider.publishPreferencesInputIds, function(inputId) { - utils.setInputValue("#input-publish-" + inputId, publishPreferences[inputId]); - }); - utils.setInputRadio("radio-publish-format", publishPreferences.format); - } - - // Open dialog box - $("#modal-publish").modal(); - } - - // Add a new publish location to a local document - function performNewLocation(event) { - var provider = newLocationProvider; - var publishAttributes = provider.newPublishAttributes(event); - if(publishAttributes === undefined) { - return; - } - - // Perform provider's publishing - var fileDesc = fileMgr.getCurrentFile(); - var title = fileDesc.title; - var content = getPublishContent(publishAttributes); - provider.publish(publishAttributes, title, content, function(error) { - if(error === undefined) { - publishAttributes.provider = provider.providerId; - sharing.createLink(publishAttributes, function() { - createPublishIndex(fileDesc, publishAttributes); - }); - } - }); - - // Store input values as preferences for next time we open the publish dialog - var publishPreferences = {}; - _.each(provider.publishPreferencesInputIds, function(inputId) { - publishPreferences[inputId] = $("#input-publish-" + inputId).val(); - }); - publishPreferences.format = publishAttributes.format; - 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"); - _.each(providerMap, function(provider) { - // Provider's publish button - publishMenu.append( - $("
  • ").append( - $(' ' + provider.providerName + '') - .click(function() { - initNewLocation(provider); - } - ) - ) - ); - // Action links (if any) - $(".action-publish-" + provider.providerId).click(function() { - initNewLocation(provider); - }); - }); - - $(".action-process-publish").click(performNewLocation); - - // Save As menu items - $(".action-download-md").click(function() { - var content = $("#wmd-input").val(); - var title = fileMgr.getCurrentFile().title; - utils.saveAs(content, title + ".md"); - }); - $(".action-download-html").click(function() { - var content = $("#wmd-preview").html(); - var title = fileMgr.getCurrentFile().title; - utils.saveAs(content, title + ".html"); - }); - $(".action-download-template").click(function() { - var content = publisher.applyTemplate(); - var title = fileMgr.getCurrentFile().title; - utils.saveAs(content, title + ".txt"); - }); - }); - - extensionMgr.onPublisherCreated(publisher); - return publisher; + // Create a map with providerId: providerModule + var providerMap = _.chain(arguments).map(function(argument) { + return argument && argument.providerId && [ + argument.providerId, + argument + ]; + }).compact().object().value(); + + // 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; + }); + }); + + // Apply template to the current document + publisher.applyTemplate = function(publishAttributes) { + var fileDesc = fileMgr.getCurrentFile(); + try { + return _.template(settings.template, { + documentTitle: fileDesc.title, + documentMarkdown: $("#wmd-input").val(), + documentHTML: $("#wmd-preview").html(), + publishAttributes: publishAttributes + }); + } + catch (e) { + extensionMgr.onError(e); + throw e; + } + }; + + // Used to get content to publish + function getPublishContent(publishAttributes) { + if(publishAttributes.format === undefined) { + publishAttributes.format = $("input:radio[name=radio-publish-format]:checked").prop("value"); + } + if(publishAttributes.format == "markdown") { + return $("#wmd-input").val(); + } + else if(publishAttributes.format == "html") { + return $("#wmd-preview").html(); + } + else { + return publisher.applyTemplate(publishAttributes); + } + } + + // Recursive function to publish a file on multiple locations + var publishAttributesList = []; + var publishTitle = undefined; + function publishLocation(callback, errorFlag) { + + // No more publish location for this document + if(publishAttributesList.length === 0) { + callback(errorFlag); + return; + } + + // Dequeue a synchronized location + var publishAttributes = publishAttributesList.pop(); + var content = getPublishContent(publishAttributes); + + // Call the provider + publishAttributes.provider.publish(publishAttributes, publishTitle, content, function(error) { + if(error !== undefined) { + var errorMsg = error.toString(); + if(errorMsg.indexOf("|removePublish") !== -1) { + fileMgr.removePublish(publishAttributes); + } + if(errorMsg.indexOf("|stopPublish") !== -1) { + callback(error); + return; + } + } + publishLocation(callback, errorFlag || error); + }); + } + + var publishRunning = false; + publisher.publish = function() { + // If publish is running or offline + if(publishRunning === true || core.isOffline) { + return; + } + + publishRunning = true; + extensionMgr.onPublishRunning(true); + var fileDesc = fileMgr.getCurrentFile(); + publishTitle = fileDesc.title; + publishAttributesList = _.values(fileDesc.publishLocations); + publishLocation(function(errorFlag) { + publishRunning = false; + extensionMgr.onPublishRunning(false); + if(errorFlag === undefined) { + extensionMgr.onPublishSuccess(fileDesc); + } + }); + }; + + // Generate a publishIndex associated to a file and store publishAttributes + function createPublishIndex(fileDesc, publishAttributes) { + var publishIndex = undefined; + do { + publishIndex = "publish." + utils.randomString(); + } while (_.has(localStorage, publishIndex)); + publishAttributes.publishIndex = publishIndex; + utils.storeAttributes(publishAttributes); + fileMgr.addPublish(fileDesc, publishAttributes); + } + + // Initialize the "New publication" dialog + var newLocationProvider = undefined; + function initNewLocation(provider) { + var defaultPublishFormat = provider.defaultPublishFormat || "markdown"; + newLocationProvider = provider; + $(".publish-provider-name").text(provider.providerName); + + // Show/hide controls depending on provider + $('div[class*=" modal-publish-"]').hide().filter(".modal-publish-" + provider.providerId).show(); + + // Reset fields + utils.resetModalInputs(); + $("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); + _.each(provider.publishPreferencesInputIds, function(inputId) { + utils.setInputValue("#input-publish-" + inputId, publishPreferences[inputId]); + }); + utils.setInputRadio("radio-publish-format", publishPreferences.format); + } + + // Open dialog box + $("#modal-publish").modal(); + } + + // Add a new publish location to a local document + function performNewLocation(event) { + var provider = newLocationProvider; + var publishAttributes = provider.newPublishAttributes(event); + if(publishAttributes === undefined) { + return; + } + + // Perform provider's publishing + var fileDesc = fileMgr.getCurrentFile(); + var title = fileDesc.title; + var content = getPublishContent(publishAttributes); + provider.publish(publishAttributes, title, content, function(error) { + if(error === undefined) { + publishAttributes.provider = provider.providerId; + sharing.createLink(publishAttributes, function() { + createPublishIndex(fileDesc, publishAttributes); + }); + } + }); + + // Store input values as preferences for next time we open the publish + // dialog + var publishPreferences = {}; + _.each(provider.publishPreferencesInputIds, function(inputId) { + publishPreferences[inputId] = $("#input-publish-" + inputId).val(); + }); + publishPreferences.format = publishAttributes.format; + 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"); + _.each(providerMap, function(provider) { + // Provider's publish button + publishMenu.append($("
  • ").append($(' ' + provider.providerName + '').click(function() { + initNewLocation(provider); + }))); + // Action links (if any) + $(".action-publish-" + provider.providerId).click(function() { + initNewLocation(provider); + }); + }); + + $(".action-process-publish").click(performNewLocation); + + // Save As menu items + $(".action-download-md").click(function() { + var content = $("#wmd-input").val(); + var title = fileMgr.getCurrentFile().title; + utils.saveAs(content, title + ".md"); + }); + $(".action-download-html").click(function() { + var content = $("#wmd-preview").html(); + var title = fileMgr.getCurrentFile().title; + utils.saveAs(content, title + ".html"); + }); + $(".action-download-template").click(function() { + var content = publisher.applyTemplate(); + var title = fileMgr.getCurrentFile().title; + utils.saveAs(content, title + ".txt"); + }); + }); + + extensionMgr.onPublisherCreated(publisher); + return publisher; }); \ No newline at end of file diff --git a/js/settings.js b/js/settings.js index e94df83a..2793ac13 100644 --- a/js/settings.js +++ b/js/settings.js @@ -2,28 +2,29 @@ define([ "underscore", "config" ], function(_) { - - var settings = { - layoutOrientation : "horizontal", - lazyRendering : true, - editorFontSize : 14, - defaultContent: "\n\n\n> Written with [StackEdit](http://benweet.github.io/stackedit/).", - commitMsg : "Published by http://benweet.github.io/stackedit", - template : [ + + var settings = { + layoutOrientation: "horizontal", + lazyRendering: true, + editorFontSize: 14, + defaultContent: "\n\n\n> Written with [StackEdit](http://benweet.github.io/stackedit/).", + commitMsg: "Published by http://benweet.github.io/stackedit", + template: [ '\n', - '\n', - '\n', - '<%= documentTitle %>\n', - '\n', - '<%= documentHTML %>\n', - ''].join(""), - sshProxy : SSH_PROXY_URL, - extensionSettings: {} - }; - - if (_.has(localStorage, "settings")) { - _.extend(settings, JSON.parse(localStorage.settings)); - } - - return settings; + '\n', + '\n', + '<%= documentTitle %>\n', + '\n', + '<%= documentHTML %>\n', + '' + ].join(""), + sshProxy: SSH_PROXY_URL, + extensionSettings: {} + }; + + if(_.has(localStorage, "settings")) { + _.extend(settings, JSON.parse(localStorage.settings)); + } + + return settings; }); \ No newline at end of file diff --git a/js/sharing.js b/js/sharing.js index 1397ab45..8ba55b08 100644 --- a/js/sharing.js +++ b/js/sharing.js @@ -10,125 +10,128 @@ define([ "download-provider", "gist-provider" ], function($, _, core, utils, extensionMgr, fileMgr, asyncRunner) { - - var sharing = {}; - - // Create a map with providerId: providerModule - var providerMap = _.chain( - arguments - ).map(function(argument) { - return argument && argument.providerId && [argument.providerId, argument]; - }).compact().object().value(); - // Used to populate the "Sharing" dropdown box - var lineTemplate = ['
    ', - '', - '', - '
    '].join(""); - sharing.refreshDocumentSharing = function(attributesList) { - var linkList = $("#link-container .link-list").empty(); - $("#link-container .no-link").show(); - _.each(attributesList, function(attributes) { - if(attributes.sharingLink) { - var lineElement = $(_.template(lineTemplate, { - link: attributes.sharingLink - })); - lineElement.click(function(event) { - event.stopPropagation(); - }); - linkList.append(lineElement); - $("#link-container .no-link").hide(); - } - }); - }; - - sharing.createLink = function(attributes, callback) { - var provider = providerMap[attributes.provider]; - // Don't create link if link already exists or provider is not compatible for sharing - if(attributes.sharingLink !== undefined || provider === undefined - // Or document is not published in markdown format - || attributes.format != "markdown") { - callback(); - return; - } - var task = asyncRunner.createTask(); - var shortUrl = undefined; - task.onRun(function() { - if(core.isOffline === true) { - task.chain(); - return; - } - var url = [MAIN_URL, 'viewer.html?provider=', attributes.provider]; - _.each(provider.sharingAttributes, function(attributeName) { - url.push('&'); - url.push(attributeName); - url.push('='); - url.push(encodeURIComponent(attributes[attributeName])); - }); - url = url.join(""); - $.getJSON( - "https://api-ssl.bitly.com/v3/shorten", - { - "access_token": BITLY_ACCESS_TOKEN, - "longUrl": url - }, - function(response) - { - if(response.data) { - shortUrl = response.data.url; - attributes.sharingLink = shortUrl; - } - else { - extensionMgr.onError("An error occured while creating sharing link."); - attributes.sharingLink = url; - } - task.chain(); - } - ); - }); - function onFinish() { - callback(); - } - task.onSuccess(onFinish); - task.onError(onFinish); - asyncRunner.addTask(task); - }; - - core.onReady(function() { - if(viewerMode === false) { - return; - } - // Check parameters to see if we have to download a shared document - var providerId = utils.getURLParameter("provider"); - if(providerId === undefined) { - providerId = "download"; - } - var provider = providerMap[providerId]; - if(provider === undefined) { - return; - } - var importParameters = {}; - _.each(provider.sharingAttributes, function(attributeName) { - var parameter = utils.getURLParameter(attributeName); - if(!parameter) { - importParameters = undefined; - return; - } - importParameters[attributeName] = parameter; - }); - if(importParameters === undefined) { - return; - } - $("#wmd-preview, #file-title").hide(); - provider.importPublic(importParameters, function(error, title, content) { - $("#wmd-preview, #file-title").show(); - if(error) { - return; - } - var fileDesc = fileMgr.createFile(title, content, undefined, true); - fileMgr.selectFile(fileDesc); - }); - }); - - return sharing; + var sharing = {}; + + // Create a map with providerId: providerModule + var providerMap = _.chain(arguments).map(function(argument) { + return argument && argument.providerId && [ + argument.providerId, + argument + ]; + }).compact().object().value(); + + // Used to populate the "Sharing" dropdown box + var lineTemplate = [ + '
    ', + ' ', + ' ', + '
    ' + ].join(""); + sharing.refreshDocumentSharing = function(attributesList) { + var linkList = $("#link-container .link-list").empty(); + $("#link-container .no-link").show(); + _.each(attributesList, function(attributes) { + if(attributes.sharingLink) { + var lineElement = $(_.template(lineTemplate, { + link: attributes.sharingLink + })); + lineElement.click(function(event) { + event.stopPropagation(); + }); + linkList.append(lineElement); + $("#link-container .no-link").hide(); + } + }); + }; + + sharing.createLink = function(attributes, callback) { + var provider = providerMap[attributes.provider]; + // Don't create link if link already exists or provider is not + // compatible for sharing + if(attributes.sharingLink !== undefined || provider === undefined + // Or document is not published in markdown format + || attributes.format != "markdown") { + callback(); + return; + } + var task = asyncRunner.createTask(); + var shortUrl = undefined; + task.onRun(function() { + if(core.isOffline === true) { + task.chain(); + return; + } + var url = [ + MAIN_URL, + 'viewer.html?provider=', + attributes.provider + ]; + _.each(provider.sharingAttributes, function(attributeName) { + url.push('&'); + url.push(attributeName); + url.push('='); + url.push(encodeURIComponent(attributes[attributeName])); + }); + url = url.join(""); + $.getJSON("https://api-ssl.bitly.com/v3/shorten", { + "access_token": BITLY_ACCESS_TOKEN, + "longUrl": url + }, function(response) { + if(response.data) { + shortUrl = response.data.url; + attributes.sharingLink = shortUrl; + } + else { + extensionMgr.onError("An error occured while creating sharing link."); + attributes.sharingLink = url; + } + task.chain(); + }); + }); + function onFinish() { + callback(); + } + task.onSuccess(onFinish); + task.onError(onFinish); + asyncRunner.addTask(task); + }; + + core.onReady(function() { + if(viewerMode === false) { + return; + } + // Check parameters to see if we have to download a shared document + var providerId = utils.getURLParameter("provider"); + if(providerId === undefined) { + providerId = "download"; + } + var provider = providerMap[providerId]; + if(provider === undefined) { + return; + } + var importParameters = {}; + _.each(provider.sharingAttributes, function(attributeName) { + var parameter = utils.getURLParameter(attributeName); + if(!parameter) { + importParameters = undefined; + return; + } + importParameters[attributeName] = parameter; + }); + if(importParameters === undefined) { + return; + } + $("#wmd-preview, #file-title").hide(); + provider.importPublic(importParameters, function(error, title, content) { + $("#wmd-preview, #file-title").show(); + if(error) { + return; + } + var fileDesc = fileMgr.createFile(title, content, undefined, true); + fileMgr.selectFile(fileDesc); + }); + }); + + return sharing; }); \ No newline at end of file diff --git a/js/ssh-helper.js b/js/ssh-helper.js index 52268fb0..36707dec 100644 --- a/js/ssh-helper.js +++ b/js/ssh-helper.js @@ -4,80 +4,80 @@ define([ "async-runner" ], function($, core, asyncRunner) { - var sshHelper = {}; + var sshHelper = {}; - // Only used to check the offline status - function connect(task) { - task.onRun(function() { - if(core.isOffline === true) { - task.error(new Error("Operation not available in offline mode.|stopPublish")); - return; - } - task.chain(); - }); - } + // Only used to check the offline status + function connect(task) { + task.onRun(function() { + if(core.isOffline === true) { + task.error(new Error("Operation not available in offline mode.|stopPublish")); + return; + } + task.chain(); + }); + } - sshHelper.upload = function(host, port, username, password, path, title, content, callback) { - var task = asyncRunner.createTask(); - connect(task); - task.onRun(function() { - var url = SSH_PROXY_URL + "upload"; - var data = { - host: host, - port: port, - username: username, - password: password, - path: path, - title: title, - content: content - }; - $.ajax({ - url : url, - data: data, - type: "POST", - dataType : "json", - timeout : AJAX_TIMEOUT - }).done(function(response, textStatus, jqXHR) { - if(response.error === undefined) { - task.chain(); - return; - } - handleError(response.error, task); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - }); - task.onSuccess(function() { - callback(); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - - function handleError(error, task) { - var errorMsg = undefined; - if (error) { - logger.error(error); - // Try to analyze the error - if (typeof error === "string") { - errorMsg = "SSH error: " + error + "."; - } - else { - errorMsg = "Could not publish on SSH server."; - if (error.code <= 0) { - core.setOffline(); - errorMsg = "|stopPublish"; - } - } - } - task.error(new Error(errorMsg)); - } + sshHelper.upload = function(host, port, username, password, path, title, content, callback) { + var task = asyncRunner.createTask(); + connect(task); + task.onRun(function() { + var url = SSH_PROXY_URL + "upload"; + var data = { + host: host, + port: port, + username: username, + password: password, + path: path, + title: title, + content: content + }; + $.ajax({ + url: url, + data: data, + type: "POST", + dataType: "json", + timeout: AJAX_TIMEOUT + }).done(function(response, textStatus, jqXHR) { + if(response.error === undefined) { + task.chain(); + return; + } + handleError(response.error, task); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + }); + task.onSuccess(function() { + callback(); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - return sshHelper; + function handleError(error, task) { + var errorMsg = undefined; + if(error) { + logger.error(error); + // Try to analyze the error + if(typeof error === "string") { + errorMsg = "SSH error: " + error + "."; + } + else { + errorMsg = "Could not publish on SSH server."; + if(error.code <= 0) { + core.setOffline(); + errorMsg = "|stopPublish"; + } + } + } + task.error(new Error(errorMsg)); + } + + return sshHelper; }); diff --git a/js/ssh-provider.js b/js/ssh-provider.js index c9500f7e..79e5dd1f 100644 --- a/js/ssh-provider.js +++ b/js/ssh-provider.js @@ -3,46 +3,35 @@ define([ "ssh-helper" ], function(utils, sshHelper) { - var PROVIDER_SSH = "ssh"; + var PROVIDER_SSH = "ssh"; - var sshProvider = { - providerId : PROVIDER_SSH, - providerName : "SSH server", - publishPreferencesInputIds: ["ssh-host", "ssh-port", "ssh-username", "ssh-password"] - }; + var sshProvider = { + providerId: PROVIDER_SSH, + providerName: "SSH server", + publishPreferencesInputIds: [ + "ssh-host", + "ssh-port", + "ssh-username", + "ssh-password" + ] + }; - sshProvider.publish = function(publishAttributes, title, content, callback) { - sshHelper.upload( - publishAttributes.host, - publishAttributes.port, - publishAttributes.username, - publishAttributes.password, - publishAttributes.path, - title, - content, - callback); - }; + sshProvider.publish = function(publishAttributes, title, content, callback) { + sshHelper.upload(publishAttributes.host, publishAttributes.port, publishAttributes.username, publishAttributes.password, publishAttributes.path, title, content, callback); + }; - sshProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - publishAttributes.host = utils - .getInputTextValue( - "#input-publish-ssh-host", - event, - /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/); - publishAttributes.port = utils.getInputIntValue( - "#input-publish-ssh-port", undefined, 0); - publishAttributes.username = utils.getInputTextValue( - "#input-publish-ssh-username", event); - publishAttributes.password = utils.getInputTextValue( - "#input-publish-ssh-password", event); - publishAttributes.path = utils.getInputTextValue( - "#input-publish-file-path", event); - if (event.isPropagationStopped()) { - return undefined; - } - return publishAttributes; - }; + sshProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + publishAttributes.host = utils.getInputTextValue("#input-publish-ssh-host", event, /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/); + publishAttributes.port = utils.getInputIntValue("#input-publish-ssh-port", undefined, 0); + publishAttributes.username = utils.getInputTextValue("#input-publish-ssh-username", event); + publishAttributes.password = utils.getInputTextValue("#input-publish-ssh-password", event); + publishAttributes.path = utils.getInputTextValue("#input-publish-file-path", event); + if(event.isPropagationStopped()) { + return undefined; + } + return publishAttributes; + }; - return sshProvider; + return sshProvider; }); \ No newline at end of file diff --git a/js/storage.js b/js/storage.js index 8f76b6dc..cb8072eb 100644 --- a/js/storage.js +++ b/js/storage.js @@ -1,131 +1,128 @@ // Setup an empty localStorage or upgrade an existing one define([ - "underscore" + "underscore" ], function(_) { - - // Create the file system if not exist - if (localStorage["file.list"] === undefined) { - localStorage["file.list"] = ";"; - } - var fileIndexList = _.compact(localStorage["file.list"].split(";")); - - // localStorage versioning - var version = localStorage["version"]; - - // Upgrade from v0 to v1 - if(version === undefined) { - - // Not used anymore - localStorage.removeItem("sync.queue"); - localStorage.removeItem("sync.current"); - localStorage.removeItem("file.counter"); - - _.each(fileIndexList, function(fileIndex) { - localStorage[fileIndex + ".publish"] = ";"; - var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); - _.each(syncIndexList, function(syncIndex) { - localStorage[syncIndex + ".contentCRC"] = "0"; - // We store title CRC only for Google Drive synchronization - if(localStorage[syncIndex + ".etag"] !== undefined) { - localStorage[syncIndex + ".titleCRC"] = "0"; - } - }); - }); - version = "v1"; - } - - // Upgrade from v1 to v2 - if(version == "v1") { - var gdriveLastChangeId = localStorage["sync.gdrive.lastChangeId"]; - if(gdriveLastChangeId) { - localStorage["gdrive.lastChangeId"] = gdriveLastChangeId; - localStorage.removeItem("sync.gdrive.lastChangeId"); - } - var dropboxLastChangeId = localStorage["sync.dropbox.lastChangeId"]; - if(dropboxLastChangeId) { - localStorage["dropbox.lastChangeId"] = dropboxLastChangeId; - localStorage.removeItem("sync.dropbox.lastChangeId"); - } - - var PROVIDER_GDRIVE = "gdrive"; - var PROVIDER_DROPBOX = "dropbox"; - 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(";")); - _.each(syncIndexList, function(syncIndex) { - var syncAttributes = {}; - if (syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { - syncAttributes.provider = PROVIDER_GDRIVE; - syncAttributes.id = syncIndex.substring(SYNC_PROVIDER_GDRIVE.length); - syncAttributes.etag = localStorage[syncIndex + ".etag"]; - syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"]; - syncAttributes.titleCRC = localStorage[syncIndex + ".titleCRC"]; - } - else if (syncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) { - syncAttributes.provider = PROVIDER_DROPBOX; - syncAttributes.path = decodeURIComponent(syncIndex.substring(SYNC_PROVIDER_DROPBOX.length)); - syncAttributes.version = localStorage[syncIndex + ".version"]; - syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"]; - } - localStorage[syncIndex] = JSON.stringify(syncAttributes); - localStorage.removeItem(syncIndex + ".etag"); - localStorage.removeItem(syncIndex + ".version"); - localStorage.removeItem(syncIndex + ".contentCRC"); - localStorage.removeItem(syncIndex + ".titleCRC"); - }); - }); - version = "v2"; - } - - // Upgrade from v2 to v3 - if(version == "v2") { - _.each(fileIndexList, function(fileIndex) { - if(!_.has(localStorage, fileIndex + ".sync")) { - localStorage.removeItem(fileIndex + ".title"); - localStorage.removeItem(fileIndex + ".publish"); - localStorage.removeItem(fileIndex + ".content"); - localStorage["file.list"] = localStorage["file.list"].replace(";" - + fileIndex + ";", ";"); - } - }); - version = "v3"; - } - - // Upgrade from v3 to v4 - if(version == "v3") { - var currentFileIndex = localStorage["file.current"]; - if(currentFileIndex !== undefined && - localStorage["file.list"].indexOf(";" + currentFileIndex + ";") === -1) - { - localStorage.removeItem("file.current"); - } - version = "v4"; - } - - // Upgrade from v4 to v5 - if(version == "v4") { - // Recreate GitHub token - localStorage.removeItem("githubToken"); - version = "v5"; - } - - // Upgrade from v5 to v6 - if(version == "v5") { - _.each(fileIndexList, function(fileIndex) { - var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); - _.each(publishIndexList, function(publishIndex) { - var publishAttributes = JSON.parse(localStorage[publishIndex]); - if(publishAttributes.provider == "gdrive") { - // Change fileId to Id to be consistent with syncAttributes - publishAttributes.id = publishAttributes.fileId; - publishAttributes.fileId = undefined; - localStorage[publishIndex] = JSON.stringify(publishAttributes); - } - }); - }); - version = "v6"; - } - - localStorage["version"] = version; + + // Create the file system if not exist + if(localStorage["file.list"] === undefined) { + localStorage["file.list"] = ";"; + } + var fileIndexList = _.compact(localStorage["file.list"].split(";")); + + // localStorage versioning + var version = localStorage["version"]; + + // Upgrade from v0 to v1 + if(version === undefined) { + + // Not used anymore + localStorage.removeItem("sync.queue"); + localStorage.removeItem("sync.current"); + localStorage.removeItem("file.counter"); + + _.each(fileIndexList, function(fileIndex) { + localStorage[fileIndex + ".publish"] = ";"; + var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); + _.each(syncIndexList, function(syncIndex) { + localStorage[syncIndex + ".contentCRC"] = "0"; + // We store title CRC only for Google Drive synchronization + if(localStorage[syncIndex + ".etag"] !== undefined) { + localStorage[syncIndex + ".titleCRC"] = "0"; + } + }); + }); + version = "v1"; + } + + // Upgrade from v1 to v2 + if(version == "v1") { + var gdriveLastChangeId = localStorage["sync.gdrive.lastChangeId"]; + if(gdriveLastChangeId) { + localStorage["gdrive.lastChangeId"] = gdriveLastChangeId; + localStorage.removeItem("sync.gdrive.lastChangeId"); + } + var dropboxLastChangeId = localStorage["sync.dropbox.lastChangeId"]; + if(dropboxLastChangeId) { + localStorage["dropbox.lastChangeId"] = dropboxLastChangeId; + localStorage.removeItem("sync.dropbox.lastChangeId"); + } + + var PROVIDER_GDRIVE = "gdrive"; + var PROVIDER_DROPBOX = "dropbox"; + 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(";")); + _.each(syncIndexList, function(syncIndex) { + var syncAttributes = {}; + if(syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { + syncAttributes.provider = PROVIDER_GDRIVE; + syncAttributes.id = syncIndex.substring(SYNC_PROVIDER_GDRIVE.length); + syncAttributes.etag = localStorage[syncIndex + ".etag"]; + syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"]; + syncAttributes.titleCRC = localStorage[syncIndex + ".titleCRC"]; + } + else if(syncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) { + syncAttributes.provider = PROVIDER_DROPBOX; + syncAttributes.path = decodeURIComponent(syncIndex.substring(SYNC_PROVIDER_DROPBOX.length)); + syncAttributes.version = localStorage[syncIndex + ".version"]; + syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"]; + } + localStorage[syncIndex] = JSON.stringify(syncAttributes); + localStorage.removeItem(syncIndex + ".etag"); + localStorage.removeItem(syncIndex + ".version"); + localStorage.removeItem(syncIndex + ".contentCRC"); + localStorage.removeItem(syncIndex + ".titleCRC"); + }); + }); + version = "v2"; + } + + // Upgrade from v2 to v3 + if(version == "v2") { + _.each(fileIndexList, function(fileIndex) { + if(!_.has(localStorage, fileIndex + ".sync")) { + localStorage.removeItem(fileIndex + ".title"); + localStorage.removeItem(fileIndex + ".publish"); + localStorage.removeItem(fileIndex + ".content"); + localStorage["file.list"] = localStorage["file.list"].replace(";" + fileIndex + ";", ";"); + } + }); + version = "v3"; + } + + // Upgrade from v3 to v4 + if(version == "v3") { + var currentFileIndex = localStorage["file.current"]; + if(currentFileIndex !== undefined && localStorage["file.list"].indexOf(";" + currentFileIndex + ";") === -1) { + localStorage.removeItem("file.current"); + } + version = "v4"; + } + + // Upgrade from v4 to v5 + if(version == "v4") { + // Recreate GitHub token + localStorage.removeItem("githubToken"); + version = "v5"; + } + + // Upgrade from v5 to v6 + if(version == "v5") { + _.each(fileIndexList, function(fileIndex) { + var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); + _.each(publishIndexList, function(publishIndex) { + var publishAttributes = JSON.parse(localStorage[publishIndex]); + if(publishAttributes.provider == "gdrive") { + // Change fileId to Id to be consistent with syncAttributes + publishAttributes.id = publishAttributes.fileId; + publishAttributes.fileId = undefined; + localStorage[publishIndex] = JSON.stringify(publishAttributes); + } + }); + }); + version = "v6"; + } + + localStorage["version"] = version; }); \ No newline at end of file diff --git a/js/synchronizer.js b/js/synchronizer.js index 929fb927..32d22411 100644 --- a/js/synchronizer.js +++ b/js/synchronizer.js @@ -9,250 +9,244 @@ define([ "dropbox-provider", "gdrive-provider" ], function($, _, core, utils, extensionMgr, fileSystem, fileMgr) { - - var synchronizer = {}; - - // Create a map with providerId: providerModule - var providerMap = _.chain( - arguments - ).map(function(argument) { - return argument && argument.providerId && [argument.providerId, argument]; - }).compact().object().value(); - - // 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; - }); - }); - // Force the synchronization - synchronizer.forceSync = function() { - lastSync = 0; - synchronizer.sync(); - }; - - // Recursive function to upload a single file on multiple locations - var uploadSyncAttributesList = []; - var uploadContent = undefined; - var uploadContentCRC = undefined; - var uploadTitle = undefined; - var uploadTitleCRC = undefined; - function locationUp(callback) { - - // No more synchronized location for this document - if (uploadSyncAttributesList.length === 0) { - fileUp(callback); - return; - } - - // Dequeue a synchronized location - var syncAttributes = uploadSyncAttributesList.pop(); - // Use the specified provider to perform the upload - syncAttributes.provider.syncUp( - uploadContent, - uploadContentCRC, - uploadTitle, - uploadTitleCRC, - syncAttributes, - function(error, uploadFlag) { - if(uploadFlag === true) { - // If uploadFlag is true, request another upload cycle - uploadCycle = true; - } - if(error) { - callback(error); - return; - } - if(uploadFlag) { - // Update syncAttributes in localStorage - utils.storeAttributes(syncAttributes); - } - locationUp(callback); - } - ); - } + var synchronizer = {}; - // Recursive function to upload multiple files - var uploadFileList = []; - function fileUp(callback) { - - // No more fileDesc to synchronize - if (uploadFileList.length === 0) { - syncUp(callback); - return; - } - - // Dequeue a fileDesc to synchronize - var fileDesc = uploadFileList.pop(); - uploadSyncAttributesList = _.values(fileDesc.syncLocations); - if(uploadSyncAttributesList.length === 0) { - fileUp(callback); - return; - } + // Create a map with providerId: providerModule + var providerMap = _.chain(arguments).map(function(argument) { + return argument && argument.providerId && [ + argument.providerId, + argument + ]; + }).compact().object().value(); - // Get document title/content - uploadContent = fileDesc.getContent(); - uploadContentCRC = utils.crc32(uploadContent); - uploadTitle = fileDesc.title; - uploadTitleCRC = utils.crc32(uploadTitle); - locationUp(callback); - } + // 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; + }); + }); - // Entry point for up synchronization (upload changes) - var uploadCycle = false; - function syncUp(callback) { - if(uploadCycle === true) { - // New upload cycle - uploadCycle = false; - uploadFileList = _.values(fileSystem); - fileUp(callback); - } - else { - callback(); - } - } + // Force the synchronization + synchronizer.forceSync = function() { + lastSync = 0; + synchronizer.sync(); + }; - // Recursive function to download changes from multiple providers - var providerList = []; - function providerDown(callback) { - if(providerList.length === 0) { - callback(); - return; - } - var provider = providerList.pop(); - - // Check that provider has files to sync - if(!fileMgr.hasSync(provider)) { - providerDown(callback); - return; - } - - // Perform provider's syncDown - provider.syncDown(function(error) { - if(error) { - callback(error); - return; - } - providerDown(callback); - }); - } - - // Entry point for down synchronization (download changes) - function syncDown(callback) { - providerList = _.values(providerMap); - providerDown(callback); - }; - - // Main entry point for synchronization - var syncRunning = false; - var lastSync = 0; - synchronizer.sync = function() { - // If sync is already running or timeout is not reached or offline - if (syncRunning || lastSync + SYNC_PERIOD > utils.currentTime || core.isOffline) { - return; - } - syncRunning = true; - extensionMgr.onSyncRunning(true); - uploadCycle = true; - lastSync = utils.currentTime; - - function isError(error) { - if(error !== undefined) { - syncRunning = false; - extensionMgr.onSyncRunning(false); - return true; - } - return false; - } + // Recursive function to upload a single file on multiple locations + var uploadSyncAttributesList = []; + var uploadContent = undefined; + var uploadContentCRC = undefined; + var uploadTitle = undefined; + var uploadTitleCRC = undefined; + function locationUp(callback) { - syncDown(function(error) { - if(isError(error)) { - return; - } - syncUp(function(error) { - if(isError(error)) { - return; - } - syncRunning = false; - extensionMgr.onSyncRunning(false); - extensionMgr.onSyncSuccess(); - }); - }); - }; - // Run sync function periodically - if(viewerMode === false) { - core.addPeriodicCallback(synchronizer.sync); - } - - // Initialize the export dialog - function initExportDialog(provider) { - - // Reset fields - utils.resetModalInputs(); - - // Load preferences - var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"]; - if(serializedPreferences) { - var exportPreferences = JSON.parse(serializedPreferences); - _.each(provider.exportPreferencesInputIds, function(inputId) { - utils.setInputValue("#input-sync-export-" + inputId, exportPreferences[inputId]); - }); - } - - // Open dialog box - $("#modal-upload-" + provider.providerId).modal(); - } - - core.onReady(function() { - // Init each provider - _.each(providerMap, function(provider) { - // Provider's import button - $(".action-sync-import-" + provider.providerId).click(function(event) { - provider.importFiles(event); - }); - // Provider's export action - $(".action-sync-export-dialog-" + provider.providerId).click(function() { - initExportDialog(provider); - }); - $(".action-sync-export-" + provider.providerId).click(function(event) { + // No more synchronized location for this document + if(uploadSyncAttributesList.length === 0) { + fileUp(callback); + return; + } - // Perform the provider's export - var fileDesc = fileMgr.getCurrentFile(); - provider.exportFile(event, fileDesc.title, fileDesc.getContent(), function(error, syncAttributes) { - if(error) { - return; - } - fileMgr.addSync(fileDesc, syncAttributes); - }); - - // Store input values as preferences for next time we open the export dialog - var exportPreferences = {}; - _.each(provider.exportPreferencesInputIds, function(inputId) { - exportPreferences[inputId] = $("#input-sync-export-" + inputId).val(); - }); - localStorage[provider.providerId + ".exportPreferences"] = JSON.stringify(exportPreferences); - }); - // Provider's manual export button - $(".action-sync-manual-" + provider.providerId).click(function(event) { - var fileDesc = fileMgr.getCurrentFile(); - provider.exportManual(event, fileDesc.title, fileDesc.getContent(), function(error, syncAttributes) { - if(error) { - return; - } - fileMgr.addSync(fileDesc, syncAttributes); - }); - }); - }); - }); + // Dequeue a synchronized location + var syncAttributes = uploadSyncAttributesList.pop(); + // Use the specified provider to perform the upload + syncAttributes.provider.syncUp(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, function(error, uploadFlag) { + if(uploadFlag === true) { + // If uploadFlag is true, request another upload cycle + uploadCycle = true; + } + if(error) { + callback(error); + return; + } + if(uploadFlag) { + // Update syncAttributes in localStorage + utils.storeAttributes(syncAttributes); + } + locationUp(callback); + }); + } - extensionMgr.onSynchronizerCreated(synchronizer); - return synchronizer; + // Recursive function to upload multiple files + var uploadFileList = []; + function fileUp(callback) { + + // No more fileDesc to synchronize + if(uploadFileList.length === 0) { + syncUp(callback); + return; + } + + // Dequeue a fileDesc to synchronize + var fileDesc = uploadFileList.pop(); + uploadSyncAttributesList = _.values(fileDesc.syncLocations); + if(uploadSyncAttributesList.length === 0) { + fileUp(callback); + return; + } + + // Get document title/content + uploadContent = fileDesc.content; + uploadContentCRC = utils.crc32(uploadContent); + uploadTitle = fileDesc.title; + uploadTitleCRC = utils.crc32(uploadTitle); + locationUp(callback); + } + + // Entry point for up synchronization (upload changes) + var uploadCycle = false; + function syncUp(callback) { + if(uploadCycle === true) { + // New upload cycle + uploadCycle = false; + uploadFileList = _.values(fileSystem); + fileUp(callback); + } + else { + callback(); + } + } + + // Recursive function to download changes from multiple providers + var providerList = []; + function providerDown(callback) { + if(providerList.length === 0) { + callback(); + return; + } + var provider = providerList.pop(); + + // Check that provider has files to sync + if(!fileMgr.hasSync(provider)) { + providerDown(callback); + return; + } + + // Perform provider's syncDown + provider.syncDown(function(error) { + if(error) { + callback(error); + return; + } + providerDown(callback); + }); + } + + // Entry point for down synchronization (download changes) + function syncDown(callback) { + providerList = _.values(providerMap); + providerDown(callback); + } + ; + + // Main entry point for synchronization + var syncRunning = false; + var lastSync = 0; + synchronizer.sync = function() { + // If sync is already running or timeout is not reached or offline + if(syncRunning || lastSync + SYNC_PERIOD > utils.currentTime || core.isOffline) { + return; + } + syncRunning = true; + extensionMgr.onSyncRunning(true); + uploadCycle = true; + lastSync = utils.currentTime; + + function isError(error) { + if(error !== undefined) { + syncRunning = false; + extensionMgr.onSyncRunning(false); + return true; + } + return false; + } + + syncDown(function(error) { + if(isError(error)) { + return; + } + syncUp(function(error) { + if(isError(error)) { + return; + } + syncRunning = false; + extensionMgr.onSyncRunning(false); + extensionMgr.onSyncSuccess(); + }); + }); + }; + // Run sync function periodically + if(viewerMode === false) { + core.addPeriodicCallback(synchronizer.sync); + } + + // Initialize the export dialog + function initExportDialog(provider) { + + // Reset fields + utils.resetModalInputs(); + + // Load preferences + var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"]; + if(serializedPreferences) { + var exportPreferences = JSON.parse(serializedPreferences); + _.each(provider.exportPreferencesInputIds, function(inputId) { + utils.setInputValue("#input-sync-export-" + inputId, exportPreferences[inputId]); + }); + } + + // Open dialog box + $("#modal-upload-" + provider.providerId).modal(); + } + + core.onReady(function() { + // Init each provider + _.each(providerMap, function(provider) { + // Provider's import button + $(".action-sync-import-" + provider.providerId).click(function(event) { + provider.importFiles(event); + }); + // Provider's export action + $(".action-sync-export-dialog-" + provider.providerId).click(function() { + initExportDialog(provider); + }); + $(".action-sync-export-" + provider.providerId).click(function(event) { + + // Perform the provider's export + var fileDesc = fileMgr.getCurrentFile(); + provider.exportFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) { + if(error) { + return; + } + fileMgr.addSync(fileDesc, syncAttributes); + }); + + // Store input values as preferences for next time we open the + // export dialog + var exportPreferences = {}; + _.each(provider.exportPreferencesInputIds, function(inputId) { + exportPreferences[inputId] = $("#input-sync-export-" + inputId).val(); + }); + localStorage[provider.providerId + ".exportPreferences"] = JSON.stringify(exportPreferences); + }); + // Provider's manual export button + $(".action-sync-manual-" + provider.providerId).click(function(event) { + var fileDesc = fileMgr.getCurrentFile(); + provider.exportManual(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) { + if(error) { + return; + } + fileMgr.addSync(fileDesc, syncAttributes); + }); + }); + }); + }); + + extensionMgr.onSynchronizerCreated(synchronizer); + return synchronizer; }); diff --git a/js/tumblr-helper.js b/js/tumblr-helper.js index 647b4cef..0c9e6fb8 100644 --- a/js/tumblr-helper.js +++ b/js/tumblr-helper.js @@ -6,164 +6,163 @@ define([ "async-runner" ], function($, core, utils, extensionMgr, asyncRunner) { - var oauthParams = undefined; + var oauthParams = undefined; - var tumblrHelper = {}; + var tumblrHelper = {}; - // Only used to check the offline status - function connect(task) { - task.onRun(function() { - if(core.isOffline === true) { - task.error(new Error("Operation not available in offline mode.|stopPublish")); - return; - } - task.chain(); - }); - } + // Only used to check the offline status + function connect(task) { + task.onRun(function() { + if(core.isOffline === true) { + task.error(new Error("Operation not available in offline mode.|stopPublish")); + return; + } + task.chain(); + }); + } - // Try to authenticate with OAuth - function authenticate(task) { - var authWindow = undefined; - var intervalId = undefined; - task.onRun(function() { - if (oauthParams !== undefined) { - task.chain(); - return; - } - var serializedOauthParams = localStorage["tumblrOauthParams"]; - if(serializedOauthParams !== undefined) { - oauthParams = JSON.parse(serializedOauthParams); - task.chain(); - return; - } - extensionMgr.onMessage("Please make sure the Tumblr authorization popup is not blocked by your browser."); - var errorMsg = "Failed to retrieve a token from Tumblr."; - // We add time for user to enter his credentials - task.timeout = ASYNC_TASK_LONG_TIMEOUT; - var oauth_object = undefined; - function getOauthToken() { - $.getJSON(TUMBLR_PROXY_URL + "request_token", function(data) { - if(data.oauth_token !== undefined) { - oauth_object = data; - task.chain(getVerifier); - } - else { - task.error(new Error(errorMsg)); - } - }); - } - function getVerifier() { - localStorage.removeItem("tumblrVerifier"); - authWindow = utils.popupWindow( - 'tumblr-oauth-client.html?oauth_token=' + oauth_object.oauth_token, - 'stackedit-tumblr-oauth', 800, 600); - authWindow.focus(); - intervalId = setInterval(function() { - if(authWindow.closed === true) { - clearInterval(intervalId); - authWindow = undefined; - intervalId = undefined; - oauth_object.oauth_verifier = localStorage["tumblrVerifier"]; - if(oauth_object.oauth_verifier === undefined) { - task.error(new Error(errorMsg)); - return; - } - localStorage.removeItem("tumblrVerifier"); - task.chain(getAccessToken); - } - }, 500); - } - function getAccessToken() { - $.getJSON(TUMBLR_PROXY_URL + "access_token", oauth_object, function(data) { - if(data.access_token !== undefined && data.access_token_secret !== undefined) { - localStorage["tumblrOauthParams"] = JSON.stringify(data); - oauthParams = data; - task.chain(); - } - else { - task.error(new Error(errorMsg)); - } - }); - } - task.chain(getOauthToken); - }); - task.onError(function() { - if(intervalId !== undefined) { - clearInterval(intervalId); - } - if(authWindow !== undefined) { - authWindow.close(); - } - }); - } + // Try to authenticate with OAuth + function authenticate(task) { + var authWindow = undefined; + var intervalId = undefined; + task.onRun(function() { + if(oauthParams !== undefined) { + task.chain(); + return; + } + var serializedOauthParams = localStorage["tumblrOauthParams"]; + if(serializedOauthParams !== undefined) { + oauthParams = JSON.parse(serializedOauthParams); + task.chain(); + return; + } + extensionMgr.onMessage("Please make sure the Tumblr authorization popup is not blocked by your browser."); + var errorMsg = "Failed to retrieve a token from Tumblr."; + // We add time for user to enter his credentials + task.timeout = ASYNC_TASK_LONG_TIMEOUT; + var oauth_object = undefined; + function getOauthToken() { + $.getJSON(TUMBLR_PROXY_URL + "request_token", function(data) { + if(data.oauth_token !== undefined) { + oauth_object = data; + task.chain(getVerifier); + } + else { + task.error(new Error(errorMsg)); + } + }); + } + function getVerifier() { + localStorage.removeItem("tumblrVerifier"); + authWindow = utils.popupWindow('tumblr-oauth-client.html?oauth_token=' + oauth_object.oauth_token, 'stackedit-tumblr-oauth', 800, 600); + authWindow.focus(); + intervalId = setInterval(function() { + if(authWindow.closed === true) { + clearInterval(intervalId); + authWindow = undefined; + intervalId = undefined; + oauth_object.oauth_verifier = localStorage["tumblrVerifier"]; + if(oauth_object.oauth_verifier === undefined) { + task.error(new Error(errorMsg)); + return; + } + localStorage.removeItem("tumblrVerifier"); + task.chain(getAccessToken); + } + }, 500); + } + function getAccessToken() { + $.getJSON(TUMBLR_PROXY_URL + "access_token", oauth_object, function(data) { + if(data.access_token !== undefined && data.access_token_secret !== undefined) { + localStorage["tumblrOauthParams"] = JSON.stringify(data); + oauthParams = data; + task.chain(); + } + else { + task.error(new Error(errorMsg)); + } + }); + } + task.chain(getOauthToken); + }); + task.onError(function() { + if(intervalId !== undefined) { + clearInterval(intervalId); + } + if(authWindow !== undefined) { + authWindow.close(); + } + }); + } - tumblrHelper.upload = function(blogHostname, postId, tags, format, title, content, callback) { - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - var data = $.extend({ - blog_hostname: blogHostname, - post_id: postId, - tags: tags, - format: format, - title: title, - content: content - }, oauthParams); - $.ajax({ - url : TUMBLR_PROXY_URL + "post", - data: data, - type: "POST", - dataType : "json", - timeout : AJAX_TIMEOUT - }).done(function(post, textStatus, jqXHR) { - postId = post.id; - task.chain(); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - // Handle error - if(error.code === 404 && postId !== undefined) { - error = 'Post ' + postId + ' not found on Tumblr.|removePublish'; - } - handleError(error, task); - }); - }); - task.onSuccess(function() { - callback(undefined, postId); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - - function handleError(error, task) { - var errorMsg = undefined; - if (error) { - logger.error(error); - // Try to analyze the error - if (typeof error === "string") { - errorMsg = error; - } - else { - errorMsg = "Could not publish on Tumblr."; - if (error.code === 401 || error.code === 403) { - oauthParams = undefined; - localStorage.removeItem("tumblrOauthParams"); - errorMsg = "Access to Tumblr account is not authorized."; - task.retry(new Error(errorMsg), 1); - return; - } else if (error.code <= 0) { - core.setOffline(); - errorMsg = "|stopPublish"; - } - } - } - task.error(new Error(errorMsg)); - } + tumblrHelper.upload = function(blogHostname, postId, tags, format, title, content, callback) { + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + var data = $.extend({ + blog_hostname: blogHostname, + post_id: postId, + tags: tags, + format: format, + title: title, + content: content + }, oauthParams); + $.ajax({ + url: TUMBLR_PROXY_URL + "post", + data: data, + type: "POST", + dataType: "json", + timeout: AJAX_TIMEOUT + }).done(function(post, textStatus, jqXHR) { + postId = post.id; + task.chain(); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + // Handle error + if(error.code === 404 && postId !== undefined) { + error = 'Post ' + postId + ' not found on Tumblr.|removePublish'; + } + handleError(error, task); + }); + }); + task.onSuccess(function() { + callback(undefined, postId); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - return tumblrHelper; + function handleError(error, task) { + var errorMsg = undefined; + if(error) { + logger.error(error); + // Try to analyze the error + if(typeof error === "string") { + errorMsg = error; + } + else { + errorMsg = "Could not publish on Tumblr."; + if(error.code === 401 || error.code === 403) { + oauthParams = undefined; + localStorage.removeItem("tumblrOauthParams"); + errorMsg = "Access to Tumblr account is not authorized."; + task.retry(new Error(errorMsg), 1); + return; + } + else if(error.code <= 0) { + core.setOffline(); + errorMsg = "|stopPublish"; + } + } + } + task.error(new Error(errorMsg)); + } + + return tumblrHelper; }); diff --git a/js/tumblr-provider.js b/js/tumblr-provider.js index 39feba95..9b0977d1 100644 --- a/js/tumblr-provider.js +++ b/js/tumblr-provider.js @@ -2,48 +2,38 @@ define([ "utils", "tumblr-helper" ], function(utils, tumblrHelper) { - - var PROVIDER_TUMBLR = "tumblr"; - - var tumblrProvider = { - providerId: PROVIDER_TUMBLR, - providerName: "Tumblr", - publishPreferencesInputIds: ["tumblr-hostname"] - }; - - tumblrProvider.publish = function(publishAttributes, title, content, callback) { - tumblrHelper.upload( - publishAttributes.blogHostname, - publishAttributes.postId, - publishAttributes.tags, - publishAttributes.format == "markdown" ? "markdown" : "html", - title, - content, - function(error, postId) { - if(error) { - callback(error); - return; - } - publishAttributes.postId = postId; - callback(); - } - ); - }; - tumblrProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - publishAttributes.blogHostname = utils - .getInputTextValue( - "#input-publish-tumblr-hostname", - event, - /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/); - publishAttributes.postId = utils.getInputTextValue("#input-publish-postid"); - publishAttributes.tags = utils.getInputTextValue("#input-publish-tags"); - if(event.isPropagationStopped()) { - return undefined; - } - return publishAttributes; - }; + var PROVIDER_TUMBLR = "tumblr"; - return tumblrProvider; + var tumblrProvider = { + providerId: PROVIDER_TUMBLR, + providerName: "Tumblr", + publishPreferencesInputIds: [ + "tumblr-hostname" + ] + }; + + tumblrProvider.publish = function(publishAttributes, title, content, callback) { + tumblrHelper.upload(publishAttributes.blogHostname, publishAttributes.postId, publishAttributes.tags, publishAttributes.format == "markdown" ? "markdown" : "html", title, content, function(error, postId) { + if(error) { + callback(error); + return; + } + publishAttributes.postId = postId; + callback(); + }); + }; + + tumblrProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + publishAttributes.blogHostname = utils.getInputTextValue("#input-publish-tumblr-hostname", event, /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/); + publishAttributes.postId = utils.getInputTextValue("#input-publish-postid"); + publishAttributes.tags = utils.getInputTextValue("#input-publish-tags"); + if(event.isPropagationStopped()) { + return undefined; + } + return publishAttributes; + }; + + return tumblrProvider; }); \ No newline at end of file diff --git a/js/utils.js b/js/utils.js index d12072d2..d7acfa14 100644 --- a/js/utils.js +++ b/js/utils.js @@ -3,311 +3,518 @@ define([ "underscore", "lib/FileSaver" ], function($, _) { - - var utils = {}; - // Return a parameter from the URL - utils.getURLParameter = function(name) { - var regex = new RegExp(name + "=(.+?)(&|$)"); - try { - return decodeURIComponent(regex.exec(location.search)[1]); - } catch (e) { - return undefined; - } - }; - - // Transform a selector into a jQuery object - function jqElt(element) { - if(_.isString(element)) { - return $(element); - } - return element; - } - - // For input control - function inputError(element, event) { - if(event !== undefined) { - element.stop(true, true).addClass("error").delay(1000).switchClass("error"); - event.stopPropagation(); - } - } - - // Return input value - utils.getInputValue = function(element) { - element = jqElt(element); - return element.val(); - }; - - // Set input value - utils.setInputValue = function(element, value) { - element = jqElt(element); - element.val(value); - }; - - // Return input text value - utils.getInputTextValue = function(element, event, validationRegex) { - element = jqElt(element); - var value = element.val(); - if (value === undefined) { - inputError(element, event); - return undefined; - } - // trim - value = utils.trim(value); - if((value.length === 0) - || (validationRegex !== undefined && !value.match(validationRegex))) { - inputError(element, event); - return undefined; - } - return value; - }; - - // Return input integer value - utils.getInputIntValue = function(element, event, min, max) { - element = jqElt(element); - var value = utils.getInputTextValue(element, event); - if(value === undefined) { - return undefined; - } - value = parseInt(value); - if((value === NaN) - || (min !== undefined && value < min) - || (max !== undefined && value > max)) { - inputError(element, event); - return undefined; - } - return value; - }; - - // Return checkbox boolean value - utils.getInputChecked = function(element) { - element = jqElt(element); - return element.prop("checked"); - }; - - // Set checkbox state - utils.setInputChecked = function(element, checked) { - element = jqElt(element); - element.prop("checked", checked); - }; - - // Get radio button value - utils.getInputRadio = function(name) { - return $("input:radio[name=" + name + "]:checked").prop("value"); - }; - - // Set radio button value - utils.setInputRadio = function(name, value) { - $("input:radio[name=" + name + "][value=" + value + "]").prop("checked", true); - }; - - // Reset input control in all modals - utils.resetModalInputs = function() { - $(".modal input[type=text]:not([disabled]), .modal input[type=password]").val(""); - }; - - // Basic trim function - utils.trim = function(str) { - return $.trim(str); - }; - - // Slug function - utils.slugify = function(text) { - return text.toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with - - .replace(/[^\w\-]+/g, '') // Remove all non-word chars - .replace(/\-\-+/g, '-') // Replace multiple - with single - - .replace(/^-+/, '') // Trim - from start of text - .replace(/-+$/, ''); // Trim - from end of text - }; - - // Check an URL - utils.checkUrl = function(url, addSlash) { - if(!url) { - return url; - } - if(url.indexOf("http") !== 0) { - url = "http://" + url; - } - if(addSlash && url.indexOf("/", url.length - 1) === -1) { - url += "/"; - } - return url; - }; - - // Base64 conversion - utils.encodeBase64 = function(str) { - if (str.length === 0) { - return ""; - } - - // UTF-8 to byte array - var bytes = [], offset = 0, length, char; + var utils = {}; - str = encodeURI(str); - length = str.length; + // Return a parameter from the URL + utils.getURLParameter = function(name) { + var regex = new RegExp(name + "=(.+?)(&|$)"); + try { + return decodeURIComponent(regex.exec(location.search)[1]); + } + catch (e) { + return undefined; + } + }; - while (offset < length) { - char = str[offset]; - offset += 1; + // Transform a selector into a jQuery object + function jqElt(element) { + if(_.isString(element)) { + return $(element); + } + return element; + } - if ('%' !== char) { - bytes.push(char.charCodeAt(0)); - } else { - char = str[offset] + str[offset + 1]; - bytes.push(parseInt(char, 16)); - offset += 2; - } - } + // For input control + function inputError(element, event) { + if(event !== undefined) { + element.stop(true, true).addClass("error").delay(1000).switchClass("error"); + event.stopPropagation(); + } + } - // byte array to base64 - var padchar = '='; - var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // Return input value + utils.getInputValue = function(element) { + element = jqElt(element); + return element.val(); + }; - var i, b10; - var x = []; + // Set input value + utils.setInputValue = function(element, value) { + element = jqElt(element); + element.val(value); + }; - var imax = bytes.length - bytes.length % 3; + // Return input text value + utils.getInputTextValue = function(element, event, validationRegex) { + element = jqElt(element); + var value = element.val(); + if(value === undefined) { + inputError(element, event); + return undefined; + } + // trim + value = utils.trim(value); + if((value.length === 0) || (validationRegex !== undefined && !value.match(validationRegex))) { + inputError(element, event); + return undefined; + } + return value; + }; - for (i = 0; i < imax; i += 3) { - b10 = (bytes[i] << 16) | (bytes[i+1] << 8) | bytes[i+2]; - x.push(alpha.charAt(b10 >> 18)); - x.push(alpha.charAt((b10 >> 12) & 0x3F)); - x.push(alpha.charAt((b10 >> 6) & 0x3f)); - x.push(alpha.charAt(b10 & 0x3f)); - } - switch (bytes.length - imax) { - case 1: - b10 = bytes[i] << 16; - x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + - padchar + padchar); - break; - case 2: - b10 = (bytes[i] << 16) | (bytes[i+1] << 8); - x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + - alpha.charAt((b10 >> 6) & 0x3f) + padchar); - break; - } - return x.join(''); - }; - - // CRC32 algorithm - var mHash = [ 0, 1996959894, 3993919788, 2567524794, 124634137, - 1886057615, 3915621685, 2657392035, 249268274, 2044508324, - 3772115230, 2547177864, 162941995, 2125561021, 3887607047, - 2428444049, 498536548, 1789927666, 4089016648, 2227061214, - 450548861, 1843258603, 4107580753, 2211677639, 325883990, - 1684777152, 4251122042, 2321926636, 335633487, 1661365465, - 4195302755, 2366115317, 997073096, 1281953886, 3579855332, - 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, - 901097722, 1119000684, 3686517206, 2898065728, 853044451, - 1172266101, 3705015759, 2882616665, 651767980, 1373503546, - 3369554304, 3218104598, 565507253, 1454621731, 3485111705, - 3099436303, 671266974, 1594198024, 3322730930, 2970347812, - 795835527, 1483230225, 3244367275, 3060149565, 1994146192, - 31158534, 2563907772, 4023717930, 1907459465, 112637215, - 2680153253, 3904427059, 2013776290, 251722036, 2517215374, - 3775830040, 2137656763, 141376813, 2439277719, 3865271297, - 1802195444, 476864866, 2238001368, 4066508878, 1812370925, - 453092731, 2181625025, 4111451223, 1706088902, 314042704, - 2344532202, 4240017532, 1658658271, 366619977, 2362670323, - 4224994405, 1303535960, 984961486, 2747007092, 3569037538, - 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, - 879679996, 2909243462, 3663771856, 1141124467, 855842277, - 2852801631, 3708648649, 1342533948, 654459306, 3188396048, - 3373015174, 1466479909, 544179635, 3110523913, 3462522015, - 1591671054, 702138776, 2966460450, 3352799412, 1504918807, - 783551873, 3082640443, 3233442989, 3988292384, 2596254646, - 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, - 3814918930, 2489596804, 225274430, 2053790376, 3826175755, - 2466906013, 167816743, 2097651377, 4027552580, 2265490386, - 503444072, 1762050814, 4150417245, 2154129355, 426522225, - 1852507879, 4275313526, 2312317920, 282753626, 1742555852, - 4189708143, 2394877945, 397917763, 1622183637, 3604390888, - 2714866558, 953729732, 1340076626, 3518719985, 2797360999, - 1068828381, 1219638859, 3624741850, 2936675148, 906185462, - 1090812512, 3747672003, 2825379669, 829329135, 1181335161, - 3412177804, 3160834842, 628085408, 1382605366, 3423369109, - 3138078467, 570562233, 1426400815, 3317316542, 2998733608, - 733239954, 1555261956, 3268935591, 3050360625, 752459403, - 1541320221, 2607071920, 3965973030, 1969922972, 40735498, - 2617837225, 3943577151, 1913087877, 83908371, 2512341634, - 3803740692, 2075208622, 213261112, 2463272603, 3855990285, - 2094854071, 198958881, 2262029012, 4057260610, 1759359992, - 534414190, 2176718541, 4139329115, 1873836001, 414664567, - 2282248934, 4279200368, 1711684554, 285281116, 2405801727, - 4167216745, 1634467795, 376229701, 2685067896, 3608007406, - 1308918612, 956543938, 2808555105, 3495958263, 1231636301, - 1047427035, 2932959818, 3654703836, 1088359270, 936918000, - 2847714899, 3736837829, 1202900863, 817233897, 3183342108, - 3401237130, 1404277552, 615818150, 3134207493, 3453421203, - 1423857449, 601450431, 3009837614, 3294710456, 1567103746, - 711928724, 3020668471, 3272380065, 1510334235, 755167117 ]; - utils.crc32 = function(str) { - var n = 0, crc = -1; - for ( var i = 0; i < str.length; i++) { - n = (crc ^ str.charCodeAt(i)) & 0xFF; - crc = (crc >>> 8) ^ mHash[n]; - } - crc = crc ^ (-1); - if (crc < 0) { - crc = 0xFFFFFFFF + crc + 1; - } - return crc.toString(16); - }; - - // Create an centered popup window - utils.popupWindow = function(url, title, width, height) { - var left = (screen.width / 2) - (width / 2); - var top = (screen.height / 2) - (height / 2); - return window.open( - url, - title, - 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=' - + width - + ', height=' - + height - + ', top=' - + top - + ', left=' - + left); - }; - - // Export data on disk - utils.saveAs = function(content, filename) { - if(saveAs !== undefined) { - var blob = new Blob([content], {type: "text/plain;charset=utf-8"}); - saveAs(blob, filename); - } - else { - var uriContent = "data:application/octet-stream;base64," - + utils.encodeBase64(content); - window.open(uriContent, 'file'); - } - }; + // Return input integer value + utils.getInputIntValue = function(element, event, min, max) { + element = jqElt(element); + var value = utils.getInputTextValue(element, event); + if(value === undefined) { + return undefined; + } + value = parseInt(value); + if((value === NaN) || (min !== undefined && value < min) || (max !== undefined && value > max)) { + inputError(element, event); + return undefined; + } + return value; + }; - // Generates a random string - utils.randomString = function() { - return _.random(4294967296).toString(36); - }; - - // Time shared by others modules - utils.updateCurrentTime = function() { - utils.currentTime = new Date().getTime(); - }; - utils.updateCurrentTime(); - - - // Serialize sync/publish attributes and store it in the fileStorage - utils.storeAttributes = function(attributes) { - var storeIndex = attributes.syncIndex || attributes.publishIndex; - // Don't store sync/publish index - attributes = _.omit(attributes, "syncIndex", "publishIndex"); - // Store providerId instead of provider - attributes.provider = attributes.provider.providerId; - localStorage[storeIndex] = JSON.stringify(attributes); - }; + // Return checkbox boolean value + utils.getInputChecked = function(element) { + element = jqElt(element); + return element.prop("checked"); + }; - return utils; + // Set checkbox state + utils.setInputChecked = function(element, checked) { + element = jqElt(element); + element.prop("checked", checked); + }; + // Get radio button value + utils.getInputRadio = function(name) { + return $("input:radio[name=" + name + "]:checked").prop("value"); + }; + + // Set radio button value + utils.setInputRadio = function(name, value) { + $("input:radio[name=" + name + "][value=" + value + "]").prop("checked", true); + }; + + // Reset input control in all modals + utils.resetModalInputs = function() { + $(".modal input[type=text]:not([disabled]), .modal input[type=password]").val(""); + }; + + // Basic trim function + utils.trim = function(str) { + return $.trim(str); + }; + + // Slug function + utils.slugify = function(text) { + return text.toLowerCase().replace(/\s+/g, '-') // Replace spaces with - + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text + }; + + // Check an URL + utils.checkUrl = function(url, addSlash) { + if(!url) { + return url; + } + if(url.indexOf("http") !== 0) { + url = "http://" + url; + } + if(addSlash && url.indexOf("/", url.length - 1) === -1) { + url += "/"; + } + return url; + }; + + // Create an centered popup window + utils.popupWindow = function(url, title, width, height) { + var left = (screen.width / 2) - (width / 2); + var top = (screen.height / 2) - (height / 2); + return window.open(url, title, [ + 'toolbar=no, ', + 'location=no, ', + 'directories=no, ', + 'status=no, ', + 'menubar=no, ', + 'scrollbars=no, ', + 'resizable=no, ', + 'copyhistory=no, ', + 'width=' + width + ', ', + 'height=' + height + ', ', + 'top=' + top + ', ', + 'left=' + left + ].join("")); + }; + + // Export data on disk + utils.saveAs = function(content, filename) { + if(saveAs !== undefined) { + var blob = new Blob([ + content + ], { + type: "text/plain;charset=utf-8" + }); + saveAs(blob, filename); + } + else { + var uriContent = "data:application/octet-stream;base64," + utils.encodeBase64(content); + window.open(uriContent, 'file'); + } + }; + + // Generates a random string + utils.randomString = function() { + return _.random(4294967296).toString(36); + }; + + // Time shared by others modules + utils.updateCurrentTime = function() { + utils.currentTime = new Date().getTime(); + }; + utils.updateCurrentTime(); + + // Serialize sync/publish attributes and store it in the fileStorage + utils.storeAttributes = function(attributes) { + var storeIndex = attributes.syncIndex || attributes.publishIndex; + // Don't store sync/publish index + attributes = _.omit(attributes, "syncIndex", "publishIndex"); + // Store providerId instead of provider + attributes.provider = attributes.provider.providerId; + localStorage[storeIndex] = JSON.stringify(attributes); + }; + + // Base64 conversion + utils.encodeBase64 = function(str) { + if(str.length === 0) { + return ""; + } + + // UTF-8 to byte array + var bytes = [], offset = 0, length, char; + + str = encodeURI(str); + length = str.length; + + while (offset < length) { + char = str[offset]; + offset += 1; + + if('%' !== char) { + bytes.push(char.charCodeAt(0)); + } + else { + char = str[offset] + str[offset + 1]; + bytes.push(parseInt(char, 16)); + offset += 2; + } + } + + // byte array to base64 + var padchar = '='; + var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + var i, b10; + var x = []; + + var imax = bytes.length - bytes.length % 3; + + for (i = 0; i < imax; i += 3) { + b10 = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; + x.push(alpha.charAt(b10 >> 18)); + x.push(alpha.charAt((b10 >> 12) & 0x3F)); + x.push(alpha.charAt((b10 >> 6) & 0x3f)); + x.push(alpha.charAt(b10 & 0x3f)); + } + switch (bytes.length - imax) { + case 1: + b10 = bytes[i] << 16; + x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + padchar + padchar); + break; + case 2: + b10 = (bytes[i] << 16) | (bytes[i + 1] << 8); + x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + alpha.charAt((b10 >> 6) & 0x3f) + padchar); + break; + } + return x.join(''); + }; + + // CRC32 algorithm + var mHash = [ + 0, + 1996959894, + 3993919788, + 2567524794, + 124634137, + 1886057615, + 3915621685, + 2657392035, + 249268274, + 2044508324, + 3772115230, + 2547177864, + 162941995, + 2125561021, + 3887607047, + 2428444049, + 498536548, + 1789927666, + 4089016648, + 2227061214, + 450548861, + 1843258603, + 4107580753, + 2211677639, + 325883990, + 1684777152, + 4251122042, + 2321926636, + 335633487, + 1661365465, + 4195302755, + 2366115317, + 997073096, + 1281953886, + 3579855332, + 2724688242, + 1006888145, + 1258607687, + 3524101629, + 2768942443, + 901097722, + 1119000684, + 3686517206, + 2898065728, + 853044451, + 1172266101, + 3705015759, + 2882616665, + 651767980, + 1373503546, + 3369554304, + 3218104598, + 565507253, + 1454621731, + 3485111705, + 3099436303, + 671266974, + 1594198024, + 3322730930, + 2970347812, + 795835527, + 1483230225, + 3244367275, + 3060149565, + 1994146192, + 31158534, + 2563907772, + 4023717930, + 1907459465, + 112637215, + 2680153253, + 3904427059, + 2013776290, + 251722036, + 2517215374, + 3775830040, + 2137656763, + 141376813, + 2439277719, + 3865271297, + 1802195444, + 476864866, + 2238001368, + 4066508878, + 1812370925, + 453092731, + 2181625025, + 4111451223, + 1706088902, + 314042704, + 2344532202, + 4240017532, + 1658658271, + 366619977, + 2362670323, + 4224994405, + 1303535960, + 984961486, + 2747007092, + 3569037538, + 1256170817, + 1037604311, + 2765210733, + 3554079995, + 1131014506, + 879679996, + 2909243462, + 3663771856, + 1141124467, + 855842277, + 2852801631, + 3708648649, + 1342533948, + 654459306, + 3188396048, + 3373015174, + 1466479909, + 544179635, + 3110523913, + 3462522015, + 1591671054, + 702138776, + 2966460450, + 3352799412, + 1504918807, + 783551873, + 3082640443, + 3233442989, + 3988292384, + 2596254646, + 62317068, + 1957810842, + 3939845945, + 2647816111, + 81470997, + 1943803523, + 3814918930, + 2489596804, + 225274430, + 2053790376, + 3826175755, + 2466906013, + 167816743, + 2097651377, + 4027552580, + 2265490386, + 503444072, + 1762050814, + 4150417245, + 2154129355, + 426522225, + 1852507879, + 4275313526, + 2312317920, + 282753626, + 1742555852, + 4189708143, + 2394877945, + 397917763, + 1622183637, + 3604390888, + 2714866558, + 953729732, + 1340076626, + 3518719985, + 2797360999, + 1068828381, + 1219638859, + 3624741850, + 2936675148, + 906185462, + 1090812512, + 3747672003, + 2825379669, + 829329135, + 1181335161, + 3412177804, + 3160834842, + 628085408, + 1382605366, + 3423369109, + 3138078467, + 570562233, + 1426400815, + 3317316542, + 2998733608, + 733239954, + 1555261956, + 3268935591, + 3050360625, + 752459403, + 1541320221, + 2607071920, + 3965973030, + 1969922972, + 40735498, + 2617837225, + 3943577151, + 1913087877, + 83908371, + 2512341634, + 3803740692, + 2075208622, + 213261112, + 2463272603, + 3855990285, + 2094854071, + 198958881, + 2262029012, + 4057260610, + 1759359992, + 534414190, + 2176718541, + 4139329115, + 1873836001, + 414664567, + 2282248934, + 4279200368, + 1711684554, + 285281116, + 2405801727, + 4167216745, + 1634467795, + 376229701, + 2685067896, + 3608007406, + 1308918612, + 956543938, + 2808555105, + 3495958263, + 1231636301, + 1047427035, + 2932959818, + 3654703836, + 1088359270, + 936918000, + 2847714899, + 3736837829, + 1202900863, + 817233897, + 3183342108, + 3401237130, + 1404277552, + 615818150, + 3134207493, + 3453421203, + 1423857449, + 601450431, + 3009837614, + 3294710456, + 1567103746, + 711928724, + 3020668471, + 3272380065, + 1510334235, + 755167117 + ]; + utils.crc32 = function(str) { + var n = 0, crc = -1; + for ( var i = 0; i < str.length; i++) { + n = (crc ^ str.charCodeAt(i)) & 0xFF; + crc = (crc >>> 8) ^ mHash[n]; + } + crc = crc ^ (-1); + if(crc < 0) { + crc = 0xFFFFFFFF + crc + 1; + } + return crc.toString(16); + }; + + return utils; }); diff --git a/js/wordpress-helper.js b/js/wordpress-helper.js index 2a10eab7..f79c694e 100644 --- a/js/wordpress-helper.js +++ b/js/wordpress-helper.js @@ -6,161 +6,160 @@ define([ "async-runner" ], function($, core, utils, extensionMgr, asyncRunner) { - var token = undefined; + var token = undefined; - var wordpressHelper = {}; + var wordpressHelper = {}; - // Only used to check the offline status - function connect(task) { - task.onRun(function() { - if(core.isOffline === true) { - task.error(new Error("Operation not available in offline mode.|stopPublish")); - return; - } - task.chain(); - }); - } + // Only used to check the offline status + function connect(task) { + task.onRun(function() { + if(core.isOffline === true) { + task.error(new Error("Operation not available in offline mode.|stopPublish")); + return; + } + task.chain(); + }); + } - // Try to authenticate with OAuth - function authenticate(task) { - var authWindow = undefined; - var intervalId = undefined; - task.onRun(function() { - token = localStorage["wordpressToken"]; - if(token !== undefined) { - task.chain(); - return; - } - extensionMgr.onMessage("Please make sure the Wordpress authorization popup is not blocked by your browser."); - var errorMsg = "Failed to retrieve a token from Wordpress."; - // We add time for user to enter his credentials - task.timeout = ASYNC_TASK_LONG_TIMEOUT; - var code = undefined; - function getCode() { - localStorage.removeItem("wordpressCode"); - authWindow = utils.popupWindow( - 'wordpress-oauth-client.html?client_id=' + WORDPRESS_CLIENT_ID, - 'stackedit-wordpress-oauth', 960, 600); - authWindow.focus(); - intervalId = setInterval(function() { - if(authWindow.closed === true) { - clearInterval(intervalId); - authWindow = undefined; - intervalId = undefined; - code = localStorage["wordpressCode"]; - if(code === undefined) { - task.error(new Error(errorMsg)); - return; - } - localStorage.removeItem("wordpressCode"); - task.chain(getToken); - } - }, 500); - } - function getToken() { - $.getJSON(WORDPRESS_PROXY_URL + "authenticate/" + code, function(data) { - if(data.token !== undefined) { - token = data.token; - localStorage["wordpressToken"] = token; - task.chain(); - } - else { - task.error(new Error(errorMsg)); - } - }); - } - task.chain(getCode); - }); - task.onError(function() { - if(intervalId !== undefined) { - clearInterval(intervalId); - } - if(authWindow !== undefined) { - authWindow.close(); - } - }); - } + // Try to authenticate with OAuth + function authenticate(task) { + var authWindow = undefined; + var intervalId = undefined; + task.onRun(function() { + token = localStorage["wordpressToken"]; + if(token !== undefined) { + task.chain(); + return; + } + extensionMgr.onMessage("Please make sure the Wordpress authorization popup is not blocked by your browser."); + var errorMsg = "Failed to retrieve a token from Wordpress."; + // We add time for user to enter his credentials + task.timeout = ASYNC_TASK_LONG_TIMEOUT; + var code = undefined; + function getCode() { + localStorage.removeItem("wordpressCode"); + authWindow = utils.popupWindow('wordpress-oauth-client.html?client_id=' + WORDPRESS_CLIENT_ID, 'stackedit-wordpress-oauth', 960, 600); + authWindow.focus(); + intervalId = setInterval(function() { + if(authWindow.closed === true) { + clearInterval(intervalId); + authWindow = undefined; + intervalId = undefined; + code = localStorage["wordpressCode"]; + if(code === undefined) { + task.error(new Error(errorMsg)); + return; + } + localStorage.removeItem("wordpressCode"); + task.chain(getToken); + } + }, 500); + } + function getToken() { + $.getJSON(WORDPRESS_PROXY_URL + "authenticate/" + code, function(data) { + if(data.token !== undefined) { + token = data.token; + localStorage["wordpressToken"] = token; + task.chain(); + } + else { + task.error(new Error(errorMsg)); + } + }); + } + task.chain(getCode); + }); + task.onError(function() { + if(intervalId !== undefined) { + clearInterval(intervalId); + } + if(authWindow !== undefined) { + authWindow.close(); + } + }); + } - wordpressHelper.upload = function(site, postId, tags, title, content, callback) { - var task = asyncRunner.createTask(); - connect(task); - authenticate(task); - task.onRun(function() { - var url = WORDPRESS_PROXY_URL + "post"; - var data = { - token: token, - site: site, - postId: postId, - tags: tags, - title: title, - content: content - }; - $.ajax({ - url : url, - data: data, - type: "POST", - dataType : "json", - timeout : AJAX_TIMEOUT - }).done(function(response, textStatus, jqXHR) { - if(response.body.ID) { - postId = response.body.ID; - task.chain(); - return; - } - var error = { - code: response.code, - message: response.body.error - }; - // Handle error - if(error.code === 404) { - if(error.message == "unknown_blog") { - error = 'Site "' + site + '" not found on WordPress.|removePublish'; - } - else if(error.message == "unknown_post"){ - error = 'Post ' + postId + ' not found on WordPress.|removePublish'; - } - } - handleError(error, task); - }).fail(function(jqXHR) { - var error = { - code: jqXHR.status, - message: jqXHR.statusText - }; - handleError(error, task); - }); - }); - task.onSuccess(function() { - callback(undefined, postId); - }); - task.onError(function(error) { - callback(error); - }); - asyncRunner.addTask(task); - }; - - function handleError(error, task) { - var errorMsg = undefined; - if (error) { - logger.error(error); - // Try to analyze the error - if (typeof error === "string") { - errorMsg = error; - } - else { - errorMsg = "Could not publish on WordPress."; - if ((error.code === 400 && error.message == "invalid_token") || error.code === 401 || error.code === 403) { - localStorage.removeItem("wordpressToken"); - errorMsg = "Access to WordPress account is not authorized."; - task.retry(new Error(errorMsg), 1); - return; - } else if (error.code <= 0) { - core.setOffline(); - errorMsg = "|stopPublish"; - } - } - } - task.error(new Error(errorMsg)); - } + wordpressHelper.upload = function(site, postId, tags, title, content, callback) { + var task = asyncRunner.createTask(); + connect(task); + authenticate(task); + task.onRun(function() { + var url = WORDPRESS_PROXY_URL + "post"; + var data = { + token: token, + site: site, + postId: postId, + tags: tags, + title: title, + content: content + }; + $.ajax({ + url: url, + data: data, + type: "POST", + dataType: "json", + timeout: AJAX_TIMEOUT + }).done(function(response, textStatus, jqXHR) { + if(response.body.ID) { + postId = response.body.ID; + task.chain(); + return; + } + var error = { + code: response.code, + message: response.body.error + }; + // Handle error + if(error.code === 404) { + if(error.message == "unknown_blog") { + error = 'Site "' + site + '" not found on WordPress.|removePublish'; + } + else if(error.message == "unknown_post") { + error = 'Post ' + postId + ' not found on WordPress.|removePublish'; + } + } + handleError(error, task); + }).fail(function(jqXHR) { + var error = { + code: jqXHR.status, + message: jqXHR.statusText + }; + handleError(error, task); + }); + }); + task.onSuccess(function() { + callback(undefined, postId); + }); + task.onError(function(error) { + callback(error); + }); + asyncRunner.addTask(task); + }; - return wordpressHelper; + function handleError(error, task) { + var errorMsg = undefined; + if(error) { + logger.error(error); + // Try to analyze the error + if(typeof error === "string") { + errorMsg = error; + } + else { + errorMsg = "Could not publish on WordPress."; + if((error.code === 400 && error.message == "invalid_token") || error.code === 401 || error.code === 403) { + localStorage.removeItem("wordpressToken"); + errorMsg = "Access to WordPress account is not authorized."; + task.retry(new Error(errorMsg), 1); + return; + } + else if(error.code <= 0) { + core.setOffline(); + errorMsg = "|stopPublish"; + } + } + } + task.error(new Error(errorMsg)); + } + + return wordpressHelper; }); diff --git a/js/wordpress-provider.js b/js/wordpress-provider.js index c9ba492d..4eb046a2 100644 --- a/js/wordpress-provider.js +++ b/js/wordpress-provider.js @@ -2,48 +2,39 @@ define([ "utils", "wordpress-helper" ], function(utils, wordpressHelper) { - - var PROVIDER_WORDPRESS = "wordpress"; - - var wordpressProvider = { - providerId: PROVIDER_WORDPRESS, - providerName: "WordPress", - defaultPublishFormat: "html", - publishPreferencesInputIds: ["wordpress-site"] - }; - - wordpressProvider.publish = function(publishAttributes, title, content, callback) { - wordpressHelper.upload( - publishAttributes.site, - publishAttributes.postId, - publishAttributes.tags, - title, - content, - function(error, postId) { - if(error) { - callback(error); - return; - } - publishAttributes.postId = postId; - callback(); - } - ); - }; - wordpressProvider.newPublishAttributes = function(event) { - var publishAttributes = {}; - publishAttributes.site = utils - .getInputTextValue( - "#input-publish-wordpress-site", - event, - /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/); - publishAttributes.postId = utils.getInputTextValue("#input-publish-postid"); - publishAttributes.tags = utils.getInputTextValue("#input-publish-tags"); - if(event.isPropagationStopped()) { - return undefined; - } - return publishAttributes; - }; + var PROVIDER_WORDPRESS = "wordpress"; - return wordpressProvider; + var wordpressProvider = { + providerId: PROVIDER_WORDPRESS, + providerName: "WordPress", + defaultPublishFormat: "html", + publishPreferencesInputIds: [ + "wordpress-site" + ] + }; + + wordpressProvider.publish = function(publishAttributes, title, content, callback) { + wordpressHelper.upload(publishAttributes.site, publishAttributes.postId, publishAttributes.tags, title, content, function(error, postId) { + if(error) { + callback(error); + return; + } + publishAttributes.postId = postId; + callback(); + }); + }; + + wordpressProvider.newPublishAttributes = function(event) { + var publishAttributes = {}; + publishAttributes.site = utils.getInputTextValue("#input-publish-wordpress-site", event, /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/); + publishAttributes.postId = utils.getInputTextValue("#input-publish-postid"); + publishAttributes.tags = utils.getInputTextValue("#input-publish-tags"); + if(event.isPropagationStopped()) { + return undefined; + } + return publishAttributes; + }; + + return wordpressProvider; }); \ No newline at end of file diff --git a/tools/eclipse-formatter-config.xml b/tools/eclipse-formatter-config.xml new file mode 100644 index 00000000..c29f4b41 --- /dev/null +++ b/tools/eclipse-formatter-config.xml @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +