New extension pattern

This commit is contained in:
benweet 2013-05-29 00:41:09 +01:00
parent 569a52ca21
commit a7a5defbc7
25 changed files with 244 additions and 176 deletions

View File

@ -174,6 +174,31 @@ input[readonly], select[readonly], textarea[readonly] {
margin-right: 10px; margin-right: 10px;
} }
#extension-buttons {
margin-right: 15px;
}
#extension-buttons > .btn-group {
margin: 5px 0 0;
}
#extension-buttons > .btn-group > .btn {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
#extension-buttons > .btn-group:first-child > .btn {
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
#extension-buttons > .btn-group:last-child > .btn {
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0;
}
.btn-group > .btn + .dropdown-toggle { .btn-group > .btn + .dropdown-toggle {
padding-right: 12px; padding-right: 12px;
padding-left: 12px; padding-left: 12px;
@ -405,7 +430,7 @@ div.dropdown-menu i {
width: 43px; width: 43px;
height: 11px; height: 11px;
background-position: 0 0; background-position: 0 0;
margin: 14px 15px 0; margin: 16px 16px 0;
} }
.working-indicator.show { .working-indicator.show {

View File

@ -34,31 +34,7 @@
<li><div id="wmd-button-bar"></div></li> <li><div id="wmd-button-bar"></div></li>
</ul> </ul>
<ul class="nav pull-right hide" id="menu-bar"> <ul class="nav pull-right hide" id="menu-bar">
<li class="btn-group"> <li id="extension-buttons">
<button class="btn action-force-sync"
title="Synchronize all documents">
<i class="icon-refresh"></i>
</button>
<button class="btn action-force-publish"
title="Publish this document">
<i class="icon-share"></i>
</button>
<button class="btn dropdown-toggle" data-toggle="dropdown"
title="Share this document">
<i class="icon-link"></i>
</button>
<div id="link-container" class="dropdown-menu">
<div class="link-list"></div>
<p class="no-link">To share this document you need first to <a
href="#" class="action-publish-gist">publish it as a Gist</a> in
Markdown format.
</p>
<blockquote class="muted">
<b>NOTE:</b> You can open any URL within StackEdit using <a
href="viewer.html?url=https://raw.github.com/benweet/stackedit/master/README.md"
title="Sharing example">viewer.html?url=...</a>
</blockquote>
</div>
</li> </li>
<li class="btn-group"><button class="btn action-create-file" <li class="btn-group"><button class="btn action-create-file"
title="New local document"> title="New local document">

View File

@ -12,6 +12,7 @@ define([
var asyncRunner = {}; var asyncRunner = {};
var taskQueue = []; var taskQueue = [];
var asyncRunning = false;
var currentTask = undefined; var currentTask = undefined;
var currentTaskRunning = false; var currentTaskRunning = false;
var currentTaskStartTime = 0; var currentTaskStartTime = 0;
@ -137,8 +138,11 @@ define([
// Dequeue an enqueued task // Dequeue an enqueued task
currentTask = taskQueue.shift(); currentTask = taskQueue.shift();
currentTaskStartTime = utils.currentTime; currentTaskStartTime = utils.currentTime;
if(asyncRunning === false) {
asyncRunning = true;
extensionMgr.onAsyncRunning(true); extensionMgr.onAsyncRunning(true);
} }
}
// Run the task // Run the task
if (currentTaskStartTime <= utils.currentTime) { if (currentTaskStartTime <= utils.currentTime) {
@ -155,13 +159,15 @@ define([
_.each(callbacks, function(callback) { _.each(callbacks, function(callback) {
callback(param); callback(param);
}); });
} finally { }
finally {
task.finished = true; task.finished = true;
if (currentTask === task) { if (currentTask === task) {
currentTask = undefined; currentTask = undefined;
currentTaskRunning = false; currentTaskRunning = false;
} }
if (taskQueue.length === 0) { if (taskQueue.length === 0) {
asyncRunning = false;
extensionMgr.onAsyncRunning(false); extensionMgr.onAsyncRunning(false);
} else { } else {
asyncRunner.runTask(); asyncRunner.runTask();

View File

@ -261,8 +261,8 @@ define([
}; };
}; };
} }
extensionMgr.onEditorConfigure(editor);
editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview); editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview);
extensionMgr.onEditorConfigure(editor);
$("#wmd-input, #wmd-preview").scrollTop(0); $("#wmd-input, #wmd-preview").scrollTop(0);
$("#wmd-button-bar").empty(); $("#wmd-button-bar").empty();

View File

@ -230,7 +230,7 @@ define([
function handleError(error, task) { function handleError(error, task) {
var errorMsg = true; var errorMsg = true;
if (error) { if (error) {
console.error(error); logger.error(error);
// Try to analyze the error // Try to analyze the error
if (typeof error === "string") { if (typeof error === "string") {
errorMsg = error; errorMsg = error;

View File

@ -39,6 +39,7 @@ define([
syncAttributes.version = versionTag; syncAttributes.version = versionTag;
syncAttributes.contentCRC = utils.crc32(content); syncAttributes.contentCRC = utils.crc32(content);
syncAttributes.syncIndex = createSyncIndex(path); syncAttributes.syncIndex = createSyncIndex(path);
utils.storeAttributes(syncAttributes);
return syncAttributes; return syncAttributes;
} }
@ -54,7 +55,6 @@ define([
var fileDescList = []; var fileDescList = [];
_.each(result, function(file) { _.each(result, function(file) {
var syncAttributes = createSyncAttributes(file.path, file.versionTag, file.content); var syncAttributes = createSyncAttributes(file.path, file.versionTag, file.content);
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
var syncLocations = {}; var syncLocations = {};
syncLocations[syncAttributes.syncIndex] = syncAttributes; syncLocations[syncAttributes.syncIndex] = syncAttributes;
var fileDesc = fileMgr.createFile(file.name, file.content, syncLocations); var fileDesc = fileMgr.createFile(file.name, file.content, syncLocations);
@ -106,7 +106,6 @@ define([
return; return;
} }
var syncAttributes = createSyncAttributes(result.path, result.versionTag, content); var syncAttributes = createSyncAttributes(result.path, result.versionTag, content);
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
callback(undefined, syncAttributes); callback(undefined, syncAttributes);
}); });
} }
@ -185,7 +184,7 @@ define([
fileMgr.removeSync(syncAttributes); fileMgr.removeSync(syncAttributes);
return; return;
} }
var localContent = localStorage[fileDesc.fileIndex + ".content"]; var localContent = fileDesc.getContent();
var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent);
var file = change.stat; var file = change.stat;
var remoteContentCRC = utils.crc32(file.content); var remoteContentCRC = utils.crc32(file.content);
@ -193,13 +192,12 @@ define([
var fileContentChanged = localContent != file.content; var fileContentChanged = localContent != file.content;
// Conflict detection // Conflict detection
if (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true) { if (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true) {
var backupFileDesc = fileMgr.createFile(localTitle + " (backup)", localContent); fileMgr.createFile(localTitle + " (backup)", localContent);
extensionMgr.onTitleChanged(backupFileDesc);
extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.'); extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.');
} }
// If file content changed // If file content changed
if(fileContentChanged && remoteContentChanged === true) { if(fileContentChanged && remoteContentChanged === true) {
localStorage[fileDesc.fileIndex + ".content"] = file.content; fileDesc.setContent(file.content);
extensionMgr.onMessage('"' + localTitle + '" has been updated from Dropbox.'); extensionMgr.onMessage('"' + localTitle + '" has been updated from Dropbox.');
if(fileMgr.isCurrentFile(fileDesc)) { if(fileMgr.isCurrentFile(fileDesc)) {
fileMgr.selectFile(); // Refresh editor fileMgr.selectFile(); // Refresh editor
@ -208,7 +206,7 @@ define([
// Update syncAttributes // Update syncAttributes
syncAttributes.version = file.versionTag; syncAttributes.version = file.versionTag;
syncAttributes.contentCRC = remoteContentCRC; syncAttributes.contentCRC = remoteContentCRC;
localStorage[syncIndex] = utils.serializeAttributes(syncAttributes); utils.storeAttributes(syncAttributes);
}); });
localStorage[PROVIDER_DROPBOX + ".lastChangeId"] = newChangeId; localStorage[PROVIDER_DROPBOX + ".lastChangeId"] = newChangeId;
callback(); callback();

View File

@ -3,9 +3,9 @@ define( [
"underscore", "underscore",
"utils", "utils",
"settings", "settings",
"extensions/button-sync",
"extensions/button-publish", "extensions/button-publish",
"extensions/button-share", "extensions/button-share",
"extensions/button-sync",
"extensions/document-selector", "extensions/document-selector",
"extensions/document-title", "extensions/document-title",
"extensions/manage-publication", "extensions/manage-publication",
@ -42,7 +42,7 @@ define( [
function createHook(hookName) { function createHook(hookName) {
var callbackList = getExtensionCallbackList(hookName); var callbackList = getExtensionCallbackList(hookName);
return function() { return function() {
console.debug(hookName); logger.debug(hookName, arguments);
var callbackArguments = arguments; var callbackArguments = arguments;
_.each(callbackList, function(callback) { _.each(callbackList, function(callback) {
callback.apply(null, callbackArguments); callback.apply(null, callbackArguments);
@ -64,7 +64,7 @@ define( [
// Load/Save extension config from/to settings // Load/Save extension config from/to settings
extensionMgr["onLoadSettings"] = function() { extensionMgr["onLoadSettings"] = function() {
console.debug("onLoadSettings"); logger.debug("onLoadSettings");
_.each(extensionList, function(extension) { _.each(extensionList, function(extension) {
utils.setInputChecked("#input-enable-extension-" + extension.extensionId, extension.config.enabled); utils.setInputChecked("#input-enable-extension-" + extension.extensionId, extension.config.enabled);
var onLoadSettingsCallback = extension.onLoadSettings; var onLoadSettingsCallback = extension.onLoadSettings;
@ -72,7 +72,7 @@ define( [
}); });
}; };
extensionMgr["onSaveSettings"] = function(newExtensionSettings, event) { extensionMgr["onSaveSettings"] = function(newExtensionSettings, event) {
console.debug("onSaveSettings"); logger.debug("onSaveSettings");
_.each(extensionList, function(extension) { _.each(extensionList, function(extension) {
var newExtensionConfig = extension.defaultConfig || {}; var newExtensionConfig = extension.defaultConfig || {};
newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId); newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
@ -88,17 +88,16 @@ define( [
addHook("onOfflineChanged"); addHook("onOfflineChanged");
addHook("onAsyncRunning"); addHook("onAsyncRunning");
// To store reference to modules that are accessible from extensions // To access modules that are loaded after extensions
addHook("onFileMgrCreated"); addHook("onFileMgrCreated");
addHook("onSynchronizerCreated"); addHook("onSynchronizerCreated");
addHook("onPublisherCreated"); addHook("onPublisherCreated");
// Operations on files // Operations on files
addHook("onFileSystemCreated");
addHook("onFileCreated"); addHook("onFileCreated");
addHook("onFileDeleted"); addHook("onFileDeleted");
addHook("onFileChanged");
addHook("onFileSelected"); addHook("onFileSelected");
addHook("onContentChanged");
addHook("onTitleChanged"); addHook("onTitleChanged");
// Sync events // Sync events
@ -124,7 +123,7 @@ define( [
var onPreviewFinished = createHook("onPreviewFinished"); var onPreviewFinished = createHook("onPreviewFinished");
var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview"); var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview");
extensionMgr["onAsyncPreview"] = function() { extensionMgr["onAsyncPreview"] = function() {
console.debug("onAsyncPreview"); logger.debug("onAsyncPreview");
// Call onPreviewFinished callbacks when all async preview are finished // Call onPreviewFinished callbacks when all async preview are finished
var counter = 0; var counter = 0;
function tryFinished() { function tryFinished() {
@ -172,6 +171,14 @@ define( [
).sortBy(function(extension) { ).sortBy(function(extension) {
return extension.extensionName.toLowerCase(); return extension.extensionName.toLowerCase();
}).each(createSettings); }).each(createSettings);
// Create extension buttons
logger.debug("onCreateButton");
var onCreateButtonCallbackList = getExtensionCallbackList("onCreateButton");
_.each(onCreateButtonCallbackList, function(callback) {
$("#extension-buttons").append($('<div class="btn-group">').append(callback()));
});
}); });
return extensionMgr; return extensionMgr;

View File

@ -9,20 +9,42 @@ define([
settingsBloc: '<p>Adds a "Publish document" button in the navigation bar.</p>' settingsBloc: '<p>Adds a "Publish document" button in the navigation bar.</p>'
}; };
var button = undefined;
var currentFileDesc = undefined; var currentFileDesc = undefined;
var publishRunning = false; var publishRunning = false;
var hasPublications = false; var hasPublications = false;
var isOffline = false; var isOffline = false;
// Enable/disable the button // Enable/disable the button
function updateButtonState() { function updateButtonState() {
if(button === undefined) {
return;
}
if(publishRunning === true || hasPublications === false || isOffline === true) { if(publishRunning === true || hasPublications === false || isOffline === true) {
$(".action-force-publish").addClass("disabled"); button.addClass("disabled");
} }
else { else {
$(".action-force-publish").removeClass("disabled"); button.removeClass("disabled");
} }
}; };
var publisher = undefined;
buttonPublish.onPublisherCreated = function(publisherParameter) {
publisher = publisherParameter;
};
buttonPublish.onCreateButton = function() {
button = $([
'<button class="btn" title="Publish this document">',
'<i class="icon-share"></i>',
'</button>'].join("")
).click(function() {
if(!$(this).hasClass("disabled")) {
publisher.publish();
}
});
return button;
};
buttonPublish.onPublishRunning = function(isRunning) { buttonPublish.onPublishRunning = function(isRunning) {
publishRunning = isRunning; publishRunning = isRunning;
updateButtonState(); updateButtonState();

View File

@ -10,6 +10,26 @@ define([
settingsBloc: '<p>Adds a "Share document" button in the navigation bar.</p>' settingsBloc: '<p>Adds a "Share document" button in the navigation bar.</p>'
}; };
buttonShare.onCreateButton = function() {
return $([
'<button class="btn dropdown-toggle" data-toggle="dropdown" title="Share this document">',
'<i class="icon-link"></i>',
'</button>',
'<div id="link-container" class="dropdown-menu pull-right">',
'<div class="link-list"></div>',
'<p class="no-link">To share this document you need first to <a',
'href="#" class="action-publish-gist">publish it as a Gist</a> in',
'Markdown format.',
'</p>',
'<blockquote class="muted">',
'<b>NOTE:</b> You can open any URL within StackEdit using <a',
'href="viewer.html?url=https://raw.github.com/benweet/stackedit/master/README.md"',
'title="Sharing example">viewer.html?url=...</a>',
'</blockquote>',
'</div>'].join("")
);
};
var fileDesc = undefined; var fileDesc = undefined;
var lineTemplate = [ var lineTemplate = [
'<div class="input-prepend">', '<div class="input-prepend">',

View File

@ -9,19 +9,43 @@ define([
settingsBloc: '<p>Adds a "Synchronize documents" button in the navigation bar.</p>' settingsBloc: '<p>Adds a "Synchronize documents" button in the navigation bar.</p>'
}; };
var button = undefined;
var syncRunning = false; var syncRunning = false;
var uploadPending = false; var uploadPending = false;
var isOffline = false; var isOffline = false;
// Enable/disable the button // Enable/disable the button
var updateButtonState = function() { var updateButtonState = function() {
if(button === undefined) {
return;
}
if(syncRunning === true || uploadPending === false || isOffline) { if(syncRunning === true || uploadPending === false || isOffline) {
$(".action-force-sync").addClass("disabled"); button.addClass("disabled");
} }
else { else {
$(".action-force-sync").removeClass("disabled"); button.removeClass("disabled");
} }
}; };
var synchronizer = undefined;
buttonSync.onSynchronizerCreated = function(synchronizerParameter) {
synchronizer = synchronizerParameter;
};
buttonSync.onCreateButton = function() {
button = $([
'<button class="btn" title="Synchronize all documents">',
'<i class="icon-refresh"></i>',
'</button>'].join("")
).click(function() {
if(!$(this).hasClass("disabled")) {
synchronizer.forceSync();
}
});
return button;
};
buttonSync.onReady = updateButtonState;
buttonSync.onSyncRunning = function(isRunning) { buttonSync.onSyncRunning = function(isRunning) {
syncRunning = isRunning; syncRunning = isRunning;
uploadPending = true; uploadPending = true;
@ -38,8 +62,6 @@ define([
updateButtonState(); updateButtonState();
}; };
buttonSync.onReady = updateButtonState;
// Check that a file has synchronized locations // Check that a file has synchronized locations
var checkSynchronization = function(fileDesc) { var checkSynchronization = function(fileDesc) {
if(_.size(fileDesc.syncLocations) !== 0) { if(_.size(fileDesc.syncLocations) !== 0) {
@ -48,7 +70,7 @@ define([
} }
}; };
buttonSync.onFileChanged = checkSynchronization; buttonSync.onContentChanged = checkSynchronization;
buttonSync.onTitleChanged = checkSynchronization; buttonSync.onTitleChanged = checkSynchronization;
return buttonSync; return buttonSync;

View File

@ -1,7 +1,8 @@
define([ define([
"jquery", "jquery",
"underscore" "underscore",
], function($, _) { "file-system"
], function($, _, fileSystem) {
var documentSelector = { var documentSelector = {
extensionId: "documentSelector", extensionId: "documentSelector",
@ -9,11 +10,6 @@ define([
settingsBloc: '<p>Builds the "Open document" dropdown menu.</p>' settingsBloc: '<p>Builds the "Open document" dropdown menu.</p>'
}; };
var fileSystem = undefined;
documentSelector.onFileSystemCreated = function(fileSystemParameter) {
fileSystem = fileSystemParameter;
};
var fileMgr = undefined; var fileMgr = undefined;
documentSelector.onFileMgrCreated = function(fileMgrParameter) { documentSelector.onFileMgrCreated = function(fileMgrParameter) {
fileMgr = fileMgrParameter; fileMgr = fileMgrParameter;

View File

@ -37,11 +37,11 @@ define([
$(".msg-no-publish").removeClass("hide"); $(".msg-no-publish").removeClass("hide");
} }
_.each(publishAttributesList, function(publishAttributes) { _.each(publishAttributesList, function(publishAttributes) {
publishAttributes = _.extend({}, publishAttributes); formattedAttributes = _.omit(publishAttributes, "provider", "publishIndex", "sharingLink");
if(publishAttributes.password) { if(formattedAttributes.password) {
publishAttributes.password = "********"; formattedAttributes.password = "********";
} }
var publishDesc = JSON.stringify(publishAttributes).replace(/{|}|"/g, ""); var publishDesc = JSON.stringify(formattedAttributes).replace(/{|}|"/g, "").replace(/,/g, ", ");
var lineElement = $(_.template(lineTemplate, { var lineElement = $(_.template(lineTemplate, {
provider: publishAttributes.provider, provider: publishAttributes.provider,
publishDesc: publishDesc publishDesc: publishDesc

View File

@ -58,12 +58,12 @@ define([
} }
notifications.onMessage = function(message) { notifications.onMessage = function(message) {
console.log(message); logger.log(message);
showMessage(message); showMessage(message);
}; };
notifications.onError = function(error) { notifications.onError = function(error) {
console.error(error); logger.error(error);
if(_.isString(error)) { if(_.isString(error)) {
showMessage(error, "icon-warning-sign"); showMessage(error, "icon-warning-sign");
} }

View File

@ -11,13 +11,35 @@ define([
var fileMgr = {}; var fileMgr = {};
// Defines the current file // Defines a file descriptor in the file system (fileDesc objects)
var currentFile = (function() { function FileDescriptor(fileIndex, title, syncLocations, publishLocations) {
var fileIndex = localStorage["file.current"]; this.fileIndex = fileIndex;
if(fileIndex !== undefined) { this.title = title;
return fileSystem[fileIndex]; 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() { fileMgr.getCurrentFile = function() {
return currentFile; return currentFile;
}; };
@ -34,7 +56,7 @@ define([
} }
}; };
// Caution: this function recreate the editor (reset undo operations) // Caution: this function recreates the editor (reset undo operations)
fileMgr.selectFile = function(fileDesc) { fileMgr.selectFile = function(fileDesc) {
fileDesc = fileDesc || fileMgr.getCurrentFile(); fileDesc = fileDesc || fileMgr.getCurrentFile();
@ -44,11 +66,17 @@ define([
if (fileSystemSize === 0) { if (fileSystemSize === 0) {
fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent); fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent);
} }
// If no file is selected take the last created
else { else {
fileDesc = fileSystem[_.keys(fileSystem)[fileSystemSize - 1]]; 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); fileMgr.setCurrentFile(fileDesc);
// Notify extensions // Notify extensions
@ -61,9 +89,10 @@ define([
else { else {
$(".action-edit-document").addClass("hide"); $(".action-edit-document").addClass("hide");
} }
}
// Recreate the editor // Recreate the editor
$("#wmd-input").val(localStorage[fileDesc.fileIndex + ".content"]); $("#wmd-input").val(fileDesc.getContent());
core.createEditor(function() { core.createEditor(function() {
// Callback to save content when textarea changes // Callback to save content when textarea changes
fileMgr.saveFile(); fileMgr.saveFile();
@ -72,7 +101,6 @@ define([
fileMgr.createFile = function(title, content, syncLocations, isTemporary) { fileMgr.createFile = function(title, content, syncLocations, isTemporary) {
content = content !== undefined ? content : settings.defaultContent; content = content !== undefined ? content : settings.defaultContent;
syncLocations = syncLocations || {};
if (!title) { if (!title) {
// Create a file title // Create a file title
title = DEFAULT_FILE_TITLE; title = DEFAULT_FILE_TITLE;
@ -92,24 +120,19 @@ define([
} while(_.has(fileSystem, fileIndex)); } while(_.has(fileSystem, fileIndex));
} }
// Create the file in the localStorage // syncIndex associations
localStorage[fileIndex + ".content"] = content; syncLocations = syncLocations || {};
localStorage[fileIndex + ".title"] = title;
// Store syncIndexes associated to the file
var sync = _.reduce(syncLocations, function(sync, syncAttributes, syncIndex) { var sync = _.reduce(syncLocations, function(sync, syncAttributes, syncIndex) {
return sync + syncIndex + ";"; return sync + syncIndex + ";";
}, ";"); }, ";");
localStorage[fileIndex + ".title"] = title;
localStorage[fileIndex + ".content"] = content;
localStorage[fileIndex + ".sync"] = sync; localStorage[fileIndex + ".sync"] = sync;
// Store publishIndexes associated to the file
localStorage[fileIndex + ".publish"] = ";"; localStorage[fileIndex + ".publish"] = ";";
// Create the file descriptor // Create the file descriptor
var fileDesc = { var fileDesc = new FileDescriptor(fileIndex, title, syncLocations);
fileIndex : fileIndex,
title : title,
syncLocations: syncLocations,
publishLocations: {}
};
// Add the index to the file list // Add the index to the file list
if(!isTemporary) { if(!isTemporary) {
@ -122,9 +145,11 @@ define([
fileMgr.deleteFile = function(fileDesc) { fileMgr.deleteFile = function(fileDesc) {
fileDesc = fileDesc || fileMgr.getCurrentFile(); fileDesc = fileDesc || fileMgr.getCurrentFile();
if(fileMgr.isCurrentFile(fileDesc)) { if(fileMgr.isCurrentFile(fileDesc) === true) {
// Unset the current fileDesc // Unset the current fileDesc
fileMgr.setCurrentFile(); fileMgr.setCurrentFile();
// Refresh the editor with an other file
fileMgr.selectFile();
} }
// Remove synchronized locations // Remove synchronized locations
@ -141,10 +166,12 @@ define([
var fileIndex = fileDesc.fileIndex; var fileIndex = fileDesc.fileIndex;
localStorage["file.list"] = localStorage["file.list"].replace(";" localStorage["file.list"] = localStorage["file.list"].replace(";"
+ fileIndex + ";", ";"); + fileIndex + ";", ";");
localStorage.removeItem(fileIndex + ".title"); localStorage.removeItem(fileIndex + ".title");
localStorage.removeItem(fileIndex + ".content"); localStorage.removeItem(fileIndex + ".content");
localStorage.removeItem(fileIndex + ".sync"); localStorage.removeItem(fileIndex + ".sync");
localStorage.removeItem(fileIndex + ".publish"); localStorage.removeItem(fileIndex + ".publish");
fileSystem.removeItem(fileIndex); fileSystem.removeItem(fileIndex);
extensionMgr.onFileDeleted(fileDesc); extensionMgr.onFileDeleted(fileDesc);
}; };
@ -153,8 +180,7 @@ define([
fileMgr.saveFile = function() { fileMgr.saveFile = function() {
var content = $("#wmd-input").val(); var content = $("#wmd-input").val();
var fileDesc = fileMgr.getCurrentFile(); var fileDesc = fileMgr.getCurrentFile();
localStorage[fileDesc.fileIndex + ".content"] = content; fileDesc.setContent(content);
extensionMgr.onFileChanged(fileDesc);
}; };
// Add a synchronized location to a file // Add a synchronized location to a file
@ -211,7 +237,7 @@ define([
// Remove a publishIndex (publish location) // Remove a publishIndex (publish location)
fileMgr.removePublish = function(publishAttributes, skipExtensions) { fileMgr.removePublish = function(publishAttributes, skipExtensions) {
var fileDesc = fileMgr.getFileFromPublish(publishAttributes.publishIndex); var fileDesc = fileMgr.getFileFromPublishIndex(publishAttributes.publishIndex);
if(fileDesc !== undefined) { if(fileDesc !== undefined) {
localStorage[fileDesc.fileIndex + ".publish"] = localStorage[fileDesc.fileIndex + ".publish"].replace(";" localStorage[fileDesc.fileIndex + ".publish"] = localStorage[fileDesc.fileIndex + ".publish"].replace(";"
+ publishAttributes.publishIndex + ";", ";"); + publishAttributes.publishIndex + ";", ";");
@ -225,7 +251,7 @@ define([
}; };
// Get the file descriptor associated to a publishIndex // Get the file descriptor associated to a publishIndex
fileMgr.getFileFromPublish = function(publishIndex) { fileMgr.getFileFromPublishIndex = function(publishIndex) {
return _.find(fileSystem, function(fileDesc) { return _.find(fileSystem, function(fileDesc) {
return _.has(fileDesc.publishLocations, publishIndex); return _.has(fileDesc.publishLocations, publishIndex);
}); });
@ -263,7 +289,6 @@ define([
}); });
$(".action-remove-file").click(function() { $(".action-remove-file").click(function() {
fileMgr.deleteFile(); fileMgr.deleteFile();
fileMgr.selectFile();
}); });
$("#file-title").click(function() { $("#file-title").click(function() {
if(viewerMode === true) { if(viewerMode === true) {
@ -280,15 +305,10 @@ define([
$("#file-title").show(); $("#file-title").show();
var title = $.trim(input.val()); var title = $.trim(input.val());
var fileDesc = fileMgr.getCurrentFile(); var fileDesc = fileMgr.getCurrentFile();
var fileIndexTitle = fileDesc.fileIndex + ".title"; if (title && title != fileDesc.title) {
if (title) { fileDesc.setTitle(title);
if (title != localStorage[fileIndexTitle]) {
localStorage[fileIndexTitle] = title;
fileDesc.title = title;
extensionMgr.onTitleChanged(fileDesc);
} }
} input.val(fileDesc.title);
input.val(localStorage[fileIndexTitle]);
$("#wmd-input").focus(); $("#wmd-input").focus();
} }
$("#file-title-input").blur(function() { $("#file-title-input").blur(function() {

View File

@ -1,22 +1,3 @@
define([ // The fileSystem module is empty when created. It's filled by fileMgr when loading.
"underscore", // syncLocations and publishLocations are respectively loaded by synchronizer and publisher.
"extension-manager" define({});
], function(_, extensionMgr) {
var fileSystem = {};
// Load file descriptors from localStorage
_.chain(
localStorage["file.list"].split(";")
).compact().each(function(fileIndex) {
fileSystem[fileIndex] = {
fileIndex : fileIndex,
title : localStorage[fileIndex + ".title"],
syncLocations: {},
publishLocations: {}
};
});
extensionMgr.onFileSystemCreated(fileSystem);
return fileSystem;
});

View File

@ -28,6 +28,7 @@ define([
syncAttributes.contentCRC = utils.crc32(content); syncAttributes.contentCRC = utils.crc32(content);
syncAttributes.titleCRC = utils.crc32(title); syncAttributes.titleCRC = utils.crc32(title);
syncAttributes.syncIndex = createSyncIndex(id); syncAttributes.syncIndex = createSyncIndex(id);
utils.storeAttributes(syncAttributes);
return syncAttributes; return syncAttributes;
} }
@ -43,7 +44,6 @@ define([
var fileDescList = []; var fileDescList = [];
_.each(result, function(file) { _.each(result, function(file) {
var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title); var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
var syncLocations = {}; var syncLocations = {};
syncLocations[syncAttributes.syncIndex] = syncAttributes; syncLocations[syncAttributes.syncIndex] = syncAttributes;
var fileDesc = fileMgr.createFile(file.title, file.content, syncLocations); var fileDesc = fileMgr.createFile(file.title, file.content, syncLocations);
@ -82,7 +82,6 @@ define([
return; return;
} }
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); var syncAttributes = createSyncAttributes(result.id, result.etag, content, title);
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
callback(undefined, syncAttributes); callback(undefined, syncAttributes);
}); });
}; };
@ -92,7 +91,7 @@ define([
if(!id) { if(!id) {
return; return;
} }
// Check that file is not synchronized with an other one // Check that file is not synchronized with another an existing document
var syncIndex = createSyncIndex(id); var syncIndex = createSyncIndex(id);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) { if(fileDesc !== undefined) {
@ -106,7 +105,6 @@ define([
return; return;
} }
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); var syncAttributes = createSyncAttributes(result.id, result.etag, content, title);
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
callback(undefined, syncAttributes); callback(undefined, syncAttributes);
}); });
}; };
@ -178,7 +176,7 @@ define([
return; return;
} }
var localTitleChanged = syncAttributes.titleCRC != utils.crc32(localTitle); var localTitleChanged = syncAttributes.titleCRC != utils.crc32(localTitle);
var localContent = localStorage[fileDesc.fileIndex + ".content"]; var localContent = fileDesc.getContent();
var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent);
var file = change.file; var file = change.file;
var remoteTitleCRC = utils.crc32(file.title); var remoteTitleCRC = utils.crc32(file.title);
@ -190,20 +188,17 @@ define([
// Conflict detection // Conflict detection
if ((fileTitleChanged === true && localTitleChanged === true && remoteTitleChanged === true) if ((fileTitleChanged === true && localTitleChanged === true && remoteTitleChanged === true)
|| (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true)) { || (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true)) {
var backupFileDesc = fileMgr.createFile(localTitle + " (backup)", localContent); fileMgr.createFile(localTitle + " (backup)", localContent);
extensionMgr.onTitleChanged(backupFileDesc);
extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.'); extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.');
} }
// If file title changed // If file title changed
if(fileTitleChanged && remoteTitleChanged === true) { if(fileTitleChanged && remoteTitleChanged === true) {
localStorage[fileDesc.fileIndex + ".title"] = file.title; fileDesc.setTitle(file.title);
fileDesc.title = file.title;
extensionMgr.onTitleChanged(fileDesc);
extensionMgr.onMessage('"' + localTitle + '" has been renamed to "' + file.title + '" on Google Drive.'); extensionMgr.onMessage('"' + localTitle + '" has been renamed to "' + file.title + '" on Google Drive.');
} }
// If file content changed // If file content changed
if(fileContentChanged && remoteContentChanged === true) { if(fileContentChanged && remoteContentChanged === true) {
localStorage[fileDesc.fileIndex + ".content"] = file.content; fileDesc.setContent(file.content);
extensionMgr.onMessage('"' + file.title + '" has been updated from Google Drive.'); extensionMgr.onMessage('"' + file.title + '" has been updated from Google Drive.');
if(fileMgr.isCurrentFile(fileDesc)) { if(fileMgr.isCurrentFile(fileDesc)) {
fileMgr.selectFile(); // Refresh editor fileMgr.selectFile(); // Refresh editor
@ -213,7 +208,7 @@ define([
syncAttributes.etag = file.etag; syncAttributes.etag = file.etag;
syncAttributes.contentCRC = remoteContentCRC; syncAttributes.contentCRC = remoteContentCRC;
syncAttributes.titleCRC = remoteTitleCRC; syncAttributes.titleCRC = remoteTitleCRC;
localStorage[syncIndex] = utils.serializeAttributes(syncAttributes); utils.storeAttributes(syncAttributes);
}); });
localStorage[PROVIDER_GDRIVE + ".lastChangeId"] = newChangeId; localStorage[PROVIDER_GDRIVE + ".lastChangeId"] = newChangeId;
callback(); callback();
@ -263,7 +258,6 @@ define([
return; return;
} }
var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title); var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
var syncLocations = {}; var syncLocations = {};
syncLocations[syncAttributes.syncIndex] = syncAttributes; syncLocations[syncAttributes.syncIndex] = syncAttributes;
var fileDesc = fileMgr.createFile(file.title, file.content, syncAttributes); var fileDesc = fileMgr.createFile(file.title, file.content, syncAttributes);

View File

@ -224,7 +224,7 @@ define([
function handleError(error, task) { function handleError(error, task) {
var errorMsg = undefined; var errorMsg = undefined;
if (error) { if (error) {
console.error(error); logger.error(error);
// Try to analyze the error // Try to analyze the error
if (typeof error === "string") { if (typeof error === "string") {
errorMsg = error; errorMsg = error;

View File

@ -326,7 +326,7 @@ define([
function handleError(error, task) { function handleError(error, task) {
var errorMsg = undefined; var errorMsg = undefined;
if (error) { if (error) {
console.error(error); logger.error(error);
// Try to analyze the error // Try to analyze the error
if (typeof error === "string") { if (typeof error === "string") {
errorMsg = error; errorMsg = error;

View File

@ -23,6 +23,20 @@ requirejs.config({
} }
}); });
// Defines the logger object
var logger = {
debug: function() {},
log: function() {},
info: function() {},
warn: function() {},
error: function() {}
};
// Use http://.../?console to print logs in the console
if (location.search.indexOf("console") !== -1) {
logger = console;
}
// RequireJS entry point. By requiring synchronizer and publisher, we are actually loading all the modules
require([ require([
"jquery", "jquery",
"core", "core",
@ -31,6 +45,7 @@ require([
], function($, core) { ], function($, core) {
$(function() { $(function() {
// If browser has detected a new application cache. // If browser has detected a new application cache.
if (window.applicationCache) { if (window.applicationCache) {
window.applicationCache.addEventListener('updateready', function(e) { window.applicationCache.addEventListener('updateready', function(e) {
@ -41,6 +56,7 @@ require([
}, false); }, false);
} }
// Here, all the modules are loaded and the DOM is ready
core.setReady(); core.setReady();
}); });

View File

@ -132,7 +132,7 @@ define([
publishIndex = "publish." + utils.randomString(); publishIndex = "publish." + utils.randomString();
} while(_.has(localStorage, publishIndex)); } while(_.has(localStorage, publishIndex));
publishAttributes.publishIndex = publishIndex; publishAttributes.publishIndex = publishIndex;
localStorage[publishIndex] = utils.serializeAttributes(publishAttributes); utils.storeAttributes(publishAttributes);
fileMgr.addPublish(fileDesc, publishAttributes); fileMgr.addPublish(fileDesc, publishAttributes);
} }
@ -230,12 +230,6 @@ define([
$(".action-process-publish").click(performNewLocation); $(".action-process-publish").click(performNewLocation);
$(".action-force-publish").click(function() {
if(!$(this).hasClass("disabled")) {
publisher.publish();
}
});
// Save As menu items // Save As menu items
$(".action-download-md").click(function() { $(".action-download-md").click(function() {
var content = $("#wmd-input").val(); var content = $("#wmd-input").val();

View File

@ -63,7 +63,7 @@ define([
function handleError(error, task) { function handleError(error, task) {
var errorMsg = undefined; var errorMsg = undefined;
if (error) { if (error) {
console.error(error); logger.error(error);
// Try to analyze the error // Try to analyze the error
if (typeof error === "string") { if (typeof error === "string") {
errorMsg = "SSH error: " + error + "."; errorMsg = "SSH error: " + error + ".";

View File

@ -73,7 +73,7 @@ define([
} }
if(uploadFlag) { if(uploadFlag) {
// Update syncAttributes in localStorage // Update syncAttributes in localStorage
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes); utils.storeAttributes(syncAttributes);
} }
locationUp(callback); locationUp(callback);
} }
@ -90,7 +90,7 @@ define([
return; return;
} }
// Dequeue a fileDesc // Dequeue a fileDesc to synchronize
var fileDesc = uploadFileList.pop(); var fileDesc = uploadFileList.pop();
uploadSyncAttributesList = _.values(fileDesc.syncLocations); uploadSyncAttributesList = _.values(fileDesc.syncLocations);
if(uploadSyncAttributesList.length === 0) { if(uploadSyncAttributesList.length === 0) {
@ -99,7 +99,7 @@ define([
} }
// Get document title/content // Get document title/content
uploadContent = localStorage[fileDesc.fileIndex + ".content"]; uploadContent = fileDesc.getContent();
uploadContentCRC = utils.crc32(uploadContent); uploadContentCRC = utils.crc32(uploadContent);
uploadTitle = fileDesc.title; uploadTitle = fileDesc.title;
uploadTitleCRC = utils.crc32(uploadTitle); uploadTitleCRC = utils.crc32(uploadTitle);
@ -226,9 +226,7 @@ define([
// Perform the provider's export // Perform the provider's export
var fileDesc = fileMgr.getCurrentFile(); var fileDesc = fileMgr.getCurrentFile();
var title = fileDesc.title; provider.exportFile(event, fileDesc.title, fileDesc.getContent(), function(error, syncAttributes) {
var content = localStorage[fileDesc.fileIndex + ".content"];
provider.exportFile(event, title, content, function(error, syncAttributes) {
if(error) { if(error) {
return; return;
} }
@ -245,9 +243,7 @@ define([
// Provider's manual export button // Provider's manual export button
$(".action-sync-manual-" + provider.providerId).click(function(event) { $(".action-sync-manual-" + provider.providerId).click(function(event) {
var fileDesc = fileMgr.getCurrentFile(); var fileDesc = fileMgr.getCurrentFile();
var title = fileDesc.title; provider.exportManual(event, fileDesc.title, fileDesc.getContent(), function(error, syncAttributes) {
var content = localStorage[fileDesc.fileIndex + ".content"];
provider.exportManual(event, title, content, function(error, syncAttributes) {
if(error) { if(error) {
return; return;
} }
@ -255,12 +251,6 @@ define([
}); });
}); });
}); });
$(".action-force-sync").click(function() {
if(!$(this).hasClass("disabled")) {
synchronizer.forceSync();
}
});
}); });
extensionMgr.onSynchronizerCreated(synchronizer); extensionMgr.onSynchronizerCreated(synchronizer);

View File

@ -143,7 +143,7 @@ define([
function handleError(error, task) { function handleError(error, task) {
var errorMsg = undefined; var errorMsg = undefined;
if (error) { if (error) {
console.error(error); logger.error(error);
// Try to analyze the error // Try to analyze the error
if (typeof error === "string") { if (typeof error === "string") {
errorMsg = error; errorMsg = error;

View File

@ -298,13 +298,14 @@ define([
utils.updateCurrentTime(); utils.updateCurrentTime();
// Serialize sync/publish attributes // Serialize sync/publish attributes and store it in the fileStorage
utils.serializeAttributes = function(attributes) { utils.storeAttributes = function(attributes) {
var storeIndex = attributes.syncIndex || attributes.publishIndex;
// Don't store sync/publish index // Don't store sync/publish index
attributes = _.omit(attributes, "syncIndex", "publishIndex"); attributes = _.omit(attributes, "syncIndex", "publishIndex");
// Store providerId instead of provider // Store providerId instead of provider
attributes.provider = attributes.provider.providerId; attributes.provider = attributes.provider.providerId;
return JSON.stringify(attributes); localStorage[storeIndex] = JSON.stringify(attributes);
}; };
return utils; return utils;

View File

@ -141,7 +141,7 @@ define([
function handleError(error, task) { function handleError(error, task) {
var errorMsg = undefined; var errorMsg = undefined;
if (error) { if (error) {
console.error(error); logger.error(error);
// Try to analyze the error // Try to analyze the error
if (typeof error === "string") { if (typeof error === "string") {
errorMsg = error; errorMsg = error;