Switch to ACE editor

This commit is contained in:
benweet 2013-09-15 15:14:42 +01:00
parent 99e5b27fa4
commit d5fddb26dd
23 changed files with 1826 additions and 6450 deletions

View File

@ -18,6 +18,11 @@
"stacktrace": "~0.5.3",
"requirejs-text": "~2.0.10",
"bootstrap-tour": "~0.6.0",
"ace": "#51b7cb67a63998c9c0b7d089a85c60e032a7cc17"
"ace": "#51b7cb67a63998c9c0b7d089a85c60e032a7cc17",
"pagedown-ace": "git@github.com:benweet/pagedown-ace.git#master",
"pagedown-extra": "git@github.com:jmcmanus/pagedown-extra.git#master",
"crel": "git@github.com:KoryNunn/crel.git#8dbda04b129fc0aec01a2a080d1cab26816e11c1",
"waitForImages": "git@github.com:alexanderdickson/waitForImages.git#~1.4.2",
"to-markdown": "git@github.com:benweet/to-markdown.git#jquery"
}
}

View File

@ -1,5 +1,5 @@
CACHE MANIFEST
#Date Sat Sep 14 2013 17:58:48
#Date Sun Sep 15 2013 15:14:20
CACHE:
index.html

View File

@ -3,8 +3,8 @@
<head>
<title>StackEdit - Markdown editor</title>
<link rel="canonical" href="http://benweet.github.io/stackedit/">
<link rel="icon" href="img/stackedit-32.ico" type="image/x-icon">
<link rel="shortcut icon" href="img/stackedit-32.ico"
<link rel="icon" href="res-min/img/stackedit-32.ico" type="image/x-icon">
<link rel="shortcut icon" href="res-min/img/stackedit-32.ico"
type="image/x-icon">
<meta name="description"
content="StackEdit is a free, open-source Markdown editor based on PageDown, the Markdown library used by Stack Overflow and the other Stack Exchange sites.">

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,7 +14,7 @@ define([
"storage",
"config",
"uilayout",
"libs/Markdown.Editor",
'pagedown-ace',
'libs/ace_mode',
'ace/requirejs/text!ace/css/editor.css',
'ace/requirejs/text!ace/theme/textmate.css',
@ -549,6 +549,10 @@ define([
makePreview();
if(documentContent === undefined) {
$previewContainerElt.scrollTop(fileDesc.previewScrollTop);
_.defer(function() {
aceEditor.renderer.scrollToY(fileDesc.editorScrollTop);
eventMgr.onFileOpen(fileDesc);
});
}
checkDocumentChanges();
};

View File

@ -19,15 +19,14 @@ define([
"Font Awesome and others...": "res/libs/fontello/LICENSE.txt",
"Gatekeeper": "https://github.com/prose/gatekeeper",
"Github.js": "https://github.com/michael/github",
"Glyphicons": "http://glyphicons.com/",
"Highlight.js": "http://softwaremaniacs.org/soft/highlight/en/",
"jGrowl": "https://github.com/stanlemon/jGrowl/",
"jQuery": "http://jquery.com/",
"LESS": "http://lesscss.org/",
"MathJax": "http://www.mathjax.org/",
"Mousetrap": "http://craig.is/killing/mice",
"PageDown": "https://code.google.com/p/pagedown/",
"Pagedown-extra": "https://github.com/jmcmanus/pagedown-extra/",
"PageDown ACE": "https://github.com/benweet/pagedown-ace",
"PageDown Extra": "https://github.com/jmcmanus/pagedown-extra/",
"Prettify": "https://code.google.com/p/google-code-prettify/",
"RequireJS": "http://requirejs.org/",
"RequireJS LESS plugin": "https://github.com/guybedford/require-less",

View File

@ -4,7 +4,7 @@ define([
"utils",
"classes/Extension",
"text!html/markdownExtraSettingsBlock.html",
"libs/Markdown.Extra",
'pagedown-extra',
], function($, _, utils, Extension, markdownExtraSettingsBlockHTML) {
var markdownExtra = new Extension("markdownExtra", "Markdown Extra", true);

View File

@ -49,6 +49,11 @@ define([
}
});
if(leftIndex !== 0 && leftIndex + rightIndex === 0) {
// nothing changed
return;
}
// Create an array composed of left unmodified, modified, right
// unmodified sections
var leftSections = sectionList.slice(0, leftIndex);
@ -63,7 +68,7 @@ define([
partialRendering.onSectionsCreated = function(sectionListParam) {
var newSectionList = [];
var newLinkDefinition = "";
var newLinkDefinition = '\n';
hasFootnotes = false;
_.each(sectionListParam, function(text) {
text += "\n\n";
@ -73,7 +78,7 @@ define([
text = text.replace(/^```.*\n[\s\S]*?\n```|\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g, function(wholeMatch, footnote) {
if(footnote) {
hasFootnotes = true;
newLinkDefinition += wholeMatch;
newLinkDefinition += wholeMatch.replace(/^\s*\n/gm, '') + '\n';
return "";
}
return wholeMatch;
@ -83,7 +88,7 @@ define([
// Strip link definitions
text = text.replace(/^```.*\n[\s\S]*?\n```|^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch, link) {
if(link) {
newLinkDefinition += wholeMatch;
newLinkDefinition += wholeMatch.replace(/^\s*\n/gm, '') + '\n';
return "";
}
return wholeMatch;
@ -94,7 +99,7 @@ define([
// Add section to the newSectionList
newSectionList.push({
id: ++sectionCounter,
text: text
text: text + '\n'
});
}
});
@ -141,7 +146,6 @@ define([
}
childNode = nextNode;
}
;
newSectionEltList.appendChild(sectionElt);
});
wmdPreviewElt.innerHTML = '';

View File

@ -69,7 +69,7 @@ define([
height: scrollHeight - htmlSectionOffset
});
// apply Scroll Link (-10 to have a gap > 9 px)
// apply Scroll Link (-10 to have a gap > 9px)
lastEditorScrollTop = -10;
lastPreviewScrollTop = -10;
doScrollLink();
@ -177,15 +177,17 @@ define([
mdSectionList = [];
};
var scrollAdjust = false;
scrollLink.onReady = function() {
$previewElt = $(".preview-container");
$previewElt.scroll(function() {
if(isPreviewMoving === false) {
if(isPreviewMoving === false && scrollAdjust === false) {
isScrollPreview = true;
isScrollEditor = false;
doScrollLink();
}
scrollAdjust = false;
});
aceEditor.session.on("changeScrollTop", function(e) {
if(isEditorMoving === false) {
@ -209,8 +211,14 @@ define([
scrollLink.onPreviewFinished = function() {
// Now set the correct height
var previousHeight = $previewContentsElt.height();
$previewContentsElt.height("auto");
var newHeight = $previewContentsElt.height();
isScrollEditor = true;
if(newHeight < previousHeight) {
// We expect a scroll adjustment
scrollAdjust = true;
}
buildSections();
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,707 +0,0 @@
(function () {
// A quick way to make sure we're only keeping span-level tags when we need to.
// This isn't supposed to be foolproof. It's just a quick way to make sure we
// keep all span-level tags returned by a pagedown converter. It should allow
// all span-level tags through, with or without attributes.
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');
/******************************************************************
* Utility Functions *
*****************************************************************/
// patch for ie7
if (!Array.indexOf) {
Array.prototype.indexOf = function(obj) {
for (var i = 0; i < this.length; i++) {
if (this[i] == obj) {
return i;
}
}
return -1;
};
}
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}
function rtrim(str) {
return str.replace(/\s+$/g, '');
}
// Remove one level of indentation from text. Indent is 4 spaces.
function outdent(text) {
return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), '');
}
function contains(str, substr) {
return str.indexOf(substr) != -1;
}
// Sanitize html, removing tags that aren't in the whitelist
function sanitizeHtml(html, whitelist) {
return html.replace(/<[^>]*>?/gi, function(tag) {
return tag.match(whitelist) ? tag : '';
});
}
// Merge two arrays, keeping only unique elements.
function union(x, y) {
var obj = {};
for (var i = 0; i < x.length; i++)
obj[x[i]] = x[i];
for (i = 0; i < y.length; i++)
obj[y[i]] = y[i];
var res = [];
for (var k in obj) {
if (obj.hasOwnProperty(k))
res.push(obj[k]);
}
return res;
}
// JS regexes don't support \A or \Z, so we add sentinels, as Pagedown
// does. In this case, we add the ascii codes for start of text (STX) and
// end of text (ETX), an idea borrowed from:
// https://github.com/tanakahisateru/js-markdown-extra
function addAnchors(text) {
if(text.charAt(0) != '\x02')
text = '\x02' + text;
if(text.charAt(text.length - 1) != '\x03')
text = text + '\x03';
return text;
}
// Remove STX and ETX sentinels.
function removeAnchors(text) {
if(text.charAt(0) == '\x02')
text = text.substr(1);
if(text.charAt(text.length - 1) == '\x03')
text = text.substr(0, text.length - 1);
return text;
}
// Convert markdown within an element, retaining only span-level tags
function convertSpans(text, extra) {
return sanitizeHtml(convertAll(text, extra), inlineTags);
}
// Convert internal markdown using the stock pagedown converter
function convertAll(text, extra) {
var result = extra.blockGamutHookCallback(text);
// We need to perform these operations since we skip the steps in the converter
result = unescapeSpecialChars(result);
result = result.replace(/~D/g, "$$").replace(/~T/g, "~");
result = extra.previousPostConversion(result);
return result;
}
// Convert escaped special characters to HTML decimal entity codes.
function processEscapes(text) {
// Markdown extra adds two escapable characters, `:` and `|`
// If escaped, we convert them to html entities so our
// regexes don't recognize them. Markdown doesn't support escaping
// the escape character, e.g. `\\`, which make this even simpler.
return text.replace(/\\\|/g, '&#124;').replace(/\\:/g, '&#58;');
}
// 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;
}
function slugify(text) {
return text.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
}
/*****************************************************************************
* Markdown.Extra *
****************************************************************************/
Markdown.Extra = function() {
// For converting internal markdown (in tables for instance).
// This is necessary since these methods are meant to be called as
// preConversion hooks, and the Markdown converter passed to init()
// won't convert any markdown contained in the html tags we return.
this.converter = null;
// Stores html blocks we generate in hooks so that
// they're not destroyed if the user is using a sanitizing converter
this.hashBlocks = [];
// Stores footnotes
this.footnotes = {};
this.usedFootnotes = [];
// Special attribute blocks for fenced code blocks and headers enabled.
this.attributeBlocks = false;
// Fenced code block options
this.googleCodePrettify = false;
this.highlightJs = false;
// Table options
this.tableClass = '';
this.tabWidth = 4;
};
Markdown.Extra.init = function(converter, options) {
// Each call to init creates a new instance of Markdown.Extra so it's
// safe to have multiple converters, with different options, on a single page
var extra = new Markdown.Extra();
var postNormalizationTransformations = [];
var preBlockGamutTransformations = [];
var postConversionTransformations = ["unHashExtraBlocks"];
options = options || {};
options.extensions = options.extensions || ["all"];
if (contains(options.extensions, "all")) {
options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes"];
}
if (contains(options.extensions, "attr_list")) {
postNormalizationTransformations.push("hashFcbAttributeBlocks");
preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
postConversionTransformations.push("applyAttributeBlocks");
extra.attributeBlocks = true;
}
if (contains(options.extensions, "tables")) {
preBlockGamutTransformations.push("tables");
}
if (contains(options.extensions, "fenced_code_gfm")) {
postNormalizationTransformations.push("fencedCodeBlocks");
}
if (contains(options.extensions, "def_list")) {
preBlockGamutTransformations.push("definitionLists");
}
if (contains(options.extensions, "footnotes")) {
postNormalizationTransformations.push("stripFootnoteDefinitions");
preBlockGamutTransformations.push("doFootnotes");
postConversionTransformations.push("printFootnotes");
}
converter.hooks.chain("postNormalization", function(text) {
return extra.doTransform(postNormalizationTransformations, text) + '\n';
});
converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
// 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;
converter.hooks.chain("postConversion", function(text) {
text = extra.doTransform(postConversionTransformations, text);
// Clear state vars that may use unnecessary memory
extra.hashBlocks = [];
extra.footnotes = {};
extra.usedFootnotes = [];
return text;
});
if ("highlighter" in options) {
extra.googleCodePrettify = options.highlighter === 'prettify';
extra.highlightJs = options.highlighter === 'highlight';
}
if ("table_class" in options) {
extra.tableClass = options.table_class;
}
extra.converter = converter;
// Caller usually won't need this, but it's handy for testing.
return extra;
};
// Do transformations
Markdown.Extra.prototype.doTransform = function(transformations, 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
// hashBlocks array. We wrap our output in a <p> tag here so Pagedown won't.
Markdown.Extra.prototype.hashExtraBlock = function(block) {
return '\n<p>~X' + (this.hashBlocks.push(block) - 1) + 'X</p>\n';
};
Markdown.Extra.prototype.hashExtraInline = function(block) {
return '~X' + (this.hashBlocks.push(block) - 1) + 'X';
};
// Replace placeholder blocks in `text` with their corresponding
// html blocks in the hashBlocks array.
Markdown.Extra.prototype.unHashExtraBlocks = function(text) {
var self = this;
function recursiveUnHash() {
var hasHash = false;
text = text.replace(/(?:<p>)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) {
hasHash = true;
var key = parseInt(m1, 10);
return self.hashBlocks[key];
});
if(hasHash === true) {
recursiveUnHash();
}
}
recursiveUnHash();
return text;
};
/******************************************************************
* Attribute Blocks *
*****************************************************************/
// 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";
}
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
// applied to, and hash them for later.
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 fcbAttributes = new RegExp("^(```[^{\\n]*)\\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";
}
return text.replace(fcbAttributes, attributeCallback);
};
Markdown.Extra.prototype.applyAttributeBlocks = function(text) {
var self = this;
var blockRe = new RegExp('<p>~XX(\\d+)XX</p>[\\s]*' +
'(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?</\\2>))', "gm");
text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) {
if (!tag) // no following header or fenced code block.
return '';
// get attributes list from hash
var key = parseInt(k, 10);
var attributes = self.hashBlocks[key];
// get id
var id = attributes.match(/#[^\s{}]+/g) || [];
var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : '';
// get classes and merge with existing classes
var classes = attributes.match(/\.[^\s{}]+/g) || [];
for (var i = 0; i < classes.length; i++) // Remove leading dot
classes[i] = classes[i].substr(1, classes[i].length - 1);
var classStr = '';
if (cls)
classes = union(classes, [cls]);
if (classes.length > 0)
classStr = ' class="' + classes.join(' ') + '"';
return "<" + tag + idStr + classStr + rest;
});
return text;
};
/******************************************************************
* Tables *
*****************************************************************/
// Find and convert Markdown Extra tables into html.
Markdown.Extra.prototype.tables = function(text) {
var self = this;
var leadingPipe = new RegExp(
['^' ,
'[ ]{0,3}' , // Allowed whitespace
'[|]' , // Initial pipe
'(.+)\\n' , // $1: Header Row
'[ ]{0,3}' , // Allowed whitespace
'[|]([ ]*[-:]+[-| :]*)\\n' , // $2: Separator
'(' , // $3: Table Body
'(?:[ ]*[|].*\\n?)*' , // Table rows
')',
'(?:\\n|$)' // Stop at final newline
].join(''),
'gm'
);
var noLeadingPipe = new RegExp(
['^' ,
'[ ]{0,3}' , // Allowed whitespace
'(\\S.*[|].*)\\n' , // $1: Header Row
'[ ]{0,3}' , // Allowed whitespace
'([-:]+[ ]*[|][-| :]*)\\n' , // $2: Separator
'(' , // $3: Table Body
'(?:.*[|].*\\n?)*' , // Table rows
')' ,
'(?:\\n|$)' // Stop at final newline
].join(''),
'gm'
);
text = text.replace(leadingPipe, doTable);
text = text.replace(noLeadingPipe, doTable);
// $1 = header, $2 = separator, $3 = body
function doTable(match, header, separator, body, offset, string) {
// remove any leading pipes and whitespace
header = header.replace(/^ *[|]/m, '');
separator = separator.replace(/^ *[|]/m, '');
body = body.replace(/^ *[|]/gm, '');
// remove trailing pipes and whitespace
header = header.replace(/[|] *$/m, '');
separator = separator.replace(/[|] *$/m, '');
body = body.replace(/[|] *$/gm, '');
// determine column alignments
alignspecs = separator.split(/ *[|] */);
align = [];
for (var i = 0; i < alignspecs.length; i++) {
var spec = alignspecs[i];
if (spec.match(/^ *-+: *$/m))
align[i] = ' style="text-align:right;"';
else if (spec.match(/^ *:-+: *$/m))
align[i] = ' style="text-align:center;"';
else if (spec.match(/^ *:-+ *$/m))
align[i] = ' style="text-align:left;"';
else align[i] = '';
}
// TODO: parse spans in header and rows before splitting, so that pipes
// inside of tags are not interpreted as separators
var headers = header.split(/ *[|] */);
var colCount = headers.length;
// build html
var cls = self.tableClass ? ' class="' + self.tableClass + '"' : '';
var html = ['<table', cls, '>\n', '<thead>\n', '<tr>\n'].join('');
// build column headers.
for (i = 0; i < colCount; i++) {
var headerHtml = convertSpans(trim(headers[i]), self);
html += [" <th", align[i], ">", headerHtml, "</th>\n"].join('');
}
html += "</tr>\n</thead>\n";
// build rows
var rows = body.split('\n');
for (i = 0; i < rows.length; i++) {
if (rows[i].match(/^\s*$/)) // can apply to final row
continue;
// ensure number of rowCells matches colCount
var rowCells = rows[i].split(/ *[|] */);
var lenDiff = colCount - rowCells.length;
for (var j = 0; j < lenDiff; j++)
rowCells.push('');
html += "<tr>\n";
for (j = 0; j < colCount; j++) {
var colHtml = convertSpans(trim(rowCells[j]), self);
html += [" <td", align[j], ">", colHtml, "</td>\n"].join('');
}
html += "</tr>\n";
}
html += "</table>\n";
// replace html with placeholder until postConversion step
return self.hashExtraBlock(html);
}
return text;
};
/******************************************************************
* Footnotes *
*****************************************************************/
// Strip footnote, store in hashes.
Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
var self = this;
text = text.replace(
/\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,
function(wholeMatch, m1, m2) {
m1 = slugify(m1);
m2 += "\n";
m2 = m2.replace(/^[ ]{0,3}/g, "");
self.footnotes[m1] = m2;
return "\n";
});
return text;
};
// Find and convert footnotes references.
Markdown.Extra.prototype.doFootnotes = function(text) {
var self = this;
if(self.isConvertingFootnote === true) {
return text;
}
var footnoteCounter = 0;
text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
var id = slugify(m1);
var footnote = self.footnotes[id];
if (footnote === undefined) {
return "";
}
footnoteCounter++;
self.usedFootnotes.push(id);
var html = '<a href="#fn:' + id + '" id="fnref:' + id
+ '" title="See footnote" class="footnote">' + footnoteCounter
+ '</a>';
return self.hashExtraInline(html);
});
return text;
};
// Print footnotes at the end of the document
Markdown.Extra.prototype.printFootnotes = function(text) {
var self = this;
if (self.usedFootnotes.length === 0) {
return text;
}
text += '\n\n<div class="footnotes">\n<hr>\n<ol>\n\n';
for(var i=0; i<self.usedFootnotes.length; i++) {
var id = self.usedFootnotes[i];
var footnote = self.footnotes[id];
self.isConvertingFootnote = true;
var formattedfootnote = convertSpans(footnote, self);
delete self.isConvertingFootnote;
text += '<li id="fn:'
+ id
+ '">'
+ formattedfootnote
+ ' <a href="#fnref:'
+ id
+ '" title="Return to article" class="reversefootnote">&#8617;</a></li>\n\n';
}
text += '</ol>\n</div>';
return text;
};
/******************************************************************
* Fenced Code Blocks (gfm) *
******************************************************************/
// Find and convert gfm-inspired fenced code blocks into html.
Markdown.Extra.prototype.fencedCodeBlocks = function(text) {
function encodeCode(code) {
code = code.replace(/&/g, "&amp;");
code = code.replace(/</g, "&lt;");
code = code.replace(/>/g, "&gt;");
// These were escaped by PageDown before postNormalization
code = code.replace(/~D/g, "$$");
code = code.replace(/~T/g, "~");
return code;
}
var self = this;
text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function(match, m1, m2) {
var language = m1, codeblock = m2;
// adhere to specified options
var preclass = self.googleCodePrettify ? ' class="prettyprint"' : '';
var codeclass = '';
if (language) {
if (self.googleCodePrettify || self.highlightJs) {
// use html5 language- class names. supported by both prettify and highlight.js
codeclass = ' class="language-' + language + '"';
} else {
codeclass = ' class="' + language + '"';
}
}
var html = ['<pre', preclass, '><code', codeclass, '>',
encodeCode(codeblock), '</code></pre>'].join('');
// replace codeblock with placeholder until postConversion step
return self.hashExtraBlock(html);
});
return text;
};
/******************************************************************
* Definition Lists *
******************************************************************/
// Find and convert markdown extra definition lists into html.
Markdown.Extra.prototype.definitionLists = function(text) {
var wholeList = new RegExp(
['(\\x02\\n?|\\n\\n)' ,
'(?:' ,
'(' , // $1 = whole list
'(' , // $2
'[ ]{0,3}' ,
'((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term
'\\n?' ,
'[ ]{0,3}:[ ]+' , // colon starting definition
')' ,
'([\\s\\S]+?)' ,
'(' , // $4
'(?=\\0x03)' , // \z
'|' ,
'(?=' ,
'\\n{2,}' ,
'(?=\\S)' ,
'(?!' , // Negative lookahead for another term
'[ ]{0,3}' ,
'(?:\\S.*\\n)+?' , // defined term
'\\n?' ,
'[ ]{0,3}:[ ]+' , // colon starting definition
')' ,
'(?!' , // Negative lookahead for another definition
'[ ]{0,3}:[ ]+' , // colon starting definition
')' ,
')' ,
')' ,
')' ,
')'
].join(''),
'gm'
);
var self = this;
text = addAnchors(text);
text = text.replace(wholeList, function(match, pre, list) {
var result = trim(self.processDefListItems(list));
result = "<dl>\n" + result + "\n</dl>";
return pre + self.hashExtraBlock(result) + "\n\n";
});
return removeAnchors(text);
};
// Process the contents of a single definition list, splitting it
// into individual term and definition list items.
Markdown.Extra.prototype.processDefListItems = function(listStr) {
var self = this;
var dt = new RegExp(
['(\\x02\\n?|\\n\\n+)' , // leading line
'(' , // definition terms = $1
'[ ]{0,3}' , // leading whitespace
'(?![:][ ]|[ ])' , // negative lookahead for a definition
// mark (colon) or more whitespace
'(?:\\S.*\\n)+?' , // actual term (not whitespace)
')' ,
'(?=\\n?[ ]{0,3}:[ ])' // lookahead for following line feed
].join(''), // with a definition mark
'gm'
);
var dd = new RegExp(
['\\n(\\n+)?' , // leading line = $1
'(' , // marker space = $2
'[ ]{0,3}' , // whitespace before colon
'[:][ ]+' , // definition mark (colon)
')' ,
'([\\s\\S]+?)' , // definition text = $3
'(?=\\n*' , // stop at next definition mark,
'(?:' , // next term or end of text
'\\n[ ]{0,3}[:][ ]|' ,
'<dt>|\\x03' , // \z
')' ,
')'
].join(''),
'gm'
);
listStr = addAnchors(listStr);
// trim trailing blank lines:
listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n");
// Process definition terms.
listStr = listStr.replace(dt, function(match, pre, termsStr) {
var terms = trim(termsStr).split("\n");
var text = '';
for (var i = 0; i < terms.length; i++) {
var term = terms[i];
// process spans inside dt
term = convertSpans(trim(term), self);
text += "\n<dt>" + term + "</dt>";
}
return text + "\n";
});
// Process actual definitions.
listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) {
if (leadingLine || def.match(/\n{2,}/)) {
// replace marker with the appropriate whitespace indentation
def = Array(markerSpace.length + 1).join(' ') + def;
// process markdown inside definition
// TODO?: currently doesn't apply extensions
def = outdent(def) + "\n\n";
def = "\n" + convertAll(def, self) + "\n";
} else {
// convert span-level markdown inside definition
def = rtrim(def);
def = convertSpans(outdent(def), self);
}
return "\n<dd>" + def + "</dd>\n";
});
return removeAnchors(listStr);
};
})();

View File

@ -1,119 +0,0 @@
//Copyright (C) 2012 Kory Nunn
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/*
This code is not formatted for readability, but rather run-speed and to assist compilers.
However, the code's intention should be transparent.
*** IE SUPPORT ***
If you require this library to work in IE7, add the following after declaring crel.
var testDiv = document.createElement('div'),
testLabel = document.createElement('label');
testDiv.setAttribute('class', 'a');
testDiv['className'] !== 'a' ? crel.attrMap['class'] = 'className':undefined;
testDiv.setAttribute('name','a');
testDiv['name'] !== 'a' ? crel.attrMap['name'] = function(element, value){
element.id = value;
}:undefined;
testLabel.setAttribute('for', 'a');
testLabel['htmlFor'] !== 'a' ? crel.attrMap['for'] = 'htmlFor':undefined;
*/
(function (root, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
root.crel = factory();
}
}(this, function () {
// based on http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
var isNode = typeof Node === 'object'
? function (object) { return object instanceof Node }
: function (object) {
return object
&& typeof object === 'object'
&& typeof object.nodeType === 'number'
&& typeof object.nodeName === 'string';
};
function crel(){
var document = window.document,
args = arguments, //Note: assigned to a variable to assist compilers. Saves about 40 bytes in closure compiler. Has negligable effect on performance.
element = document.createElement(args[0]),
child,
settings = args[1],
childIndex = 2,
argumentsLength = args.length,
attributeMap = crel.attrMap;
// shortcut
if(argumentsLength === 1){
return element;
}
if(typeof settings !== 'object' || isNode(settings)) {
--childIndex;
settings = null;
}
// shortcut if there is only one child that is a string
if((argumentsLength - childIndex) === 1 && typeof args[childIndex] === 'string' && element.textContent !== undefined){
element.textContent = args[childIndex];
}else{
for(; childIndex < argumentsLength; ++childIndex){
child = args[childIndex];
if(child == null){
continue;
}
if(!isNode(child)){
child = document.createTextNode(child);
}
element.appendChild(child);
}
}
for(var key in settings){
if(!attributeMap[key]){
element.setAttribute(key, settings[key]);
}else{
var attr = crel.attrMap[key];
if(typeof attr === 'function'){
attr(element, settings[key]);
}else{
element.setAttribute(attr, settings[key]);
}
}
}
return element;
}
// Used for mapping one kind of attribute to the supported version of that in bad browsers.
// String referenced so that compilers maintain the property name.
crel['attrMap'] = {};
// String referenced so that compilers maintain the property name.
crel["isNode"] = isNode;
return crel;
}));

View File

@ -1,142 +0,0 @@
/*
* waitForImages 1.4.2
* -------------------
* Provides a callback when all images have loaded in your given selector.
* https://github.com/alexanderdickson/waitForImages
*
* Copyright (c) 2013 Alex Dickson
* Licensed under the MIT license.
*/
(function ($) {
// Namespace all events.
var eventNamespace = 'waitForImages';
// CSS properties which contain references to images.
$.waitForImages = {
hasImageProperties: ['backgroundImage', 'listStyleImage', 'borderImage', 'borderCornerImage']
};
// Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded.
$.expr[':'].uncached = function (obj) {
// Ensure we are dealing with an `img` element with a valid `src` attribute.
if (!$(obj).is('img[src!=""]')) {
return false;
}
// Firefox's `complete` property will always be `true` even if the image has not been downloaded.
// Doing it this way works in Firefox.
var img = new Image();
img.src = obj.src;
return !img.complete;
};
$.fn.waitForImages = function (finishedCallback, eachCallback, waitForAll) {
var allImgsLength = 0;
var allImgsLoaded = 0;
// Handle options object.
if ($.isPlainObject(arguments[0])) {
waitForAll = arguments[0].waitForAll;
eachCallback = arguments[0].each;
// This must be last as arguments[0]
// is aliased with finishedCallback.
finishedCallback = arguments[0].finished;
}
// Handle missing callbacks.
finishedCallback = finishedCallback || $.noop;
eachCallback = eachCallback || $.noop;
// Convert waitForAll to Boolean
waitForAll = !! waitForAll;
// Ensure callbacks are functions.
if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
throw new TypeError('An invalid callback was supplied.');
}
return this.each(function () {
// Build a list of all imgs, dependent on what images will be considered.
var obj = $(this);
var allImgs = [];
// CSS properties which may contain an image.
var hasImgProperties = $.waitForImages.hasImageProperties || [];
// To match `url()` references.
// Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri
var matchUrl = /url\(\s*(['"]?)(.*?)\1\s*\)/g;
if (waitForAll) {
// Get all elements (including the original), as any one of them could have a background image.
obj.find('*').andSelf().each(function () {
var element = $(this);
// If an `img` element, add it. But keep iterating in case it has a background image too.
if (element.is('img:uncached')) {
allImgs.push({
src: element.attr('src'),
element: element[0]
});
}
$.each(hasImgProperties, function (i, property) {
var propertyValue = element.css(property);
var match;
// If it doesn't contain this property, skip.
if (!propertyValue) {
return true;
}
// Get all url() of this element.
while (match = matchUrl.exec(propertyValue)) {
allImgs.push({
src: match[2],
element: element[0]
});
}
});
});
} else {
// For images only, the task is simpler.
obj.find('img:uncached')
.each(function () {
allImgs.push({
src: this.src,
element: this
});
});
}
allImgsLength = allImgs.length;
allImgsLoaded = 0;
// If no images found, don't bother.
if (allImgsLength === 0) {
finishedCallback.call(obj[0]);
}
$.each(allImgs, function (i, img) {
var image = new Image();
// Handle the image loading and error with the same callback.
$(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function (event) {
allImgsLoaded++;
// If an error occurred with loading the image, set the third argument accordingly.
eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load');
if (allImgsLoaded == allImgsLength) {
finishedCallback.call(obj[0]);
return false;
}
});
image.src = img.src;
});
});
};
}(jQuery));

View File

@ -1,248 +0,0 @@
/*
* to-markdown - an HTML to Markdown converter
*
* Copyright 2011-2012, Dom Christie
* Licenced under the MIT licence
*
*/
(function() {
var root = this;
var toMarkdown = {};
var isNode = false;
if(typeof module !== 'undefined' && module.exports) {
module.exports = toMarkdown;
root.toMarkdown = toMarkdown;
isNode = true;
}
else {
root.toMarkdown = toMarkdown;
}
toMarkdown.converter = function(options) {
if(options && options.elements && $.isArray(options.elements)) {
ELEMENTS = ELEMENTS.concat(options.elements);
}
this.makeMd = function(input, callback) {
var result;
if(isNode) {
var jsdom = require('jsdom');
jsdom.env({
html: input,
scripts: [
'http://code.jquery.com/jquery-1.6.4.min.js'
],
done: function(errors, window) {
if(typeof callback === 'function') {
callback(process(input, window.$));
}
}
});
}
else {
result = process(input, $);
}
return result;
};
};
var process = function(input, $) {
// Escape potential ol triggers
// see bottom of lists section: http://daringfireball.net/projects/markdown/syntax#list
input = input.replace(/(\d+)\. /g, '$1\\. ');
// Wrap in containing div
var $container = $('<div/>');
var $input = $container.html(input);
// Remove whitespace
$input.find('*:not(pre, code)').contents().filter(function() {
return this.nodeType === 3 && (/^\s+$/.test(this.nodeValue));
}).remove();
var selectors = [];
for(var i = 0, len = ELEMENTS.length; i < len; i++) {
selectors.push(ELEMENTS[i].selector);
}
selectors = selectors.join(',');
while($input.find(selectors).length) {
for(var i = 0, len = ELEMENTS.length; i < len; i++) {
// Find the innermost elements containing NO children that convert to markdown
$matches = $input.find(ELEMENTS[i].selector + ':not(:has("' + selectors + '"))');
$matches.each(function(j, el) {
var $el = $(el);
$el.before(ELEMENTS[i].replacement($el.html(), $el)).remove();
});
}
}
return cleanUp($input.html());
};
// =============
// = Utilities =
// =============
var trimNewLines = function(str) {
return str.replace(/^[\n\r\f]+|[\n\r\f]+$/g, '');
};
var decodeHtmlEntities = function(str) {
return String(str).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
};
var cleanUp = function(string) {
string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace
string = string.replace(/\n\s+\n/g, '\n\n');
string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2
string = decodeHtmlEntities(string);
return string;
};
var strongReplacement = function(innerHTML) {
innerHTML = trimNewLines(innerHTML);
return innerHTML ? '**' + innerHTML + '**' : '';
};
var emReplacement = function(innerHTML) {
innerHTML = trimNewLines(innerHTML);
return innerHTML ? '_' + innerHTML + '_' : '';
};
// ============
// = Elements =
// ============
var ELEMENTS = [
{
selector: 'p',
replacement: function(innerHTML, el) {
innerHTML = $.trim(innerHTML);
return innerHTML ? '\n\n' + innerHTML + '\n\n' : '';
}
},
{
selector: 'br',
replacement: function(innerHTML, el) {
return '\n';
}
},
{
selector: 'h1,h2,h3,h4,h5,h6',
replacement: function(innerHTML, $el) {
innerHTML = $.trim(innerHTML);
var hLevel = $el.prop("nodeName").charAt(1),
prefix = '';
for(var i = 0; i < hLevel; i++) {
prefix += '#';
}
return innerHTML ? '\n\n' + prefix + ' ' + innerHTML + '\n\n' : '';
}
},
{
selector: 'hr',
replacement: function(innerHTML, el) {
return '\n\n* * *\n\n';
}
},
{
selector: 'a[href]',
replacement: function(innerHTML, $el) {
if(innerHTML) {
innerHTML = trimNewLines(innerHTML);
var href = $el.attr('href'),
title = $el.attr('title') || '';
return '[' + innerHTML + ']' + '(' + href + (title ? ' "' + title + '"' : '') + ')';
}
else {
return false;
}
}
},
{
selector: 'b',
replacement: strongReplacement
},
{
selector: 'strong',
replacement: strongReplacement
},
{
selector: 'i',
replacement: emReplacement
},
{
selector: 'em',
replacement: emReplacement
},
{
selector: 'code',
replacement: function(innerHTML, el) {
innerHTML = trimNewLines(innerHTML);
return innerHTML ? '`' + innerHTML + '`' : '';
}
},
{
selector: 'img',
replacement: function(innerHTML, $el) {
var alt = $el.attr('alt') || '',
src = $el.attr('src') || '',
title = $el.attr('title') || '';
return '![' + alt + ']' + '(' + src + (title ? ' "' + title + '"' : '') + ')';
}
},
{
selector: 'pre',
replacement: function(innerHTML, el) {
if(/^\s*\`/.test(innerHTML)) {
innerHTML = innerHTML.replace(/\`/g, '');
return ' ' + innerHTML.replace(/\n/g, '\n ');
}
else {
return '';
}
}
},
{
selector: 'li',
replacement: function(innerHTML, $el) {
innerHTML = innerHTML.replace(/^\s+|\s+$/, '').replace(/\n/gm, '\n ');
var prefix = '* ';
var suffix = '';
var $parent = $el.parent();
var $children = $parent.contents().filter(function() {
return (this.nodeType === 1 && this.nodeName === 'LI') || (this.nodeType === 3);
});
var index = $children.index($el) + 1;
prefix = $parent.is('ol') ? index + '. ' : '* ';
if(index == $children.length) {
if(!$el.parents('li').length) {
suffix = '\n';
}
innerHTML = innerHTML.replace(/\s+$/, ''); // Trim
$el.unwrap();
}
return prefix + innerHTML + suffix + '\n';
}
},
{
selector: 'blockquote',
replacement: function(innerHTML, el) {
innerHTML = innerHTML = $.trim(innerHTML).replace(/\n{3,}/g, '\n\n');
innerHTML = innerHTML.replace(/\n/g, '\n&gt; ');
return "&gt; " + innerHTML;
}
}
];
var NON_MD_BLOCK_ELEMENTS = ['address', 'article', 'aside', 'audio', 'canvas', 'div', 'dl', 'dd', 'dt',
'fieldset', 'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'output',
'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'section', 'video'];
})();

View File

@ -21,17 +21,17 @@ requirejs.config({
paths: {
jquery: 'bower-libs/jquery/jquery',
underscore: 'bower-libs/underscore/underscore',
crel: 'libs/crel',
crel: 'bower-libs/crel/crel',
jgrowl: 'bower-libs/jgrowl/jquery.jgrowl',
mousetrap: 'bower-libs/mousetrap/mousetrap',
toMarkdown: 'libs/to-markdown',
toMarkdown: 'bower-libs/to-markdown/src/to-markdown',
text: 'bower-libs/requirejs-text/text',
mathjax: '../lib/MathJax/MathJax.js?config=TeX-AMS_HTML',
bootstrap: 'bower-libs/bootstrap/dist/js/bootstrap',
requirejs: 'bower-libs/requirejs/require',
'google-code-prettify': 'bower-libs/google-code-prettify/src/prettify',
highlightjs: 'bower-libs/highlightjs/highlight.pack',
'jquery-waitforimages': 'libs/jquery.waitforimages',
'jquery-waitforimages': 'bower-libs/waitForImages/src/jquery.waitforimages',
'jquery-ui': 'bower-libs/jquery-ui/ui/jquery-ui',
'jquery-ui-core': 'bower-libs/jquery-ui/ui/jquery.ui.core',
'jquery-ui-widget': 'bower-libs/jquery-ui/ui/jquery.ui.widget',
@ -43,13 +43,13 @@ requirejs.config({
FileSaver: 'bower-libs/FileSaver/FileSaver',
stacktrace: 'bower-libs/stacktrace/stacktrace',
'requirejs-text': 'bower-libs/requirejs-text/text',
'bootstrap-tour': 'bower-libs/bootstrap-tour/build/js/bootstrap-tour'
},
map: {
'ace': {
'bootstrap-tour': 'bower-libs/bootstrap-tour/build/js/bootstrap-tour',
css_browser_selector: 'bower-libs/css_browser_selector/css_browser_selector',
'jquery-mousewheel': 'bower-libs/jquery-mousewheel/jquery.mousewheel',
'pagedown-ace': 'bower-libs/pagedown-ace/Markdown.Editor',
'pagedown-extra': 'bower-libs/pagedown-extra/Markdown.Extra',
'ace/requirejs/text': 'libs/ace_text',
'ace/commands/default_commands': 'libs/ace_commands'
}
},
shim: {
underscore: {
@ -103,14 +103,14 @@ requirejs.config({
'jquery-ui-core': [
'jquery'
],
'libs/Markdown.Extra': [
'libs/Markdown.Converter',
'pagedown-extra': [
'pagedown-ace',
'google-code-prettify',
'highlightjs'
],
'libs/Markdown.Editor': [
'libs/Markdown.Converter'
],
'pagedown-ace': [
'bower-libs/pagedown-ace/Markdown.Converter'
]
}
});

View File

@ -1,6 +1,7 @@
define([
"underscore",
"config"
"config",
"storage"
], function(_) {
var settings = {

View File

@ -155,5 +155,17 @@ define([
version = "v9";
}
// Upgrade from v9 to v10
if(version == "v9") {
if(_.has(localStorage, 'settings')) {
settings = JSON.parse(localStorage.settings);
delete settings.editorFontFamily;
delete settings.editorFontSize;
settings.template && (settings.template = settings.template.replace('http://benweet.github.io/stackedit/css/main-min.css', 'http://benweet.github.io/stackedit/res-min/themes/default.css'));
localStorage.settings = JSON.stringify(settings);
}
version = "v10";
}
localStorage["version"] = version;
});

View File

@ -923,6 +923,9 @@ blockquote {
ul,ol {
margin-bottom: 15px;
ul,ol {
margin-bottom: 15px;
}
}
/*****************************
@ -1000,18 +1003,6 @@ ul,ol {
overflow: auto;
}
.wmd-button-row {
padding: 0;
}
.wmd-spacer {
display: none;
}
.wmd-spacer + .wmd-button {
margin-left: 20px;
}
.wmd-prompt-background {
display: none;
}

View File

@ -4,8 +4,8 @@
<title>StackEdit Viewer</title>
<link rel="canonical"
href="http://benweet.github.io/stackedit/viewer.html">
<link rel="icon" href="img/stackedit-32.ico" type="image/x-icon">
<link rel="shortcut icon" href="img/stackedit-32.ico"
<link rel="icon" href="res-min/img/stackedit-32.ico" type="image/x-icon">
<link rel="shortcut icon" href="res-min/img/stackedit-32.ico"
type="image/x-icon">
<meta name="description"
content="StackEdit is a free, open-source Markdown editor based on PageDown, the Markdown library used by Stack Overflow and the other Stack Exchange sites.">