mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
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:
parent
0b9455e8bb
commit
1db59e12ee
17 changed files with 8 additions and 1420 deletions
|
@ -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();
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}());
|
171
core/client/vendor/showdown/extensions/ghostgfm.js
vendored
171
core/client/vendor/showdown/extensions/ghostgfm.js
vendored
|
@ -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, '_');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}());
|
|
@ -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;
|
||||
}
|
||||
}());
|
|
@ -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;
|
||||
}
|
||||
}());
|
|
@ -5,10 +5,7 @@ var _ = require('lodash'),
|
|||
sequence = require('../utils/sequence'),
|
||||
errors = require('../errors'),
|
||||
Showdown = require('showdown-ghost'),
|
||||
ghostgfm = require('../../shared/lib/showdown/extensions/ghostgfm'),
|
||||
footnotes = require('../../shared/lib/showdown/extensions/ghostfootnotes'),
|
||||
highlight = require('../../shared/lib/showdown/extensions/ghosthighlight'),
|
||||
converter = new Showdown.converter({extensions: [ghostgfm, footnotes, highlight]}),
|
||||
converter = new Showdown.converter({extensions: ['ghostgfm', 'footnotes', 'highlight']}),
|
||||
ghostBookshelf = require('./base'),
|
||||
xmlrpc = require('../xmlrpc'),
|
||||
sitemap = require('../data/sitemap'),
|
||||
|
|
|
@ -1,123 +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(/<(pre|code)>[\s\S]*?<\/(\1)>/gim, function (x) {
|
||||
var hash = hashId();
|
||||
preExtractions[hash] = x;
|
||||
return '{gfm-js-extract-pre-' + hash + '}';
|
||||
}, 'm');
|
||||
|
||||
text = text.replace(/```[\s\S]*?\n```/gim, function (x) {
|
||||
var hash = hashId();
|
||||
preExtractions[hash] = x;
|
||||
return '{gfm-js-extract-pre-' + hash + '}';
|
||||
}, 'm');
|
||||
|
||||
// Extract code blocks
|
||||
text = text.replace(/`[\s\S]*?`/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;
|
||||
}
|
||||
}());
|
|
@ -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, '_');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
// 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|script)>[\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;
|
||||
}
|
||||
}());
|
|
@ -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;
|
||||
}
|
||||
}());
|
|
@ -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;
|
||||
}
|
||||
}());
|
|
@ -10,12 +10,7 @@ var should = require('should'),
|
|||
|
||||
// Stuff we are testing
|
||||
Showdown = require('showdown-ghost'),
|
||||
ghostgfm = require('../../shared/lib/showdown/extensions/ghostgfm'),
|
||||
ghostimagepreview = require('../../shared/lib/showdown/extensions/ghostimagepreview'),
|
||||
footnotes = require('../../shared/lib/showdown/extensions/ghostfootnotes'),
|
||||
highlight = require('../../shared/lib/showdown/extensions/ghosthighlight'),
|
||||
|
||||
converter = new Showdown.converter({extensions: [ghostimagepreview, ghostgfm, footnotes, highlight]});
|
||||
converter = new Showdown.converter({extensions: ['ghostimagepreview', 'ghostgfm', 'footnotes', 'highlight']});
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
/**
|
||||
* Tests the footnotes extension for showdown
|
||||
*
|
||||
*/
|
||||
|
||||
/*globals describe, it */
|
||||
/*jshint expr:true*/
|
||||
var should = require('should'),
|
||||
|
||||
// Stuff we are testing
|
||||
ghostfootnotes = require('../../shared/lib/showdown/extensions/ghostfootnotes'),
|
||||
Showdown = require('showdown-ghost'),
|
||||
converter = new Showdown.converter({extensions: []});
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
||||
function _ExecuteExtension(ext, text) {
|
||||
if (ext.regex) {
|
||||
var re = new RegExp(ext.regex, 'g');
|
||||
return text.replace(re, ext.replace);
|
||||
} else if (ext.filter) {
|
||||
return ext.filter(text);
|
||||
}
|
||||
}
|
||||
|
||||
function _ConvertPhrase(testPhrase) {
|
||||
return ghostfootnotes(converter).reduce(function (text, ext) {
|
||||
return _ExecuteExtension(ext, text);
|
||||
}, testPhrase);
|
||||
}
|
||||
|
||||
describe('Ghost footnotes showdown extension', function () {
|
||||
/*jslint regexp: true */
|
||||
|
||||
it('should export an array of methods for processing', function () {
|
||||
ghostfootnotes.should.be.a.function;
|
||||
ghostfootnotes().should.be.an.Array;
|
||||
|
||||
ghostfootnotes().forEach(function (processor) {
|
||||
processor.should.be.an.Object;
|
||||
processor.should.have.property('type');
|
||||
processor.type.should.be.a.String;
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace inline footnotes with the right html', function () {
|
||||
var testPhrase = {
|
||||
input: 'foo_bar[^1]',
|
||||
output: /<sup id="fnref:1"><a href="#fn:1" rel="footnote">1<\/a><\/sup>/
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should handle n-digit footnotes', function () {
|
||||
var testPhrase = {
|
||||
input: 'foo_bar[^42]',
|
||||
output: /<sup id="fnref:42"><a href="#fn:42" rel="footnote">42<\/a><\/sup>/
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should replace end footnotes with the right html', function () {
|
||||
var testPhrase = {
|
||||
input: '[^1]: foo bar',
|
||||
output: /<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>foo bar <a href="#fnref:1" title="return to article">↩<\/a><\/p><\/li><\/ol><\/div>/
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should expand Markdown inside footnotes', function () {
|
||||
var testPhrase = {
|
||||
input: '[^1]: *foo*',
|
||||
output: /<em>foo<\/em>/
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should number multiple footnotes correctly', function () {
|
||||
var testPhrase = {
|
||||
input: 'foo[^1] bar[^n] etc[^2]',
|
||||
output: /foo<sup id="fnref:1"><a href="#fn:1" rel="footnote">1<\/a><\/sup> bar<sup id="fnref:2"><a href="#fn:2" rel="footnote">2<\/a><\/sup> etc<sup id="fnref:2"><a href="#fn:2" rel="footnote">2<\/a><\/sup>/
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should put everything together', function () {
|
||||
// Tests for some interaction bugs between components e.g.
|
||||
// confusing the end form and the inline form
|
||||
var testPhrase = {
|
||||
input: 'foo bar[^1] is a very[^n] foo bar[^1]\n' +
|
||||
'[^n]: a metasyntactic variable\n' +
|
||||
'[^n]: this is hard to measure',
|
||||
output: 'foo bar<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup> is a very<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup> foo bar<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>\n' +
|
||||
'<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>a metasyntactic variable <a href="#fnref:1" title="return to article">↩</a></p></li>\n' +
|
||||
'<li class="footnote" id="fn:2"><p>this is hard to measure <a href="#fnref:2" title="return to article">↩</a></p></li></ol></div>'
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should show markdown inside code block', function () {
|
||||
var testPhrase = {
|
||||
input: '<code>[^n]<\/code>',
|
||||
output: '<code>[^n]<\/code>'
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should show markdown inside pre block', function () {
|
||||
var testPhrase = {
|
||||
input: '<pre>[^n]<\/pre>',
|
||||
output: '<pre>[^n]<\/pre>'
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should show markdown inside single tick', function () {
|
||||
var testPhrase = {
|
||||
input: '`[^n]`',
|
||||
output: '`[^n]`'
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should show markdown inside triple tick', function () {
|
||||
var testPhrase = {
|
||||
input: '```[^n]```',
|
||||
output: '```[^n]```'
|
||||
}, processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
|
@ -1,285 +0,0 @@
|
|||
/**
|
||||
* Tests the github extension for showdown
|
||||
*
|
||||
*/
|
||||
|
||||
/*globals describe, it */
|
||||
/*jshint expr:true*/
|
||||
var should = require('should'),
|
||||
|
||||
// Stuff we are testing
|
||||
ghostgfm = require('../../shared/lib/showdown/extensions/ghostgfm');
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
||||
function _ExecuteExtension(ext, text) {
|
||||
if (ext.regex) {
|
||||
var re = new RegExp(ext.regex, 'g');
|
||||
return text.replace(re, ext.replace);
|
||||
} else if (ext.filter) {
|
||||
return ext.filter(text);
|
||||
}
|
||||
}
|
||||
|
||||
function _ConvertPhrase(testPhrase) {
|
||||
return ghostgfm().reduce(function (text, ext) {
|
||||
return _ExecuteExtension(ext, text);
|
||||
}, testPhrase);
|
||||
}
|
||||
|
||||
describe('Ghost GFM showdown extension', function () {
|
||||
/*jslint regexp: true */
|
||||
|
||||
it('should export an array of methods for processing', function () {
|
||||
ghostgfm.should.be.a.function;
|
||||
ghostgfm().should.be.an.Array;
|
||||
|
||||
ghostgfm().forEach(function (processor) {
|
||||
processor.should.be.an.Object;
|
||||
processor.should.have.property('type');
|
||||
processor.type.should.be.a.String;
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace showdown strike through with html', function () {
|
||||
var testPhrase = {input: '~T~Tfoo_bar~T~T', output: /<del>foo_bar<\/del>/},
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
// The image is the entire markup, so the image box should be too
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should honour escaped tildes', function () {
|
||||
/*jshint -W044 */
|
||||
var testPhrase = {input: '\\~T\\~Tfoo_bar\\~T\\~T', output: /~T~Tfoo_bar~T~T/},
|
||||
/*jshint +W044 */
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
// The image is the entire markup, so the image box should be too
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should allow 4 underscores', function () {
|
||||
var testPhrase = {input: 'Ghost ____', output: /Ghost\s(?:_){4}$/},
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should auto-link URL in text with markdown syntax', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: 'http://google.co.uk',
|
||||
output: /^<a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: 'https://atest.com/fizz/buzz?baz=fizzbuzz',
|
||||
output: /^<a href="https:\/\/atest.com\/fizz\/buzz\?baz=fizzbuzz">https:\/\/atest.com\/fizz\/buzz\?baz=fizzbuzz<\/a>$/
|
||||
},
|
||||
{
|
||||
input: 'Some text http://www.google.co.uk some other text',
|
||||
output: /^Some text <a href="http:\/\/www.google.co.uk">http:\/\/www.google.co.uk<\/a> some other text$/
|
||||
},
|
||||
{
|
||||
input: 'Some [ text http://www.google.co.uk some other text',
|
||||
output: /^Some \[ text <a href="http:\/\/www.google.co.uk">http:\/\/www.google.co.uk<\/a> some other text$/
|
||||
},
|
||||
{
|
||||
input: 'Some [ text (http://www.google.co.uk) some other text',
|
||||
output: /^Some \[ text \(<a href="http:\/\/www.google.co.uk">http:\/\/www.google.co.uk<\/a>\) some other text$/
|
||||
},
|
||||
{
|
||||
input: ' http://google.co.uk ',
|
||||
output: /^ <a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a> $/
|
||||
},
|
||||
{
|
||||
input: '>http://google.co.uk',
|
||||
output: /^><a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '> http://google.co.uk',
|
||||
output: /^> <a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '<>>> http://google.co.uk',
|
||||
output: /^<>>> <a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '<>>>http://google.co.uk',
|
||||
output: /^<>>><a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '<some text>>>http://google.co.uk',
|
||||
output: /^<some text>>><a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '<strong>http://google.co.uk',
|
||||
output: /^<strong><a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '# http://google.co.uk',
|
||||
output: /^# <a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '#http://google.co.uk',
|
||||
output: /^#<a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '* http://google.co.uk',
|
||||
output: /^\* <a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
}
|
||||
],
|
||||
processedMarkup;
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT auto-link URL in HTML', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: '<img src="http://placekitten.com/50">',
|
||||
output: /^<img src=\"http:\/\/placekitten.com\/50\">$/
|
||||
},
|
||||
{
|
||||
input: '<img src="http://placekitten.com/50" />',
|
||||
output: /^<img src=\"http:\/\/placekitten.com\/50\" \/>$/
|
||||
},
|
||||
{
|
||||
input: '<script type="text/javascript" src="http://google.co.uk"></script>',
|
||||
output: /^<script type=\"text\/javascript\" src=\"http:\/\/google.co.uk\"><\/script>$/
|
||||
},
|
||||
{
|
||||
input: '<script>var url = "http://google.co.uk"</script>',
|
||||
output: /^<script>var url = \"http:\/\/google.co.uk\"<\/script>$/
|
||||
},
|
||||
{
|
||||
input: '<a href="http://facebook.com">http://google.co.uk</a>',
|
||||
output: /^<a href=\"http:\/\/facebook.com\">http:\/\/google.co.uk<\/a>$/
|
||||
},
|
||||
{
|
||||
input: '<a href="http://facebook.com">test</a> http://google.co.uk',
|
||||
output: /^<a href="http:\/\/facebook.com">test<\/a> <a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>$/
|
||||
}
|
||||
],
|
||||
processedMarkup;
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT auto-link reference URL', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: '[1]: http://www.google.co.uk',
|
||||
output: /^\n\n\[1\]: http:\/\/www.google.co.uk$/
|
||||
},
|
||||
{
|
||||
input: '[http://www.google.co.uk]: http://www.google.co.uk',
|
||||
output: /^\n\n\[http:\/\/www.google.co.uk]: http:\/\/www.google.co.uk$/
|
||||
},
|
||||
{
|
||||
input: '[1]: http://dsurl.stuff/something.jpg',
|
||||
output: /^\n\n\[1\]: http:\/\/dsurl.stuff\/something.jpg$/
|
||||
},
|
||||
{
|
||||
input: '[1]:http://www.google.co.uk',
|
||||
output: /^\n\n\[1\]:http:\/\/www.google.co.uk$/
|
||||
},
|
||||
{
|
||||
input: ' [1]:http://www.google.co.uk',
|
||||
output: /^\n\n \[1\]:http:\/\/www.google.co.uk$/
|
||||
},
|
||||
{
|
||||
input: '[http://www.google.co.uk]: http://www.google.co.uk',
|
||||
output: /^\n\n\[http:\/\/www.google.co.uk\]: http:\/\/www.google.co.uk$/
|
||||
}
|
||||
],
|
||||
processedMarkup;
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT auto-link URL in link or image markdown', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: '[1](http://google.co.uk)',
|
||||
output: /^\[1\]\(http:\/\/google.co.uk\)$/
|
||||
},
|
||||
{
|
||||
input: ' [1](http://google.co.uk)',
|
||||
output: /^ \[1\]\(http:\/\/google.co.uk\)$/
|
||||
},
|
||||
{
|
||||
input: '[http://google.co.uk](http://google.co.uk)',
|
||||
output: /^\[http:\/\/google.co.uk\]\(http:\/\/google.co.uk\)$/
|
||||
},
|
||||
{
|
||||
input: '[http://google.co.uk][id]',
|
||||
output: /^\[http:\/\/google.co.uk\]\[id\]$/
|
||||
},
|
||||
{
|
||||
input: '',
|
||||
output: /^<img src=\"http:\/\/google.co.uk\/kitten.jpg\" alt=\"1\" \/>$/
|
||||
},
|
||||
{
|
||||
input: ' ',
|
||||
output: /^ !\[1\]\(http:\/\/google.co.uk\/kitten.jpg\)$/
|
||||
},
|
||||
{
|
||||
input: '',
|
||||
output: /^<img src=\"http:\/\/google.co.uk\/kitten.jpg\" alt=\"http:\/\/google.co.uk\/kitten.jpg\" \/>$/
|
||||
}
|
||||
],
|
||||
processedMarkup;
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
|
||||
// behaviour if you add a gap between [] and ()
|
||||
it('should auto-link if markdown is invalid', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: '[1] (http://google.co.uk)',
|
||||
output: /^\[1\] \(<a href="http:\/\/google.co.uk">http:\/\/google.co.uk<\/a>\)$/
|
||||
},
|
||||
{
|
||||
input: '![1] (http://google.co.uk/kitten.jpg)',
|
||||
output:
|
||||
/^!\[1\] \(<a href="http:\/\/google.co.uk\/kitten.jpg">http:\/\/google.co.uk\/kitten.jpg<\/a>\)$/
|
||||
}
|
||||
],
|
||||
processedMarkup;
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not output image if there is no src', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: '![anything here]()',
|
||||
output: /^$/
|
||||
}
|
||||
],
|
||||
processedMarkup;
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,76 +0,0 @@
|
|||
/**
|
||||
* Test the ghostdown extension
|
||||
*
|
||||
* Only ever runs on the client (i.e in the editor)
|
||||
* Server processes showdown without it so there can never be an image upload form in a post.
|
||||
*/
|
||||
|
||||
/*globals describe, it */
|
||||
/*jshint expr:true*/
|
||||
var should = require('should'),
|
||||
|
||||
// Stuff we are testing
|
||||
ghostimagepreview = require('../../shared/lib/showdown/extensions/ghostimagepreview');
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
||||
describe('Ghost Image Preview showdown extension', function () {
|
||||
it('should export an array of methods for processing', function () {
|
||||
ghostimagepreview.should.be.a.function;
|
||||
ghostimagepreview().should.be.an.instanceof(Array);
|
||||
|
||||
ghostimagepreview().forEach(function (processor) {
|
||||
processor.should.be.an.Object;
|
||||
processor.should.have.property('type');
|
||||
processor.should.have.property('filter');
|
||||
processor.type.should.be.a.String;
|
||||
processor.filter.should.be.a.function;
|
||||
});
|
||||
});
|
||||
|
||||
it('should accurately detect images in markdown', function () {
|
||||
[
|
||||
'![]',
|
||||
'![]()',
|
||||
'![image and another,/ image]',
|
||||
'![image and another,/ image]()',
|
||||
'',
|
||||
''
|
||||
/* No ref-style for now
|
||||
'![][]',
|
||||
'![image and another,/ image][stuff]',
|
||||
'![][stuff]',
|
||||
'![image and another,/ image][]'
|
||||
*/
|
||||
]
|
||||
.forEach(function (imageMarkup) {
|
||||
var processedMarkup =
|
||||
ghostimagepreview().reduce(function (prev, processor) {
|
||||
return processor.filter(prev);
|
||||
}, imageMarkup);
|
||||
|
||||
// The image is the entire markup, so the image box should be too
|
||||
processedMarkup.should.match(/^<section.*?section>\n*$/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly include an image', function () {
|
||||
[
|
||||
'',
|
||||
''
|
||||
/* No ref-style for now
|
||||
'![image and another,/ image][test]\n\n[test]: http://dsurl.stuff',
|
||||
'![][test]\n\n[test]: http://dsurl.stuff'
|
||||
*/
|
||||
]
|
||||
.forEach(function (imageMarkup) {
|
||||
var processedMarkup =
|
||||
ghostimagepreview().reduce(function (prev, processor) {
|
||||
return processor.filter(prev);
|
||||
}, imageMarkup);
|
||||
|
||||
processedMarkup.should.match(/<img class="js-upload-target"/);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* Tests the highlight extension for showdown
|
||||
*
|
||||
* Note, that the showdown highlight extension is a post-HTML filter, so most of the tests are in
|
||||
* showdown_client_integration_spec, to make it easier to test the behaviour on top of the already converted HTML
|
||||
*
|
||||
*/
|
||||
|
||||
/*globals describe, it */
|
||||
/*jshint expr:true*/
|
||||
var should = require('should'),
|
||||
|
||||
// Stuff we are testing
|
||||
ghosthighlight = require('../../shared/lib/showdown/extensions/ghosthighlight');
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
||||
function _ExecuteExtension(ext, text) {
|
||||
if (ext.regex) {
|
||||
var re = new RegExp(ext.regex, 'g');
|
||||
return text.replace(re, ext.replace);
|
||||
} else if (ext.filter) {
|
||||
return ext.filter(text);
|
||||
}
|
||||
}
|
||||
|
||||
function _ConvertPhrase(testPhrase) {
|
||||
return ghosthighlight().reduce(function (text, ext) {
|
||||
return _ExecuteExtension(ext, text);
|
||||
}, testPhrase);
|
||||
}
|
||||
|
||||
describe('Ghost highlight showdown extension', function () {
|
||||
/*jslint regexp: true */
|
||||
|
||||
it('should export an array of methods for processing', function () {
|
||||
ghosthighlight.should.be.a.function;
|
||||
ghosthighlight().should.be.an.Array;
|
||||
|
||||
ghosthighlight().forEach(function (processor) {
|
||||
processor.should.be.an.Object;
|
||||
processor.should.have.property('type');
|
||||
processor.type.should.be.a.String;
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace showdown highlight with html', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: '==foo_bar==',
|
||||
output: /^<mark>foo_bar<\/mark>$/
|
||||
},
|
||||
{
|
||||
input: 'My stuff that has a ==highlight== in the middle.',
|
||||
output: /^My stuff that has a <mark>highlight<\/mark> in the middle.$/
|
||||
},
|
||||
{
|
||||
input: 'My stuff that has a ==multiple word highlight== in the middle.',
|
||||
output: /^My stuff that has a <mark>multiple word highlight<\/mark> in the middle.$/
|
||||
},
|
||||
{
|
||||
input: 'My stuff that has a ==multiple word and\n line broken highlight== in the middle.',
|
||||
output: /^My stuff that has a <mark>multiple word and\n line broken highlight<\/mark> in the middle.$/
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
var processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore multiple equals', function () {
|
||||
var testPhrase = {input: '=====', output: /^=====$/},
|
||||
processedMarkup = _ConvertPhrase(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
|
@ -62,7 +62,7 @@
|
|||
"request": "2.51.0",
|
||||
"rss": "1.0.0",
|
||||
"semver": "4.1.0",
|
||||
"showdown-ghost": "0.3.4",
|
||||
"showdown-ghost": "0.3.6",
|
||||
"sqlite3": "3.0.5",
|
||||
"unidecode": "0.1.3",
|
||||
"validator": "3.28.0",
|
||||
|
|
Loading…
Add table
Reference in a new issue