diff --git a/lib/plugins/filter/after_post_render/external_link.js b/lib/plugins/filter/after_post_render/external_link.js index f43ea74716..229f3a3f41 100644 --- a/lib/plugins/filter/after_post_render/external_link.js +++ b/lib/plugins/filter/after_post_render/external_link.js @@ -1,39 +1,32 @@ 'use strict'; const url = require('url'); -let cheerio; function externalLinkFilter(data) { const { config } = this; if (!config.external_link) return; - if (!cheerio) cheerio = require('cheerio'); - - const $ = cheerio.load(data.content, {decodeEntities: false}); const siteHost = url.parse(config.url).hostname || config.url; - $('a').each(function() { - // Exit if the link has target attribute - if ($(this).attr('target')) return; - - // Exit if the href attribute doesn't exists - const href = $(this).attr('href'); - if (!href) return; + data.content = data.content.replace(//gi, (str, hrefStr, href) => { + if (/target=/gi.test(str)) return str; const data = url.parse(href); - // Exit if the link doesn't have protocol, which means it's a internal link - if (!data.protocol) return; - // Exit if the url has same host with config.url - if (data.hostname === siteHost) return; + if (!data.protocol || data.hostname === siteHost) return str; + + if (/rel=/gi.test(str)) { + str = str.replace(/rel="(.*?)"/gi, (relStr, rel) => { + if (!rel.includes('noopenner')) relStr = relStr.replace(rel, `${rel} noopener`); + return relStr; + }); + return str.replace(hrefStr, `${hrefStr} target="_blank"`); + } - $(this) - .attr('target', '_blank') - .attr('rel', 'noopener'); + return str.replace(hrefStr, `${hrefStr} target="_blank" rel="noopener"`); }); - data.content = $.html(); } module.exports = externalLinkFilter; diff --git a/test/scripts/filters/external_link.js b/test/scripts/filters/external_link.js index 18f6d696a1..1e095d6a82 100644 --- a/test/scripts/filters/external_link.js +++ b/test/scripts/filters/external_link.js @@ -26,13 +26,23 @@ describe('External link', () => { '# External link test', '1. External link', 'Hexo', - '2. Internal link', + '2. External link with "rel" Attribute', + 'Hexo', + 'Hexo', + 'Hexo', + 'Hexo', + 'Hexo', + 'Hexo', + '3. External link with Other Attributes', + 'Hexo', + 'Hexo', + '4. Internal link', 'Link', - '3. Ignore links have "target" attribute', + '5. Ignore links have "target" attribute', 'Hexo', - '4. Ignore links don\'t have "href" attribute', + '6. Ignore links don\'t have "href" attribute', 'Anchor', - '5. Ignore links whose hostname is same as config', + '7. Ignore links whose hostname is same as config', 'Example Domain' ].join('\n'); @@ -44,13 +54,23 @@ describe('External link', () => { '# External link test', '1. External link', 'Hexo', - '2. Internal link', + '2. External link with "rel" Attribute', + 'Hexo', + 'Hexo', + 'Hexo', + 'Hexo', + 'Hexo', + 'Hexo', + '3. External link with Other Attributes', + 'Hexo', + 'Hexo', + '4. Internal link', 'Link', - '3. Ignore links have "target" attribute', + '5. Ignore links have "target" attribute', 'Hexo', - '4. Ignore links don\'t have "href" attribute', + '6. Ignore links don\'t have "href" attribute', 'Anchor', - '5. Ignore links whose hostname is same as config', + '7. Ignore links whose hostname is same as config', 'Example Domain' ].join('\n')); });