New extension pattern

This commit is contained in:
benweet 2013-05-29 20:55:23 +01:00
parent c6c06373da
commit d7304444a1
44 changed files with 5801 additions and 5349 deletions

View File

@ -327,7 +327,6 @@ hr {
div.dropdown-menu { div.dropdown-menu {
padding: 5px 20px; padding: 5px 20px;
white-space: normal;
} }
div.dropdown-menu p, div.dropdown-menu p,
@ -335,12 +334,17 @@ div.dropdown-menu blockquote {
margin: 10px 0; margin: 10px 0;
} }
div.dropdown-menu .stat {
margin-bottom: 10px;
}
div.dropdown-menu i { div.dropdown-menu i {
margin-right: 0; margin-right: 0;
} }
#link-container { #link-container {
min-width: 210px; min-width: 210px;
white-space: normal;
} }
#link-container .link-list { #link-container .link-list {
@ -518,6 +522,10 @@ div.dropdown-menu i {
text-align: left; text-align: left;
} }
#modal-settings .accordion-inner .form-inline .label-text {
margin: 0 10px;
}
.accordion-toggle { .accordion-toggle {
cursor: help; cursor: help;
} }

BIN
doc/img/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -9,184 +9,185 @@ define([
"extension-manager" "extension-manager"
], function(_, core, utils, extensionMgr) { ], function(_, core, utils, extensionMgr) {
var asyncRunner = {}; var asyncRunner = {};
var taskQueue = []; var taskQueue = [];
var asyncRunning = false; var asyncRunning = false;
var currentTask = undefined; var currentTask = undefined;
var currentTaskRunning = false; var currentTaskRunning = false;
var currentTaskStartTime = 0; var currentTaskStartTime = 0;
asyncRunner.createTask = function() { asyncRunner.createTask = function() {
var task = {}; var task = {};
task.finished = false; task.finished = false;
task.timeout = ASYNC_TASK_DEFAULT_TIMEOUT; task.timeout = ASYNC_TASK_DEFAULT_TIMEOUT;
task.retryCounter = 0; task.retryCounter = 0;
/** /**
* onRun callbacks are called by chain(). These callbacks have to call * onRun callbacks are called by chain(). These callbacks have to call
* chain() themselves to chain with next onRun callback or error() to * chain() themselves to chain with next onRun callback or error() to
* throw an exception or retry() to restart the task. * throw an exception or retry() to restart the task.
*/ */
// Run callbacks // Run callbacks
task.runCallbacks = []; task.runCallbacks = [];
task.onRun = function(callback) { task.onRun = function(callback) {
task.runCallbacks.push(callback); task.runCallbacks.push(callback);
}; };
/** /**
* onSuccess callbacks are called when every onRun callbacks have * onSuccess callbacks are called when every onRun callbacks have
* succeed. * succeed.
*/ */
task.successCallbacks = []; task.successCallbacks = [];
task.onSuccess = function(callback) { task.onSuccess = function(callback) {
task.successCallbacks.push(callback); task.successCallbacks.push(callback);
}; };
/** /**
* onError callbacks are called when error() is called in a onRun * onError callbacks are called when error() is called in a onRun
* callback. * callback.
*/ */
task.errorCallbacks = []; task.errorCallbacks = [];
task.onError = function(callback) { task.onError = function(callback) {
task.errorCallbacks.push(callback); task.errorCallbacks.push(callback);
}; };
/** /**
* chain() calls the next onRun callback or the onSuccess callbacks when * chain() calls the next onRun callback or the onSuccess callbacks when
* finished. The optional callback parameter can be used to pass an onRun * finished. The optional callback parameter can be used to pass an
* callback during execution. * onRun callback during execution.
*/ */
task.chain = function(callback) { task.chain = function(callback) {
if (task.finished === true) { if(task.finished === true) {
return; return;
} }
// If first execution // If first execution
if (task.queue === undefined) { if(task.queue === undefined) {
// Create a copy of the onRun callbacks // Create a copy of the onRun callbacks
task.queue = task.runCallbacks.slice(); task.queue = task.runCallbacks.slice();
} }
// If a callback is passed as a parameter // If a callback is passed as a parameter
if(callback !== undefined) { if(callback !== undefined) {
callback(); callback();
return; return;
} }
// If all callbacks have been run // If all callbacks have been run
if (task.queue.length === 0) { if(task.queue.length === 0) {
// Run the onSuccess callbacks // Run the onSuccess callbacks
runSafe(task, task.successCallbacks); runSafe(task, task.successCallbacks);
return; return;
} }
// Run the next callback // Run the next callback
var runCallback = task.queue.shift(); var runCallback = task.queue.shift();
runCallback(); runCallback();
}; };
/** /**
* error() calls the onError callbacks passing the error parameter and ends * error() calls the onError callbacks passing the error parameter and
* the task by throwing an exception. * ends the task by throwing an exception.
*/ */
task.error = function(error) { task.error = function(error) {
if (task.finished === true) { if(task.finished === true) {
return; return;
} }
error = error || new Error("Unknown error"); error = error || new Error("Unknown error");
if(error.message) { if(error.message) {
extensionMgr.onError(error); extensionMgr.onError(error);
} }
runSafe(task, task.errorCallbacks, error); runSafe(task, task.errorCallbacks, error);
// Exit the current call stack // Exit the current call stack
throw error; throw error;
}; };
/** /**
* retry() can be called in an onRun callback to restart the task * retry() can be called in an onRun callback to restart the task
*/ */
task.retry = function(error, maxRetryCounter) { task.retry = function(error, maxRetryCounter) {
if (task.finished === true) { if(task.finished === true) {
return; return;
} }
maxRetryCounter = maxRetryCounter || 5; maxRetryCounter = maxRetryCounter || 5;
task.queue = undefined; task.queue = undefined;
if (task.retryCounter >= maxRetryCounter) { if(task.retryCounter >= maxRetryCounter) {
task.error(error); task.error(error);
return; return;
} }
// Implement an exponential backoff // Implement an exponential backoff
var delay = Math.pow(2, task.retryCounter++) * 1000; var delay = Math.pow(2, task.retryCounter++) * 1000;
currentTaskStartTime = utils.currentTime + delay; currentTaskStartTime = utils.currentTime + delay;
currentTaskRunning = false; currentTaskRunning = false;
asyncRunner.runTask(); asyncRunner.runTask();
}; };
return task; return task;
}; };
// Run the next task in the queue if any and no other running // Run the next task in the queue if any and no other running
asyncRunner.runTask = function() { asyncRunner.runTask = function() {
// Use defer to avoid stack overflow // Use defer to avoid stack overflow
_.defer(function() { _.defer(function() {
// If there is a task currently running // If there is a task currently running
if (currentTaskRunning === true) { if(currentTaskRunning === true) {
// If the current task takes too long // If the current task takes too long
if (currentTaskStartTime + currentTask.timeout < utils.currentTime) { if(currentTaskStartTime + currentTask.timeout < utils.currentTime) {
currentTask.error(new Error("A timeout occurred.")); currentTask.error(new Error("A timeout occurred."));
} }
return; return;
} }
if (currentTask === undefined) { if(currentTask === undefined) {
// If no task in the queue // If no task in the queue
if (taskQueue.length === 0) { if(taskQueue.length === 0) {
return; return;
} }
// Dequeue an enqueued task // Dequeue an enqueued task
currentTask = taskQueue.shift(); currentTask = taskQueue.shift();
currentTaskStartTime = utils.currentTime; currentTaskStartTime = utils.currentTime;
if(asyncRunning === false) { if(asyncRunning === false) {
asyncRunning = true; asyncRunning = true;
extensionMgr.onAsyncRunning(true); extensionMgr.onAsyncRunning(true);
} }
} }
// Run the task // Run the task
if (currentTaskStartTime <= utils.currentTime) { if(currentTaskStartTime <= utils.currentTime) {
currentTaskRunning = true; currentTaskRunning = true;
currentTask.chain(); currentTask.chain();
} }
}); });
}; };
// Run runTask function periodically // Run runTask function periodically
core.addPeriodicCallback(asyncRunner.runTask); core.addPeriodicCallback(asyncRunner.runTask);
function runSafe(task, callbacks, param) { function runSafe(task, callbacks, param) {
try { try {
_.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; asyncRunning = false;
extensionMgr.onAsyncRunning(false); extensionMgr.onAsyncRunning(false);
} else { }
asyncRunner.runTask(); else {
} asyncRunner.runTask();
} }
} }
}
// Add a task to the queue // Add a task to the queue
asyncRunner.addTask = function(task) { asyncRunner.addTask = function(task) {
taskQueue.push(task); taskQueue.push(task);
asyncRunner.runTask(); asyncRunner.runTask();
}; };
// Change current task timeout // Change current task timeout
asyncRunner.setCurrentTaskTimeout = function(timeout) { asyncRunner.setCurrentTaskTimeout = function(timeout) {
if (currentTask !== undefined) { if(currentTask !== undefined) {
currentTask.timeout = timeout; currentTask.timeout = timeout;
} }
}; };
return asyncRunner; return asyncRunner;
}); });

View File

@ -1,59 +1,51 @@
define([ define([
"underscore", "underscore",
"utils", "utils",
"google-helper" "google-helper"
], function(_, utils, googleHelper) { ], function(_, utils, googleHelper) {
var PROVIDER_BLOGGER = "blogger";
var bloggerProvider = { var PROVIDER_BLOGGER = "blogger";
providerId: PROVIDER_BLOGGER,
providerName: "Blogger",
defaultPublishFormat: "html",
publishPreferencesInputIds: ["blogger-url"]
};
bloggerProvider.publish = function(publishAttributes, title, content, callback) {
googleHelper.uploadBlogger(
publishAttributes.blogUrl,
publishAttributes.blogId,
publishAttributes.postId,
publishAttributes.labelList,
title,
content,
function(error, blogId, postId) {
if(error) {
callback(error);
return;
}
publishAttributes.blogId = blogId;
publishAttributes.postId = postId;
callback();
}
);
};
bloggerProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
var blogUrl = utils.getInputTextValue("#input-publish-blogger-url", event);
if(blogUrl !== undefined) {
publishAttributes.blogUrl = utils.checkUrl(blogUrl);
}
publishAttributes.postId = utils.getInputTextValue("#input-publish-postid");
publishAttributes.labelList = [];
var labels = utils.getInputTextValue("#input-publish-labels");
if(labels !== undefined) {
publishAttributes.labelList = _.chain(
labels.split(",")
).map(function(label) {
return utils.trim(label);
}).compact().value();
}
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return bloggerProvider; var bloggerProvider = {
providerId: PROVIDER_BLOGGER,
providerName: "Blogger",
defaultPublishFormat: "html",
publishPreferencesInputIds: [
"blogger-url"
]
};
bloggerProvider.publish = function(publishAttributes, title, content, callback) {
googleHelper.uploadBlogger(publishAttributes.blogUrl, publishAttributes.blogId, publishAttributes.postId, publishAttributes.labelList, title, content, function(error, blogId, postId) {
if(error) {
callback(error);
return;
}
publishAttributes.blogId = blogId;
publishAttributes.postId = postId;
callback();
});
};
bloggerProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
var blogUrl = utils.getInputTextValue("#input-publish-blogger-url", event);
if(blogUrl !== undefined) {
publishAttributes.blogUrl = utils.checkUrl(blogUrl);
}
publishAttributes.postId = utils.getInputTextValue("#input-publish-postid");
publishAttributes.labelList = [];
var labels = utils.getInputTextValue("#input-publish-labels");
if(labels !== undefined) {
publishAttributes.labelList = _.chain(labels.split(",")).map(function(label) {
return utils.trim(label);
}).compact().value();
}
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return bloggerProvider;
}); });

View File

@ -1,8 +1,10 @@
var MAIN_URL = "http://benweet.github.io/stackedit/"; var MAIN_URL = "http://benweet.github.io/stackedit/";
var GOOGLE_API_KEY = "AIzaSyAeCU8CGcSkn0z9js6iocHuPBX4f_mMWkw"; var GOOGLE_API_KEY = "AIzaSyAeCU8CGcSkn0z9js6iocHuPBX4f_mMWkw";
var GOOGLE_SCOPES = [ "https://www.googleapis.com/auth/drive.install", var GOOGLE_SCOPES = [
"https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.install",
"https://www.googleapis.com/auth/blogger" ]; "https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/blogger"
];
var GOOGLE_DRIVE_APP_ID = "241271498917"; var GOOGLE_DRIVE_APP_ID = "241271498917";
var DROPBOX_APP_KEY = "lq6mwopab8wskas"; var DROPBOX_APP_KEY = "lq6mwopab8wskas";
var DROPBOX_APP_SECRET = "851fgnucpezy84t"; var DROPBOX_APP_SECRET = "851fgnucpezy84t";
@ -14,7 +16,7 @@ var AJAX_TIMEOUT = 30000;
var ASYNC_TASK_DEFAULT_TIMEOUT = 60000; var ASYNC_TASK_DEFAULT_TIMEOUT = 60000;
var ASYNC_TASK_LONG_TIMEOUT = 120000; var ASYNC_TASK_LONG_TIMEOUT = 120000;
var SYNC_PERIOD = 180000; var SYNC_PERIOD = 180000;
var USER_IDLE_THRESHOLD = 300000; var USER_IDLE_THRESHOLD = 300000;
var TEMPORARY_FILE_INDEX = "file.tempIndex"; var TEMPORARY_FILE_INDEX = "file.tempIndex";
var WELCOME_DOCUMENT_TITLE = "Welcome document"; var WELCOME_DOCUMENT_TITLE = "Welcome document";
var DOWNLOAD_PROXY_URL = "http://stackedit-download-proxy.herokuapp.com/"; var DOWNLOAD_PROXY_URL = "http://stackedit-download-proxy.herokuapp.com/";
@ -25,9 +27,9 @@ var SSH_PROXY_URL = "http://stackedit-ssh-proxy.herokuapp.com/";
// Use by Google's client.js // Use by Google's client.js
var delayedFunction = undefined; var delayedFunction = undefined;
function runDelayedFunction() { function runDelayedFunction() {
if (delayedFunction !== undefined) { if(delayedFunction !== undefined) {
delayedFunction(); delayedFunction();
} }
} }
// Site dependent // Site dependent
@ -38,15 +40,15 @@ var GATEKEEPER_URL = "http://stackedit-gatekeeper-localhost.herokuapp.com/";
var TUMBLR_PROXY_URL = "http://stackedit-tumblr-proxy-local.herokuapp.com/"; var TUMBLR_PROXY_URL = "http://stackedit-tumblr-proxy-local.herokuapp.com/";
if(location.hostname.indexOf("benweet.github.io") === 0) { if(location.hostname.indexOf("benweet.github.io") === 0) {
BASE_URL = MAIN_URL; BASE_URL = MAIN_URL;
GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com'; GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com';
GITHUB_CLIENT_ID = 'fa0d09514da8377ee32e'; GITHUB_CLIENT_ID = 'fa0d09514da8377ee32e';
GATEKEEPER_URL = "http://stackedit-gatekeeper.herokuapp.com/"; GATEKEEPER_URL = "http://stackedit-gatekeeper.herokuapp.com/";
TUMBLR_PROXY_URL = "http://stackedit-tumblr-proxy.herokuapp.com/"; TUMBLR_PROXY_URL = "http://stackedit-tumblr-proxy.herokuapp.com/";
} }
var THEME_LIST = { var THEME_LIST = {
"": "Default", "": "Default",
"blue-gray": "Blue-Gray", "blue-gray": "Blue-Gray",
"night": "Night" "night": "Night"
}; };

View File

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

View File

