New extension pattern
This commit is contained in:
parent
c6c06373da
commit
d7304444a1
@ -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
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 |
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
32
js/config.js
32
js/config.js
@ -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"
|
||||
};
|
||||
|
885
js/core.js
885
js/core.js
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
@ -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;
|
||||
|
||||
});
|
@ -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;
|
||||
|
||||
});
|
@ -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;
|
||||
|
||||
});
|
@ -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;
|
||||
|
||||
});
|
@ -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;
|
||||
|
||||
});
|
@ -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;
|
||||
|
||||
});
|
@ -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 <email@example.com> 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 <email@example.com> 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;
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
||||
});
|
@ -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;
|
||||
|
||||
});
|
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
||||
});
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
1027
js/google-helper.js
1027
js/google-helper.js
File diff suppressed because it is too large
Load Diff
455
js/publisher.js
455
js/publisher.js
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
243
js/sharing.js
243
js/sharing.js
@ -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;
|
||||
});
|
146
js/ssh-helper.js
146
js/ssh-helper.js
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
251
js/storage.js
251
js/storage.js
@ -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;
|
||||
});
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
799
js/utils.js
799
js/utils.js
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
267
tools/eclipse-formatter-config.xml
Normal file
267
tools/eclipse-formatter-config.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user