mirror of
https://github.com/showdownjs/showdown.git
synced 2024-03-22 13:30:55 +08:00
headings compliance
This commit is contained in:
parent
75b7707460
commit
42959b78c1
|
@ -23,10 +23,11 @@ showdown.subParser('makehtml.blockGamut', function (text, options, globals, skip
|
|||
startEvent = globals.converter.dispatch(startEvent);
|
||||
text = startEvent.output;
|
||||
|
||||
// we parse blockquotes first so that we can have headings and hrs
|
||||
// inside blockquotes
|
||||
if (skip !== 'makehtml.heading') {
|
||||
text = showdown.subParser('makehtml.heading')(text, options, globals);
|
||||
if (skip !== 'makehtml.heading.setext') {
|
||||
text = showdown.subParser('makehtml.heading.setext')(text, options, globals);
|
||||
}
|
||||
if (skip !== 'makehtml.heading.atx') {
|
||||
text = showdown.subParser('makehtml.heading.atx')(text, options, globals);
|
||||
}
|
||||
|
||||
// Do Horizontal Rules:
|
||||
|
|
|
@ -90,8 +90,7 @@ showdown.subParser('makehtml.codeSpan', function (text, options, globals) {
|
|||
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
|
||||
otp = beforeHashEvent.output;
|
||||
return showdown.subParser('makehtml.hashHTMLSpans')(otp, options, globals);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
let afterEvent = new showdown.Event('makehtml.codeSpan.onEnd', text);
|
||||
afterEvent
|
||||
|
|
|
@ -21,8 +21,60 @@
|
|||
// ***Author:***
|
||||
// - Estêvão Soares dos Santos (Tivie) <https://github.com/tivie>
|
||||
////
|
||||
(function () {
|
||||
|
||||
showdown.subParser('makehtml.heading', function (text, options, globals) {
|
||||
/**
|
||||
*
|
||||
* @param {RegExp} pattern
|
||||
* @param {string} wholeMatch
|
||||
* @param {string} headingText
|
||||
* @param {string} headingLevel
|
||||
* @param {string} headingId
|
||||
* @param {{}} options
|
||||
* @param {{}} globals
|
||||
* @returns {string}
|
||||
*/
|
||||
function parseHeader (pattern, wholeMatch, headingText, headingLevel, headingId, options, globals) {
|
||||
let captureStartEvent = new showdown.Event('makehtml.heading.onCapture', headingText),
|
||||
otp;
|
||||
|
||||
captureStartEvent
|
||||
.setOutput(null)
|
||||
._setGlobals(globals)
|
||||
._setOptions(options)
|
||||
.setRegexp(pattern)
|
||||
.setMatches({
|
||||
_wholeMatch: wholeMatch,
|
||||
heading: headingText
|
||||
})
|
||||
.setAttributes({
|
||||
id: headingId
|
||||
});
|
||||
captureStartEvent = globals.converter.dispatch(captureStartEvent);
|
||||
// if something was passed as output, it takes precedence
|
||||
// and will be used as output
|
||||
if (captureStartEvent.output && captureStartEvent.output !== '') {
|
||||
otp = captureStartEvent.output;
|
||||
|
||||
} else {
|
||||
headingText = captureStartEvent.matches.heading;
|
||||
let spanGamut = showdown.subParser('makehtml.spanGamut')(headingText, options, globals),
|
||||
attributes = captureStartEvent.attributes;
|
||||
otp = '<h' + headingLevel + showdown.helper._populateAttributes(attributes) + '>' + spanGamut + '</h' + headingLevel + '>';
|
||||
}
|
||||
|
||||
let beforeHashEvent = new showdown.Event('makehtml.heading.onHash', otp);
|
||||
beforeHashEvent
|
||||
.setOutput(otp)
|
||||
._setGlobals(globals)
|
||||
._setOptions(options);
|
||||
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
|
||||
otp = beforeHashEvent.output;
|
||||
|
||||
return showdown.subParser('makehtml.hashBlock')(otp, options, globals);
|
||||
}
|
||||
|
||||
showdown.subParser('makehtml.heading', function (text, options, globals) {
|
||||
'use strict';
|
||||
|
||||
let startEvent = new showdown.Event('makehtml.heading.onStart', text);
|
||||
|
@ -33,24 +85,8 @@ showdown.subParser('makehtml.heading', function (text, options, globals) {
|
|||
startEvent = globals.converter.dispatch(startEvent);
|
||||
text = startEvent.output;
|
||||
|
||||
let setextRegexH1 = /^( {0,3}([^ \t\n]+.*\n)(.+\n)?(.+\n)?)( {0,3}=+[ \t]*)$/gm,
|
||||
setextRegexH2 = /^( {0,3}([^ \t\n]+.*\n)(.+\n)?(.+\n)?)( {0,3}(-+)[ \t]*)$/gm,
|
||||
atxRegex = (options.requireSpaceBeforeHeadingText) ? /^ {0,3}(#{1,6})[ \t]+(.+?)(?:[ \t]+#+)?[ \t]*$/gm : /^ {0,3}(#{1,6})[ \t]*(.+?)[ \t]*#*[ \t]*$/gm;
|
||||
|
||||
text = text.replace(setextRegexH1, function (wholeMatch, headingText, line1, line2, line3, line4) {
|
||||
return parseSetextHeading(setextRegexH2, options.headerLevelStart, wholeMatch, headingText, line1, line2, line3, line4);
|
||||
});
|
||||
|
||||
text = text.replace(setextRegexH2, function (wholeMatch, headingText, line1, line2, line3, line4) {
|
||||
return parseSetextHeading(setextRegexH2, options.headerLevelStart + 1, wholeMatch, headingText, line1, line2, line3, line4);
|
||||
});
|
||||
|
||||
text = text.replace(atxRegex, function (wholeMatch, m1, m2) {
|
||||
let headingLevel = options.headerLevelStart - 1 + m1.length,
|
||||
headingText = (options.customizedHeaderId) ? m2.replace(/\s?{([^{]+?)}\s*$/, '') : m2,
|
||||
id = (options.noHeaderId) ? null : showdown.subParser('makehtml.heading.id')(m2, options, globals);
|
||||
return parseHeader(setextRegexH2, wholeMatch, headingText, headingLevel, id);
|
||||
});
|
||||
text = showdown.subParser('makehtml.heading.setext')(text, options, globals);
|
||||
text = showdown.subParser('makehtml.heading.atx')(text, options, globals);
|
||||
|
||||
let afterEvent = new showdown.Event('makehtml.heading.onEnd', text);
|
||||
afterEvent
|
||||
|
@ -60,6 +96,103 @@ showdown.subParser('makehtml.heading', function (text, options, globals) {
|
|||
afterEvent = globals.converter.dispatch(afterEvent);
|
||||
return afterEvent.output;
|
||||
|
||||
});
|
||||
|
||||
showdown.subParser('makehtml.heading.id', function (m, options, globals) {
|
||||
let title,
|
||||
prefix;
|
||||
|
||||
// It is separate from other options to allow combining prefix and customized
|
||||
if (options.customizedHeaderId) {
|
||||
let match = m.match(/{([^{]+?)}\s*$/);
|
||||
if (match && match[1]) {
|
||||
m = match[1];
|
||||
}
|
||||
}
|
||||
|
||||
title = m;
|
||||
|
||||
// Prefix id to prevent causing inadvertent pre-existing style matches.
|
||||
if (showdown.helper.isString(options.prefixHeaderId)) {
|
||||
prefix = options.prefixHeaderId;
|
||||
} else if (options.prefixHeaderId === true) {
|
||||
prefix = 'section-';
|
||||
} else {
|
||||
prefix = '';
|
||||
}
|
||||
|
||||
if (!options.rawPrefixHeaderId) {
|
||||
title = prefix + title;
|
||||
}
|
||||
|
||||
if (options.ghCompatibleHeaderId) {
|
||||
title = title
|
||||
.replace(/ /g, '-')
|
||||
// replace previously escaped chars (&, ¨ and $)
|
||||
.replace(/&/g, '')
|
||||
.replace(/¨T/g, '')
|
||||
.replace(/¨D/g, '')
|
||||
// replace rest of the chars (&~$ are repeated as they might have been escaped)
|
||||
// borrowed from github's redcarpet (so they should produce similar results)
|
||||
.replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g, '')
|
||||
.toLowerCase();
|
||||
} else if (options.rawHeaderId) {
|
||||
title = title
|
||||
.replace(/ /g, '-')
|
||||
// replace previously escaped chars (&, ¨ and $)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/¨T/g, '¨')
|
||||
.replace(/¨D/g, '$')
|
||||
// replace " and '
|
||||
.replace(/["']/g, '-')
|
||||
.toLowerCase();
|
||||
} else {
|
||||
title = title
|
||||
.replace(/\W/g, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
if (options.rawPrefixHeaderId) {
|
||||
title = prefix + title;
|
||||
}
|
||||
|
||||
if (globals.hashLinkCounts[title]) {
|
||||
title = title + '-' + (globals.hashLinkCounts[title]++);
|
||||
} else {
|
||||
globals.hashLinkCounts[title] = 1;
|
||||
}
|
||||
return title;
|
||||
});
|
||||
|
||||
showdown.subParser('makehtml.heading.setext', function (text, options, globals) {
|
||||
|
||||
let startEvent = new showdown.Event('makehtml.heading.setext.onStart', text);
|
||||
startEvent
|
||||
.setOutput(text)
|
||||
._setGlobals(globals)
|
||||
._setOptions(options);
|
||||
startEvent = globals.converter.dispatch(startEvent);
|
||||
text = startEvent.output;
|
||||
|
||||
const setextRegexH1 = /^( {0,3}([^ \t\n]+.*\n)(.+\n)?(.+\n)?)( {0,3}=+[ \t]*)$/gm,
|
||||
setextRegexH2 = /^( {0,3}([^ \t\n]+.*\n)(.+\n)?(.+\n)?)( {0,3}(-+)[ \t]*)$/gm;
|
||||
|
||||
text = text.replace(setextRegexH1, function (wholeMatch, headingText, line1, line2, line3, line4) {
|
||||
return parseSetextHeading(setextRegexH2, options.headerLevelStart, wholeMatch, headingText, line1, line2, line3, line4);
|
||||
});
|
||||
|
||||
text = text.replace(setextRegexH2, function (wholeMatch, headingText, line1, line2, line3, line4) {
|
||||
return parseSetextHeading(setextRegexH2, options.headerLevelStart + 1, wholeMatch, headingText, line1, line2, line3, line4);
|
||||
});
|
||||
|
||||
let afterEvent = new showdown.Event('makehtml.heading.setext.onEnd', text);
|
||||
afterEvent
|
||||
.setOutput(text)
|
||||
._setGlobals(globals)
|
||||
._setOptions(options);
|
||||
afterEvent = globals.converter.dispatch(afterEvent);
|
||||
|
||||
return showdown.subParser('makehtml.hashHTMLBlocks')(afterEvent.output, options, globals);
|
||||
|
||||
|
||||
function parseSetextHeading (pattern, headingLevel, wholeMatch, headingText, line1, line2, line3, line4) {
|
||||
|
@ -163,16 +296,17 @@ showdown.subParser('makehtml.heading', function (text, options, globals) {
|
|||
|
||||
// all edge cases should be treated now
|
||||
multilineText = line1 + line2 + ((line3) ? line3 : '');
|
||||
if (line4.trim().match(/^=+/)) {
|
||||
multilineText += line4;
|
||||
}
|
||||
//if (line4.trim().match(/^=+/)) {
|
||||
// multilineText += line4;
|
||||
//}
|
||||
|
||||
nPrepend = showdown.subParser('makehtml.blockGamut')(multilineText, options, globals, 'makehtml.heading');
|
||||
nPrepend = showdown.subParser('makehtml.blockGamut')(multilineText, options, globals, 'makehtml.heading.setext');
|
||||
if (nPrepend !== multilineText) {
|
||||
// we found a block, so it should take precedence
|
||||
prepend += nPrepend;
|
||||
headingText = '';
|
||||
}
|
||||
console.log(prepend);
|
||||
}
|
||||
|
||||
// trim stuff
|
||||
|
@ -186,113 +320,39 @@ showdown.subParser('makehtml.heading', function (text, options, globals) {
|
|||
|
||||
// after this, we're pretty sure it's a heading so let's proceed
|
||||
let id = (options.noHeaderId) ? null : showdown.subParser('makehtml.heading.id')(headingText, options, globals);
|
||||
return prepend + parseHeader(pattern, wholeMatch, headingText, headingLevel, id);
|
||||
return prepend + parseHeader(pattern, wholeMatch, headingText, headingLevel, id, options, globals);
|
||||
}
|
||||
|
||||
function parseHeader (pattern, wholeMatch, headingText, headingLevel, headingId) {
|
||||
let captureStartEvent = new showdown.Event('makehtml.heading.onCapture', headingText),
|
||||
otp;
|
||||
|
||||
captureStartEvent
|
||||
.setOutput(null)
|
||||
._setGlobals(globals)
|
||||
._setOptions(options)
|
||||
.setRegexp(pattern)
|
||||
.setMatches({
|
||||
_wholeMatch: wholeMatch,
|
||||
heading: headingText
|
||||
})
|
||||
.setAttributes({
|
||||
id: headingId
|
||||
});
|
||||
captureStartEvent = globals.converter.dispatch(captureStartEvent);
|
||||
// if something was passed as output, it takes precedence
|
||||
// and will be used as output
|
||||
if (captureStartEvent.output && captureStartEvent.output !== '') {
|
||||
otp = captureStartEvent.output;
|
||||
|
||||
} else {
|
||||
headingText = captureStartEvent.matches.heading;
|
||||
let spanGamut = showdown.subParser('makehtml.spanGamut')(headingText, options, globals),
|
||||
attributes = captureStartEvent.attributes;
|
||||
otp = '<h' + headingLevel + showdown.helper._populateAttributes(attributes) + '>' + spanGamut + '</h' + headingLevel + '>';
|
||||
}
|
||||
showdown.subParser('makehtml.heading.atx', function (text, options, globals) {
|
||||
|
||||
let beforeHashEvent = new showdown.Event('makehtml.heading.onHash', otp);
|
||||
beforeHashEvent
|
||||
.setOutput(otp)
|
||||
let startEvent = new showdown.Event('makehtml.heading.atx.onStart', text);
|
||||
startEvent
|
||||
.setOutput(text)
|
||||
._setGlobals(globals)
|
||||
._setOptions(options);
|
||||
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
|
||||
otp = beforeHashEvent.output;
|
||||
startEvent = globals.converter.dispatch(startEvent);
|
||||
text = startEvent.output;
|
||||
|
||||
return showdown.subParser('makehtml.hashBlock')(otp, options, globals);
|
||||
}
|
||||
const atxRegex = (options.requireSpaceBeforeHeadingText) ? /^ {0,3}(#{1,6})[ \t]+(.+?)(?:[ \t]+#+)?[ \t]*$/gm : /^ {0,3}(#{1,6})[ \t]*(.+?)[ \t]*#*[ \t]*$/gm;
|
||||
text = text.replace(atxRegex, function (wholeMatch, m1, m2) {
|
||||
let headingLevel = options.headerLevelStart - 1 + m1.length,
|
||||
headingText = (options.customizedHeaderId) ? m2.replace(/\s?{([^{]+?)}\s*$/, '') : m2,
|
||||
id = (options.noHeaderId) ? null : showdown.subParser('makehtml.heading.id')(m2, options, globals);
|
||||
return parseHeader(atxRegex, wholeMatch, headingText, headingLevel, id, options, globals);
|
||||
});
|
||||
|
||||
});
|
||||
let afterEvent = new showdown.Event('makehtml.heading.atx.onEnd', text);
|
||||
afterEvent
|
||||
.setOutput(text)
|
||||
._setGlobals(globals)
|
||||
._setOptions(options);
|
||||
afterEvent = globals.converter.dispatch(afterEvent);
|
||||
|
||||
showdown.subParser('makehtml.heading.id', function (m, options, globals) {
|
||||
let title,
|
||||
prefix;
|
||||
return showdown.subParser('makehtml.hashHTMLBlocks')(afterEvent.output, options, globals);
|
||||
|
||||
// It is separate from other options to allow combining prefix and customized
|
||||
if (options.customizedHeaderId) {
|
||||
let match = m.match(/{([^{]+?)}\s*$/);
|
||||
if (match && match[1]) {
|
||||
m = match[1];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
title = m;
|
||||
|
||||
// Prefix id to prevent causing inadvertent pre-existing style matches.
|
||||
if (showdown.helper.isString(options.prefixHeaderId)) {
|
||||
prefix = options.prefixHeaderId;
|
||||
} else if (options.prefixHeaderId === true) {
|
||||
prefix = 'section-';
|
||||
} else {
|
||||
prefix = '';
|
||||
}
|
||||
|
||||
if (!options.rawPrefixHeaderId) {
|
||||
title = prefix + title;
|
||||
}
|
||||
|
||||
if (options.ghCompatibleHeaderId) {
|
||||
title = title
|
||||
.replace(/ /g, '-')
|
||||
// replace previously escaped chars (&, ¨ and $)
|
||||
.replace(/&/g, '')
|
||||
.replace(/¨T/g, '')
|
||||
.replace(/¨D/g, '')
|
||||
// replace rest of the chars (&~$ are repeated as they might have been escaped)
|
||||
// borrowed from github's redcarpet (so they should produce similar results)
|
||||
.replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g, '')
|
||||
.toLowerCase();
|
||||
} else if (options.rawHeaderId) {
|
||||
title = title
|
||||
.replace(/ /g, '-')
|
||||
// replace previously escaped chars (&, ¨ and $)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/¨T/g, '¨')
|
||||
.replace(/¨D/g, '$')
|
||||
// replace " and '
|
||||
.replace(/["']/g, '-')
|
||||
.toLowerCase();
|
||||
} else {
|
||||
title = title
|
||||
.replace(/\W/g, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
if (options.rawPrefixHeaderId) {
|
||||
title = prefix + title;
|
||||
}
|
||||
|
||||
if (globals.hashLinkCounts[title]) {
|
||||
title = title + '-' + (globals.hashLinkCounts[title]++);
|
||||
} else {
|
||||
globals.hashLinkCounts[title] = 1;
|
||||
}
|
||||
return title;
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
<p>this is some text</p>
|
||||
<p><code>php
|
||||
function thisThing() {
|
||||
echo "some weird formatted code!";
|
||||
}
|
||||
</code></p>
|
||||
<p><code>php function thisThing() { echo "some weird formatted code!"; } </code></p>
|
||||
<p>some other text</p>
|
||||
|
|
|
@ -109,16 +109,32 @@ describe('showdown.Event', function () {
|
|||
{ event: 'onHash', text: '```\nfoo\n```', result: true },
|
||||
{ event: 'onHash', text: 'foo', result: false }
|
||||
],
|
||||
heading: [
|
||||
'heading.atx': [
|
||||
{ event: 'onStart', text: '# foo', result: true },
|
||||
{ event: 'onStart', text: 'foo', result: true },
|
||||
{ event: 'onEnd', text: '# foo', result: true },
|
||||
{ event: 'onEnd', text: 'foo', result: true },
|
||||
{ event: 'onCapture', text: '# foo', result: true },
|
||||
{ event: 'onCapture', text: 'foo\n---', result: false },
|
||||
{ event: 'onCapture', text: 'foo\n===', result: false },
|
||||
{ event: 'onCapture', text: 'foo', result: false },
|
||||
{ event: 'onHash', text: '# foo', result: true },
|
||||
{ event: 'onHash', text: 'foo\n---', result: false },
|
||||
{ event: 'onHash', text: 'foo\n===', result: false },
|
||||
{ event: 'onHash', text: 'foo', result: false }
|
||||
],
|
||||
'heading.setext': [
|
||||
{ event: 'onStart', text: 'foo\n---', result: true },
|
||||
{ event: 'onStart', text: 'foo\n===', result: true },
|
||||
{ event: 'onStart', text: 'foo', result: true },
|
||||
{ event: 'onEnd', text: 'foo\n---', result: true },
|
||||
{ event: 'onEnd', text: 'foo\n===', result: true },
|
||||
{ event: 'onEnd', text: 'foo', result: true },
|
||||
{ event: 'onCapture', text: '# foo', result: false },
|
||||
{ event: 'onCapture', text: 'foo\n---', result: true },
|
||||
{ event: 'onCapture', text: 'foo\n===', result: true },
|
||||
{ event: 'onCapture', text: 'foo', result: false },
|
||||
{ event: 'onHash', text: '# foo', result: true },
|
||||
{ event: 'onHash', text: '# foo', result: false },
|
||||
{ event: 'onHash', text: 'foo\n---', result: true },
|
||||
{ event: 'onHash', text: 'foo\n===', result: true },
|
||||
{ event: 'onHash', text: 'foo', result: false }
|
||||
|
@ -267,8 +283,10 @@ describe('showdown.Event', function () {
|
|||
describe(parser, function () {
|
||||
for (let ts in testSpec.makehtml[parser]) {
|
||||
let event = 'makehtml.' + parser + '.' + testSpec.makehtml[parser][ts].event;
|
||||
|
||||
let md = testSpec.makehtml[parser][ts].text;
|
||||
let title = (testSpec.makehtml[parser][ts].result) ? 'should ' : 'should NOT ';
|
||||
let title = '«' + md + '» ';
|
||||
title += (testSpec.makehtml[parser][ts].result) ? 'should ' : 'should NOT ';
|
||||
title += 'trigger "' + event + ' event"';
|
||||
let expected = testSpec.makehtml[parser][ts].result;
|
||||
let actual = false;
|
||||
|
|
Loading…
Reference in New Issue
Block a user