mirror of
https://github.com/showdownjs/showdown.git
synced 2024-03-22 13:30:55 +08:00
feat(relativePathBaseUrl): Add support for prepending a base URL
This feature enables support for prepending a base URL to relative paths in links and images when converting Markdown to HTML. Closes #536
This commit is contained in:
parent
9c362e832e
commit
e3a5b5928f
|
@ -400,6 +400,34 @@ showdown.helper._hashHTMLSpan = function (html, globals) {
|
||||||
return '¨C' + (globals.gHtmlSpans.push(html) - 1) + 'C';
|
return '¨C' + (globals.gHtmlSpans.push(html) - 1) + 'C';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepends a base URL to relative paths.
|
||||||
|
*
|
||||||
|
* @param {string} baseUrl the base URL to prepend to a relative path
|
||||||
|
* @param {string} url the path to modify, which may be relative
|
||||||
|
* @returns {string} the full URL
|
||||||
|
*/
|
||||||
|
showdown.helper.applyBaseUrl = function (baseUrl, url) {
|
||||||
|
// Only prepend if given a base URL and the path is not absolute.
|
||||||
|
if (baseUrl && !this.isAbsolutePath(url)) {
|
||||||
|
var urlResolve = require('url').resolve;
|
||||||
|
url = urlResolve(baseUrl, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given path is absolute.
|
||||||
|
*
|
||||||
|
* @param {string} path the path to test for absolution
|
||||||
|
* @returns {boolean} `true` if the given path is absolute, else `false`
|
||||||
|
*/
|
||||||
|
showdown.helper.isAbsolutePath = function (path) {
|
||||||
|
// Absolute paths begin with '[protocol:]//' or '#' (anchors)
|
||||||
|
return /(^([a-z]+:)?\/\/)|(^#)/i.test(path);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Showdown's Event Object
|
* Showdown's Event Object
|
||||||
* @param {string} name Name of the event
|
* @param {string} name Name of the event
|
||||||
|
@ -476,7 +504,7 @@ showdown.helper.Event = function (name, text, params) {
|
||||||
/**
|
/**
|
||||||
* POLYFILLS
|
* POLYFILLS
|
||||||
*/
|
*/
|
||||||
// use this instead of builtin is undefined for IE8 compatibility
|
// use this instead if builtin is undefined for IE8 compatibility
|
||||||
if (typeof(console) === 'undefined') {
|
if (typeof(console) === 'undefined') {
|
||||||
console = {
|
console = {
|
||||||
warn: function (msg) {
|
warn: function (msg) {
|
||||||
|
|
|
@ -160,7 +160,12 @@ function getDefaultOpts (simple) {
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
description: 'Split adjacent blockquote blocks',
|
description: 'Split adjacent blockquote blocks',
|
||||||
type: 'boolean'
|
type: 'boolean'
|
||||||
}
|
},
|
||||||
|
relativePathBaseUrl: {
|
||||||
|
defaultValue: false,
|
||||||
|
describe: 'Prepends a base URL to relative paths',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if (simple === false) {
|
if (simple === false) {
|
||||||
return JSON.parse(JSON.stringify(defaultOptions));
|
return JSON.parse(JSON.stringify(defaultOptions));
|
||||||
|
|
|
@ -17,6 +17,12 @@ showdown.subParser('makehtml.images', function (text, options, globals) {
|
||||||
return writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title);
|
return writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function writeImageTagBaseUrl (wholeMatch, altText, linkId, url, width, height, m5, title) {
|
||||||
|
url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
|
||||||
|
|
||||||
|
return writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title);
|
||||||
|
}
|
||||||
|
|
||||||
function writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title) {
|
function writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title) {
|
||||||
|
|
||||||
var gUrls = globals.gUrls,
|
var gUrls = globals.gUrls,
|
||||||
|
@ -91,10 +97,10 @@ showdown.subParser('makehtml.images', function (text, options, globals) {
|
||||||
text = text.replace(base64RegExp, writeImageTagBase64);
|
text = text.replace(base64RegExp, writeImageTagBase64);
|
||||||
|
|
||||||
// cases with crazy urls like ./image/cat1).png
|
// cases with crazy urls like ./image/cat1).png
|
||||||
text = text.replace(crazyRegExp, writeImageTag);
|
text = text.replace(crazyRegExp, writeImageTagBaseUrl);
|
||||||
|
|
||||||
// normal cases
|
// normal cases
|
||||||
text = text.replace(inlineRegExp, writeImageTag);
|
text = text.replace(inlineRegExp, writeImageTagBaseUrl);
|
||||||
|
|
||||||
// handle reference-style shortcuts: ![img text]
|
// handle reference-style shortcuts: ![img text]
|
||||||
text = text.replace(refShortcutRegExp, writeImageTag);
|
text = text.replace(refShortcutRegExp, writeImageTag);
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
* @param {{}} globals
|
* @param {{}} globals
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
function replaceAnchorTag (rgx, evtRootName, options, globals, emptyCase) {
|
function replaceAnchorTagReference (rgx, evtRootName, options, globals, emptyCase) {
|
||||||
emptyCase = !!emptyCase;
|
emptyCase = !!emptyCase;
|
||||||
return function (wholeMatch, text, id, url, m5, m6, title) {
|
return function (wholeMatch, text, id, url, m5, m6, title) {
|
||||||
// bail we we find 2 newlines somewhere
|
// bail we we find 2 newlines somewhere
|
||||||
|
@ -36,6 +36,15 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* TODO Normalize this
|
||||||
* Helper function: Create a capture event
|
* Helper function: Create a capture event
|
||||||
|
@ -192,21 +201,21 @@
|
||||||
|
|
||||||
// 1. Look for empty cases: []() and [empty]() and []("title")
|
// 1. Look for empty cases: []() and [empty]() and []("title")
|
||||||
var rgxEmpty = /\[(.*?)]()()()()\(<? ?>? ?(?:["'](.*)["'])?\)/g;
|
var rgxEmpty = /\[(.*?)]()()()()\(<? ?>? ?(?:["'](.*)["'])?\)/g;
|
||||||
text = text.replace(rgxEmpty, replaceAnchorTag(rgxEmpty, evtRootName, options, globals, true));
|
text = text.replace(rgxEmpty, replaceAnchorTagBaseUrl(rgxEmpty, evtRootName, options, globals, true));
|
||||||
|
|
||||||
// 2. Look for cases with crazy urls like ./image/cat1).png
|
// 2. Look for cases with crazy urls like ./image/cat1).png
|
||||||
var rgxCrazy = /\[((?:\[[^\]]*]|[^\[\]])*)]()\s?\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g;
|
var rgxCrazy = /\[((?:\[[^\]]*]|[^\[\]])*)]()\s?\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g;
|
||||||
text = text.replace(rgxCrazy, replaceAnchorTag(rgxCrazy, evtRootName, options, globals));
|
text = text.replace(rgxCrazy, replaceAnchorTagBaseUrl(rgxCrazy, evtRootName, options, globals));
|
||||||
|
|
||||||
// 3. inline links with no title or titles wrapped in ' or ":
|
// 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")
|
// [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]?[ ]*([^\n\[\]]*?)[ ]*[\s]?[ ]*] ?()\(<?[ ]*[\s]?[ ]*([^\s'"]*)>?(?:[ ]*[\n]?[ ]*()(['"])(.*?)\5)?[ ]*[\s]?[ ]*\)/; // this regex is too slow!!!
|
||||||
var rgx2 = /\[([\S ]*?)]\s?()\( *<?([^\s'"]*?(?:\([\S]*?\)[\S]*?)?)>?\s*(?:()(['"])(.*?)\5)? *\)/g;
|
var rgx2 = /\[([\S ]*?)]\s?()\( *<?([^\s'"]*?(?:\([\S]*?\)[\S]*?)?)>?\s*(?:()(['"])(.*?)\5)? *\)/g;
|
||||||
text = text.replace(rgx2, replaceAnchorTag(rgx2, evtRootName, options, globals));
|
text = text.replace(rgx2, replaceAnchorTagBaseUrl(rgx2, evtRootName, options, globals));
|
||||||
|
|
||||||
// 4. inline links with titles wrapped in (): [foo](bar.com (title))
|
// 4. inline links with titles wrapped in (): [foo](bar.com (title))
|
||||||
var rgx3 = /\[([\S ]*?)]\s?()\( *<?([^\s'"]*?(?:\([\S]*?\)[\S]*?)?)>?\s+()()\((.*?)\) *\)/g;
|
var rgx3 = /\[([\S ]*?)]\s?()\( *<?([^\s'"]*?(?:\([\S]*?\)[\S]*?)?)>?\s+()()\((.*?)\) *\)/g;
|
||||||
text = text.replace(rgx3, replaceAnchorTag(rgx3, evtRootName, options, globals));
|
text = text.replace(rgx3, replaceAnchorTagBaseUrl(rgx3, evtRootName, options, globals));
|
||||||
|
|
||||||
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
|
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
|
||||||
|
|
||||||
|
@ -222,7 +231,7 @@
|
||||||
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
|
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
|
||||||
|
|
||||||
var rgx = /\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g;
|
var rgx = /\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g;
|
||||||
text = text.replace(rgx, replaceAnchorTag(rgx, evtRootName, options, globals));
|
text = text.replace(rgx, replaceAnchorTagReference(rgx, evtRootName, options, globals));
|
||||||
|
|
||||||
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
|
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
|
||||||
|
|
||||||
|
@ -238,7 +247,7 @@
|
||||||
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
|
text = globals.converter._dispatch(evtRootName + '.start', text, options, globals).getText();
|
||||||
|
|
||||||
var rgx = /\[([^\[\]]+)]()()()()()/g;
|
var rgx = /\[([^\[\]]+)]()()()()()/g;
|
||||||
text = text.replace(rgx, replaceAnchorTag(rgx, evtRootName, options, globals));
|
text = text.replace(rgx, replaceAnchorTagReference(rgx, evtRootName, options, globals));
|
||||||
|
|
||||||
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
|
text = globals.converter._dispatch(evtRootName + '.end', text, options, globals).getText();
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ showdown.subParser('makehtml.stripLinkDefinitions', function (text, options, glo
|
||||||
// remove newlines
|
// remove newlines
|
||||||
globals.gUrls[linkId] = url.replace(/\s/g, '');
|
globals.gUrls[linkId] = url.replace(/\s/g, '');
|
||||||
} else {
|
} else {
|
||||||
|
url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
|
||||||
|
|
||||||
globals.gUrls[linkId] = showdown.subParser('makehtml.encodeAmpsAndAngles')(url, options, globals); // Link IDs are case-insensitive
|
globals.gUrls[linkId] = showdown.subParser('makehtml.encodeAmpsAndAngles')(url, options, globals); // Link IDs are case-insensitive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<p><a href="http://my.site.com/that_dude_mike.js">inline relative linky</a></p>
|
||||||
|
<p><a href="ftp://wikis.com/micky.txt">inline absolute linky</a></p>
|
||||||
|
<p><a href="http://my.site.com/painters/Michelangelo.html">global relative linky</a></p>
|
||||||
|
<p><a href="https://www.my-wikis-site.com/peeps/Michelangelo.html">global absolute linky</a></p>
|
||||||
|
<p><img src="http://my.site.com/mona-lisa.png" alt="inline relative image" /></p>
|
||||||
|
<p><img src="http://images.com/mona-lisa.png" alt="inline absolute image" /></p>
|
||||||
|
<p><img src="http://my.site.com/mona-lisa.png" alt="global relative image" /></p>
|
||||||
|
<p><img src="https://www.my-photo-site.com/mona-lisa.png" alt="global absolute image" /></p>
|
||||||
|
<p><a href="#holdin_it_down">just an anchor</a></p>
|
|
@ -0,0 +1,22 @@
|
||||||
|
[inline relative linky](that_dude_mike.js)
|
||||||
|
|
||||||
|
[inline absolute linky](ftp://wikis.com/micky.txt)
|
||||||
|
|
||||||
|
[global relative linky][relative_linky]
|
||||||
|
|
||||||
|
[global absolute linky][absolute_linky]
|
||||||
|
|
||||||
|
![inline relative image](mona-lisa.png)
|
||||||
|
|
||||||
|
![inline absolute image](http://images.com/mona-lisa.png)
|
||||||
|
|
||||||
|
![global relative image][relative_image]
|
||||||
|
|
||||||
|
![global absolute image][absolute_image]
|
||||||
|
|
||||||
|
[just an anchor](#holdin_it_down)
|
||||||
|
|
||||||
|
[relative_linky]: painters/Michelangelo.html
|
||||||
|
[relative_image]: ./mona-lisa.png
|
||||||
|
[absolute_linky]: https://www.my-wikis-site.com/peeps/Michelangelo.html
|
||||||
|
[absolute_image]: https://www.my-photo-site.com/mona-lisa.png
|
|
@ -95,6 +95,8 @@ describe('makeHtml() features testsuite', function () {
|
||||||
converter = new showdown.Converter({openLinksInNewWindow: true});
|
converter = new showdown.Converter({openLinksInNewWindow: true});
|
||||||
} else if (testsuite[i].name === '#355.simplifiedAutoLink-URLs-inside-parenthesis-followed-by-another-character-are-not-parsed-correctly') {
|
} else if (testsuite[i].name === '#355.simplifiedAutoLink-URLs-inside-parenthesis-followed-by-another-character-are-not-parsed-correctly') {
|
||||||
converter = new showdown.Converter({simplifiedAutoLink: true});
|
converter = new showdown.Converter({simplifiedAutoLink: true});
|
||||||
|
} else if (testsuite[i].name === 'relativePathBaseUrl') {
|
||||||
|
converter = new showdown.Converter({relativePathBaseUrl: 'http://my.site.com/'});
|
||||||
} else {
|
} else {
|
||||||
converter = new showdown.Converter();
|
converter = new showdown.Converter();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user