mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Adds markdown highlight support
closes #4574 - adds highlight showdown extension with tests
This commit is contained in:
parent
83ef557c88
commit
9783f16e76
7 changed files with 253 additions and 8 deletions
|
@ -590,7 +590,8 @@ var _ = require('lodash'),
|
|||
|
||||
'core/shared/lib/showdown/extensions/ghostimagepreview.js',
|
||||
'core/shared/lib/showdown/extensions/ghostgfm.js',
|
||||
'core/shared/lib/showdown/extensions/ghostfootnotes.js'
|
||||
'core/shared/lib/showdown/extensions/ghostfootnotes.js',
|
||||
'core/shared/lib/showdown/extensions/ghosthighlight.js'
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -626,7 +627,8 @@ var _ = require('lodash'),
|
|||
|
||||
'core/shared/lib/showdown/extensions/ghostimagepreview.js',
|
||||
'core/shared/lib/showdown/extensions/ghostgfm.js',
|
||||
'core/shared/lib/showdown/extensions/ghostfootnotes.js'
|
||||
'core/shared/lib/showdown/extensions/ghostfootnotes.js',
|
||||
'core/shared/lib/showdown/extensions/ghosthighlight.js'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -101,7 +101,7 @@ ul ol, ol ul {
|
|||
}
|
||||
|
||||
mark {
|
||||
background-color: #ffc336;
|
||||
background-color: #fdffb6;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
@ -4,7 +4,7 @@ import cajaSanitizers from 'ghost/utils/caja-sanitizers';
|
|||
var showdown,
|
||||
formatMarkdown;
|
||||
|
||||
showdown = new Showdown.converter({extensions: ['ghostimagepreview', 'ghostgfm', 'footnotes']});
|
||||
showdown = new Showdown.converter({extensions: ['ghostimagepreview', 'ghostgfm', 'footnotes', 'highlight']});
|
||||
|
||||
formatMarkdown = Ember.Handlebars.makeBoundHelper(function (markdown) {
|
||||
var escapedhtml = '';
|
||||
|
|
|
@ -5,8 +5,9 @@ var _ = require('lodash'),
|
|||
errors = require('../errors'),
|
||||
Showdown = require('showdown-ghost'),
|
||||
ghostgfm = require('../../shared/lib/showdown/extensions/ghostgfm'),
|
||||
ghostfootnotes = require('../../shared/lib/showdown/extensions/ghostfootnotes'),
|
||||
converter = new Showdown.converter({extensions: [ghostgfm, ghostfootnotes]}),
|
||||
footnotes = require('../../shared/lib/showdown/extensions/ghostfootnotes'),
|
||||
highlight = require('../../shared/lib/showdown/extensions/ghosthighlight'),
|
||||
converter = new Showdown.converter({extensions: [ghostgfm, footnotes, highlight]}),
|
||||
ghostBookshelf = require('./base'),
|
||||
xmlrpc = require('../xmlrpc'),
|
||||
sitemap = require('../data/sitemap'),
|
||||
|
|
71
core/shared/lib/showdown/extensions/ghosthighlight.js
Normal file
71
core/shared/lib/showdown/extensions/ghosthighlight.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* 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;
|
||||
}
|
||||
}());
|
|
@ -12,9 +12,10 @@ var should = require('should'),
|
|||
Showdown = require('showdown-ghost'),
|
||||
ghostgfm = require('../../shared/lib/showdown/extensions/ghostgfm'),
|
||||
ghostimagepreview = require('../../shared/lib/showdown/extensions/ghostimagepreview'),
|
||||
ghostfootnotes = require('../../shared/lib/showdown/extensions/ghostfootnotes'),
|
||||
footnotes = require('../../shared/lib/showdown/extensions/ghostfootnotes'),
|
||||
highlight = require('../../shared/lib/showdown/extensions/ghosthighlight'),
|
||||
|
||||
converter = new Showdown.converter({extensions: [ghostimagepreview, ghostgfm, ghostfootnotes]});
|
||||
converter = new Showdown.converter({extensions: [ghostimagepreview, ghostgfm, footnotes, highlight]});
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
@ -532,6 +533,95 @@ describe('Showdown client side converter', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should replace showdown highlight with html', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: '==foo_bar==',
|
||||
output: /^<p><mark>foo_bar<\/mark><\/p>$/
|
||||
},
|
||||
{
|
||||
input: 'My stuff that has a ==highlight== in the middle.',
|
||||
output: /^<p>My stuff that has a <mark>highlight<\/mark> in the middle.<\/p>$/
|
||||
},
|
||||
{
|
||||
input: 'My stuff that has a ==multiple word highlight== in the middle.',
|
||||
output: /^<p>My stuff that has a <mark>multiple word highlight<\/mark> in the middle.<\/p>$/
|
||||
},
|
||||
{
|
||||
input: 'My stuff that has a ==multiple word **bold** highlight== in the middle.',
|
||||
output: /^<p>My stuff that has a <mark>multiple word <strong>bold<\/strong> highlight<\/mark> in the middle.<\/p>$/
|
||||
},
|
||||
{
|
||||
input: 'My stuff that has a ==multiple word and\n line broken highlight== in the middle.',
|
||||
output: /^<p>My stuff that has a <mark>multiple word and <br \/>\n line broken highlight<\/mark> in the middle.<\/p>$/
|
||||
},
|
||||
{
|
||||
input: 'Test ==Highlighting with a [link](https://ghost.org) in the middle== of it.',
|
||||
output: /^<p>Test <mark>Highlighting with a <a href="https:\/\/ghost.org">link<\/a> in the middle<\/mark> of it.<\/p>$/
|
||||
},
|
||||
{
|
||||
input: '==[link](http://ghost.org)==',
|
||||
output: /^<p><mark><a href="http:\/\/ghost.org">link<\/a><\/mark><\/p>$/
|
||||
},
|
||||
{
|
||||
input: '[==link==](http://ghost.org)',
|
||||
output: /^<p><a href="http:\/\/ghost.org"><mark>link<\/mark><\/a><\/p>$/
|
||||
},
|
||||
{
|
||||
input: '====test==test==test====test',
|
||||
output: /^<p><mark>==test<\/mark>test<mark>test<\/mark>==test<\/p>$/
|
||||
}
|
||||
];
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
var processedMarkup = converter.makeHtml(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not effect pre tags', function () {
|
||||
var testPhrase = {
|
||||
input: '```javascript\n' +
|
||||
'var foo = "bar";\n' +
|
||||
'if (foo === "bar") {\n' +
|
||||
' return true;\n' +
|
||||
'} else if (foo === "baz") {\n' +
|
||||
' return "magic happened";\n' +
|
||||
'}\n' +
|
||||
'```',
|
||||
output: /^<mark><\/mark>$/
|
||||
},
|
||||
processedMarkup = converter.makeHtml(testPhrase.input);
|
||||
|
||||
// this does not get mark tags
|
||||
processedMarkup.should.not.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should ignore multiple equals', function () {
|
||||
var testPhrase = {input: '=====', output: /^<p>=====<\/p>$/},
|
||||
processedMarkup = converter.makeHtml(testPhrase.input);
|
||||
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
|
||||
it('should still handle headers correctly', function () {
|
||||
var testPhrases = [
|
||||
{
|
||||
input: 'Header\n==',
|
||||
output: /^<h1 id="header">Header <\/h1>$/
|
||||
},
|
||||
{
|
||||
input: 'First Header\n==\nSecond Header\n==',
|
||||
output: /^<h1 id="firstheader">First Header <\/h1>\n\n<h1 id="secondheader">Second Header <\/h1>$/
|
||||
}
|
||||
];
|
||||
|
||||
testPhrases.forEach(function (testPhrase) {
|
||||
var processedMarkup = converter.makeHtml(testPhrase.input);
|
||||
processedMarkup.should.match(testPhrase.output);
|
||||
});
|
||||
});
|
||||
|
||||
// Waiting for showdown typography to be updated
|
||||
// it('should correctly convert quotes to curly quotes', function () {
|
||||
// var testPhrases = [
|
||||
|
|
81
core/test/unit/showdown_highlight_spec.js
Normal file
81
core/test/unit/showdown_highlight_spec.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue