Implemented markdown footnotes

This commit is contained in:
benweet 2013-06-23 00:48:57 +01:00
parent 56f1c3d8c4
commit abe18e41bf
56 changed files with 538 additions and 347 deletions

View File

@ -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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

10
js/classes/Extension.js Normal file
View File

@ -0,0 +1,10 @@
define(function() {
function Extension(extensionId, extensionName, isOptional) {
this.extensionId = extensionId;
this.extensionName = extensionName;
this.isOptional = isOptional;
}
return Extension;
});

9
js/classes/Provider.js Normal file
View File

@ -0,0 +1,9 @@
define(function() {
function Provider(providerId, providerName) {
this.providerId = providerId;
this.providerName = providerName;
}
return Provider;
});

View File

@ -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;
});

View File

@ -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
})));
}

View File

@ -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: '<p>Adds a "HTML code" button over the preview.</p>'
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() {

View File

@ -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: '<p>Adds a "Markdown syntax" button over the preview.</p>'
};
var buttonMarkdownSyntax = new Extension("buttonMarkdownSyntax", 'Button "Markdown syntax', true);
buttonMarkdownSyntax.settingsBlock = '<p>Adds a "Markdown syntax" button over the preview.</p>';
buttonMarkdownSyntax.onCreatePreviewButton = function() {
return $(buttonMarkdownSyntaxHTML);

View File

@ -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: '<p>Adds a "Publish document" button in the navigation bar.</p>'
};
var buttonPublish = new Extension("buttonPublish", 'Button "Publish"');
buttonPublish.settingsBlock = '<p>Adds a "Publish document" button in the navigation bar.</p>';
var button = undefined;
var currentFileDesc = undefined;

View File