@ -3,46 +3,48 @@ define([
"core", "core",
"async-runner" "async-runner"
], function($, core, asyncRunner) { ], function($, core, asyncRunner) {
var PROVIDER_DOWNLOAD = "download";
var downloadProvider = {
providerId: PROVIDER_DOWNLOAD,
sharingAttributes: ["url"]
};
downloadProvider.importPublic = function(importParameters, callback) {
var task = asyncRunner.createTask();
var title = undefined;
var content = undefined;
task.onRun(function() {
var url = importParameters.url;
var slashUrl = url.lastIndexOf("/");
if(slashUrl === -1) {
task.error(new Error("Invalid URL parameter."));
return;
}
title = url.substring(slashUrl + 1);
$.ajax({
url : DOWNLOAD_PROXY_URL + "download?url=" + url,
type: "GET",
dataType : "text",
timeout : AJAX_TIMEOUT
}).done(function(result, textStatus, jqXHR) {
content = result;
task.chain();
}).fail(function(jqXHR) {
task.error(new Error("Unable to access URL " + url));
});
});
task.onSuccess(function() {
callback(undefined, title, content);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
return downloadProvider; var PROVIDER_DOWNLOAD = "download";
var downloadProvider = {
providerId: PROVIDER_DOWNLOAD,
sharingAttributes: [
"url"
]
};
downloadProvider.importPublic = function(importParameters, callback) {
var task = asyncRunner.createTask();
var title = undefined;
var content = undefined;
task.onRun(function() {
var url = importParameters.url;
var slashUrl = url.lastIndexOf("/");
if(slashUrl === -1) {
task.error(new Error("Invalid URL parameter."));
return;
}
title = url.substring(slashUrl + 1);
$.ajax({
url: DOWNLOAD_PROXY_URL + "download?url=" + url,
type: "GET",
dataType: "text",
timeout: AJAX_TIMEOUT
}).done(function(result, textStatus, jqXHR) {
content = result;
task.chain();
}).fail(function(jqXHR) {
task.error(new Error("Unable to access URL " + url));
});
});
task.onSuccess(function() {
callback(undefined, title, content);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
return downloadProvider;
}); });

View File

@ -6,323 +6,330 @@ define([
"async-runner" "async-runner"
], function($, _, core, extensionMgr, asyncRunner) { ], function($, _, core, extensionMgr, asyncRunner) {
var client = undefined; var client = undefined;
var authenticated = false; var authenticated = false;
var dropboxHelper = {}; var dropboxHelper = {};
// Try to connect dropbox by downloading client.js // Try to connect dropbox by downloading client.js
function connect(task) { function connect(task) {
task.onRun(function() { task.onRun(function() {
if(core.isOffline === true) { if(core.isOffline === true) {
client = undefined; client = undefined;
task.error(new Error("Operation not available in offline mode.|stopPublish")); task.error(new Error("Operation not available in offline mode.|stopPublish"));
return; return;
} }
if (client !== undefined) { if(client !== undefined) {
task.chain(); task.chain();
return; return;
} }
$.ajax({ $.ajax({
url : "lib/dropbox.min.js", url: "lib/dropbox.min.js",
dataType : "script", timeout : AJAX_TIMEOUT dataType: "script",
}).done(function() { timeout: AJAX_TIMEOUT
client = new Dropbox.Client({ }).done(function() {
key: DROPBOX_APP_KEY, client = new Dropbox.Client({
secret: DROPBOX_APP_SECRET key: DROPBOX_APP_KEY,
}); secret: DROPBOX_APP_SECRET
client.authDriver(new Dropbox.Drivers.Popup({ });
receiverUrl: BASE_URL + "dropbox-oauth-receiver.html", client.authDriver(new Dropbox.Drivers.Popup({
rememberUser: true receiverUrl: BASE_URL + "dropbox-oauth-receiver.html",
})); rememberUser: true
task.chain(); }));
}).fail(function(jqXHR) { task.chain();
var error = { }).fail(function(jqXHR) {
status: jqXHR.status, var error = {
responseText: jqXHR.statusText status: jqXHR.status,
}; responseText: jqXHR.statusText
handleError(error, task); };
}); handleError(error, task);
}); });
} });
}
// Try to authenticate with Oauth // Try to authenticate with Oauth
function authenticate(task) { function authenticate(task) {
task.onRun(function() { task.onRun(function() {
if (authenticated === true) { if(authenticated === true) {
task.chain(); task.chain();
return; return;
} }
var immediate = true; var immediate = true;
function localAuthenticate() { function localAuthenticate() {
if (immediate === false) { if(immediate === false) {
extensionMgr.onMessage("Please make sure the Dropbox authorization popup is not blocked by your browser."); extensionMgr.onMessage("Please make sure the Dropbox authorization popup is not blocked by your browser.");
// If not immediate we add time for user to enter his credentials // If not immediate we add time for user to enter his
task.timeout = ASYNC_TASK_LONG_TIMEOUT; // credentials
} task.timeout = ASYNC_TASK_LONG_TIMEOUT;
client.reset(); }
client.authenticate({interactive: !immediate}, function(error, client) { client.reset();
// Success client.authenticate({
if (client.authState === Dropbox.Client.DONE) { interactive: !immediate
authenticated = true; }, function(error, client) {
task.chain(); // Success
return; if(client.authState === Dropbox.Client.DONE) {
} authenticated = true;
// If immediate did not work retry without immediate flag task.chain();
if (immediate === true) { return;
immediate = false; }
task.chain(localAuthenticate); // If immediate did not work retry without immediate flag
return; if(immediate === true) {
} immediate = false;
// Error task.chain(localAuthenticate);
task.error(new Error("Access to Dropbox account is not authorized.")); return;
}); }
} // Error
task.chain(localAuthenticate); task.error(new Error("Access to Dropbox account is not authorized."));
}); });
} }
task.chain(localAuthenticate);
});
}
dropboxHelper.upload = function(path, content, callback) { dropboxHelper.upload = function(path, content, callback) {
var result = undefined; var result = undefined;
var task = asyncRunner.createTask(); var task = asyncRunner.createTask();
connect(task); connect(task);
authenticate(task); authenticate(task);
task.onRun(function() { task.onRun(function() {
client.writeFile(path, content, function(error, stat) { client.writeFile(path, content, function(error, stat) {
if (!error) { if(!error) {
result = stat; result = stat;
task.chain(); task.chain();
return; return;
} }
// Handle error // Handle error
if(error.status === 400) { if(error.status === 400) {
error = 'Could not upload document into path "' + path + '".'; error = 'Could not upload document into path "' + path + '".';
} }
handleError(error, task); handleError(error, task);
}); });
}); });
task.onSuccess(function() { task.onSuccess(function() {
callback(undefined, result); callback(undefined, result);
}); });
task.onError(function(error) { task.onError(function(error) {
callback(error); callback(error);
}); });
asyncRunner.addTask(task); asyncRunner.addTask(task);
}; };
dropboxHelper.checkChanges = function(lastChangeId, callback) { dropboxHelper.checkChanges = function(lastChangeId, callback) {
var changes = []; var changes = [];
var newChangeId = lastChangeId || 0; var newChangeId = lastChangeId || 0;
var task = asyncRunner.createTask(); var task = asyncRunner.createTask();
connect(task); connect(task);
authenticate(task); authenticate(task);
task.onRun(function() { task.onRun(function() {
function retrievePageOfChanges() { function retrievePageOfChanges() {
client.pullChanges(newChangeId, function(error, pullChanges) { client.pullChanges(newChangeId, function(error, pullChanges) {
if (error) { if(error) {
handleError(error, task); handleError(error, task);
return; return;
} }
// Retrieve success // Retrieve success
newChangeId = pullChanges.cursor(); newChangeId = pullChanges.cursor();
if(pullChanges.changes !== undefined) { if(pullChanges.changes !== undefined) {
changes = changes.concat(pullChanges.changes); changes = changes.concat(pullChanges.changes);
} }
if (pullChanges.shouldPullAgain) { if(pullChanges.shouldPullAgain) {
task.chain(retrievePageOfChanges); task.chain(retrievePageOfChanges);
} else { }
task.chain(); else {
} task.chain();
}); }
} });
task.chain(retrievePageOfChanges); }
}); task.chain(retrievePageOfChanges);
task.onSuccess(function() { });
callback(undefined, changes, newChangeId); task.onSuccess(function() {
}); callback(undefined, changes, newChangeId);
task.onError(function(error) { });
callback(error); task.onError(function(error) {
}); callback(error);
asyncRunner.addTask(task); });
}; asyncRunner.addTask(task);
};
dropboxHelper.downloadMetadata = function(paths, callback) { dropboxHelper.downloadMetadata = function(paths, callback) {
var result = []; var result = [];
var task = asyncRunner.createTask(); var task = asyncRunner.createTask();
connect(task); connect(task);
authenticate(task); authenticate(task);
task.onRun(function() { task.onRun(function() {
function recursiveDownloadMetadata() { function recursiveDownloadMetadata() {
if(paths.length === 0) { if(paths.length === 0) {
task.chain(); task.chain();
return; return;
} }
var path = paths[0]; var path = paths[0];
client.stat(path, function(error, stat) { client.stat(path, function(error, stat) {
if(stat) { if(stat) {
result.push(stat); result.push(stat);
paths.shift(); paths.shift();
task.chain(recursiveDownloadMetadata); task.chain(recursiveDownloadMetadata);
return; return;
} }
handleError(error, task); handleError(error, task);
}); });
} }
task.chain(recursiveDownloadMetadata); task.chain(recursiveDownloadMetadata);
}); });
task.onSuccess(function() { task.onSuccess(function() {
callback(undefined, result); callback(undefined, result);
}); });
task.onError(function(error) { task.onError(function(error) {
callback(error); callback(error);
}); });
asyncRunner.addTask(task); asyncRunner.addTask(task);
}; };
dropboxHelper.downloadContent = function(objects, callback) { dropboxHelper.downloadContent = function(objects, callback) {
var result = []; var result = [];
var task = asyncRunner.createTask(); var task = asyncRunner.createTask();
connect(task); connect(task);
authenticate(task); authenticate(task);
task.onRun(function() { task.onRun(function() {
function recursiveDownloadContent() { function recursiveDownloadContent() {
if(objects.length === 0) { if(objects.length === 0) {
task.chain(); task.chain();
return; return;
} }
var object = objects[0]; var object = objects[0];
result.push(object); result.push(object);
var file = undefined; var file = undefined;
// object may be a file // object may be a file
if(object.isFile === true) { if(object.isFile === true) {
file = object; file = object;
} }
// object may be a change // object may be a change
else if(object.wasRemoved !== undefined) { else if(object.wasRemoved !== undefined) {
file = object.stat; file = object.stat;
} }
if(!file) { if(!file) {
objects.shift(); objects.shift();
task.chain(recursiveDownloadContent); task.chain(recursiveDownloadContent);
return; return;
} }
client.readFile(file.path, function(error, data) { client.readFile(file.path, function(error, data) {
if(data) { if(data) {
file.content = data; file.content = data;
objects.shift(); objects.shift();
task.chain(recursiveDownloadContent); task.chain(recursiveDownloadContent);
return; return;
} }
handleError(error, task); handleError(error, task);
}); });
} }
task.chain(recursiveDownloadContent); task.chain(recursiveDownloadContent);
}); });
task.onSuccess(function() { task.onSuccess(function() {
callback(undefined, result); callback(undefined, result);
}); });
task.onError(function(error) { task.onError(function(error) {
callback(error); callback(error);
}); });
asyncRunner.addTask(task); asyncRunner.addTask(task);
}; };
function handleError(error, task) {
var errorMsg = true;
if (error) {
logger.error(error);
// Try to analyze the error
if (typeof error === "string") {
errorMsg = error;
}
else {
errorMsg = "Dropbox error ("
+ error.status + ": " + error.responseText + ").";
if (error.status === 401 || error.status === 403) { function handleError(error, task) {
authenticated = false; var errorMsg = true;
errorMsg = "Access to Dropbox account is not authorized."; if(error) {
task.retry(new Error(errorMsg), 1); logger.error(error);
return; // Try to analyze the error
} else if(error.status === 400 && error.responseText if(typeof error === "string") {
.indexOf("oauth_nonce") !== -1) { errorMsg = error;
// A bug I guess... }
_.each(_.keys(localStorage), function(key) { else {
// We have to remove the Oauth cache from the localStorage errorMsg = "Dropbox error (" + error.status + ": " + error.responseText + ").";
if(key.indexOf("dropbox-auth") === 0) {
localStorage.removeItem(key);
}
});
authenticated = false;
task.retry(new Error(errorMsg), 1);
return;
} else if (error.status <= 0) {
client = undefined;
authenticated = false;
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
var pickerLoaded = false; if(error.status === 401 || error.status === 403) {
function loadPicker(task) { authenticated = false;
task.onRun(function() { errorMsg = "Access to Dropbox account is not authorized.";
if (pickerLoaded === true) { task.retry(new Error(errorMsg), 1);
task.chain(); return;
return; }
} else if(error.status === 400 && error.responseText.indexOf("oauth_nonce") !== -1) {
$.ajax({ // A bug I guess...
url : "https://www.dropbox.com/static/api/1/dropbox.js", _.each(_.keys(localStorage), function(key) {
dataType : "script", timeout : AJAX_TIMEOUT // We have to remove the Oauth cache from the
}).done(function() { // localStorage
pickerLoaded = true; if(key.indexOf("dropbox-auth") === 0) {
task.chain(); localStorage.removeItem(key);
}).fail(function(jqXHR) { }
var error = { });
status: jqXHR.status, authenticated = false;
responseText: jqXHR.statusText task.retry(new Error(errorMsg), 1);
}; return;
handleError(error, task); }
}); else if(error.status <= 0) {
}); client = undefined;
} authenticated = false;
core.setOffline();
dropboxHelper.picker = function(callback) { errorMsg = "|stopPublish";
var paths = []; }
var task = asyncRunner.createTask(); }
// Add some time for user to choose his files }
task.timeout = ASYNC_TASK_LONG_TIMEOUT; task.error(new Error(errorMsg));
connect(task); }
loadPicker(task);
task.onRun(function() { var pickerLoaded = false;
var options = {}; function loadPicker(task) {
options.multiselect = true; task.onRun(function() {
options.linkType = "direct"; if(pickerLoaded === true) {
options.success = function(files) { task.chain();
for(var i=0; i<files.length; i++) { return;
var path = files[i].link; }
path = path.replace(/.*\/view\/[^\/]*/, ""); $.ajax({
paths.push(decodeURI(path)); url: "https://www.dropbox.com/static/api/1/dropbox.js",
} dataType: "script",
task.chain(); timeout: AJAX_TIMEOUT
}).done(function() {
pickerLoaded = true;
task.chain();
}).fail(function(jqXHR) {
var error = {
status: jqXHR.status,
responseText: jqXHR.statusText
};
handleError(error, task);
});
});
}
dropboxHelper.picker = function(callback) {
var paths = [];
var task = asyncRunner.createTask();
// Add some time for user to choose his files
task.timeout = ASYNC_TASK_LONG_TIMEOUT;
connect(task);
loadPicker(task);
task.onRun(function() {
var options = {};
options.multiselect = true;
options.linkType = "direct";
options.success = function(files) {
for ( var i = 0; i < files.length; i++) {
var path = files[i].link;
path = path.replace(/.*\/view\/[^\/]*/, "");
paths.push(decodeURI(path));
}
task.chain();
}; };
options.cancel = function() { options.cancel = function() {
task.chain(); task.chain();
}; };
Dropbox.choose(options); Dropbox.choose(options);
extensionMgr.onMessage("Please make sure the Dropbox chooser popup is not blocked by your browser."); extensionMgr.onMessage("Please make sure the Dropbox chooser popup is not blocked by your browser.");
}); });
task.onSuccess(function() { task.onSuccess(function() {
callback(undefined, paths); callback(undefined, paths);
}); });
task.onError(function(error) { task.onError(function(error) {
callback(error); callback(error);
}); });
asyncRunner.addTask(task); asyncRunner.addTask(task);
}; };
return dropboxHelper; return dropboxHelper;
}); });

View File

@ -5,232 +5,233 @@ define([
"file-manager", "file-manager",
"dropbox-helper" "dropbox-helper"
], function(_, utils, extensionMgr, fileMgr, dropboxHelper) { ], function(_, utils, extensionMgr, fileMgr, dropboxHelper) {
var PROVIDER_DROPBOX = "dropbox";
var dropboxProvider = {
providerId: PROVIDER_DROPBOX,
providerName: "Dropbox",
defaultPublishFormat: "template"
};
function checkPath(path) {
if(path === undefined) {
return undefined;
}
if(!path.match(/^[^\\<>:"\|?\*]+$/)) {
extensionMgr.onError('"' + path + '" contains invalid characters.');
return undefined;
}
if(path.indexOf("/") !== 0) {
return "/" + path;
}
return path;
}
function createSyncIndex(path) {
return "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(path.toLowerCase());
}
function createSyncAttributes(path, versionTag, content) {
var syncAttributes = {};
syncAttributes.provider = dropboxProvider;
syncAttributes.path = path;
syncAttributes.version = versionTag;
syncAttributes.contentCRC = utils.crc32(content);
syncAttributes.syncIndex = createSyncIndex(path);
utils.storeAttributes(syncAttributes);
return syncAttributes;
}
function importFilesFromPaths(paths) {
dropboxHelper.downloadMetadata(paths, function(error, result) {
if(error) {
return;
}
dropboxHelper.downloadContent(result, function(error, result) {
if(error) {
return;
}
var fileDescList = [];
_.each(result, function(file) {
var syncAttributes = createSyncAttributes(file.path, file.versionTag, file.content);
var syncLocations = {};
syncLocations[syncAttributes.syncIndex] = syncAttributes;
var fileDesc = fileMgr.createFile(file.name, file.content, syncLocations);
fileMgr.selectFile(fileDesc);
fileDescList.push(fileDesc);
});
extensionMgr.onSyncImportSuccess(fileDescList, dropboxProvider);
});
});
}
dropboxProvider.importFiles = function() { var PROVIDER_DROPBOX = "dropbox";
dropboxHelper.picker(function(error, paths) {
if(error || paths.length === 0) {
return;
}
var importPaths = [];
_.each(paths, function(path) {
var syncIndex = createSyncIndex(path);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) {
extensionMgr.onError('"' + fileDesc.title + '" was already imported');
return;
}
importPaths.push(path);
});
importFilesFromPaths(importPaths);
});
};
function exportFileToPath(path, title, content, callback) {
path = checkPath(path);
if(path === undefined) {
callback(true);
return;
}
// Check that file is not synchronized with an other one
var syncIndex = createSyncIndex(path);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) {
var existingTitle = fileDesc.title;
extensionMgr.onError('File path is already synchronized with "' + existingTitle + '"');
callback(true);
return;
}
dropboxHelper.upload(path, content, function(error, result) {
if (error) {
callback(error);
return;
}
var syncAttributes = createSyncAttributes(result.path, result.versionTag, content);
callback(undefined, syncAttributes);
});
}
dropboxProvider.exportFile = function(event, title, content, callback) {
var path = utils.getInputTextValue("#input-sync-export-dropbox-path", event);
exportFileToPath(path, title, content, callback);
};
dropboxProvider.exportManual = function(event, title, content, callback) { var dropboxProvider = {
var path = utils.getInputTextValue("#input-sync-manual-dropbox-path", event); providerId: PROVIDER_DROPBOX,
exportFileToPath(path, title, content, callback); providerName: "Dropbox",
}; defaultPublishFormat: "template"
};
dropboxProvider.syncUp = function(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, callback) {
var syncContentCRC = syncAttributes.contentCRC; function checkPath(path) {
// Skip if CRC has not changed if(path === undefined) {
if(uploadContentCRC == syncContentCRC) { return undefined;
callback(undefined, false); }
return; if(!path.match(/^[^\\<>:"\|?\*]+$/)) {
} extensionMgr.onError('"' + path + '" contains invalid characters.');
dropboxHelper.upload(syncAttributes.path, uploadContent, function(error, result) { return undefined;
if(error) { }
callback(error, true); if(path.indexOf("/") !== 0) {
return; return "/" + path;
} }
syncAttributes.version = result.versionTag; return path;
syncAttributes.contentCRC = uploadContentCRC; }
callback(undefined, true);
}); function createSyncIndex(path) {
}; return "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(path.toLowerCase());
}
dropboxProvider.syncDown = function(callback) {
var lastChangeId = localStorage[PROVIDER_DROPBOX + ".lastChangeId"]; function createSyncAttributes(path, versionTag, content) {
dropboxHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) { var syncAttributes = {};
if (error) { syncAttributes.provider = dropboxProvider;
callback(error); syncAttributes.path = path;
return; syncAttributes.version = versionTag;
} syncAttributes.contentCRC = utils.crc32(content);
var interestingChanges = []; syncAttributes.syncIndex = createSyncIndex(path);
_.each(changes, function(change) { utils.storeAttributes(syncAttributes);
var syncIndex = createSyncIndex(change.path); return syncAttributes;
var syncAttributes = fileMgr.getSyncAttributes(syncIndex); }
if(syncAttributes === undefined) {
return; function importFilesFromPaths(paths) {
} dropboxHelper.downloadMetadata(paths, function(error, result) {
// Store syncAttributes to avoid 2 times searching if(error) {
change.syncAttributes = syncAttributes; return;
// Delete }
if(change.wasRemoved === true) { dropboxHelper.downloadContent(result, function(error, result) {
interestingChanges.push(change); if(error) {
return; return;
} }
// Modify var fileDescList = [];
if(syncAttributes.version != change.stat.versionTag) { _.each(result, function(file) {
interestingChanges.push(change); var syncAttributes = createSyncAttributes(file.path, file.versionTag, file.content);
} var syncLocations = {};
}); syncLocations[syncAttributes.syncIndex] = syncAttributes;
dropboxHelper.downloadContent(interestingChanges, function(error, changes) { var fileDesc = fileMgr.createFile(file.name, file.content, syncLocations);
if (error) { fileMgr.selectFile(fileDesc);
callback(error); fileDescList.push(fileDesc);
return; });
} extensionMgr.onSyncImportSuccess(fileDescList, dropboxProvider);
_.each(changes, function(change) { });
var syncAttributes = change.syncAttributes; });
var syncIndex = syncAttributes.syncIndex; }
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
// No file corresponding (file may have been deleted locally) dropboxProvider.importFiles = function() {
if(fileDesc === undefined) { dropboxHelper.picker(function(error, paths) {
return; if(error || paths.length === 0) {
} return;
var localTitle = fileDesc.title; }
// File deleted var importPaths = [];
if (change.wasRemoved === true) { _.each(paths, function(path) {
extensionMgr.onError('"' + localTitle + '" has been removed from Dropbox.'); var syncIndex = createSyncIndex(path);
fileMgr.removeSync(syncAttributes); var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
return; if(fileDesc !== undefined) {
} extensionMgr.onError('"' + fileDesc.title + '" was already imported');
var localContent = fileDesc.getContent(); return;
var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); }
var file = change.stat; importPaths.push(path);
});
importFilesFromPaths(importPaths);
});
};
function exportFileToPath(path, title, content, callback) {
path = checkPath(path);
if(path === undefined) {
callback(true);
return;
}
// Check that file is not synchronized with an other one
var syncIndex = createSyncIndex(path);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) {
var existingTitle = fileDesc.title;
extensionMgr.onError('File path is already synchronized with "' + existingTitle + '"');
callback(true);
return;
}
dropboxHelper.upload(path, content, function(error, result) {
if(error) {
callback(error);
return;
}
var syncAttributes = createSyncAttributes(result.path, result.versionTag, content);
callback(undefined, syncAttributes);
});
}
dropboxProvider.exportFile = function(event, title, content, callback) {
var path = utils.getInputTextValue("#input-sync-export-dropbox-path", event);
exportFileToPath(path, title, content, callback);
};
dropboxProvider.exportManual = function(event, title, content, callback) {
var path = utils.getInputTextValue("#input-sync-manual-dropbox-path", event);
exportFileToPath(path, title, content, callback);
};
dropboxProvider.syncUp = function(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, callback) {
var syncContentCRC = syncAttributes.contentCRC;
// Skip if CRC has not changed
if(uploadContentCRC == syncContentCRC) {
callback(undefined, false);
return;
}
dropboxHelper.upload(syncAttributes.path, uploadContent, function(error, result) {
if(error) {
callback(error, true);
return;
}
syncAttributes.version = result.versionTag;
syncAttributes.contentCRC = uploadContentCRC;
callback(undefined, true);
});
};
dropboxProvider.syncDown = function(callback) {
var lastChangeId = localStorage[PROVIDER_DROPBOX + ".lastChangeId"];
dropboxHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) {
if(error) {
callback(error);
return;
}
var interestingChanges = [];
_.each(changes, function(change) {
var syncIndex = createSyncIndex(change.path);
var syncAttributes = fileMgr.getSyncAttributes(syncIndex);
if(syncAttributes === undefined) {
return;
}
// Store syncAttributes to avoid 2 times searching
change.syncAttributes = syncAttributes;
// Delete
if(change.wasRemoved === true) {
interestingChanges.push(change);
return;
}
// Modify
if(syncAttributes.version != change.stat.versionTag) {
interestingChanges.push(change);
}
});
dropboxHelper.downloadContent(interestingChanges, function(error, changes) {
if(error) {
callback(error);
return;
}
_.each(changes, function(change) {
var syncAttributes = change.syncAttributes;
var syncIndex = syncAttributes.syncIndex;
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
// No file corresponding (file may have been deleted
// locally)
if(fileDesc === undefined) {
return;
}
var localTitle = fileDesc.title;
// File deleted
if(change.wasRemoved === true) {
extensionMgr.onError('"' + localTitle + '" has been removed from Dropbox.');
fileMgr.removeSync(syncAttributes);
return;
}
var localContent = fileDesc.content;
var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent);
var file = change.stat;
var remoteContentCRC = utils.crc32(file.content); var remoteContentCRC = utils.crc32(file.content);
var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC; var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC;
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) {
fileMgr.createFile(localTitle + " (backup)", localContent); fileMgr.createFile(localTitle + " (backup)", localContent);
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) {
fileDesc.setContent(file.content); fileDesc.content = 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
} }
} }
// Update syncAttributes // Update syncAttributes
syncAttributes.version = file.versionTag; syncAttributes.version = file.versionTag;
syncAttributes.contentCRC = remoteContentCRC; syncAttributes.contentCRC = remoteContentCRC;
utils.storeAttributes(syncAttributes); utils.storeAttributes(syncAttributes);
}); });
localStorage[PROVIDER_DROPBOX + ".lastChangeId"] = newChangeId; localStorage[PROVIDER_DROPBOX + ".lastChangeId"] = newChangeId;
callback(); callback();
}); });
}); });
}; };
dropboxProvider.publish = function(publishAttributes, title, content, callback) {
var path = checkPath(publishAttributes.path);
if(path === undefined) {
callback(true);
return;
}
dropboxHelper.upload(path, content, callback);
};
dropboxProvider.newPublishAttributes = function(event) { dropboxProvider.publish = function(publishAttributes, title, content, callback) {
var publishAttributes = {}; var path = checkPath(publishAttributes.path);
publishAttributes.path = utils.getInputTextValue("#input-publish-dropbox-path", event); if(path === undefined) {
if(event.isPropagationStopped()) { callback(true);
return undefined; return;
} }
return publishAttributes; dropboxHelper.upload(path, content, callback);
}; };
return dropboxProvider; dropboxProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
publishAttributes.path = utils.getInputTextValue("#input-publish-dropbox-path", event);
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return dropboxProvider;
}); });

View File

@ -1,4 +1,4 @@
define( [ define([
"jquery", "jquery",
"underscore", "underscore",
"utils", "utils",
@ -20,167 +20,162 @@ define( [
"extensions/scroll-link", "extensions/scroll-link",
"lib/bootstrap" "lib/bootstrap"
], function($, _, utils, settings) { ], function($, _, utils, settings) {
var extensionMgr = {};
// Create a list of extensions
var extensionList = _.chain(
arguments
).map(function(argument) {
return _.isObject(argument) && argument.extensionId && argument;
}).compact().value();
// Return every named callbacks implemented in extensions var extensionMgr = {};
function getExtensionCallbackList(hookName) {
return _.chain( // Create a list of extensions
extensionList var extensionList = _.chain(arguments).map(function(argument) {
).map(function(extension) { return _.isObject(argument) && argument.extensionId && argument;
return extension.config.enabled && extension[hookName]; }).compact().value();
}).compact().value();
} // Return every named callbacks implemented in extensions
function getExtensionCallbackList(hookName) {
// Return a function that calls every callbacks from extensions return _.chain(extensionList).map(function(extension) {
function createHook(hookName) { return extension.config.enabled && extension[hookName];
var callbackList = getExtensionCallbackList(hookName); }).compact().value();
return function() { }
logger.debug(hookName, arguments);
var callbackArguments = arguments; // Return a function that calls every callbacks from extensions
_.each(callbackList, function(callback) { function createHook(hookName) {
callback.apply(null, callbackArguments); var callbackList = getExtensionCallbackList(hookName);
}); return function() {
}; logger.debug(hookName, arguments);
} var callbackArguments = arguments;
_.each(callbackList, function(callback) {
// Add a Hook to the extensionMgr callback.apply(null, callbackArguments);
function addHook(hookName) { });
extensionMgr[hookName] = createHook(hookName); };
} }
// Set extension config // Add a Hook to the extensionMgr
extensionSettings = settings.extensionSettings || {}; function addHook(hookName) {
_.each(extensionList, function(extension) { extensionMgr[hookName] = createHook(hookName);
extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]); }
extension.config.enabled = !extension.optional || extension.config.enabled === undefined || extension.config.enabled === true;
}); // Set extension config
extensionSettings = settings.extensionSettings || {};
// Load/Save extension config from/to settings _.each(extensionList, function(extension) {
extensionMgr["onLoadSettings"] = function() { extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
logger.debug("onLoadSettings"); extension.config.enabled = !extension.optional || extension.config.enabled === undefined || extension.config.enabled === true;
_.each(extensionList, function(extension) { });
utils.setInputChecked("#input-enable-extension-" + extension.extensionId, extension.config.enabled);
var onLoadSettingsCallback = extension.onLoadSettings; // Load/Save extension config from/to settings
onLoadSettingsCallback && onLoadSettingsCallback(); extensionMgr["onLoadSettings"] = function() {
}); logger.debug("onLoadSettings");
}; _.each(extensionList, function(extension) {
extensionMgr["onSaveSettings"] = function(newExtensionSettings, event) { utils.setInputChecked("#input-enable-extension-" + extension.extensionId, extension.config.enabled);
logger.debug("onSaveSettings"); var onLoadSettingsCallback = extension.onLoadSettings;
_.each(extensionList, function(extension) { onLoadSettingsCallback && onLoadSettingsCallback();
var newExtensionConfig = extension.defaultConfig || {}; });
newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId); };
var onSaveSettingsCallback = extension.onSaveSettings; extensionMgr["onSaveSettings"] = function(newExtensionSettings, event) {
onSaveSettingsCallback && onSaveSettingsCallback(newExtensionConfig, event); logger.debug("onSaveSettings");
newExtensionSettings[extension.extensionId] = newExtensionConfig; _.each(extensionList, function(extension) {
}); var newExtensionConfig = _.extend({}, extension.defaultConfig);
}; newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
var onSaveSettingsCallback = extension.onSaveSettings;
addHook("onReady"); onSaveSettingsCallback && onSaveSettingsCallback(newExtensionConfig, event);
addHook("onMessage"); newExtensionSettings[extension.extensionId] = newExtensionConfig;
addHook("onError"); });
addHook("onOfflineChanged"); };
addHook("onAsyncRunning");
addHook("onReady");
// To access modules that are loaded after extensions addHook("onMessage");
addHook("onFileMgrCreated"); addHook("onError");
addHook("onSynchronizerCreated"); addHook("onOfflineChanged");
addHook("onPublisherCreated"); addHook("onAsyncRunning");
// Operations on files // To access modules that are loaded after extensions
addHook("onFileCreated"); addHook("onFileMgrCreated");
addHook("onFileDeleted"); addHook("onSynchronizerCreated");
addHook("onFileSelected"); addHook("onPublisherCreated");
addHook("onContentChanged");
addHook("onTitleChanged"); // Operations on files
addHook("onFileCreated");
// Sync events addHook("onFileDeleted");
addHook("onSyncRunning"); addHook("onFileSelected");
addHook("onSyncSuccess"); addHook("onContentChanged");
addHook("onSyncImportSuccess"); addHook("onTitleChanged");
addHook("onSyncExportSuccess");
addHook("onSyncRemoved"); // Sync events
addHook("onSyncRunning");
// Publish events addHook("onSyncSuccess");
addHook("onPublishRunning"); addHook("onSyncImportSuccess");
addHook("onPublishSuccess"); addHook("onSyncExportSuccess");
addHook("onNewPublishSuccess"); addHook("onSyncRemoved");
addHook("onPublishRemoved");
// Publish events
// Operations on Layout addHook("onPublishRunning");
addHook("onLayoutConfigure"); addHook("onPublishSuccess");
addHook("onLayoutCreated"); addHook("onNewPublishSuccess");
addHook("onPublishRemoved");
// Operations on PageDown
addHook("onEditorConfigure"); // Operations on Layout
addHook("onLayoutConfigure");
var onPreviewFinished = createHook("onPreviewFinished"); addHook("onLayoutCreated");
var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview");
extensionMgr["onAsyncPreview"] = function() { // Operations on PageDown
logger.debug("onAsyncPreview"); addHook("onEditorConfigure");
// Call onPreviewFinished callbacks when all async preview are finished
var counter = 0; var onPreviewFinished = createHook("onPreviewFinished");
function tryFinished() { var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview");
if(counter === onAsyncPreviewCallbackList.length) { extensionMgr["onAsyncPreview"] = function() {
onPreviewFinished(); logger.debug("onAsyncPreview");
} // Call onPreviewFinished callbacks when all async preview are finished
} var counter = 0;
_.each(onAsyncPreviewCallbackList, function(asyncPreviewCallback) { function tryFinished() {
asyncPreviewCallback(function() { if(counter === onAsyncPreviewCallbackList.length) {
counter++; onPreviewFinished();
tryFinished(); }
}); }
}); _.each(onAsyncPreviewCallbackList, function(asyncPreviewCallback) {
tryFinished(); asyncPreviewCallback(function() {
}; counter++;
tryFinished();
var accordionTmpl = [ });
});
tryFinished();
};
var accordionTmpl = [
'<div class="accordion-group">', '<div class="accordion-group">',
'<div class="accordion-heading">', ' <div class="accordion-heading">',
'<label class="checkbox pull-right">', ' <label class="checkbox pull-right">',
'<input id="input-enable-extension-<%= extensionId %>" type="checkbox" <% if(!optional) { %> disabled <% } %>> enabled', ' <input id="input-enable-extension-<%= extensionId %>" type="checkbox" <% if(!optional) { %> disabled <% } %>> enabled',
'</label>', ' </label>',
'<a id="accordion-toggle-test" data-toggle="collapse" data-parent="#accordion-extensions" class="accordion-toggle" href="#collapse-<%= extensionId %>">', ' <a id="accordion-toggle-test" data-toggle="collapse" data-parent="#accordion-extensions" class="accordion-toggle" href="#collapse-<%= extensionId %>">',
'<%= extensionName %>', ' <%= extensionName %>',
'</a>', ' </a>',
'</div>', ' </div>',
'<div id="collapse-<%= extensionId %>" class="accordion-body collapse">', ' <div id="collapse-<%= extensionId %>" class="accordion-body collapse">',
'<div class="accordion-inner"><%= settingsBloc %></div>', ' <div class="accordion-inner"><%= settingsBloc %></div>',
'</div>', ' </div>',
'</div>'].join(""); '</div>'
].join("");
function createSettings(extension) {
$("#accordion-extensions").append($(_.template(accordionTmpl, {
extensionId: extension.extensionId,
extensionName: extension.extensionName,
optional: extension.optional,
settingsBloc: extension.settingsBloc
})));
}
$(function() { function createSettings(extension) {
// Create accordion in settings dialog $("#accordion-extensions").append($(_.template(accordionTmpl, {
_.chain( extensionId: extension.extensionId,
extensionList extensionName: extension.extensionName,
).sortBy(function(extension) { optional: extension.optional,
return extension.extensionName.toLowerCase(); settingsBloc: extension.settingsBloc
}).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()));
});
}); $(function() {
// Create accordion in settings dialog
return extensionMgr; _.chain(extensionList).sortBy(function(extension) {
return extension.extensionName.toLowerCase();
}).each(createSettings);
// Create extension buttons
logger.debug("onCreateButton");
var onCreateButtonCallbackList = getExtensionCallbackList("onCreateButton");
_.each(onCreateButtonCallbackList, function(callback) {
$("#extension-buttons").append($('<div class="btn-group">').append(callback()));
});
});
return extensionMgr;
}); });

View File

@ -2,78 +2,79 @@ define([
"jquery", "jquery",
"underscore" "underscore"
], function($, _) { ], function($, _) {
var buttonPublish = { var buttonPublish = {
extensionId: "buttonPublish", extensionId: "buttonPublish",
extensionName: 'Button "Publish"', extensionName: 'Button "Publish"',
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 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) { if(button === undefined) {
return; return;
} }
if(publishRunning === true || hasPublications === false || isOffline === true) { if(publishRunning === true || hasPublications === false || isOffline === true) {
button.addClass("disabled"); button.addClass("disabled");
} }
else { else {
button.removeClass("disabled"); button.removeClass("disabled");
} }
}; }
;
var publisher = undefined;
buttonPublish.onPublisherCreated = function(publisherParameter) { var publisher = undefined;
publisher = publisherParameter; buttonPublish.onPublisherCreated = function(publisherParameter) {
}; publisher = publisherParameter;
};
buttonPublish.onCreateButton = function() {
button = $([ buttonPublish.onCreateButton = function() {
'<button class="btn" title="Publish this document">', button = $([
'<i class="icon-share"></i>', '<button class="btn" title="Publish this document">',
'</button>'].join("") ' <i class="icon-share"></i>',
).click(function() { '</button>'
if(!$(this).hasClass("disabled")) { ].join("")).click(function() {
publisher.publish(); if(!$(this).hasClass("disabled")) {
} publisher.publish();
}); }
return button; });
}; return button;
};
buttonPublish.onPublishRunning = function(isRunning) {
publishRunning = isRunning; buttonPublish.onPublishRunning = function(isRunning) {
updateButtonState(); publishRunning = isRunning;
}; updateButtonState();
};
buttonPublish.onOfflineChanged = function(isOfflineParameter) {
isOffline = isOfflineParameter; buttonPublish.onOfflineChanged = function(isOfflineParameter) {
updateButtonState(); isOffline = isOfflineParameter;
}; updateButtonState();
};
// Check that current file has publications
var checkPublication = function() { // Check that current file has publications
if(_.size(currentFileDesc.publishLocations) === 0) { var checkPublication = function() {
hasPublications = false; if(_.size(currentFileDesc.publishLocations) === 0) {
} hasPublications = false;
else { }
hasPublications = true; else {
} hasPublications = true;
updateButtonState(); }
}; updateButtonState();
};
buttonPublish.onFileSelected = function(fileDesc) {
currentFileDesc = fileDesc; buttonPublish.onFileSelected = function(fileDesc) {
checkPublication(); currentFileDesc = fileDesc;
}; checkPublication();
};
buttonPublish.onPublishRemoved = checkPublication;
buttonPublish.onNewPublishSuccess = checkPublication; buttonPublish.onPublishRemoved = checkPublication;
buttonPublish.onNewPublishSuccess = checkPublication;
return buttonPublish;
return buttonPublish;
}); });

View File

@ -2,71 +2,73 @@ define([
"jquery", "jquery",
"underscore" "underscore"
], function($, _) { ], function($, _) {
var buttonShare = { var buttonShare = {
extensionId: "buttonShare", extensionId: "buttonShare",
extensionName: 'Button "Share"', extensionName: 'Button "Share"',
optional: true, optional: true,
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() { buttonShare.onCreateButton = function() {
return $([ return $([
'<button class="btn dropdown-toggle" data-toggle="dropdown" title="Share this document">', '<button class="btn dropdown-toggle" data-toggle="dropdown" title="Share this document">',
'<i class="icon-link"></i>', ' <i class="icon-link"></i>',
'</button>', '</button>',
'<div id="link-container" class="dropdown-menu pull-right">', '<div id="link-container" class="dropdown-menu pull-right">',
'<div class="link-list"></div>', ' <h3 class="muted">Sharing</h3>',
'<p class="no-link">To share this document you need first to ', ' <div class="link-list"></div>',
'<a href="#" class="action-publish-gist">publish it as a Gist</a>', ' <p class="no-link">To share this document you need first to ',
' in Markdown format.', ' <a href="#" class="action-publish-gist">publish it as a Gist</a>',
'</p>', ' in Markdown format.',
'<blockquote class="muted">', ' </p>',
'<b>NOTE:</b> You can open any URL within StackEdit using ', ' <blockquote class="muted">',
'<a href="viewer.html?url=https://raw.github.com/benweet/stackedit/master/README.md"', ' <b>NOTE:</b> You can open any URL within StackEdit using',
'title="Sharing example">viewer.html?url=...</a>', ' <a href="viewer.html?url=https://raw.github.com/benweet/stackedit/master/README.md"',
'</blockquote>', ' title="Sharing example">viewer.html?url=...</a>',
'</div>'].join("") ' </blockquote>',
); '</div>'
}; ].join(""));
};
var fileDesc = undefined;
var lineTemplate = [ var fileDesc = undefined;
var lineTemplate = [
'<div class="input-prepend">', '<div class="input-prepend">',
'<a href="<%= link %>" class="add-on" title="Sharing location"><i class="icon-link"></i></a>', ' <a href="<%= link %>" class="add-on" title="Sharing location"><i class="icon-link"></i></a>',
'<input class="span2" type="text" value="<%= link %>" readonly />', ' <input class="span2" type="text" value="<%= link %>" readonly />',
'</div>'].join(""); '</div>'
var refreshDocumentSharing = function(fileDescParameter) { ].join("");
if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { var refreshDocumentSharing = function(fileDescParameter) {
return; if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) {
} return;
}
var linkList = $("#link-container .link-list").empty();
$("#link-container .no-link").show(); var linkList = $("#link-container .link-list").empty();
$("#link-container .no-link").show();
var attributesList = _.values(fileDesc.publishLocations);
_.each(attributesList, function(attributes) { var attributesList = _.values(fileDesc.publishLocations);
if(attributes.sharingLink) { _.each(attributesList, function(attributes) {
var lineElement = $(_.template(lineTemplate, { if(attributes.sharingLink) {
link: attributes.sharingLink var lineElement = $(_.template(lineTemplate, {
})); link: attributes.sharingLink
lineElement.click(function(event) { }));
event.stopPropagation(); lineElement.click(function(event) {
}); event.stopPropagation();
linkList.append(lineElement); });
$("#link-container .no-link").hide(); linkList.append(lineElement);
} $("#link-container .no-link").hide();
}); }
}; });
};
buttonShare.onFileSelected = function(fileDescParameter) {
fileDesc = fileDescParameter; buttonShare.onFileSelected = function(fileDescParameter) {
refreshDocumentSharing(fileDescParameter); fileDesc = fileDescParameter;
}; refreshDocumentSharing(fileDescParameter);
};
buttonShare.onNewPublishSuccess = refreshDocumentSharing;
buttonShare.onPublishRemoved = refreshDocumentSharing; buttonShare.onNewPublishSuccess = refreshDocumentSharing;
buttonShare.onPublishRemoved = refreshDocumentSharing;
return buttonShare;
return buttonShare;
}); });

