From a4c46085e4bf061ba819897800c84a731286cdbc Mon Sep 17 00:00:00 2001 From: benweet Date: Sun, 26 May 2013 23:59:03 +0100 Subject: [PATCH] Extension pattern --- js/config.js | 1 + js/core.js | 33 ++-- js/dropbox-provider.js | 88 ++++----- js/extension-manager.js | 70 +++++--- js/extensions/notifications.js | 30 ++++ js/file-manager.js | 320 ++++++++++++++++++--------------- js/gdrive-provider.js | 90 +++++----- js/publisher.js | 113 +++++------- js/sharing.js | 18 +- js/synchronizer.js | 146 ++++++--------- js/utils.js | 9 + 11 files changed, 469 insertions(+), 449 deletions(-) diff --git a/js/config.js b/js/config.js index 5e44319a..e73fa82c 100644 --- a/js/config.js +++ b/js/config.js @@ -15,6 +15,7 @@ var ASYNC_TASK_DEFAULT_TIMEOUT = 60000; var ASYNC_TASK_LONG_TIMEOUT = 120000; var SYNC_PERIOD = 180000; 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/"; var WORDPRESS_CLIENT_ID = '3185'; diff --git a/js/core.js b/js/core.js index 72d00da3..5e86cf8d 100644 --- a/js/core.js +++ b/js/core.js @@ -1,7 +1,16 @@ -define( - [ "jquery", "utils", "extension-manager", "bootstrap", "layout", "Markdown.Editor", "storage", "config", - "underscore", "FileSaver", "css_browser_selector" ], - function($, utils, extensionManager) { +define([ + "jquery", + "utils", + "extension-manager", + "bootstrap", + "layout", + "Markdown.Editor", + "storage", + "config", + "underscore", + "FileSaver", + "css_browser_selector" +], function($, utils, extensionManager) { var core = {}; @@ -159,11 +168,13 @@ define( '\n', '<%= documentHTML %>\n', ''].join(""), - sshProxy : SSH_PROXY_URL + sshProxy : SSH_PROXY_URL, + extensionSettings: {} }; if (_.has(localStorage, "settings")) { _.extend(core.settings, JSON.parse(localStorage.settings)); } + extensionManager.init(core.settings.extensionSettings); core.loadSettings = function() { @@ -271,13 +282,7 @@ define( layout.allowOverflow('north'); }); - extensionManager.onLayoutCreated(); - }; - core.layoutRefresh = function() { - if(layout !== undefined) { - // Use defer to make sure UI has been updated - _.defer(layout.resizeAll); - } + extensionManager.onLayoutCreated(layout); }; // Create the PageDown editor @@ -413,9 +418,7 @@ define( runReadyCallbacks(); }); - core.onReady(function() { - extensionManager.init(core.settings.extensionSettings); - }); + core.onReady(extensionManager.onReady); core.onReady(function() { // Load theme list diff --git a/js/dropbox-provider.js b/js/dropbox-provider.js index fa991c5d..13aaca74 100644 --- a/js/dropbox-provider.js +++ b/js/dropbox-provider.js @@ -1,12 +1,11 @@ -define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) { +define(["core", "utils", "extension-manager", "dropbox-helper"], function(core, utils, extensionManager, dropboxHelper) { var PROVIDER_DROPBOX = "dropbox"; var dropboxProvider = { providerId: PROVIDER_DROPBOX, providerName: "Dropbox", - defaultPublishFormat: "template", - useSync: false + defaultPublishFormat: "template" }; function checkPath(path) { @@ -23,19 +22,20 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) return path; } + function createSyncIndex(path) { + return "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(path.toLowerCase()); + } + function createSyncAttributes(path, versionTag, content) { var syncAttributes = {}; - syncAttributes.provider = PROVIDER_DROPBOX; + syncAttributes.provider = dropboxProvider; syncAttributes.path = path; syncAttributes.version = versionTag; syncAttributes.contentCRC = utils.crc32(content); + syncAttributes.syncIndex = createSyncIndex(path); return syncAttributes; } - function createSyncIndex(path) { - return "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(path.toLowerCase()); - } - function importFilesFromPaths(paths) { dropboxHelper.downloadMetadata(paths, function(error, result) { if(error) { @@ -45,16 +45,17 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) if(error) { return; } - var titleList = []; + var fileDescList = []; _.each(result, function(file) { var syncAttributes = createSyncAttributes(file.path, file.versionTag, file.content); - var syncIndex = createSyncIndex(syncAttributes.path); - localStorage[syncIndex] = JSON.stringify(syncAttributes); - var fileIndex = core.fileManager.createFile(file.name, file.content, [syncIndex]); - core.fileManager.selectFile(fileIndex); - titleList.push('"' + file.name + '"'); + localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes); + var syncLocations = {}; + syncLocations[syncAttributes.syncIndex] = syncAttributes; + var fileDesc = core.fileManager.createFile(file.name, file.content, syncLocations); + core.fileManager.selectFile(fileDesc); + fileDescList.push(fileDesc); }); - core.showMessage(titleList.join(", ") + ' imported successfully from Dropbox.'); + extensionManager.onSyncImportSuccess(fileDescList, dropboxProvider); }); }); } @@ -67,10 +68,9 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) var importPaths = []; _.each(paths, function(path) { var syncIndex = createSyncIndex(path); - var fileIndex = core.fileManager.getFileIndexFromSync(syncIndex); - if(fileIndex !== undefined) { - var title = localStorage[fileIndex + ".title"]; - core.showError('"' + title + '" was already imported'); + var fileDesc = core.fileManager.getFileFromSync(syncIndex); + if(fileDesc !== undefined) { + core.showError('"' + fileDesc.title + '" was already imported'); return; } importPaths.push(path); @@ -87,9 +87,9 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) } // Check that file is not synchronized with an other one var syncIndex = createSyncIndex(path); - var fileIndex = core.fileManager.getFileIndexFromSync(syncIndex); - if(fileIndex !== undefined) { - var existingTitle = localStorage[fileIndex + ".title"]; + var fileDesc = core.fileManager.getFileFromSync(syncIndex); + if(fileDesc !== undefined) { + var existingTitle = fileDesc.title; core.showError('File path is already synchronized with "' + existingTitle + '"'); callback(true); return; @@ -100,9 +100,8 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) return; } var syncAttributes = createSyncAttributes(result.path, result.versionTag, content); - var syncIndex = createSyncIndex(syncAttributes.path); - localStorage[syncIndex] = JSON.stringify(syncAttributes); - callback(undefined, syncIndex); + localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes); + callback(undefined, syncIndex, syncAttributes); }); } @@ -135,10 +134,6 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) }; dropboxProvider.syncDown = function(callback) { - if (dropboxProvider.useSync === false) { - callback(); - return; - } var lastChangeId = localStorage[PROVIDER_DROPBOX + ".lastChangeId"]; dropboxHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) { if (error) { @@ -148,23 +143,20 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) var interestingChanges = []; _.each(changes, function(change) { var syncIndex = createSyncIndex(change.path); - var serializedAttributes = localStorage[syncIndex]; - if(serializedAttributes === undefined) { + var syncAttributes = core.fileManager.getSyncAttributes(syncIndex); + if(syncAttributes === undefined) { return; } - // Store syncIndex to avoid 2 times formating - change.syncIndex = syncIndex; + // Store syncAttributes to avoid 2 times searching + change.syncAttributes = syncAttributes; // Delete if(change.wasRemoved === true) { interestingChanges.push(change); return; } // Modify - var syncAttributes = JSON.parse(serializedAttributes); if(syncAttributes.version != change.stat.versionTag) { interestingChanges.push(change); - // Store syncAttributes to avoid 2 times parsing - change.syncAttributes = syncAttributes; } }); dropboxHelper.downloadContent(interestingChanges, function(error, changes) { @@ -174,23 +166,21 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) } var updateFileTitles = false; _.each(changes, function(change) { - var syncIndex = change.syncIndex; - var fileIndex = core.fileManager.getFileIndexFromSync(syncIndex); + var syncAttributes = change.syncAttributes; + var syncIndex = syncAttributes.syncIndex; + var fileDesc = core.fileManager.getFileFromSync(syncIndex); // No file corresponding (file may have been deleted locally) - if(fileIndex === undefined) { - core.fileManager.removeSync(syncIndex); + if(fileDesc === undefined) { return; } - var localTitle = localStorage[fileIndex + ".title"]; + var localTitle = fileDesc.title; // File deleted if (change.wasRemoved === true) { + core.showError('"' + localTitle + '" has been removed from Dropbox.'); core.fileManager.removeSync(syncIndex); - updateFileTitles = true; - core.showMessage('"' + localTitle + '" has been removed from Dropbox.'); return; } - var syncAttributes = change.syncAttributes; - var localContent = localStorage[fileIndex + ".content"]; + var localContent = localStorage[fileDesc.index + ".content"]; var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); var file = change.stat; var remoteContentCRC = utils.crc32(file.content); @@ -204,9 +194,9 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) } // If file content changed if(fileContentChanged && remoteContentChanged === true) { - localStorage[fileIndex + ".content"] = file.content; + localStorage[fileDesc.index + ".content"] = file.content; core.showMessage('"' + localTitle + '" has been updated from Dropbox.'); - if(core.fileManager.isCurrentFileIndex(fileIndex)) { + if(core.fileManager.isCurrentFile(fileDesc)) { updateFileTitles = false; // Done by next function core.fileManager.selectFile(); // Refresh editor } @@ -214,10 +204,10 @@ define(["core", "utils", "dropbox-helper"], function(core, utils, dropboxHelper) // Update syncAttributes syncAttributes.version = file.versionTag; syncAttributes.contentCRC = remoteContentCRC; - localStorage[syncIndex] = JSON.stringify(syncAttributes); + localStorage[syncIndex] = utils.serializeAttributes(syncAttributes); }); if(updateFileTitles) { - core.fileManager.updateFileTitles(); + extensionManager.onTitleChanged(); } localStorage[PROVIDER_DROPBOX + ".lastChangeId"] = newChangeId; callback(); diff --git a/js/extension-manager.js b/js/extension-manager.js index 17cf1e05..59a1368c 100644 --- a/js/extension-manager.js +++ b/js/extension-manager.js @@ -12,24 +12,25 @@ define( [ var extensionManager = {}; - // Create a map with providerId: providerObject + // 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(callbackName) { + function getExtensionCallbackList(hookName) { return _.chain(extensionList) .map(function(extension) { - return extension.config.enabled && extension[callbackName]; + return extension.config.enabled && extension[hookName]; }).compact().value(); } // Return a function that calls every callbacks from extensions - function createCallback(callbackName) { - var callbackList = getExtensionCallbackList(callbackName); + function createHook(hookName) { + var callbackList = getExtensionCallbackList(hookName); return function() { + console.debug(hookName); var callbackArguments = arguments; _.each(callbackList, function(callback) { callback.apply(null, callbackArguments); @@ -37,9 +38,9 @@ define( [ }; } - // Add a callback to the extensionManager - function addCallback(callbackName) { - extensionManager[callbackName] = createCallback(callbackName); + // Add a Hook to the extensionManager + function addHook(hookName) { + extensionManager[hookName] = createHook(hookName); } var accordionTmpl = [ @@ -75,12 +76,9 @@ define( [ extension.config.enabled = !extension.optional || extension.config.enabled === undefined || extension.config.enabled === true; }); - // Create accordion in settings dialog - _.each(extensionList, createSettings); - // Load/Save extension config from/to settings - addCallback("onLoadSettings"); extensionManager["onLoadSettings"] = function() { + console.debug("onLoadSettings"); _.each(extensionList, function(extension) { utils.setInputChecked("#input-enable-extension-" + extension.extensionId, extension.config.enabled); var onLoadSettingsCallback = extension.onLoadSettings; @@ -88,6 +86,7 @@ define( [ }); }; extensionManager["onSaveSettings"] = function(newExtensionSettings, event) { + console.debug("onSaveSettings"); _.each(extensionList, function(extension) { var newExtensionConfig = extension.defaultConfig || {}; newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId); @@ -97,16 +96,40 @@ define( [ }); }; - addCallback("onMessage"); - addCallback("onError"); - addCallback("onOfflineChanged"); - addCallback("onLayoutConfigure"); - addCallback("onLayoutCreated"); - addCallback("onEditorConfigure"); + addHook("onMessage"); + addHook("onError"); + addHook("onOfflineChanged"); - var onPreviewFinished = createCallback("onPreviewFinished"); + // To store reference to modules that are accessible from extensions + addHook("onFileManagerCreated"); + addHook("onSynchronizerCreated"); + addHook("onPublisherCreated"); + + // Operations on files + addHook("onFileSystemLoaded"); + addHook("onFileCreated"); + addHook("onFileDeleted"); + addHook("onFileChanged"); + addHook("onFileSelected"); + addHook("onTitleChanged"); + addHook("onSyncImportSuccess"); + addHook("onSyncExportSuccess"); + addHook("onSyncRemoved"); + 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"); extensionManager["onAsyncPreview"] = function() { + console.debug("onAsyncPreview"); // Call onPreviewFinished callbacks when all async preview are finished var counter = 0; function tryFinished() { @@ -124,7 +147,14 @@ define( [ }; // Call onReady callbacks - createCallback("onReady")(); + var onReady = createHook("onReady"); + extensionManager["onReady"] = function() { + + // Create accordion in settings dialog + _.each(extensionList, createSettings); + + onReady(); + }; }; return extensionManager; diff --git a/js/extensions/notifications.js b/js/extensions/notifications.js index 1cdb9898..35086b1d 100644 --- a/js/extensions/notifications.js +++ b/js/extensions/notifications.js @@ -55,5 +55,35 @@ define( [ "jquery", "jgrowl", "underscore" ], function($) { } }; + 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/file-manager.js b/js/file-manager.js index 1bc1e02b..9c4581f3 100644 --- a/js/file-manager.js +++ b/js/file-manager.js @@ -1,47 +1,84 @@ -define(["jquery", "core", "utils", "synchronizer", "publisher", "sharing", "text!../WELCOME.md", "underscore"], - function($, core, utils, synchronizer, publisher, sharing, welcomeContent) { +define([ + "jquery", + "core", + "utils", + "extension-manager", + "synchronizer", + "publisher", + "sharing", + "text!../WELCOME.md", + "underscore" +], function($, core, utils, extensionManager, synchronizer, publisher, sharing, welcomeContent) { - var TEMPORARY_FILE_INDEX = "file.tempIndex"; - var fileManager = {}; + // Load file descriptors from localStorage and store in a map + var fileSystemDescriptor = _.chain(localStorage["file.list"].split(";")) + .compact() + .reduce(function(fileSystemDescriptor, fileIndex) { + var title = localStorage[fileIndex + ".title"]; + var fileDesc = { + index : fileIndex, + title : title, + syncLocations: {}, + publishLocations: {} + }; + synchronizer.populateSyncLocations(fileDesc), + publisher.populatePublishLocations(fileDesc), + fileSystemDescriptor[fileIndex] = fileDesc; + return fileSystemDescriptor; + }, {}) + .value(); + extensionManager.onFileSystemLoaded(fileSystemDescriptor); + fileManager.getFileList = function() { + return _.values(fileSystemDescriptor); + }; + // Defines the current file - var currentFileIndex = localStorage["file.current"]; - fileManager.getCurrentFileIndex = function() { - return currentFileIndex; + var currentFile = (function() { + var currentFileIndex = localStorage["file.current"]; + if(currentFileIndex !== undefined) { + return fileSystemDescriptor[currentFileIndex]; + } + })(); + fileManager.getCurrentFile = function() { + return currentFile; }; - fileManager.isCurrentFileIndex = function(fileIndex) { - return fileIndex == currentFileIndex; + fileManager.isCurrentFile = function(fileDesc) { + return fileDesc === currentFile; }; - fileManager.setCurrentFileIndex = function(fileIndex) { - currentFileIndex = fileIndex; - if(fileIndex === undefined) { + fileManager.setCurrentFile = function(fileDesc) { + currentFile = fileDesc; + if(fileDesc === undefined) { localStorage.removeItem("file.current"); } - else if(fileIndex != TEMPORARY_FILE_INDEX) { - localStorage["file.current"] = fileIndex; + else if(fileDesc.index != TEMPORARY_FILE_INDEX) { + localStorage["file.current"] = fileDesc.index; } }; // Caution: this function recreate the editor (reset undo operations) - var fileDescList = []; - fileManager.selectFile = function(fileIndex) { + fileManager.selectFile = function(fileDesc) { // If no file create one - if (localStorage["file.list"].length === 1) { - fileIndex = fileManager.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); + if (_.size(fileSystemDescriptor) === 0) { + fileDesc = fileManager.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); } - if(fileIndex !== undefined) { - fileManager.setCurrentFileIndex(fileIndex); + if(fileDesc === undefined) { + // If no file is selected take the last created + fileDesc = fileSystemDescriptor[_.keys(fileSystemDescriptor)[fileSystemDescriptor.length - 1]]; } + fileManager.setCurrentFile(fileDesc); // Update the file titles fileManager.updateFileTitles(); - synchronizer.refreshManageSync(); publisher.notifyPublish(); + // Notify extensions + extensionManager.onFileSelected(fileDesc); + // Hide the viewer pencil button - if(fileIndex == TEMPORARY_FILE_INDEX) { + if(fileDesc.index == TEMPORARY_FILE_INDEX) { $(".action-edit-document").removeClass("hide"); } else { @@ -49,22 +86,21 @@ define(["jquery", "core", "utils", "synchronizer", "publisher", "sharing", "text } // Recreate the editor - fileIndex = fileManager.getCurrentFileIndex(); - $("#wmd-input").val(localStorage[fileIndex + ".content"]); + $("#wmd-input").val(localStorage[fileDesc.index + ".content"]); core.createEditor(function() { // Callback to save content when textarea changes fileManager.saveFile(); }); }; - - fileManager.createFile = function(title, content, syncIndexes, isTemporary) { + + fileManager.createFile = function(title, content, syncLocations, isTemporary) { content = content !== undefined ? content : core.settings.defaultContent; - syncIndexes = syncIndexes || []; + syncLocations = syncLocations || {}; if (!title) { // Create a file title title = DEFAULT_FILE_TITLE; var indicator = 2; - while(_.some(fileDescList, function(fileDesc) { + while(_.some(fileSystemDescriptor, function(fileDesc) { return fileDesc.title == title; })) { title = DEFAULT_FILE_TITLE + indicator++; @@ -76,170 +112,152 @@ define(["jquery", "core", "utils", "synchronizer", "publisher", "sharing", "text if(!isTemporary) { do { fileIndex = "file." + utils.randomString(); - } while(_.has(localStorage, fileIndex + ".title")); + } while(_.has(fileSystemDescriptor, fileIndex)); } // Create the file in the localStorage localStorage[fileIndex + ".content"] = content; localStorage[fileIndex + ".title"] = title; // Store syncIndexes associated to the file - var sync = _.reduce(syncIndexes, function(sync, syncIndex) { + var sync = _.reduce(syncLocations, function(sync, syncAttributes, syncIndex) { return sync + syncIndex + ";"; }, ";"); localStorage[fileIndex + ".sync"] = sync; // Store publishIndexes associated to the file localStorage[fileIndex + ".publish"] = ";"; + + // Create the file descriptor + var fileDesc = { + index : fileIndex, + title : title, + syncLocations: syncLocations, + publishLocations: {} + }; + // Add the index to the file list if(!isTemporary) { localStorage["file.list"] += fileIndex + ";"; + fileSystemDescriptor[fileIndex] = fileDesc; + extensionManager.onFileCreated(fileDesc); } - return fileIndex; + return fileDesc; }; - fileManager.deleteFile = function(fileIndex) { - fileIndex = fileIndex || fileManager.getCurrentFileIndex(); - if(fileManager.isCurrentFileIndex(fileIndex)) { - // Unset the current fileIndex - fileManager.setCurrentFileIndex(); + fileManager.deleteFile = function(fileDesc) { + fileDesc = fileDesc || fileManager.getCurrentFile(); + if(fileManager.isCurrentFile(fileDesc)) { + // Unset the current fileDesc + fileManager.setCurrentFile(); } // Remove synchronized locations - var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); - _.each(syncIndexList, function(syncIndex) { - fileManager.removeSync(syncIndex); + _.each(fileDesc.syncLocations, function(syncAttributes, syncIndex) { + fileManager.removeSync(syncIndex, true); }); // Remove publish locations - var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); - _.each(publishIndexList, function(publishIndex) { + _.each(fileDesc.publishLocations, function(publishAttributes, publishIndex) { fileManager.removePublish(publishIndex); }); // Remove the index from the file list + var fileIndex = fileDesc.index; localStorage["file.list"] = localStorage["file.list"].replace(";" + fileIndex + ";", ";"); localStorage.removeItem(fileIndex + ".title"); localStorage.removeItem(fileIndex + ".content"); localStorage.removeItem(fileIndex + ".sync"); localStorage.removeItem(fileIndex + ".publish"); + fileSystemDescriptor.removeItem(fileIndex); + extensionManager.onFileDeleted(fileDesc); }; // Save current file in localStorage fileManager.saveFile = function() { var content = $("#wmd-input").val(); - var fileIndex = fileManager.getCurrentFileIndex(); - localStorage[fileIndex + ".content"] = content; - synchronizer.notifyChange(fileIndex); + var fileDesc = fileManager.getCurrentFile(); + localStorage[fileDesc.index + ".content"] = content; + extensionManager.onFileChanged(fileDesc); + synchronizer.notifyChange(fileDesc); + }; + + // Add a syncIndex (synchronized location) to a file + fileManager.addSync = function(fileDesc, syncIndex, syncAttributes) { + localStorage[fileDesc.index + ".sync"] += syncIndex + ";"; + fileDesc.syncLocations[syncIndex] = syncAttributes; + // addSync is only used for export, not for import + extensionManager.onSyncExportSuccess(fileDesc, syncIndex, syncAttributes); }; - fileManager.updateFileTitles = function() { - fileDescList = _.chain(localStorage["file.list"].split(";")).compact() - .reduce(function(fileDescList, fileIndex) { - var title = localStorage[fileIndex + ".title"]; - fileDescList.push({ index : fileIndex, title : title }); - return fileDescList; - }, []) - .sortBy(function(fileDesc) { - return fileDesc.title.toLowerCase(); - }).value(); - - var fileIndex = fileManager.getCurrentFileIndex(); - // If no default file take first one - if (fileIndex === undefined) { - fileIndex = fileDescList[0].index; - fileManager.setCurrentFileIndex(fileIndex); - } - - synchronizer.resetSyncFlags(); - function composeTitle(fileIndex, refreshSharing) { - var result = []; - var syncAttributesList = synchronizer.getSyncAttributesFromFile(fileIndex); - var publishAttributesList = publisher.getPublishAttributesFromFile(fileIndex); - var attributesList = syncAttributesList.concat(publishAttributesList); - if(refreshSharing === true) { - sharing.refreshDocumentSharing(attributesList); - } - _.chain(attributesList).sortBy(function(attributes) { - return attributes.provider; - }).each(function(attributes) { - result.push(''); - }); - result.push(" "); - result.push(localStorage[fileIndex + ".title"]); - return result.join(""); - } - - // Update the file title - var title = localStorage[fileIndex + ".title"]; - document.title = "StackEdit - " + title; - $("#file-title").html(composeTitle(fileIndex, true)); - $(".file-title").text(title); - $("#file-title-input").val(title); - - // Update the file selector - $("#file-selector li:not(.stick)").empty(); - _.each(fileDescList, function(fileDesc) { - var a = $("").html(composeTitle(fileDesc.index)); - var li = $("
  • ").append(a); - if (fileDesc.index == fileIndex) { - li.addClass("disabled"); - } else { - a.prop("href", "#").click((function(fileIndex) { - return function() { - fileManager.selectFile(fileIndex); - }; - })(fileDesc.index)); - } - $("#file-selector").append(li); - }); - - core.layoutRefresh(); - }; - // Remove a syncIndex (synchronized location) - fileManager.removeSync = function(syncIndex) { - var fileIndex = fileManager.getFileIndexFromSync(syncIndex); - if(fileIndex !== undefined) { - localStorage[fileIndex + ".sync"] = localStorage[fileIndex + ".sync"].replace(";" + fileManager.removeSync = function(syncIndex, skipExtensions) { + var fileDesc = fileManager.getFileFromSync(syncIndex); + if(fileDesc !== undefined) { + localStorage[fileDesc.index + ".sync"] = localStorage[fileDesc.index + ".sync"].replace(";" + syncIndex + ";", ";"); - if(fileManager.isCurrentFileIndex(fileIndex)) { - synchronizer.refreshManageSync(); - } } // Remove sync attributes localStorage.removeItem(syncIndex); + var syncAttributes = fileDesc.syncLocations[syncIndex]; + fileDesc.syncLocations.removeItem(syncIndex); + if(!skipExtensions) { + extensionManager.onSyncRemoved(fileDesc, syncAttributes); + } }; - // Get the fileIndex associated to a syncIndex - fileManager.getFileIndexFromSync = function(syncIndex) { - return _.chain(localStorage["file.list"].split(";")).compact() - .find(function(fileIndex) { - var sync = localStorage[fileIndex + ".sync"]; - return sync.indexOf(";" + syncIndex + ";") !== -1; - }).value(); + // Get the file descriptor associated to a syncIndex + fileManager.getFileFromSync = function(syncIndex) { + return _.find(fileSystemDescriptor, function(fileDesc) { + return _.has(fileDesc.syncLocations, syncIndex); + }); + }; + + // Get syncAttributes from syncIndex + fileManager.getSyncAttributes = function(syncIndex) { + var fileDesc = fileManager.getFileFromSync(syncIndex); + return fileDesc && fileDesc.syncLocations[syncIndex]; + }; + + // Returns true if provider has locations to synchronize + fileManager.hasSync = function(provider) { + return _.some(fileSystemDescriptor, function(fileDesc) { + return _.some(fileDesc.syncLocations, function(syncAttributes) { + syncAttributes.provider == provider.providerId; + }); + }); }; + // Add a publishIndex (publish location) to a file + fileManager.addPublish = function(fileDesc, publishIndex, publishAttributes) { + localStorage[fileDesc.index + ".publish"] += publishIndex + ";"; + fileDesc.publishLocations[publishIndex] = publishAttributes; + extensionManager.onNewPublishSuccess(fileDesc, publishIndex, publishAttributes); + }; + // Remove a publishIndex (publish location) - fileManager.removePublish = function(publishIndex) { - var fileIndex = fileManager.getFileIndexFromPublish(publishIndex); - if(fileIndex !== undefined) { - localStorage[fileIndex + ".publish"] = localStorage[fileIndex + ".publish"].replace(";" + fileManager.removePublish = function(publishIndex, skipExtensions) { + var fileDesc = fileManager.getFileFromPublish(publishIndex); + if(fileDesc !== undefined) { + localStorage[fileDesc.index + ".publish"] = localStorage[fileDesc.index + ".publish"].replace(";" + publishIndex + ";", ";"); - if(fileManager.isCurrentFileIndex(fileIndex)) { + if(fileManager.isCurrentFile(fileDesc)) { publisher.notifyPublish(); } } // Remove publish attributes localStorage.removeItem(publishIndex); + var publishAttributes = fileDesc.publishLocations[publishIndex]; + fileDesc.publishLocations.removeItem(publishIndex); + if(!skipExtensions) { + extensionManager.onPublishRemoved(fileDesc, publishAttributes); + } }; - // Get the fileIndex associated to a publishIndex - fileManager.getFileIndexFromPublish = function(publishIndex) { - return _.chain(localStorage["file.list"].split(";")).compact() - .find(function(fileIndex) { - var sync = localStorage[fileIndex + ".publish"]; - return sync.indexOf(";" + publishIndex + ";") !== -1; - }).value(); + // Get the file descriptor associated to a publishIndex + fileManager.getFileFromPublish = function(publishIndex) { + return _.find(fileSystemDescriptor, function(fileDesc) { + return _.has(fileDesc.publishLocations, publishIndex); + }); }; // Filter for search input in file selector @@ -263,8 +281,8 @@ define(["jquery", "core", "utils", "synchronizer", "publisher", "sharing", "text fileManager.selectFile(); $(".action-create-file").click(function() { - var fileIndex = fileManager.createFile(); - fileManager.selectFile(fileIndex); + var fileDesc = fileManager.createFile(); + fileManager.selectFile(fileDesc); var wmdInput = $("#wmd-input").focus().get(0); if(wmdInput.setSelectionRange) { wmdInput.setSelectionRange(0, 0); @@ -286,18 +304,21 @@ define(["jquery", "core", "utils", "synchronizer", "publisher", "sharing", "text }); }); function applyTitle(input) { + input.hide(); + $("#file-title").show(); var title = $.trim(input.val()); - var fileIndexTitle = fileManager.getCurrentFileIndex() + ".title"; + var fileDesc = fileManager.getCurrentFile(); + var fileIndexTitle = fileDesc.index + ".title"; if (title) { if (title != localStorage[fileIndexTitle]) { localStorage[fileIndexTitle] = title; + fileDesc.title = title; fileManager.updateFileTitles(); - fileManager.saveFile(); + synchronizer.notifyChange(fileDesc); + extensionManager.onTitleChanged(fileDesc); } } - input.hide().val(localStorage[fileIndexTitle]); - $("#file-title").show(); - core.layoutRefresh(); + input.val(localStorage[fileIndexTitle]); $("#wmd-input").focus(); } $("#file-title-input").blur(function() { @@ -327,32 +348,33 @@ define(["jquery", "core", "utils", "synchronizer", "publisher", "sharing", "text }); $(".action-edit-document").click(function() { var content = $("#wmd-input").val(); - var title = localStorage[fileManager.getCurrentFileIndex() + ".title"]; - var fileIndex = fileManager.createFile(title, content); - fileManager.selectFile(fileIndex); + var title = fileManager.getCurrentFile().title; + var fileDesc = fileManager.createFile(title, content); + fileManager.selectFile(fileDesc); window.location.href = "."; }); $(".action-download-md").click(function() { var content = $("#wmd-input").val(); - var title = localStorage[fileManager.getCurrentFileIndex() + ".title"]; + var title = fileManager.getCurrentFile().title; core.saveFile(content, title + ".md"); }); $(".action-download-html").click(function() { var content = $("#wmd-preview").html(); - var title = localStorage[fileManager.getCurrentFileIndex() + ".title"]; + var title = fileManager.getCurrentFile().title; core.saveFile(content, title + ".html"); }); $(".action-download-template").click(function() { var content = publisher.applyTemplate(); - var title = localStorage[fileManager.getCurrentFileIndex() + ".title"]; + var title = fileManager.getCurrentFile().title; core.saveFile(content, title + ".txt"); }); $(".action-welcome-file").click(function() { - var fileIndex = fileManager.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); - fileManager.selectFile(fileIndex); + var fileDesc = fileManager.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); + fileManager.selectFile(fileDesc); }); }); core.setFileManager(fileManager); + extensionManager.onFileManagerCreated(fileManager); return fileManager; }); diff --git a/js/gdrive-provider.js b/js/gdrive-provider.js index 3a70bbab..df2b12b5 100644 --- a/js/gdrive-provider.js +++ b/js/gdrive-provider.js @@ -1,4 +1,4 @@ -define(["core", "utils", "google-helper", "underscore"], function(core, utils, googleHelper) { +define(["core", "utils", "extension-manager", "google-helper", "underscore"], function(core, utils, extensionManager, googleHelper) { var PROVIDER_GDRIVE = "gdrive"; @@ -6,13 +6,12 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g providerId: PROVIDER_GDRIVE, providerName: "Google Drive", defaultPublishFormat: "template", - exportPreferencesInputIds: ["gdrive-parentid"], - useSync: false + exportPreferencesInputIds: ["gdrive-parentid"] }; function createSyncAttributes(id, etag, content, title) { var syncAttributes = {}; - syncAttributes.provider = PROVIDER_GDRIVE; + syncAttributes.provider = gdriveProvider; syncAttributes.id = id; syncAttributes.etag = etag; syncAttributes.contentCRC = utils.crc32(content); @@ -33,19 +32,18 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g if(error) { return; } - var titleList = []; + var fileDescList = []; _.each(result, function(file) { var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title); var syncIndex = createSyncIndex(syncAttributes.id); - localStorage[syncIndex] = JSON.stringify(syncAttributes); - var fileIndex = core.fileManager.createFile(file.title, file.content, [syncIndex]); - core.fileManager.selectFile(fileIndex); - titleList.push('"' + file.title + '"'); + localStorage[syncIndex] = utils.serializeAttributes(syncAttributes); + var syncLocations = {}; + syncLocations[syncIndex] = syncAttributes; + var fileDesc = core.fileManager.createFile(file.title, file.content, syncLocations); + core.fileManager.selectFile(fileDesc); + fileDescList.push(fileDesc); }); - if(titleList.length === 0) { - return; - } - core.showMessage(titleList.join(", ") + ' imported successfully from Google Drive.'); + extensionManager.onSyncImportSuccess(fileDescList, gdriveProvider); }); }); }; @@ -58,10 +56,9 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g var importIds = []; _.each(ids, function(id) { var syncIndex = createSyncIndex(id); - var fileIndex = core.fileManager.getFileIndexFromSync(syncIndex); - if(fileIndex !== undefined) { - var title = localStorage[fileIndex + ".title"]; - core.showError('"' + title + '" was already imported'); + var fileDesc = core.fileManager.getFileFromSync(syncIndex); + if(fileDesc !== undefined) { + core.showError('"' + fileDesc.title + '" was already imported'); return; } importIds.push(id); @@ -79,8 +76,8 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g } var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); var syncIndex = createSyncIndex(syncAttributes.id); - localStorage[syncIndex] = JSON.stringify(syncAttributes); - callback(undefined, syncIndex); + localStorage[syncIndex] = utils.serializeAttributes(syncAttributes); + callback(undefined, syncIndex, syncAttributes); }); }; @@ -91,10 +88,9 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g } // Check that file is not synchronized with an other one var syncIndex = createSyncIndex(id); - var fileIndex = core.fileManager.getFileIndexFromSync(syncIndex); - if(fileIndex !== undefined) { - var existingTitle = localStorage[fileIndex + ".title"]; - core.showError('File ID is already synchronized with "' + existingTitle + '"'); + var fileDesc = core.fileManager.getFileFromSync(syncIndex); + if(fileDesc !== undefined) { + core.showError('File ID is already synchronized with "' + fileDesc.title + '"'); callback(true); return; } @@ -105,8 +101,8 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g } var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); var syncIndex = createSyncIndex(syncAttributes.id); - localStorage[syncIndex] = JSON.stringify(syncAttributes); - callback(undefined, syncIndex); + localStorage[syncIndex] = utils.serializeAttributes(syncAttributes); + callback(undefined, syncIndex, syncAttributes); }); }; @@ -131,10 +127,6 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g }; gdriveProvider.syncDown = function(callback) { - if (gdriveProvider.useSync === false) { - callback(); - return; - } var lastChangeId = parseInt(localStorage[PROVIDER_GDRIVE + ".lastChangeId"]); googleHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) { if (error) { @@ -144,8 +136,8 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g var interestingChanges = []; _.each(changes, function(change) { var syncIndex = createSyncIndex(change.fileId); - var serializedAttributes = localStorage[syncIndex]; - if(serializedAttributes === undefined) { + var syncAttributes = core.fileManager.getSyncAttributes(syncIndex); + if(syncAttributes === undefined) { return; } // Store syncIndex to avoid 2 times formating @@ -156,10 +148,9 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g return; } // Modify - var syncAttributes = JSON.parse(serializedAttributes); if(syncAttributes.etag != change.file.etag) { interestingChanges.push(change); - // Store syncAttributes to avoid 2 times parsing + // Store syncAttributes to avoid 2 times searching change.syncAttributes = syncAttributes; } }); @@ -171,23 +162,21 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g var updateFileTitles = false; _.each(changes, function(change) { var syncIndex = change.syncIndex; - var fileIndex = core.fileManager.getFileIndexFromSync(syncIndex); + var fileDesc = core.fileManager.getFileFromSync(syncIndex); // No file corresponding (file may have been deleted locally) - if(fileIndex === undefined) { - core.fileManager.removeSync(syncIndex); + if(fileDesc === undefined) { return; } - var localTitle = localStorage[fileIndex + ".title"]; + var localTitle = fileDesc.title; // File deleted if (change.deleted === true) { + core.showError('"' + localTitle + '" has been removed from Google Drive.'); core.fileManager.removeSync(syncIndex); - updateFileTitles = true; - core.showMessage('"' + localTitle + '" has been removed from Google Drive.'); return; } var syncAttributes = change.syncAttributes; var localTitleChanged = syncAttributes.titleCRC != utils.crc32(localTitle); - var localContent = localStorage[fileIndex + ".content"]; + var localContent = localStorage[fileDesc.index + ".content"]; var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); var file = change.file; var remoteTitleCRC = utils.crc32(file.title); @@ -205,15 +194,16 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g } // If file title changed if(fileTitleChanged && remoteTitleChanged === true) { - localStorage[fileIndex + ".title"] = file.title; + localStorage[fileDesc.index + ".title"] = file.title; + fileDesc.title = file.title; updateFileTitles = true; core.showMessage('"' + localTitle + '" has been renamed to "' + file.title + '" on Google Drive.'); } // If file content changed if(fileContentChanged && remoteContentChanged === true) { - localStorage[fileIndex + ".content"] = file.content; + localStorage[fileDesc.index + ".content"] = file.content; core.showMessage('"' + file.title + '" has been updated from Google Drive.'); - if(core.fileManager.isCurrentFileIndex(fileIndex)) { + if(core.fileManager.isCurrentFile(fileDesc)) { updateFileTitles = false; // Done by next function core.fileManager.selectFile(); // Refresh editor } @@ -222,10 +212,10 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g syncAttributes.etag = file.etag; syncAttributes.contentCRC = remoteContentCRC; syncAttributes.titleCRC = remoteTitleCRC; - localStorage[syncIndex] = JSON.stringify(syncAttributes); + localStorage[syncIndex] = utils.serializeAttributes(syncAttributes); }); if(updateFileTitles) { - core.fileManager.updateFileTitles(); + extensionManager.onTitleChanged(); } localStorage[PROVIDER_GDRIVE + ".lastChangeId"] = newChangeId; callback(); @@ -275,8 +265,8 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g return; } var syncIndex = createSyncAttributes(file.id, file.etag, file.content, file.title); - var fileIndex = core.fileManager.createFile(file.title, file.content, [syncIndex]); - core.fileManager.selectFile(fileIndex); + var fileDesc = core.fileManager.createFile(file.title, file.content, [syncIndex]); + core.fileManager.selectFile(fileDesc); core.showMessage('"' + file.title + '" created successfully on Google Drive.'); }); } @@ -284,9 +274,9 @@ define(["core", "utils", "google-helper", "underscore"], function(core, utils, g var importIds = []; _.each(state.ids, function(id) { var syncIndex = createSyncIndex(id); - var fileIndex = core.fileManager.getFileIndexFromSync(syncIndex); - if(fileIndex !== undefined) { - core.fileManager.selectFile(fileIndex); + var fileDesc = core.fileManager.getFileFromSync(syncIndex); + if(fileDesc !== undefined) { + core.fileManager.selectFile(fileDesc); } else { importIds.push(id); diff --git a/js/publisher.js b/js/publisher.js index df7165ad..31d940fd 100644 --- a/js/publisher.js +++ b/js/publisher.js @@ -1,5 +1,19 @@ -define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provider", "gist-provider", "github-provider", "gdrive-provider", "ssh-provider", "tumblr-provider", "wordpress-provider", "underscore"], - function($, core, utils, sharing) { +define([ + "jquery", + "core", + "utils", + "extension-manager", + "sharing", + "blogger-provider", + "dropbox-provider", + "gist-provider", + "github-provider", + "gdrive-provider", + "ssh-provider", + "tumblr-provider", + "wordpress-provider", + "underscore" +], function($, core, utils, extensionManager, sharing) { var publisher = {}; @@ -14,17 +28,16 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi // Allows external modules to update hasPublications flag publisher.notifyPublish = function() { - var fileIndex = core.fileManager.getCurrentFileIndex(); + var fileDesc = core.fileManager.getCurrentFile(); // Check that file has publications - if(localStorage[fileIndex + ".publish"].length === 1) { + if(_.size(fileDesc.publishLocations) === 0) { hasPublications = false; } else { hasPublications = true; } publisher.updatePublishButton(); - publisher.refreshManagePublish(); }; // Used to enable/disable the publish button @@ -41,10 +54,10 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi // Apply template to the current document publisher.applyTemplate = function(publishAttributes) { - var fileIndex = core.fileManager.getCurrentFileIndex(); + var fileDesc = core.fileManager.getCurrentFile(); try { return _.template(core.settings.template, { - documentTitle: localStorage[fileIndex + ".title"], + documentTitle: fileDesc.title, documentMarkdown: $("#wmd-input").val(), documentHTML: $("#wmd-preview").html(), publishAttributes: publishAttributes @@ -72,18 +85,18 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi } // Recursive function to publish a file on multiple locations - var publishIndexList = []; + var publishAttributesList = []; var publishTitle = undefined; function publishLocation(callback, errorFlag) { // No more publish location for this document - if (publishIndexList.length === 0) { + if (publishAttributesList.length === 0) { callback(errorFlag); return; } // Dequeue a synchronized location - var publishIndex = publishIndexList.pop(); + var publishIndex = publishAttributesList.pop(); var publishAttributes = JSON.parse(localStorage[publishIndex]); var content = getPublishContent(publishAttributes); @@ -94,8 +107,6 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi var errorMsg = error.toString(); if(errorMsg.indexOf("|removePublish") !== -1) { core.fileManager.removePublish(publishIndex); - core.fileManager.updateFileTitles(); - core.showMessage(provider.providerName + " publish location has been removed."); } if(errorMsg.indexOf("|stopPublish") !== -1) { callback(error); @@ -115,26 +126,26 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi publishRunning = true; publisher.updatePublishButton(); - var fileIndex = core.fileManager.getCurrentFileIndex(); - publishTitle = localStorage[fileIndex + ".title"]; - publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); + var fileDesc = fileManager.getCurrentFile(); + publishTitle = fileDesc.title; + publishAttributesList = _.values(fileDesc.publishLocations); publishLocation(function(errorFlag) { publishRunning = false; publisher.updatePublishButton(); if(errorFlag === undefined) { - core.showMessage('"' + publishTitle + '" successfully published.'); + extensionManager.onPublishSuccess(fileDesc); } }); }; - // Generate a publishIndex associated to a fileIndex and store publishAttributes - function createPublishIndex(fileIndex, publishAttributes) { + // 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)); localStorage[publishIndex] = JSON.stringify(publishAttributes); - localStorage[fileIndex + ".publish"] += publishIndex + ";"; + core.fileManager.addPublish(fileDesc, publishIndex, publishAttributes); } // Initialize the "New publication" dialog @@ -174,18 +185,16 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi } // Perform provider's publishing - var fileIndex = core.fileManager.getCurrentFileIndex(); - var title = localStorage[fileIndex + ".title"]; + var fileDesc = core.fileManager.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(fileIndex, publishAttributes); + createPublishIndex(fileDesc, publishAttributes); publisher.notifyPublish(); core.fileManager.updateFileTitles(); - core.showMessage('"' + title - + '" is now published on ' + provider.providerName + '.'); }); } }); @@ -198,50 +207,19 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi publishPreferences.format = publishAttributes.format; localStorage[provider.providerId + ".publishPreferences"] = JSON.stringify(publishPreferences); } - - // Used to populate the "Manage publication" dialog - var lineTemplate = ['
    ', - '', - '', - '', - '
    '].join(""); - var removeButtonTemplate = '
    '; - publisher.refreshManagePublish = function() { - var fileIndex = core.fileManager.getCurrentFileIndex(); - var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); - $(".msg-no-publish, .msg-publish-list").addClass("hide"); - var publishList = $("#manage-publish-list").empty(); - if (publishIndexList.length > 0) { - $(".msg-publish-list").removeClass("hide"); - } else { - $(".msg-no-publish").removeClass("hide"); - } - _.each(publishIndexList, function(publishIndex) { - var publishAttributes = JSON.parse(localStorage[publishIndex]); - if(publishAttributes.password) { - publishAttributes.password = "********"; - } - var publishDesc = JSON.stringify(publishAttributes).replace(/{|}|"/g, ""); - var lineElement = $(_.template(lineTemplate, { - provider: providerMap[publishAttributes.provider], - publishDesc: publishDesc - })); - lineElement.append($(removeButtonTemplate).click(function() { - core.fileManager.removePublish(publishIndex); - core.fileManager.updateFileTitles(); - })); - publishList.append(lineElement); - }); - }; - publisher.getPublishAttributesFromFile = function(fileIndex) { - var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); - var attributesList = []; - _.each(publishIndexList, function(publishIndex) { - var publishAttributes = JSON.parse(localStorage[publishIndex]); - attributesList.push(publishAttributes); - }); - return attributesList; + // Retrieve file's publish locations from localStorage + publisher.populatePublishLocations = function(fileDesc) { + _.chain(localStorage[fileDesc.index + ".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() { @@ -273,5 +251,6 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi }); }); + extensionManager.onPublisherCreated(publisher); return publisher; }); \ No newline at end of file diff --git a/js/sharing.js b/js/sharing.js index d24cb9bd..478a7652 100644 --- a/js/sharing.js +++ b/js/sharing.js @@ -88,16 +88,12 @@ define(["jquery", "core", "utils", "async-runner", "download-provider", "gist-pr return; } localStorage.removeItem("missingSharingLink"); - var fileIndexList = _.compact(localStorage["file.list"].split(";")); - _.each(fileIndexList, function(fileIndex) { - var syncIndexList = localStorage[fileIndex + ".sync"].split(";"); - var publishIndexList = localStorage[fileIndex + ".publish"].split(";"); - var attributesIndexList = _.compact(syncIndexList.concat(publishIndexList)); - _.each(attributesIndexList, function(attributesIndex) { - var attributes = JSON.parse(localStorage[attributesIndex]); - sharing.createLink(attributes, function(shortUrl) { + var fileDescList = core.fileManager.getFileList(); + _.each(fileDescList, function(fileDesc) { + _.each(fileDescList.publishLocations, function(publishAttributes, publishIndex) { + sharing.createLink(publishAttributes, function(shortUrl) { if(shortUrl !== undefined) { - localStorage[attributesIndex] = JSON.stringify(attributes); + localStorage[publishIndex] = utils.serializeAttributes(attributes); } }); }); @@ -139,8 +135,8 @@ define(["jquery", "core", "utils", "async-runner", "download-provider", "gist-pr if(error) { return; } - var fileIndex = core.fileManager.createFile(title, content, undefined, true); - core.fileManager.selectFile(fileIndex); + var fileDesc = core.fileManager.createFile(title, content, undefined, true); + core.fileManager.selectFile(fileDesc); }); }); diff --git a/js/synchronizer.js b/js/synchronizer.js index 896098ce..998cc96f 100644 --- a/js/synchronizer.js +++ b/js/synchronizer.js @@ -1,4 +1,13 @@ -define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "underscore"], function($, core, utils) { +define([ + "jquery", + "core", + "utils", + "extension-manager", + "dropbox-provider", + "gdrive-provider", + "underscore" +], function($, core, utils, extensionManager) { + var synchronizer = {}; // Create a map with providerId: providerObject @@ -11,9 +20,9 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under var uploadPending = false; // Allows external modules to update uploadPending flag - synchronizer.notifyChange = function(fileIndex) { + synchronizer.notifyChange = function(fileDesc) { // Check that file has synchronized locations - if(localStorage[fileIndex + ".sync"].length !== 1) { + if(_.size(fileDesc.syncLocations) !== 0) { uploadPending = true; synchronizer.updateSyncButton(); } @@ -38,7 +47,7 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under }; // Recursive function to upload a single file on multiple locations - var uploadFileSyncIndexList = []; + var uploadSyncAttributesList = []; var uploadContent = undefined; var uploadContentCRC = undefined; var uploadTitle = undefined; @@ -46,16 +55,15 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under function locationUp(callback) { // No more synchronized location for this document - if (uploadFileSyncIndexList.length === 0) { + if (uploadSyncAttributesList.length === 0) { fileUp(callback); return; } // Dequeue a synchronized location - var syncIndex = uploadFileSyncIndexList.pop(); - var syncAttributes = JSON.parse(localStorage[syncIndex]); + var syncAttributes = uploadSyncAttributesList.pop(); // Use the specified provider to perform the upload - providerMap[syncAttributes.provider].syncUp( + syncAttributes.provider.syncUp( uploadContent, uploadContentCRC, uploadTitle, @@ -74,7 +82,7 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under } if(uploadFlag) { // Update syncAttributes in localStorage - localStorage[syncIndex] = JSON.stringify(syncAttributes); + localStorage[syncIndex] = utils.serializeAttributes(syncAttributes); } locationUp(callback); } @@ -82,31 +90,28 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under } // Recursive function to upload multiple files - var uploadFileIndexList = []; + var uploadFileList = []; function fileUp(callback) { - // No more fileIndex to synchronize - if (uploadFileIndexList.length === 0) { + // No more fileDesc to synchronize + if (uploadFileList.length === 0) { syncUp(callback); return; } - // Dequeue a fileIndex - var fileIndex = uploadFileIndexList.pop(); - var fileSyncIndexes = localStorage[fileIndex + ".sync"]; - if(fileSyncIndexes.length === 1) { + // Dequeue a fileDesc + var fileDesc = uploadFileList.pop(); + uploadSyncAttributesList = _.values(fileDesc.syncLocations); + if(uploadSyncAttributesList.length === 0) { fileUp(callback); return; } // Get document title/content - uploadContent = localStorage[fileIndex + ".content"]; + uploadContent = localStorage[fileDesc.index + ".content"]; uploadContentCRC = utils.crc32(uploadContent); - uploadTitle = localStorage[fileIndex + ".title"]; + uploadTitle = fileDesc.title; uploadTitleCRC = utils.crc32(uploadTitle); - - // Parse the list of synchronized locations associated to the document - uploadFileSyncIndexList = _.compact(fileSyncIndexes.split(";")); locationUp(callback); } @@ -116,7 +121,7 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under if(uploadCycle === true) { // New upload cycle uploadCycle = false; - uploadFileIndexList = _.compact(localStorage["file.list"].split(";")); + uploadFileList = core.fileManager.getFileList(); fileUp(callback); } else { @@ -132,6 +137,14 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under return; } var provider = providerList.pop(); + + // Check that provider has files to sync + if(!core.fileManager.hasSync(provider)) { + providerDown(callback); + return; + } + + // Perform provider's syncDown provider.syncDown(function(error) { if(error) { callback(error); @@ -187,53 +200,18 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under core.addPeriodicCallback(synchronizer.sync); } - // Used to populate the "Manage synchronization" dialog - var lineTemplate = ['
    ', - '', - '', - '', - '
    '].join(""); - var removeButtonTemplate = ''; - synchronizer.refreshManageSync = function() { - var fileIndex = core.fileManager.getCurrentFileIndex(); - var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); - $(".msg-no-sync, .msg-sync-list").addClass("hide"); - var syncList = $("#manage-sync-list").empty(); - if (syncIndexList.length > 0) { - $(".msg-sync-list").removeClass("hide"); - } else { - $(".msg-no-sync").removeClass("hide"); - } - _.each(syncIndexList, function(syncIndex) { - var syncAttributes = JSON.parse(localStorage[syncIndex]); - var syncDesc = syncAttributes.id || syncAttributes.path; - var lineElement = $(_.template(lineTemplate, { - provider: providerMap[syncAttributes.provider], - syncDesc: syncDesc - })); - lineElement.append($(removeButtonTemplate).click(function() { - core.fileManager.removeSync(syncIndex); - core.fileManager.updateFileTitles(); - })); - syncList.append(lineElement); - }); - }; - - // Used to enable/disable provider synchronization - synchronizer.resetSyncFlags = function() { - _.each(providerMap, function(provider) { - provider.useSync = false; - }); - }; - synchronizer.getSyncAttributesFromFile = function(fileIndex) { - var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); - var attributesList = []; - _.each(syncIndexList, function(syncIndex) { - var syncAttributes = JSON.parse(localStorage[syncIndex]); - attributesList.push(syncAttributes); - providerMap[syncAttributes.provider].useSync = true; - }); - return attributesList; + // Retrieve file's sync locations from localStorage + publisher.populateSyncLocations = function(fileDesc) { + _.chain(localStorage[fileDesc.index + ".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; + }); }; // Initialize the export dialog @@ -269,19 +247,14 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under $(".action-sync-export-" + provider.providerId).click(function(event) { // Perform the provider's export - var fileIndex = core.fileManager.getCurrentFileIndex(); - var title = localStorage[fileIndex + ".title"]; - var content = localStorage[fileIndex + ".content"]; - provider.exportFile(event, title, content, function(error, syncIndex) { + var fileDesc = core.fileManager.getCurrentFile(); + var title = fileDesc.title; + var content = localStorage[fileDesc.index + ".content"]; + provider.exportFile(event, title, content, function(error, syncIndex, syncAttributes) { if(error) { return; } - // Link syncIndex with fileIndex - localStorage[fileIndex + ".sync"] += syncIndex + ";"; - synchronizer.refreshManageSync(); - core.fileManager.updateFileTitles(); - core.showMessage('"' + title - + '" will now be synchronized on ' + provider.providerName + '.'); + core.fileManager.addSync(fileDesc, syncIndex, syncAttributes); }); // Store input values as preferences for next time we open the export dialog @@ -293,18 +266,14 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under }); // Provider's manual export button $(".action-sync-manual-" + provider.providerId).click(function(event) { - var fileIndex = core.fileManager.getCurrentFileIndex(); - var title = localStorage[fileIndex + ".title"]; - var content = localStorage[fileIndex + ".content"]; - provider.exportManual(event, title, content, function(error, syncIndex) { + var fileDesc = core.fileManager.getCurrentFile(); + var title = fileDesc.title; + var content = localStorage[fileDesc.index + ".content"]; + provider.exportManual(event, title, content, function(error, syncIndex, syncAttributes) { if(error) { return; } - localStorage[fileIndex + ".sync"] += syncIndex + ";"; - synchronizer.refreshManageSync(); - core.fileManager.updateFileTitles(); - core.showMessage('"' + title - + '" will now be synchronized on ' + provider.providerName + '.'); + core.fileManager.addSync(fileDesc, syncIndex, syncAttributes); }); }); }); @@ -317,5 +286,6 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under }); }); + extensionManager.onSynchronizerCreated(synchronizer); return synchronizer; }); diff --git a/js/utils.js b/js/utils.js index 1ba5bb64..4759f38f 100644 --- a/js/utils.js +++ b/js/utils.js @@ -246,6 +246,15 @@ define([ "jquery", "underscore" ], function($) { utils.randomString = function() { return _.random(4294967296).toString(36); }; + + // Serialize sync/publish attributes + utils.serializeAttributes = function(attributes) { + // Don't store sync/publish index + attributes = _.omit(attributes, "syncIndex", "publishIndex"); + // Store providerId instead of provider + attributes.provider = attributes.provider.providerId; + return JSON.stringify(attributes); + }; return utils;