Skip to content

Commit 1e708bd

Browse files
authored
feat(richtext-lexical): adds ability to disable auto link creation (#11563)
This adds a new `disableAutoLinks` property to the `LinkFeature` that lets you disable the automatic creation of links while typing them in the editor or pasting them.
1 parent 36921bd commit 1e708bd

File tree

2 files changed

+75
-54
lines changed

2 files changed

+75
-54
lines changed

packages/richtext-lexical/src/features/link/client/index.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
'use client'
22

3-
import type { LexicalNode } from 'lexical'
3+
import type { Klass, LexicalNode } from 'lexical'
44

55
import { $findMatchingParent } from '@lexical/utils'
66
import { $getSelection, $isRangeSelection } from 'lexical'
77

88
import type { ToolbarGroup } from '../../toolbars/types.js'
9+
import type { ClientFeature } from '../../typesClient.js'
910
import type { LinkFields } from '../nodes/types.js'
1011
import type { ExclusiveLinkCollectionsProps } from '../server/index.js'
1112

@@ -25,6 +26,7 @@ import { LinkPlugin } from './plugins/link/index.js'
2526
export type ClientProps = {
2627
defaultLinkType?: string
2728
defaultLinkURL?: string
29+
disableAutoLinks?: 'creationOnly' | true
2830
} & ExclusiveLinkCollectionsProps
2931

3032
const toolbarGroups: ToolbarGroup[] = [
@@ -80,18 +82,22 @@ const toolbarGroups: ToolbarGroup[] = [
8082
]),
8183
]
8284

83-
export const LinkFeatureClient = createClientFeature({
85+
export const LinkFeatureClient = createClientFeature<ClientProps>(({ props }) => ({
8486
markdownTransformers: [LinkMarkdownTransformer],
85-
nodes: [LinkNode, AutoLinkNode],
87+
nodes: [LinkNode, props?.disableAutoLinks === true ? null : AutoLinkNode].filter(
88+
Boolean,
89+
) as Array<Klass<LexicalNode>>,
8690
plugins: [
8791
{
8892
Component: LinkPlugin,
8993
position: 'normal',
9094
},
91-
{
92-
Component: AutoLinkPlugin,
93-
position: 'normal',
94-
},
95+
props?.disableAutoLinks === true || props?.disableAutoLinks === 'creationOnly'
96+
? null
97+
: {
98+
Component: AutoLinkPlugin,
99+
position: 'normal',
100+
},
95101
{
96102
Component: ClickableLinkPlugin,
97103
position: 'normal',
@@ -100,11 +106,12 @@ export const LinkFeatureClient = createClientFeature({
100106
Component: FloatingLinkEditorPlugin,
101107
position: 'floatingAnchorElem',
102108
},
103-
],
109+
].filter(Boolean) as ClientFeature<ClientProps>['plugins'],
110+
sanitizedClientFeatureProps: props,
104111
toolbarFixed: {
105112
groups: toolbarGroups,
106113
},
107114
toolbarInline: {
108115
groups: toolbarGroups,
109116
},
110-
})
117+
}))

packages/richtext-lexical/src/features/link/server/index.ts

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
import escapeHTML from 'escape-html'
1111
import { sanitizeFields } from 'payload'
1212

13+
import type { NodeWithHooks } from '../../typesServer.js'
1314
import type { ClientProps } from '../client/index.js'
1415

1516
import { createServerFeature } from '../../../utilities/createServerFeature.js'
@@ -46,6 +47,16 @@ export type ExclusiveLinkCollectionsProps =
4647
}
4748