View File

@ -1,72 +1,78 @@
define([ define([
"jquery", "jquery",
"underscore" "underscore",
], function($, _) { "utils"
], function($, _, utils) {
var buttonStat = {
extensionId: "buttonStat", var buttonStat = {
extensionName: 'Button "Statistics"', extensionId: "buttonStat",
extensionName: 'Button "Statistics"',
optional: true, optional: true,
settingsBloc: '<p>Adds a "Document statistics" button in the navigation bar.</p>' defaultConfig: {
}; name1: "Words",
value1: "\\S+",
buttonStat.onCreateButton = function() { name2: "Characters",
return $([ value2: "\\S",
'<button class="btn dropdown-toggle" data-toggle="dropdown" title="Document statistics">', name3: "Paragraphs",
'<i class="icon-stat"></i>', value3: ".+",
'</button>', },
'<div id="statistics-container" class="dropdown-menu pull-right">', settingsBloc: [
'<div class="link-list"></div>', '<p>Adds a "Document statistics" button in the navigation bar.</p>',
'<p class="no-link">To share this document you need first to <a', '<p><div class="form-inline">',
'href="#" class="action-publish-gist">publish it as a Gist</a> in', ' <label class="label-text" for="input-stat-name1">Title</label>',
'Markdown format.', ' <input id="input-stat-name1" type="text" class="input-small">',
'</p>', ' <label class="label-text" for="input-stat-value1">RegExp</label>',
'<blockquote class="muted">', ' <input id="input-stat-value1" type="text" class="span2">',
'<b>NOTE:</b> You can open any URL within StackEdit using <a', '</div></p>',
'href="viewer.html?url=https://raw.github.com/benweet/stackedit/master/README.md"', '<p><div class="form-inline">',
'title="Sharing example">viewer.html?url=...</a>', ' <label class="label-text" for="input-stat-name2">Title</label>',
'</blockquote>', ' <input id="input-stat-name2" type="text" class="input-small">',
'</div>'].join("") ' <label class="label-text" for="input-stat-value2">RegExp</label>',
); ' <input id="input-stat-value2" type="text" class="span2">',
}; '</div></p>',
'<p><div class="form-inline">',
var fileDesc = undefined; ' <label class="label-text" for="input-stat-name3">Title</label>',
var lineTemplate = [ ' <input id="input-stat-name3" type="text" class="input-small">',
'<div class="input-prepend">', ' <label class="label-text" for="input-stat-value3">RegExp</label>',
'<a href="<%= link %>" class="add-on" title="Sharing location"><i class="icon-link"></i></a>', ' <input id="input-stat-value3" type="text" class="span2">',
'<input class="span2" type="text" value="<%= link %>" readonly />', '</div></p>'].join("")
'</div>'].join(""); };
var refreshDocumentSharing = function(fileDescParameter) {
if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { buttonStat.onLoadSettings = function() {
return; _.each(buttonStat.defaultConfig, function(value, key) {
} utils.setInputValue("#input-stat-" + key, buttonStat.config[key]);
});
var linkList = $("#link-container .link-list").empty(); };
$("#link-container .no-link").show();
buttonStat.onSaveSettings = function(newConfig, event) {
var attributesList = _.values(fileDesc.publishLocations); _.each(buttonStat.defaultConfig, function(value, key) {
_.each(attributesList, function(attributes) { newConfig[key] = utils.getInputTextValue("#input-stat-" + key, event);
if(attributes.sharingLink) { });
var lineElement = $(_.template(lineTemplate, { };
link: attributes.sharingLink
})); buttonStat.onCreateButton = function() {
lineElement.click(function(event) { return $([
event.stopPropagation(); '<button class="btn dropdown-toggle" data-toggle="dropdown" title="Document statistics">',
}); ' <i class="icon-stat"></i>',
linkList.append(lineElement); '</button>',
$("#link-container .no-link").hide(); '<div id="statistics-container" class="dropdown-menu pull-right">',
} ' <h3 class="muted">Statistics</h3>',
}); ' <div class="stat">',
}; ' <div>' + buttonStat.config.name1 + ': <span id="span-stat-value1"></span></div>',
' <div>' + buttonStat.config.name2 + ': <span id="span-stat-value2"></span></div>',
buttonStat.onFileSelected = function(fileDescParameter) { ' <div>' + buttonStat.config.name3 + ': <span id="span-stat-value3"></span></div>',
fileDesc = fileDescParameter; ' </div>',
refreshDocumentSharing(fileDescParameter); '</div>'
}; ].join(""));
};
buttonStat.onNewPublishSuccess = refreshDocumentSharing;
buttonStat.onPublishRemoved = refreshDocumentSharing; buttonStat.onPreviewFinished = function() {
var text = $("#wmd-preview").text();
return buttonStat; $("#span-stat-value1").text(text.match(new RegExp(buttonStat.config.value1, "g")).length);
$("#span-stat-value2").text(text.match(new RegExp(buttonStat.config.value2, "g")).length);
$("#span-stat-value3").text(text.match(new RegExp(buttonStat.config.value3, "g")).length);
};
return buttonStat;
}); });

View File

@ -2,77 +2,77 @@ define([
"jquery", "jquery",
"underscore" "underscore"
], function($, _) { ], function($, _) {
var buttonSync = { var buttonSync = {
extensionId: "buttonSync", extensionId: "buttonSync",
extensionName: 'Button "Synchronize"', extensionName: 'Button "Synchronize"',
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 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) { if(button === undefined) {
return; return;
} }
if(syncRunning === true || uploadPending === false || isOffline) { if(syncRunning === true || uploadPending === false || isOffline) {
button.addClass("disabled"); button.addClass("disabled");
} }
else { else {
button.removeClass("disabled"); button.removeClass("disabled");
} }
}; };
var synchronizer = undefined; var synchronizer = undefined;
buttonSync.onSynchronizerCreated = function(synchronizerParameter) { buttonSync.onSynchronizerCreated = function(synchronizerParameter) {
synchronizer = synchronizerParameter; synchronizer = synchronizerParameter;
}; };
buttonSync.onCreateButton = function() { buttonSync.onCreateButton = function() {
button = $([ button = $([
'<button class="btn" title="Synchronize all documents">', '<button class="btn" title="Synchronize all documents">',
'<i class="icon-refresh"></i>', ' <i class="icon-refresh"></i>',
'</button>'].join("") '</button>'
).click(function() { ].join("")).click(function() {
if(!$(this).hasClass("disabled")) { if(!$(this).hasClass("disabled")) {
synchronizer.forceSync(); synchronizer.forceSync();
} }
}); });
return button; return button;
}; };
buttonSync.onReady = updateButtonState; buttonSync.onReady = updateButtonState;
buttonSync.onSyncRunning = function(isRunning) { buttonSync.onSyncRunning = function(isRunning) {
syncRunning = isRunning; syncRunning = isRunning;
uploadPending = true; uploadPending = true;
updateButtonState(); updateButtonState();
}; };
buttonSync.onSyncSuccess = function() { buttonSync.onSyncSuccess = function() {
uploadPending = false; uploadPending = false;
updateButtonState(); updateButtonState();
}; };
buttonSync.onOfflineChanged = function(isOfflineParameter) { buttonSync.onOfflineChanged = function(isOfflineParameter) {
isOffline = isOfflineParameter; isOffline = isOfflineParameter;
updateButtonState(); 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) {
uploadPending = true; uploadPending = true;
updateButtonState(); updateButtonState();
} }
}; };
buttonSync.onContentChanged = checkSynchronization; buttonSync.onContentChanged = checkSynchronization;
buttonSync.onTitleChanged = checkSynchronization; buttonSync.onTitleChanged = checkSynchronization;
return buttonSync; return buttonSync;
}); });

View File

@ -3,70 +3,99 @@ define([
"underscore", "underscore",
"file-system" "file-system"
], function($, _, fileSystem) { ], function($, _, fileSystem) {
var documentSelector = {
extensionId: "documentSelector",
extensionName: "Document selector",
settingsBloc: '<p>Builds the "Open document" dropdown menu.</p>'
};
var fileMgr = undefined;
documentSelector.onFileMgrCreated = function(fileMgrParameter) {
fileMgr = fileMgrParameter;
};
var liMap = undefined;
var buildSelector = function() {
function composeTitle(fileDesc) {
var result = [];
var syncAttributesList = _.values(fileDesc.syncLocations);
var publishAttributesList = _.values(fileDesc.publishLocations);
var attributesList = syncAttributesList.concat(publishAttributesList);
_.chain(attributesList).sortBy(function(attributes) {
return attributes.provider.providerId;
}).each(function(attributes) {
result.push('<i class="icon-' + attributes.provider.providerId + '"></i>');
});
result.push(" ");
result.push(fileDesc.title);
return result.join("");
}
liMap = {}; var documentSelector = {
$("#file-selector li:not(.stick)").empty(); extensionId: "documentSelector",
_.chain( extensionName: "Document selector",
fileSystem settingsBloc: '<p>Builds the "Open document" dropdown menu.</p>'
).sortBy(function(fileDesc) { };
return fileDesc.title.toLowerCase();
}).each(function(fileDesc) { var fileMgr = undefined;
var a = $('<a href="#">').html(composeTitle(fileDesc)).click(function() { documentSelector.onFileMgrCreated = function(fileMgrParameter) {
if(!liMap[fileDesc.fileIndex].is(".disabled")) { fileMgr = fileMgrParameter;
fileMgr.selectFile(fileDesc); };
}
}); var liMap = undefined;
var li = $("<li>").append(a); var buildSelector = function() {
liMap[fileDesc.fileIndex] = li;
$("#file-selector").append(li); function composeTitle(fileDesc) {
}); var result = [];
}; var syncAttributesList = _.values(fileDesc.syncLocations);
var publishAttributesList = _.values(fileDesc.publishLocations);
documentSelector.onFileSelected = function(fileDesc) { var attributesList = syncAttributesList.concat(publishAttributesList);
if(liMap === undefined) { _.chain(attributesList).sortBy(function(attributes) {
buildSelector(); return attributes.provider.providerId;
} }).each(function(attributes) {
$("#file-selector li:not(.stick)").removeClass("disabled"); result.push('<i class="icon-' + attributes.provider.providerId + '"></i>');
liMap[fileDesc.fileIndex].addClass("disabled"); });
}; result.push(" ");
result.push(fileDesc.title);
documentSelector.onFileCreated = buildSelector; return result.join("");
documentSelector.onFileDeleted = buildSelector; }
documentSelector.onTitleChanged = buildSelector;
documentSelector.onSyncExportSuccess = buildSelector; liMap = {};
documentSelector.onSyncRemoved = buildSelector; $("#file-selector li:not(.stick)").empty();
documentSelector.onNewPublishSuccess = buildSelector; _.chain(fileSystem).sortBy(function(fileDesc) {
documentSelector.onPublishRemoved = buildSelector; return fileDesc.title.toLowerCase();
}).each(function(fileDesc) {
return documentSelector; var a = $('<a href="#">').html(composeTitle(fileDesc)).click(function() {
if(!liMap[fileDesc.fileIndex].is(".disabled")) {
fileMgr.selectFile(fileDesc);
}
});
var li = $("<li>").append(a);
liMap[fileDesc.fileIndex] = li;
$("#file-selector").append(li);
});
};
documentSelector.onFileSelected = function(fileDesc) {
if(liMap === undefined) {
buildSelector();
}
$("#file-selector li:not(.stick)").removeClass("disabled");
liMap[fileDesc.fileIndex].addClass("disabled");
};
documentSelector.onFileCreated = buildSelector;
documentSelector.onFileDeleted = buildSelector;
documentSelector.onTitleChanged = buildSelector;
documentSelector.onSyncExportSuccess = buildSelector;
documentSelector.onSyncRemoved = buildSelector;
documentSelector.onNewPublishSuccess = buildSelector;
documentSelector.onPublishRemoved = buildSelector;
// Filter for search input in file selector
function filterFileSelector(filter) {
var liList = $("#file-selector li:not(.stick)");
liList.show();
if(filter) {
var words = filter.toLowerCase().split(/\s+/);
liList.each(function() {
var fileTitle = $(this).text().toLowerCase();
if(_.some(words, function(word) {
return fileTitle.indexOf(word) === -1;
})) {
$(this).hide();
}
});
}
}
documentSelector.onReady = function() {
$(".action-open-file").click(function() {
filterFileSelector();
_.defer(function() {
$("#file-search").val("").focus();
});
});
$("#file-search").keyup(function() {
filterFileSelector($(this).val());
}).click(function(event) {
event.stopPropagation();
});
};
return documentSelector;
}); });

View File

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

View File

@ -1,20 +1,19 @@
define(function() { define(function() {
var emailConverter = { var emailConverter = {
extensionId: "emailConverter", extensionId: "emailConverter",
extensionName: "Email Converter", extensionName: "Email Converter",
optional: true, optional: true,
settingsBloc: '<p>Converts email adresses in the form &lt;email@example.com&gt; into a clickable links.</p>' settingsBloc: '<p>Converts email adresses in the form &lt;email@example.com&gt; into a clickable links.</p>'
}; };
emailConverter.onEditorConfigure = function(editor) { emailConverter.onEditorConfigure = function(editor) {
editor.getConverter().hooks.chain("postConversion", function(text) { editor.getConverter().hooks.chain("postConversion", function(text) {
return text.replace(/<(mailto\:)?([^\s>]+@[^\s>]+\.\S+?)>/g, function(match, mailto, email) { return text.replace(/<(mailto\:)?([^\s>]+@[^\s>]+\.\S+?)>/g, function(match, mailto, email) {
return '<a href="mailto:' + email + '">' + email + '</a>'; return '<a href="mailto:' + email + '">' + email + '</a>';
}); });
}); });
}; };
return emailConverter; return emailConverter;
}); });

View File

@ -2,65 +2,67 @@ define([
"jquery", "jquery",
"underscore" "underscore"
], function($, _) { ], function($, _) {
var managePublication = { var managePublication = {
extensionId: "managePublication", extensionId: "managePublication",
extensionName: "Manage publication", extensionName: "Manage publication",
settingsBloc: '<p>Populates the "Manage publication" dialog box.</p>' settingsBloc: '<p>Populates the "Manage publication" dialog box.</p>'
}; };
var fileMgr = undefined; var fileMgr = undefined;
managePublication.onFileMgrCreated = function(fileMgrParameter) { managePublication.onFileMgrCreated = function(fileMgrParameter) {
fileMgr = fileMgrParameter; fileMgr = fileMgrParameter;
}; };
var fileDesc = undefined; var fileDesc = undefined;
var lineTemplate = [ var lineTemplate = [
'<div class="input-prepend input-append">', '<div class="input-prepend input-append">',
'<span class="add-on" title="<%= provider.providerName %>">', ' <span class="add-on" title="<%= provider.providerName %>">',
'<i class="icon-<%= provider.providerId %>"></i>', ' <i class="icon-<%= provider.providerId %>"></i>',
'</span>', ' </span>',
'<input class="span5" type="text" value="<%= publishDesc %>" disabled />', ' <input class="span5" type="text" value="<%= publishDesc %>" disabled />',
'</div>'].join(""); '</div>'
var removeButtonTemplate = '<a class="btn" title="Remove this location"><i class="icon-trash"></i></a>'; ].join("");
var refreshDialog = function(fileDescParameter) { var removeButtonTemplate = '<a class="btn" title="Remove this location"><i class="icon-trash"></i></a>';
if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { var refreshDialog = function(fileDescParameter) {
return; if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) {
} return;
}
var publishAttributesList = _.values(fileDesc.publishLocations);
$(".msg-no-publish, .msg-publish-list").addClass("hide"); var publishAttributesList = _.values(fileDesc.publishLocations);
var publishList = $("#manage-publish-list").empty(); $(".msg-no-publish, .msg-publish-list").addClass("hide");
if (publishAttributesList.length > 0) { var publishList = $("#manage-publish-list").empty();
$(".msg-publish-list").removeClass("hide"); if(publishAttributesList.length > 0) {
} else { $(".msg-publish-list").removeClass("hide");
$(".msg-no-publish").removeClass("hide"); }
} else {
_.each(publishAttributesList, function(publishAttributes) { $(".msg-no-publish").removeClass("hide");
formattedAttributes = _.omit(publishAttributes, "provider", "publishIndex", "sharingLink"); }
if(formattedAttributes.password) { _.each(publishAttributesList, function(publishAttributes) {
formattedAttributes.password = "********"; formattedAttributes = _.omit(publishAttributes, "provider", "publishIndex", "sharingLink");
} if(formattedAttributes.password) {
var publishDesc = JSON.stringify(formattedAttributes).replace(/{|}|"/g, "").replace(/,/g, ", "); formattedAttributes.password = "********";
var lineElement = $(_.template(lineTemplate, { }
provider: publishAttributes.provider, var publishDesc = JSON.stringify(formattedAttributes).replace(/{|}|"/g, "").replace(/,/g, ", ");
publishDesc: publishDesc var lineElement = $(_.template(lineTemplate, {
})); provider: publishAttributes.provider,
lineElement.append($(removeButtonTemplate).click(function() { publishDesc: publishDesc
fileMgr.removePublish(publishAttributes); }));
})); lineElement.append($(removeButtonTemplate).click(function() {
publishList.append(lineElement); fileMgr.removePublish(publishAttributes);
}); }));
}; publishList.append(lineElement);
});
managePublication.onFileSelected = function(fileDescParameter) { };
fileDesc = fileDescParameter;
refreshDialog(fileDescParameter); managePublication.onFileSelected = function(fileDescParameter) {
}; fileDesc = fileDescParameter;
refreshDialog(fileDescParameter);
managePublication.onNewPublishSuccess = refreshDialog; };
managePublication.onPublishRemoved = refreshDialog;
managePublication.onNewPublishSuccess = refreshDialog;
return managePublication; managePublication.onPublishRemoved = refreshDialog;
return managePublication;
}); });

View File

@ -2,61 +2,63 @@ define([
"jquery", "jquery",
"underscore" "underscore"
], function($, _) { ], function($, _) {
var manageSynchronization = { var manageSynchronization = {
extensionId: "manageSynchronization", extensionId: "manageSynchronization",
extensionName: "Manage synchronization", extensionName: "Manage synchronization",
settingsBloc: '<p>Populates the "Manage synchronization" dialog box.</p>' settingsBloc: '<p>Populates the "Manage synchronization" dialog box.</p>'
}; };
var fileMgr = undefined; var fileMgr = undefined;
manageSynchronization.onFileMgrCreated = function(fileMgrParameter) { manageSynchronization.onFileMgrCreated = function(fileMgrParameter) {
fileMgr = fileMgrParameter; fileMgr = fileMgrParameter;
}; };
var fileDesc = undefined; var fileDesc = undefined;
var lineTemplate = [ var lineTemplate = [
'<div class="input-prepend input-append">', '<div class="input-prepend input-append">',
'<span class="add-on" title="<%= provider.providerName %>">', ' <span class="add-on" title="<%= provider.providerName %>">',
'<i class="icon-<%= provider.providerId %>"></i>', ' <i class="icon-<%= provider.providerId %>"></i>',
'</span>', ' </span>',
'<input class="span5" type="text" value="<%= syncDesc %>" disabled />', ' <input class="span5" type="text" value="<%= syncDesc %>" disabled />',
'</div>'].join(""); '</div>'
var removeButtonTemplate = '<a class="btn" title="Remove this location"><i class="icon-trash"></i></a>'; ].join("");
var refreshDialog = function(fileDescParameter) { var removeButtonTemplate = '<a class="btn" title="Remove this location"><i class="icon-trash"></i></a>';
if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) { var refreshDialog = function(fileDescParameter) {
return; if(fileDescParameter !== undefined && fileDescParameter !== fileDesc) {
} return;
}
var syncAttributesList = _.values(fileDesc.syncLocations);
$(".msg-no-sync, .msg-sync-list").addClass("hide"); var syncAttributesList = _.values(fileDesc.syncLocations);
var syncList = $("#manage-sync-list").empty(); $(".msg-no-sync, .msg-sync-list").addClass("hide");
if (syncAttributesList.length > 0) { var syncList = $("#manage-sync-list").empty();
$(".msg-sync-list").removeClass("hide"); if(syncAttributesList.length > 0) {
} else { $(".msg-sync-list").removeClass("hide");
$(".msg-no-sync").removeClass("hide"); }
} else {
_.each(syncAttributesList, function(syncAttributes) { $(".msg-no-sync").removeClass("hide");
var syncDesc = syncAttributes.id || syncAttributes.path; }
var lineElement = $(_.template(lineTemplate, { _.each(syncAttributesList, function(syncAttributes) {
provider: syncAttributes.provider, var syncDesc = syncAttributes.id || syncAttributes.path;
syncDesc: syncDesc var lineElement = $(_.template(lineTemplate, {
})); provider: syncAttributes.provider,
lineElement.append($(removeButtonTemplate).click(function() { syncDesc: syncDesc
fileMgr.removeSync(syncAttributes); }));
})); lineElement.append($(removeButtonTemplate).click(function() {
syncList.append(lineElement); fileMgr.removeSync(syncAttributes);
}); }));
}; syncList.append(lineElement);
});
manageSynchronization.onFileSelected = function(fileDescParameter) { };
fileDesc = fileDescParameter;
refreshDialog(fileDescParameter); manageSynchronization.onFileSelected = function(fileDescParameter) {
}; fileDesc = fileDescParameter;
refreshDialog(fileDescParameter);
manageSynchronization.onSyncExportSuccess = refreshDialog; };
manageSynchronization.onSyncRemoved = refreshDialog;
manageSynchronization.onSyncExportSuccess = refreshDialog;
return manageSynchronization; manageSynchronization.onSyncRemoved = refreshDialog;
return manageSynchronization;
}); });

View File

@ -2,44 +2,44 @@ define([
"utils", "utils",
"lib/Markdown.Extra" "lib/Markdown.Extra"
], function(utils) { ], function(utils) {
var markdownExtra = { var markdownExtra = {
extensionId: "markdownExtra", extensionId: "markdownExtra",
extensionName: "Markdown Extra", extensionName: "Markdown Extra",
optional: true, optional: true,
defaultConfig: { defaultConfig: {
prettify: true prettify: true
}, },
settingsBloc: [ settingsBloc: [
'<p>Adds extra features to the original Markdown syntax.</p>', '<p>Adds extra features to the original Markdown syntax.</p>',
'<div class="form-horizontal">', '<div class="form-horizontal">',
'<div class="control-group">', ' <div class="control-group">',
'<label class="control-label" for="input-markdownextra-prettify">Prettify syntax highlighting</label>', ' <label class="control-label" for="input-markdownextra-prettify">Prettify syntax highlighting</label>',
'<div class="controls">', ' <div class="controls">',
'<input type="checkbox" id="input-markdownextra-prettify">', ' <input type="checkbox" id="input-markdownextra-prettify">',
'</div>', ' </div>',
'</div>', ' </div>',
'</div>' '</div>'
].join("") ].join("")
}; };
markdownExtra.onLoadSettings = function() { markdownExtra.onLoadSettings = function() {
utils.setInputChecked("#input-markdownextra-prettify", markdownExtra.config.prettify); utils.setInputChecked("#input-markdownextra-prettify", markdownExtra.config.prettify);
}; };
markdownExtra.onSaveSettings = function(newConfig, event) { markdownExtra.onSaveSettings = function(newConfig, event) {
newConfig.prettify = utils.getInputChecked("#input-markdownextra-prettify"); newConfig.prettify = utils.getInputChecked("#input-markdownextra-prettify");
}; };
markdownExtra.onEditorConfigure = function(editor) { markdownExtra.onEditorConfigure = function(editor) {
var converter = editor.getConverter(); var converter = editor.getConverter();
var options = {}; var options = {};
if(markdownExtra.config.prettify === true) { if(markdownExtra.config.prettify === true) {
options.highlighter = "prettify"; options.highlighter = "prettify";
editor.hooks.chain("onPreviewRefresh", prettyPrint); editor.hooks.chain("onPreviewRefresh", prettyPrint);
} }
Markdown.Extra.init(converter, options); Markdown.Extra.init(converter, options);
}; };
return markdownExtra; return markdownExtra;
}); });

View File

