diff --git a/css/default.css b/css/default.css index 00f7dcc0..2c2abfad 100644 --- a/css/default.css +++ b/css/default.css @@ -419,6 +419,13 @@ div.dropdown-menu textarea { margin-top: 10px; } +.footnote { + vertical-align: top; + position: relative; + top: -0.5em; + font-size: 0.8em; +} + .icon-link { background-position: -72px -168px; } @@ -642,6 +649,10 @@ div.dropdown-menu textarea { text-align: left; } +.tooltip li { + line-height: 1.4; +} + /* Definition list */ dt,dd { margin-top: 5px; diff --git a/doc/img/architecture.png b/doc/img/architecture.png index 56b2ba47..6bc437f3 100644 Binary files a/doc/img/architecture.png and b/doc/img/architecture.png differ diff --git a/js/classes/Extension.js b/js/classes/Extension.js new file mode 100644 index 00000000..d0e8be4f --- /dev/null +++ b/js/classes/Extension.js @@ -0,0 +1,10 @@ +define(function() { + + function Extension(extensionId, extensionName, isOptional) { + this.extensionId = extensionId; + this.extensionName = extensionName; + this.isOptional = isOptional; + } + + return Extension; +}); \ No newline at end of file diff --git a/js/classes/Provider.js b/js/classes/Provider.js new file mode 100644 index 00000000..98523dd8 --- /dev/null +++ b/js/classes/Provider.js @@ -0,0 +1,9 @@ +define(function() { + + function Provider(providerId, providerName) { + this.providerId = providerId; + this.providerName = providerName; + } + + return Provider; +}); \ No newline at end of file diff --git a/js/core.js b/js/core.js index 21bc1821..1aad1911 100644 --- a/js/core.js +++ b/js/core.js @@ -461,6 +461,28 @@ define([ } }); + // Reset inputs + $(".action-reset-input").click(function() { + utils.resetModalInputs(); + }); + + // Do periodic tasks + intervalId = window.setInterval(function() { + utils.updateCurrentTime(); + checkWindowUnique(); + if(isUserActive() === true || viewerMode === true) { + _.each(periodicCallbacks, function(callback) { + callback(); + }); + checkOnline(); + } + }, 1000); + }); + core.onReady(extensionMgr.onReady); + + // After extensions onReady callbacks + core.onReady(function() { + // Tooltips $(".tooltip-lazy-rendering").tooltip({ container: '#modal-settings', @@ -490,24 +512,7 @@ define([ e.stopPropagation(); }); - // Reset inputs - $(".action-reset-input").click(function() { - utils.resetModalInputs(); - }); - - // Do periodic tasks - intervalId = window.setInterval(function() { - utils.updateCurrentTime(); - checkWindowUnique(); - if(isUserActive() === true || viewerMode === true) { - _.each(periodicCallbacks, function(callback) { - callback(); - }); - checkOnline(); - } - }, 1000); }); - core.onReady(extensionMgr.onReady); return core; }); diff --git a/js/extensionMgr.js b/js/extensionMgr.js index e5aa1b6a..40c27ca6 100644 --- a/js/extensionMgr.js +++ b/js/extensionMgr.js @@ -2,6 +2,7 @@ define([ "jquery", "underscore", "utils", + "classes/Extension", "settings", "text!html/settingsExtensionsAccordion.html", "extensions/googleAnalytics", @@ -20,20 +21,21 @@ define([ "extensions/documentTitle", "extensions/workingIndicator", "extensions/notifications", - "extensions/markdown-extra", + "extensions/markdownExtra", + "extensions/markdownFootnotes", "extensions/toc", "extensions/mathJax", "extensions/emailConverter", "extensions/scrollLink", "libs/bootstrap", "libs/jquery.waitforimages" -], function($, _, utils, settings, settingsExtensionsAccordionHTML) { +], function($, _, utils, Extension, settings, settingsExtensionsAccordionHTML) { var extensionMgr = {}; // Create a list of extensions var extensionList = _.chain(arguments).map(function(argument) { - return _.isObject(argument) && argument.extensionId && argument; + return argument instanceof Extension && argument; }).compact().value(); // Return every named callbacks implemented in extensions @@ -66,7 +68,7 @@ define([ extensionSettings = settings.extensionSettings || {}; _.each(extensionList, function(extension) { extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]); - extension.config.enabled = !extension.optional || extension.config.enabled === undefined || extension.config.enabled === true; + extension.config.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true; }); // Load/Save extension config from/to settings @@ -152,8 +154,8 @@ define([ $("#accordion-extensions").append($(_.template(settingsExtensionsAccordionHTML, { extensionId: extension.extensionId, extensionName: extension.extensionName, - optional: extension.optional, - settingsBloc: extension.settingsBloc + isOptional: extension.isOptional, + settingsBlock: extension.settingsBlock }))); } diff --git a/js/extensions/buttonHtmlCode.js b/js/extensions/buttonHtmlCode.js index a4d46e32..f0e2e44f 100644 --- a/js/extensions/buttonHtmlCode.js +++ b/js/extensions/buttonHtmlCode.js @@ -1,21 +1,48 @@ define([ "jquery", + "underscore", + "utils", + "classes/Extension", "text!html/buttonHtmlCode.html", -], function($, buttonHtmlCodeHTML) { + "text!html/buttonHtmlCodeSettingsBlock.html", +], function($, _, utils, Extension, buttonHtmlCodeHTML, buttonHtmlCodeSettingsBlockHTML) { - var buttonHtmlCode = { - extensionId: "buttonHtmlCode", - extensionName: 'Button "HTML code"', - optional: true, - settingsBloc: '

Adds a "HTML code" button over the preview.

' + var buttonHtmlCode = new Extension("buttonHtmlCode", 'Button "HTML code"', true); + buttonHtmlCode.settingsBlock = buttonHtmlCodeSettingsBlockHTML; + buttonHtmlCode.defaultConfig = { + template: "<%= documentHTML %>", + }; + + buttonHtmlCode.onLoadSettings = function() { + utils.setInputValue("#textarea-html-code-template", buttonHtmlCode.config.template); + }; + + buttonHtmlCode.onSaveSettings = function(newConfig, event) { + newConfig.template = utils.getInputValue("#textarea-html-code-template"); }; buttonHtmlCode.onCreatePreviewButton = function() { return $(buttonHtmlCodeHTML); }; + var selectedFileDesc = undefined; + buttonHtmlCode.onFileSelected = function(fileDesc) { + selectedFileDesc = fileDesc; + }; + buttonHtmlCode.onPreviewFinished = function() { - $("#input-html-code").val($("#wmd-preview").html()); + try { + var htmlCode = _.template(buttonHtmlCode.config.template, { + documentTitle: selectedFileDesc.title, + documentMarkdown: selectedFileDesc.content, + documentHTML: $("#wmd-preview").html() + }); + $("#input-html-code").val(htmlCode); + } + catch(e) { + extensionMgr.onError(e); + return e.message; + } }; buttonHtmlCode.onReady = function() { diff --git a/js/extensions/buttonMarkdownSyntax.js b/js/extensions/buttonMarkdownSyntax.js index 2e766afc..4480f9db 100644 --- a/js/extensions/buttonMarkdownSyntax.js +++ b/js/extensions/buttonMarkdownSyntax.js @@ -1,14 +1,11 @@ define([ "jquery", + "classes/Extension", "text!html/buttonMarkdownSyntax.html", -], function($, buttonMarkdownSyntaxHTML) { +], function($, Extension, buttonMarkdownSyntaxHTML) { - var buttonMarkdownSyntax = { - extensionId: "buttonMarkdownSyntax", - extensionName: 'Button "Markdown syntax"', - optional: true, - settingsBloc: '

