New extension pattern
This commit is contained in:
parent
569a52ca21
commit
a7a5defbc7
@ -174,6 +174,31 @@ input[readonly], select[readonly], textarea[readonly] {
|
||||
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 {
|
||||
padding-right: 12px;
|
||||
padding-left: 12px;
|
||||
@ -405,7 +430,7 @@ div.dropdown-menu i {
|
||||
width: 43px;
|
||||
height: 11px;
|
||||
background-position: 0 0;
|
||||
margin: 14px 15px 0;
|
||||
margin: 16px 16px 0;
|
||||
}
|
||||
|
||||
.working-indicator.show {
|
||||
|
26
index.html
26
index.html
@ -34,31 +34,7 @@
|
||||
<li><div id="wmd-button-bar"></div></li>
|
||||
</ul>
|
||||
<ul class="nav pull-right hide" id="menu-bar">
|
||||
<li class="btn-group">
|
||||
<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 id="extension-buttons">
|
||||
</li>
|
||||
<li class="btn-group"><button class="btn action-create-file"
|
||||
title="New local document">
|
||||
|
@ -12,6 +12,7 @@ define([
|
||||
var asyncRunner = {};
|
||||
|
||||
var taskQueue = [];
|
||||
var asyncRunning = false;
|
||||
var currentTask = undefined;
|
||||
var currentTaskRunning = false;
|
||||
var currentTaskStartTime = 0;
|
||||
@ -137,8 +138,11 @@ define([
|
||||
// Dequeue an enqueued task
|
||||
currentTask = taskQueue.shift();
|
||||
currentTaskStartTime = utils.currentTime;
|
||||
if(asyncRunning === false) {
|
||||
asyncRunning = true;
|
||||
extensionMgr.onAsyncRunning(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the task
|
||||
if (currentTaskStartTime <= utils.currentTime) {
|
||||
@ -155,13 +159,15 @@ define([
|
||||
_.each(callbacks, function(callback) {
|
||||
callback(param);
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
task.finished = true;
|
||||
if (currentTask === task) {
|
||||
currentTask = undefined;
|
||||
currentTaskRunning = false;
|
||||
}
|
||||
if (taskQueue.length === 0) {
|
||||
asyncRunning = false;
|
||||
extensionMgr.onAsyncRunning(false);
|
||||
} else {
|
||||
asyncRunner.runTask();
|
||||
|
@ -261,8 +261,8 @@ define([
|
||||
};
|
||||
};
|
||||
}
|
||||
extensionMgr.onEditorConfigure(editor);
|
||||
editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview);
|
||||
extensionMgr.onEditorConfigure(editor);
|
||||
|
||||
$("#wmd-input, #wmd-preview").scrollTop(0);
|
||||
$("#wmd-button-bar").empty();
|
||||
|
@ -230,7 +230,7 @@ define([
|
||||
function handleError(error, task) {
|
||||
var errorMsg = true;
|
||||
if (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
// Try to analyze the error
|
||||
if (typeof error === "string") {
|
||||
errorMsg = error;
|
||||
|
@ -39,6 +39,7 @@ define([
|
||||
syncAttributes.version = versionTag;
|
||||
syncAttributes.contentCRC = utils.crc32(content);
|
||||
syncAttributes.syncIndex = createSyncIndex(path);
|
||||
utils.storeAttributes(syncAttributes);
|
||||
return syncAttributes;
|
||||
}
|
||||
|
||||
@ -54,7 +55,6 @@ define([
|
||||
var fileDescList = [];
|
||||
_.each(result, function(file) {
|
||||
var syncAttributes = createSyncAttributes(file.path, file.versionTag, file.content);
|
||||
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
var syncLocations = {};
|
||||
syncLocations[syncAttributes.syncIndex] = syncAttributes;
|
||||
var fileDesc = fileMgr.createFile(file.name, file.content, syncLocations);
|
||||
@ -106,7 +106,6 @@ define([
|
||||
return;
|
||||
}
|
||||
var syncAttributes = createSyncAttributes(result.path, result.versionTag, content);
|
||||
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
callback(undefined, syncAttributes);
|
||||
});
|
||||
}
|
||||
@ -185,7 +184,7 @@ define([
|
||||
fileMgr.removeSync(syncAttributes);
|
||||
return;
|
||||
}
|
||||
var localContent = localStorage[fileDesc.fileIndex + ".content"];
|
||||
var localContent = fileDesc.getContent();
|
||||
var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent);
|
||||
var file = change.stat;
|
||||
var remoteContentCRC = utils.crc32(file.content);
|
||||
@ -193,13 +192,12 @@ define([
|
||||
var fileContentChanged = localContent != file.content;
|
||||
// Conflict detection
|
||||
if (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true) {
|
||||
var backupFileDesc = fileMgr.createFile(localTitle + " (backup)", localContent);
|
||||
extensionMgr.onTitleChanged(backupFileDesc);
|
||||
fileMgr.createFile(localTitle + " (backup)", localContent);
|
||||
extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.');
|
||||
}
|
||||
// If file content changed
|
||||
if(fileContentChanged && remoteContentChanged === true) {
|
||||
localStorage[fileDesc.fileIndex + ".content"] = file.content;
|
||||
fileDesc.setContent(file.content);
|
||||
extensionMgr.onMessage('"' + localTitle + '" has been updated from Dropbox.');
|
||||
if(fileMgr.isCurrentFile(fileDesc)) {
|
||||
fileMgr.selectFile(); // Refresh editor
|
||||
@ -208,7 +206,7 @@ define([
|
||||
// Update syncAttributes
|
||||
syncAttributes.version = file.versionTag;
|
||||
syncAttributes.contentCRC = remoteContentCRC;
|
||||
localStorage[syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
utils.storeAttributes(syncAttributes);
|
||||
});
|
||||
localStorage[PROVIDER_DROPBOX + ".lastChangeId"] = newChangeId;
|
||||
callback();
|
||||
|
@ -3,9 +3,9 @@ define( [
|
||||
"underscore",
|
||||
"utils",
|
||||
"settings",
|
||||
"extensions/button-sync",
|
||||
"extensions/button-publish",
|
||||
"extensions/button-share",
|
||||
"extensions/button-sync",
|
||||
"extensions/document-selector",
|
||||
"extensions/document-title",
|
||||
"extensions/manage-publication",
|
||||
@ -42,7 +42,7 @@ define( [
|
||||
function createHook(hookName) {
|
||||
var callbackList = getExtensionCallbackList(hookName);
|
||||
return function() {
|
||||
console.debug(hookName);
|
||||
logger.debug(hookName, arguments);
|
||||
var callbackArguments = arguments;
|
||||
_.each(callbackList, function(callback) {
|
||||
callback.apply(null, callbackArguments);
|
||||
@ -64,7 +64,7 @@ define( [
|
||||
|
||||
// Load/Save extension config from/to settings
|
||||
extensionMgr["onLoadSettings"] = function() {
|
||||
console.debug("onLoadSettings");
|
||||
logger.debug("onLoadSettings");
|
||||
_.each(extensionList, function(extension) {
|
||||
utils.setInputChecked("#input-enable-extension-" + extension.extensionId, extension.config.enabled);
|
||||
var onLoadSettingsCallback = extension.onLoadSettings;
|
||||
@ -72,7 +72,7 @@ define( [
|
||||
});
|
||||
};
|
||||
extensionMgr["onSaveSettings"] = function(newExtensionSettings, event) {
|
||||
console.debug("onSaveSettings");
|
||||
logger.debug("onSaveSettings");
|
||||
_.each(extensionList, function(extension) {
|
||||
var newExtensionConfig = extension.defaultConfig || {};
|
||||
newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
|
||||
@ -88,17 +88,16 @@ define( [
|
||||
addHook("onOfflineChanged");
|
||||
addHook("onAsyncRunning");
|
||||
|
||||
// To store reference to modules that are accessible from extensions
|
||||
// To access modules that are loaded after extensions
|
||||
addHook("onFileMgrCreated");
|
||||
addHook("onSynchronizerCreated");
|
||||
addHook("onPublisherCreated");
|
||||
|
||||
// Operations on files
|
||||
addHook("onFileSystemCreated");
|
||||
addHook("onFileCreated");
|
||||
addHook("onFileDeleted");
|
||||
addHook("onFileChanged");
|
||||
addHook("onFileSelected");
|
||||
addHook("onContentChanged");
|
||||
addHook("onTitleChanged");
|
||||
|
||||
// Sync events
|
||||
@ -124,7 +123,7 @@ define( [
|
||||
var onPreviewFinished = createHook("onPreviewFinished");
|
||||
var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview");
|
||||
extensionMgr["onAsyncPreview"] = function() {
|
||||
console.debug("onAsyncPreview");
|
||||
logger.debug("onAsyncPreview");
|
||||
// Call onPreviewFinished callbacks when all async preview are finished
|
||||
var counter = 0;
|
||||
function tryFinished() {
|
||||
@ -172,6 +171,14 @@ define( [
|
||||
).sortBy(function(extension) {
|
||||
return extension.extensionName.toLowerCase();
|
||||
}).each(createSettings);
|
||||
|
||||
// Create extension buttons
|
||||
logger.debug("onCreateButton");
|
||||
var onCreateButtonCallbackList = getExtensionCallbackList("onCreateButton");
|
||||
_.each(onCreateButtonCallbackList, function(callback) {
|
||||
$("#extension-buttons").append($('<div class="btn-group">').append(callback()));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return extensionMgr;
|
||||
|
@ -9,20 +9,42 @@ define([
|
||||
settingsBloc: '<p>Adds a "Publish document" button in the navigation bar.</p>'
|
||||
};
|
||||
|
||||
var button = undefined;
|
||||
var currentFileDesc = undefined;
|
||||
var publishRunning = false;
|
||||
var hasPublications = false;
|
||||
var isOffline = false;
|
||||
// Enable/disable the button
|
||||
function updateButtonState() {
|
||||
if(button === undefined) {
|
||||
return;
|
||||
}
|
||||
if(publishRunning === true || hasPublications === false || isOffline === true) {
|
||||
$(".action-force-publish").addClass("disabled");
|
||||
button.addClass("disabled");
|
||||
}
|
||||
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) {
|
||||
publishRunning = isRunning;
|
||||
updateButtonState();
|
||||
|
@ -10,6 +10,26 @@ define([
|
||||
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 lineTemplate = [
|
||||
'<div class="input-prepend">',
|
||||
|
@ -9,19 +9,43 @@ define([
|
||||
settingsBloc: '<p>Adds a "Synchronize documents" button in the navigation bar.</p>'
|
||||
};
|
||||
|
||||
var button = undefined;
|
||||
var syncRunning = false;
|
||||
var uploadPending = false;
|
||||
var isOffline = false;
|
||||
// Enable/disable the button
|
||||
var updateButtonState = function() {
|
||||
if(button === undefined) {
|
||||
return;
|
||||
}
|
||||
if(syncRunning === true || uploadPending === false || isOffline) {
|
||||
$(".action-force-sync").addClass("disabled");
|
||||
button.addClass("disabled");
|
||||
}
|
||||
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) {
|
||||
syncRunning = isRunning;
|
||||
uploadPending = true;
|
||||
@ -38,8 +62,6 @@ define([
|
||||
updateButtonState();
|
||||
};
|
||||
|
||||
buttonSync.onReady = updateButtonState;
|
||||
|
||||
// Check that a file has synchronized locations
|
||||
var checkSynchronization = function(fileDesc) {
|
||||
if(_.size(fileDesc.syncLocations) !== 0) {
|
||||
@ -48,7 +70,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
buttonSync.onFileChanged = checkSynchronization;
|
||||
buttonSync.onContentChanged = checkSynchronization;
|
||||
buttonSync.onTitleChanged = checkSynchronization;
|
||||
|
||||
return buttonSync;
|
||||
|
@ -1,7 +1,8 @@
|
||||
define([
|
||||
"jquery",
|
||||
"underscore"
|
||||
], function($, _) {
|
||||
"underscore",
|
||||
"file-system"
|
||||
], function($, _, fileSystem) {
|
||||
|
||||
var documentSelector = {
|
||||
extensionId: "documentSelector",
|
||||
@ -9,11 +10,6 @@ define([
|
||||
settingsBloc: '<p>Builds the "Open document" dropdown menu.</p>'
|
||||
};
|
||||
|
||||
var fileSystem = undefined;
|
||||
documentSelector.onFileSystemCreated = function(fileSystemParameter) {
|
||||
fileSystem = fileSystemParameter;
|
||||
};
|
||||
|
||||
var fileMgr = undefined;
|
||||
documentSelector.onFileMgrCreated = function(fileMgrParameter) {
|
||||
fileMgr = fileMgrParameter;
|
||||
|
@ -37,11 +37,11 @@ define([
|
||||
$(".msg-no-publish").removeClass("hide");
|
||||
}
|
||||
_.each(publishAttributesList, function(publishAttributes) {
|
||||
publishAttributes = _.extend({}, publishAttributes);
|
||||
if(publishAttributes.password) {
|
||||
publishAttributes.password = "********";
|
||||
formattedAttributes = _.omit(publishAttributes, "provider", "publishIndex", "sharingLink");
|
||||
if(formattedAttributes.password) {
|
||||
formattedAttributes.password = "********";
|
||||
}
|
||||
var publishDesc = JSON.stringify(publishAttributes).replace(/{|}|"/g, "");
|
||||
var publishDesc = JSON.stringify(formattedAttributes).replace(/{|}|"/g, "").replace(/,/g, ", ");
|
||||
var lineElement = $(_.template(lineTemplate, {
|
||||
provider: publishAttributes.provider,
|
||||
publishDesc: publishDesc
|
||||
|
@ -58,12 +58,12 @@ define([
|
||||
}
|
||||
|
||||
notifications.onMessage = function(message) {
|
||||
console.log(message);
|
||||
logger.log(message);
|
||||
showMessage(message);
|
||||
};
|
||||
|
||||
notifications.onError = function(error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
if(_.isString(error)) {
|
||||
showMessage(error, "icon-warning-sign");
|
||||
}
|
||||
|
@ -11,13 +11,35 @@ define([
|
||||
|
||||
var fileMgr = {};
|
||||
|
||||
// Defines the current file
|
||||
var currentFile = (function() {
|
||||
var fileIndex = localStorage["file.current"];
|
||||
if(fileIndex !== undefined) {
|
||||
return fileSystem[fileIndex];
|
||||
// Defines a file descriptor in the file system (fileDesc objects)
|
||||
function FileDescriptor(fileIndex, title, syncLocations, publishLocations) {
|
||||
this.fileIndex = fileIndex;
|
||||
this.title = title;
|
||||
this.syncLocations = syncLocations || {};
|
||||
this.publishLocations = publishLocations || {};
|
||||
}
|
||||
})();
|
||||
FileDescriptor.prototype.getContent = function() {
|
||||
return localStorage[this.fileIndex + ".content"];
|
||||
};
|
||||
FileDescriptor.prototype.setContent = function(content) {
|
||||
localStorage[this.fileIndex + ".content"] = content;
|
||||
extensionMgr.onContentChanged(this);
|
||||
};
|
||||
FileDescriptor.prototype.setTitle = function(title) {
|
||||
this.title = title;
|
||||
localStorage[this.fileIndex + ".title"] = title;
|
||||
extensionMgr.onTitleChanged(this);
|
||||
};
|
||||
|
||||
// Load file descriptors from localStorage
|
||||
_.chain(
|
||||
localStorage["file.list"].split(";")
|
||||
).compact().each(function(fileIndex) {
|
||||
fileSystem[fileIndex] = new FileDescriptor(fileIndex, localStorage[fileIndex + ".title"]);
|
||||
});
|
||||
|
||||
// Defines the current file
|
||||
var currentFile = undefined;
|
||||
fileMgr.getCurrentFile = function() {
|
||||
return currentFile;
|
||||
};
|
||||
@ -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) {
|
||||
fileDesc = fileDesc || fileMgr.getCurrentFile();
|
||||
|
||||
@ -44,11 +66,17 @@ define([
|
||||
if (fileSystemSize === 0) {
|
||||
fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent);
|
||||
}
|
||||
// If no file is selected take the last created
|
||||
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);
|
||||
|
||||
// Notify extensions
|
||||
@ -61,9 +89,10 @@ define([
|
||||
else {
|
||||
$(".action-edit-document").addClass("hide");
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate the editor
|
||||
$("#wmd-input").val(localStorage[fileDesc.fileIndex + ".content"]);
|
||||
$("#wmd-input").val(fileDesc.getContent());
|
||||
core.createEditor(function() {
|
||||
// Callback to save content when textarea changes
|
||||
fileMgr.saveFile();
|
||||
@ -72,7 +101,6 @@ define([
|
||||
|
||||
fileMgr.createFile = function(title, content, syncLocations, isTemporary) {
|
||||
content = content !== undefined ? content : settings.defaultContent;
|
||||
syncLocations = syncLocations || {};
|
||||
if (!title) {
|
||||
// Create a file title
|
||||
title = DEFAULT_FILE_TITLE;
|
||||
@ -92,24 +120,19 @@ define([
|
||||
} while(_.has(fileSystem, fileIndex));
|
||||
}
|
||||
|
||||
// Create the file in the localStorage
|
||||
localStorage[fileIndex + ".content"] = content;
|
||||
localStorage[fileIndex + ".title"] = title;
|
||||
// Store syncIndexes associated to the file
|
||||
// syncIndex associations
|
||||
syncLocations = syncLocations || {};
|
||||
var sync = _.reduce(syncLocations, function(sync, syncAttributes, syncIndex) {
|
||||
return sync + syncIndex + ";";
|
||||
}, ";");
|
||||
|
||||
localStorage[fileIndex + ".title"] = title;
|
||||
localStorage[fileIndex + ".content"] = content;
|
||||
localStorage[fileIndex + ".sync"] = sync;
|
||||
// Store publishIndexes associated to the file
|
||||
localStorage[fileIndex + ".publish"] = ";";
|
||||
|
||||
// Create the file descriptor
|
||||
var fileDesc = {
|
||||
fileIndex : fileIndex,
|
||||
title : title,
|
||||
syncLocations: syncLocations,
|
||||
publishLocations: {}
|
||||
};
|
||||
var fileDesc = new FileDescriptor(fileIndex, title, syncLocations);
|
||||
|
||||
// Add the index to the file list
|
||||
if(!isTemporary) {
|
||||
@ -122,9 +145,11 @@ define([
|
||||
|
||||
fileMgr.deleteFile = function(fileDesc) {
|
||||
fileDesc = fileDesc || fileMgr.getCurrentFile();
|
||||
if(fileMgr.isCurrentFile(fileDesc)) {
|
||||
if(fileMgr.isCurrentFile(fileDesc) === true) {
|
||||
// Unset the current fileDesc
|
||||
fileMgr.setCurrentFile();
|
||||
// Refresh the editor with an other file
|
||||
fileMgr.selectFile();
|
||||
}
|
||||
|
||||
// Remove synchronized locations
|
||||
@ -141,10 +166,12 @@ define([
|
||||
var fileIndex = fileDesc.fileIndex;
|
||||
localStorage["file.list"] = localStorage["file.list"].replace(";"
|
||||
+ fileIndex + ";", ";");
|
||||
|
||||
localStorage.removeItem(fileIndex + ".title");
|
||||
localStorage.removeItem(fileIndex + ".content");
|
||||
localStorage.removeItem(fileIndex + ".sync");
|
||||
localStorage.removeItem(fileIndex + ".publish");
|
||||
|
||||
fileSystem.removeItem(fileIndex);
|
||||
extensionMgr.onFileDeleted(fileDesc);
|
||||
};
|
||||
@ -153,8 +180,7 @@ define([
|
||||
fileMgr.saveFile = function() {
|
||||
var content = $("#wmd-input").val();
|
||||
var fileDesc = fileMgr.getCurrentFile();
|
||||
localStorage[fileDesc.fileIndex + ".content"] = content;
|
||||
extensionMgr.onFileChanged(fileDesc);
|
||||
fileDesc.setContent(content);
|
||||
};
|
||||
|
||||
// Add a synchronized location to a file
|
||||
@ -211,7 +237,7 @@ define([
|
||||
|
||||
// Remove a publishIndex (publish location)
|
||||
fileMgr.removePublish = function(publishAttributes, skipExtensions) {
|
||||
var fileDesc = fileMgr.getFileFromPublish(publishAttributes.publishIndex);
|
||||
var fileDesc = fileMgr.getFileFromPublishIndex(publishAttributes.publishIndex);
|
||||
if(fileDesc !== undefined) {
|
||||
localStorage[fileDesc.fileIndex + ".publish"] = localStorage[fileDesc.fileIndex + ".publish"].replace(";"
|
||||
+ publishAttributes.publishIndex + ";", ";");
|
||||
@ -225,7 +251,7 @@ define([
|
||||
};
|
||||
|
||||
// Get the file descriptor associated to a publishIndex
|
||||
fileMgr.getFileFromPublish = function(publishIndex) {
|
||||
fileMgr.getFileFromPublishIndex = function(publishIndex) {
|
||||
return _.find(fileSystem, function(fileDesc) {
|
||||
return _.has(fileDesc.publishLocations, publishIndex);
|
||||
});
|
||||
@ -263,7 +289,6 @@ define([
|
||||
});
|
||||
$(".action-remove-file").click(function() {
|
||||
fileMgr.deleteFile();
|
||||
fileMgr.selectFile();
|
||||
});
|
||||
$("#file-title").click(function() {
|
||||
if(viewerMode === true) {
|
||||
@ -280,15 +305,10 @@ define([
|
||||
$("#file-title").show();
|
||||
var title = $.trim(input.val());
|
||||
var fileDesc = fileMgr.getCurrentFile();
|
||||
var fileIndexTitle = fileDesc.fileIndex + ".title";
|
||||
if (title) {
|
||||
if (title != localStorage[fileIndexTitle]) {
|
||||
localStorage[fileIndexTitle] = title;
|
||||
fileDesc.title = title;
|
||||
extensionMgr.onTitleChanged(fileDesc);
|
||||
if (title && title != fileDesc.title) {
|
||||
fileDesc.setTitle(title);
|
||||
}
|
||||
}
|
||||
input.val(localStorage[fileIndexTitle]);
|
||||
input.val(fileDesc.title);
|
||||
$("#wmd-input").focus();
|
||||
}
|
||||
$("#file-title-input").blur(function() {
|
||||
|
@ -1,22 +1,3 @@
|
||||
define([
|
||||
"underscore",
|
||||
"extension-manager"
|
||||
], 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;
|
||||
});
|
||||
// The fileSystem module is empty when created. It's filled by fileMgr when loading.
|
||||
// syncLocations and publishLocations are respectively loaded by synchronizer and publisher.
|
||||
define({});
|
@ -28,6 +28,7 @@ define([
|
||||
syncAttributes.contentCRC = utils.crc32(content);
|
||||
syncAttributes.titleCRC = utils.crc32(title);
|
||||
syncAttributes.syncIndex = createSyncIndex(id);
|
||||
utils.storeAttributes(syncAttributes);
|
||||
return syncAttributes;
|
||||
}
|
||||
|
||||
@ -43,7 +44,6 @@ define([
|
||||
var fileDescList = [];
|
||||
_.each(result, function(file) {
|
||||
var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
|
||||
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
var syncLocations = {};
|
||||
syncLocations[syncAttributes.syncIndex] = syncAttributes;
|
||||
var fileDesc = fileMgr.createFile(file.title, file.content, syncLocations);
|
||||
@ -82,7 +82,6 @@ define([
|
||||
return;
|
||||
}
|
||||
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title);
|
||||
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
callback(undefined, syncAttributes);
|
||||
});
|
||||
};
|
||||
@ -92,7 +91,7 @@ define([
|
||||
if(!id) {
|
||||
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 fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
|
||||
if(fileDesc !== undefined) {
|
||||
@ -106,7 +105,6 @@ define([
|
||||
return;
|
||||
}
|
||||
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title);
|
||||
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
callback(undefined, syncAttributes);
|
||||
});
|
||||
};
|
||||
@ -178,7 +176,7 @@ define([
|
||||
return;
|
||||
}
|
||||
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 file = change.file;
|
||||
var remoteTitleCRC = utils.crc32(file.title);
|
||||
@ -190,20 +188,17 @@ define([
|
||||
// Conflict detection
|
||||
if ((fileTitleChanged === true && localTitleChanged === true && remoteTitleChanged === true)
|
||||
|| (fileContentChanged === true && localContentChanged === true && remoteContentChanged === true)) {
|
||||
var backupFileDesc = fileMgr.createFile(localTitle + " (backup)", localContent);
|
||||
extensionMgr.onTitleChanged(backupFileDesc);
|
||||
fileMgr.createFile(localTitle + " (backup)", localContent);
|
||||
extensionMgr.onMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.');
|
||||
}
|
||||
// If file title changed
|
||||
if(fileTitleChanged && remoteTitleChanged === true) {
|
||||
localStorage[fileDesc.fileIndex + ".title"] = file.title;
|
||||
fileDesc.title = file.title;
|
||||
extensionMgr.onTitleChanged(fileDesc);
|
||||
fileDesc.setTitle(file.title);
|
||||
extensionMgr.onMessage('"' + localTitle + '" has been renamed to "' + file.title + '" on Google Drive.');
|
||||
}
|
||||
// If file content changed
|
||||
if(fileContentChanged && remoteContentChanged === true) {
|
||||
localStorage[fileDesc.fileIndex + ".content"] = file.content;
|
||||
fileDesc.setContent(file.content);
|
||||
extensionMgr.onMessage('"' + file.title + '" has been updated from Google Drive.');
|
||||
if(fileMgr.isCurrentFile(fileDesc)) {
|
||||
fileMgr.selectFile(); // Refresh editor
|
||||
@ -213,7 +208,7 @@ define([
|
||||
syncAttributes.etag = file.etag;
|
||||
syncAttributes.contentCRC = remoteContentCRC;
|
||||
syncAttributes.titleCRC = remoteTitleCRC;
|
||||
localStorage[syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
utils.storeAttributes(syncAttributes);
|
||||
});
|
||||
localStorage[PROVIDER_GDRIVE + ".lastChangeId"] = newChangeId;
|
||||
callback();
|
||||
@ -263,7 +258,6 @@ define([
|
||||
return;
|
||||
}
|
||||
var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
|
||||
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
var syncLocations = {};
|
||||
syncLocations[syncAttributes.syncIndex] = syncAttributes;
|
||||
var fileDesc = fileMgr.createFile(file.title, file.content, syncAttributes);
|
||||
|
@ -224,7 +224,7 @@ define([
|
||||
function handleError(error, task) {
|
||||
var errorMsg = undefined;
|
||||
if (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
// Try to analyze the error
|
||||
if (typeof error === "string") {
|
||||
errorMsg = error;
|
||||
|
@ -326,7 +326,7 @@ define([
|
||||
function handleError(error, task) {
|
||||
var errorMsg = undefined;
|
||||
if (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
// Try to analyze the error
|
||||
if (typeof error === "string") {
|
||||
errorMsg = error;
|
||||
|
16
js/main.js
16
js/main.js
@ -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([
|
||||
"jquery",
|
||||
"core",
|
||||
@ -31,6 +45,7 @@ require([
|
||||
], function($, core) {
|
||||
|
||||
$(function() {
|
||||
|
||||
// If browser has detected a new application cache.
|
||||
if (window.applicationCache) {
|
||||
window.applicationCache.addEventListener('updateready', function(e) {
|
||||
@ -41,6 +56,7 @@ require([
|
||||
}, false);
|
||||
}
|
||||
|
||||
// Here, all the modules are loaded and the DOM is ready
|
||||
core.setReady();
|
||||
});
|
||||
|
||||
|
@ -132,7 +132,7 @@ define([
|
||||
publishIndex = "publish." + utils.randomString();
|
||||
} while(_.has(localStorage, publishIndex));
|
||||
publishAttributes.publishIndex = publishIndex;
|
||||
localStorage[publishIndex] = utils.serializeAttributes(publishAttributes);
|
||||
utils.storeAttributes(publishAttributes);
|
||||
fileMgr.addPublish(fileDesc, publishAttributes);
|
||||
}
|
||||
|
||||
@ -230,12 +230,6 @@ define([
|
||||
|
||||
$(".action-process-publish").click(performNewLocation);
|
||||
|
||||
$(".action-force-publish").click(function() {
|
||||
if(!$(this).hasClass("disabled")) {
|
||||
publisher.publish();
|
||||
}
|
||||
});
|
||||
|
||||
// Save As menu items
|
||||
$(".action-download-md").click(function() {
|
||||
var content = $("#wmd-input").val();
|
||||
|
@ -63,7 +63,7 @@ define([
|
||||
function handleError(error, task) {
|
||||
var errorMsg = undefined;
|
||||
if (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
// Try to analyze the error
|
||||
if (typeof error === "string") {
|
||||
errorMsg = "SSH error: " + error + ".";
|
||||
|
@ -73,7 +73,7 @@ define([
|
||||
}
|
||||
if(uploadFlag) {
|
||||
// Update syncAttributes in localStorage
|
||||
localStorage[syncAttributes.syncIndex] = utils.serializeAttributes(syncAttributes);
|
||||
utils.storeAttributes(syncAttributes);
|
||||
}
|
||||
locationUp(callback);
|
||||
}
|
||||
@ -90,7 +90,7 @@ define([
|
||||
return;
|
||||
}
|
||||
|
||||
// Dequeue a fileDesc
|
||||
// Dequeue a fileDesc to synchronize
|
||||
var fileDesc = uploadFileList.pop();
|
||||
uploadSyncAttributesList = _.values(fileDesc.syncLocations);
|
||||
if(uploadSyncAttributesList.length === 0) {
|
||||
@ -99,7 +99,7 @@ define([
|
||||
}
|
||||
|
||||
// Get document title/content
|
||||
uploadContent = localStorage[fileDesc.fileIndex + ".content"];
|
||||
uploadContent = fileDesc.getContent();
|
||||
uploadContentCRC = utils.crc32(uploadContent);
|
||||
uploadTitle = fileDesc.title;
|
||||
uploadTitleCRC = utils.crc32(uploadTitle);
|
||||
@ -226,9 +226,7 @@ define([
|
||||
|
||||
// Perform the provider's export
|
||||
var fileDesc = fileMgr.getCurrentFile();
|
||||
var title = fileDesc.title;
|
||||
var content = localStorage[fileDesc.fileIndex + ".content"];
|
||||
provider.exportFile(event, title, content, function(error, syncAttributes) {
|
||||
provider.exportFile(event, fileDesc.title, fileDesc.getContent(), function(error, syncAttributes) {
|
||||
if(error) {
|
||||
return;
|
||||
}
|
||||
@ -245,9 +243,7 @@ define([
|
||||
// Provider's manual export button
|
||||
$(".action-sync-manual-" + provider.providerId).click(function(event) {
|
||||
var fileDesc = fileMgr.getCurrentFile();
|
||||
var title = fileDesc.title;
|
||||
var content = localStorage[fileDesc.fileIndex + ".content"];
|
||||
provider.exportManual(event, title, content, function(error, syncAttributes) {
|
||||
provider.exportManual(event, fileDesc.title, fileDesc.getContent(), function(error, syncAttributes) {
|
||||
if(error) {
|
||||
return;
|
||||
}
|
||||
@ -255,12 +251,6 @@ define([
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(".action-force-sync").click(function() {
|
||||
if(!$(this).hasClass("disabled")) {
|
||||
synchronizer.forceSync();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
extensionMgr.onSynchronizerCreated(synchronizer);
|
||||
|
@ -143,7 +143,7 @@ define([
|
||||
function handleError(error, task) {
|
||||
var errorMsg = undefined;
|
||||
if (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
// Try to analyze the error
|
||||
if (typeof error === "string") {
|
||||
errorMsg = error;
|
||||
|
@ -298,13 +298,14 @@ define([
|
||||
utils.updateCurrentTime();
|
||||
|
||||
|
||||
// Serialize sync/publish attributes
|
||||
utils.serializeAttributes = function(attributes) {
|
||||
// Serialize sync/publish attributes and store it in the fileStorage
|
||||
utils.storeAttributes = function(attributes) {
|
||||
var storeIndex = attributes.syncIndex || attributes.publishIndex;
|
||||
// Don't store sync/publish index
|
||||
attributes = _.omit(attributes, "syncIndex", "publishIndex");
|
||||
// Store providerId instead of provider
|
||||
attributes.provider = attributes.provider.providerId;
|
||||
return JSON.stringify(attributes);
|
||||
localStorage[storeIndex] = JSON.stringify(attributes);
|
||||
};
|
||||
|
||||
return utils;
|
||||
|
@ -141,7 +141,7 @@ define([
|
||||
function handleError(error, task) {
|
||||
var errorMsg = undefined;
|
||||
if (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
// Try to analyze the error
|
||||
if (typeof error === "string") {
|
||||
errorMsg = error;
|
||||
|
Loading…
Reference in New Issue
Block a user