Skip to content

Commit cc80160

Browse files
authored
fix: share internal/external link logic with Media, Autolink and MDAST (#2686)
1 parent 01af6b7 commit cc80160

File tree

4 files changed

+30
-14
lines changed

4 files changed

+30
-14
lines changed

components/editor/nodes/media.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import MediaOrLink from '@/components/media-or-link'
2-
import { IMGPROXY_URL_REGEXP, decodeProxyUrl } from '@/lib/url'
2+
import { IMGPROXY_URL_REGEXP, decodeProxyUrl, getLinkAttributes } from '@/lib/url'
33
import { useState, useEffect } from 'react'
44
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
55
import { $createLinkNode } from '@lexical/link'
@@ -39,9 +39,11 @@ export default function MediaComponent ({ src, srcSet, bestResSrc, width, height
3939
const parent = node.getParent()
4040
if (!parent) return
4141

42+
const { target, rel } = getLinkAttributes(url)
4243
const linkNode = $createLinkNode(url, {
4344
title: url,
44-
rel: UNKNOWN_LINK_REL
45+
target,
46+
rel
4547
}).append($createTextNode(url))
4648

4749
// If parent is a paragraph, directly replace the media node with the link

lib/lexical/exts/autolink.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { defineExtension } from '@lexical/extension'
22
import { $createItemMentionNode } from '@/lib/lexical/nodes/decorative/mentions/item'
33
import { $createEmbedNode } from '@/lib/lexical/nodes/content/embed'
44
import { $createMediaNode } from '@/lib/lexical/nodes/content/media'
5-
import { parseInternalLinks, parseEmbedUrl, ensureProtocol, isExternal } from '@/lib/url'
5+
import { parseInternalLinks, parseEmbedUrl, ensureProtocol, getLinkAttributes } from '@/lib/url'
66
import { AutoLinkNode, $createLinkNode } from '@lexical/link'
77
import { $isParagraphNode, $createTextNode } from 'lexical'
88

@@ -62,13 +62,12 @@ export const AutoLinkExtension = defineExtension({
6262
return
6363
}
6464

65-
const isHashLink = url?.startsWith('#')
66-
const isInternalLink = isHashLink || !isExternal(url)
6765
// step 4: fallback to full link node
66+
const { target, rel } = getLinkAttributes(url)
6867
const linkNode = $createLinkNode(url, {
6968
title: url,
70-
target: isInternalLink ? '' : '_blank',
71-
rel: isInternalLink ? '' : 'noopener nofollow noreferrer'
69+
target,
70+
rel
7271
}).append($createTextNode(url))
7372
node.replace(linkNode)
7473
})

lib/lexical/mdast/visitors/link.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { $createTextNode } from 'lexical'
22
import { $createLinkNode, $isLinkNode, $createAutoLinkNode, $isAutoLinkNode } from '@lexical/link'
33
import { $isEmbedNode } from '@/lib/lexical/nodes/content/embed'
4-
import { isExternal } from '@/lib/url'
4+
import { getLinkAttributes } from '@/lib/url'
55

66
// check if link is a "bare link" (text matches url)
77
function isBareLink (mdastNode) {
@@ -26,12 +26,11 @@ export const MdastAutolinkVisitor = {
2626
export const MdastLinkVisitor = {
2727
testNode: 'link',
2828
visitNode ({ mdastNode, actions }) {
29-
const isHashLink = mdastNode.url?.startsWith('#')
30-
const isInternalLink = isHashLink || !isExternal(mdastNode.url)
31-
32-
// empty for hash links, use stored value or default to security attributes
33-
const target = isInternalLink ? '' : (mdastNode.target ?? '_blank')
34-
const rel = isInternalLink ? '' : (mdastNode.rel ?? 'noopener nofollow noreferrer')
29+
// use stored values from mdast or defaults for external links
30+
const { target, rel } = getLinkAttributes(mdastNode.url, {
31+
target: mdastNode.target,
32+
rel: mdastNode.rel
33+
})
3534

3635
const link = $createLinkNode(mdastNode.url, {
3736
title: mdastNode.title,

lib/url.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ export function isExternal (url) {
2727
return !url.startsWith(process.env.NEXT_PUBLIC_URL + '/') && !url.startsWith('/')
2828
}
2929

30+
export function isHashLink (url) {
31+
return url?.startsWith('#')
32+
}
33+
34+
export function isInternalLink (url) {
35+
return isHashLink(url) || !isExternal(url)
36+
}
37+
38+
export function getLinkAttributes (url, { target, rel } = {}) {
39+
const internal = isInternalLink(url)
40+
return {
41+
target: internal ? '' : (target ?? '_blank'),
42+
rel: internal ? '' : (rel ?? 'noopener nofollow noreferrer')
43+
}
44+
}
45+
3046
export function removeTracking (value) {
3147
if (!value) return value
3248
const exprs = [

0 commit comments

Comments
 (0)