Adds a "Markdown syntax" button over the preview.

' - }; + var buttonMarkdownSyntax = new Extension("buttonMarkdownSyntax", 'Button "Markdown syntax', true); + buttonMarkdownSyntax.settingsBlock = '

Adds a "Markdown syntax" button over the preview.

'; buttonMarkdownSyntax.onCreatePreviewButton = function() { return $(buttonMarkdownSyntaxHTML); diff --git a/js/extensions/buttonPublish.js b/js/extensions/buttonPublish.js index d86dc403..3ed9af77 100644 --- a/js/extensions/buttonPublish.js +++ b/js/extensions/buttonPublish.js @@ -1,14 +1,12 @@ define([ "jquery", "underscore", + "classes/Extension", "text!html/buttonPublish.html", -], function($, _, buttonPublishHTML) { +], function($, _, Extension, buttonPublishHTML) { - var buttonPublish = { - extensionId: "buttonPublish", - extensionName: 'Button "Publish"', - settingsBloc: '

Adds a "Publish document" button in the navigation bar.

' - }; + var buttonPublish = new Extension("buttonPublish", 'Button "Publish"'); + buttonPublish.settingsBlock = '

Adds a "Publish document" button in the navigation bar.

'; var button = undefined; var currentFileDesc = undefined; diff --git a/js/extensions/buttonShare.js b/js/extensions/buttonShare.js index 9ef0b061..811834c9 100644 --- a/js/extensions/buttonShare.js +++ b/js/extensions/buttonShare.js @@ -1,16 +1,13 @@ define([ "jquery", "underscore", + "classes/Extension", "text!html/buttonShare.html", "text!html/buttonShareLocation.html", -], function($, _, buttonShareHTML, buttonShareLocationHTML) { +], function($, _, Extension, buttonShareHTML, buttonShareLocationHTML) { - var buttonShare = { - extensionId: "buttonShare", - extensionName: 'Button "Share"', - optional: true, - settingsBloc: '

Adds a "Share document" button in the navigation bar.

' - }; + var buttonShare = new Extension("buttonShare", 'Button "Share"', true); + buttonShare.settingsBlock = '

Adds a "Share document" button in the navigation bar.

'; buttonShare.onCreateButton = function() { return $(buttonShareHTML); diff --git a/js/extensions/buttonStat.js b/js/extensions/buttonStat.js index 1ca172f5..88c718c2 100644 --- a/js/extensions/buttonStat.js +++ b/js/extensions/buttonStat.js @@ -2,23 +2,20 @@ define([ "jquery", "underscore", "utils", + "classes/Extension", "text!html/buttonStat.html", - "text!html/buttonStatSettingsBloc.html", -], function($, _, utils, buttonStatHTML, buttonStatSettingsBlocHTML) { + "text!html/buttonStatSettingsBlock.html", +], function($, _, utils, Extension, buttonStatHTML, buttonStatSettingsBlockHTML) { - var buttonStat = { - extensionId: "buttonStat", - extensionName: 'Button "Statistics"', - optional: true, - defaultConfig: { - name1: "Characters", - value1: "\\S", - name2: "Words", - value2: "\\S+", - name3: "Paragraphs", - value3: "\\S.*", - }, - settingsBloc: buttonStatSettingsBlocHTML + var buttonStat = new Extension("buttonStat", 'Button "Statistics"', true); + buttonStat.settingsBlock = buttonStatSettingsBlockHTML; + buttonStat.defaultConfig = { + name1: "Characters", + value1: "\\S", + name2: "Words", + value2: "\\S+", + name3: "Paragraphs", + value3: "\\S.*", }; buttonStat.onLoadSettings = function() { diff --git a/js/extensions/buttonSync.js b/js/extensions/buttonSync.js index 65cd522f..e17ff00c 100644 --- a/js/extensions/buttonSync.js +++ b/js/extensions/buttonSync.js @@ -2,17 +2,15 @@ define([ "jquery", "underscore", "utils", + "classes/Extension", "text!html/buttonSync.html", - "text!html/buttonSyncSettingsBloc.html", -], function($, _, utils, buttonSyncHTML, buttonSyncSettingsBlocHTML) { + "text!html/buttonSyncSettingsBlock.html", +], function($, _, utils, Extension, buttonSyncHTML, buttonSyncSettingsBlockHTML) { - var buttonSync = { - extensionId: "buttonSync", - extensionName: 'Button "Synchronize"', - defaultConfig: { - syncPeriod: 180000 - }, - settingsBloc: buttonSyncSettingsBlocHTML + var buttonSync = new Extension("buttonSync", 'Button "Synchronize"'); + buttonSync.settingsBlock = buttonSyncSettingsBlockHTML; + buttonSync.defaultConfig = { + syncPeriod: 180000 }; buttonSync.onLoadSettings = function() { diff --git a/js/extensions/buttonViewer.js b/js/extensions/buttonViewer.js index ae2e7d5d..db3be4c6 100644 --- a/js/extensions/buttonViewer.js +++ b/js/extensions/buttonViewer.js @@ -1,14 +1,11 @@ define([ "jquery", + "classes/Extension", "text!html/buttonViewer.html", -], function($, buttonViewerHTML) { +], function($, Extension, buttonViewerHTML) { - var buttonViewer = { - extensionId: "buttonViewer", - extensionName: 'Button "Viewer"', - optional: true, - settingsBloc: '

Adds a "Viewer" button over the preview.

' - }; + var buttonViewer = new Extension("buttonViewer", 'Button "Viewer"', true); + buttonViewer.settingsBlock = '

Adds a "Viewer" button over the preview.

'; buttonViewer.onCreatePreviewButton = function() { return $(buttonViewerHTML); diff --git a/js/extensions/dialogAbout.js b/js/extensions/dialogAbout.js index 425cd4ca..f476f5e8 100644 --- a/js/extensions/dialogAbout.js +++ b/js/extensions/dialogAbout.js @@ -1,14 +1,12 @@ define([ "jquery", "underscore", + "classes/Extension", "text!html/dialogAbout.html", -], function($, _, dialogAboutHTML) { +], function($, _, Extension, dialogAboutHTML) { - var dialogAbout = { - extensionId: "dialogAbout", - extensionName: 'Dialog "About"', - settingsBloc: '

Prints the content of the "About" dialog box.

' - }; + var dialogAbout = new Extension("dialogAbout", 'Dialog "About"'); + dialogAbout.settingsBlock = '

Prints the content of the "About" dialog box.

'; var libraries = { "Bootstrap": "http://twitter.github.io/bootstrap/", diff --git a/js/extensions/dialogManagePublication.js b/js/extensions/dialogManagePublication.js index cc6fe1c4..44aea9a7 100644 --- a/js/extensions/dialogManagePublication.js +++ b/js/extensions/dialogManagePublication.js @@ -1,14 +1,12 @@ define([ "jquery", "underscore", + "classes/Extension", "text!html/dialogManagePublicationLocation.html", -], function($, _, dialogManagePublicationLocationHTML) { +], function($, _, Extension, dialogManagePublicationLocationHTML) { - var dialogManagePublication = { - extensionId: "dialogManagePublication", - extensionName: 'Dialog "Manage publication"', - settingsBloc: '

