0
Fork 0
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:
Hannah Wolfe 2014-12-03 19:52:29 +00:00
parent 83ef557c88
commit 9783f16e76
7 changed files with 253 additions and 8 deletions

View file

@ -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'
]
}
},

View file

@ -101,7 +101,7 @@ ul ol, ol ul {
}
mark {
background-color: #ffc336;
background-color: #fdffb6;
}
a {

View file

@ -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 = '';

View file

@ -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'),

View 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;
}
}());

View file

@ -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 = [

View 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);
});
});