Move showdown extensions to ghost-showdown

no issue

- We already maintain our own fork of showdown, this moves our custom extensions to our fork
- Code duplication is removed
- Tests are also moved to the other repo
This commit is contained in:
Hannah Wolfe 2015-03-16 19:30:50 +00:00
parent f17dbfc6f0
commit 085caa9370
6 changed files with 5 additions and 409 deletions

View File

@ -36,6 +36,10 @@ app.import('bower_components/ember-load-initializers/ember-load-initializers.js'
app.import('bower_components/validator-js/validator.js');
app.import('bower_components/rangyinputs/rangyinputs-jquery-src.js');
app.import('bower_components/showdown-ghost/src/showdown.js');
app.import('bower_components/showdown-ghost/src/extensions/ghostgfm.js');
app.import('bower_components/showdown-ghost/src/extensions/ghostimagepreview.js');
app.import('bower_components/showdown-ghost/src/extensions/footnotes.js');
app.import('bower_components/showdown-ghost/src/extensions/highlight.js');
app.import('bower_components/moment/moment.js');
app.import('bower_components/keymaster/keymaster.js');
app.import('bower_components/device/lib/device.js');
@ -49,9 +53,4 @@ app.import('bower_components/google-caja/html-css-sanitizer-bundle.js');
app.import('bower_components/nanoscroller/bin/javascripts/jquery.nanoscroller.js');
app.import('bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.js');
app.import('vendor/showdown/extensions/ghostgfm.js');
app.import('vendor/showdown/extensions/ghostimagepreview.js');
app.import('vendor/showdown/extensions/ghostfootnotes.js');
app.import('vendor/showdown/extensions/ghosthighlight.js');
module.exports = app.toTree();

View File

@ -23,7 +23,7 @@
"normalize-scss": "~3.0.1",
"nprogress": "0.1.2",
"rangyinputs": "1.2.0",
"showdown-ghost": "0.3.4",
"showdown-ghost": "0.3.6",
"validator-js": "3.28.0"
},
"devDependencies": {

View File

@ -1,110 +0,0 @@
/* jshint node:true, browser:true */
// Adds footnote syntax as per Markdown Extra:
//
// https://michelf.ca/projects/php-markdown/extra/#footnotes
//
// That's some text with a footnote.[^1]
//
// [^1]: And that's the footnote.
//
// That's the second paragraph.
//
// Also supports [^n] if you don't want to worry about preserving
// the footnote order yourself.
function replaceInlineFootnotes(text) {
// Inline footnotes e.g. "foo[^1]"
var inlineRegex = /(?!^)\[\^(\d+|n)\]/gim,
i = 0;
return text.replace(inlineRegex, function (match, n) {
// We allow both automatic and manual footnote numbering
if (n === 'n') {
n = i + 1;
}
var s = '<sup id="fnref:' + n + '">' +
'<a href="#fn:' + n + '" rel="footnote">' + n + '</a>' +
'</sup>';
i += 1;
return s;
});
}
function replaceEndFootnotes(text, converter) {
// Expanded footnotes at the end e.g. "[^1]: cool stuff"
var endRegex = /\[\^(\d+|n)\]: ([\s\S]*?)$(?! )/gim,
m = text.match(endRegex),
total = m ? m.length : 0,
i = 0;
return text.replace(endRegex, function (match, n, content) {
if (n === 'n') {
n = i + 1;
}
content = content.replace(/\n /g, '<br>');
content = converter.makeHtml(content);
content = content.replace(/<\/p>$/, '');
var s = '<li class="footnote" id="fn:' + n + '">' +
content + ' <a href="#fnref:' + n +
'" title="return to article">↩</a>' +
'</p></li>';
if (i === 0) {
s = '<div class="footnotes"><ol>' + s;
}
if (i === total - 1) {
s = s + '</ol></div>';
}
i += 1;
return s;
});
}
(function () {
var footnotes = function (converter) {
return [
{
type: 'lang',
filter: function (text) {
var preExtractions = {},
hashID = 0;
function hashId() {
return hashID += 1;
}
// Extract pre blocks
text = text.replace(/```[\s\S]*?\n```/gim, function (x) {
var hash = hashId();
preExtractions[hash] = x;
return '{gfm-js-extract-pre-' + hash + '}';
}, 'm');
text = replaceInlineFootnotes(text);
text = replaceEndFootnotes(text, converter);
// replace extractions
text = text.replace(/\{gfm-js-extract-pre-([0-9]+)\}/gm, function (x, y) {
return preExtractions[y];
});
return text;
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) {
window.Showdown.extensions.footnotes = footnotes;
}
// Server-side export
if (typeof module !== 'undefined') {
module.exports = footnotes;
}
}());

View File

@ -1,171 +0,0 @@
/* jshint node:true, browser:true */
// Ghost GFM
// Taken and extended from the Showdown Github Extension (WIP)
// Makes a number of pre and post-processing changes to the way markdown is handled
//
// ~~strike-through~~ -> <del>strike-through</del> (Pre)
// GFM newlines & underscores (Pre)
// 4 or more underscores (Pre)
// autolinking / custom image handling (Post)
(function () {
var ghostgfm = function () {
return [
{
// strike-through
// NOTE: showdown already replaced "~" with "~T", so we need to adjust accordingly.
type: 'lang',
regex: '(~T){2}([^~]+)(~T){2}',
replace: function (match, prefix, content) {
return '<del>' + content + '</del>';
}
},
{
// Escaped tildes
// NOTE: showdown already replaced "~" with "~T", and this char doesn't get escaped properly.
type: 'lang',
regex: '\\\\(~T)',
replace: function (match, content) {
return content;
}
},
{
// GFM newline and underscore modifications, happen BEFORE showdown
type: 'lang',
filter: function (text) {
var extractions = {},
imageMarkdownRegex = /^(?:\{(.*?)\})?!(?:\[([^\n\]]*)\])(?:\(([^\n\]]*)\))?$/gim,
hashID = 0;
function hashId() {
/*jshint plusplus:false*/
return hashID++;
}
// Extract pre blocks
text = text.replace(/<pre>[\s\S]*?<\/pre>/gim, function (x) {
var hash = hashId();
extractions[hash] = x;
return '{gfm-js-extract-pre-' + hash + '}';
}, 'm');
// Extract code blocks
text = text.replace(/```[\s\S]*```/gim, function (x) {
var hash = hashId();
extractions[hash] = x;
return '{gfm-js-extract-code-' + hash + '}';
}, 'm');
// prevent foo_bar and foo_bar_baz from ending up with an italic word in the middle
text = text.replace(/(^(?! {4}|\t)(?!__)\w+_\w+_\w[\w_]*)/gm, function (x) {
return x.replace(/_/gm, '\\_');
});
text = text.replace(/\{gfm-js-extract-code-([0-9]+)\}/gm, function (x, y) {
return extractions[y];
});
// in very clear cases, let newlines become <br /> tags
/*jshint -W049 */
text = text.replace(/^[\w\<\'\'][^\n]*\n+/gm, function (x) {
return x.match(/\n{2}/) ? x : x.trim() + ' \n';
});
/*jshint +W049 */
// better URL support, but no title support
text = text.replace(imageMarkdownRegex, function (match, key, alt, src) {
if (src) {
return '<img src="' + src + '" alt="' + alt + '" />';
}
return '';
});
text = text.replace(/\{gfm-js-extract-pre-([0-9]+)\}/gm, function (x, y) {
return '\n\n' + extractions[y];
});
return text;
}
},
// 4 or more inline underscores e.g. Ghost rocks my _____!
{
type: 'lang',
filter: function (text) {
return text.replace(/([^_\n\r])(_{4,})/g, function (match, prefix, underscores) {
return prefix + underscores.replace(/_/g, '&#95;');
});
}
},
{
// GFM autolinking & custom image handling, happens AFTER showdown
type: 'html',
filter: function (text) {
var refExtractions = {},
preExtractions = {},
hashID = 0;
function hashId() {
/*jshint plusplus:false*/
return hashID++;
}
// Extract pre blocks
text = text.replace(/<(pre|code)>[\s\S]*?<\/(\1)>/gim, function (x) {
var hash = hashId();
preExtractions[hash] = x;
return '{gfm-js-extract-pre-' + hash + '}';
}, 'm');
// filter out def urls
// from Marked https://github.com/chjj/marked/blob/master/lib/marked.js#L24
text = text.replace(/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/gmi,
function (x) {
var hash = hashId();
refExtractions[hash] = x;
return '{gfm-js-extract-ref-url-' + hash + '}';
});
// match a URL
// adapted from https://gist.github.com/jorilallo/1283095#L158
// and http://blog.stevenlevithan.com/archives/mimic-lookbehind-javascript
/*jshint -W049 */
text = text.replace(/(\]\(|\]|\[|<a[^\>]*?\>)?https?\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!]/gmi,
function (wholeMatch, lookBehind, matchIndex) {
// Check we are not inside an HTML tag
var left = text.slice(0, matchIndex), right = text.slice(matchIndex);
if ((left.match(/<[^>]+$/) && right.match(/^[^>]*>/)) || lookBehind) {
return wholeMatch;
}
// If we have a matching lookBehind, this is a failure, else wrap the match in <a> tag
return lookBehind ? wholeMatch : '<a href="' + wholeMatch + '">' + wholeMatch + '</a>';
});
/*jshint +W049 */
// replace extractions
text = text.replace(/\{gfm-js-extract-pre-([0-9]+)\}/gm, function (x, y) {
return preExtractions[y];
});
text = text.replace(/\{gfm-js-extract-ref-url-([0-9]+)\}/gi, function (x, y) {
return '\n\n' + refExtractions[y];
});
return text;
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) {
window.Showdown.extensions.ghostgfm = ghostgfm;
}
// Server-side export
if (typeof module !== 'undefined') {
module.exports = ghostgfm;
}
}());

View File

@ -1,71 +0,0 @@
/* jshint node:true, browser:true, -W044 */
// Adds highlight syntax as per RedCarpet:
//
// https://github.com/vmg/redcarpet
//
// This is ==highlighted==. It looks like this: <mark>highlighted</mark>
(function () {
var highlight = function () {
return [
{
type: 'html',
filter: function (text) {
var highlightRegex = /(=){2}([\s\S]+?)(=){2}/gim,
preExtractions = {},
codeExtractions = {},
hashID = 0;
function hashId() {
return hashID += 1;
}
// Extract pre blocks
text = text.replace(/<pre>[\s\S]*?<\/pre>/gim, function (x) {
var hash = hashId();
preExtractions[hash] = x;
return '{gfm-js-extract-pre-' + hash + '}';
}, 'm');
// Extract code blocks
text = text.replace(/<code>[\s\S]*?<\/code>/gim, function (x) {
var hash = hashId();
codeExtractions[hash] = x;
return '{gfm-js-extract-code-' + hash + '}';
}, 'm');
text = text.replace(highlightRegex, function (match, n, content) {
// Check the content isn't just an `=`
if (!/^=+$/.test(content)) {
return '<mark>' + content + '</mark>';
}
return match;
});
// replace pre extractions
text = text.replace(/\{gfm-js-extract-pre-([0-9]+)\}/gm, function (x, y) {
return preExtractions[y];
});
// replace code extractions
text = text.replace(/\{gfm-js-extract-code-([0-9]+)\}/gm, function (x, y) {
return codeExtractions[y];
});
return text;
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) {
window.Showdown.extensions.highlight = highlight;
}
// Server-side export
if (typeof module !== 'undefined') {
module.exports = highlight;
}
}());

View File

@ -1,51 +0,0 @@
/* jshint node:true, browser:true */
// Ghost Image Preview
//
// Manages the conversion of image markdown `![]()` from markdown into the HTML image preview
// This provides a dropzone and other interface elements for adding images
// Is only used in the admin client.
var Ghost = Ghost || {};
(function () {
var ghostimagepreview = function () {
return [
// ![] image syntax
{
type: 'lang',
filter: function (text) {
var imageMarkdownRegex = /^!(?:\[([^\n\]]*)\])(?:\(([^\n\]]*)\))?$/gim,
/* regex from isURL in node-validator. Yum! */
uriRegex = /^(?!mailto:)(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i,
pathRegex = /^(\/)?([^\/\0]+(\/)?)+$/i;
return text.replace(imageMarkdownRegex, function (match, alt, src) {
var result = '',
output;
if (src && (src.match(uriRegex) || src.match(pathRegex))) {
result = '<img class="js-upload-target" src="' + src + '"/>';
}
output = '<section class="js-drop-zone image-uploader">' +
result +
'<div class="description">Add image of <strong>' + alt + '</strong></div>' +
'<input data-url="upload" class="js-fileupload main fileupload" type="file" name="uploadimage">' +
'</section>';
return output;
});
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) {
window.Showdown.extensions.ghostimagepreview = ghostimagepreview;
}
// Server-side export
if (typeof module !== 'undefined') {
module.exports = ghostimagepreview;
}
}());