Populates the "Manage publication" dialog box.

' - }; + var dialogManagePublication = new Extension("dialogManagePublication", 'Dialog "Manage publication"'); + dialogManagePublication.settingsBlock = '

Populates the "Manage publication" dialog box.

'; var extensionMgr = undefined; dialogManagePublication.onExtensionMgrCreated = function(extensionMgrParameter) { diff --git a/js/extensions/dialogManageSynchronization.js b/js/extensions/dialogManageSynchronization.js index eb1baf1a..d5c0fc5e 100644 --- a/js/extensions/dialogManageSynchronization.js +++ b/js/extensions/dialogManageSynchronization.js @@ -1,14 +1,12 @@ define([ "jquery", "underscore", + "classes/Extension", "text!html/dialogManageSynchronizationLocation.html", -], function($, _, dialogManageSynchronizationLocationHTML) { +], function($, _, Extension, dialogManageSynchronizationLocationHTML) { - var dialogManageSynchronization = { - extensionId: "dialogManageSynchronization", - extensionName: 'Dialog "Manage synchronization"', - settingsBloc: '

Populates the "Manage synchronization" dialog box.

' - }; + var dialogManageSynchronization = new Extension("dialogManageSynchronization", 'Dialog "Manage synchronization"'); + dialogManageSynchronization.settingsBlock = '

Populates the "Manage synchronization" dialog box.

'; var extensionMgr = undefined; dialogManageSynchronization.onExtensionMgrCreated = function(extensionMgrParameter) { diff --git a/js/extensions/dialogOpenHarddrive.js b/js/extensions/dialogOpenHarddrive.js index 5b617b05..b67511c6 100644 --- a/js/extensions/dialogOpenHarddrive.js +++ b/js/extensions/dialogOpenHarddrive.js @@ -2,15 +2,13 @@ define([ "jquery", "underscore", "utils", + "classes/Extension", "toMarkdown", "config", -], function($, _, utils, toMarkdown) { +], function($, _, utils, Extension, toMarkdown) { - var dialogOpenHarddrive = { - extensionId: "dialogOpenHarddrive", - extensionName: 'Dialog "Open from"', - settingsBloc: '

Handles the "Import from hard drive" and the "Convert HTML to Markdown" dialog boxes.

' - }; + var dialogOpenHarddrive = new Extension("dialogOpenHarddrive", 'Dialog "Open from"'); + dialogOpenHarddrive.settingsBlock = '

Handles the "Import from hard drive" and the "Convert HTML to Markdown" dialog boxes.

'; var fileMgr = undefined; dialogOpenHarddrive.onFileMgrCreated = function(fileMgrParameter) { diff --git a/js/extensions/documentSelector.js b/js/extensions/documentSelector.js index e696a426..1bccad5b 100644 --- a/js/extensions/documentSelector.js +++ b/js/extensions/documentSelector.js @@ -2,20 +2,18 @@ define([ "jquery", "underscore", "utils", + "classes/Extension", "mousetrap", "fileSystem", - "text!html/documentSelectorSettingsBloc.html", -], function($, _, utils, mousetrap, fileSystem, documentSelectorSettingsBlocHTML) { + "text!html/documentSelectorSettingsBlock.html", +], function($, _, utils, Extension, mousetrap, fileSystem, documentSelectorSettingsBlockHTML) { - var documentSelector = { - extensionId: "documentSelector", - extensionName: "Document Selector", - defaultConfig: { - orderBy: "title", - shortcutPrevious: "Ctrl+[", - shortcutNext: "Ctrl+]" - }, - settingsBloc: documentSelectorSettingsBlocHTML + var documentSelector = new Extension("documentSelector", 'Document Selector'); + documentSelector.settingsBlock = documentSelectorSettingsBlockHTML; + documentSelector.defaultConfig = { + orderBy: "title", + shortcutPrevious: "Ctrl+[", + shortcutNext: "Ctrl+]" }; documentSelector.onLoadSettings = function() { @@ -148,7 +146,8 @@ define([ }); // Handle key shortcut - mousetrap.bind(documentSelector.config.shortcutPrevious.toLowerCase(), function() { + var shortcutPrevious = documentSelector.config.shortcutPrevious.toLowerCase(); + mousetrap.bind(shortcutPrevious, function() { if(shortcutLi === undefined) { $("#file-selector").parent().is(".open") || $(".action-open-file").click(); shortcutLi = liMap[selectFileDesc.fileIndex]; @@ -163,6 +162,7 @@ define([ }); return false; }); + var shortcutNext = documentSelector.config.shortcutNext.toLowerCase(); mousetrap.bind(documentSelector.config.shortcutNext.toLowerCase(), function() { if(shortcutLi === undefined) { $("#file-selector").parent().is(".open") || $(".action-open-file").click(); @@ -175,7 +175,14 @@ define([ }); return false; }); - mousetrap.bind('ctrl', function() { + var delimiter1 = shortcutPrevious.indexOf("+"); + var shortcutSelect1 = delimiter1 === -1 ? shortcutPrevious : shortcutPrevious.substring(0, delimiter1); + var delimiter2 = shortcutNext.indexOf("+"); + var shortcutSelect2 = delimiter2 === -1 ? shortcutNext : shortcutNext.substring(0, delimiter2); + mousetrap.bind([ + shortcutSelect1, + shortcutSelect2 + ], function() { if(shortcutLi !== undefined) { shortcutLi.find("a").click(); shortcutLi = undefined; diff --git a/js/extensions/documentTitle.js b/js/extensions/documentTitle.js index 85fe9910..c742dc29 100644 --- a/js/extensions/documentTitle.js +++ b/js/extensions/documentTitle.js @@ -1,13 +1,11 @@ define([ "jquery", - "underscore" -], function($, _) { + "underscore", + "classes/Extension", +], function($, _, Extension) { - var documentTitle = { - extensionId: "documentTitle", - extensionName: "Document Title", - settingsBloc: '

Responsible for showing the document title in the navigation bar.

' - }; + var documentTitle = new Extension("documentTitle", "Document Title"); + documentTitle.settingsBlock = '

Responsible for showing the document title in the navigation bar.

'; var layout = undefined; documentTitle.onLayoutCreated = function(layoutParameter) { diff --git a/js/extensions/emailConverter.js b/js/extensions/emailConverter.js index f8206757..7d85a704 100644 --- a/js/extensions/emailConverter.js +++ b/js/extensions/emailConverter.js @@ -1,11 +1,9 @@ -define(function() { +define([ + "classes/Extension", +], function(Extension) { - var emailConverter = { - extensionId: "emailConverter", - extensionName: "Email Converter", - optional: true, - settingsBloc: '

Converts email adresses in the form <email@example.com> into clickable links.

' - }; + var emailConverter = new Extension("emailConverter", "Markdown Email", true); + emailConverter.settingsBlock = '

Converts email adresses in the form <email@example.com> into clickable links.