@ -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: '<p>Adds a "Share document" button in the navigation bar.</p>'
};
var buttonShare = new Extension("buttonShare", 'Button "Share"', true);
buttonShare.settingsBlock = '<p>Adds a "Share document" button in the navigation bar.</p>';
buttonShare.onCreateButton = function() {
return $(buttonShareHTML);

View File

@ -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() {

View File

@ -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() {

View File

@ -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: '<p>Adds a "Viewer" button over the preview.</p>'
};
var buttonViewer = new Extension("buttonViewer", 'Button "Viewer"', true);
buttonViewer.settingsBlock = '<p>Adds a "Viewer" button over the preview.</p>';
buttonViewer.onCreatePreviewButton = function() {
return $(buttonViewerHTML);

View File

@ -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: '<p>Prints the content of the "About" dialog box.</p>'
};
var dialogAbout = new Extension("dialogAbout", 'Dialog "About"');
dialogAbout.settingsBlock = '<p>Prints the content of the "About" dialog box.</p>';
var libraries = {
"Bootstrap": "http://twitter.github.io/bootstrap/",

View File

@ -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: '<p>Populates the "Manage publication" dialog box.</p>'
};
var dialogManagePublication = new Extension("dialogManagePublication", 'Dialog "Manage publication"');
dialogManagePublication.settingsBlock = '<p>Populates the "Manage publication" dialog box.</p>';
var extensionMgr = undefined;
dialogManagePublication.onExtensionMgrCreated = function(extensionMgrParameter) {

View File

@ -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: '<p>Populates the "Manage synchronization" dialog box.</p>'
};
var dialogManageSynchronization = new Extension("dialogManageSynchronization", 'Dialog "Manage synchronization"');
dialogManageSynchronization.settingsBlock = '<p>Populates the "Manage synchronization" dialog box.</p>';
var extensionMgr = undefined;
dialogManageSynchronization.onExtensionMgrCreated = function(extensionMgrParameter) {

View File

@ -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: '<p>Handles the "Import from hard drive" and the "Convert HTML to Markdown" dialog boxes.</p>'
};
var dialogOpenHarddrive = new Extension("dialogOpenHarddrive", 'Dialog "Open from"');
dialogOpenHarddrive.settingsBlock = '<p>Handles the "Import from hard drive" and the "Convert HTML to Markdown" dialog boxes.</p>';
var fileMgr = undefined;
dialogOpenHarddrive.onFileMgrCreated = function(fileMgrParameter) {

View File

@ -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;

View File

@ -1,13 +1,11 @@
define([
"jquery",
"underscore"
], function($, _) {
"underscore",
"classes/Extension",
], function($, _, Extension) {
var documentTitle = {
extensionId: "documentTitle",
extensionName: "Document Title",
settingsBloc: '<p>Responsible for showing the document title in the navigation bar.</p>'
};
var documentTitle = new Extension("documentTitle", "Document Title");
documentTitle.settingsBlock = '<p>Responsible for showing the document title in the navigation bar.</p>';
var layout = undefined;
documentTitle.onLayoutCreated = function(layoutParameter) {

View File

@ -1,11 +1,9 @@
define(function() {
define([
"classes/Extension",
], function(Extension) {
var emailConverter = {
extensionId: "emailConverter",
extensionName: "Email Converter",
optional: true,
settingsBloc: '<p>Converts email adresses in the form &lt;email@example.com&gt; into clickable links.</p>'
};
var emailConverter = new Extension("emailConverter", "Markdown Email", true);
emailConverter.settingsBlock = '<p>Converts email adresses in the form &lt;email@example.com&gt; into clickable links.</p>';
emailConverter.onEditorConfigure = function(editor) {
editor.getConverter().hooks.chain("postConversion", function(text) {

View File

@ -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: '<p>Sends anonymous statistics about usage and errors to help improve StackEdit.</p>'
};
var googleAnalytics = new Extension("googleAnalytics", "Google Analytics", true);
googleAnalytics.settingsBlock = '<p>Sends anonymous statistics about usage and errors to help improve StackEdit.</p>';
var isLoaded = false;
var isOffline = false;

View File

@ -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() {

View File

@ -0,0 +1,100 @@
define([
"underscore",
"utils",
"classes/Extension",
], function(_, utils, Extension) {
var markdownFootnotes = new Extension("markdownFootnotes", "Markdown Footnotes", true);
markdownFootnotes.settingsBlock = '<p>Adds support for Markdown footnotes.</p>';
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 '<a href="#fn:' + id + '" id="fnref:' + id + '" title="See footnote" class="footnote">' + footnoteCounter + '</a>';
});
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<div class="footnotes">\n<hr>\n<ol>\n\n';
_.each(usedFootnotes, function(id) {
var footnote = footnotes[id];
text += '<li id="fn:' + id + '">' + footnote + ' <a href="#fnref:' + id + '" title="Return to article" class="reversefootnote">&#8617;</a></li>\n\n';
});
text += '</ol>\n</div>';
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;
});

View File

@ -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: '<p>Allows StackEdit to interpret LaTex mathematical expressions.</p>'
};
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

View File

@ -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() {

View File

@ -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 = [];

View File

@ -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) {

View File

@ -1,13 +1,11 @@
define([
"jquery",
"underscore"
], function($, _) {
"underscore",
"classes/Extension",
], function($, _, Extension) {
var workingIndicator = {
extensionId: "workingIndicator",
extensionName: "Working Indicator",
settingsBloc: '<p>Displays an animated image when a network operation is running.</p>'
};
var workingIndicator = new Extension("workingIndicator", "Working Indicator");
workingIndicator.settingsBlock = '<p>Displays an animated image when a network operation is running.</p>';
workingIndicator.onAsyncRunning = function(isRunning) {
if(isRunning === false) {

View File

@ -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,

View File

@ -0,0 +1,11 @@
<p>Adds a "HTML code" button over the preview.</p>
<div class="form-horizontal">
<div class="control-group">
<label class="control-label" for="textarea-html-code-template">Template
<a href="#" class="tooltip-template">(?)</a>
</label>
<div class="controls">
<textarea id="textarea-html-code-template"></textarea>
</div>
</div>
</div>

View File

@ -8,3 +8,4 @@
</div>
</div>
</div>
<span class="help-block pull-right"><a target="_blank" href="https://github.com/jmcmanus/pagedown-extra">More info</a></span>

View File

@ -0,0 +1,18 @@
<p>Allows StackEdit to interpret LaTeX mathematical expressions.</p>
<div class="form-horizontal">
<div class="control-group">
<label class="control-label"
for="input-mathjax-config-tex">TeX configuration</label>
<div class="controls">
<input type="text" id="input-mathjax-config-tex">
</div>
</div>
<div class="control-group">
<label class="control-label"
for="input-mathjax-config-tex2jax">tex2jax configuration</label>
<div class="controls">
<input type="text" id="input-mathjax-config-tex2jax">
</div>
</div>
</div>
<span class="help-block pull-right"><a target="_blank" href="http://docs.mathjax.org/en/latest/options/">More info</a></span>

View File

@ -2,12 +2,12 @@
<div class="accordion-heading">
<label class="checkbox pull-right"> <input
id="input-enable-extension-<%= extensionId %>" type="checkbox"<%
if(!optional) print('disabled') %>> enabled
if(!isOptional) print('disabled') %>> enabled
</label> <a data-toggle="collapse"
data-parent="#accordion-extensions" class="accordion-toggle"
href="#collapse-<%= extensionId %>"> <%= extensionName %> </a>
</div>
<div id="collapse-<%= extensionId %>" class="accordion-body collapse">
<div class="accordion-inner"><%= settingsBloc %></div>
<div class="accordion-inner"><%= settingsBlock %></div>
</div>
</div>

View File

@ -5,7 +5,7 @@ Available variables:
<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>
(undefined if not publishing)</li>
</ul>
Examples:
<br />
@ -13,8 +13,10 @@ Examples:
<br />
&lt;div&gt;&lt;%- documentHTML %&gt;&lt;&#x2F;div&gt;
<br />
&lt;% if(publishAttributes.provider == &quot;github&quot;)
print(documentMarkdown); %&gt;
&lt;%<br />
if(publishAttributes.provider.providerId == &quot;github&quot;)
print(documentMarkdown);<br />
%&gt;
<br />
<br />
<a target="_blank" href="http://underscorejs.org/#template">More

View File

@ -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 '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\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 '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\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 '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
}
var self = this;
function attributeCallback(wholeMatch, pre, attr) {
return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
}
return text.replace(fcbAttributes, attributeCallback);
};

View File

@ -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
];

View File

@ -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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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) {

View File

@ -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) {

View File

@ -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
];

View File

@ -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
];

View File

@ -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
];

View File

@ -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);