@ -4,117 +4,117 @@ define([
"utils", "utils",
"jgrowl" "jgrowl"
], function($, _, utils, jGrowl) { ], function($, _, utils, jGrowl) {
var notifications = { var notifications = {
extensionId: "notifications", extensionId: "notifications",
extensionName: "Notifications", extensionName: "Notifications",
defaultConfig: { defaultConfig: {
timeout: 5000 timeout: 5000
}, },
settingsBloc: [ settingsBloc: [
'<p>Shows notification messages in the bottom-right corner of the screen.</p>', '<p>Shows notification messages in the bottom-right corner of the screen.</p>',
'<div class="form-horizontal">', '<div class="form-horizontal">',
'<div class="control-group">', ' <div class="control-group">',
'<label class="control-label" for="input-notifications-timeout">Timeout</label>', ' <label class="control-label" for="input-notifications-timeout">Timeout</label>',
'<div class="controls">', ' <div class="controls">',
'<input type="text" id="input-notifications-timeout" class="input-mini">', ' <input type="text" id="input-notifications-timeout" class="input-mini">',
'<span class="help-inline">ms</span>', ' <span class="help-inline">ms</span>',
'</div>', ' </div>',
'</div>', ' </div>',
'</div>' '</div>'
].join("") ].join("")
};
notifications.onLoadSettings = function() {
utils.setInputValue("#input-notifications-timeout", notifications.config.timeout);
}; };
notifications.onLoadSettings = function() {
utils.setInputValue("#input-notifications-timeout", notifications.config.timeout);
};
notifications.onSaveSettings = function(newConfig, event) { notifications.onSaveSettings = function(newConfig, event) {
newConfig.timeout = utils.getInputIntValue("#input-notifications-timeout", event, 1, 60000); newConfig.timeout = utils.getInputIntValue("#input-notifications-timeout", event, 1, 60000);
}; };
notifications.onReady = function() { notifications.onReady = function() {
// jGrowl configuration // jGrowl configuration
jGrowl.defaults.life = notifications.config.timeout; jGrowl.defaults.life = notifications.config.timeout;
jGrowl.defaults.closer = false; jGrowl.defaults.closer = false;
jGrowl.defaults.closeTemplate = ''; jGrowl.defaults.closeTemplate = '';
jGrowl.defaults.position = 'bottom-right'; jGrowl.defaults.position = 'bottom-right';
}; };
function showMessage(msg, iconClass, options) { function showMessage(msg, iconClass, options) {
if(!msg) { if(!msg) {
return; return;
} }
var endOfMsg = msg.indexOf("|"); var endOfMsg = msg.indexOf("|");
if(endOfMsg !== -1) { if(endOfMsg !== -1) {
msg = msg.substring(0, endOfMsg); msg = msg.substring(0, endOfMsg);
if(!msg) { if(!msg) {
return; return;
} }
} }
options = options || {}; options = options || {};
iconClass = iconClass || "icon-info-sign"; iconClass = iconClass || "icon-info-sign";
jGrowl("<i class='icon-white " + iconClass + "'></i> " + _.escape(msg), options); jGrowl("<i class='icon-white " + iconClass + "'></i> " + _.escape(msg), options);
} }
notifications.onMessage = function(message) { notifications.onMessage = function(message) {
logger.log(message); logger.log(message);
showMessage(message); showMessage(message);
}; };
notifications.onError = function(error) { notifications.onError = function(error) {
logger.error(error); logger.error(error);
if(_.isString(error)) { if(_.isString(error)) {
showMessage(error, "icon-warning-sign"); showMessage(error, "icon-warning-sign");
} }
else if(_.isObject(error)) { else if(_.isObject(error)) {
showMessage(error.message, "icon-warning-sign"); showMessage(error.message, "icon-warning-sign");
} }
}; };
notifications.onOfflineChanged = function(isOffline) { notifications.onOfflineChanged = function(isOffline) {
if(isOffline === true) { if(isOffline === true) {
showMessage("You are offline.", "icon-exclamation-sign msg-offline", { showMessage("You are offline.", "icon-exclamation-sign msg-offline", {
sticky : true, sticky: true,
close : function() { close: function() {
showMessage("You are back online!", "icon-signal"); showMessage("You are back online!", "icon-signal");
} }
}); });
} else { }
$(".msg-offline").parents(".jGrowl-notification").trigger( else {
'jGrowl.beforeClose'); $(".msg-offline").parents(".jGrowl-notification").trigger('jGrowl.beforeClose');
} }
}; };
notifications.onSyncImportSuccess = function(fileDescList, provider) { notifications.onSyncImportSuccess = function(fileDescList, provider) {
if(!fileDescList) { if(!fileDescList) {
return; return;
} }
var titles = _.map(fileDescList, function(fileDesc) { var titles = _.map(fileDescList, function(fileDesc) {
return fileDesc.title; return fileDesc.title;
}).join(", "); }).join(", ");
showMessage(titles + ' imported successfully from ' + provider.providerName + '.'); showMessage(titles + ' imported successfully from ' + provider.providerName + '.');
}; };
notifications.onSyncExportSuccess = function(fileDesc, syncAttributes) { notifications.onSyncExportSuccess = function(fileDesc, syncAttributes) {
showMessage('"' + fileDesc.title + '" will now be synchronized on ' + syncAttributes.provider.providerName + '.'); showMessage('"' + fileDesc.title + '" will now be synchronized on ' + syncAttributes.provider.providerName + '.');
}; };
notifications.onSyncRemoved = function(fileDesc, syncAttributes) { notifications.onSyncRemoved = function(fileDesc, syncAttributes) {
showMessage(syncAttributes.provider.providerName + " synchronized location has been removed."); showMessage(syncAttributes.provider.providerName + " synchronized location has been removed.");
}; };
notifications.onPublishSuccess = function(fileDesc) { notifications.onPublishSuccess = function(fileDesc) {
showMessage('"' + fileDesc.title + '" successfully published.'); showMessage('"' + fileDesc.title + '" successfully published.');
}; };
notifications.onNewPublishSuccess = function(fileDesc, publishIndex, publishAttributes) { notifications.onNewPublishSuccess = function(fileDesc, publishIndex, publishAttributes) {
showMessage('"' + fileDesc.title + '" is now published on ' + publishAttributes.provider.providerName + '.'); showMessage('"' + fileDesc.title + '" is now published on ' + publishAttributes.provider.providerName + '.');
}; };
notifications.onPublishRemoved = function(fileDesc, publishAttributes) { notifications.onPublishRemoved = function(fileDesc, publishAttributes) {
showMessage(publishAttributes.provider.providerName + " publish location has been removed."); showMessage(publishAttributes.provider.providerName + " publish location has been removed.");
}; };
return notifications; return notifications;
}); });

View File

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

View File

@ -3,116 +3,106 @@ define([
"underscore", "underscore",
"utils" "utils"
], function($, _, utils) { ], function($, _, utils) {
var toc = { var toc = {
extensionId: "toc", extensionId: "toc",
extensionName: "Table of content", extensionName: "Table of content",
optional: true, optional: true,
settingsBloc: '<p>Generates a table of content when a [TOC] marker is found.</p>' settingsBloc: '<p>Generates a table of content when a [TOC] marker is found.</p>'
}; };
// TOC element description // TOC element description
function TocElement(tagName, anchor, text) { function TocElement(tagName, anchor, text) {
this.tagName = tagName; this.tagName = tagName;
this.anchor = anchor; this.anchor = anchor;
this.text = text; this.text = text;
this.children = []; this.children = [];
} }
TocElement.prototype.childrenToString = function() { TocElement.prototype.childrenToString = function() {
if(this.children.length === 0) { if(this.children.length === 0) {
return ""; return "";
} }
var result = "<ul>"; var result = "<ul>";
_.each(this.children, function(child) { _.each(this.children, function(child) {
result += child.toString(); result += child.toString();
}); });
result += "</ul>"; result += "</ul>";
return result; return result;
}; };
TocElement.prototype.toString = function() { TocElement.prototype.toString = function() {
var result = "<li>"; var result = "<li>";
if(this.anchor && this.text) { if(this.anchor && this.text) {
result += '<a href="#' + this.anchor + '">' + this.text + '</a>'; result += '<a href="#' + this.anchor + '">' + this.text + '</a>';
} }
result += this.childrenToString() + "</li>"; result += this.childrenToString() + "</li>";
return result; return result;
}; };
// Transform flat list of TocElement into a tree // Transform flat list of TocElement into a tree
function groupTags(array, level) { function groupTags(array, level) {
level = level || 1; level = level || 1;
var tagName = "H" + level; var tagName = "H" + level;
var result = []; var result = [];
var currentElement = undefined; var currentElement = undefined;
function pushCurrentElement() { function pushCurrentElement() {
if(currentElement !== undefined) { if(currentElement !== undefined) {
if(currentElement.children.length > 0) { if(currentElement.children.length > 0) {
currentElement.children = groupTags(currentElement.children, level + 1); currentElement.children = groupTags(currentElement.children, level + 1);
} }
result.push(currentElement); result.push(currentElement);
} }
} }
_.each(array, function(element, index) { _.each(array, function(element, index) {
if(element.tagName != tagName) { if(element.tagName != tagName) {
if(currentElement === undefined) { if(currentElement === undefined) {
currentElement = new TocElement(); currentElement = new TocElement();
} }
currentElement.children.push(element); currentElement.children.push(element);
} }
else { else {
pushCurrentElement(); pushCurrentElement();
currentElement = element; currentElement = element;
} }
}); });
pushCurrentElement(); pushCurrentElement();
return result; return result;
} }
// Build the TOC // Build the TOC
function buildToc() { function buildToc() {
var anchorList = {}; var anchorList = {};
function createAnchor(element) { function createAnchor(element) {
var id = element.prop("id") || utils.slugify(element.text()); var id = element.prop("id") || utils.slugify(element.text());
var anchor = id; var anchor = id;
var index = 0; var index = 0;
while(_.has(anchorList, anchor)) { while (_.has(anchorList, anchor)) {
anchor = id + "-" + (++index); anchor = id + "-" + (++index);
} }
anchorList[anchor] = true; anchorList[anchor] = true;
// Update the id of the element // Update the id of the element
element.prop("id", anchor); element.prop("id", anchor);
return anchor; return anchor;
} }
var elementList = []; var elementList = [];
$("#wmd-preview > h1," + $("#wmd-preview > h1," + "#wmd-preview > h2," + "#wmd-preview > h3," + "#wmd-preview > h4," + "#wmd-preview > h5," + "#wmd-preview > h6").each(function() {
"#wmd-preview > h2," + elementList.push(new TocElement($(this).prop("tagName"), createAnchor($(this)), $(this).text()));
"#wmd-preview > h3," + });
"#wmd-preview > h4," + elementList = groupTags(elementList);
"#wmd-preview > h5," + return '<div class="toc"><ul>' + elementList.toString() + '</ul></div>';
"#wmd-preview > h6").each(function() { }
elementList.push(new TocElement(
$(this).prop("tagName"), toc.onEditorConfigure = function(editor) {
createAnchor($(this)), // Run TOC generation when conversion is finished directly on HTML
$(this).text() editor.hooks.chain("onPreviewRefresh", function() {
)); var toc = buildToc();
}); var html = $("#wmd-preview").html();
elementList = groupTags(elementList); html = html.replace(/<p>\[TOC\]<\/p>/g, toc);
return '<div class="toc"><ul>' + elementList.toString() + '</ul></div>'; $("#wmd-preview").html(html);
} });
};
toc.onEditorConfigure = function(editor) {
// Run TOC generation when conversion is finished directly on HTML return toc;
editor.hooks.chain("onPreviewRefresh", function() {
var toc = buildToc();
var html = $("#wmd-preview").html();
html = html.replace(/<p>\[TOC\]<\/p>/g, toc);
$("#wmd-preview").html(html);
});
};
return toc;
}); });

View File

@ -1,24 +1,25 @@
define([ define([
"jquery", "jquery",
"underscore" "underscore"
], function($, _) { ], function($, _) {
var workingIndicator = { var workingIndicator = {
extensionId: "workingIndicator", extensionId: "workingIndicator",
extensionName: "Working indicator", extensionName: "Working indicator",
settingsBloc: '<p>Displays an animated image when a network operation is running.</p>' settingsBloc: '<p>Displays an animated image when a network operation is running.</p>'
}; };
workingIndicator.onAsyncRunning = function(isRunning) { workingIndicator.onAsyncRunning = function(isRunning) {
if (isRunning === false) { if(isRunning === false) {
$(".working-indicator").removeClass("show"); $(".working-indicator").removeClass("show");
$("body").removeClass("working"); $("body").removeClass("working");
} else { }
$(".working-indicator").addClass("show"); else {
$("body").addClass("working"); $(".working-indicator").addClass("show");
} $("body").addClass("working");
}; }
};
return workingIndicator;
return workingIndicator;
}); });

View File

@ -8,347 +8,316 @@ define([
"file-system", "file-system",
"lib/text!../WELCOME.md" "lib/text!../WELCOME.md"
], function($, _, core, utils, settings, extensionMgr, fileSystem, welcomeContent) { ], function($, _, core, utils, settings, extensionMgr, fileSystem, welcomeContent) {
var fileMgr = {};
// Defines a file descriptor in the file system (fileDesc objects)
function FileDescriptor(fileIndex, title, syncLocations, publishLocations) {
this.fileIndex = fileIndex;
this.title = title;
this.syncLocations = syncLocations || {};
this.publishLocations = publishLocations || {};
}
FileDescriptor.prototype.getContent = function() {
return localStorage[this.fileIndex + ".content"];
};
FileDescriptor.prototype.setContent = function(content) {
localStorage[this.fileIndex + ".content"] = content;
extensionMgr.onContentChanged(this);
};
FileDescriptor.prototype.setTitle = function(title) {
this.title = title;
localStorage[this.fileIndex + ".title"] = title;
extensionMgr.onTitleChanged(this);
};
// Load file descriptors from localStorage var fileMgr = {};
_.chain(
localStorage["file.list"].split(";")
).compact().each(function(fileIndex) {
fileSystem[fileIndex] = new FileDescriptor(fileIndex, localStorage[fileIndex + ".title"]);
});
// Defines the current file
var currentFile = undefined;
fileMgr.getCurrentFile = function() {
return currentFile;
};
fileMgr.isCurrentFile = function(fileDesc) {
return fileDesc === currentFile;
};
fileMgr.setCurrentFile = function(fileDesc) {
currentFile = fileDesc;
if(fileDesc === undefined) {
localStorage.removeItem("file.current");
}
else if(fileDesc.fileIndex != TEMPORARY_FILE_INDEX) {
localStorage["file.current"] = fileDesc.fileIndex;
}
};
// Caution: this function recreates the editor (reset undo operations)
fileMgr.selectFile = function(fileDesc) {
fileDesc = fileDesc || fileMgr.getCurrentFile();
if(fileDesc === undefined) {
var fileSystemSize = _.size(fileSystem);
// If fileSystem empty create one file
if (fileSystemSize === 0) {
fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent);
}
else {
var fileIndex = localStorage["file.current"];
// If no file is selected take the last created
if(fileIndex === undefined) {
fileIndex = _.keys(fileSystem)[fileSystemSize - 1];
}
fileDesc = fileSystem[fileIndex];
}
}
if(fileMgr.isCurrentFile(fileDesc) === false) {
fileMgr.setCurrentFile(fileDesc);
// Notify extensions
extensionMgr.onFileSelected(fileDesc);
// Hide the viewer pencil button // Defines a file descriptor in the file system (fileDesc objects)
if(fileDesc.fileIndex == TEMPORARY_FILE_INDEX) { function FileDescriptor(fileIndex, title, syncLocations, publishLocations) {
$(".action-edit-document").removeClass("hide"); this.fileIndex = fileIndex;
} this._title = title;
else { this.__defineGetter__("title", function() {
$(".action-edit-document").addClass("hide"); return this._title;
} });
} this.__defineSetter__("title", function(title) {
this._title = title;
// Recreate the editor localStorage[this.fileIndex + ".title"] = title;
$("#wmd-input").val(fileDesc.getContent()); extensionMgr.onTitleChanged(this);
core.createEditor(function() { });
// Callback to save content when textarea changes this.__defineGetter__("content", function() {
fileMgr.saveFile(); return localStorage[this.fileIndex + ".content"];
}); });
}; this.__defineSetter__("content", function(content) {
localStorage[this.fileIndex + ".content"] = content;
fileMgr.createFile = function(title, content, syncLocations, isTemporary) { extensionMgr.onContentChanged(this);
content = content !== undefined ? content : settings.defaultContent; });
if (!title) { this.syncLocations = syncLocations || {};
// Create a file title this.publishLocations = publishLocations || {};
title = DEFAULT_FILE_TITLE; }
var indicator = 2;
while(_.some(fileSystem, function(fileDesc) {
return fileDesc.title == title;
})) {
title = DEFAULT_FILE_TITLE + indicator++;
}
}
// Generate a unique fileIndex
var fileIndex = TEMPORARY_FILE_INDEX;
if(!isTemporary) {
do {
fileIndex = "file." + utils.randomString();
} while(_.has(fileSystem, fileIndex));
}
// syncIndex associations
syncLocations = syncLocations || {};
var sync = _.reduce(syncLocations, function(sync, syncAttributes, syncIndex) {
return sync + syncIndex + ";";
}, ";");
localStorage[fileIndex + ".title"] = title;
localStorage[fileIndex + ".content"] = content;
localStorage[fileIndex + ".sync"] = sync;
localStorage[fileIndex + ".publish"] = ";";
// Create the file descriptor
var fileDesc = new FileDescriptor(fileIndex, title, syncLocations);
// Add the index to the file list
if(!isTemporary) {
localStorage["file.list"] += fileIndex + ";";
fileSystem[fileIndex] = fileDesc;
extensionMgr.onFileCreated(fileDesc);
}
return fileDesc;
};
fileMgr.deleteFile = function(fileDesc) { // Load file descriptors from localStorage
fileDesc = fileDesc || fileMgr.getCurrentFile(); _.chain(localStorage["file.list"].split(";")).compact().each(function(fileIndex) {
if(fileMgr.isCurrentFile(fileDesc) === true) { fileSystem[fileIndex] = new FileDescriptor(fileIndex, localStorage[fileIndex + ".title"]);
// Unset the current fileDesc });
fileMgr.setCurrentFile();
// Refresh the editor with an other file
fileMgr.selectFile();
}
// Remove synchronized locations // Defines the current file
_.each(fileDesc.syncLocations, function(syncAttributes) { var currentFile = undefined;
fileMgr.removeSync(syncAttributes, true); fileMgr.getCurrentFile = function() {
}); return currentFile;
};
// Remove publish locations fileMgr.isCurrentFile = function(fileDesc) {
_.each(fileDesc.publishLocations, function(publishAttributes) { return fileDesc === currentFile;
fileMgr.removePublish(publishAttributes, true); };
}); fileMgr.setCurrentFile = function(fileDesc) {
currentFile = fileDesc;
if(fileDesc === undefined) {
localStorage.removeItem("file.current");
}
else if(fileDesc.fileIndex != TEMPORARY_FILE_INDEX) {
localStorage["file.current"] = fileDesc.fileIndex;
}
};
// Remove the index from the file list // Caution: this function recreates the editor (reset undo operations)
var fileIndex = fileDesc.fileIndex; fileMgr.selectFile = function(fileDesc) {
localStorage["file.list"] = localStorage["file.list"].replace(";" fileDesc = fileDesc || fileMgr.getCurrentFile();
+ fileIndex + ";", ";");
localStorage.removeItem(fileIndex + ".title");
localStorage.removeItem(fileIndex + ".content");
localStorage.removeItem(fileIndex + ".sync");
localStorage.removeItem(fileIndex + ".publish");
fileSystem.removeItem(fileIndex);
extensionMgr.onFileDeleted(fileDesc);
};
// Save current file in localStorage if(fileDesc === undefined) {
fileMgr.saveFile = function() { var fileSystemSize = _.size(fileSystem);
var content = $("#wmd-input").val(); // If fileSystem empty create one file
var fileDesc = fileMgr.getCurrentFile(); if(fileSystemSize === 0) {
fileDesc.setContent(content); fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent);
}; }
else {
var fileIndex = localStorage["file.current"];
// If no file is selected take the last created
if(fileIndex === undefined) {
fileIndex = _.keys(fileSystem)[fileSystemSize - 1];
}
fileDesc = fileSystem[fileIndex];
}
}
// Add a synchronized location to a file if(fileMgr.isCurrentFile(fileDesc) === false) {
fileMgr.addSync = function(fileDesc, syncAttributes) { fileMgr.setCurrentFile(fileDesc);
localStorage[fileDesc.fileIndex + ".sync"] += syncAttributes.syncIndex + ";";
fileDesc.syncLocations[syncAttributes.syncIndex] = syncAttributes;
// addSync is only used for export, not for import
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
};
// Remove a synchronized location
fileMgr.removeSync = function(syncAttributes, skipExtensions) {
var fileDesc = fileMgr.getFileFromSyncIndex(syncAttributes.syncIndex);
if(fileDesc !== undefined) {
localStorage[fileDesc.fileIndex + ".sync"] = localStorage[fileDesc.fileIndex + ".sync"].replace(";"
+ syncAttributes.syncIndex + ";", ";");
}
// Remove sync attributes
localStorage.removeItem(syncAttributes.syncIndex);
fileDesc.syncLocations.removeItem(syncAttributes.syncIndex);
if(!skipExtensions) {
extensionMgr.onSyncRemoved(fileDesc, syncAttributes);
}
};
// Get the file descriptor associated to a syncIndex
fileMgr.getFileFromSyncIndex = function(syncIndex) {
return _.find(fileSystem, function(fileDesc) {
return _.has(fileDesc.syncLocations, syncIndex);
});
};
// Get syncAttributes from syncIndex
fileMgr.getSyncAttributes = function(syncIndex) {
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
return fileDesc && fileDesc.syncLocations[syncIndex];
};
// Returns true if provider has locations to synchronize
fileMgr.hasSync = function(provider) {
return _.some(fileSystem, function(fileDesc) {
return _.some(fileDesc.syncLocations, function(syncAttributes) {
return syncAttributes.provider === provider;
});
});
};
// Add a publishIndex (publish location) to a file // Notify extensions
fileMgr.addPublish = function(fileDesc, publishAttributes) { extensionMgr.onFileSelected(fileDesc);
localStorage[fileDesc.fileIndex + ".publish"] += publishAttributes.publishIndex + ";";
fileDesc.publishLocations[publishAttributes.publishIndex] = publishAttributes;
extensionMgr.onNewPublishSuccess(fileDesc, publishAttributes);
};
// Remove a publishIndex (publish location)
fileMgr.removePublish = function(publishAttributes, skipExtensions) {
var fileDesc = fileMgr.getFileFromPublishIndex(publishAttributes.publishIndex);
if(fileDesc !== undefined) {
localStorage[fileDesc.fileIndex + ".publish"] = localStorage[fileDesc.fileIndex + ".publish"].replace(";"
+ publishAttributes.publishIndex + ";", ";");
}
// Remove publish attributes
localStorage.removeItem(publishAttributes.publishIndex);
fileDesc.publishLocations.removeItem(publishAttributes.publishIndex);
if(!skipExtensions) {
extensionMgr.onPublishRemoved(fileDesc, publishAttributes);
}
};
// Get the file descriptor associated to a publishIndex
fileMgr.getFileFromPublishIndex = function(publishIndex) {
return _.find(fileSystem, function(fileDesc) {
return _.has(fileDesc.publishLocations, publishIndex);
});
};
// Filter for search input in file selector
function filterFileSelector(filter) {
var liList = $("#file-selector li:not(.stick)");
liList.show();
if(filter) {
var words = filter.toLowerCase().split(/\s+/);
liList.each(function() {
var fileTitle = $(this).text().toLowerCase();
if(_.some(words, function(word) {
return fileTitle.indexOf(word) === -1;
})) {
$(this).hide();
}
});
}
}
core.onReady(function() {
fileMgr.selectFile();
$(".action-create-file").click(function() { // Hide the viewer pencil button
var fileDesc = fileMgr.createFile(); if(fileDesc.fileIndex == TEMPORARY_FILE_INDEX) {
fileMgr.selectFile(fileDesc); $(".action-edit-document").removeClass("hide");
var wmdInput = $("#wmd-input").focus().get(0); }
if(wmdInput.setSelectionRange) { else {
wmdInput.setSelectionRange(0, 0); $(".action-edit-document").addClass("hide");
} }
$("#file-title").click(); }
});
$(".action-remove-file").click(function() {
fileMgr.deleteFile();
});
$("#file-title").click(function() {
if(viewerMode === true) {
return;
}
$(this).hide();
var fileTitleInput = $("#file-title-input").show();
_.defer(function() {
fileTitleInput.focus().get(0).select();
});
});
function applyTitle(input) {
input.hide();
$("#file-title").show();
var title = $.trim(input.val());
var fileDesc = fileMgr.getCurrentFile();
if (title && title != fileDesc.title) {
fileDesc.setTitle(title);
}
input.val(fileDesc.title);
$("#wmd-input").focus();
}
$("#file-title-input").blur(function() {
applyTitle($(this));
}).keyup(function(e) {
if (e.keyCode == 13) {
applyTitle($(this));
}
if (e.keyCode == 27) {
$(this).val("");
applyTitle($(this));
}
});
$(".action-open-file").click(function() {
filterFileSelector();
_.defer(function() {
$("#file-search").val("").focus();
});
});
$("#file-search").keyup(function() {
filterFileSelector($(this).val());
}).click(function(event) {
event.stopPropagation();
});
$(".action-open-stackedit").click(function() {
window.location.href = ".";
});
$(".action-edit-document").click(function() {
var content = $("#wmd-input").val();
var title = fileMgr.getCurrentFile().title;
var fileDesc = fileMgr.createFile(title, content);
fileMgr.selectFile(fileDesc);
window.location.href = ".";
});
$(".action-welcome-file").click(function() {
var fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent);
fileMgr.selectFile(fileDesc);
});
});
extensionMgr.onFileMgrCreated(fileMgr); // Recreate the editor
return fileMgr; $("#wmd-input").val(fileDesc.content);
core.createEditor(function() {
// Callback to save content when textarea changes
fileMgr.saveFile();
});
};
fileMgr.createFile = function(title, content, syncLocations, isTemporary) {
content = content !== undefined ? content : settings.defaultContent;
if(!title) {
// Create a file title
title = DEFAULT_FILE_TITLE;
var indicator = 2;
while (_.some(fileSystem, function(fileDesc) {
return fileDesc.title == title;
})) {
title = DEFAULT_FILE_TITLE + indicator++;
}
}
// Generate a unique fileIndex
var fileIndex = TEMPORARY_FILE_INDEX;
if(!isTemporary) {
do {
fileIndex = "file." + utils.randomString();
} while (_.has(fileSystem, fileIndex));
}
// syncIndex associations
syncLocations = syncLocations || {};
var sync = _.reduce(syncLocations, function(sync, syncAttributes, syncIndex) {
return sync + syncIndex + ";";
}, ";");
localStorage[fileIndex + ".title"] = title;
localStorage[fileIndex + ".content"] = content;
localStorage[fileIndex + ".sync"] = sync;
localStorage[fileIndex + ".publish"] = ";";
// Create the file descriptor
var fileDesc = new FileDescriptor(fileIndex, title, syncLocations);
// Add the index to the file list
if(!isTemporary) {
localStorage["file.list"] += fileIndex + ";";
fileSystem[fileIndex] = fileDesc;
extensionMgr.onFileCreated(fileDesc);
}
return fileDesc;
};
fileMgr.deleteFile = function(fileDesc) {
fileDesc = fileDesc || fileMgr.getCurrentFile();
if(fileMgr.isCurrentFile(fileDesc) === true) {
// Unset the current fileDesc
fileMgr.setCurrentFile();
// Refresh the editor with an other file
fileMgr.selectFile();
}
// Remove synchronized locations
_.each(fileDesc.syncLocations, function(syncAttributes) {
fileMgr.removeSync(syncAttributes, true);
});
// Remove publish locations
_.each(fileDesc.publishLocations, function(publishAttributes) {
fileMgr.removePublish(publishAttributes, true);
});
// Remove the index from the file list
var fileIndex = fileDesc.fileIndex;
localStorage["file.list"] = localStorage["file.list"].replace(";" + fileIndex + ";", ";");
localStorage.removeItem(fileIndex + ".title");
localStorage.removeItem(fileIndex + ".content");
localStorage.removeItem(fileIndex + ".sync");
localStorage.removeItem(fileIndex + ".publish");
fileSystem.removeItem(fileIndex);
extensionMgr.onFileDeleted(fileDesc);
};
// Save current file in localStorage
fileMgr.saveFile = function() {
var fileDesc = fileMgr.getCurrentFile();
fileDesc.content = $("#wmd-input").val();
};
// Add a synchronized location to a file
fileMgr.addSync = function(fileDesc, syncAttributes) {
localStorage[fileDesc.fileIndex + ".sync"] += syncAttributes.syncIndex + ";";
fileDesc.syncLocations[syncAttributes.syncIndex] = syncAttributes;
// addSync is only used for export, not for import
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
};
// Remove a synchronized location
fileMgr.removeSync = function(syncAttributes, skipExtensions) {
var fileDesc = fileMgr.getFileFromSyncIndex(syncAttributes.syncIndex);
if(fileDesc !== undefined) {
localStorage[fileDesc.fileIndex + ".sync"] = localStorage[fileDesc.fileIndex + ".sync"].replace(";" + syncAttributes.syncIndex + ";", ";");
}
// Remove sync attributes
localStorage.removeItem(syncAttributes.syncIndex);
fileDesc.syncLocations.removeItem(syncAttributes.syncIndex);
if(!skipExtensions) {
extensionMgr.onSyncRemoved(fileDesc, syncAttributes);
}
};
// Get the file descriptor associated to a syncIndex
fileMgr.getFileFromSyncIndex = function(syncIndex) {
return _.find(fileSystem, function(fileDesc) {
return _.has(fileDesc.syncLocations, syncIndex);
});
};
// Get syncAttributes from syncIndex
fileMgr.getSyncAttributes = function(syncIndex) {
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
return fileDesc && fileDesc.syncLocations[syncIndex];
};
// Returns true if provider has locations to synchronize
fileMgr.hasSync = function(provider) {
return _.some(fileSystem, function(fileDesc) {
return _.some(fileDesc.syncLocations, function(syncAttributes) {
return syncAttributes.provider === provider;
});
});
};
// Add a publishIndex (publish location) to a file
fileMgr.addPublish = function(fileDesc, publishAttributes) {
localStorage[fileDesc.fileIndex + ".publish"] += publishAttributes.publishIndex + ";";
fileDesc.publishLocations[publishAttributes.publishIndex] = publishAttributes;
extensionMgr.onNewPublishSuccess(fileDesc, publishAttributes);
};
// Remove a publishIndex (publish location)
fileMgr.removePublish = function(publishAttributes, skipExtensions) {
var fileDesc = fileMgr.getFileFromPublishIndex(publishAttributes.publishIndex);
if(fileDesc !== undefined) {
localStorage[fileDesc.fileIndex + ".publish"] = localStorage[fileDesc.fileIndex + ".publish"].replace(";" + publishAttributes.publishIndex + ";", ";");
}
// Remove publish attributes
localStorage.removeItem(publishAttributes.publishIndex);
fileDesc.publishLocations.removeItem(publishAttributes.publishIndex);
if(!skipExtensions) {
extensionMgr.onPublishRemoved(fileDesc, publishAttributes);
}
};
// Get the file descriptor associated to a publishIndex
fileMgr.getFileFromPublishIndex = function(publishIndex) {
return _.find(fileSystem, function(fileDesc) {
return _.has(fileDesc.publishLocations, publishIndex);
});
};
core.onReady(function() {
fileMgr.selectFile();
$(".action-create-file").click(function() {
var fileDesc = fileMgr.createFile();
fileMgr.selectFile(fileDesc);
var wmdInput = $("#wmd-input").focus().get(0);
if(wmdInput.setSelectionRange) {
wmdInput.setSelectionRange(0, 0);
}
$("#file-title").click();
});
$(".action-remove-file").click(function() {
fileMgr.deleteFile();
});
$("#file-title").click(function() {
if(viewerMode === true) {
return;
}
$(this).hide();
var fileTitleInput = $("#file-title-input").show();
_.defer(function() {
fileTitleInput.focus().get(0).select();
});
});
function applyTitle(input) {
input.hide();
$("#file-title").show();
var title = $.trim(input.val());
var fileDesc = fileMgr.getCurrentFile();
if(title && title != fileDesc.title) {
fileDesc.title = title;
}
input.val(fileDesc.title);
$("#wmd-input").focus();
}
$("#file-title-input").blur(function() {
applyTitle($(this));
}).keyup(function(e) {
if(e.keyCode == 13) {
applyTitle($(this));
}
if(e.keyCode == 27) {
$(this).val("");
applyTitle($(this));
}
});
$(".action-open-stackedit").click(function() {
window.location.href = ".";
});
$(".action-edit-document").click(function() {
var content = $("#wmd-input").val();
var title = fileMgr.getCurrentFile().title;
var fileDesc = fileMgr.createFile(title, content);
fileMgr.selectFile(fileDesc);
window.location.href = ".";
});
$(".action-welcome-file").click(function() {
var fileDesc = fileMgr.createFile(WELCOME_DOCUMENT_TITLE, welcomeContent);
fileMgr.selectFile(fileDesc);
});
});
extensionMgr.onFileMgrCreated(fileMgr);
return fileMgr;
}); });