'; emailConverter.onEditorConfigure = function(editor) { editor.getConverter().hooks.chain("postConversion", function(text) { diff --git a/js/extensions/googleAnalytics.js b/js/extensions/googleAnalytics.js index 52cbdea3..150ab379 100644 --- a/js/extensions/googleAnalytics.js +++ b/js/extensions/googleAnalytics.js @@ -2,16 +2,13 @@ define([ "jquery", "underscore", "utils", + "classes/Extension", "settings", "config", -], function($, _, utils, settings) { +], function($, _, utils, Extension, settings) { - var googleAnalytics = { - extensionId: "googleAnalytics", - extensionName: 'Google Analytics', - optional: true, - settingsBloc: '

Sends anonymous statistics about usage and errors to help improve StackEdit.

' - }; + var googleAnalytics = new Extension("googleAnalytics", "Google Analytics", true); + googleAnalytics.settingsBlock = '

Sends anonymous statistics about usage and errors to help improve StackEdit.

'; var isLoaded = false; var isOffline = false; diff --git a/js/extensions/markdown-extra.js b/js/extensions/markdownExtra.js similarity index 68% rename from js/extensions/markdown-extra.js rename to js/extensions/markdownExtra.js index 76c8407a..de484250 100644 --- a/js/extensions/markdown-extra.js +++ b/js/extensions/markdownExtra.js @@ -1,17 +1,14 @@ define([ "utils", - "text!html/markdownExtraSettingsBloc.html", + "classes/Extension", + "text!html/markdownExtraSettingsBlock.html", "libs/Markdown.Extra", -], function(utils, markdownExtraSettingsBlocHTML) { +], function(utils, Extension, markdownExtraSettingsBlockHTML) { - var markdownExtra = { - extensionId: "markdownExtra", - extensionName: "Markdown Extra", - optional: true, - defaultConfig: { - prettify: true - }, - settingsBloc: markdownExtraSettingsBlocHTML + var markdownExtra = new Extension("markdownExtra", "Markdown Extra", true); + markdownExtra.settingsBlock = markdownExtraSettingsBlockHTML; + markdownExtra.defaultConfig = { + prettify: true }; markdownExtra.onLoadSettings = function() { diff --git a/js/extensions/markdownFootnotes.js b/js/extensions/markdownFootnotes.js new file mode 100644 index 00000000..46f0714e --- /dev/null +++ b/js/extensions/markdownFootnotes.js @@ -0,0 +1,100 @@ +define([ + "underscore", + "utils", + "classes/Extension", +], function(_, utils, Extension) { + + var markdownFootnotes = new Extension("markdownFootnotes", "Markdown Footnotes", true); + markdownFootnotes.settingsBlock = '

Adds support for Markdown footnotes.

