New extension pattern

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

View File

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

BIN
doc/img/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
define([ define([
"jquery", "jquery",
"underscore", "underscore",
"utils", "utils",
"settings", "settings",
"extension-manager", "extension-manager",
@ -11,460 +11,445 @@ define([
"lib/Markdown.Editor" "lib/Markdown.Editor"
], function($, _, utils, settings, extensionMgr) { ], function($, _, utils, settings, extensionMgr) {
var core = {}; var core = {};
// Used for periodic tasks // Used for periodic tasks
var intervalId = undefined; var intervalId = undefined;
var periodicCallbacks = []; var periodicCallbacks = [];
core.addPeriodicCallback = function(callback) { core.addPeriodicCallback = function(callback) {
periodicCallbacks.push(callback); periodicCallbacks.push(callback);
}; };
// Used to detect user activity // Used to detect user activity
var userReal = false; var userReal = false;
var userActive = false; var userActive = false;
var windowUnique = true; var windowUnique = true;
var userLastActivity = 0; var userLastActivity = 0;
function setUserActive() { function setUserActive() {
userReal = true; userReal = true;
userActive = true; userActive = true;
userLastActivity = utils.currentTime; 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 function isUserActive() {
var windowId = undefined; if(userActive === true && utils.currentTime - userLastActivity > USER_IDLE_THRESHOLD) {
function checkWindowUnique() { userActive = false;
if(userReal === false || windowUnique === false) { }
return; return userActive && windowUnique;
} }
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 // Used to only have 1 window of the application in the same browser
core.isOffline = false; var windowId = undefined;
var offlineTime = utils.currentTime; function checkWindowUnique() {
core.setOffline = function() { if(userReal === false || windowUnique === false) {
offlineTime = utils.currentTime; return;
if(core.isOffline === false) { }
core.isOffline = true; if(windowId === undefined) {
extensionMgr.onOfflineChanged(true); windowId = utils.randomString();
} localStorage["frontWindowId"] = windowId;
}; }
function setOnline() { var frontWindowId = localStorage["frontWindowId"];
if(core.isOffline === true) { if(frontWindowId != windowId) {
core.isOffline = false; windowUnique = false;
extensionMgr.onOfflineChanged(false); if(intervalId !== undefined) {
} clearInterval(intervalId);
} }
function checkOnline() { $(".modal").modal("hide");
// Try to reconnect if we are offline but we have some network $('#modal-non-unique').modal({
if (core.isOffline === true && navigator.onLine === true backdrop: "static",
&& offlineTime + CHECK_ONLINE_PERIOD < utils.currentTime) { keyboard: false
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 // Offline management
function loadSettings() { 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();
});
}
}
// Layout orientation // Load settings in settings dialog
utils.setInputRadio("radio-layout-orientation", settings.layoutOrientation); function loadSettings() {
// 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 // Layout orientation
extensionMgr.onLoadSettings(); 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);
// Save settings from settings dialog // Load extension settings
function saveSettings(event) { extensionMgr.onLoadSettings();
var newSettings = {}; }
// Layout orientation // Save settings from settings dialog
newSettings.layoutOrientation = utils.getInputRadio("radio-layout-orientation"); function saveSettings(event) {
// Theme var newSettings = {};
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 // Layout orientation
newSettings.extensionSettings = {}; newSettings.layoutOrientation = utils.getInputRadio("radio-layout-orientation");
extensionMgr.onSaveSettings(newSettings.extensionSettings, event); // 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);
if(!event.isPropagationStopped()) { // Save extension settings
$.extend(settings, newSettings); newSettings.extensionSettings = {};
localStorage.settings = JSON.stringify(settings); extensionMgr.onSaveSettings(newSettings.extensionSettings, event);
localStorage.theme = theme;
}
}
// Create the layout if(!event.isPropagationStopped()) {
var layout = undefined; $.extend(settings, newSettings);
core.createLayout = function() { localStorage.settings = JSON.stringify(settings);
if(viewerMode === true) { localStorage.theme = theme;
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 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');
});
// Create the PageDown editor extensionMgr.onLayoutCreated(layout);
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; // Create the PageDown editor
function checkDocumentChanges() { var insertLinkCallback = undefined;
var newDocumentContent = $("#wmd-input").val(); core.createEditor = function(onTextChange) {
if(documentContent !== undefined && documentContent != newDocumentContent) { var converter = new Markdown.Converter();
onTextChange(); var editor = new Markdown.Editor(converter);
} // Custom insert link dialog
documentContent = newDocumentContent; editor.hooks.set("insertLinkDialog", function(callback) {
} insertLinkCallback = callback;
var previewWrapper = undefined; utils.resetModalInputs();
if(settings.lazyRendering === true) { $("#modal-insert-link").modal();
previewWrapper = function(makePreview) { _.defer(function() {
var debouncedMakePreview = _.debounce(makePreview, 500); $("#input-insert-link").focus();
return function() { });
if(documentContent === undefined) { return true;
makePreview(); });
} // Custom insert image dialog
else { editor.hooks.set("insertImageDialog", function(callback) {
debouncedMakePreview(); insertLinkCallback = callback;
} utils.resetModalInputs();
checkDocumentChanges(); $("#modal-insert-image").modal();
}; _.defer(function() {
}; $("#input-insert-image").focus();
} });
else { return true;
previewWrapper = function(makePreview) { });
return function() {
checkDocumentChanges();
makePreview();
};
};
}
editor.hooks.chain("onPreviewRefresh", extensionMgr.onAsyncPreview);
extensionMgr.onEditorConfigure(editor);
$("#wmd-input, #wmd-preview").scrollTop(0); var documentContent = undefined;
$("#wmd-button-bar").empty(); function checkDocumentChanges() {
editor.run(previewWrapper); var newDocumentContent = $("#wmd-input").val();
firstChange = false; 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);
// Hide default buttons $("#wmd-input, #wmd-preview").scrollTop(0);
$(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)") $("#wmd-button-bar").empty();
.addClass("btn").css("left", 0).find("span").hide(); editor.run(previewWrapper);
firstChange = false;
// Add customized buttons // Hide default buttons
$("#wmd-bold-button").append($("<i>").addClass("icon-bold")); $(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)").addClass("btn").css("left", 0).find("span").hide();
$("#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 // Add customized buttons
var readyCallbacks = []; $("#wmd-bold-button").append($("<i>").addClass("icon-bold"));
core.onReady = function(callback) { $("#wmd-italic-button").append($("<i>").addClass("icon-italic"));
readyCallbacks.push(callback); $("#wmd-link-button").append($("<i>").addClass("icon-globe"));
runReadyCallbacks(); $("#wmd-quote-button").append($("<i>").addClass("icon-indent-left"));
}; $("#wmd-code-button").append($("<i>").addClass("icon-code"));
var ready = false; $("#wmd-image-button").append($("<i>").addClass("icon-picture"));
core.setReady = function() { $("#wmd-olist-button").append($("<i>").addClass("icon-numbered-list"));
ready = true; $("#wmd-ulist-button").append($("<i>").addClass("icon-list"));
runReadyCallbacks(); $("#wmd-heading-button").append($("<i>").addClass("icon-text-height"));
}; $("#wmd-hr-button").append($("<i>").addClass("icon-hr"));
function runReadyCallbacks() { $("#wmd-undo-button").append($("<i>").addClass("icon-undo"));
if(ready === true) { $("#wmd-redo-button").append($("<i>").addClass("icon-share-alt"));
_.each(readyCallbacks, function(callback) { };
callback();
});
readyCallbacks = [];
}
}
core.onReady(extensionMgr.onReady); // onReady event callbacks
core.onReady(function() { 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 = [];
}
}
// Load theme list core.onReady(extensionMgr.onReady);
_.each(THEME_LIST, function(name, value) { core.onReady(function() {
$("#input-settings-theme").append($('<option value="' + value + '">' + name + '</option>'));
});
// listen to online/offline events // Load theme list
$(window).on('offline', core.setOffline); _.each(THEME_LIST, function(name, value) {
$(window).on('online', setOnline); $("#input-settings-theme").append($('<option value="' + value + '">' + name + '</option>'));
if (navigator.onLine === false) { });
core.setOffline();
}
// Detect user activity // listen to online/offline events
$(document).mousemove(setUserActive).keypress(setUserActive); $(window).on('offline', core.setOffline);
$(window).on('online', setOnline);
if(navigator.onLine === false) {
core.setOffline();
}
// Avoid dropdown to close when clicking on submenu // Detect user activity
$(".dropdown-submenu > a").click(function(e) { $(document).mousemove(setUserActive).keypress(setUserActive);
e.stopPropagation();
});
// Click events on "insert link" and "insert image" dialog buttons // Avoid dropdown to close when clicking on submenu
$(".action-insert-link").click(function(e) { $(".dropdown-submenu > a").click(function(e) {
var value = utils.getInputTextValue($("#input-insert-link"), e); e.stopPropagation();
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 // Click events on "insert link" and "insert image" dialog buttons
$(".action-load-settings").click(function() { $(".action-insert-link").click(function(e) {
loadSettings(); var value = utils.getInputTextValue($("#input-insert-link"), e);
}); if(value !== undefined) {
$(".action-apply-settings").click(function(e) { insertLinkCallback(value);
saveSettings(e); }
if(!e.isPropagationStopped()) { });
window.location.reload(); $(".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);
});
$(".action-default-settings").click(function() { // Settings loading/saving
localStorage.removeItem("settings"); $(".action-load-settings").click(function() {
localStorage.removeItem("theme"); loadSettings();
window.location.reload(); });
}); $(".action-apply-settings").click(function(e) {
saveSettings(e);
if(!e.isPropagationStopped()) {
window.location.reload();
}
});
$(".action-app-reset").click(function() { $(".action-default-settings").click(function() {
localStorage.clear(); localStorage.removeItem("settings");
window.location.reload(); localStorage.removeItem("theme");
}); window.location.reload();
});
// UI layout $(".action-app-reset").click(function() {
$("#menu-bar, .ui-layout-center, .ui-layout-east, .ui-layout-south").removeClass("hide"); localStorage.clear();
core.createLayout(); window.location.reload();
});
// Editor's textarea // UI layout
$("#wmd-input, #md-section-helper").css({ $("#menu-bar, .ui-layout-center, .ui-layout-east, .ui-layout-south").removeClass("hide");
// Apply editor font size core.createLayout();
"font-size": settings.editorFontSize + "px",
"line-height": Math.round(settings.editorFontSize * (20/14)) + "px"
});
// Manage tab key // Editor's textarea
$("#wmd-input").keydown(function(e) { $("#wmd-input, #md-section-helper").css({
if(e.keyCode === 9) { // Apply editor font size
var value = $(this).val(); "font-size": settings.editorFontSize + "px",
var start = this.selectionStart; "line-height": Math.round(settings.editorFontSize * (20 / 14)) + "px"
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 // Manage tab key
$(".tooltip-scroll-link").tooltip({ $("#wmd-input").keydown(function(e) {
html: true, if(e.keyCode === 9) {
container: '#modal-settings', var value = $(this).val();
placement: 'right', var start = this.selectionStart;
title: ['Scroll Link is a feature that binds together editor and preview scrollbars. ', var end = this.selectionEnd;
'It allows you to keep an eye on the preview while scrolling the editor and vice versa. ', // IE8 does not support selection attributes
'<br><br>', if(start === undefined || end === undefined) {
'The mapping between Markdown and HTML is based on the position of the title elements (h1, h2, ...) in the page. ', return;
'Therefore, if your document does not contain any title, the mapping will be linear and consequently less efficient.', }
].join("") $(this).val(value.substring(0, start) + "\t" + value.substring(end));
}); this.selectionStart = this.selectionEnd = start + 1;
$(".tooltip-lazy-rendering").tooltip({ e.preventDefault();
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) { // Tooltips
$(".tooltip-template").tooltip('hide'); $(".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();
});
// Reset inputs $(document).click(function(e) {
$(".action-reset-input").click(function() { $(".tooltip-template").tooltip('hide');
utils.resetModalInputs(); });
});
// Do periodic tasks // Reset inputs
intervalId = window.setInterval(function() { $(".action-reset-input").click(function() {
utils.updateCurrentTime(); utils.resetModalInputs();
checkWindowUnique(); });
if(isUserActive() === true || viewerMode === true) {
_.each(periodicCallbacks, function(callback) {
callback();
});
checkOnline();
}
}, 1000);
});
return core; // Do periodic tasks
intervalId = window.setInterval(function() {
utils.updateCurrentTime();
checkWindowUnique();
if(isUserActive() === true || viewerMode === true) {
_.each(periodicCallbacks, function(callback) {
callback();
});
checkOnline();
}
}, 1000);
});
return core;
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,72 +1,78 @@
define([ define([
"jquery", "jquery",
"underscore" "underscore",
], function($, _) { "utils"
], function($, _, utils) {
var buttonStat = { var buttonStat = {
extensionId: "buttonStat", extensionId: "buttonStat",
extensionName: 'Button "Statistics"', extensionName: 'Button "Statistics"',
optional: true, optional: true,
settingsBloc: '<p>Adds a "Document statistics" button in the navigation bar.</p>' 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.onCreateButton = function() { buttonStat.onLoadSettings = function() {
return $([ _.each(buttonStat.defaultConfig, function(value, key) {
'<button class="btn dropdown-toggle" data-toggle="dropdown" title="Document statistics">', utils.setInputValue("#input-stat-" + key, buttonStat.config[key]);
'<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; buttonStat.onSaveSettings = function(newConfig, event) {
var lineTemplate = [ _.each(buttonStat.defaultConfig, function(value, key) {
'<div class="input-prepend">', newConfig[key] = utils.getInputTextValue("#input-stat-" + key, event);
'<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(); buttonStat.onCreateButton = function() {
$("#link-container .no-link").show(); 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(""));
};
var attributesList = _.values(fileDesc.publishLocations); buttonStat.onPreviewFinished = function() {
_.each(attributesList, function(attributes) { var text = $("#wmd-preview").text();
if(attributes.sharingLink) { $("#span-stat-value1").text(text.match(new RegExp(buttonStat.config.value1, "g")).length);
var lineElement = $(_.template(lineTemplate, { $("#span-stat-value2").text(text.match(new RegExp(buttonStat.config.value2, "g")).length);
link: attributes.sharingLink $("#span-stat-value3").text(text.match(new RegExp(buttonStat.config.value3, "g")).length);
})); };
lineElement.click(function(event) {
event.stopPropagation();
});
linkList.append(lineElement);
$("#link-container .no-link").hide();
}
});
};
buttonStat.onFileSelected = function(fileDescParameter) { return buttonStat;
fileDesc = fileDescParameter;
refreshDocumentSharing(fileDescParameter);
};
buttonStat.onNewPublishSuccess = refreshDocumentSharing;
buttonStat.onPublishRemoved = refreshDocumentSharing;
return buttonStat;
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,47 +3,37 @@ define([
"tumblr-helper" "tumblr-helper"
], function(utils, tumblrHelper) { ], function(utils, tumblrHelper) {
var PROVIDER_TUMBLR = "tumblr"; var PROVIDER_TUMBLR = "tumblr";
var tumblrProvider = { var tumblrProvider = {
providerId: PROVIDER_TUMBLR, providerId: PROVIDER_TUMBLR,
providerName: "Tumblr", providerName: "Tumblr",
publishPreferencesInputIds: ["tumblr-hostname"] publishPreferencesInputIds: [
}; "tumblr-hostname"
]
};
tumblrProvider.publish = function(publishAttributes, title, content, callback) { tumblrProvider.publish = function(publishAttributes, title, content, callback) {
tumblrHelper.upload( tumblrHelper.upload(publishAttributes.blogHostname, publishAttributes.postId, publishAttributes.tags, publishAttributes.format == "markdown" ? "markdown" : "html", title, content, function(error, postId) {
publishAttributes.blogHostname, if(error) {
publishAttributes.postId, callback(error);
publishAttributes.tags, return;
publishAttributes.format == "markdown" ? "markdown" : "html", }
title, publishAttributes.postId = postId;
content, callback();
function(error, postId) { });
if(error) { };
callback(error);
return;
}
publishAttributes.postId = postId;
callback();
}
);
};
tumblrProvider.newPublishAttributes = function(event) { tumblrProvider.newPublishAttributes = function(event) {
var publishAttributes = {}; var publishAttributes = {};
publishAttributes.blogHostname = utils 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])$/);
.getInputTextValue( publishAttributes.postId = utils.getInputTextValue("#input-publish-postid");
"#input-publish-tumblr-hostname", publishAttributes.tags = utils.getInputTextValue("#input-publish-tags");
event, if(event.isPropagationStopped()) {
/^(([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])$/); return undefined;
publishAttributes.postId = utils.getInputTextValue("#input-publish-postid"); }
publishAttributes.tags = utils.getInputTextValue("#input-publish-tags"); return publishAttributes;
if(event.isPropagationStopped()) { };
return undefined;
}
return publishAttributes;
};
return tumblrProvider; return tumblrProvider;
}); });

View File

@ -4,310 +4,517 @@ define([
"lib/FileSaver" "lib/FileSaver"
], function($, _) { ], function($, _) {
var utils = {}; var utils = {};
// Return a parameter from the URL // Return a parameter from the URL
utils.getURLParameter = function(name) { utils.getURLParameter = function(name) {
var regex = new RegExp(name + "=(.+?)(&|$)"); var regex = new RegExp(name + "=(.+?)(&|$)");
try { try {
return decodeURIComponent(regex.exec(location.search)[1]); return decodeURIComponent(regex.exec(location.search)[1]);
} catch (e) { }
return undefined; catch (e) {
} return undefined;
}; }
};
// Transform a selector into a jQuery object // Transform a selector into a jQuery object
function jqElt(element) { function jqElt(element) {
if(_.isString(element)) { if(_.isString(element)) {
return $(element); return $(element);
} }
return element; return element;
} }
// For input control // For input control
function inputError(element, event) { function inputError(element, event) {
if(event !== undefined) { if(event !== undefined) {
element.stop(true, true).addClass("error").delay(1000).switchClass("error"); element.stop(true, true).addClass("error").delay(1000).switchClass("error");
event.stopPropagation(); event.stopPropagation();
} }
} }
// Return input value // Return input value
utils.getInputValue = function(element) { utils.getInputValue = function(element) {
element = jqElt(element); element = jqElt(element);
return element.val(); return element.val();
}; };
// Set input value // Set input value
utils.setInputValue = function(element, value) { utils.setInputValue = function(element, value) {
element = jqElt(element); element = jqElt(element);
element.val(value); element.val(value);
}; };
// Return input text value // Return input text value
utils.getInputTextValue = function(element, event, validationRegex) { utils.getInputTextValue = function(element, event, validationRegex) {
element = jqElt(element); element = jqElt(element);
var value = element.val(); var value = element.val();
if (value === undefined) { if(value === undefined) {
inputError(element, event); inputError(element, event);
return undefined; return undefined;
} }
// trim // trim
value = utils.trim(value); value = utils.trim(value);
if((value.length === 0) if((value.length === 0) || (validationRegex !== undefined && !value.match(validationRegex))) {
|| (validationRegex !== undefined && !value.match(validationRegex))) { inputError(element, event);
inputError(element, event); return undefined;
return undefined; }
} return value;
return value; };
};
// Return input integer value // Return input integer value
utils.getInputIntValue = function(element, event, min, max) { utils.getInputIntValue = function(element, event, min, max) {
element = jqElt(element); element = jqElt(element);
var value = utils.getInputTextValue(element, event); var value = utils.getInputTextValue(element, event);
if(value === undefined) { if(value === undefined) {
return undefined; return undefined;
} }
value = parseInt(value); value = parseInt(value);
if((value === NaN) if((value === NaN) || (min !== undefined && value < min) || (max !== undefined && value > max)) {
|| (min !== undefined && value < min) inputError(element, event);
|| (max !== undefined && value > max)) { return undefined;
inputError(element, event); }
return undefined; return value;
} };
return value;
};
// Return checkbox boolean value // Return checkbox boolean value
utils.getInputChecked = function(element) { utils.getInputChecked = function(element) {
element = jqElt(element); element = jqElt(element);
return element.prop("checked"); return element.prop("checked");
}; };
// Set checkbox state // Set checkbox state
utils.setInputChecked = function(element, checked) { utils.setInputChecked = function(element, checked) {
element = jqElt(element); element = jqElt(element);
element.prop("checked", checked); element.prop("checked", checked);
}; };
// Get radio button value // Get radio button value
utils.getInputRadio = function(name) { utils.getInputRadio = function(name) {
return $("input:radio[name=" + name + "]:checked").prop("value"); return $("input:radio[name=" + name + "]:checked").prop("value");
}; };
// Set radio button value // Set radio button value
utils.setInputRadio = function(name, value) { utils.setInputRadio = function(name, value) {
$("input:radio[name=" + name + "][value=" + value + "]").prop("checked", true); $("input:radio[name=" + name + "][value=" + value + "]").prop("checked", true);
}; };
// Reset input control in all modals // Reset input control in all modals
utils.resetModalInputs = function() { utils.resetModalInputs = function() {
$(".modal input[type=text]:not([disabled]), .modal input[type=password]").val(""); $(".modal input[type=text]:not([disabled]), .modal input[type=password]").val("");
}; };
// Basic trim function // Basic trim function
utils.trim = function(str) { utils.trim = function(str) {
return $.trim(str); return $.trim(str);
}; };
// Slug function // Slug function
utils.slugify = function(text) { utils.slugify = function(text) {
return text.toLowerCase() return text.toLowerCase().replace(/\s+/g, '-') // Replace spaces with -
.replace(/\s+/g, '-') // Replace spaces with - .replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/[^\w\-]+/g, '') // Remove all non-word chars .replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/\-\-+/g, '-') // Replace multiple - with single - .replace(/^-+/, '') // Trim - from start of text
.replace(/^-+/, '') // Trim - from start of text .replace(/-+$/, ''); // Trim - from end of text
.replace(/-+$/, ''); // Trim - from end of text };
};
// Check an URL // Check an URL
utils.checkUrl = function(url, addSlash) { utils.checkUrl = function(url, addSlash) {
if(!url) { if(!url) {
return url; return url;
} }
if(url.indexOf("http") !== 0) { if(url.indexOf("http") !== 0) {
url = "http://" + url; url = "http://" + url;
} }
if(addSlash && url.indexOf("/", url.length - 1) === -1) { if(addSlash && url.indexOf("/", url.length - 1) === -1) {
url += "/"; url += "/";
} }
return url; return url;
}; };
// Base64 conversion // Create an centered popup window
utils.encodeBase64 = function(str) { utils.popupWindow = function(url, title, width, height) {
if (str.length === 0) { var left = (screen.width / 2) - (width / 2);
return ""; 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(""));
};
// UTF-8 to byte array // Export data on disk
var bytes = [], offset = 0, length, char; 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');
}
};
str = encodeURI(str); // Generates a random string
length = str.length; utils.randomString = function() {
return _.random(4294967296).toString(36);
};
while (offset < length) { // Time shared by others modules
char = str[offset]; utils.updateCurrentTime = function() {
offset += 1; utils.currentTime = new Date().getTime();
};
utils.updateCurrentTime();
if ('%' !== char) { // Serialize sync/publish attributes and store it in the fileStorage
bytes.push(char.charCodeAt(0)); utils.storeAttributes = function(attributes) {
} else { var storeIndex = attributes.syncIndex || attributes.publishIndex;
char = str[offset] + str[offset + 1]; // Don't store sync/publish index
bytes.push(parseInt(char, 16)); attributes = _.omit(attributes, "syncIndex", "publishIndex");
offset += 2; // Store providerId instead of provider
} attributes.provider = attributes.provider.providerId;
} localStorage[storeIndex] = JSON.stringify(attributes);
};
// byte array to base64 // Base64 conversion
var padchar = '='; utils.encodeBase64 = function(str) {
var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; if(str.length === 0) {
return "";
}
var i, b10; // UTF-8 to byte array
var x = []; var bytes = [], offset = 0, length, char;
var imax = bytes.length - bytes.length % 3; str = encodeURI(str);
length = str.length;
for (i = 0; i < imax; i += 3) { while (offset < length) {
b10 = (bytes[i] << 16) | (bytes[i+1] << 8) | bytes[i+2]; char = str[offset];
x.push(alpha.charAt(b10 >> 18)); offset += 1;
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 if('%' !== char) {
var mHash = [ 0, 1996959894, 3993919788, 2567524794, 124634137, bytes.push(char.charCodeAt(0));
1886057615, 3915621685, 2657392035, 249268274, 2044508324, }
3772115230, 2547177864, 162941995, 2125561021, 3887607047, else {
2428444049, 498536548, 1789927666, 4089016648, 2227061214, char = str[offset] + str[offset + 1];
450548861, 1843258603, 4107580753, 2211677639, 325883990, bytes.push(parseInt(char, 16));
1684777152, 4251122042, 2321926636, 335633487, 1661365465, offset += 2;
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 // byte array to base64
utils.popupWindow = function(url, title, width, height) { var padchar = '=';
var left = (screen.width / 2) - (width / 2); var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
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 var i, b10;
utils.saveAs = function(content, filename) { var x = [];
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 var imax = bytes.length - bytes.length % 3;
utils.randomString = function() {
return _.random(4294967296).toString(36);
};
// Time shared by others modules for (i = 0; i < imax; i += 3) {
utils.updateCurrentTime = function() { b10 = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
utils.currentTime = new Date().getTime(); x.push(alpha.charAt(b10 >> 18));
}; x.push(alpha.charAt((b10 >> 12) & 0x3F));
utils.updateCurrentTime(); 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);
};
// Serialize sync/publish attributes and store it in the fileStorage return utils;
utils.storeAttributes = function(attributes) {
var storeIndex = attributes.syncIndex || attributes.publishIndex;
// Don't store sync/publish index
attributes = _.omit(attributes, "syncIndex", "publishIndex");
// Store providerId instead of provider
attributes.provider = attributes.provider.providerId;
localStorage[storeIndex] = JSON.stringify(attributes);
};
return utils;
}); });

View File

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

View File

@ -3,47 +3,38 @@ define([
"wordpress-helper" "wordpress-helper"
], function(utils, wordpressHelper) { ], function(utils, wordpressHelper) {
var PROVIDER_WORDPRESS = "wordpress"; var PROVIDER_WORDPRESS = "wordpress";
var wordpressProvider = { var wordpressProvider = {
providerId: PROVIDER_WORDPRESS, providerId: PROVIDER_WORDPRESS,
providerName: "WordPress", providerName: "WordPress",
defaultPublishFormat: "html", defaultPublishFormat: "html",
publishPreferencesInputIds: ["wordpress-site"] publishPreferencesInputIds: [
}; "wordpress-site"
]
};
wordpressProvider.publish = function(publishAttributes, title, content, callback) { wordpressProvider.publish = function(publishAttributes, title, content, callback) {
wordpressHelper.upload( wordpressHelper.upload(publishAttributes.site, publishAttributes.postId, publishAttributes.tags, title, content, function(error, postId) {
publishAttributes.site, if(error) {
publishAttributes.postId, callback(error);
publishAttributes.tags, return;
title, }
content, publishAttributes.postId = postId;
function(error, postId) { callback();
if(error) { });
callback(error); };
return;
}
publishAttributes.postId = postId;
callback();
}
);
};
wordpressProvider.newPublishAttributes = function(event) { wordpressProvider.newPublishAttributes = function(event) {
var publishAttributes = {}; var publishAttributes = {};
publishAttributes.site = utils 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])$/);
.getInputTextValue( publishAttributes.postId = utils.getInputTextValue("#input-publish-postid");
"#input-publish-wordpress-site", publishAttributes.tags = utils.getInputTextValue("#input-publish-tags");
event, if(event.isPropagationStopped()) {
/^(([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])$/); return undefined;
publishAttributes.postId = utils.getInputTextValue("#input-publish-postid"); }
publishAttributes.tags = utils.getInputTextValue("#input-publish-tags"); return publishAttributes;
if(event.isPropagationStopped()) { };
return undefined;
}
return publishAttributes;
};
return wordpressProvider; return wordpressProvider;
}); });

View File

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