mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-28 05:14:12 +03:00
172 lines
7.0 KiB
JavaScript
172 lines
7.0 KiB
JavaScript
/* 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;
|
|
}
|
|
}());
|