'; + + var inlineTags = new RegExp([ + '^(<\\/?(a|abbr|acronym|applet|area|b|basefont|', + 'bdo|big|button|cite|code|del|dfn|em|figcaption|', + 'font|i|iframe|img|input|ins|kbd|label|map|', + 'mark|meter|object|param|progress|q|ruby|rp|rt|s|', + 'samp|script|select|small|span|strike|strong|', + 'sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|', + '<(br)\\s?\\/?>)$' + ].join(''), 'i'); + + var previousPostConversion = undefined; + markdownFootnotes.onEditorConfigure = function(editor) { + var converter = editor.getConverter(); + previousPostConversion = converter.hooks.postConversion; + converter.hooks.chain("postNormalization", _StripFootnoteDefinitions); + converter.hooks.chain("postBlockGamut", _DoFootnotes); + converter.hooks.chain("postConversion", _PrintFootnotes); + }; + + var footnotes = undefined; + var usedFootnotes = undefined; + function _StripFootnoteDefinitions(text) { + footnotes = {}; + usedFootnotes = []; + + text = text.replace(/\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?(.*?)\n{1,2}((?=\n[ ]{0,3}\S)|\Z)/g, function(wholeMatch, m1, m2) { + m1 = utils.slugify(m1); + m2 += "\n"; + m2 = m2.replace(/^[ ]{0,3}/gm, ""); + footnotes[m1] = m2; + return "\n"; + }); + + return text; + } + + var blockGamutHookCallback = undefined; + function _DoFootnotes(text, blockGamutHookCallbackParam) { + blockGamutHookCallback = blockGamutHookCallbackParam; + var footnoteCounter = 0; + text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) { + var id = utils.slugify(m1); + var footnote = footnotes[id]; + if(footnote === undefined) { + return ""; + } + footnoteCounter++; + usedFootnotes.push(id); + return '' + footnoteCounter + ''; + }); + + return text; + } + + function _PrintFootnotes(text) { + if(usedFootnotes.length === 0) { + return text; + } + + _.each(footnotes, function(footnote, id) { + var formattedfootnote = blockGamutHookCallback(footnote); + formattedfootnote = unescapeSpecialChars(formattedfootnote); + formattedfootnote = formattedfootnote.replace(/~D/g, "$$").replace(/~T/g, "~"); + formattedfootnote = previousPostConversion(formattedfootnote); + formattedfootnote = formattedfootnote.replace(/<[^>]*>?/gi, function(tag) { + return tag.match(inlineTags) ? tag : ''; + }); + footnotes[id] = formattedfootnote; + }); + + text += '\n\n
\n
\n
    \n\n'; + _.each(usedFootnotes, function(id) { + var footnote = footnotes[id]; + text += '
  1. ' + footnote + '
  2. \n\n'; + }); + text += '
\n
'; + return text; + } + + // Duplicated from PageDown converter + function unescapeSpecialChars(text) { + // Swap back in all the special characters we've hidden. + text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + }); + return text; + } + + return markdownFootnotes; +}); \ No newline at end of file diff --git a/js/extensions/mathJax.js b/js/extensions/mathJax.js index a329cee3..337771b4 100644 --- a/js/extensions/mathJax.js +++ b/js/extensions/mathJax.js @@ -1,22 +1,65 @@ define([ - "libs/MathJax" -], function() { + "utils", + "classes/Extension", + "text!html/mathJaxSettingsBlock.html", + "libs/MathJax", +], function(utils, Extension, mathJaxSettingsBlockHTML) { - var mathJax = { - extensionId: "mathJax", - extensionName: "MathJax", - optional: true, - settingsBloc: '

Allows StackEdit to interpret LaTex mathematical expressions.

' - }; + var mathJax = new Extension("mathJax", "MathJax", true); + mathJax.settingsBlock = mathJaxSettingsBlockHTML; + mathJax.defaultConfig = { + tex: "{}", + tex2jax: '{ inlineMath: [["$","$"],["\\\\(","\\\\)"]], displayMath: [["$$","$$"],["\\[","\\]"]], processEscapes: true }' + }; + + mathJax.onLoadSettings = function() { + utils.setInputValue("#input-mathjax-config-tex", mathJax.config.tex); + utils.setInputValue("#input-mathjax-config-tex2jax", mathJax.config.tex2jax); + }; + + mathJax.onSaveSettings = function(newConfig, event) { + newConfig.tex = utils.getInputJsValue("#input-mathjax-config-tex", event); + newConfig.tex2jax = utils.getInputJsValue("#input-mathjax-config-tex2jax", event); + }; - mathJax.onReady = function() { - MathJax.Hub.Config({"HTML-CSS": {preferredFont: "TeX",availableFonts: ["STIX", "TeX"],linebreaks: {automatic: true},EqnChunk: (MathJax.Hub.Browser.isMobile ? 10 : 50), imageFont: null}, - tex2jax: {inlineMath: [["$", "$"], ["\\\\(", "\\\\)"]],displayMath: [["$$", "$$"], ["\\[", "\\]"]],processEscapes: true,ignoreClass: "tex2jax_ignore|dno"}, - TeX: {noUndefined: {attributes: {mathcolor: "red",mathbackground: "#FFEEEE",mathsize: "90%"}}, - Safe: {allow: {URLs: "safe",classes: "safe",cssIDs: "safe",styles: "safe",fontsize: "all"}}}, - messageStyle: "none" - }); - }; + mathJax.onReady = function() { + eval("var tex = " + mathJax.config.tex); + eval("var tex2jax = " + mathJax.config.tex2jax); + MathJax.Hub.Config({ + "HTML-CSS": { + preferredFont: "TeX", + availableFonts: [ + "STIX", + "TeX" + ], + linebreaks: { + automatic: true + }, + EqnChunk: (MathJax.Hub.Browser.isMobile ? 10 : 50), + imageFont: null + }, + tex2jax: tex2jax, + TeX: $.extend({ + noUndefined: { + attributes: { + mathcolor: "red", + mathbackground: "#FFEEEE", + mathsize: "90%" + } + }, + Safe: { + allow: { + URLs: "safe", + classes: "safe", + cssIDs: "safe", + styles: "safe", + fontsize: "all" + } + } + }, tex), + messageStyle: "none" + }); + }; var ready = false; // true after initial typeset is complete var pending = false; // true when MathJax has been requested diff --git a/js/extensions/notifications.js b/js/extensions/notifications.js index b5e2a849..08a17508 100644 --- a/js/extensions/notifications.js +++ b/js/extensions/notifications.js @@ -2,17 +2,15 @@ define([ "jquery", "underscore", "utils", + "classes/Extension", "jgrowl", - "text!html/notificationsSettingsBloc.html", -], function($, _, utils, jGrowl, notificationsSettingsBlocHTML) { + "text!html/notificationsSettingsBlock.html", +], function($, _, utils, Extension, jGrowl, notificationsSettingsBlockHTML) { - var notifications = { - extensionId: "notifications", - extensionName: "Notifications", - defaultConfig: { - timeout: 8000 - }, - settingsBloc: notificationsSettingsBlocHTML + var notifications = new Extension("notifications", "Notifications"); + notifications.settingsBlock = notificationsSettingsBlockHTML; + notifications.defaultConfig = { + timeout: 8000 }; notifications.onLoadSettings = function() { diff --git a/js/extensions/scrollLink.js b/js/extensions/scrollLink.js index b3492054..5242200d 100644 --- a/js/extensions/scrollLink.js +++ b/js/extensions/scrollLink.js @@ -1,17 +1,14 @@ define([ "jquery", "underscore", - "text!html/scrollLinkSettingsBloc.html", + "classes/Extension", + "text!html/scrollLinkSettingsBlock.html", "libs/css_browser_selector", "libs/jquery.mousewheel" -], function($, _, scrollLinkSettingsBlocHTML) { +], function($, _, Extension, scrollLinkSettingsBlockHTML) { - var scrollLink = { - extensionId: "scrollLink", - extensionName: "Scroll Link", - optional: true, - settingsBloc: scrollLinkSettingsBlocHTML - }; + var scrollLink = new Extension("scrollLink", "Scroll Link", true); + scrollLink.settingsBlock = scrollLinkSettingsBlockHTML; var mdSectionList = []; var htmlSectionList = []; diff --git a/js/extensions/toc.js b/js/extensions/toc.js index b1945563..e27a77ba 100644 --- a/js/extensions/toc.js +++ b/js/extensions/toc.js @@ -2,17 +2,14 @@ define([ "jquery", "underscore", "utils", - "text!html/tocSettingsBloc.html", -], function($, _, utils, tocSettingsBlocHTML) { + "classes/Extension", + "text!html/tocSettingsBlock.html", +], function($, _, utils, Extension, tocSettingsBlockHTML) { - var toc = { - extensionId: "toc", - extensionName: "Table of Content", - optional: true, - defaultConfig: { - marker: "\\[(TOC|toc)\\]" - }, - settingsBloc: tocSettingsBlocHTML + var toc = new Extension("toc", "Markdown Table of Content", true); + toc.settingsBlock = tocSettingsBlockHTML; + toc.defaultConfig = { + marker: "\\[(TOC|toc)\\]" }; toc.onLoadSettings = function() { @@ -21,7 +18,7 @@ define([ toc.onSaveSettings = function(newConfig, event) { newConfig.marker = utils.getInputRegExpValue("#input-toc-marker", event); -}; + }; // TOC element description function TocElement(tagName, anchor, text) { diff --git a/js/extensions/workingIndicator.js b/js/extensions/workingIndicator.js index 48e68fac..f168cc03 100644 --- a/js/extensions/workingIndicator.js +++ b/js/extensions/workingIndicator.js @@ -1,13 +1,11 @@ define([ "jquery", - "underscore" -], function($, _) { + "underscore", + "classes/Extension", +], function($, _, Extension) { - var workingIndicator = { - extensionId: "workingIndicator", - extensionName: "Working Indicator", - settingsBloc: '

Displays an animated image when a network operation is running.

' - }; + var workingIndicator = new Extension("workingIndicator", "Working Indicator"); + workingIndicator.settingsBlock = '

Displays an animated image when a network operation is running.

'; workingIndicator.onAsyncRunning = function(isRunning) { if(isRunning === false) { diff --git a/js/helpers/sshHelper.js b/js/helpers/sshHelper.js index 7344f847..ea27c41e 100644 --- a/js/helpers/sshHelper.js +++ b/js/helpers/sshHelper.js @@ -1,8 +1,9 @@ define([ "jquery", "core", + "settings", "classes/AsyncTask" -], function($, core, AsyncTask) { +], function($, core, settings, AsyncTask) { var sshHelper = {}; @@ -21,7 +22,7 @@ define([ var task = new AsyncTask(); connect(task); task.onRun(function() { - var url = SSH_PROXY_URL + "upload"; + var url = settings.sshProxy + "upload"; var data = { host: host, port: port, diff --git a/js/html/buttonHtmlCodeSettingsBlock.html b/js/html/buttonHtmlCodeSettingsBlock.html new file mode 100644 index 00000000..33e1e0ce --- /dev/null +++ b/js/html/buttonHtmlCodeSettingsBlock.html @@ -0,0 +1,11 @@ +

Adds a "HTML code" button over the preview.

+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/js/html/buttonStatSettingsBloc.html b/js/html/buttonStatSettingsBlock.html similarity index 100% rename from js/html/buttonStatSettingsBloc.html rename to js/html/buttonStatSettingsBlock.html diff --git a/js/html/buttonSyncSettingsBloc.html b/js/html/buttonSyncSettingsBlock.html similarity index 100% rename from js/html/buttonSyncSettingsBloc.html rename to js/html/buttonSyncSettingsBlock.html diff --git a/js/html/documentSelectorSettingsBloc.html b/js/html/documentSelectorSettingsBlock.html similarity index 100% rename from js/html/documentSelectorSettingsBloc.html rename to js/html/documentSelectorSettingsBlock.html diff --git a/js/html/markdownExtraSettingsBloc.html b/js/html/markdownExtraSettingsBlock.html similarity index 72% rename from js/html/markdownExtraSettingsBloc.html rename to js/html/markdownExtraSettingsBlock.html index 486129ce..09cfa152 100644 --- a/js/html/markdownExtraSettingsBloc.html +++ b/js/html/markdownExtraSettingsBlock.html @@ -8,3 +8,4 @@ +More info \ No newline at end of file diff --git a/js/html/mathJaxSettingsBlock.html b/js/html/mathJaxSettingsBlock.html new file mode 100644 index 00000000..339ae732 --- /dev/null +++ b/js/html/mathJaxSettingsBlock.html @@ -0,0 +1,18 @@ +

Allows StackEdit to interpret LaTeX mathematical expressions.

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+More info \ No newline at end of file diff --git a/js/html/notificationsSettingsBloc.html b/js/html/notificationsSettingsBlock.html similarity index 100% rename from js/html/notificationsSettingsBloc.html rename to js/html/notificationsSettingsBlock.html diff --git a/js/html/scrollLinkSettingsBloc.html b/js/html/scrollLinkSettingsBlock.html similarity index 100% rename from js/html/scrollLinkSettingsBloc.html rename to js/html/scrollLinkSettingsBlock.html diff --git a/js/html/settingsExtensionsAccordion.html b/js/html/settingsExtensionsAccordion.html index efc0e042..906d93c0 100644 --- a/js/html/settingsExtensionsAccordion.html +++ b/js/html/settingsExtensionsAccordion.html @@ -2,12 +2,12 @@
<%= extensionName %>
-
<%= settingsBloc %>
+
<%= settingsBlock %>
diff --git a/js/html/settingsTemplateTooltip.html b/js/html/settingsTemplateTooltip.html index e14675bf..c586eb64 100644 --- a/js/html/settingsTemplateTooltip.html +++ b/js/html/settingsTemplateTooltip.html @@ -5,7 +5,7 @@ Available variables:
  • documentMarkdown: document in Markdown format
  • documentHTML: document in HTML format
  • publishAttributes: attributes of the publish location - (undefined when using "Save")
  • + (undefined if not publishing) Examples:
    @@ -13,8 +13,10 @@ Examples:
    <div><%- documentHTML %></div>
    -<% if(publishAttributes.provider == "github") -print(documentMarkdown); %> +<%
    +if(publishAttributes.provider.providerId == "github") +print(documentMarkdown);
    +%>

    More diff --git a/js/html/tocSettingsBloc.html b/js/html/tocSettingsBlock.html similarity index 100% rename from js/html/tocSettingsBloc.html rename to js/html/tocSettingsBlock.html diff --git a/js/libs/Markdown.Extra.js b/js/libs/Markdown.Extra.js index b9c7f56b..990dc158 100644 --- a/js/libs/Markdown.Extra.js +++ b/js/libs/Markdown.Extra.js @@ -37,7 +37,7 @@ // Remove one level of indentation from text. Indent is 4 spaces. function outdent(text) { - return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), ''); + return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), ''); } function contains(str, substr) { @@ -115,15 +115,15 @@ function unescapeSpecialChars(text) { // Swap back in all the special characters we've hidden. text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) { - var charCodeToReplace = parseInt(m1); - return String.fromCharCode(charCodeToReplace); - }); + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + }); return text; } /***************************************************************************** - * Markdown.Extra * - ****************************************************************************/ + * Markdown.Extra * + ****************************************************************************/ Markdown.Extra = function() { // For converting internal markdown (in tables for instance). @@ -160,19 +160,19 @@ options = options || {}; options.extensions = options.extensions || ["all"]; if (contains(options.extensions, "all")) { - options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list"]; + options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list"]; } if (contains(options.extensions, "attr_list")) { - postNormalizationTransformations.push("hashFcbAttributeBlocks"); - preBlockGamutTransformations.push("hashHeaderAttributeBlocks"); - postConversionTransformations.push("applyAttributeBlocks"); + postNormalizationTransformations.push("hashFcbAttributeBlocks"); + preBlockGamutTransformations.push("hashHeaderAttributeBlocks"); + postConversionTransformations.push("applyAttributeBlocks"); extra.attributeBlocks = true; } if (contains(options.extensions, "tables")) { - preBlockGamutTransformations.push("tables"); + preBlockGamutTransformations.push("tables"); } if (contains(options.extensions, "fenced_code_gfm")) { - postNormalizationTransformations.push("fencedCodeBlocks"); + postNormalizationTransformations.push("fencedCodeBlocks"); } if (contains(options.extensions, "def_list")) { preBlockGamutTransformations.push("definitionLists"); @@ -183,14 +183,14 @@ }); converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) { - // Keep a reference to the block gamut callback to run recursively + // Keep a reference to the block gamut callback to run recursively extra.blockGamutHookCallback = blockGamutHookCallback; text = processEscapes(text); return extra.doTransform(preBlockGamutTransformations, text) + '\n'; }); // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks - extra.previousPostConversion = converter.hooks.postConversion; + extra.previousPostConversion = converter.hooks.postConversion; converter.hooks.chain("postConversion", function(text) { text = extra.doTransform(postConversionTransformations, text); // Clear state vars that may use unnecessary memory @@ -215,9 +215,9 @@ // Do transformations Markdown.Extra.prototype.doTransform = function(transformations, text) { - for(var i = 0; i < transformations.length; i++) - text = this[transformations[i]](text); - return text; + for(var i = 0; i < transformations.length; i++) + text = this[transformations[i]](text); + return text; }; // Return a placeholder containing a key, which is the block's index in the @@ -253,21 +253,21 @@ // Extract headers attribute blocks, move them above the element they will be // applied to, and hash them for later. Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) { - // TODO: use sentinels. Should we just add/remove them in doConversion? - // TODO: better matches for id / class attributes - var attrBlock = "\\{\\s*[.|#][^}]+\\}"; - var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})\\s+(" + attrBlock + ")[ \\t]*(\\n|0x03)", "gm"); - var hdrAttributesB = new RegExp("^(.*)\\s+(" + attrBlock + ")[ \\t]*\\n" + - "(?=[\\-|=]+\\s*(\\n|0x03))", "gm"); // underline lookahead - - var self = this; - function attributeCallback(wholeMatch, pre, attr) { - return '

    ~XX' + (self.hashBlocks.push(attr) - 1) + 'XX

    \n' + pre + "\n"; - } + // TODO: use sentinels. Should we just add/remove them in doConversion? + // TODO: better matches for id / class attributes + var attrBlock = "\\{\\s*[.|#][^}]+\\}"; + var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})\\s+(" + attrBlock + ")[ \\t]*(\\n|0x03)", "gm"); + var hdrAttributesB = new RegExp("^(.*)\\s+(" + attrBlock + ")[ \\t]*\\n" + + "(?=[\\-|=]+\\s*(\\n|0x03))", "gm"); // underline lookahead + + var self = this; + function attributeCallback(wholeMatch, pre, attr) { + return '

    ~XX' + (self.hashBlocks.push(attr) - 1) + 'XX

    \n' + pre + "\n"; + } - text = text.replace(hdrAttributesA, attributeCallback); // ## headers - text = text.replace(hdrAttributesB, attributeCallback); // underline headers - return text; + text = text.replace(hdrAttributesA, attributeCallback); // ## headers + text = text.replace(hdrAttributesB, attributeCallback); // underline headers + return text; }; // Extract FCB attribute blocks, move them above the element they will be @@ -275,14 +275,14 @@ Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) { // TODO: use sentinels. Should we just add/remove them in doConversion? // TODO: better matches for id / class attributes - var attrBlock = "\\{\\s*[.|#][^}]+\\}"; + var attrBlock = "\\{\\s*[.|#][^}]+\\}"; var fcbAttributes = new RegExp("^(```[^{]*)\\s+(" + attrBlock + ")[ \\t]*\\n" + "(?=([\\s\\S]*?)\\n```\\s*(\\n|0x03))", "gm"); - var self = this; - function attributeCallback(wholeMatch, pre, attr) { - return '

    ~XX' + (self.hashBlocks.push(attr) - 1) + 'XX

    \n' + pre + "\n"; - } + var self = this; + function attributeCallback(wholeMatch, pre, attr) { + return '

    ~XX' + (self.hashBlocks.push(attr) - 1) + 'XX

    \n' + pre + "\n"; + } return text.replace(fcbAttributes, attributeCallback); }; diff --git a/js/mediaImporter.js b/js/mediaImporter.js index 99790575..2b9c77a2 100644 --- a/js/mediaImporter.js +++ b/js/mediaImporter.js @@ -1,15 +1,16 @@ define([ "jquery", "underscore", + "classes/Provider", "core", "providers/gplusProvider" -], function($, _, core) { +], function($, _, Provider, core) { var mediaImporter = {}; // Create a map with providerId: providerModule var providerMap = _.chain(arguments).map(function(argument) { - return argument && argument.providerId && [ + return argument instanceof Provider && [ argument.providerId, argument ]; diff --git a/js/providers/bloggerProvider.js b/js/providers/bloggerProvider.js index 52122c25..4f57df88 100644 --- a/js/providers/bloggerProvider.js +++ b/js/providers/bloggerProvider.js @@ -1,19 +1,15 @@ define([ "underscore", "utils", + "classes/Provider", "helpers/googleHelper" -], function(_, utils, googleHelper) { +], function(_, utils, Provider, googleHelper) { - var PROVIDER_BLOGGER = "blogger"; - - var bloggerProvider = { - providerId: PROVIDER_BLOGGER, - providerName: "Blogger", - defaultPublishFormat: "html", - publishPreferencesInputIds: [ - "blogger-url" - ] - }; + var bloggerProvider = new Provider("blogger", "Blogger"); + bloggerProvider.defaultPublishFormat = "html"; + bloggerProvider.publishPreferencesInputIds = [ + "blogger-url" + ]; bloggerProvider.publish = function(publishAttributes, title, content, callback) { googleHelper.uploadBlogger(publishAttributes.blogUrl, publishAttributes.blogId, publishAttributes.postId, publishAttributes.labelList, title, content, function(error, blogId, postId) { diff --git a/js/providers/downloadProvider.js b/js/providers/downloadProvider.js index b9db9ccc..2020caac 100644 --- a/js/providers/downloadProvider.js +++ b/js/providers/downloadProvider.js @@ -1,17 +1,14 @@ define([ "jquery", "core", + "classes/Provider", "classes/AsyncTask" -], function($, core, AsyncTask) { +], function($, core, Provider, AsyncTask) { - var PROVIDER_DOWNLOAD = "download"; - - var downloadProvider = { - providerId: PROVIDER_DOWNLOAD, - sharingAttributes: [ - "url" - ] - }; + var downloadProvider = new Provider("download"); + downloadProvider.sharingAttributes = [ + "url" + ]; downloadProvider.importPublic = function(importParameters, callback) { var title = undefined; diff --git a/js/providers/dropboxProvider.js b/js/providers/dropboxProvider.js index cd306fba..83b76cc2 100644 --- a/js/providers/dropboxProvider.js +++ b/js/providers/dropboxProvider.js @@ -1,18 +1,16 @@ define([ "underscore", "utils", + "classes/Provider", "extensionMgr", "fileMgr", "helpers/dropboxHelper" -], function(_, utils, extensionMgr, fileMgr, dropboxHelper) { +], function(_, utils, Provider, extensionMgr, fileMgr, dropboxHelper) { var PROVIDER_DROPBOX = "dropbox"; - var dropboxProvider = { - providerId: PROVIDER_DROPBOX, - providerName: "Dropbox", - defaultPublishFormat: "template" - }; + var dropboxProvider = new Provider(PROVIDER_DROPBOX, "Dropbox"); + dropboxProvider.defaultPublishFormat = "template"; function checkPath(path) { if(path === undefined) { diff --git a/js/providers/gdriveProvider.js b/js/providers/gdriveProvider.js index 60d98d14..e2662b4e 100644 --- a/js/providers/gdriveProvider.js +++ b/js/providers/gdriveProvider.js @@ -2,22 +2,20 @@ define([ "underscore", "core", "utils", + "classes/Provider", "settings", "extensionMgr", "fileMgr", "helpers/googleHelper" -], function(_, core, utils, settings, extensionMgr, fileMgr, googleHelper) { +], function(_, core, utils, Provider, settings, extensionMgr, fileMgr, googleHelper) { var PROVIDER_GDRIVE = "gdrive"; - var gdriveProvider = { - providerId: PROVIDER_GDRIVE, - providerName: "Google Drive", - defaultPublishFormat: "template", - exportPreferencesInputIds: [ - "gdrive-parentid" - ] - }; + var gdriveProvider = new Provider(PROVIDER_GDRIVE, "Google Drive"); + gdriveProvider.defaultPublishFormat = "template"; + gdriveProvider.exportPreferencesInputIds = [ + "gdrive-parentid" + ]; function createSyncIndex(id) { return "sync." + PROVIDER_GDRIVE + "." + id; diff --git a/js/providers/gistProvider.js b/js/providers/gistProvider.js index 4e455114..2abe85e1 100644 --- a/js/providers/gistProvider.js +++ b/js/providers/gistProvider.js @@ -1,18 +1,14 @@ define([ "utils", + "classes/Provider", "helpers/githubHelper" -], function(utils, githubHelper) { +], function(utils, Provider, githubHelper) { - var PROVIDER_GIST = "gist"; - - var gistProvider = { - providerId: PROVIDER_GIST, - providerName: "Gist", - sharingAttributes: [ - "gistId", - "filename" - ] - }; + var gistProvider = new Provider("gist", "Gist"); + gistProvider.sharingAttributes = [ + "gistId", + "filename" + ]; gistProvider.publish = function(publishAttributes, title, content, callback) { githubHelper.uploadGist(publishAttributes.gistId, publishAttributes.filename, publishAttributes.isPublic, title, content, function(error, gistId) { diff --git a/js/providers/githubProvider.js b/js/providers/githubProvider.js index 3bdc05e0..b1a6f665 100644 --- a/js/providers/githubProvider.js +++ b/js/providers/githubProvider.js @@ -1,19 +1,15 @@ define([ "utils", + "classes/Provider", "settings", "helpers/githubHelper" -], function(utils, settings, githubHelper) { +], function(utils, Provider, settings, githubHelper) { - var PROVIDER_GITHUB = "github"; - - var githubProvider = { - providerId: PROVIDER_GITHUB, - providerName: "GitHub", - publishPreferencesInputIds: [ - "github-reponame", - "github-branch" - ] - }; + var githubProvider = new Provider("github", "GitHub"); + githubProvider.publishPreferencesInputIds = [ + "github-reponame", + "github-branch" + ]; githubProvider.publish = function(publishAttributes, title, content, callback) { var commitMsg = settings.commitMsg; diff --git a/js/providers/gplusProvider.js b/js/providers/gplusProvider.js index f5b26d20..9be1640a 100644 --- a/js/providers/gplusProvider.js +++ b/js/providers/gplusProvider.js @@ -2,16 +2,14 @@ define([ "underscore", "core", "utils", + "classes/Provider", "extensionMgr", "helpers/googleHelper" -], function(_, core, utils, extensionMgr, googleHelper) { +], function(_, core, utils, Provider, extensionMgr, googleHelper) { var PROVIDER_GPLUS = "gplus"; - var gplusProvider = { - providerId: PROVIDER_GPLUS, - providerName: "Google+" - }; + var gplusProvider = new Provider(PROVIDER_GPLUS, "Google+"); function getThumbnailUrl(doc, size) { var result = undefined; diff --git a/js/providers/sshProvider.js b/js/providers/sshProvider.js index 51fd52f2..2f163946 100644 --- a/js/providers/sshProvider.js +++ b/js/providers/sshProvider.js @@ -1,20 +1,16 @@ define([ "utils", + "classes/Provider", "helpers/sshHelper" -], function(utils, sshHelper) { +], function(utils, Provider, sshHelper) { - var PROVIDER_SSH = "ssh"; - - var sshProvider = { - providerId: PROVIDER_SSH, - providerName: "SSH server", - publishPreferencesInputIds: [ - "ssh-host", - "ssh-port", - "ssh-username", - "ssh-password" - ] - }; + var sshProvider = new Provider("ssh", "SSH server"); + sshProvider.publishPreferencesInputIds = [ + "ssh-host", + "ssh-port", + "ssh-username", + "ssh-password" + ]; sshProvider.publish = function(publishAttributes, title, content, callback) { sshHelper.upload(publishAttributes.host, publishAttributes.port, publishAttributes.username, publishAttributes.password, publishAttributes.path, title, content, callback); diff --git a/js/providers/tumblrProvider.js b/js/providers/tumblrProvider.js index a5d38ce2..d7ad709f 100644 --- a/js/providers/tumblrProvider.js +++ b/js/providers/tumblrProvider.js @@ -1,17 +1,13 @@ define([ "utils", + "classes/Provider", "helpers/tumblrHelper" -], function(utils, tumblrHelper) { +], function(utils, Provider, tumblrHelper) { - var PROVIDER_TUMBLR = "tumblr"; - - var tumblrProvider = { - providerId: PROVIDER_TUMBLR, - providerName: "Tumblr", - publishPreferencesInputIds: [ - "tumblr-hostname" - ] - }; + var tumblrProvider = new Provider("tumblr", "Tumblr"); + tumblrProvider.publishPreferencesInputIds = [ + "tumblr-hostname" + ]; tumblrProvider.publish = function(publishAttributes, title, content, callback) { tumblrHelper.upload(publishAttributes.blogHostname, publishAttributes.postId, publishAttributes.tags, publishAttributes.format == "markdown" ? "markdown" : "html", title, content, function(error, postId) { diff --git a/js/providers/wordpressProvider.js b/js/providers/wordpressProvider.js index a8609fac..d05560ed 100644 --- a/js/providers/wordpressProvider.js +++ b/js/providers/wordpressProvider.js @@ -1,18 +1,14 @@ define([ "utils", + "classes/Provider", "helpers/wordpressHelper" -], function(utils, wordpressHelper) { +], function(utils, Provider, wordpressHelper) { - var PROVIDER_WORDPRESS = "wordpress"; - - var wordpressProvider = { - providerId: PROVIDER_WORDPRESS, - providerName: "WordPress", - defaultPublishFormat: "html", - publishPreferencesInputIds: [ - "wordpress-site" - ] - }; + var wordpressProvider = new Provider("wordpress", "WordPress"); + wordpressProvider.defaultPublishFormat = "html"; + wordpressProvider.publishPreferencesInputIds = [ + "wordpress-site" + ]; wordpressProvider.publish = function(publishAttributes, title, content, callback) { wordpressHelper.upload(publishAttributes.site, publishAttributes.postId, publishAttributes.tags, title, content, function(error, postId) { diff --git a/js/publisher.js b/js/publisher.js index 844ee676..6bee6cfe 100644 --- a/js/publisher.js +++ b/js/publisher.js @@ -8,6 +8,7 @@ define([ "fileSystem", "fileMgr", "sharing", + "classes/Provider", "providers/bloggerProvider", "providers/dropboxProvider", "providers/gistProvider", @@ -16,13 +17,13 @@ define([ "providers/sshProvider", "providers/tumblrProvider", "providers/wordpressProvider" -], function($, _, core, utils, settings, extensionMgr, fileSystem, fileMgr, sharing) { +], function($, _, core, utils, settings, extensionMgr, fileSystem, fileMgr, sharing, Provider) { var publisher = {}; // Create a map with providerId: providerModule var providerMap = _.chain(arguments).map(function(argument) { - return argument && argument.providerId && [ + return argument instanceof Provider && [ argument.providerId, argument ]; diff --git a/js/sharing.js b/js/sharing.js index e0a870db..e591e345 100644 --- a/js/sharing.js +++ b/js/sharing.js @@ -6,15 +6,16 @@ define([ "extensionMgr", "fileMgr", "classes/AsyncTask", + "classes/Provider", "providers/downloadProvider", "providers/gistProvider" -], function($, _, core, utils, extensionMgr, fileMgr, AsyncTask) { +], function($, _, core, utils, extensionMgr, fileMgr, AsyncTask, Provider) { var sharing = {}; // Create a map with providerId: providerModule var providerMap = _.chain(arguments).map(function(argument) { - return argument && argument.providerId && [ + return argument instanceof Provider && [ argument.providerId, argument ]; diff --git a/js/synchronizer.js b/js/synchronizer.js index a9ff9f40..546c874f 100644 --- a/js/synchronizer.js +++ b/js/synchronizer.js @@ -6,15 +6,16 @@ define([ "extensionMgr", "fileSystem", "fileMgr", + "classes/Provider", "providers/dropboxProvider", "providers/gdriveProvider" -], function($, _, core, utils, extensionMgr, fileSystem, fileMgr) { +], function($, _, core, utils, extensionMgr, fileSystem, fileMgr, Provider) { var synchronizer = {}; // Create a map with providerId: providerModule var providerMap = _.chain(arguments).map(function(argument) { - return argument && argument.providerId && [ + return argument instanceof Provider && [ argument.providerId, argument ]; diff --git a/js/utils.js b/js/utils.js index 02fa9c57..d13b4276 100644 --- a/js/utils.js +++ b/js/utils.js @@ -95,6 +95,23 @@ define([ return value; }; + // Return input value and check that it's a valid JavaScript object + utils.getInputJsValue = function(element, event) { + element = jqElt(element); + var value = utils.getInputTextValue(element, event); + if(value === undefined) { + return undefined; + } + try { + eval("var test=" + value); + } + catch(e) { + inputError(element, event); + return undefined; + } + return value; + }; + // Return checkbox boolean value utils.getInputChecked = function(element) { element = jqElt(element);