View File

@ -6,280 +6,275 @@ define([
"file-manager", "file-manager",
"google-helper" "google-helper"
], function(_, core, utils, extensionMgr, fileMgr, googleHelper) { ], function(_, core, utils, extensionMgr, fileMgr, googleHelper) {
var PROVIDER_GDRIVE = "gdrive";
var gdriveProvider = {
providerId: PROVIDER_GDRIVE,
providerName: "Google Drive",
defaultPublishFormat: "template",
exportPreferencesInputIds: ["gdrive-parentid"]
};
function createSyncIndex(id) {
return "sync." + PROVIDER_GDRIVE + "." + id;
}
function createSyncAttributes(id, etag, content, title) {
var syncAttributes = {};
syncAttributes.provider = gdriveProvider;
syncAttributes.id = id;
syncAttributes.etag = etag;
syncAttributes.contentCRC = utils.crc32(content);
syncAttributes.titleCRC = utils.crc32(title);
syncAttributes.syncIndex = createSyncIndex(id);
utils.storeAttributes(syncAttributes);
return syncAttributes;
}
function importFilesFromIds(ids) {
googleHelper.downloadMetadata(ids, function(error, result) {
if(error) {
return;
}
googleHelper.downloadContent(result, function(error, result) {
if(error) {
return;
}
var fileDescList = [];
_.each(result, function(file) {
var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
var syncLocations = {};
syncLocations[syncAttributes.syncIndex] = syncAttributes;
var fileDesc = fileMgr.createFile(file.title, file.content, syncLocations);
fileMgr.selectFile(fileDesc);
fileDescList.push(fileDesc);
});
extensionMgr.onSyncImportSuccess(fileDescList, gdriveProvider);
});
});
};
gdriveProvider.importFiles = function() { var PROVIDER_GDRIVE = "gdrive";
googleHelper.picker(function(error, ids) {
if(error || ids.length === 0) {
return;
}
var importIds = [];
_.each(ids, function(id) {
var syncIndex = createSyncIndex(id);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) {
extensionMgr.onError('"' + fileDesc.title + '" was already imported');
return;
}
importIds.push(id);
});
importFilesFromIds(importIds);
});
};
gdriveProvider.exportFile = function(event, title, content, callback) {
var parentId = utils.getInputTextValue("#input-sync-export-gdrive-parentid");
googleHelper.upload(undefined, parentId, title, content, undefined, function(error, result) {
if (error) {
callback(error);
return;
}
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title);
callback(undefined, syncAttributes);
});
};
gdriveProvider.exportManual = function(event, title, content, callback) { var gdriveProvider = {
var id = utils.getInputTextValue("#input-sync-manual-gdrive-id", event); providerId: PROVIDER_GDRIVE,
if(!id) { providerName: "Google Drive",
return; defaultPublishFormat: "template",
} exportPreferencesInputIds: [
// Check that file is not synchronized with another an existing document "gdrive-parentid"
var syncIndex = createSyncIndex(id); ]
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); };
if(fileDesc !== undefined) {
extensionMgr.onError('File ID is already synchronized with "' + fileDesc.title + '"'); function createSyncIndex(id) {
callback(true); return "sync." + PROVIDER_GDRIVE + "." + id;
return; }
}
googleHelper.upload(id, undefined, title, content, undefined, function(error, result) { function createSyncAttributes(id, etag, content, title) {
if (error) { var syncAttributes = {};
callback(error); syncAttributes.provider = gdriveProvider;
return; syncAttributes.id = id;
} syncAttributes.etag = etag;
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title); syncAttributes.contentCRC = utils.crc32(content);
callback(undefined, syncAttributes); syncAttributes.titleCRC = utils.crc32(title);
}); syncAttributes.syncIndex = createSyncIndex(id);
}; utils.storeAttributes(syncAttributes);
return syncAttributes;
gdriveProvider.syncUp = function(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, callback) { }
var syncContentCRC = syncAttributes.contentCRC;
var syncTitleCRC = syncAttributes.titleCRC; function importFilesFromIds(ids) {
// Skip if CRC has not changed googleHelper.downloadMetadata(ids, function(error, result) {
if(uploadContentCRC == syncContentCRC && uploadTitleCRC == syncTitleCRC) { if(error) {
callback(undefined, false); return;
return; }
} googleHelper.downloadContent(result, function(error, result) {
googleHelper.upload(syncAttributes.id, undefined, uploadTitle, uploadContent, syncAttributes.etag, function(error, result) { if(error) {
if(error) { return;
callback(error, true); }
return; var fileDescList = [];
} _.each(result, function(file) {
syncAttributes.etag = result.etag; var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
syncAttributes.contentCRC = uploadContentCRC; var syncLocations = {};
syncAttributes.titleCRC = uploadTitleCRC; syncLocations[syncAttributes.syncIndex] = syncAttributes;
callback(undefined, true); var fileDesc = fileMgr.createFile(file.title, file.content, syncLocations);
}); fileMgr.selectFile(fileDesc);
}; fileDescList.push(fileDesc);
});
gdriveProvider.syncDown = function(callback) { extensionMgr.onSyncImportSuccess(fileDescList, gdriveProvider);
var lastChangeId = parseInt(localStorage[PROVIDER_GDRIVE + ".lastChangeId"]); });
googleHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) { });
if (error) { }
callback(error); ;
return;
} gdriveProvider.importFiles = function() {
var interestingChanges = []; googleHelper.picker(function(error, ids) {
_.each(changes, function(change) { if(error || ids.length === 0) {
var syncIndex = createSyncIndex(change.fileId); return;
var syncAttributes = fileMgr.getSyncAttributes(syncIndex); }
if(syncAttributes === undefined) { var importIds = [];
return; _.each(ids, function(id) {
} var syncIndex = createSyncIndex(id);
// Store syncAttributes to avoid 2 times searching var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
change.syncAttributes = syncAttributes; if(fileDesc !== undefined) {
// Delete extensionMgr.onError('"' + fileDesc.title + '" was already imported');
if(change.deleted === true) { return;
interestingChanges.push(change); }
return; importIds.push(id);
} });
// Modify importFilesFromIds(importIds);
if(syncAttributes.etag != change.file.etag) { });
interestingChanges.push(change); };
}
}); gdriveProvider.exportFile = function(event, title, content, callback) {
googleHelper.downloadContent(interestingChanges, function(error, changes) { var parentId = utils.getInputTextValue("#input-sync-export-gdrive-parentid");
if (error) { googleHelper.upload(undefined, parentId, title, content, undefined, function(error, result) {
callback(error); if(error) {
return; callback(error);
} return;
_.each(changes, function(change) { }
var syncAttributes = change.syncAttributes; var syncAttributes = createSyncAttributes(result.id, result.etag, content, title);
var syncIndex = syncAttributes.syncIndex; callback(undefined, syncAttributes);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); });
// No file corresponding (file may have been deleted locally) };
if(fileDesc === undefined) {
return; gdriveProvider.exportManual = function(event, title, content, callback) {
} var id = utils.getInputTextValue("#input-sync-manual-gdrive-id", event);
var localTitle = fileDesc.title; if(!id) {
// File deleted return;
if (change.deleted === true) { }
extensionMgr.onError('"' + localTitle + '" has been removed from Google Drive.'); // Check that file is not synchronized with another an existing document
fileMgr.removeSync(syncAttributes); var syncIndex = createSyncIndex(id);
return; var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
} if(fileDesc !== undefined) {
var localTitleChanged = syncAttributes.titleCRC != utils.crc32(localTitle); extensionMgr.onError('File ID is already synchronized with "' + fileDesc.title + '"');
var localContent = fileDesc.getContent(); callback(true);
var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent); return;
var file = change.file; }
googleHelper.upload(id, undefined, title, content, undefined, function(error, result) {
if(error) {
callback(error);
return;
}
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title);
callback(undefined, syncAttributes);
});
};
gdriveProvider.syncUp = function(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, callback) {
var syncContentCRC = syncAttributes.contentCRC;
var syncTitleCRC = syncAttributes.titleCRC;
// Skip if CRC has not changed
if(uploadContentCRC == syncContentCRC && uploadTitleCRC == syncTitleCRC) {
callback(undefined, false);
return;
}
googleHelper.upload(syncAttributes.id, undefined, uploadTitle, uploadContent, syncAttributes.etag, function(error, result) {
if(error) {
callback(error, true);
return;
}
syncAttributes.etag = result.etag;
syncAttributes.contentCRC = uploadContentCRC;
syncAttributes.titleCRC = uploadTitleCRC;
callback(undefined, true);
});
};
gdriveProvider.syncDown = function(callback) {
var lastChangeId = parseInt(localStorage[PROVIDER_GDRIVE + ".lastChangeId"]);
googleHelper.checkChanges(lastChangeId, function(error, changes, newChangeId) {
if(error) {
callback(error);
return;
}
var interestingChanges = [];
_.each(changes, function(change) {
var syncIndex = createSyncIndex(change.fileId);
var syncAttributes = fileMgr.getSyncAttributes(syncIndex);
if(syncAttributes === undefined) {
return;
}
// Store syncAttributes to avoid 2 times searching
change.syncAttributes = syncAttributes;
// Delete
if(change.deleted === true) {
interestingChanges.push(change);
return;
}
// Modify
if(syncAttributes.etag != change.file.etag) {
interestingChanges.push(change);
}
});
googleHelper.downloadContent(interestingChanges, function(error, changes) {
if(error) {
callback(error);
return;
}
_.each(changes, function(change) {
var syncAttributes = change.syncAttributes;
var syncIndex = syncAttributes.syncIndex;
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
// No file corresponding (file may have been deleted
// locally)
if(fileDesc === undefined) {
return;
}
var localTitle = fileDesc.title;
// File deleted
if(change.deleted === true) {
extensionMgr.onError('"' + localTitle + '" has been removed from Google Drive.');
fileMgr.removeSync(syncAttributes);
return;
}
var localTitleChanged = syncAttributes.titleCRC != utils.crc32(localTitle);
var localContent = fileDesc.content;
var localContentChanged = syncAttributes.contentCRC != utils.crc32(localContent);
var file = change.file;
var remoteTitleCRC = utils.crc32(file.title); var remoteTitleCRC = utils.crc32(file.title);
var remoteTitleChanged = syncAttributes.titleCRC != remoteTitleCRC; var remoteTitleChanged = syncAttributes.titleCRC != remoteTitleCRC;
var fileTitleChanged = localTitle != file.title; var fileTitleChanged = localTitle != file.title;
var remoteContentCRC = utils.crc32(file.content); var remoteContentCRC = utils.crc32(file.content);
var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC; var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC;
var fileContentChanged = localContent != file.content; var fileContentChanged = localContent != file.content;
// 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)) { fileMgr.createFile(localTitle + " (backup)", localContent);
fileMgr.createFile(localTitle + " (backup)", localContent); 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) { fileDesc.title = file.title;
fileDesc.setTitle(file.title); 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) { fileDesc.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 }
} }
} // Update syncAttributes
// Update syncAttributes syncAttributes.etag = file.etag;
syncAttributes.etag = file.etag; syncAttributes.contentCRC = remoteContentCRC;
syncAttributes.contentCRC = remoteContentCRC; syncAttributes.titleCRC = remoteTitleCRC;
syncAttributes.titleCRC = remoteTitleCRC; utils.storeAttributes(syncAttributes);
utils.storeAttributes(syncAttributes); });
}); localStorage[PROVIDER_GDRIVE + ".lastChangeId"] = newChangeId;
localStorage[PROVIDER_GDRIVE + ".lastChangeId"] = newChangeId; callback();
callback(); });
}); });
}); };
};
gdriveProvider.publish = function(publishAttributes, title, content, callback) {
googleHelper.upload(
publishAttributes.id,
undefined,
publishAttributes.fileName || title,
content,
undefined,
function(error, result) {
if(error) {
callback(error);
return;
}
publishAttributes.id = result.id;
callback();
}
);
};
gdriveProvider.newPublishAttributes = function(event) { gdriveProvider.publish = function(publishAttributes, title, content, callback) {
var publishAttributes = {}; googleHelper.upload(publishAttributes.id, undefined, publishAttributes.fileName || title, content, undefined, function(error, result) {
publishAttributes.id = utils.getInputTextValue("#input-publish-gdrive-fileid"); if(error) {
publishAttributes.fileName = utils.getInputTextValue("#input-publish-gdrive-filename"); callback(error);
if(event.isPropagationStopped()) { return;
return undefined; }
} publishAttributes.id = result.id;
return publishAttributes; callback();
}; });
};
core.onReady(function() {
var state = localStorage[PROVIDER_GDRIVE + ".state"];
if(state === undefined) {
return;
}
localStorage.removeItem(PROVIDER_GDRIVE + ".state");
state = JSON.parse(state);
if (state.action == "create") {
googleHelper.upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE,
"", undefined, function(error, file) {
if(error) {
return;
}
var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
var syncLocations = {};
syncLocations[syncAttributes.syncIndex] = syncAttributes;
var fileDesc = fileMgr.createFile(file.title, file.content, syncAttributes);
fileMgr.selectFile(fileDesc);
extensionMgr.onMessage('"' + file.title + '" created successfully on Google Drive.');
});
}
else if (state.action == "open") {
var importIds = [];
_.each(state.ids, function(id) {
var syncIndex = createSyncIndex(id);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) {
fileMgr.selectFile(fileDesc);
}
else {
importIds.push(id);
}
});
importFilesFromIds(importIds);
}
});
return gdriveProvider; gdriveProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
publishAttributes.id = utils.getInputTextValue("#input-publish-gdrive-fileid");
publishAttributes.fileName = utils.getInputTextValue("#input-publish-gdrive-filename");
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
core.onReady(function() {
var state = localStorage[PROVIDER_GDRIVE + ".state"];
if(state === undefined) {
return;
}
localStorage.removeItem(PROVIDER_GDRIVE + ".state");
state = JSON.parse(state);
if(state.action == "create") {
googleHelper.upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE, "", undefined, function(error, file) {
if(error) {
return;
}
var syncAttributes = createSyncAttributes(file.id, file.etag, file.content, file.title);
var syncLocations = {};
syncLocations[syncAttributes.syncIndex] = syncAttributes;
var fileDesc = fileMgr.createFile(file.title, file.content, syncAttributes);
fileMgr.selectFile(fileDesc);
extensionMgr.onMessage('"' + file.title + '" created successfully on Google Drive.');
});
}
else if(state.action == "open") {
var importIds = [];
_.each(state.ids, function(id) {
var syncIndex = createSyncIndex(id);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) {
fileMgr.selectFile(fileDesc);
}
else {
importIds.push(id);
}
});
importFilesFromIds(importIds);
}
});
return gdriveProvider;
}); });

View File

@ -2,42 +2,43 @@ define([
"utils", "utils",
"github-helper" "github-helper"
], function(utils, githubHelper) { ], function(utils, githubHelper) {
var PROVIDER_GIST = "gist";
var gistProvider = {
providerId: PROVIDER_GIST,
providerName: "Gist",
sharingAttributes: ["gistId", "filename"]
};
gistProvider.publish = function(publishAttributes, title, content, callback) {
githubHelper.uploadGist(publishAttributes.gistId, publishAttributes.filename, publishAttributes.isPublic,
title, content, function(error, gistId) {
if(error) {
callback(error);
return;
}
publishAttributes.gistId = gistId;
callback();
}
);
};
gistProvider.newPublishAttributes = function(event) { var PROVIDER_GIST = "gist";
var publishAttributes = {};
publishAttributes.gistId = utils.getInputTextValue("#input-publish-gist-id");
publishAttributes.filename = utils.getInputTextValue("#input-publish-filename", event);
publishAttributes.isPublic = utils.getInputChecked("#input-publish-gist-public");
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
gistProvider.importPublic = function(importParameters, callback) {
githubHelper.downloadGist(importParameters.gistId, importParameters.filename, callback);
};
return gistProvider; var gistProvider = {
providerId: PROVIDER_GIST,
providerName: "Gist",
sharingAttributes: [
"gistId",
"filename"
]
};
gistProvider.publish = function(publishAttributes, title, content, callback) {
githubHelper.uploadGist(publishAttributes.gistId, publishAttributes.filename, publishAttributes.isPublic, title, content, function(error, gistId) {
if(error) {
callback(error);
return;
}
publishAttributes.gistId = gistId;
callback();
});
};
gistProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
publishAttributes.gistId = utils.getInputTextValue("#input-publish-gist-id");
publishAttributes.filename = utils.getInputTextValue("#input-publish-filename", event);
publishAttributes.isPublic = utils.getInputChecked("#input-publish-gist-public");
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
gistProvider.importPublic = function(importParameters, callback) {
githubHelper.downloadGist(importParameters.gistId, importParameters.filename, callback);
};
return gistProvider;
}); });

View File