4849
export type LinkFeatureServerProps = {
50+
/**
51+
* Disables the automatic creation of links from URLs pasted into the editor, as well
52+
* as auto link nodes.
53+
*
54+
* If set to 'creationOnly', only the creation of new auto link nodes will be disabled.
55+
* Existing auto link nodes will still be editable.
56+
*
57+
* @default false
58+
*/
59+
disableAutoLinks?: 'creationOnly' | true
4960
/**
5061
* A function or array defining additional fields for the link feature. These will be
5162
* displayed in the link editor drawer.
@@ -130,6 +141,7 @@ export const LinkFeature = createServerFeature<
130141
clientFeatureProps: {
131142
defaultLinkType,
132143
defaultLinkURL,
144+
disableAutoLinks: props.disableAutoLinks,
133145
disabledCollections: props.disabledCollections,
134146
enabledCollections: props.enabledCollections,
135147
} as ClientProps,
@@ -148,55 +160,57 @@ export const LinkFeature = createServerFeature<
148160
i18n,
149161
markdownTransformers: [LinkMarkdownTransformer],
150162
nodes: [
151-
createNode({
152-
converters: {
153-
html: {
154-
converter: async ({
155-
converters,
156-
currentDepth,
157-
depth,
158-
draft,
159-
node,
160-
overrideAccess,
161-
parent,
162-
req,
163-
showHiddenFields,
164-
}) => {
165-
const childrenText = await convertLexicalNodesToHTML({
166-
converters,
167-
currentDepth,
168-
depth,
169-
draft,
170-
lexicalNodes: node.children,
171-
overrideAccess,
172-
parent: {
173-
...node,
163+
props?.disableAutoLinks === true
164+
? null
165+
: createNode({
166+
converters: {
167+
html: {
168+
converter: async ({
169+
converters,
170+
currentDepth,
171+
depth,
172+
draft,
173+
node,
174+
overrideAccess,
174175
parent,
175-
},
176-
req,
177-
showHiddenFields,
178-
})
176+
req,
177+
showHiddenFields,
178+
}) => {
179+
const childrenText = await convertLexicalNodesToHTML({
180+
converters,
181+
currentDepth,
182+
depth,
183+
draft,
184+
lexicalNodes: node.children,
185+
overrideAccess,
186+
parent: {
187+
...node,
188+
parent,
189+
},
190+
req,
191+
showHiddenFields,
192+
})
179193

180-
const rel: string = node.fields.newTab ? ' rel="noopener noreferrer"' : ''
181-
const target: string = node.fields.newTab ? ' target="_blank"' : ''
194+
const rel: string = node.fields.newTab ? ' rel="noopener noreferrer"' : ''
195+
const target: string = node.fields.newTab ? ' target="_blank"' : ''
182196

183-
let href: string = node.fields.url ?? ''
184-
if (node.fields.linkType === 'internal') {
185-
href =
186-
typeof node.fields.doc?.value !== 'object'
187-
? String(node.fields.doc?.value)
188-
: String(node.fields.doc?.value?.id)
189-
}
197+
let href: string = node.fields.url ?? ''
198+
if (node.fields.linkType === 'internal') {
199+
href =
200+
typeof node.fields.doc?.value !== 'object'
201+
? String(node.fields.doc?.value)
202+
: String(node.fields.doc?.value?.id)
203+
}
190204

191-
return `<a href="${href}"${target}${rel}>${childrenText}</a>`
205+
return `<a href="${href}"${target}${rel}>${childrenText}</a>`
206+
},
207+
nodeTypes: [AutoLinkNode.getType()],
208+
},
192209
},
193-
nodeTypes: [AutoLinkNode.getType()],
194-
},
195-
},
196-
node: AutoLinkNode,
197-
// Since AutoLinkNodes are just internal links, they need no hooks or graphQL population promises
198-
validations: [linkValidation(props, sanitizedFieldsWithoutText)],
199-
}),
210+
node: AutoLinkNode,
211+
// Since AutoLinkNodes are just internal links, they need no hooks or graphQL population promises
212+
validations: [linkValidation(props, sanitizedFieldsWithoutText)],
213+
}),
200214
createNode({
201215
converters: {
202216
html: {
@@ -249,7 +263,7 @@ export const LinkFeature = createServerFeature<
249263
node: LinkNode,
250264
validations: [linkValidation(props, sanitizedFieldsWithoutText)],
251265
}),
252-
],
266+
].filter(Boolean) as Array<NodeWithHooks>,
253267
sanitizedServerFeatureProps: props,
254268
}
255269
},

0 commit comments

Comments
 (0)