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

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
define([
"jquery",
"underscore",
"underscore",
"utils",
"settings",
"extension-manager",
@ -10,461 +10,446 @@ define([
"lib/layout",
"lib/Markdown.Editor"
], function($, _, utils, settings, extensionMgr) {
var core = {};
// Used for periodic tasks
var intervalId = undefined;
var periodicCallbacks = [];
core.addPeriodicCallback = function(callback) {
periodicCallbacks.push(callback);
};
// Used to detect user activity
var userReal = false;
var userActive = false;
var windowUnique = true;
var userLastActivity = 0;
function setUserActive() {
userReal = true;
userActive = true;
userLastActivity = utils.currentTime;
};
function isUserActive() {
if(userActive === true
&& utils.currentTime - userLastActivity > USER_IDLE_THRESHOLD) {
userActive = false;
}
return userActive && windowUnique;
}
// Used to only have 1 window of the application in the same browser
var windowId = undefined;
function checkWindowUnique() {
if(userReal === false || windowUnique === false) {
return;
}
if(windowId === undefined) {
windowId = utils.randomString();
localStorage["frontWindowId"] = windowId;
}
var frontWindowId = localStorage["frontWindowId"];
if(frontWindowId != windowId) {
windowUnique = false;
if(intervalId !== undefined) {
clearInterval(intervalId);
}
$(".modal").modal("hide");
$('#modal-non-unique').modal({
backdrop: "static",
keyboard: false
});
}
}
// Offline management
core.isOffline = false;
var offlineTime = utils.currentTime;
core.setOffline = function() {
offlineTime = utils.currentTime;
if(core.isOffline === false) {
core.isOffline = true;
extensionMgr.onOfflineChanged(true);
}
};
function setOnline() {
if(core.isOffline === true) {
core.isOffline = false;
extensionMgr.onOfflineChanged(false);
}
}
function checkOnline() {
// Try to reconnect if we are offline but we have some network
if (core.isOffline === true && navigator.onLine === true
&& offlineTime + CHECK_ONLINE_PERIOD < utils.currentTime) {
offlineTime = utils.currentTime;
// Try to download anything to test the connection
$.ajax({
url : "//www.google.com/jsapi",
timeout : AJAX_TIMEOUT, dataType : "script"
}).done(function() {
setOnline();
});
}
}
// Load settings in settings dialog
function loadSettings() {
// Layout orientation
utils.setInputRadio("radio-layout-orientation", settings.layoutOrientation);
// Theme
utils.setInputValue("#input-settings-theme", localStorage.theme);
// Lazy rendering
utils.setInputChecked("#input-settings-lazy-rendering", settings.lazyRendering);
// Editor font size
utils.setInputValue("#input-settings-editor-font-size", settings.editorFontSize);
// Default content
utils.setInputValue("#textarea-settings-default-content", settings.defaultContent);
// Commit message
utils.setInputValue("#input-settings-publish-commit-msg", settings.commitMsg);
// Template
utils.setInputValue("#textarea-settings-publish-template", settings.template);
// SSH proxy
utils.setInputValue("#input-settings-ssh-proxy", settings.sshProxy);
// Load extension settings
extensionMgr.onLoadSettings();
}
// Save settings from settings dialog
function saveSettings(event) {
var newSettings = {};
// Layout orientation
newSettings.layoutOrientation = utils.getInputRadio("radio-layout-orientation");
// Theme
var theme = utils.getInputValue("#input-settings-theme");
// Lazy Rendering
newSettings.lazyRendering = utils.getInputChecked("#input-settings-lazy-rendering");
// Editor font size
newSettings.editorFontSize = utils.getInputIntValue("#input-settings-editor-font-size", event, 1, 99);
// Default content
newSettings.defaultContent = utils.getInputValue("#textarea-settings-default-content");
// Commit message
newSettings.commitMsg = utils.getInputTextValue("#input-settings-publish-commit-msg", event);
// Template
newSettings.template = utils.getInputTextValue("#textarea-settings-publish-template", event);
// SSH proxy
newSettings.sshProxy = utils.checkUrl(utils.getInputTextValue("#input-settings-ssh-proxy", event), true);
// Save extension settings
newSettings.extensionSettings = {};
extensionMgr.onSaveSettings(newSettings.extensionSettings, event);
if(!event.isPropagationStopped()) {
$.extend(settings, newSettings);
localStorage.settings = JSON.stringify(settings);
localStorage.theme = theme;
}
}
// Create the layout
var layout = undefined;
core.createLayout = function() {
if(viewerMode === true) {
return;
}
var layoutGlobalConfig = {
closable : true,
resizable : false,
slidable : false,
livePaneResizing : true,
enableCursorHotkey : false,
spacing_open : 15,
spacing_closed : 15,
togglerLength_open : 90,
togglerLength_closed : 90,
stateManagement__enabled : false,
center__minWidth : 200,
center__minHeight : 200
};
extensionMgr.onLayoutConfigure(layoutGlobalConfig);
if (settings.layoutOrientation == "horizontal") {
$(".ui-layout-south").remove();
$(".ui-layout-east").addClass("well").prop("id", "wmd-preview");
layout = $('body').layout(
$.extend(layoutGlobalConfig, {
east__resizable : true,
east__size : .5,
east__minSize : 200
})
);
} else if (settings.layoutOrientation == "vertical") {
$(".ui-layout-east").remove();
$(".ui-layout-south").addClass("well").prop("id", "wmd-preview");
layout = $('body').layout(
$.extend(layoutGlobalConfig, {
south__resizable : true,
south__size : .5,
south__minSize : 200
})
);
}
$(".ui-layout-toggler-north").addClass("btn").append(
$("<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;
var core = {};
// Hide default buttons
$(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)")
.addClass("btn").css("left", 0).find("span").hide();
// Add customized buttons
$("#wmd-bold-button").append($("<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"));
};
// Used for periodic tasks
var intervalId = undefined;
var periodicCallbacks = [];
core.addPeriodicCallback = function(callback) {
periodicCallbacks.push(callback);
};
// onReady event callbacks
var readyCallbacks = [];
core.onReady = function(callback) {
readyCallbacks.push(callback);
runReadyCallbacks();
};
var ready = false;
core.setReady = function() {
ready = true;
runReadyCallbacks();
};
function runReadyCallbacks() {
if(ready === true) {
_.each(readyCallbacks, function(callback) {
callback();
});
readyCallbacks = [];
}
}
core.onReady(extensionMgr.onReady);
core.onReady(function() {
// Load theme list
_.each(THEME_LIST, function(name, value) {
$("#input-settings-theme").append($('<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);
});
// Used to detect user activity
var userReal = false;
var userActive = false;
var windowUnique = true;
var userLastActivity = 0;
function setUserActive() {
userReal = true;
userActive = true;
userLastActivity = utils.currentTime;
}
// Settings loading/saving
$(".action-load-settings").click(function() {
loadSettings();
});
$(".action-apply-settings").click(function(e) {
saveSettings(e);
if(!e.isPropagationStopped()) {
window.location.reload();
}
});
$(".action-default-settings").click(function() {
localStorage.removeItem("settings");
localStorage.removeItem("theme");
window.location.reload();
});
$(".action-app-reset").click(function() {
localStorage.clear();
window.location.reload();
});
// UI layout
$("#menu-bar, .ui-layout-center, .ui-layout-east, .ui-layout-south").removeClass("hide");
core.createLayout();
function isUserActive() {
if(userActive === true && utils.currentTime - userLastActivity > USER_IDLE_THRESHOLD) {
userActive = false;
}
return userActive && windowUnique;
}
// Editor's textarea
$("#wmd-input, #md-section-helper").css({
// Apply editor font size
"font-size": settings.editorFontSize + "px",
"line-height": Math.round(settings.editorFontSize * (20/14)) + "px"
});
// Manage tab key
$("#wmd-input").keydown(function(e) {
if(e.keyCode === 9) {
var value = $(this).val();
var start = this.selectionStart;
var end = this.selectionEnd;
// IE8 does not support selection attributes
if(start === undefined || end === undefined) {
return;
}
$(this).val(value.substring(0, start) + "\t" + value.substring(end));
this.selectionStart = this.selectionEnd = start + 1;
e.preventDefault();
}
});
// Used to only have 1 window of the application in the same browser
var windowId = undefined;
function checkWindowUnique() {
if(userReal === false || windowUnique === false) {
return;
}
if(windowId === undefined) {
windowId = utils.randomString();
localStorage["frontWindowId"] = windowId;
}
var frontWindowId = localStorage["frontWindowId"];
if(frontWindowId != windowId) {
windowUnique = false;
if(intervalId !== undefined) {
clearInterval(intervalId);
}
$(".modal").modal("hide");
$('#modal-non-unique').modal({
backdrop: "static",
keyboard: false
});
}
}
// Tooltips
$(".tooltip-scroll-link").tooltip({
html: true,
container: '#modal-settings',
placement: 'right',
title: ['Scroll Link is a feature that binds together editor and preview scrollbars. ',
'It allows you to keep an eye on the preview while scrolling the editor and vice versa. ',
'<br><br>',
'The mapping between Markdown and HTML is based on the position of the title elements (h1, h2, ...) in the page. ',
'Therefore, if your document does not contain any title, the mapping will be linear and consequently less efficient.',
].join("")
});
$(".tooltip-lazy-rendering").tooltip({
container: '#modal-settings',
placement: 'right',
title: 'Disable preview rendering while typing in order to offload CPU. Refresh preview after 500 ms of inactivity.'
});
$(".tooltip-default-content").tooltip({
html: true,
container: '#modal-settings',
placement: 'right',
title: 'Thanks for supporting StackEdit by adding a backlink in your documents!'
});
$(".tooltip-template").tooltip({
html: true,
container: '#modal-settings',
placement: 'right',
trigger: 'manual',
title: ['Available variables:<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');
});
// Offline management
core.isOffline = false;
var offlineTime = utils.currentTime;
core.setOffline = function() {
offlineTime = utils.currentTime;
if(core.isOffline === false) {
core.isOffline = true;
extensionMgr.onOfflineChanged(true);
}
};
function setOnline() {
if(core.isOffline === true) {
core.isOffline = false;
extensionMgr.onOfflineChanged(false);
}
}
function checkOnline() {
// Try to reconnect if we are offline but we have some network
if(core.isOffline === true && navigator.onLine === true && offlineTime + CHECK_ONLINE_PERIOD < utils.currentTime) {
offlineTime = utils.currentTime;
// Try to download anything to test the connection
$.ajax({
url: "//www.google.com/jsapi",
timeout: AJAX_TIMEOUT,
dataType: "script"
}).done(function() {
setOnline();
});
}
}
// Reset inputs
$(".action-reset-input").click(function() {
utils.resetModalInputs();
});
// Do periodic tasks
intervalId = window.setInterval(function() {
utils.updateCurrentTime();
checkWindowUnique();
if(isUserActive() === true || viewerMode === true) {
_.each(periodicCallbacks, function(callback) {
callback();
});
checkOnline();
}
}, 1000);
});
// Load settings in settings dialog
function loadSettings() {
return core;
// Layout orientation
utils.setInputRadio("radio-layout-orientation", settings.layoutOrientation);
// Theme
utils.setInputValue("#input-settings-theme", localStorage.theme);
// Lazy rendering
utils.setInputChecked("#input-settings-lazy-rendering", settings.lazyRendering);
// Editor font size
utils.setInputValue("#input-settings-editor-font-size", settings.editorFontSize);
// Default content
utils.setInputValue("#textarea-settings-default-content", settings.defaultContent);
// Commit message
utils.setInputValue("#input-settings-publish-commit-msg", settings.commitMsg);
// Template
utils.setInputValue("#textarea-settings-publish-template", settings.template);
// SSH proxy
utils.setInputValue("#input-settings-ssh-proxy", settings.sshProxy);
// Load extension settings
extensionMgr.onLoadSettings();
}
// Save settings from settings dialog
function saveSettings(event) {
var newSettings = {};
// Layout orientation
newSettings.layoutOrientation = utils.getInputRadio("radio-layout-orientation");
// Theme
var theme = utils.getInputValue("#input-settings-theme");
// Lazy Rendering
newSettings.lazyRendering = utils.getInputChecked("#input-settings-lazy-rendering");
// Editor font size
newSettings.editorFontSize = utils.getInputIntValue("#input-settings-editor-font-size", event, 1, 99);
// Default content
newSettings.defaultContent = utils.getInputValue("#textarea-settings-default-content");
// Commit message
newSettings.commitMsg = utils.getInputTextValue("#input-settings-publish-commit-msg", event);
// Template
newSettings.template = utils.getInputTextValue("#textarea-settings-publish-template", event);
// SSH proxy
newSettings.sshProxy = utils.checkUrl(utils.getInputTextValue("#input-settings-ssh-proxy", event), true);
// Save extension settings
newSettings.extensionSettings = {};
extensionMgr.onSaveSettings(newSettings.extensionSettings, event);
if(!event.isPropagationStopped()) {
$.extend(settings, newSettings);
localStorage.settings = JSON.stringify(settings);
localStorage.theme = theme;
}
}
// Create the layout
var layout = undefined;
core.createLayout = function() {
if(viewerMode === true) {
return;
}
var layoutGlobalConfig = {
closable: true,
resizable: false,
slidable: false,
livePaneResizing: true,
enableCursorHotkey: false,
spacing_open: 15,
spacing_closed: 15,
togglerLength_open: 90,
togglerLength_closed: 90,
stateManagement__enabled: false,
center__minWidth: 200,
center__minHeight: 200
};
extensionMgr.onLayoutConfigure(layoutGlobalConfig);
if(settings.layoutOrientation == "horizontal") {
$(".ui-layout-south").remove();
$(".ui-layout-east").addClass("well").prop("id", "wmd-preview");
layout = $('body').layout($.extend(layoutGlobalConfig, {
east__resizable: true,
east__size: .5,
east__minSize: 200
}));
}
else if(settings.layoutOrientation == "vertical") {
$(".ui-layout-east").remove();
$(".ui-layout-south").addClass("well").prop("id", "wmd-preview");
layout = $('body').layout($.extend(layoutGlobalConfig, {
south__resizable: true,
south__size: .5,
south__minSize: 200
}));
}
$(".ui-layout-toggler-north").addClass("btn").append($("<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",
"async-runner"
], function($, core, asyncRunner) {
var PROVIDER_DOWNLOAD = "download";
var downloadProvider = {
providerId: PROVIDER_DOWNLOAD,
sharingAttributes: ["url"]
};
downloadProvider.importPublic = function(importParameters, callback) {
var task = asyncRunner.createTask();
var title = undefined;
var content = undefined;
task.onRun(function() {
var url = importParameters.url;
var slashUrl = url.lastIndexOf("/");
if(slashUrl === -1) {
task.error(new Error("Invalid URL parameter."));
return;
}
title = url.substring(slashUrl + 1);
$.ajax({
url : DOWNLOAD_PROXY_URL + "download?url=" + url,
type: "GET",
dataType : "text",
timeout : AJAX_TIMEOUT
}).done(function(result, textStatus, jqXHR) {
content = result;
task.chain();
}).fail(function(jqXHR) {
task.error(new Error("Unable to access URL " + url));
});
});
task.onSuccess(function() {
callback(undefined, title, content);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
return downloadProvider;
var PROVIDER_DOWNLOAD = "download";
var downloadProvider = {
providerId: PROVIDER_DOWNLOAD,
sharingAttributes: [
"url"
]
};
downloadProvider.importPublic = function(importParameters, callback) {
var task = asyncRunner.createTask();
var title = undefined;
var content = undefined;
task.onRun(function() {
var url = importParameters.url;
var slashUrl = url.lastIndexOf("/");
if(slashUrl === -1) {
task.error(new Error("Invalid URL parameter."));
return;
}
title = url.substring(slashUrl + 1);
$.ajax({
url: DOWNLOAD_PROXY_URL + "download?url=" + url,
type: "GET",
dataType: "text",
timeout: AJAX_TIMEOUT
}).done(function(result, textStatus, jqXHR) {
content = result;
task.chain();
}).fail(function(jqXHR) {
task.error(new Error("Unable to access URL " + url));
});
});
task.onSuccess(function() {
callback(undefined, title, content);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
return downloadProvider;
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,77 +2,77 @@ define([
"jquery",
"underscore"
], function($, _) {
var buttonSync = {
extensionId: "buttonSync",
extensionName: 'Button "Synchronize"',
settingsBloc: '<p>Adds a "Synchronize documents" button in the navigation bar.</p>'
};
var button = undefined;
var syncRunning = false;
var uploadPending = false;
var isOffline = false;
// Enable/disable the button
var updateButtonState = function() {
if(button === undefined) {
return;
}
if(syncRunning === true || uploadPending === false || isOffline) {
button.addClass("disabled");
}
else {
button.removeClass("disabled");
}
};
var synchronizer = undefined;
buttonSync.onSynchronizerCreated = function(synchronizerParameter) {
synchronizer = synchronizerParameter;
};
buttonSync.onCreateButton = function() {
button = $([
'<button class="btn" title="Synchronize all documents">',
'<i class="icon-refresh"></i>',
'</button>'].join("")
).click(function() {
if(!$(this).hasClass("disabled")) {
synchronizer.forceSync();
}
});
return button;
};
buttonSync.onReady = updateButtonState;
buttonSync.onSyncRunning = function(isRunning) {
syncRunning = isRunning;
uploadPending = true;
updateButtonState();
};
buttonSync.onSyncSuccess = function() {
uploadPending = false;
updateButtonState();
};
buttonSync.onOfflineChanged = function(isOfflineParameter) {
isOffline = isOfflineParameter;
updateButtonState();
};
// Check that a file has synchronized locations
var checkSynchronization = function(fileDesc) {
if(_.size(fileDesc.syncLocations) !== 0) {
uploadPending = true;
updateButtonState();
}
};
buttonSync.onContentChanged = checkSynchronization;
buttonSync.onTitleChanged = checkSynchronization;
return buttonSync;
var buttonSync = {
extensionId: "buttonSync",
extensionName: 'Button "Synchronize"',
settingsBloc: '<p>Adds a "Synchronize documents" button in the navigation bar.</p>'
};
var button = undefined;
var syncRunning = false;
var uploadPending = false;
var isOffline = false;
// Enable/disable the button
var updateButtonState = function() {
if(button === undefined) {
return;
}
if(syncRunning === true || uploadPending === false || isOffline) {
button.addClass("disabled");
}
else {
button.removeClass("disabled");
}
};
var synchronizer = undefined;
buttonSync.onSynchronizerCreated = function(synchronizerParameter) {
synchronizer = synchronizerParameter;
};
buttonSync.onCreateButton = function() {
button = $([
'<button class="btn" title="Synchronize all documents">',
' <i class="icon-refresh"></i>',
'</button>'
].join("")).click(function() {
if(!$(this).hasClass("disabled")) {
synchronizer.forceSync();
}
});
return button;
};
buttonSync.onReady = updateButtonState;
buttonSync.onSyncRunning = function(isRunning) {
syncRunning = isRunning;
uploadPending = true;
updateButtonState();
};
buttonSync.onSyncSuccess = function() {
uploadPending = false;
updateButtonState();
};
buttonSync.onOfflineChanged = function(isOfflineParameter) {
isOffline = isOfflineParameter;
updateButtonState();
};
// Check that a file has synchronized locations
var checkSynchronization = function(fileDesc) {
if(_.size(fileDesc.syncLocations) !== 0) {
uploadPending = true;
updateButtonState();
}
};
buttonSync.onContentChanged = checkSynchronization;
buttonSync.onTitleChanged = checkSynchronization;
return buttonSync;
});

View File

@ -3,70 +3,99 @@ define([
"underscore",
"file-system"
], 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 = {};
$("#file-selector li:not(.stick)").empty();
_.chain(
fileSystem
).sortBy(function(fileDesc) {
return fileDesc.title.toLowerCase();
}).each(function(fileDesc) {
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;
return documentSelector;
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 = {};
$("#file-selector li:not(.stick)").empty();
_.chain(fileSystem).sortBy(function(fileDesc) {
return fileDesc.title.toLowerCase();
}).each(function(fileDesc) {
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",
"underscore"
], 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;
document.title = "StackEdit - " + title;
$("#file-title").html(composeTitle(fileDesc));
$(".file-title").text(title);
$("#file-title-input").val(title);
if(layout !== undefined) {
// Use defer to make sure UI has been updated
_.defer(layout.resizeAll);
}
};
documentTitle.onFileSelected = function(fileDescParameter) {
fileDesc = fileDescParameter;
updateTitle(fileDescParameter);
};
documentTitle.onTitleChanged = updateTitle;
documentTitle.onSyncExportSuccess = updateTitle;
documentTitle.onSyncRemoved = updateTitle;
documentTitle.onNewPublishSuccess = updateTitle;
documentTitle.onPublishRemoved = updateTitle;
return documentTitle;
var documentTitle = {
extensionId: "documentTitle",
extensionName: "Document title",
settingsBloc: '<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;
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() {
var emailConverter = {
extensionId: "emailConverter",
extensionName: "Email Converter",
var emailConverter = {
extensionId: "emailConverter",
extensionName: "Email Converter",
optional: true,
settingsBloc: '<p>Converts email adresses in the form &lt;email@example.com&gt; into a clickable links.</p>'
};
emailConverter.onEditorConfigure = function(editor) {
editor.getConverter().hooks.chain("postConversion", function(text) {
return text.replace(/<(mailto\:)?([^\s>]+@[^\s>]+\.\S+?)>/g, function(match, mailto, email) {
return '<a href="mailto:' + email + '">' + email + '</a>';
});
});
};
return emailConverter;
settingsBloc: '<p>Converts email adresses in the form &lt;email@example.com&gt; into a clickable links.</p>'
};
emailConverter.onEditorConfigure = function(editor) {
editor.getConverter().hooks.chain("postConversion", function(text) {
return text.replace(/<(mailto\:)?([^\s>]+@[^\s>]+\.\S+?)>/g, function(match, mailto, email) {
return '<a href="mailto:' + email + '">' + email + '</a>';
});
});
};
return emailConverter;
});

View File

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

View File

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

View File

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

View File

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

View File

@ -1,199 +1,202 @@
define([
"jquery",
"underscore",
"lib/css_browser_selector"
"lib/css_browser_selector"
], function($, _) {
var scrollLink = {
extensionId: "scrollLink",
extensionName: "Scroll Link",
optional: true,
settingsBloc: [
'<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) {
layoutConfig.onresize = buildSections;
};
scrollLink.onLayoutCreated = function() {
$("#wmd-preview").scroll(function() {
isScrollPreview = true;
runScrollLink();
});
$("#wmd-input").scroll(function() {
isScrollPreview = false;
runScrollLink();
});
};
scrollLink.onEditorConfigure = function(editor) {
skipScrollLink = true;
lastPreviewScrollTop = 0;
editor.hooks.chain("onPreviewRefresh", function() {
skipScrollLink = true;
});
};
scrollLink.onPreviewFinished = function() {
// MathJax may have change the scrolling position. Restore it.
if(lastPreviewScrollTop >= 0) {
$("#wmd-preview").scrollTop(lastPreviewScrollTop);
}
_.defer(function() {
// Modify scroll position of the preview not the editor
lastEditorScrollTop = -9;
buildSections();
// Preview may change if images are loading
$("#wmd-preview img").load(function() {
lastEditorScrollTop = -9;
buildSections();
});
});
};
return scrollLink;
var scrollLink = {
extensionId: "scrollLink",
extensionName: "Scroll Link",
optional: true,
settingsBloc: [
'<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) {
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",
"utils"
], function($, _, utils) {
var toc = {
extensionId: "toc",
extensionName: "Table of content",
var toc = {
extensionId: "toc",
extensionName: "Table of content",
optional: true,
settingsBloc: '<p>Generates a table of content when a [TOC] marker is found.</p>'
};
// TOC element description
function TocElement(tagName, anchor, text) {
this.tagName = tagName;
this.anchor = anchor;
this.text = text;
this.children = [];
}
TocElement.prototype.childrenToString = function() {
if(this.children.length === 0) {
return "";
}
var result = "<ul>";
_.each(this.children, function(child) {
result += child.toString();
});
result += "</ul>";
return result;
};
TocElement.prototype.toString = function() {
var result = "<li>";
if(this.anchor && this.text) {
result += '<a href="#' + this.anchor + '">' + this.text + '</a>';
}
result += this.childrenToString() + "</li>";
return result;
};
// Transform flat list of TocElement into a tree
function groupTags(array, level) {
level = level || 1;
var tagName = "H" + level;
var result = [];
var currentElement = undefined;
function pushCurrentElement() {
if(currentElement !== undefined) {
if(currentElement.children.length > 0) {
currentElement.children = groupTags(currentElement.children, level + 1);
}
result.push(currentElement);
}
}
_.each(array, function(element, index) {
if(element.tagName != tagName) {
if(currentElement === undefined) {
currentElement = new TocElement();
}
currentElement.children.push(element);
}
else {
pushCurrentElement();
currentElement = element;
}
});
pushCurrentElement();
return result;
}
// Build the TOC
function buildToc() {
var anchorList = {};
function createAnchor(element) {
var id = element.prop("id") || utils.slugify(element.text());
var anchor = id;
var index = 0;
while(_.has(anchorList, anchor)) {
anchor = id + "-" + (++index);
}
anchorList[anchor] = true;
// Update the id of the element
element.prop("id", anchor);
return anchor;
}
var elementList = [];
$("#wmd-preview > h1," +
"#wmd-preview > h2," +
"#wmd-preview > h3," +
"#wmd-preview > h4," +
"#wmd-preview > h5," +
"#wmd-preview > h6").each(function() {
elementList.push(new TocElement(
$(this).prop("tagName"),
createAnchor($(this)),
$(this).text()
));
});
elementList = groupTags(elementList);
return '<div class="toc"><ul>' + elementList.toString() + '</ul></div>';
}
toc.onEditorConfigure = function(editor) {
// Run TOC generation when conversion is finished directly on HTML
editor.hooks.chain("onPreviewRefresh", function() {
var toc = buildToc();
var html = $("#wmd-preview").html();
html = html.replace(/<p>\[TOC\]<\/p>/g, toc);
$("#wmd-preview").html(html);
});
};
return toc;
settingsBloc: '<p>Generates a table of content when a [TOC] marker is found.</p>'
};
// TOC element description
function TocElement(tagName, anchor, text) {
this.tagName = tagName;
this.anchor = anchor;
this.text = text;
this.children = [];
}
TocElement.prototype.childrenToString = function() {
if(this.children.length === 0) {
return "";
}
var result = "<ul>";
_.each(this.children, function(child) {
result += child.toString();
});
result += "</ul>";
return result;
};
TocElement.prototype.toString = function() {
var result = "<li>";
if(this.anchor && this.text) {
result += '<a href="#' + this.anchor + '">' + this.text + '</a>';
}
result += this.childrenToString() + "</li>";
return result;
};
// Transform flat list of TocElement into a tree
function groupTags(array, level) {
level = level || 1;
var tagName = "H" + level;
var result = [];
var currentElement = undefined;
function pushCurrentElement() {
if(currentElement !== undefined) {
if(currentElement.children.length > 0) {
currentElement.children = groupTags(currentElement.children, level + 1);
}
result.push(currentElement);
}
}
_.each(array, function(element, index) {
if(element.tagName != tagName) {
if(currentElement === undefined) {
currentElement = new TocElement();
}
currentElement.children.push(element);
}
else {
pushCurrentElement();
currentElement = element;
}
});
pushCurrentElement();
return result;
}
// Build the TOC
function buildToc() {
var anchorList = {};
function createAnchor(element) {
var id = element.prop("id") || utils.slugify(element.text());
var anchor = id;
var index = 0;
while (_.has(anchorList, anchor)) {
anchor = id + "-" + (++index);
}
anchorList[anchor] = true;
// Update the id of the element
element.prop("id", anchor);
return anchor;
}
var elementList = [];
$("#wmd-preview > h1," + "#wmd-preview > h2," + "#wmd-preview > h3," + "#wmd-preview > h4," + "#wmd-preview > h5," + "#wmd-preview > h6").each(function() {
elementList.push(new TocElement($(this).prop("tagName"), createAnchor($(this)), $(this).text()));
});
elementList = groupTags(elementList);
return '<div class="toc"><ul>' + elementList.toString() + '</ul></div>';
}
toc.onEditorConfigure = function(editor) {
// Run TOC generation when conversion is finished directly on HTML
editor.hooks.chain("onPreviewRefresh", function() {
var toc = buildToc();
var html = $("#wmd-preview").html();
html = html.replace(/<p>\[TOC\]<\/p>/g, toc);
$("#wmd-preview").html(html);
});
};
return toc;
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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>