@ -6,247 +6,249 @@ define([
"async-runner" "async-runner"
], function($, core, utils, extensionMgr, asyncRunner) { ], function($, core, utils, extensionMgr, asyncRunner) {
var connected = undefined; var connected = undefined;
var github = undefined; var github = undefined;
var githubHelper = {}; var githubHelper = {};
// Try to connect github by downloading js file // Try to connect github by downloading js file
function connect(task) { function connect(task) {
task.onRun(function() { task.onRun(function() {
if(core.isOffline === true) { if(core.isOffline === true) {
connected = false; connected = false;
task.error(new Error("Operation not available in offline mode.|stopPublish")); task.error(new Error("Operation not available in offline mode.|stopPublish"));
return; return;
} }
if (connected === true) { if(connected === true) {
task.chain(); task.chain();
return; return;
} }
$.ajax({ $.ajax({
url : "lib/github.js", url: "lib/github.js",
dataType : "script", timeout : AJAX_TIMEOUT dataType: "script",
}).done(function() { timeout: AJAX_TIMEOUT
connected = true; }).done(function() {
task.chain(); connected = true;
}).fail(function(jqXHR) { task.chain();
var error = { }).fail(function(jqXHR) {
error: jqXHR.status, var error = {
message: jqXHR.statusText error: jqXHR.status,
}; message: jqXHR.statusText
handleError(error, task); };
}); handleError(error, task);
}); });
} });
}
// Try to authenticate with Oauth // Try to authenticate with Oauth
function authenticate(task) { function authenticate(task) {
var authWindow = undefined; var authWindow = undefined;
var intervalId = undefined; var intervalId = undefined;
task.onRun(function() { task.onRun(function() {
if (github !== undefined) { if(github !== undefined) {
task.chain(); task.chain();
return; return;
} }
var token = localStorage["githubToken"]; var token = localStorage["githubToken"];
if(token !== undefined) { if(token !== undefined) {
github = new Github({ github = new Github({
token: token, token: token,
auth: "oauth" auth: "oauth"
}); });
task.chain(); task.chain();
return; return;
} }
extensionMgr.onMessage("Please make sure the Github authorization popup is not blocked by your browser."); extensionMgr.onMessage("Please make sure the Github authorization popup is not blocked by your browser.");
var errorMsg = "Failed to retrieve a token from GitHub."; var errorMsg = "Failed to retrieve a token from GitHub.";
// We add time for user to enter his credentials // We add time for user to enter his credentials
task.timeout = ASYNC_TASK_LONG_TIMEOUT; task.timeout = ASYNC_TASK_LONG_TIMEOUT;
var code = undefined; var code = undefined;
function getCode() { function getCode() {
localStorage.removeItem("githubCode"); localStorage.removeItem("githubCode");
authWindow = utils.popupWindow( authWindow = utils.popupWindow('github-oauth-client.html?client_id=' + GITHUB_CLIENT_ID, 'stackedit-github-oauth', 960, 600);
'github-oauth-client.html?client_id=' + GITHUB_CLIENT_ID, authWindow.focus();
'stackedit-github-oauth', 960, 600); intervalId = setInterval(function() {
authWindow.focus(); if(authWindow.closed === true) {
intervalId = setInterval(function() { clearInterval(intervalId);
if(authWindow.closed === true) { authWindow = undefined;
clearInterval(intervalId); intervalId = undefined;
authWindow = undefined; code = localStorage["githubCode"];
intervalId = undefined; if(code === undefined) {
code = localStorage["githubCode"]; task.error(new Error(errorMsg));
if(code === undefined) { return;
task.error(new Error(errorMsg)); }
return; localStorage.removeItem("githubCode");
} task.chain(getToken);
localStorage.removeItem("githubCode"); }
task.chain(getToken); }, 500);
} }
}, 500); function getToken() {
} $.getJSON(GATEKEEPER_URL + "authenticate/" + code, function(data) {
function getToken() { if(data.token !== undefined) {
$.getJSON(GATEKEEPER_URL + "authenticate/" + code, function(data) { token = data.token;
if(data.token !== undefined) { localStorage["githubToken"] = token;
token = data.token; github = new Github({
localStorage["githubToken"] = token; token: token,
github = new Github({ auth: "oauth"
token: token, });
auth: "oauth" task.chain();
}); }
task.chain(); else {
} task.error(new Error(errorMsg));
else { }
task.error(new Error(errorMsg)); });
} }
}); task.chain(getCode);
} });
task.chain(getCode); task.onError(function() {
}); if(intervalId !== undefined) {
task.onError(function() { clearInterval(intervalId);
if(intervalId !== undefined) { }
clearInterval(intervalId); if(authWindow !== undefined) {
} authWindow.close();
if(authWindow !== undefined) { }
authWindow.close(); });
} }
});
}
githubHelper.upload = function(reponame, branch, path, content, commitMsg, callback) { githubHelper.upload = function(reponame, branch, path, content, commitMsg, callback) {
var task = asyncRunner.createTask(); var task = asyncRunner.createTask();
connect(task); connect(task);
authenticate(task); authenticate(task);
task.onRun(function() { task.onRun(function() {
var userLogin = undefined; var userLogin = undefined;
function getUserLogin() { function getUserLogin() {
var user = github.getUser(); var user = github.getUser();
user.show(undefined, function(err, result) { user.show(undefined, function(err, result) {
if(err) { if(err) {
handleError(err, task); handleError(err, task);
return; return;
} }
userLogin = result.login; userLogin = result.login;
task.chain(write); task.chain(write);
}); });
} }
function write() { function write() {
var repo = github.getRepo(userLogin, reponame); var repo = github.getRepo(userLogin, reponame);
repo.write(branch, path, content, commitMsg, function(err) { repo.write(branch, path, content, commitMsg, function(err) {
if(err) { if(err) {
handleError(err, task); handleError(err, task);
return; return;
} }
task.chain(); task.chain();
}); });
} }
task.chain(getUserLogin); task.chain(getUserLogin);
}); });
task.onSuccess(function() { task.onSuccess(function() {
callback(); callback();
}); });
task.onError(function(error) { task.onError(function(error) {
callback(error); callback(error);
}); });
asyncRunner.addTask(task); asyncRunner.addTask(task);
}; };
githubHelper.uploadGist = function(gistId, filename, isPublic, title, content, callback) {
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
var gist = github.getGist(gistId);
var files = {};
files[filename] = {content: content};
githubFunction = gist.update;
if(gistId === undefined) {
githubFunction = gist.create;
}
githubFunction({
description: title,
"public": isPublic,
files: files
}, function(err, gist) {
if(err) {
// Handle error
if(err.error === 404 && gistId !== undefined) {
err = 'Gist ' + gistId + ' not found on GitHub.|removePublish';
}
handleError(err, task);
return;
}
gistId = gist.id;
task.chain();
});
});
task.onSuccess(function() {
callback(undefined, gistId);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
githubHelper.downloadGist = function(gistId, filename, callback) {
var task = asyncRunner.createTask();
connect(task);
// No need for authentication
var title = undefined;
var content = undefined;
task.onRun(function() {
var github = new Github({});
var gist = github.getGist(gistId);
gist.read(function(err, gist) {
if(err) {
// Handle error
task.error(new Error('Error trying to access Gist ' + gistId + '.'));
return;
}
title = gist.description;
var file = gist.files[filename];
if(file === undefined) {
task.error(new Error('Gist ' + gistId + ' does not contain "' + filename + '".'));
return;
}
content = file.content;
task.chain();
});
});
task.onSuccess(function() {
callback(undefined, title, content);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
function handleError(error, task) {
var errorMsg = undefined;
if (error) {
logger.error(error);
// Try to analyze the error
if (typeof error === "string") {
errorMsg = error;
}
else {
errorMsg = "Could not publish on GitHub.";
if (error.error === 401 || error.error === 403) {
github = undefined;
localStorage.removeItem("githubToken");
errorMsg = "Access to GitHub account is not authorized.";
task.retry(new Error(errorMsg), 1);
return;
} else if (error.error <= 0) {
connected = false;
github = undefined;
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
return githubHelper; githubHelper.uploadGist = function(gistId, filename, isPublic, title, content, callback) {
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
var gist = github.getGist(gistId);
var files = {};
files[filename] = {
content: content
};
githubFunction = gist.update;
if(gistId === undefined) {
githubFunction = gist.create;
}
githubFunction({
description: title,
"public": isPublic,
files: files
}, function(err, gist) {
if(err) {
// Handle error
if(err.error === 404 && gistId !== undefined) {
err = 'Gist ' + gistId + ' not found on GitHub.|removePublish';
}
handleError(err, task);
return;
}
gistId = gist.id;
task.chain();
});
});
task.onSuccess(function() {
callback(undefined, gistId);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
githubHelper.downloadGist = function(gistId, filename, callback) {
var task = asyncRunner.createTask();
connect(task);
// No need for authentication
var title = undefined;
var content = undefined;
task.onRun(function() {
var github = new Github({});
var gist = github.getGist(gistId);
gist.read(function(err, gist) {
if(err) {
// Handle error
task.error(new Error('Error trying to access Gist ' + gistId + '.'));
return;
}
title = gist.description;
var file = gist.files[filename];
if(file === undefined) {
task.error(new Error('Gist ' + gistId + ' does not contain "' + filename + '".'));
return;
}
content = file.content;
task.chain();
});
});
task.onSuccess(function() {
callback(undefined, title, content);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
function handleError(error, task) {
var errorMsg = undefined;
if(error) {
logger.error(error);
// Try to analyze the error
if(typeof error === "string") {
errorMsg = error;
}
else {
errorMsg = "Could not publish on GitHub.";
if(error.error === 401 || error.error === 403) {
github = undefined;
localStorage.removeItem("githubToken");
errorMsg = "Access to GitHub account is not authorized.";
task.retry(new Error(errorMsg), 1);
return;
}
else if(error.error <= 0) {
connected = false;
github = undefined;
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
return githubHelper;
}); });

View File

@ -3,31 +3,33 @@ define([
"settings", "settings",
"github-helper" "github-helper"
], function(utils, settings, githubHelper) { ], function(utils, settings, githubHelper) {
var PROVIDER_GITHUB = "github";
var githubProvider = {
providerId: PROVIDER_GITHUB,
providerName: "GitHub",
publishPreferencesInputIds: ["github-reponame", "github-branch"]
};
githubProvider.publish = function(publishAttributes, title, content, callback) {
var commitMsg = settings.commitMsg;
githubHelper.upload(publishAttributes.repository, publishAttributes.branch,
publishAttributes.path, content, commitMsg, callback);
};
githubProvider.newPublishAttributes = function(event) { var PROVIDER_GITHUB = "github";
var publishAttributes = {};
publishAttributes.repository = utils.getInputTextValue("#input-publish-github-reponame", event);
publishAttributes.branch = utils.getInputTextValue("#input-publish-github-branch", event);
publishAttributes.path = utils.getInputTextValue("#input-publish-file-path", event);
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return githubProvider; var githubProvider = {
providerId: PROVIDER_GITHUB,
providerName: "GitHub",
publishPreferencesInputIds: [
"github-reponame",
"github-branch"
]
};
githubProvider.publish = function(publishAttributes, title, content, callback) {
var commitMsg = settings.commitMsg;
githubHelper.upload(publishAttributes.repository, publishAttributes.branch, publishAttributes.path, content, commitMsg, callback);
};
githubProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
publishAttributes.repository = utils.getInputTextValue("#input-publish-github-reponame", event);
publishAttributes.branch = utils.getInputTextValue("#input-publish-github-branch", event);
publishAttributes.path = utils.getInputTextValue("#input-publish-file-path", event);
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return githubProvider;
}); });

File diff suppressed because it is too large Load Diff

View File

@ -18,236 +18,229 @@ define([
"wordpress-provider" "wordpress-provider"
], function($, _, core, utils, settings, extensionMgr, fileSystem, fileMgr, sharing) { ], function($, _, core, utils, settings, extensionMgr, fileSystem, fileMgr, sharing) {
var publisher = {}; var publisher = {};
// Create a map with providerId: providerModule
var providerMap = _.chain(
arguments
).map(function(argument) {
return argument && argument.providerId && [argument.providerId, argument];
}).compact().object().value();
// Retrieve publish locations from localStorage
_.each(fileSystem, function(fileDesc) {
_.chain(
localStorage[fileDesc.fileIndex + ".publish"].split(";")
).compact().each(function(publishIndex) {
var publishAttributes = JSON.parse(localStorage[publishIndex]);
// Store publishIndex
publishAttributes.publishIndex = publishIndex;
// Replace provider ID by provider module in attributes
publishAttributes.provider = providerMap[publishAttributes.provider];
fileDesc.publishLocations[publishIndex] = publishAttributes;
});
});
// Apply template to the current document // Create a map with providerId: providerModule
publisher.applyTemplate = function(publishAttributes) { var providerMap = _.chain(arguments).map(function(argument) {
var fileDesc = fileMgr.getCurrentFile(); return argument && argument.providerId && [
try { argument.providerId,
return _.template(settings.template, { argument
documentTitle: fileDesc.title, ];
documentMarkdown: $("#wmd-input").val(), }).compact().object().value();
documentHTML: $("#wmd-preview").html(),
publishAttributes: publishAttributes // Retrieve publish locations from localStorage
}); _.each(fileSystem, function(fileDesc) {
} catch(e) { _.chain(localStorage[fileDesc.fileIndex + ".publish"].split(";")).compact().each(function(publishIndex) {
extensionMgr.onError(e); var publishAttributes = JSON.parse(localStorage[publishIndex]);
throw e; // Store publishIndex
} publishAttributes.publishIndex = publishIndex;
}; // Replace provider ID by provider module in attributes
publishAttributes.provider = providerMap[publishAttributes.provider];
// Used to get content to publish fileDesc.publishLocations[publishIndex] = publishAttributes;
function getPublishContent(publishAttributes) { });
if(publishAttributes.format === undefined) { });
publishAttributes.format = $("input:radio[name=radio-publish-format]:checked").prop("value");
} // Apply template to the current document
if(publishAttributes.format == "markdown") { publisher.applyTemplate = function(publishAttributes) {
return $("#wmd-input").val(); var fileDesc = fileMgr.getCurrentFile();
} try {
else if(publishAttributes.format == "html") { return _.template(settings.template, {
return $("#wmd-preview").html(); documentTitle: fileDesc.title,
} documentMarkdown: $("#wmd-input").val(),
else { documentHTML: $("#wmd-preview").html(),
return publisher.applyTemplate(publishAttributes); publishAttributes: publishAttributes
} });
} }
catch (e) {
// Recursive function to publish a file on multiple locations extensionMgr.onError(e);
var publishAttributesList = []; throw e;
var publishTitle = undefined; }
function publishLocation(callback, errorFlag) { };
// No more publish location for this document // Used to get content to publish
if (publishAttributesList.length === 0) { function getPublishContent(publishAttributes) {
callback(errorFlag); if(publishAttributes.format === undefined) {
return; publishAttributes.format = $("input:radio[name=radio-publish-format]:checked").prop("value");
} }
if(publishAttributes.format == "markdown") {
// Dequeue a synchronized location return $("#wmd-input").val();
var publishAttributes = publishAttributesList.pop(); }
var content = getPublishContent(publishAttributes); else if(publishAttributes.format == "html") {
return $("#wmd-preview").html();
// Call the provider }
publishAttributes.provider.publish(publishAttributes, publishTitle, content, function(error) { else {
if(error !== undefined) { return publisher.applyTemplate(publishAttributes);
var errorMsg = error.toString(); }
if(errorMsg.indexOf("|removePublish") !== -1) { }
fileMgr.removePublish(publishAttributes);
} // Recursive function to publish a file on multiple locations
if(errorMsg.indexOf("|stopPublish") !== -1) { var publishAttributesList = [];
callback(error); var publishTitle = undefined;
return; function publishLocation(callback, errorFlag) {
}
} // No more publish location for this document
publishLocation(callback, errorFlag || error ); if(publishAttributesList.length === 0) {
}); callback(errorFlag);
} return;
}
var publishRunning = false;
publisher.publish = function() { // Dequeue a synchronized location
// If publish is running or offline var publishAttributes = publishAttributesList.pop();
if(publishRunning === true || core.isOffline) { var content = getPublishContent(publishAttributes);
return;
} // Call the provider
publishAttributes.provider.publish(publishAttributes, publishTitle, content, function(error) {
publishRunning = true; if(error !== undefined) {
extensionMgr.onPublishRunning(true); var errorMsg = error.toString();
var fileDesc = fileMgr.getCurrentFile(); if(errorMsg.indexOf("|removePublish") !== -1) {
publishTitle = fileDesc.title; fileMgr.removePublish(publishAttributes);
publishAttributesList = _.values(fileDesc.publishLocations); }
publishLocation(function(errorFlag) { if(errorMsg.indexOf("|stopPublish") !== -1) {
publishRunning = false; callback(error);
extensionMgr.onPublishRunning(false); return;
if(errorFlag === undefined) { }
extensionMgr.onPublishSuccess(fileDesc); }
} publishLocation(callback, errorFlag || error);
}); });
}; }
// Generate a publishIndex associated to a file and store publishAttributes var publishRunning = false;
function createPublishIndex(fileDesc, publishAttributes) { publisher.publish = function() {
var publishIndex = undefined; // If publish is running or offline
do { if(publishRunning === true || core.isOffline) {
publishIndex = "publish." + utils.randomString(); return;
} while(_.has(localStorage, publishIndex)); }
publishAttributes.publishIndex = publishIndex;
utils.storeAttributes(publishAttributes); publishRunning = true;
fileMgr.addPublish(fileDesc, publishAttributes); extensionMgr.onPublishRunning(true);
} var fileDesc = fileMgr.getCurrentFile();
publishTitle = fileDesc.title;
// Initialize the "New publication" dialog publishAttributesList = _.values(fileDesc.publishLocations);
var newLocationProvider = undefined; publishLocation(function(errorFlag) {
function initNewLocation(provider) { publishRunning = false;
var defaultPublishFormat = provider.defaultPublishFormat || "markdown"; extensionMgr.onPublishRunning(false);
newLocationProvider = provider; if(errorFlag === undefined) {
$(".publish-provider-name").text(provider.providerName); extensionMgr.onPublishSuccess(fileDesc);
}
// Show/hide controls depending on provider });
$('div[class*=" modal-publish-"]').hide().filter(".modal-publish-" + provider.providerId).show(); };
// Reset fields // Generate a publishIndex associated to a file and store publishAttributes
utils.resetModalInputs(); function createPublishIndex(fileDesc, publishAttributes) {
$("input:radio[name=radio-publish-format][value=" + defaultPublishFormat + "]").prop("checked", true); var publishIndex = undefined;
do {
// Load preferences publishIndex = "publish." + utils.randomString();
var serializedPreferences = localStorage[provider.providerId + ".publishPreferences"]; } while (_.has(localStorage, publishIndex));
if(serializedPreferences) { publishAttributes.publishIndex = publishIndex;
var publishPreferences = JSON.parse(serializedPreferences); utils.storeAttributes(publishAttributes);
_.each(provider.publishPreferencesInputIds, function(inputId) { fileMgr.addPublish(fileDesc, publishAttributes);
utils.setInputValue("#input-publish-" + inputId, publishPreferences[inputId]); }
});
utils.setInputRadio("radio-publish-format", publishPreferences.format); // Initialize the "New publication" dialog
} var newLocationProvider = undefined;
function initNewLocation(provider) {
// Open dialog box var defaultPublishFormat = provider.defaultPublishFormat || "markdown";
$("#modal-publish").modal(); newLocationProvider = provider;
} $(".publish-provider-name").text(provider.providerName);
// Add a new publish location to a local document // Show/hide controls depending on provider
function performNewLocation(event) { $('div[class*=" modal-publish-"]').hide().filter(".modal-publish-" + provider.providerId).show();
var provider = newLocationProvider;
var publishAttributes = provider.newPublishAttributes(event); // Reset fields
if(publishAttributes === undefined) { utils.resetModalInputs();
return; $("input:radio[name=radio-publish-format][value=" + defaultPublishFormat + "]").prop("checked", true);
}
// Load preferences
// Perform provider's publishing var serializedPreferences = localStorage[provider.providerId + ".publishPreferences"];
var fileDesc = fileMgr.getCurrentFile(); if(serializedPreferences) {
var title = fileDesc.title; var publishPreferences = JSON.parse(serializedPreferences);
var content = getPublishContent(publishAttributes); _.each(provider.publishPreferencesInputIds, function(inputId) {
provider.publish(publishAttributes, title, content, function(error) { utils.setInputValue("#input-publish-" + inputId, publishPreferences[inputId]);
if(error === undefined) { });
publishAttributes.provider = provider.providerId; utils.setInputRadio("radio-publish-format", publishPreferences.format);
sharing.createLink(publishAttributes, function() { }
createPublishIndex(fileDesc, publishAttributes);
}); // Open dialog box
} $("#modal-publish").modal();
}); }
// Store input values as preferences for next time we open the publish dialog // Add a new publish location to a local document
var publishPreferences = {}; function performNewLocation(event) {
_.each(provider.publishPreferencesInputIds, function(inputId) { var provider = newLocationProvider;
publishPreferences[inputId] = $("#input-publish-" + inputId).val(); var publishAttributes = provider.newPublishAttributes(event);
}); if(publishAttributes === undefined) {
publishPreferences.format = publishAttributes.format; return;
localStorage[provider.providerId + ".publishPreferences"] = JSON.stringify(publishPreferences); }
}
// Perform provider's publishing
// Retrieve file's publish locations from localStorage var fileDesc = fileMgr.getCurrentFile();
publisher.populatePublishLocations = function(fileDesc) { var title = fileDesc.title;
_.chain( var content = getPublishContent(publishAttributes);
localStorage[fileDesc.fileIndex + ".publish"].split(";") provider.publish(publishAttributes, title, content, function(error) {
).compact().each(function(publishIndex) { if(error === undefined) {
var publishAttributes = JSON.parse(localStorage[publishIndex]); publishAttributes.provider = provider.providerId;
// Store publishIndex sharing.createLink(publishAttributes, function() {
publishAttributes.publishIndex = publishIndex; createPublishIndex(fileDesc, publishAttributes);
// Replace provider ID by provider module in attributes });
publishAttributes.provider = providerMap[publishAttributes.provider]; }
fileDesc.publishLocations[publishIndex] = publishAttributes; });
});
}; // Store input values as preferences for next time we open the publish
// dialog
core.onReady(function() { var publishPreferences = {};
// Add every provider _.each(provider.publishPreferencesInputIds, function(inputId) {
var publishMenu = $("#publish-menu"); publishPreferences[inputId] = $("#input-publish-" + inputId).val();
_.each(providerMap, function(provider) { });
// Provider's publish button publishPreferences.format = publishAttributes.format;
publishMenu.append( localStorage[provider.providerId + ".publishPreferences"] = JSON.stringify(publishPreferences);
$("<li>").append( }
$('<a href="#"><i class="icon-' + provider.providerId + '"></i> ' + provider.providerName + '</a>')
.click(function() { // Retrieve file's publish locations from localStorage
initNewLocation(provider); publisher.populatePublishLocations = function(fileDesc) {
} _.chain(localStorage[fileDesc.fileIndex + ".publish"].split(";")).compact().each(function(publishIndex) {
) var publishAttributes = JSON.parse(localStorage[publishIndex]);
) // Store publishIndex
); publishAttributes.publishIndex = publishIndex;
// Action links (if any) // Replace provider ID by provider module in attributes
$(".action-publish-" + provider.providerId).click(function() { publishAttributes.provider = providerMap[publishAttributes.provider];
initNewLocation(provider); fileDesc.publishLocations[publishIndex] = publishAttributes;
}); });
}); };
$(".action-process-publish").click(performNewLocation); core.onReady(function() {
// Add every provider
// Save As menu items var publishMenu = $("#publish-menu");
$(".action-download-md").click(function() { _.each(providerMap, function(provider) {
var content = $("#wmd-input").val(); // Provider's publish button
var title = fileMgr.getCurrentFile().title; publishMenu.append($("<li>").append($('<a href="#"><i class="icon-' + provider.providerId + '"></i> ' + provider.providerName + '</a>').click(function() {
utils.saveAs(content, title + ".md"); initNewLocation(provider);
}); })));
$(".action-download-html").click(function() { // Action links (if any)
var content = $("#wmd-preview").html(); $(".action-publish-" + provider.providerId).click(function() {
var title = fileMgr.getCurrentFile().title; initNewLocation(provider);
utils.saveAs(content, title + ".html"); });
}); });
$(".action-download-template").click(function() {
var content = publisher.applyTemplate(); $(".action-process-publish").click(performNewLocation);
var title = fileMgr.getCurrentFile().title;
utils.saveAs(content, title + ".txt"); // Save As menu items
}); $(".action-download-md").click(function() {
}); var content = $("#wmd-input").val();
var title = fileMgr.getCurrentFile().title;
extensionMgr.onPublisherCreated(publisher); utils.saveAs(content, title + ".md");
return publisher; });
$(".action-download-html").click(function() {
var content = $("#wmd-preview").html();
var title = fileMgr.getCurrentFile().title;
utils.saveAs(content, title + ".html");
});
$(".action-download-template").click(function() {
var content = publisher.applyTemplate();
var title = fileMgr.getCurrentFile().title;
utils.saveAs(content, title + ".txt");
});
});
extensionMgr.onPublisherCreated(publisher);
return publisher;
}); });

View File

@ -2,28 +2,29 @@ define([
"underscore", "underscore",
"config" "config"
], function(_) { ], function(_) {
var settings = { var settings = {
layoutOrientation : "horizontal", layoutOrientation: "horizontal",
lazyRendering : true, lazyRendering: true,
editorFontSize : 14, editorFontSize: 14,
defaultContent: "\n\n\n> Written with [StackEdit](http://benweet.github.io/stackedit/).", defaultContent: "\n\n\n> Written with [StackEdit](http://benweet.github.io/stackedit/).",
commitMsg : "Published by http://benweet.github.io/stackedit", commitMsg: "Published by http://benweet.github.io/stackedit",
template : [ template: [
'<!DOCTYPE html>\n', '<!DOCTYPE html>\n',
'<html>\n', '<html>\n',
'<head>\n', '<head>\n',
'<title><%= documentTitle %></title>\n', '<title><%= documentTitle %></title>\n',
'</head>\n', '</head>\n',
'<body><%= documentHTML %></body>\n', '<body><%= documentHTML %></body>\n',
'</html>'].join(""), '</html>'
sshProxy : SSH_PROXY_URL, ].join(""),
extensionSettings: {} sshProxy: SSH_PROXY_URL,
}; extensionSettings: {}
};
if (_.has(localStorage, "settings")) {
_.extend(settings, JSON.parse(localStorage.settings)); if(_.has(localStorage, "settings")) {
} _.extend(settings, JSON.parse(localStorage.settings));
}
return settings;
return settings;
}); });

View File

@ -10,125 +10,128 @@ define([
"download-provider", "download-provider",
"gist-provider" "gist-provider"
], function($, _, core, utils, extensionMgr, fileMgr, asyncRunner) { ], function($, _, core, utils, extensionMgr, fileMgr, asyncRunner) {
var sharing = {};
// Create a map with providerId: providerModule
var providerMap = _.chain(
arguments
).map(function(argument) {
return argument && argument.providerId && [argument.providerId, argument];
}).compact().object().value();
// Used to populate the "Sharing" dropdown box var sharing = {};
var lineTemplate = ['<div class="input-prepend">',
'<a href="<%= link %>" class="add-on" title="Sharing location"><i class="icon-link"></i></a>', // Create a map with providerId: providerModule
'<input class="span2" type="text" value="<%= link %>" readonly />', var providerMap = _.chain(arguments).map(function(argument) {
'</div>'].join(""); return argument && argument.providerId && [
sharing.refreshDocumentSharing = function(attributesList) { argument.providerId,
var linkList = $("#link-container .link-list").empty(); argument
$("#link-container .no-link").show(); ];
_.each(attributesList, function(attributes) { }).compact().object().value();
if(attributes.sharingLink) {
var lineElement = $(_.template(lineTemplate, { // Used to populate the "Sharing" dropdown box
link: attributes.sharingLink var lineTemplate = [
})); '<div class="input-prepend">',
lineElement.click(function(event) { ' <a href="<%= link %>" class="add-on" title="Sharing location"><i class="icon-link"></i></a>',
event.stopPropagation(); ' <input class="span2" type="text" value="<%= link %>" readonly />',
}); '</div>'
linkList.append(lineElement); ].join("");
$("#link-container .no-link").hide(); sharing.refreshDocumentSharing = function(attributesList) {
} var linkList = $("#link-container .link-list").empty();
}); $("#link-container .no-link").show();
}; _.each(attributesList, function(attributes) {
if(attributes.sharingLink) {
sharing.createLink = function(attributes, callback) { var lineElement = $(_.template(lineTemplate, {
var provider = providerMap[attributes.provider]; link: attributes.sharingLink
// Don't create link if link already exists or provider is not compatible for sharing }));
if(attributes.sharingLink !== undefined || provider === undefined lineElement.click(function(event) {
// Or document is not published in markdown format event.stopPropagation();
|| attributes.format != "markdown") { });
callback(); linkList.append(lineElement);
return; $("#link-container .no-link").hide();
} }
var task = asyncRunner.createTask(); });
var shortUrl = undefined; };
task.onRun(function() {
if(core.isOffline === true) { sharing.createLink = function(attributes, callback) {
task.chain(); var provider = providerMap[attributes.provider];
return; // Don't create link if link already exists or provider is not
} // compatible for sharing
var url = [MAIN_URL, 'viewer.html?provider=', attributes.provider]; if(attributes.sharingLink !== undefined || provider === undefined
_.each(provider.sharingAttributes, function(attributeName) { // Or document is not published in markdown format
url.push('&'); || attributes.format != "markdown") {
url.push(attributeName); callback();
url.push('='); return;
url.push(encodeURIComponent(attributes[attributeName])); }
}); var task = asyncRunner.createTask();
url = url.join(""); var shortUrl = undefined;
$.getJSON( task.onRun(function() {
"https://api-ssl.bitly.com/v3/shorten", if(core.isOffline === true) {
{ task.chain();
"access_token": BITLY_ACCESS_TOKEN, return;
"longUrl": url }
}, var url = [
function(response) MAIN_URL,
{ 'viewer.html?provider=',
if(response.data) { attributes.provider
shortUrl = response.data.url; ];
attributes.sharingLink = shortUrl; _.each(provider.sharingAttributes, function(attributeName) {
} url.push('&');
else { url.push(attributeName);
extensionMgr.onError("An error occured while creating sharing link."); url.push('=');
attributes.sharingLink = url; url.push(encodeURIComponent(attributes[attributeName]));
} });
task.chain(); url = url.join("");
} $.getJSON("https://api-ssl.bitly.com/v3/shorten", {
); "access_token": BITLY_ACCESS_TOKEN,
}); "longUrl": url
function onFinish() { }, function(response) {
callback(); if(response.data) {
} shortUrl = response.data.url;
task.onSuccess(onFinish); attributes.sharingLink = shortUrl;
task.onError(onFinish); }
asyncRunner.addTask(task); else {
}; extensionMgr.onError("An error occured while creating sharing link.");
attributes.sharingLink = url;
core.onReady(function() { }
if(viewerMode === false) { task.chain();
return; });
} });
// Check parameters to see if we have to download a shared document function onFinish() {
var providerId = utils.getURLParameter("provider"); callback();
if(providerId === undefined) { }
providerId = "download"; task.onSuccess(onFinish);
} task.onError(onFinish);
var provider = providerMap[providerId]; asyncRunner.addTask(task);
if(provider === undefined) { };
return;
} core.onReady(function() {
var importParameters = {}; if(viewerMode === false) {
_.each(provider.sharingAttributes, function(attributeName) { return;
var parameter = utils.getURLParameter(attributeName); }
if(!parameter) { // Check parameters to see if we have to download a shared document
importParameters = undefined; var providerId = utils.getURLParameter("provider");
return; if(providerId === undefined) {
} providerId = "download";
importParameters[attributeName] = parameter; }
}); var provider = providerMap[providerId];
if(importParameters === undefined) { if(provider === undefined) {
return; return;
} }
$("#wmd-preview, #file-title").hide(); var importParameters = {};
provider.importPublic(importParameters, function(error, title, content) { _.each(provider.sharingAttributes, function(attributeName) {
$("#wmd-preview, #file-title").show(); var parameter = utils.getURLParameter(attributeName);
if(error) { if(!parameter) {
return; importParameters = undefined;
} return;
var fileDesc = fileMgr.createFile(title, content, undefined, true); }
fileMgr.selectFile(fileDesc); importParameters[attributeName] = parameter;
}); });
}); if(importParameters === undefined) {
return;
return sharing; }
$("#wmd-preview, #file-title").hide();
provider.importPublic(importParameters, function(error, title, content) {
$("#wmd-preview, #file-title").show();
if(error) {
return;
}
var fileDesc = fileMgr.createFile(title, content, undefined, true);
fileMgr.selectFile(fileDesc);
});
});
return sharing;
}); });

View File

@ -4,80 +4,80 @@ define([
"async-runner" "async-runner"
], function($, core, asyncRunner) { ], function($, core, asyncRunner) {
var sshHelper = {}; var sshHelper = {};
// Only used to check the offline status // Only used to check the offline status
function connect(task) { function connect(task) {
task.onRun(function() { task.onRun(function() {
if(core.isOffline === true) { if(core.isOffline === true) {
task.error(new Error("Operation not available in offline mode.|stopPublish")); task.error(new Error("Operation not available in offline mode.|stopPublish"));
return; return;
} }
task.chain(); task.chain();
}); });
} }
sshHelper.upload = function(host, port, username, password, path, title, content, callback) { sshHelper.upload = function(host, port, username, password, path, title, content, callback) {
var task = asyncRunner.createTask(); var task = asyncRunner.createTask();
connect(task); connect(task);
task.onRun(function() { task.onRun(function() {
var url = SSH_PROXY_URL + "upload"; var url = SSH_PROXY_URL + "upload";
var data = { var data = {
host: host, host: host,
port: port, port: port,
username: username, username: username,
password: password, password: password,
path: path, path: path,
title: title, title: title,
content: content content: content
}; };
$.ajax({ $.ajax({
url : url, url: url,
data: data, data: data,
type: "POST", type: "POST",
dataType : "json", dataType: "json",
timeout : AJAX_TIMEOUT timeout: AJAX_TIMEOUT
}).done(function(response, textStatus, jqXHR) { }).done(function(response, textStatus, jqXHR) {
if(response.error === undefined) { if(response.error === undefined) {
task.chain(); task.chain();
return; return;
} }
handleError(response.error, task); handleError(response.error, task);
}).fail(function(jqXHR) { }).fail(function(jqXHR) {
var error = { var error = {
code: jqXHR.status, code: jqXHR.status,
message: jqXHR.statusText message: jqXHR.statusText
}; };
handleError(error, task); handleError(error, task);
}); });
}); });
task.onSuccess(function() { task.onSuccess(function() {
callback(); callback();
}); });
task.onError(function(error) { task.onError(function(error) {
callback(error); callback(error);
}); });
asyncRunner.addTask(task); asyncRunner.addTask(task);
}; };
function handleError(error, task) {
var errorMsg = undefined;
if (error) {
logger.error(error);
// Try to analyze the error
if (typeof error === "string") {
errorMsg = "SSH error: " + error + ".";
}
else {
errorMsg = "Could not publish on SSH server.";
if (error.code <= 0) {
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
return sshHelper; function handleError(error, task) {
var errorMsg = undefined;
if(error) {
logger.error(error);
// Try to analyze the error
if(typeof error === "string") {
errorMsg = "SSH error: " + error + ".";
}
else {
errorMsg = "Could not publish on SSH server.";
if(error.code <= 0) {
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
return sshHelper;
}); });

View File

@ -3,46 +3,35 @@ define([
"ssh-helper" "ssh-helper"
], function(utils, sshHelper) { ], function(utils, sshHelper) {
var PROVIDER_SSH = "ssh"; var PROVIDER_SSH = "ssh";
var sshProvider = { var sshProvider = {
providerId : PROVIDER_SSH, providerId: PROVIDER_SSH,
providerName : "SSH server", providerName: "SSH server",
publishPreferencesInputIds: ["ssh-host", "ssh-port", "ssh-username", "ssh-password"] publishPreferencesInputIds: [
}; "ssh-host",
"ssh-port",
"ssh-username",
"ssh-password"
]
};
sshProvider.publish = function(publishAttributes, title, content, callback) { sshProvider.publish = function(publishAttributes, title, content, callback) {
sshHelper.upload( sshHelper.upload(publishAttributes.host, publishAttributes.port, publishAttributes.username, publishAttributes.password, publishAttributes.path, title, content, callback);
publishAttributes.host, };
publishAttributes.port,
publishAttributes.username,
publishAttributes.password,
publishAttributes.path,
title,
content,
callback);
};
sshProvider.newPublishAttributes = function(event) { sshProvider.newPublishAttributes = function(event) {
var publishAttributes = {}; var publishAttributes = {};
publishAttributes.host = utils publishAttributes.host = utils.getInputTextValue("#input-publish-ssh-host", event, /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/);
.getInputTextValue( publishAttributes.port = utils.getInputIntValue("#input-publish-ssh-port", undefined, 0);
"#input-publish-ssh-host", publishAttributes.username = utils.getInputTextValue("#input-publish-ssh-username", event);
event, publishAttributes.password = utils.getInputTextValue("#input-publish-ssh-password", event);
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/); publishAttributes.path = utils.getInputTextValue("#input-publish-file-path", event);
publishAttributes.port = utils.getInputIntValue( if(event.isPropagationStopped()) {
"#input-publish-ssh-port", undefined, 0); return undefined;
publishAttributes.username = utils.getInputTextValue( }
"#input-publish-ssh-username", event); return publishAttributes;
publishAttributes.password = utils.getInputTextValue( };
"#input-publish-ssh-password", event);
publishAttributes.path = utils.getInputTextValue(
"#input-publish-file-path", event);
if (event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return sshProvider; return sshProvider;
}); });

View File

@ -1,131 +1,128 @@
// Setup an empty localStorage or upgrade an existing one // Setup an empty localStorage or upgrade an existing one
define([ define([
"underscore" "underscore"
], function(_) { ], function(_) {
// Create the file system if not exist // Create the file system if not exist
if (localStorage["file.list"] === undefined) { if(localStorage["file.list"] === undefined) {
localStorage["file.list"] = ";"; localStorage["file.list"] = ";";
} }
var fileIndexList = _.compact(localStorage["file.list"].split(";")); var fileIndexList = _.compact(localStorage["file.list"].split(";"));
// localStorage versioning // localStorage versioning
var version = localStorage["version"]; var version = localStorage["version"];
// Upgrade from v0 to v1 // Upgrade from v0 to v1
if(version === undefined) { if(version === undefined) {
// Not used anymore // Not used anymore
localStorage.removeItem("sync.queue"); localStorage.removeItem("sync.queue");
localStorage.removeItem("sync.current"); localStorage.removeItem("sync.current");
localStorage.removeItem("file.counter"); localStorage.removeItem("file.counter");
_.each(fileIndexList, function(fileIndex) { _.each(fileIndexList, function(fileIndex) {
localStorage[fileIndex + ".publish"] = ";"; localStorage[fileIndex + ".publish"] = ";";
var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";"));
_.each(syncIndexList, function(syncIndex) { _.each(syncIndexList, function(syncIndex) {
localStorage[syncIndex + ".contentCRC"] = "0"; localStorage[syncIndex + ".contentCRC"] = "0";
// We store title CRC only for Google Drive synchronization // We store title CRC only for Google Drive synchronization
if(localStorage[syncIndex + ".etag"] !== undefined) { if(localStorage[syncIndex + ".etag"] !== undefined) {
localStorage[syncIndex + ".titleCRC"] = "0"; localStorage[syncIndex + ".titleCRC"] = "0";
} }
}); });
}); });
version = "v1"; version = "v1";
} }
// Upgrade from v1 to v2 // Upgrade from v1 to v2
if(version == "v1") { if(version == "v1") {
var gdriveLastChangeId = localStorage["sync.gdrive.lastChangeId"]; var gdriveLastChangeId = localStorage["sync.gdrive.lastChangeId"];
if(gdriveLastChangeId) { if(gdriveLastChangeId) {
localStorage["gdrive.lastChangeId"] = gdriveLastChangeId; localStorage["gdrive.lastChangeId"] = gdriveLastChangeId;
localStorage.removeItem("sync.gdrive.lastChangeId"); localStorage.removeItem("sync.gdrive.lastChangeId");
} }
var dropboxLastChangeId = localStorage["sync.dropbox.lastChangeId"]; var dropboxLastChangeId = localStorage["sync.dropbox.lastChangeId"];
if(dropboxLastChangeId) { if(dropboxLastChangeId) {
localStorage["dropbox.lastChangeId"] = dropboxLastChangeId; localStorage["dropbox.lastChangeId"] = dropboxLastChangeId;
localStorage.removeItem("sync.dropbox.lastChangeId"); localStorage.removeItem("sync.dropbox.lastChangeId");
} }
var PROVIDER_GDRIVE = "gdrive"; var PROVIDER_GDRIVE = "gdrive";
var PROVIDER_DROPBOX = "dropbox"; var PROVIDER_DROPBOX = "dropbox";
var SYNC_PROVIDER_GDRIVE = "sync." + PROVIDER_GDRIVE + "."; var SYNC_PROVIDER_GDRIVE = "sync." + PROVIDER_GDRIVE + ".";
var SYNC_PROVIDER_DROPBOX = "sync." + PROVIDER_DROPBOX + "."; var SYNC_PROVIDER_DROPBOX = "sync." + PROVIDER_DROPBOX + ".";
_.each(fileIndexList, function(fileIndex) { _.each(fileIndexList, function(fileIndex) {
var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";")); var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";"));
_.each(syncIndexList, function(syncIndex) { _.each(syncIndexList, function(syncIndex) {
var syncAttributes = {}; var syncAttributes = {};
if (syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { if(syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
syncAttributes.provider = PROVIDER_GDRIVE; syncAttributes.provider = PROVIDER_GDRIVE;
syncAttributes.id = syncIndex.substring(SYNC_PROVIDER_GDRIVE.length); syncAttributes.id = syncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
syncAttributes.etag = localStorage[syncIndex + ".etag"]; syncAttributes.etag = localStorage[syncIndex + ".etag"];
syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"]; syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"];
syncAttributes.titleCRC = localStorage[syncIndex + ".titleCRC"]; syncAttributes.titleCRC = localStorage[syncIndex + ".titleCRC"];
} }
else if (syncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) { else if(syncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) {
syncAttributes.provider = PROVIDER_DROPBOX; syncAttributes.provider = PROVIDER_DROPBOX;
syncAttributes.path = decodeURIComponent(syncIndex.substring(SYNC_PROVIDER_DROPBOX.length)); syncAttributes.path = decodeURIComponent(syncIndex.substring(SYNC_PROVIDER_DROPBOX.length));
syncAttributes.version = localStorage[syncIndex + ".version"]; syncAttributes.version = localStorage[syncIndex + ".version"];
syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"]; syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"];
} }
localStorage[syncIndex] = JSON.stringify(syncAttributes); localStorage[syncIndex] = JSON.stringify(syncAttributes);
localStorage.removeItem(syncIndex + ".etag"); localStorage.removeItem(syncIndex + ".etag");
localStorage.removeItem(syncIndex + ".version"); localStorage.removeItem(syncIndex + ".version");
localStorage.removeItem(syncIndex + ".contentCRC"); localStorage.removeItem(syncIndex + ".contentCRC");
localStorage.removeItem(syncIndex + ".titleCRC"); localStorage.removeItem(syncIndex + ".titleCRC");
}); });
}); });
version = "v2"; version = "v2";
} }
// Upgrade from v2 to v3 // Upgrade from v2 to v3
if(version == "v2") { if(version == "v2") {
_.each(fileIndexList, function(fileIndex) { _.each(fileIndexList, function(fileIndex) {
if(!_.has(localStorage, fileIndex + ".sync")) { if(!_.has(localStorage, fileIndex + ".sync")) {
localStorage.removeItem(fileIndex + ".title"); localStorage.removeItem(fileIndex + ".title");
localStorage.removeItem(fileIndex + ".publish"); localStorage.removeItem(fileIndex + ".publish");
localStorage.removeItem(fileIndex + ".content"); localStorage.removeItem(fileIndex + ".content");
localStorage["file.list"] = localStorage["file.list"].replace(";" localStorage["file.list"] = localStorage["file.list"].replace(";" + fileIndex + ";", ";");
+ fileIndex + ";", ";"); }
} });
}); version = "v3";
version = "v3"; }
}
// Upgrade from v3 to v4
// Upgrade from v3 to v4 if(version == "v3") {
if(version == "v3") { var currentFileIndex = localStorage["file.current"];
var currentFileIndex = localStorage["file.current"]; if(currentFileIndex !== undefined && localStorage["file.list"].indexOf(";" + currentFileIndex + ";") === -1) {
if(currentFileIndex !== undefined && localStorage.removeItem("file.current");
localStorage["file.list"].indexOf(";" + currentFileIndex + ";") === -1) }
{ version = "v4";
localStorage.removeItem("file.current"); }
}
version = "v4"; // Upgrade from v4 to v5
} if(version == "v4") {
// Recreate GitHub token
// Upgrade from v4 to v5 localStorage.removeItem("githubToken");
if(version == "v4") { version = "v5";
// Recreate GitHub token }
localStorage.removeItem("githubToken");
version = "v5"; // Upgrade from v5 to v6
} if(version == "v5") {
_.each(fileIndexList, function(fileIndex) {
// Upgrade from v5 to v6 var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";"));
if(version == "v5") { _.each(publishIndexList, function(publishIndex) {
_.each(fileIndexList, function(fileIndex) { var publishAttributes = JSON.parse(localStorage[publishIndex]);
var publishIndexList = _.compact(localStorage[fileIndex + ".publish"].split(";")); if(publishAttributes.provider == "gdrive") {
_.each(publishIndexList, function(publishIndex) { // Change fileId to Id to be consistent with syncAttributes
var publishAttributes = JSON.parse(localStorage[publishIndex]); publishAttributes.id = publishAttributes.fileId;
if(publishAttributes.provider == "gdrive") { publishAttributes.fileId = undefined;
// Change fileId to Id to be consistent with syncAttributes localStorage[publishIndex] = JSON.stringify(publishAttributes);
publishAttributes.id = publishAttributes.fileId; }
publishAttributes.fileId = undefined; });
localStorage[publishIndex] = JSON.stringify(publishAttributes); });
} version = "v6";
}); }
});
version = "v6"; localStorage["version"] = version;
}
localStorage["version"] = version;
}); });

View File

@ -9,250 +9,244 @@ define([
"dropbox-provider", "dropbox-provider",
"gdrive-provider" "gdrive-provider"
], function($, _, core, utils, extensionMgr, fileSystem, fileMgr) { ], function($, _, core, utils, extensionMgr, fileSystem, fileMgr) {
var synchronizer = {};
// Create a map with providerId: providerModule
var providerMap = _.chain(
arguments
).map(function(argument) {
return argument && argument.providerId && [argument.providerId, argument];
}).compact().object().value();
// Retrieve sync locations from localStorage
_.each(fileSystem, function(fileDesc) {
_.chain(
localStorage[fileDesc.fileIndex + ".sync"].split(";")
).compact().each(function(syncIndex) {
var syncAttributes = JSON.parse(localStorage[syncIndex]);
// Store syncIndex
syncAttributes.syncIndex = syncIndex;
// Replace provider ID by provider module in attributes
syncAttributes.provider = providerMap[syncAttributes.provider];
fileDesc.syncLocations[syncIndex] = syncAttributes;
});
});
// Force the synchronization var synchronizer = {};
synchronizer.forceSync = function() {
lastSync = 0;
synchronizer.sync();
};
// Recursive function to upload a single file on multiple locations
var uploadSyncAttributesList = [];
var uploadContent = undefined;
var uploadContentCRC = undefined;
var uploadTitle = undefined;
var uploadTitleCRC = undefined;
function locationUp(callback) {
// No more synchronized location for this document
if (uploadSyncAttributesList.length === 0) {
fileUp(callback);
return;
}
// Dequeue a synchronized location
var syncAttributes = uploadSyncAttributesList.pop();
// Use the specified provider to perform the upload
syncAttributes.provider.syncUp(
uploadContent,
uploadContentCRC,
uploadTitle,
uploadTitleCRC,
syncAttributes,
function(error, uploadFlag) {
if(uploadFlag === true) {
// If uploadFlag is true, request another upload cycle
uploadCycle = true;
}
if(error) {
callback(error);
return;
}
if(uploadFlag) {
// Update syncAttributes in localStorage
utils.storeAttributes(syncAttributes);
}
locationUp(callback);
}
);
}
// Recursive function to upload multiple files // Create a map with providerId: providerModule
var uploadFileList = []; var providerMap = _.chain(arguments).map(function(argument) {
function fileUp(callback) { return argument && argument.providerId && [
argument.providerId,
// No more fileDesc to synchronize argument
if (uploadFileList.length === 0) { ];
syncUp(callback); }).compact().object().value();
return;
}
// Dequeue a fileDesc to synchronize
var fileDesc = uploadFileList.pop();
uploadSyncAttributesList = _.values(fileDesc.syncLocations);
if(uploadSyncAttributesList.length === 0) {
fileUp(callback);
return;
}
// Get document title/content // Retrieve sync locations from localStorage
uploadContent = fileDesc.getContent(); _.each(fileSystem, function(fileDesc) {
uploadContentCRC = utils.crc32(uploadContent); _.chain(localStorage[fileDesc.fileIndex + ".sync"].split(";")).compact().each(function(syncIndex) {
uploadTitle = fileDesc.title; var syncAttributes = JSON.parse(localStorage[syncIndex]);
uploadTitleCRC = utils.crc32(uploadTitle); // Store syncIndex
locationUp(callback); syncAttributes.syncIndex = syncIndex;
} // Replace provider ID by provider module in attributes
syncAttributes.provider = providerMap[syncAttributes.provider];
fileDesc.syncLocations[syncIndex] = syncAttributes;
});
});
// Entry point for up synchronization (upload changes) // Force the synchronization
var uploadCycle = false; synchronizer.forceSync = function() {
function syncUp(callback) { lastSync = 0;
if(uploadCycle === true) { synchronizer.sync();
// New upload cycle };
uploadCycle = false;
uploadFileList = _.values(fileSystem);
fileUp(callback);
}
else {
callback();
}
}
// Recursive function to download changes from multiple providers // Recursive function to upload a single file on multiple locations
var providerList = []; var uploadSyncAttributesList = [];
function providerDown(callback) { var uploadContent = undefined;
if(providerList.length === 0) { var uploadContentCRC = undefined;
callback(); var uploadTitle = undefined;
return; var uploadTitleCRC = undefined;
} function locationUp(callback) {
var provider = providerList.pop();
// Check that provider has files to sync
if(!fileMgr.hasSync(provider)) {
providerDown(callback);
return;
}
// Perform provider's syncDown
provider.syncDown(function(error) {
if(error) {
callback(error);
return;
}
providerDown(callback);
});
}
// Entry point for down synchronization (download changes)
function syncDown(callback) {
providerList = _.values(providerMap);
providerDown(callback);
};
// Main entry point for synchronization
var syncRunning = false;
var lastSync = 0;
synchronizer.sync = function() {
// If sync is already running or timeout is not reached or offline
if (syncRunning || lastSync + SYNC_PERIOD > utils.currentTime || core.isOffline) {
return;
}
syncRunning = true;
extensionMgr.onSyncRunning(true);
uploadCycle = true;
lastSync = utils.currentTime;
function isError(error) {
if(error !== undefined) {
syncRunning = false;
extensionMgr.onSyncRunning(false);
return true;
}
return false;
}
syncDown(function(error) { // No more synchronized location for this document
if(isError(error)) { if(uploadSyncAttributesList.length === 0) {
return; fileUp(callback);
} return;
syncUp(function(error) { }
if(isError(error)) {
return;
}
syncRunning = false;
extensionMgr.onSyncRunning(false);
extensionMgr.onSyncSuccess();
});
});
};
// Run sync function periodically
if(viewerMode === false) {
core.addPeriodicCallback(synchronizer.sync);
}
// Initialize the export dialog
function initExportDialog(provider) {
// Reset fields
utils.resetModalInputs();
// Load preferences
var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"];
if(serializedPreferences) {
var exportPreferences = JSON.parse(serializedPreferences);
_.each(provider.exportPreferencesInputIds, function(inputId) {
utils.setInputValue("#input-sync-export-" + inputId, exportPreferences[inputId]);
});
}
// Open dialog box
$("#modal-upload-" + provider.providerId).modal();
}
core.onReady(function() {
// Init each provider
_.each(providerMap, function(provider) {
// Provider's import button
$(".action-sync-import-" + provider.providerId).click(function(event) {
provider.importFiles(event);
});
// Provider's export action
$(".action-sync-export-dialog-" + provider.providerId).click(function() {
initExportDialog(provider);
});
$(".action-sync-export-" + provider.providerId).click(function(event) {
// Perform the provider's export // Dequeue a synchronized location
var fileDesc = fileMgr.getCurrentFile(); var syncAttributes = uploadSyncAttributesList.pop();
provider.exportFile(event, fileDesc.title, fileDesc.getContent(), function(error, syncAttributes) { // Use the specified provider to perform the upload
if(error) { syncAttributes.provider.syncUp(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, function(error, uploadFlag) {
return; if(uploadFlag === true) {
} // If uploadFlag is true, request another upload cycle
fileMgr.addSync(fileDesc, syncAttributes); uploadCycle = true;
}); }
if(error) {
// Store input values as preferences for next time we open the export dialog callback(error);
var exportPreferences = {}; return;
_.each(provider.exportPreferencesInputIds, function(inputId) { }
exportPreferences[inputId] = $("#input-sync-export-" + inputId).val(); if(uploadFlag) {
}); // Update syncAttributes in localStorage
localStorage[provider.providerId + ".exportPreferences"] = JSON.stringify(exportPreferences); utils.storeAttributes(syncAttributes);
}); }
// Provider's manual export button locationUp(callback);
$(".action-sync-manual-" + provider.providerId).click(function(event) { });
var fileDesc = fileMgr.getCurrentFile(); }
provider.exportManual(event, fileDesc.title, fileDesc.getContent(), function(error, syncAttributes) {
if(error) {
return;
}
fileMgr.addSync(fileDesc, syncAttributes);
});
});
});
});
extensionMgr.onSynchronizerCreated(synchronizer); // Recursive function to upload multiple files
return synchronizer; var uploadFileList = [];
function fileUp(callback) {
// No more fileDesc to synchronize
if(uploadFileList.length === 0) {
syncUp(callback);
return;
}
// Dequeue a fileDesc to synchronize
var fileDesc = uploadFileList.pop();
uploadSyncAttributesList = _.values(fileDesc.syncLocations);
if(uploadSyncAttributesList.length === 0) {
fileUp(callback);
return;
}
// Get document title/content
uploadContent = fileDesc.content;
uploadContentCRC = utils.crc32(uploadContent);
uploadTitle = fileDesc.title;
uploadTitleCRC = utils.crc32(uploadTitle);
locationUp(callback);
}
// Entry point for up synchronization (upload changes)
var uploadCycle = false;
function syncUp(callback) {
if(uploadCycle === true) {
// New upload cycle
uploadCycle = false;
uploadFileList = _.values(fileSystem);
fileUp(callback);
}
else {
callback();
}
}
// Recursive function to download changes from multiple providers
var providerList = [];
function providerDown(callback) {
if(providerList.length === 0) {
callback();
return;
}
var provider = providerList.pop();
// Check that provider has files to sync
if(!fileMgr.hasSync(provider)) {
providerDown(callback);
return;
}
// Perform provider's syncDown
provider.syncDown(function(error) {
if(error) {
callback(error);
return;
}
providerDown(callback);
});
}
// Entry point for down synchronization (download changes)
function syncDown(callback) {
providerList = _.values(providerMap);
providerDown(callback);
}
;
// Main entry point for synchronization
var syncRunning = false;
var lastSync = 0;
synchronizer.sync = function() {
// If sync is already running or timeout is not reached or offline
if(syncRunning || lastSync + SYNC_PERIOD > utils.currentTime || core.isOffline) {
return;
}
syncRunning = true;
extensionMgr.onSyncRunning(true);
uploadCycle = true;
lastSync = utils.currentTime;
function isError(error) {
if(error !== undefined) {
syncRunning = false;
extensionMgr.onSyncRunning(false);
return true;
}
return false;
}
syncDown(function(error) {
if(isError(error)) {
return;
}
syncUp(function(error) {
if(isError(error)) {
return;
}
syncRunning = false;
extensionMgr.onSyncRunning(false);
extensionMgr.onSyncSuccess();
});
});
};
// Run sync function periodically
if(viewerMode === false) {
core.addPeriodicCallback(synchronizer.sync);
}
// Initialize the export dialog
function initExportDialog(provider) {
// Reset fields
utils.resetModalInputs();
// Load preferences
var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"];
if(serializedPreferences) {
var exportPreferences = JSON.parse(serializedPreferences);
_.each(provider.exportPreferencesInputIds, function(inputId) {
utils.setInputValue("#input-sync-export-" + inputId, exportPreferences[inputId]);
});
}
// Open dialog box
$("#modal-upload-" + provider.providerId).modal();
}
core.onReady(function() {
// Init each provider
_.each(providerMap, function(provider) {
// Provider's import button
$(".action-sync-import-" + provider.providerId).click(function(event) {
provider.importFiles(event);
});
// Provider's export action
$(".action-sync-export-dialog-" + provider.providerId).click(function() {
initExportDialog(provider);
});
$(".action-sync-export-" + provider.providerId).click(function(event) {
// Perform the provider's export
var fileDesc = fileMgr.getCurrentFile();
provider.exportFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
if(error) {
return;
}
fileMgr.addSync(fileDesc, syncAttributes);
});
// Store input values as preferences for next time we open the
// export dialog
var exportPreferences = {};
_.each(provider.exportPreferencesInputIds, function(inputId) {
exportPreferences[inputId] = $("#input-sync-export-" + inputId).val();
});
localStorage[provider.providerId + ".exportPreferences"] = JSON.stringify(exportPreferences);
});
// Provider's manual export button
$(".action-sync-manual-" + provider.providerId).click(function(event) {
var fileDesc = fileMgr.getCurrentFile();
provider.exportManual(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
if(error) {
return;
}
fileMgr.addSync(fileDesc, syncAttributes);
});
});
});
});
extensionMgr.onSynchronizerCreated(synchronizer);
return synchronizer;
}); });

View File

@ -6,164 +6,163 @@ define([
"async-runner" "async-runner"
], function($, core, utils, extensionMgr, asyncRunner) { ], function($, core, utils, extensionMgr, asyncRunner) {
var oauthParams = undefined; var oauthParams = undefined;
var tumblrHelper = {}; var tumblrHelper = {};
// Only used to check the offline status // Only used to check the offline status
function connect(task) { function connect(task) {
task.onRun(function() { task.onRun(function() {
if(core.isOffline === true) { if(core.isOffline === true) {
task.error(new Error("Operation not available in offline mode.|stopPublish")); task.error(new Error("Operation not available in offline mode.|stopPublish"));
return; return;
} }
task.chain(); task.chain();
}); });
} }
// Try to authenticate with OAuth // Try to authenticate with OAuth
function authenticate(task) { function authenticate(task) {
var authWindow = undefined; var authWindow = undefined;
var intervalId = undefined; var intervalId = undefined;
task.onRun(function() { task.onRun(function() {
if (oauthParams !== undefined) { if(oauthParams !== undefined) {
task.chain(); task.chain();
return; return;
} }
var serializedOauthParams = localStorage["tumblrOauthParams"]; var serializedOauthParams = localStorage["tumblrOauthParams"];
if(serializedOauthParams !== undefined) { if(serializedOauthParams !== undefined) {
oauthParams = JSON.parse(serializedOauthParams); oauthParams = JSON.parse(serializedOauthParams);
task.chain(); task.chain();
return; return;
} }
extensionMgr.onMessage("Please make sure the Tumblr authorization popup is not blocked by your browser."); extensionMgr.onMessage("Please make sure the Tumblr authorization popup is not blocked by your browser.");
var errorMsg = "Failed to retrieve a token from Tumblr."; var errorMsg = "Failed to retrieve a token from Tumblr.";
// We add time for user to enter his credentials // We add time for user to enter his credentials
task.timeout = ASYNC_TASK_LONG_TIMEOUT; task.timeout = ASYNC_TASK_LONG_TIMEOUT;
var oauth_object = undefined; var oauth_object = undefined;
function getOauthToken() { function getOauthToken() {
$.getJSON(TUMBLR_PROXY_URL + "request_token", function(data) { $.getJSON(TUMBLR_PROXY_URL + "request_token", function(data) {
if(data.oauth_token !== undefined) { if(data.oauth_token !== undefined) {
oauth_object = data; oauth_object = data;
task.chain(getVerifier); task.chain(getVerifier);
} }
else { else {
task.error(new Error(errorMsg)); task.error(new Error(errorMsg));
} }
}); });
} }
function getVerifier() { function getVerifier() {
localStorage.removeItem("tumblrVerifier"); localStorage.removeItem("tumblrVerifier");
authWindow = utils.popupWindow( authWindow = utils.popupWindow('tumblr-oauth-client.html?oauth_token=' + oauth_object.oauth_token, 'stackedit-tumblr-oauth', 800, 600);
'tumblr-oauth-client.html?oauth_token=' + oauth_object.oauth_token, authWindow.focus();
'stackedit-tumblr-oauth', 800, 600); intervalId = setInterval(function() {
authWindow.focus(); if(authWindow.closed === true) {
intervalId = setInterval(function() { clearInterval(intervalId);
if(authWindow.closed === true) { authWindow = undefined;
clearInterval(intervalId); intervalId = undefined;
authWindow = undefined; oauth_object.oauth_verifier = localStorage["tumblrVerifier"];
intervalId = undefined; if(oauth_object.oauth_verifier === undefined) {
oauth_object.oauth_verifier = localStorage["tumblrVerifier"]; task.error(new Error(errorMsg));
if(oauth_object.oauth_verifier === undefined) { return;
task.error(new Error(errorMsg)); }
return; localStorage.removeItem("tumblrVerifier");
} task.chain(getAccessToken);
localStorage.removeItem("tumblrVerifier"); }
task.chain(getAccessToken); }, 500);
} }
}, 500); function getAccessToken() {
} $.getJSON(TUMBLR_PROXY_URL + "access_token", oauth_object, function(data) {
function getAccessToken() { if(data.access_token !== undefined && data.access_token_secret !== undefined) {
$.getJSON(TUMBLR_PROXY_URL + "access_token", oauth_object, function(data) { localStorage["tumblrOauthParams"] = JSON.stringify(data);
if(data.access_token !== undefined && data.access_token_secret !== undefined) { oauthParams = data;
localStorage["tumblrOauthParams"] = JSON.stringify(data); task.chain();
oauthParams = data; }
task.chain(); else {
} task.error(new Error(errorMsg));
else { }
task.error(new Error(errorMsg)); });
} }
}); task.chain(getOauthToken);
} });
task.chain(getOauthToken); task.onError(function() {
}); if(intervalId !== undefined) {
task.onError(function() { clearInterval(intervalId);
if(intervalId !== undefined) { }
clearInterval(intervalId); if(authWindow !== undefined) {
} authWindow.close();
if(authWindow !== undefined) { }
authWindow.close(); });
} }
});
}
tumblrHelper.upload = function(blogHostname, postId, tags, format, title, content, callback) { tumblrHelper.upload = function(blogHostname, postId, tags, format, title, content, callback) {
var task = asyncRunner.createTask(); var task = asyncRunner.createTask();
connect(task); connect(task);
authenticate(task); authenticate(task);
task.onRun(function() { task.onRun(function() {
var data = $.extend({ var data = $.extend({
blog_hostname: blogHostname, blog_hostname: blogHostname,
post_id: postId, post_id: postId,
tags: tags, tags: tags,
format: format, format: format,
title: title, title: title,
content: content content: content
}, oauthParams); }, oauthParams);
$.ajax({ $.ajax({
url : TUMBLR_PROXY_URL + "post", url: TUMBLR_PROXY_URL + "post",
data: data, data: data,
type: "POST", type: "POST",
dataType : "json", dataType: "json",
timeout : AJAX_TIMEOUT timeout: AJAX_TIMEOUT
}).done(function(post, textStatus, jqXHR) { }).done(function(post, textStatus, jqXHR) {
postId = post.id; postId = post.id;
task.chain(); task.chain();
}).fail(function(jqXHR) { }).fail(function(jqXHR) {
var error = { var error = {
code: jqXHR.status, code: jqXHR.status,
message: jqXHR.statusText message: jqXHR.statusText
}; };
// Handle error // Handle error
if(error.code === 404 && postId !== undefined) { if(error.code === 404 && postId !== undefined) {
error = 'Post ' + postId + ' not found on Tumblr.|removePublish'; error = 'Post ' + postId + ' not found on Tumblr.|removePublish';
} }
handleError(error, task); handleError(error, task);
}); });
}); });
task.onSuccess(function() { task.onSuccess(function() {
callback(undefined, postId); callback(undefined, postId);
}); });
task.onError(function(error) { task.onError(function(error) {
callback(error); callback(error);
}); });
asyncRunner.addTask(task); asyncRunner.addTask(task);
}; };
function handleError(error, task) {
var errorMsg = undefined;
if (error) {
logger.error(error);
// Try to analyze the error
if (typeof error === "string") {
errorMsg = error;
}
else {
errorMsg = "Could not publish on Tumblr.";
if (error.code === 401 || error.code === 403) {
oauthParams = undefined;
localStorage.removeItem("tumblrOauthParams");
errorMsg = "Access to Tumblr account is not authorized.";
task.retry(new Error(errorMsg), 1);
return;
} else if (error.code <= 0) {
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
return tumblrHelper; function handleError(error, task) {
var errorMsg = undefined;
if(error) {
logger.error(error);
// Try to analyze the error
if(typeof error === "string") {
errorMsg = error;
}
else {
errorMsg = "Could not publish on Tumblr.";
if(error.code === 401 || error.code === 403) {
oauthParams = undefined;
localStorage.removeItem("tumblrOauthParams");
errorMsg = "Access to Tumblr account is not authorized.";
task.retry(new Error(errorMsg), 1);
return;
}
else if(error.code <= 0) {
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
return tumblrHelper;
}); });

View File

@ -2,48 +2,38 @@ define([
"utils", "utils",
"tumblr-helper" "tumblr-helper"
], function(utils, tumblrHelper) { ], function(utils, tumblrHelper) {
var PROVIDER_TUMBLR = "tumblr";
var tumblrProvider = {
providerId: PROVIDER_TUMBLR,
providerName: "Tumblr",
publishPreferencesInputIds: ["tumblr-hostname"]
};
tumblrProvider.publish = function(publishAttributes, title, content, callback) {
tumblrHelper.upload(
publishAttributes.blogHostname,
publishAttributes.postId,
publishAttributes.tags,
publishAttributes.format == "markdown" ? "markdown" : "html",
title,
content,
function(error, postId) {
if(error) {
callback(error);
return;
}
publishAttributes.postId = postId;
callback();
}
);
};
tumblrProvider.newPublishAttributes = function(event) { var PROVIDER_TUMBLR = "tumblr";
var publishAttributes = {};
publishAttributes.blogHostname = utils
.getInputTextValue(
"#input-publish-tumblr-hostname",
event,
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/);
publishAttributes.postId = utils.getInputTextValue("#input-publish-postid");
publishAttributes.tags = utils.getInputTextValue("#input-publish-tags");
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return tumblrProvider; var tumblrProvider = {
providerId: PROVIDER_TUMBLR,
providerName: "Tumblr",
publishPreferencesInputIds: [
"tumblr-hostname"
]
};
tumblrProvider.publish = function(publishAttributes, title, content, callback) {
tumblrHelper.upload(publishAttributes.blogHostname, publishAttributes.postId, publishAttributes.tags, publishAttributes.format == "markdown" ? "markdown" : "html", title, content, function(error, postId) {
if(error) {
callback(error);
return;
}
publishAttributes.postId = postId;
callback();
});
};
tumblrProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
publishAttributes.blogHostname = utils.getInputTextValue("#input-publish-tumblr-hostname", event, /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/);
publishAttributes.postId = utils.getInputTextValue("#input-publish-postid");
publishAttributes.tags = utils.getInputTextValue("#input-publish-tags");
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return tumblrProvider;
}); });

View File

@ -3,311 +3,518 @@ define([
"underscore", "underscore",
"lib/FileSaver" "lib/FileSaver"
], function($, _) { ], function($, _) {
var utils = {};
// Return a parameter from the URL var utils = {};
utils.getURLParameter = function(name) {
var regex = new RegExp(name + "=(.+?)(&|$)");
try {
return decodeURIComponent(regex.exec(location.search)[1]);
} catch (e) {
return undefined;
}
};
// Transform a selector into a jQuery object
function jqElt(element) {
if(_.isString(element)) {
return $(element);
}
return element;
}
// For input control
function inputError(element, event) {
if(event !== undefined) {
element.stop(true, true).addClass("error").delay(1000).switchClass("error");
event.stopPropagation();
}
}
// Return input value
utils.getInputValue = function(element) {
element = jqElt(element);
return element.val();
};
// Set input value
utils.setInputValue = function(element, value) {
element = jqElt(element);
element.val(value);
};
// Return input text value
utils.getInputTextValue = function(element, event, validationRegex) {
element = jqElt(element);
var value = element.val();
if (value === undefined) {
inputError(element, event);
return undefined;
}
// trim
value = utils.trim(value);
if((value.length === 0)
|| (validationRegex !== undefined && !value.match(validationRegex))) {
inputError(element, event);
return undefined;
}
return value;
};
// Return input integer value
utils.getInputIntValue = function(element, event, min, max) {
element = jqElt(element);
var value = utils.getInputTextValue(element, event);
if(value === undefined) {
return undefined;
}
value = parseInt(value);
if((value === NaN)
|| (min !== undefined && value < min)
|| (max !== undefined && value > max)) {
inputError(element, event);
return undefined;
}
return value;
};
// Return checkbox boolean value
utils.getInputChecked = function(element) {
element = jqElt(element);
return element.prop("checked");
};
// Set checkbox state
utils.setInputChecked = function(element, checked) {
element = jqElt(element);
element.prop("checked", checked);
};
// Get radio button value
utils.getInputRadio = function(name) {
return $("input:radio[name=" + name + "]:checked").prop("value");
};
// Set radio button value
utils.setInputRadio = function(name, value) {
$("input:radio[name=" + name + "][value=" + value + "]").prop("checked", true);
};
// Reset input control in all modals
utils.resetModalInputs = function() {
$(".modal input[type=text]:not([disabled]), .modal input[type=password]").val("");
};
// Basic trim function
utils.trim = function(str) {
return $.trim(str);
};
// Slug function
utils.slugify = function(text) {
return text.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
};
// Check an URL
utils.checkUrl = function(url, addSlash) {
if(!url) {
return url;
}
if(url.indexOf("http") !== 0) {
url = "http://" + url;
}
if(addSlash && url.indexOf("/", url.length - 1) === -1) {
url += "/";
}
return url;
};
// Base64 conversion
utils.encodeBase64 = function(str) {
if (str.length === 0) {
return "";
}
// UTF-8 to byte array
var bytes = [], offset = 0, length, char;
str = encodeURI(str); // Return a parameter from the URL
length = str.length; utils.getURLParameter = function(name) {
var regex = new RegExp(name + "=(.+?)(&|$)");
try {
return decodeURIComponent(regex.exec(location.search)[1]);
}
catch (e) {
return undefined;
}
};
while (offset < length) { // Transform a selector into a jQuery object
char = str[offset]; function jqElt(element) {
offset += 1; if(_.isString(element)) {
return $(element);
}
return element;
}
if ('%' !== char) { // For input control
bytes.push(char.charCodeAt(0)); function inputError(element, event) {
} else { if(event !== undefined) {
char = str[offset] + str[offset + 1]; element.stop(true, true).addClass("error").delay(1000).switchClass("error");
bytes.push(parseInt(char, 16)); event.stopPropagation();
offset += 2; }
} }
}
// byte array to base64 // Return input value
var padchar = '='; utils.getInputValue = function(element) {
var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; element = jqElt(element);
return element.val();
};
var i, b10; // Set input value
var x = []; utils.setInputValue = function(element, value) {
element = jqElt(element);
element.val(value);
};
var imax = bytes.length - bytes.length % 3; // Return input text value
utils.getInputTextValue = function(element, event, validationRegex) {
element = jqElt(element);
var value = element.val();
if(value === undefined) {
inputError(element, event);
return undefined;
}
// trim
value = utils.trim(value);
if((value.length === 0) || (validationRegex !== undefined && !value.match(validationRegex))) {
inputError(element, event);
return undefined;
}
return value;
};
for (i = 0; i < imax; i += 3) { // Return input integer value
b10 = (bytes[i] << 16) | (bytes[i+1] << 8) | bytes[i+2]; utils.getInputIntValue = function(element, event, min, max) {
x.push(alpha.charAt(b10 >> 18)); element = jqElt(element);
x.push(alpha.charAt((b10 >> 12) & 0x3F)); var value = utils.getInputTextValue(element, event);
x.push(alpha.charAt((b10 >> 6) & 0x3f)); if(value === undefined) {
x.push(alpha.charAt(b10 & 0x3f)); return undefined;
} }
switch (bytes.length - imax) { value = parseInt(value);
case 1: if((value === NaN) || (min !== undefined && value < min) || (max !== undefined && value > max)) {
b10 = bytes[i] << 16; inputError(element, event);
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + return undefined;
padchar + padchar); }
break; return value;
case 2: };
b10 = (bytes[i] << 16) | (bytes[i+1] << 8);
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
alpha.charAt((b10 >> 6) & 0x3f) + padchar);
break;
}
return x.join('');
};
// CRC32 algorithm
var mHash = [ 0, 1996959894, 3993919788, 2567524794, 124634137,
1886057615, 3915621685, 2657392035, 249268274, 2044508324,
3772115230, 2547177864, 162941995, 2125561021, 3887607047,
2428444049, 498536548, 1789927666, 4089016648, 2227061214,
450548861, 1843258603, 4107580753, 2211677639, 325883990,
1684777152, 4251122042, 2321926636, 335633487, 1661365465,
4195302755, 2366115317, 997073096, 1281953886, 3579855332,
2724688242, 1006888145, 1258607687, 3524101629, 2768942443,
901097722, 1119000684, 3686517206, 2898065728, 853044451,
1172266101, 3705015759, 2882616665, 651767980, 1373503546,
3369554304, 3218104598, 565507253, 1454621731, 3485111705,
3099436303, 671266974, 1594198024, 3322730930, 2970347812,
795835527, 1483230225, 3244367275, 3060149565, 1994146192,
31158534, 2563907772, 4023717930, 1907459465, 112637215,
2680153253, 3904427059, 2013776290, 251722036, 2517215374,
3775830040, 2137656763, 141376813, 2439277719, 3865271297,
1802195444, 476864866, 2238001368, 4066508878, 1812370925,
453092731, 2181625025, 4111451223, 1706088902, 314042704,
2344532202, 4240017532, 1658658271, 366619977, 2362670323,
4224994405, 1303535960, 984961486, 2747007092, 3569037538,
1256170817, 1037604311, 2765210733, 3554079995, 1131014506,
879679996, 2909243462, 3663771856, 1141124467, 855842277,
2852801631, 3708648649, 1342533948, 654459306, 3188396048,
3373015174, 1466479909, 544179635, 3110523913, 3462522015,
1591671054, 702138776, 2966460450, 3352799412, 1504918807,
783551873, 3082640443, 3233442989, 3988292384, 2596254646,
62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523,
3814918930, 2489596804, 225274430, 2053790376, 3826175755,
2466906013, 167816743, 2097651377, 4027552580, 2265490386,
503444072, 1762050814, 4150417245, 2154129355, 426522225,
1852507879, 4275313526, 2312317920, 282753626, 1742555852,
4189708143, 2394877945, 397917763, 1622183637, 3604390888,
2714866558, 953729732, 1340076626, 3518719985, 2797360999,
1068828381, 1219638859, 3624741850, 2936675148, 906185462,
1090812512, 3747672003, 2825379669, 829329135, 1181335161,
3412177804, 3160834842, 628085408, 1382605366, 3423369109,
3138078467, 570562233, 1426400815, 3317316542, 2998733608,
733239954, 1555261956, 3268935591, 3050360625, 752459403,
1541320221, 2607071920, 3965973030, 1969922972, 40735498,
2617837225, 3943577151, 1913087877, 83908371, 2512341634,
3803740692, 2075208622, 213261112, 2463272603, 3855990285,
2094854071, 198958881, 2262029012, 4057260610, 1759359992,
534414190, 2176718541, 4139329115, 1873836001, 414664567,
2282248934, 4279200368, 1711684554, 285281116, 2405801727,
4167216745, 1634467795, 376229701, 2685067896, 3608007406,
1308918612, 956543938, 2808555105, 3495958263, 1231636301,
1047427035, 2932959818, 3654703836, 1088359270, 936918000,
2847714899, 3736837829, 1202900863, 817233897, 3183342108,
3401237130, 1404277552, 615818150, 3134207493, 3453421203,
1423857449, 601450431, 3009837614, 3294710456, 1567103746,
711928724, 3020668471, 3272380065, 1510334235, 755167117 ];
utils.crc32 = function(str) {
var n = 0, crc = -1;
for ( var i = 0; i < str.length; i++) {
n = (crc ^ str.charCodeAt(i)) & 0xFF;
crc = (crc >>> 8) ^ mHash[n];
}
crc = crc ^ (-1);
if (crc < 0) {
crc = 0xFFFFFFFF + crc + 1;
}
return crc.toString(16);
};
// Create an centered popup window
utils.popupWindow = function(url, title, width, height) {
var left = (screen.width / 2) - (width / 2);
var top = (screen.height / 2) - (height / 2);
return window.open(
url,
title,
'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='
+ width
+ ', height='
+ height
+ ', top='
+ top
+ ', left='
+ left);
};
// Export data on disk
utils.saveAs = function(content, filename) {
if(saveAs !== undefined) {
var blob = new Blob([content], {type: "text/plain;charset=utf-8"});
saveAs(blob, filename);
}
else {
var uriContent = "data:application/octet-stream;base64,"
+ utils.encodeBase64(content);
window.open(uriContent, 'file');
}
};
// Generates a random string // Return checkbox boolean value
utils.randomString = function() { utils.getInputChecked = function(element) {
return _.random(4294967296).toString(36); element = jqElt(element);
}; return element.prop("checked");
};
// Time shared by others modules
utils.updateCurrentTime = function() {
utils.currentTime = new Date().getTime();
};
utils.updateCurrentTime();
// Serialize sync/publish attributes and store it in the fileStorage
utils.storeAttributes = function(attributes) {
var storeIndex = attributes.syncIndex || attributes.publishIndex;
// Don't store sync/publish index
attributes = _.omit(attributes, "syncIndex", "publishIndex");
// Store providerId instead of provider
attributes.provider = attributes.provider.providerId;
localStorage[storeIndex] = JSON.stringify(attributes);
};
return utils; // Set checkbox state
utils.setInputChecked = function(element, checked) {
element = jqElt(element);
element.prop("checked", checked);
};
// Get radio button value
utils.getInputRadio = function(name) {
return $("input:radio[name=" + name + "]:checked").prop("value");
};
// Set radio button value
utils.setInputRadio = function(name, value) {
$("input:radio[name=" + name + "][value=" + value + "]").prop("checked", true);
};
// Reset input control in all modals
utils.resetModalInputs = function() {
$(".modal input[type=text]:not([disabled]), .modal input[type=password]").val("");
};
// Basic trim function
utils.trim = function(str) {
return $.trim(str);
};
// Slug function
utils.slugify = function(text) {
return text.toLowerCase().replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
};
// Check an URL
utils.checkUrl = function(url, addSlash) {
if(!url) {
return url;
}
if(url.indexOf("http") !== 0) {
url = "http://" + url;
}
if(addSlash && url.indexOf("/", url.length - 1) === -1) {
url += "/";
}
return url;
};
// Create an centered popup window
utils.popupWindow = function(url, title, width, height) {
var left = (screen.width / 2) - (width / 2);
var top = (screen.height / 2) - (height / 2);
return window.open(url, title, [
'toolbar=no, ',
'location=no, ',
'directories=no, ',
'status=no, ',
'menubar=no, ',
'scrollbars=no, ',
'resizable=no, ',
'copyhistory=no, ',
'width=' + width + ', ',
'height=' + height + ', ',
'top=' + top + ', ',
'left=' + left
].join(""));
};
// Export data on disk
utils.saveAs = function(content, filename) {
if(saveAs !== undefined) {
var blob = new Blob([
content
], {
type: "text/plain;charset=utf-8"
});
saveAs(blob, filename);
}
else {
var uriContent = "data:application/octet-stream;base64," + utils.encodeBase64(content);
window.open(uriContent, 'file');
}
};
// Generates a random string
utils.randomString = function() {
return _.random(4294967296).toString(36);
};
// Time shared by others modules
utils.updateCurrentTime = function() {
utils.currentTime = new Date().getTime();
};
utils.updateCurrentTime();
// Serialize sync/publish attributes and store it in the fileStorage
utils.storeAttributes = function(attributes) {
var storeIndex = attributes.syncIndex || attributes.publishIndex;
// Don't store sync/publish index
attributes = _.omit(attributes, "syncIndex", "publishIndex");
// Store providerId instead of provider
attributes.provider = attributes.provider.providerId;
localStorage[storeIndex] = JSON.stringify(attributes);
};
// Base64 conversion
utils.encodeBase64 = function(str) {
if(str.length === 0) {
return "";
}
// UTF-8 to byte array
var bytes = [], offset = 0, length, char;
str = encodeURI(str);
length = str.length;
while (offset < length) {
char = str[offset];
offset += 1;
if('%' !== char) {
bytes.push(char.charCodeAt(0));
}
else {
char = str[offset] + str[offset + 1];
bytes.push(parseInt(char, 16));
offset += 2;
}
}
// byte array to base64
var padchar = '=';
var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var i, b10;
var x = [];
var imax = bytes.length - bytes.length % 3;
for (i = 0; i < imax; i += 3) {
b10 = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
x.push(alpha.charAt(b10 >> 18));
x.push(alpha.charAt((b10 >> 12) & 0x3F));
x.push(alpha.charAt((b10 >> 6) & 0x3f));
x.push(alpha.charAt(b10 & 0x3f));
}
switch (bytes.length - imax) {
case 1:
b10 = bytes[i] << 16;
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + padchar + padchar);
break;
case 2:
b10 = (bytes[i] << 16) | (bytes[i + 1] << 8);
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + alpha.charAt((b10 >> 6) & 0x3f) + padchar);
break;
}
return x.join('');
};
// CRC32 algorithm
var mHash = [
0,
1996959894,
3993919788,
2567524794,
124634137,
1886057615,
3915621685,
2657392035,
249268274,
2044508324,
3772115230,
2547177864,
162941995,
2125561021,
3887607047,
2428444049,
498536548,
1789927666,
4089016648,
2227061214,
450548861,
1843258603,
4107580753,
2211677639,
325883990,
1684777152,
4251122042,
2321926636,
335633487,
1661365465,
4195302755,
2366115317,
997073096,
1281953886,
3579855332,
2724688242,
1006888145,
1258607687,
3524101629,
2768942443,
901097722,
1119000684,
3686517206,
2898065728,
853044451,
1172266101,
3705015759,
2882616665,
651767980,
1373503546,
3369554304,
3218104598,
565507253,
1454621731,
3485111705,
3099436303,
671266974,
1594198024,
3322730930,
2970347812,
795835527,
1483230225,
3244367275,
3060149565,
1994146192,
31158534,
2563907772,
4023717930,
1907459465,
112637215,
2680153253,
3904427059,
2013776290,
251722036,
2517215374,
3775830040,
2137656763,
141376813,
2439277719,
3865271297,
1802195444,
476864866,
2238001368,
4066508878,
1812370925,
453092731,
2181625025,
4111451223,
1706088902,
314042704,
2344532202,
4240017532,
1658658271,
366619977,
2362670323,
4224994405,
1303535960,
984961486,
2747007092,
3569037538,
1256170817,
1037604311,
2765210733,
3554079995,
1131014506,
879679996,
2909243462,
3663771856,
1141124467,
855842277,
2852801631,
3708648649,
1342533948,
654459306,
3188396048,
3373015174,
1466479909,
544179635,
3110523913,
3462522015,
1591671054,
702138776,
2966460450,
3352799412,
1504918807,
783551873,
3082640443,
3233442989,
3988292384,
2596254646,
62317068,
1957810842,
3939845945,
2647816111,
81470997,
1943803523,
3814918930,
2489596804,
225274430,
2053790376,
3826175755,
2466906013,
167816743,
2097651377,
4027552580,
2265490386,
503444072,
1762050814,
4150417245,
2154129355,
426522225,
1852507879,
4275313526,
2312317920,
282753626,
1742555852,
4189708143,
2394877945,
397917763,
1622183637,
3604390888,
2714866558,
953729732,
1340076626,
3518719985,
2797360999,
1068828381,
1219638859,
3624741850,
2936675148,
906185462,
1090812512,
3747672003,
2825379669,
829329135,
1181335161,
3412177804,
3160834842,
628085408,
1382605366,
3423369109,
3138078467,
570562233,
1426400815,
3317316542,
2998733608,
733239954,
1555261956,
3268935591,
3050360625,
752459403,
1541320221,
2607071920,
3965973030,
1969922972,
40735498,
2617837225,
3943577151,
1913087877,
83908371,
2512341634,
3803740692,
2075208622,
213261112,
2463272603,
3855990285,
2094854071,
198958881,
2262029012,
4057260610,
1759359992,
534414190,
2176718541,
4139329115,
1873836001,
414664567,
2282248934,
4279200368,
1711684554,
285281116,
2405801727,
4167216745,
1634467795,
376229701,
2685067896,
3608007406,
1308918612,
956543938,
2808555105,
3495958263,
1231636301,
1047427035,
2932959818,
3654703836,
1088359270,
936918000,
2847714899,
3736837829,
1202900863,
817233897,
3183342108,
3401237130,
1404277552,
615818150,
3134207493,
3453421203,
1423857449,
601450431,
3009837614,
3294710456,
1567103746,
711928724,
3020668471,
3272380065,
1510334235,
755167117
];
utils.crc32 = function(str) {
var n = 0, crc = -1;
for ( var i = 0; i < str.length; i++) {
n = (crc ^ str.charCodeAt(i)) & 0xFF;
crc = (crc >>> 8) ^ mHash[n];
}
crc = crc ^ (-1);
if(crc < 0) {
crc = 0xFFFFFFFF + crc + 1;
}
return crc.toString(16);
};
return utils;
}); });

View File

@ -6,161 +6,160 @@ define([
"async-runner" "async-runner"
], function($, core, utils, extensionMgr, asyncRunner) { ], function($, core, utils, extensionMgr, asyncRunner) {
var token = undefined; var token = undefined;
var wordpressHelper = {}; var wordpressHelper = {};
// Only used to check the offline status // Only used to check the offline status
function connect(task) { function connect(task) {
task.onRun(function() { task.onRun(function() {
if(core.isOffline === true) { if(core.isOffline === true) {
task.error(new Error("Operation not available in offline mode.|stopPublish")); task.error(new Error("Operation not available in offline mode.|stopPublish"));
return; return;
} }
task.chain(); task.chain();
}); });
} }
// Try to authenticate with OAuth // Try to authenticate with OAuth
function authenticate(task) { function authenticate(task) {
var authWindow = undefined; var authWindow = undefined;
var intervalId = undefined; var intervalId = undefined;
task.onRun(function() { task.onRun(function() {
token = localStorage["wordpressToken"]; token = localStorage["wordpressToken"];
if(token !== undefined) { if(token !== undefined) {
task.chain(); task.chain();
return; return;
} }
extensionMgr.onMessage("Please make sure the Wordpress authorization popup is not blocked by your browser."); extensionMgr.onMessage("Please make sure the Wordpress authorization popup is not blocked by your browser.");
var errorMsg = "Failed to retrieve a token from Wordpress."; var errorMsg = "Failed to retrieve a token from Wordpress.";
// We add time for user to enter his credentials // We add time for user to enter his credentials
task.timeout = ASYNC_TASK_LONG_TIMEOUT; task.timeout = ASYNC_TASK_LONG_TIMEOUT;
var code = undefined; var code = undefined;
function getCode() { function getCode() {
localStorage.removeItem("wordpressCode"); localStorage.removeItem("wordpressCode");
authWindow = utils.popupWindow( authWindow = utils.popupWindow('wordpress-oauth-client.html?client_id=' + WORDPRESS_CLIENT_ID, 'stackedit-wordpress-oauth', 960, 600);
'wordpress-oauth-client.html?client_id=' + WORDPRESS_CLIENT_ID, authWindow.focus();
'stackedit-wordpress-oauth', 960, 600); intervalId = setInterval(function() {
authWindow.focus(); if(authWindow.closed === true) {
intervalId = setInterval(function() { clearInterval(intervalId);
if(authWindow.closed === true) { authWindow = undefined;
clearInterval(intervalId); intervalId = undefined;
authWindow = undefined; code = localStorage["wordpressCode"];
intervalId = undefined; if(code === undefined) {
code = localStorage["wordpressCode"]; task.error(new Error(errorMsg));
if(code === undefined) { return;
task.error(new Error(errorMsg)); }
return; localStorage.removeItem("wordpressCode");
} task.chain(getToken);
localStorage.removeItem("wordpressCode"); }
task.chain(getToken); }, 500);
} }
}, 500); function getToken() {
} $.getJSON(WORDPRESS_PROXY_URL + "authenticate/" + code, function(data) {
function getToken() { if(data.token !== undefined) {
$.getJSON(WORDPRESS_PROXY_URL + "authenticate/" + code, function(data) { token = data.token;
if(data.token !== undefined) { localStorage["wordpressToken"] = token;
token = data.token; task.chain();
localStorage["wordpressToken"] = token; }
task.chain(); else {
} task.error(new Error(errorMsg));
else { }
task.error(new Error(errorMsg)); });
} }
}); task.chain(getCode);
} });
task.chain(getCode); task.onError(function() {
}); if(intervalId !== undefined) {
task.onError(function() { clearInterval(intervalId);
if(intervalId !== undefined) { }
clearInterval(intervalId); if(authWindow !== undefined) {
} authWindow.close();
if(authWindow !== undefined) { }
authWindow.close(); });
} }
});
}
wordpressHelper.upload = function(site, postId, tags, title, content, callback) { wordpressHelper.upload = function(site, postId, tags, title, content, callback) {
var task = asyncRunner.createTask(); var task = asyncRunner.createTask();
connect(task); connect(task);
authenticate(task); authenticate(task);
task.onRun(function() { task.onRun(function() {
var url = WORDPRESS_PROXY_URL + "post"; var url = WORDPRESS_PROXY_URL + "post";
var data = { var data = {
token: token, token: token,
site: site, site: site,
postId: postId, postId: postId,
tags: tags, tags: tags,
title: title, title: title,
content: content content: content
}; };
$.ajax({ $.ajax({
url : url, url: url,
data: data, data: data,
type: "POST", type: "POST",
dataType : "json", dataType: "json",
timeout : AJAX_TIMEOUT timeout: AJAX_TIMEOUT
}).done(function(response, textStatus, jqXHR) { }).done(function(response, textStatus, jqXHR) {
if(response.body.ID) { if(response.body.ID) {
postId = response.body.ID; postId = response.body.ID;
task.chain(); task.chain();
return; return;
} }
var error = { var error = {
code: response.code, code: response.code,
message: response.body.error message: response.body.error
}; };
// Handle error // Handle error
if(error.code === 404) { if(error.code === 404) {
if(error.message == "unknown_blog") { if(error.message == "unknown_blog") {
error = 'Site "' + site + '" not found on WordPress.|removePublish'; error = 'Site "' + site + '" not found on WordPress.|removePublish';
} }
else if(error.message == "unknown_post"){ else if(error.message == "unknown_post") {
error = 'Post ' + postId + ' not found on WordPress.|removePublish'; error = 'Post ' + postId + ' not found on WordPress.|removePublish';
} }
} }
handleError(error, task); handleError(error, task);
}).fail(function(jqXHR) { }).fail(function(jqXHR) {
var error = { var error = {
code: jqXHR.status, code: jqXHR.status,
message: jqXHR.statusText message: jqXHR.statusText
}; };
handleError(error, task); handleError(error, task);
}); });
}); });
task.onSuccess(function() { task.onSuccess(function() {
callback(undefined, postId); callback(undefined, postId);
}); });
task.onError(function(error) { task.onError(function(error) {
callback(error); callback(error);
}); });
asyncRunner.addTask(task); asyncRunner.addTask(task);
}; };
function handleError(error, task) {
var errorMsg = undefined;
if (error) {
logger.error(error);
// Try to analyze the error
if (typeof error === "string") {
errorMsg = error;
}
else {
errorMsg = "Could not publish on WordPress.";
if ((error.code === 400 && error.message == "invalid_token") || error.code === 401 || error.code === 403) {
localStorage.removeItem("wordpressToken");
errorMsg = "Access to WordPress account is not authorized.";
task.retry(new Error(errorMsg), 1);
return;
} else if (error.code <= 0) {
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
return wordpressHelper; function handleError(error, task) {
var errorMsg = undefined;
if(error) {
logger.error(error);
// Try to analyze the error
if(typeof error === "string") {
errorMsg = error;
}
else {
errorMsg = "Could not publish on WordPress.";
if((error.code === 400 && error.message == "invalid_token") || error.code === 401 || error.code === 403) {
localStorage.removeItem("wordpressToken");
errorMsg = "Access to WordPress account is not authorized.";
task.retry(new Error(errorMsg), 1);
return;
}
else if(error.code <= 0) {
core.setOffline();
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
return wordpressHelper;
}); });

View File

@ -2,48 +2,39 @@ define([
"utils", "utils",
"wordpress-helper" "wordpress-helper"
], function(utils, wordpressHelper) { ], function(utils, wordpressHelper) {
var PROVIDER_WORDPRESS = "wordpress";
var wordpressProvider = {
providerId: PROVIDER_WORDPRESS,
providerName: "WordPress",
defaultPublishFormat: "html",
publishPreferencesInputIds: ["wordpress-site"]
};
wordpressProvider.publish = function(publishAttributes, title, content, callback) {
wordpressHelper.upload(
publishAttributes.site,
publishAttributes.postId,
publishAttributes.tags,
title,
content,
function(error, postId) {
if(error) {
callback(error);
return;
}
publishAttributes.postId = postId;
callback();
}
);
};
wordpressProvider.newPublishAttributes = function(event) { var PROVIDER_WORDPRESS = "wordpress";
var publishAttributes = {};
publishAttributes.site = utils
.getInputTextValue(
"#input-publish-wordpress-site",
event,
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/);
publishAttributes.postId = utils.getInputTextValue("#input-publish-postid");
publishAttributes.tags = utils.getInputTextValue("#input-publish-tags");
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return wordpressProvider; var wordpressProvider = {
providerId: PROVIDER_WORDPRESS,
providerName: "WordPress",
defaultPublishFormat: "html",
publishPreferencesInputIds: [
"wordpress-site"
]
};
wordpressProvider.publish = function(publishAttributes, title, content, callback) {
wordpressHelper.upload(publishAttributes.site, publishAttributes.postId, publishAttributes.tags, title, content, function(error, postId) {
if(error) {
callback(error);
return;
}
publishAttributes.postId = postId;
callback();
});
};
wordpressProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
publishAttributes.site = utils.getInputTextValue("#input-publish-wordpress-site", event, /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/);
publishAttributes.postId = utils.getInputTextValue("#input-publish-postid");
publishAttributes.tags = utils.getInputTextValue("#input-publish-tags");
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
return wordpressProvider;
}); });

View File

@ -0,0 +1,267 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="11">
<profile kind="CodeFormatterProfile" name="JsFormatter" version="11">
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_annotation" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_object_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_between_type_declarations" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_assignment" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation_for_objlit_initializer" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.compliance" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_closing_brace_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation_for_array_initializer" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_expressions_in_array_initializer" value="49"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_comma_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_objlit_initializer" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.source" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_conditional_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_enum_constants" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.lineSplit" value="999"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.codegen.targetPlatform" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_empty_objlit_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_object_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_opening_brace_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
</profile>
</profiles>