From c48801d8fab440e526e9ea889d0546b3dfd1715c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Estev=C3=A3o=20Soares=20dos=20Santos?= Date: Thu, 21 Apr 2022 00:36:17 +0100 Subject: [PATCH] add links --- package-lock.json | 4 +- src/subParsers/makehtml/image.js | 4 +- src/subParsers/makehtml/link.js | 256 ++++++++++- src/subParsers/makehtml/links.old.js | 429 ------------------ src/subParsers/makehtml/spanGamut.js | 2 +- .../functional/makehtml/testsuite.features.js | 68 +-- test/unit/events.js | 6 +- 7 files changed, 279 insertions(+), 490 deletions(-) delete mode 100644 src/subParsers/makehtml/links.old.js diff --git a/package-lock.json b/package-lock.json index 473b1b6..5b3af32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/src/subParsers/makehtml/image.js b/src/subParsers/makehtml/image.js index 0ed6128..a42eb33 100644 --- a/src/subParsers/makehtml/image.js +++ b/src/subParsers/makehtml/image.js @@ -140,9 +140,9 @@ showdown.subParser('makehtml.image', function (text, options, globals) { startEvent = globals.converter.dispatch(startEvent); text = startEvent.output; - let inlineRegExp = /!\[([^\]]*?)][ \t]*\([ \t]??(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\5)?[ \t]?\)/g, + let inlineRegExp = /!\[([^\]]*?)][ \t]*\([ \t]??(?: =([*\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]??(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g, + base64RegExp = /!\[([^\]]*?)][ \t]*\([ \t]??(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g, referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]/g, refShortcutRegExp = /!\[([^\[\]]+)]/g; diff --git a/src/subParsers/makehtml/link.js b/src/subParsers/makehtml/link.js index 2b87930..b5e8700 100644 --- a/src/subParsers/makehtml/link.js +++ b/src/subParsers/makehtml/link.js @@ -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, '"') .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 = '' + text + ''; + } - - 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]() || [text](url.com "title") || [text]( "title") + let inlineNormalRegex1 = /\[([\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+\((.*?)\) *\)/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 -> `` + // Must come after links, because you can use < and > delimiters in inline links like [this](). + + // 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 tags + text = text.replace(/]*>[\s\S]*<\/a>/g, function (wholeMatch) { + return showdown.helper._hashHTMLSpan(wholeMatch, globals); + }); + + // 7. Handle tags + text = text.replace(/]*\/?>/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; }); diff --git a/src/subParsers/makehtml/links.old.js b/src/subParsers/makehtml/links.old.js deleted file mode 100644 index 70c2a07..0000000 --- a/src/subParsers/makehtml/links.old.js +++ /dev/null @@ -1,429 +0,0 @@ -//// -// makehtml/links.js -// Copyright (c) 2018 ShowdownJS -// -// Transforms MD links into `` 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) -//// - -(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, '"'); - //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 = '' + text + ''; - - //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 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 -> `` - // Must come after links, because you can use < and > delimiters in inline links like [this](). - 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 tags and img tags - text = text.replace(/]*>[\s\S]*<\/a>/g, function (wholeMatch) { - return showdown.helper._hashHTMLSpan(wholeMatch, globals); - }); - - text = text.replace(/]*\/?>/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]() || [text](url.com "title") || [text]( "title") - //var rgx2 = /\[[ ]*[\s]?[ ]*([^\n\[\]]*?)[ ]*[\s]?[ ]*] ?()\(?(?:[ ]*[\n]?[ ]*()(['"])(.*?)\5)?[ ]*[\s]?[ ]*\)/; // this regex is too slow!!! - var rgx2 = /\[([\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+()()\((.*?)\) *\)/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; - }); -})(); diff --git a/src/subParsers/makehtml/spanGamut.js b/src/subParsers/makehtml/spanGamut.js index e16662e..794a115 100644 --- a/src/subParsers/makehtml/spanGamut.js +++ b/src/subParsers/makehtml/spanGamut.js @@ -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); diff --git a/test/functional/makehtml/testsuite.features.js b/test/functional/makehtml/testsuite.features.js index add5f0b..5e8b18a 100644 --- a/test/functional/makehtml/testsuite.features.js +++ b/test/functional/makehtml/testsuite.features.js @@ -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 = /]+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)); } diff --git a/test/unit/events.js b/test/unit/events.js index 31b1186..812c04d 100644 --- a/test/unit/events.js +++ b/test/unit/events.js @@ -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 () { - }) + }); }); });