add links

This commit is contained in:
Estevão Soares dos Santos 2022-04-21 00:36:17 +01:00
parent 5a14ce02db
commit c48801d8fa
7 changed files with 279 additions and 490 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "showdown",
"version": "2.0.0",
"version": "3.0.0-alpha",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "showdown",
"version": "2.0.0",
"version": "3.0.0-alpha",
"license": "MIT",
"dependencies": {
"commander": "^9.0.0",

View File

@ -140,9 +140,9 @@ showdown.subParser('makehtml.image', function (text, options, globals) {
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let inlineRegExp = /!\[([^\]]*?)][ \t]*\([ \t]?<?([\S]+?(?:\([\S]*?\)[\S]*?)?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\5)?[ \t]?\)/g,
let inlineRegExp = /!\[([^\]]*?)][ \t]*\([ \t]?<?(\S+?(?:\(\S*?\)\S*?)?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\5)?[ \t]?\)/g,
crazyRegExp = /!\[([^\]]*?)][ \t]*\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\5)?[ \t]?\)/g,
base64RegExp = /!\[([^\]]*?)][ \t]*\([ \t]?<?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,
base64RegExp = /!\[([^\]]*?)][ \t]*\([ \t]?<?(data:.+?\/.+?;base64,[A-Za-z\d+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,
referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]/g,
refShortcutRegExp = /!\[([^\[\]]+)]/g;

View File

@ -25,11 +25,19 @@ showdown.subParser('makehtml.link', function (text, options, globals) {
* @param {string|null} [url]
* @param {string|null} [title]
* @param {boolean} [emptyCase]
* @returns {string}
*/
function writeAnchorTag (subEvtName, pattern, wholeMatch, text, linkId, url, title, emptyCase) {
let otp = '';
let target = null;
let matches = {
_wholeMatch: wholeMatch,
_linkId: linkId,
_url: url,
_title: title,
text: text
},
otp,
attributes = {};
title = title || null;
url = url || null;
@ -55,35 +63,85 @@ showdown.subParser('makehtml.link', function (text, options, globals) {
}
}
url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
url = url.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
attributes.href = url;
if (title && showdown.helper.isString(title)) {
title = title
.replace(/"/g, '&quot;')
.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
attributes.title = title;
}
// optionLinksInNewWindow only applies
// to external links. Hash links (#) open in same page
if (options.openLinksInNewWindow && !/^#/.test(url)) {
// escaped _
target = ' rel="noopener noreferrer" target="¨E95Eblank"';
attributes.rel = 'noopener noreferrer';
attributes.target = '¨E95Eblank'; // escaped _
}
// Text can be a markdown element, so we run through the appropriate parsers
text = showdown.subParser('makehtml.codeSpan')(text, options, globals);
text = showdown.subParser('makehtml.emoji')(text, options, globals);
text = showdown.subParser('makehtml.underline')(text, options, globals);
text = showdown.subParser('makehtml.emphasisAndStrong')(text, options, globals);
text = showdown.subParser('makehtml.strikethrough')(text, options, globals);
text = showdown.subParser('makehtml.ellipsis')(text, options, globals);
text = showdown.subParser('makehtml.hashHTMLSpans')(text, options, globals);
let captureStartEvent = new showdown.Event('makehtml.link.' + subEvtName + '.onCapture', wholeMatch);
captureStartEvent
.setOutput(null)
._setGlobals(globals)
._setOptions(options)
.setRegexp(pattern)
.setMatches(matches)
.setAttributes(attributes);
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 {
attributes = captureStartEvent.attributes;
text = captureStartEvent.matches.text || '';
// Text can be a markdown element, so we run through the appropriate parsers
text = showdown.subParser('makehtml.codeSpan')(text, options, globals);
text = showdown.subParser('makehtml.emoji')(text, options, globals);
text = showdown.subParser('makehtml.underline')(text, options, globals);
text = showdown.subParser('makehtml.emphasisAndStrong')(text, options, globals);
text = showdown.subParser('makehtml.strikethrough')(text, options, globals);
text = showdown.subParser('makehtml.ellipsis')(text, options, globals);
text = showdown.subParser('makehtml.hashHTMLSpans')(text, options, globals);
otp = '<a' + showdown.helper._populateAttributes(attributes) + '>' + text + '</a>';
}
return otp;
let beforeHashEvent = new showdown.Event('makehtml.link.' + subEvtName + '.onHash', otp);
beforeHashEvent
.setOutput(otp)
._setGlobals(globals)
._setOptions(options);
beforeHashEvent = globals.converter.dispatch(beforeHashEvent);
otp = beforeHashEvent.output;
return showdown.subParser('makehtml.hashHTMLSpans')(otp, options, globals);
}
/**
* @param {string} mail
* @returns {{mail: string, url: string}}
*/
function parseMail (mail) {
let url = 'mailto:';
mail = showdown.subParser('makehtml.unescapeSpecialChars')(mail, options, globals);
if (options.encodeEmails) {
url = showdown.helper.encodeEmailAddress(url + mail);
mail = showdown.helper.encodeEmailAddress(mail);
} else {
url = url + mail;
}
return {
mail: mail,
url: url
};
}
//
// Parser starts here
//
let startEvent = new showdown.Event('makehtml.link.onStart', text);
startEvent
.setOutput(text)
@ -92,10 +150,172 @@ showdown.subParser('makehtml.link', function (text, options, globals) {
startEvent = globals.converter.dispatch(startEvent);
text = startEvent.output;
let referenceRegex = /\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g;
// 1. Handle reference-style links: [link text] [id]
text = text.replace(referenceRegex, function (wholeMatch, altText, linkId) {
return writeAnchorTag ('reference', referenceRegex, wholeMatch, altText, linkId, '');
let referenceRegex = /\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]/g;
text = text.replace(referenceRegex, function (wholeMatch, text, linkId) {
// bail if we find 2 newlines somewhere
if (/\n\n/.test(wholeMatch)) {
return wholeMatch;
}
return writeAnchorTag ('reference', referenceRegex, wholeMatch, text, linkId);
});
// 2. Handle inline-style links: [link text](url "optional title")
// 2.1. Look for empty cases: []() and [empty]() and []("title")
let inlineEmptyRegex = /\[(.*?)]\(<? ?>? ?(["'](.*)["'])?\)/g;
text = text.replace(inlineEmptyRegex, function (wholeMatch, text, m1, title) {
return writeAnchorTag ('inline', inlineEmptyRegex, wholeMatch, text, null, null, title, true);
});
// 2.2. Look for cases with crazy urls like ./image/cat1).png
// the url mus be enclosed in <>
let inlineCrazyRegex = /\[((?:\[[^\]]*]|[^\[\]])*)]\s?\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\4))?[ \t]?\)/g;
text = text.replace(inlineCrazyRegex, function (wholeMatch, text, url, m1, m2, title) {
return writeAnchorTag ('inline', inlineCrazyRegex, wholeMatch, text, null, url, title);
});
// 2.3. inline links with no title or titles wrapped in ' or ":
// [text](url.com) || [text](<url.com>) || [text](url.com "title") || [text](<url.com> "title")
let inlineNormalRegex1 = /\[([\S ]*?)]\s?\( *<?([^\s'"]*?(?:\(\S*?\)\S*?)?)>?\s*(?:(['"])(.*?)\3)? *\)/g;
text = text.replace(inlineNormalRegex1, function (wholeMatch, text, url, m1, title) {
return writeAnchorTag ('inline', inlineNormalRegex1, wholeMatch, text, null, url, title);
});
// 2.4. inline links with titles wrapped in (): [foo](bar.com (title))
let inlineNormalRegex2 = /\[([\S ]*?)]\s?\( *<?([^\s'"]*?(?:\(\S*?\)\S*?)?)>?\s+\((.*?)\) *\)/g;
text = text.replace(inlineNormalRegex2, function (wholeMatch, text, url, title) {
return writeAnchorTag ('inline', inlineNormalRegex2, wholeMatch, text, null, url, title);
});
// 3. Handle reference-style shortcuts: [link text]
// These must come last in case there's a [link text][1] or [link text](/foo)
let referenceShortcutRegex = /\[([^\[\]]+)]/g;
text = text.replace(referenceShortcutRegex, function (wholeMatch, text) {
return writeAnchorTag ('reference', referenceShortcutRegex, wholeMatch, text);
});
// 4. Handle angle brackets links -> `<http://example.com/>`
// Must come after links, because you can use < and > delimiters in inline links like [this](<url>).
// 4.1. Handle links first
let angleBracketsLinksRegex = /<(((?:https?|ftp):\/\/|www\.)[^'">\s]+)>/gi;
text = text.replace(angleBracketsLinksRegex, function (wholeMatch, url, urlStart) {
let text = url;
url = (urlStart === 'www.') ? 'http://' + url : url;
return writeAnchorTag ('angleBrackets', angleBracketsLinksRegex, wholeMatch, text, null, url);
});
// 4.2. Then mail adresses
let angleBracketsMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z\d]+(\.[-a-z\d]+)*\.[a-z]+)>/gi;
text = text.replace(angleBracketsMailRegex, function (wholeMatch, mail) {
const m = parseMail(mail);
return writeAnchorTag ('angleBrackets', angleBracketsMailRegex, wholeMatch, m.mail, null, m.url);
});
// 5. Handle GithubMentions (if option is enabled)
if (options.ghMentions) {
let ghMentionsRegex = /(^|\s)(\\)?(@([a-z\d]+(?:[a-z\d._-]+?[a-z\d]+)*))/gi;
text = text.replace(ghMentionsRegex, function (wholeMatch, st, escape, mentions, username) {
// bail if the mentions was escaped
if (escape === '\\') {
return st + mentions;
}
// check if options.ghMentionsLink is a string
// TODO Validation should be done at initialization not at runtime
if (!showdown.helper.isString(options.ghMentionsLink)) {
throw new Error('ghMentionsLink option must be a string');
}
let url = options.ghMentionsLink.replace(/\{u}/g, username);
return st + writeAnchorTag ('reference', ghMentionsRegex, wholeMatch, mentions, null, url);
});
}
// 6 and 7 have to come here to prevent naked links to catch html
// 6. Handle <a> tags
text = text.replace(/<a\s[^>]*>[\s\S]*<\/a>/g, function (wholeMatch) {
return showdown.helper._hashHTMLSpan(wholeMatch, globals);
});
// 7. Handle <img> tags
text = text.replace(/<img\s[^>]*\/?>/g, function (wholeMatch) {
return showdown.helper._hashHTMLSpan(wholeMatch, globals);
});
// 8. Handle naked links (if option is enabled)
if (options.simplifiedAutoLink) {
// 8.1. Check for naked URLs
// we also include leading markdown magic chars [_*~] for cases like __https://www.google.com/foobar__
let nakedUrlRegex = /([_*~]*?)(((?:https?|ftp):\/\/|www\.)[^\s<>"'`´.-][^\s<>"'`´]*?\.[a-z\d.]+[^\s<>"']*)\1/gi;
text = text.replace(nakedUrlRegex, function (wholeMatch, leadingMDChars, url, urlPrefix) {
// we now will start traversing the url from the front to back, looking for punctuation chars [_*~,;:.!?\)\]]
const len = url.length;
let suffix = '';
for (let i = len - 1; i >= 0; --i) {
let char = url.charAt(i);
if (/[_*~,;:.!?]/.test(char)) {
// it's a punctuation char so we remove it from the url
url = url.slice(0, -1);
// and prepend it to the suffix
suffix = char + suffix;
} else if (/[)\]]/.test(char)) {
// it's a parenthesis so we need to check for "balance" (kinda)
let opPar, clPar;
if (/\)/.test(char)) {
// it's a curved parenthesis
opPar = url.match(/\(/g) || [];
clPar = url.match(/\)/g);
} else {
// it's a squared parenthesis
opPar = url.match(/\[/g) || [];
clPar = url.match(/]/g);
}
if (opPar.length < clPar.length) {
// there are more closing Parenthesis than opening so chop it!!!!!
url = url.slice(0, -1);
// and prepend it to the suffix
suffix = char + suffix;
} else {
// it's (kinda) balanced so our work is done
break;
}
} else {
// it's not a punctuation or a parenthesis so our work is done
break;
}
}
// we copy the treated url to the text variable
let txt = url;
// finally, if it's a www shortcut, we prepend http
url = (urlPrefix === 'www.') ? 'http://' + url : url;
// url part is done so let's take care of text now
// we need to escape the text (because of links such as www.example.com/foo__bar__baz)
txt = txt.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
// and return the link tag, with the leadingMDChars and suffix. The leadingMDChars are added at the end too because
// we consumed those characters in the regexp
return leadingMDChars +
writeAnchorTag ('autoLink', nakedUrlRegex, wholeMatch, txt, null, url) +
suffix +
leadingMDChars;
});
// 8.2. Now check for naked mail
let nakedMailRegex = /(^|\s)(?:mailto:)?([A-Za-z\d!#$%&'*+-/=?^_`{|}~.]+@[-a-z\d]+(\.[-a-z\d]+)*\.[a-z]+)(?=$|\s)/gmi;
text = text.replace(nakedMailRegex, function (wholeMatch, leadingChar, mail) {
const m = parseMail(mail);
return leadingChar + writeAnchorTag ('autoLink', nakedMailRegex, wholeMatch, m.mail, null, m.url);
});
}
let afterEvent = new showdown.Event('makehtml.link.onEnd', text);
afterEvent
.setOutput(text)
._setGlobals(globals)
._setOptions(options);
afterEvent = globals.converter.dispatch(afterEvent);
return afterEvent.output;
});

View File

@ -1,429 +0,0 @@
////
// makehtml/links.js
// Copyright (c) 2018 ShowdownJS
//
// Transforms MD links into `<a>` html anchors
//
// A link contains link text (the visible text), a link destination (the URI that is the link destination), and
// optionally a link title. There are two basic kinds of links in Markdown.
// In inline links the destination and title are given immediately after the link text.
// In reference links the destination and title are defined elsewhere in the document.
//
// ***Author:***
// - Estevão Soares dos Santos (Tivie) <https://github.com/tivie>
////
(function () {
/**
* Helper function: Wrapper function to pass as second replace parameter
*
* @param {RegExp} rgx
* @param {string} evtRootName
* @param {{}} options
* @param {{}} globals
* @param {boolean} emptyCase
* @returns {Function}
*/
function replaceAnchorTagReference (rgx, evtRootName, options, globals, emptyCase) {
emptyCase = !!emptyCase;
return function (wholeMatch, text, id, url, m5, m6, title) {
// bail we we find 2 newlines somewhere
if (/\n\n/.test(wholeMatch)) {
return wholeMatch;
}
var evt = createEvent(rgx, evtRootName + '.captureStart', wholeMatch, text, id, url, title, options, globals);
return writeAnchorTag(evt, options, globals, emptyCase);
};
}
function replaceAnchorTagBaseUrl (rgx, evtRootName, options, globals, emptyCase) {
return function (wholeMatch, text, id, url, m5, m6, title) {
url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
var evt = createEvent(rgx, evtRootName + '.captureStart', wholeMatch, text, id, url, title, options, globals);
return writeAnchorTag(evt, options, globals, emptyCase);
};
}
/**
* TODO Normalize this
* Helper function: Create a capture event
* @param {RegExp} rgx
* @param {String} evtName Event name
* @param {String} wholeMatch
* @param {String} text
* @param {String} id
* @param {String} url
* @param {String} title
* @param {{}} options
* @param {{}} globals
* @returns {showdown.Event|*}
*/
function createEvent (rgx, evtName, wholeMatch, text, id, url, title, options, globals) {
return globals.converter._dispatch(evtName, wholeMatch, options, globals, {
regexp: rgx,
matches: {
wholeMatch: wholeMatch,
text: text,
id: id,
url: url,
title: title
}
});
}
/**
* Helper Function: Normalize and write an anchor tag based on passed parameters
* @param evt
* @param options
* @param globals
* @param {boolean} emptyCase
* @returns {string}
*/
function writeAnchorTag (evt, options, globals, emptyCase) {
var wholeMatch = evt.matches.wholeMatch;
var text = evt.matches.text;
var id = evt.matches.id;
var url = evt.matches.url;
var title = evt.matches.title;
var target = '';
if (!title) {
title = '';
}
id = (id) ? id.toLowerCase() : '';
if (emptyCase) {
url = '';
} else if (!url) {
if (!id) {
// lower-case and turn embedded newlines into spaces
id = text.toLowerCase().replace(/ ?\n/g, ' ');
}
url = '#' + id;
if (!showdown.helper.isUndefined(globals.gUrls[id])) {
url = globals.gUrls[id];
if (!showdown.helper.isUndefined(globals.gTitles[id])) {
title = globals.gTitles[id];
}
} else {
return wholeMatch;
}
}
//url = showdown.helper.escapeCharacters(url, '*_:~', false); // replaced line to improve performance
url = url.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
if (title !== '' && title !== null) {
title = title.replace(/"/g, '&quot;');
//title = showdown.helper.escapeCharacters(title, '*_', false); // replaced line to improve performance
title = title.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
title = ' title="' + title + '"';
}
// optionLinksInNewWindow only applies
// to external links. Hash links (#) open in same page
if (options.openLinksInNewWindow && !/^#/.test(url)) {
// escaped _
target = ' rel="noopener noreferrer" target="¨E95Eblank"';
}
// Text can be a markdown element, so we run through the appropriate parsers
text = showdown.subParser('makehtml.codeSpan')(text, options, globals);
text = showdown.subParser('makehtml.emoji')(text, options, globals);
text = showdown.subParser('makehtml.underline')(text, options, globals);
text = showdown.subParser('makehtml.emphasisAndStrong')(text, options, globals);
text = showdown.subParser('makehtml.strikethrough')(text, options, globals);
text = showdown.subParser('makehtml.ellipsis')(text, options, globals);
text = showdown.subParser('makehtml.hashHTMLSpans')(text, options, globals);
//evt = createEvent(rgx, evtRootName + '.captureEnd', wholeMatch, text, id, url, title, options, globals);
var result = '<a href="' + url + '"' + title + target + '>' + text + '</a>';
//evt = createEvent(rgx, evtRootName + '.beforeHash', wholeMatch, text, id, url, title, options, globals);
result = showdown.subParser('makehtml.hashHTMLSpans')(result, options, globals);
return result;
}
var evtRootName = 'makehtml.links';
/**
* Turn Markdown link shortcuts into XHTML <a> tags.
*/
showdown.subParser('makehtml.links', function (text, options, globals) {
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
// 1. Handle reference-style links: [link text] [id]
text = showdown.subParser('makehtml.links.reference')(text, options, globals);
// 2. Handle inline-style links: [link text](url "optional title")
text = showdown.subParser('makehtml.links.inline')(text, options, globals);
// 3. Handle reference-style shortcuts: [link text]
// These must come last in case there's a [link text][1] or [link text](/foo)
text = showdown.subParser('makehtml.links.referenceShortcut')(text, options, globals);
// 4. Handle angle brackets links -> `<http://example.com/>`
// Must come after links, because you can use < and > delimiters in inline links like [this](<url>).
text = showdown.subParser('makehtml.links.angleBrackets')(text, options, globals);
// 5. Handle GithubMentions (if option is enabled)
text = showdown.subParser('makehtml.links.ghMentions')(text, options, globals);
// 6. Handle <a> tags and img tags
text = text.replace(/<a\s[^>]*>[\s\S]*<\/a>/g, function (wholeMatch) {
return showdown.helper._hashHTMLSpan(wholeMatch, globals);
});
text = text.replace(/<img\s[^>]*\/?>/g, function (wholeMatch) {
return showdown.helper._hashHTMLSpan(wholeMatch, globals);
});
// 7. Handle naked links (if option is enabled)
text = showdown.subParser('makehtml.links.naked')(text, options, globals);
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
return text;
});
/**
* TODO WRITE THIS DOCUMENTATION
*/
showdown.subParser('makehtml.links.inline', function (text, options, globals) {
var evtRootName = evtRootName + '.inline';
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
// 1. Look for empty cases: []() and [empty]() and []("title")
var rgxEmpty = /\[(.*?)]()()()()\(<? ?>? ?(?:["'](.*)["'])?\)/g;
text = text.replace(rgxEmpty, replaceAnchorTagBaseUrl(rgxEmpty, evtRootName, options, globals, true));
// 2. Look for cases with crazy urls like ./image/cat1).png
var rgxCrazy = /\[((?:\[[^\]]*]|[^\[\]])*)]()\s?\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g;
text = text.replace(rgxCrazy, replaceAnchorTagBaseUrl(rgxCrazy, evtRootName, options, globals));
// 3. inline links with no title or titles wrapped in ' or ":
// [text](url.com) || [text](<url.com>) || [text](url.com "title") || [text](<url.com> "title")
//var rgx2 = /\[[ ]*[\s]?[ ]*([^\n\[\]]*?)[ ]*[\s]?[ ]*] ?()\(<?[ ]*[\s]?[ ]*([^\s'"]*)>?(?:[ ]*[\n]?[ ]*()(['"])(.*?)\5)?[ ]*[\s]?[ ]*\)/; // this regex is too slow!!!
var rgx2 = /\[([\S ]*?)]\s?()\( *<?([^\s'"]*?(?:\([\S]*?\)[\S]*?)?)>?\s*(?:()(['"])(.*?)\5)? *\)/g;
text = text.replace(rgx2, replaceAnchorTagBaseUrl(rgx2, evtRootName, options, globals));
// 4. inline links with titles wrapped in (): [foo](bar.com (title))
var rgx3 = /\[([\S ]*?)]\s?()\( *<?([^\s'"]*?(?:\([\S]*?\)[\S]*?)?)>?\s+()()\((.*?)\) *\)/g;
text = text.replace(rgx3, replaceAnchorTagBaseUrl(rgx3, evtRootName, options, globals));
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
return text;
});
/**
* TODO WRITE THIS DOCUMENTATION
*/
showdown.subParser('makehtml.links.reference', function (text, options, globals) {
var evtRootName = evtRootName + '.reference';
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
var rgx = /\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g;
text = text.replace(rgx, replaceAnchorTagReference(rgx, evtRootName, options, globals));
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
return text;
});
/**
* TODO WRITE THIS DOCUMENTATION
*/
showdown.subParser('makehtml.links.referenceShortcut', function (text, options, globals) {
var evtRootName = evtRootName + '.referenceShortcut';
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
var rgx = /\[([^\[\]]+)]()()()()()/g;
text = text.replace(rgx, replaceAnchorTagReference(rgx, evtRootName, options, globals));
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
return text;
});
/**
* TODO WRITE THIS DOCUMENTATION
*/
showdown.subParser('makehtml.links.ghMentions', function (text, options, globals) {
var evtRootName = evtRootName + 'ghMentions';
if (!options.ghMentions) {
return text;
}
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
var rgx = /(^|\s)(\\)?(@([a-z\d]+(?:[a-z\d._-]+?[a-z\d]+)*))/gi;
text = text.replace(rgx, function (wholeMatch, st, escape, mentions, username) {
// bail if the mentions was escaped
if (escape === '\\') {
return st + mentions;
}
// check if options.ghMentionsLink is a string
// TODO Validation should be done at initialization not at runtime
if (!showdown.helper.isString(options.ghMentionsLink)) {
throw new Error('ghMentionsLink option must be a string');
}
var url = options.ghMentionsLink.replace(/{u}/g, username);
var evt = createEvent(rgx, evtRootName + '.captureStart', wholeMatch, mentions, null, url, null, options, globals);
// captureEnd Event is triggered inside writeAnchorTag function
return st + writeAnchorTag(evt, options, globals);
});
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
return text;
});
/**
* TODO WRITE THIS DOCUMENTATION
*/
showdown.subParser('makehtml.links.angleBrackets', function (text, options, globals) {
var evtRootName = 'makehtml.links.angleBrackets';
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
// 1. Parse links first
var urlRgx = /<(((?:https?|ftp):\/\/|www\.)[^'">\s]+)>/gi;
text = text.replace(urlRgx, function (wholeMatch, url, urlStart) {
var text = url;
url = (urlStart === 'www.') ? 'http://' + url : url;
var evt = createEvent(urlRgx, evtRootName + '.captureStart', wholeMatch, text, null, url, null, options, globals);
return writeAnchorTag(evt, options, globals);
});
// 2. Then Mail Addresses
var mailRgx = /<(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;
text = text.replace(mailRgx, function (wholeMatch, mail) {
var url = 'mailto:';
mail = showdown.subParser('makehtml.unescapeSpecialChars')(mail, options, globals);
if (options.encodeEmails) {
url = showdown.helper.encodeEmailAddress(url + mail);
mail = showdown.helper.encodeEmailAddress(mail);
} else {
url = url + mail;
}
var evt = createEvent(mailRgx, evtRootName + '.captureStart', wholeMatch, mail, null, url, null, options, globals);
return writeAnchorTag(evt, options, globals);
});
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
return text;
});
/**
* TODO MAKE THIS WORK (IT'S NOT ACTIVATED)
* TODO WRITE THIS DOCUMENTATION
*/
showdown.subParser('makehtml.links.naked', function (text, options, globals) {
if (!options.simplifiedAutoLink) {
return text;
}
var evtRootName = 'makehtml.links.naked';
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
// 2. Now we check for
// we also include leading markdown magic chars [_*~] for cases like __https://www.google.com/foobar__
var urlRgx = /([_*~]*?)(((?:https?|ftp):\/\/|www\.)[^\s<>"'`´.-][^\s<>"'`´]*?\.[a-z\d.]+[^\s<>"']*)\1/gi;
text = text.replace(urlRgx, function (wholeMatch, leadingMDChars, url, urlPrefix) {
// we now will start traversing the url from the front to back, looking for punctuation chars [_*~,;:.!?\)\]]
var len = url.length;
var suffix = '';
for (var i = len - 1; i >= 0; --i) {
var char = url.charAt(i);
if (/[_*~,;:.!?]/.test(char)) {
// it's a punctuation char
// we remove it from the url
url = url.slice(0, -1);
// and prepend it to the suffix
suffix = char + suffix;
} else if (/\)/.test(char)) {
var opPar = url.match(/\(/g) || [];
var clPar = url.match(/\)/g);
// it's a curved parenthesis so we need to check for "balance" (kinda)
if (opPar.length < clPar.length) {
// there are more closing Parenthesis than opening so chop it!!!!!
url = url.slice(0, -1);
// and prepend it to the suffix
suffix = char + suffix;
} else {
// it's (kinda) balanced so our work is done
break;
}
} else if (/]/.test(char)) {
var opPar2 = url.match(/\[/g) || [];
var clPar2 = url.match(/\]/g);
// it's a squared parenthesis so we need to check for "balance" (kinda)
if (opPar2.length < clPar2.length) {
// there are more closing Parenthesis than opening so chop it!!!!!
url = url.slice(0, -1);
// and prepend it to the suffix
suffix = char + suffix;
} else {
// it's (kinda) balanced so our work is done
break;
}
} else {
// it's not a punctuation or a parenthesis so our work is done
break;
}
}
// we copy the treated url to the text variable
var text = url;
// finally, if it's a www shortcut, we prepend http
url = (urlPrefix === 'www.') ? 'http://' + url : url;
// url part is done so let's take care of text now
// we need to escape the text (because of links such as www.example.com/foo__bar__baz)
text = text.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
// finally we dispatch the event
var evt = createEvent(urlRgx, evtRootName + '.captureStart', wholeMatch, text, null, url, null, options, globals);
// and return the link tag, with the leadingMDChars and suffix. The leadingMDChars are added at the end too because
// we consumed those characters in the regexp
return leadingMDChars + writeAnchorTag(evt, options, globals) + suffix + leadingMDChars;
});
// 2. Then mails
var mailRgx = /(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gmi;
text = text.replace(mailRgx, function (wholeMatch, leadingChar, mail) {
var url = 'mailto:';
mail = showdown.subParser('makehtml.unescapeSpecialChars')(mail, options, globals);
if (options.encodeEmails) {
url = showdown.helper.encodeEmailAddress(url + mail);
mail = showdown.helper.encodeEmailAddress(mail);
} else {
url = url + mail;
}
var evt = createEvent(mailRgx, evtRootName + '.captureStart', wholeMatch, mail, null, url, null, options, globals);
return leadingChar + writeAnchorTag(evt, options, globals);
});
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
return text;
});
})();

View File

@ -20,7 +20,7 @@ showdown.subParser('makehtml.spanGamut', function (text, options, globals) {
// Process link and image tags. Images must come first,
// because ![foo][f] looks like a link.
text = showdown.subParser('makehtml.image')(text, options, globals);
text = showdown.subParser('makehtml.links')(text, options, globals);
text = showdown.subParser('makehtml.link')(text, options, globals);
text = showdown.subParser('makehtml.emoji')(text, options, globals);
text = showdown.subParser('makehtml.underline')(text, options, globals);

View File

@ -1,7 +1,7 @@
/**
* Created by Estevao on 08-06-2015.
*/
var bootstrap = require('./makehtml.bootstrap.js'),
const bootstrap = require('./makehtml.bootstrap.js'),
showdown = bootstrap.showdown,
assertion = bootstrap.assertion,
testsuite = bootstrap.getTestSuite('test/functional/makehtml/cases/features/'),
@ -29,8 +29,8 @@ describe('makeHtml() features testsuite', function () {
'use strict';
describe('issues', function () {
for (var i = 0; i < testsuite.length; ++i) {
var converter;
for (let i = 0; i < testsuite.length; ++i) {
let converter;
if (testsuite[i].name === '#143.support-image-dimensions') {
converter = new showdown.Converter({parseImgDimensions: true});
} else if (testsuite[i].name === '#69.header-level-start') {
@ -114,9 +114,9 @@ describe('makeHtml() features testsuite', function () {
/** test Table Syntax Support **/
describe('table support', function () {
var converter,
let converter,
suite = tableSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
if (suite[i].name === 'basic-with-header-ids') {
converter = new showdown.Converter({tables: true, tablesHeaderId: true});
} else if (suite[i].name === '#179.parse-md-in-table-ths') {
@ -130,9 +130,9 @@ describe('makeHtml() features testsuite', function () {
/** test simplifiedAutoLink Support **/
describe('simplifiedAutoLink support in', function () {
var converter,
let converter,
suite = simplifiedAutoLinkSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
if (suite[i].name === 'emphasis-and-strikethrough') {
converter = new showdown.Converter({simplifiedAutoLink: true, strikethrough: true});
} else if (suite[i].name === 'disallow-underscores') {
@ -148,9 +148,9 @@ describe('makeHtml() features testsuite', function () {
/** test openLinksInNewWindow support **/
describe('openLinksInNewWindow support in', function () {
var converter,
let converter,
suite = openLinksInNewWindowSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
if (suite[i].name === 'simplifiedAutoLink') {
converter = new showdown.Converter({openLinksInNewWindow: true, simplifiedAutoLink: true});
} else {
@ -162,9 +162,9 @@ describe('makeHtml() features testsuite', function () {
/** test disableForced4SpacesIndentedSublists support **/
describe('disableForced4SpacesIndentedSublists support in', function () {
var converter,
let converter,
suite = disableForced4SpacesIndentedSublistsSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
converter = new showdown.Converter({disableForced4SpacesIndentedSublists: true});
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
}
@ -172,9 +172,9 @@ describe('makeHtml() features testsuite', function () {
/** test rawHeaderId support **/
describe('rawHeaderId support', function () {
var converter,
let converter,
suite = rawHeaderIdSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
if (suite[i].name === 'with-prefixHeaderId') {
converter = new showdown.Converter({rawHeaderId: true, prefixHeaderId: '/prefix/'});
} else {
@ -186,9 +186,9 @@ describe('makeHtml() features testsuite', function () {
/** test rawPrefixHeaderId support **/
describe('rawPrefixHeaderId support', function () {
var converter,
let converter,
suite = rawPrefixHeaderIdSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
converter = new showdown.Converter({rawPrefixHeaderId: true, prefixHeaderId: '/prefix/'});
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
}
@ -196,7 +196,7 @@ describe('makeHtml() features testsuite', function () {
/** test emojis support **/
describe('emojis support', function () {
var converter,
let converter,
suite = emojisSuite,
imgSrcRegexp = /<img[^>]+src=("https?:\/\/[^"]+"|'https?:\/\/[^']+')/g;
@ -222,7 +222,7 @@ describe('makeHtml() features testsuite', function () {
};
}
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
if (suite[i].name === 'simplifiedautolinks') {
converter = new showdown.Converter({emoji: true, simplifiedAutoLink: true});
} else {
@ -231,7 +231,7 @@ describe('makeHtml() features testsuite', function () {
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
var imgUrl = imgSrcRegexp.exec(suite[i].expected);
let imgUrl = imgSrcRegexp.exec(suite[i].expected);
if (imgUrl) {
it('should use a working emoji URL', testImageUrlExists(imgUrl[1]));
}
@ -240,9 +240,9 @@ describe('makeHtml() features testsuite', function () {
/** test underline support **/
describe('underline support', function () {
var converter,
let converter,
suite = underlineSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
if (suite[i].name === 'simplifiedautolinks') {
converter = new showdown.Converter({underline: true, simplifiedAutoLink: true});
} else {
@ -254,9 +254,9 @@ describe('makeHtml() features testsuite', function () {
/** test ellipsis option **/
describe('ellipsis option', function () {
var converter,
let converter,
suite = ellipsisSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
converter = new showdown.Converter({ellipsis: false});
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
}
@ -264,9 +264,9 @@ describe('makeHtml() features testsuite', function () {
/** test literalMidWordUnderscores option **/
describe('literalMidWordUnderscores option', function () {
var converter,
let converter,
suite = literalMidWordUnderscoresSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
converter = new showdown.Converter({literalMidWordUnderscores: true});
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
}
@ -275,9 +275,9 @@ describe('makeHtml() features testsuite', function () {
/** test literalMidWordAsterisks option **/
/*
describe('literalMidWordAsterisks option', function () {
var converter,
let converter,
suite = literalMidWordAsterisksSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
converter = new showdown.Converter({literalMidWordAsterisks: true});
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
}
@ -286,9 +286,9 @@ describe('makeHtml() features testsuite', function () {
/** test completeHTMLDocument option **/
describe('completeHTMLDocument option', function () {
var converter,
let converter,
suite = completeHTMLOutputSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
converter = new showdown.Converter({completeHTMLDocument: true});
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
@ -297,10 +297,10 @@ describe('makeHtml() features testsuite', function () {
/** test metadata option **/
describe('metadata option', function () {
var converter,
let converter,
suite = metadataSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
if (suite[i].name === 'embeded-in-output' ||
suite[i].name === 'embeded-two-consecutive-metadata-blocks' ||
suite[i].name === 'embeded-two-consecutive-metadata-blocks-different-symbols') {
@ -316,10 +316,10 @@ describe('makeHtml() features testsuite', function () {
/** test metadata option **/
describe('splitAdjacentBlockquotes option', function () {
var converter,
let converter,
suite = splitAdjacentBlockquotesSuite;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
converter = new showdown.Converter({splitAdjacentBlockquotes: true});
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
}
@ -327,10 +327,10 @@ describe('makeHtml() features testsuite', function () {
/** test moreStyling option **/
describe('moreStyling option', function () {
var converter,
let converter,
suite = moreStyling;
for (var i = 0; i < suite.length; ++i) {
for (let i = 0; i < suite.length; ++i) {
converter = new showdown.Converter({moreStyling: true, tasklists: true});
it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
}

View File

@ -4,14 +4,12 @@
describe('showdown.Event', function () {
'use strict';
let eventList = {
};
//let eventList = {};
describe('event listeners', function () {
it('should listen to triggered event', function () {
})
});
});
});