diff --git a/lib/plugins/filter/after_render/external_link.ts b/lib/plugins/filter/after_render/external_link.ts index 7a0168d842..411502fc17 100644 --- a/lib/plugins/filter/after_render/external_link.ts +++ b/lib/plugins/filter/after_render/external_link.ts @@ -1,5 +1,5 @@ import { isExternalLink } from 'hexo-util'; -import Hexo from '../../../hexo'; +import type Hexo from '../../../hexo'; let EXTERNAL_LINK_SITE_ENABLED = true; const rATag = /]+?\s+?)href=["']((?:https?:|\/\/)[^<>"']+)["'][^<>]*>/gi; @@ -7,6 +7,10 @@ const rTargetAttr = /target=/i; const rRelAttr = /rel=/i; const rRelStrAttr = /rel=["']([^<>"']*)["']/i; +const addNoopener = (relStr: string, rel: string) => { + return rel.includes('noopenner') ? relStr : `rel="${rel} noopener"`; +}; + function externalLinkFilter(this: Hexo, data: string): string { if (!EXTERNAL_LINK_SITE_ENABLED) return; @@ -17,18 +21,30 @@ function externalLinkFilter(this: Hexo, data: string): string { return; } - return data.replace(rATag, (str, href) => { - if (!isExternalLink(href, url, external_link.exclude as any) || rTargetAttr.test(str)) return str; + let result = ''; + let lastIndex = 0; + let match; + + while ((match = rATag.exec(data)) !== null) { + result += data.slice(lastIndex, match.index); - if (rRelAttr.test(str)) { - str = str.replace(rRelStrAttr, (relStr, rel) => { - return rel.includes('noopenner') ? relStr : `rel="${rel} noopener"`; - }); - return str.replace('href=', 'target="_blank" href='); + const str = match[0]; + const href = match[1]; + + if (!isExternalLink(href, url, external_link.exclude as any) || rTargetAttr.test(str)) { + result += str; + } else { + if (rRelAttr.test(str)) { + result += str.replace(rRelStrAttr, addNoopener).replace('href=', 'target="_blank" href='); + } else { + result += str.replace('href=', 'target="_blank" rel="noopener" href='); + } } + lastIndex = rATag.lastIndex; + } + result += data.slice(lastIndex); - return str.replace('href=', 'target="_blank" rel="noopener" href='); - }); + return result; } export = externalLinkFilter;