diff --git a/packages/richtext-lexical/src/field/features/link/drawer/baseFields.ts b/packages/richtext-lexical/src/field/features/link/drawer/baseFields.ts index 335abe4e21..a7a34df2fb 100644 --- a/packages/richtext-lexical/src/field/features/link/drawer/baseFields.ts +++ b/packages/richtext-lexical/src/field/features/link/drawer/baseFields.ts @@ -32,7 +32,7 @@ export const getBaseFields = ( .map(({ slug }) => slug) } - const baseFields = [ + const baseFields: Field[] = [ { name: 'text', type: 'text', @@ -40,63 +40,49 @@ export const getBaseFields = ( required: true, }, { - name: 'fields', - type: 'group', + name: 'linkType', + type: 'radio', admin: { - style: { - borderBottom: 0, - borderTop: 0, - margin: 0, - padding: 0, - }, + description: ({ t }) => t('fields:chooseBetweenCustomTextOrDocument'), }, - fields: [ + defaultValue: 'custom', + label: ({ t }) => t('fields:linkType'), + options: [ { - name: 'linkType', - type: 'radio', - admin: { - description: ({ t }) => t('fields:chooseBetweenCustomTextOrDocument'), - }, - defaultValue: 'custom', - label: ({ t }) => t('fields:linkType'), - options: [ - { - label: ({ t }) => t('fields:customURL'), - value: 'custom', - }, - ], - required: true, + label: ({ t }) => t('fields:customURL'), + value: 'custom', }, - { - name: 'url', - type: 'text', - label: ({ t }) => t('fields:enterURL'), - required: true, - validate: (value: string) => { - if (!validateUrl(value)) { - return 'Invalid URL' - } - }, - }, - ] as Field[], + ], + required: true, + } as RadioField, + { + name: 'url', + type: 'text', + label: ({ t }) => t('fields:enterURL'), + required: true, + validate: (value: string) => { + if (!validateUrl(value)) { + return 'Invalid URL' + } + }, }, ] // Only display internal link-specific fields / options / conditions if there are enabled relations if (enabledRelations?.length) { - ;(baseFields[1].fields[0] as RadioField).options.push({ + ;(baseFields[1] as RadioField).options.push({ label: ({ t }) => t('fields:internalLink'), value: 'internal', }) - ;(baseFields[1].fields[1] as TextField).admin = { - condition: ({ fields }) => fields?.linkType !== 'internal', + ;(baseFields[2] as TextField).admin = { + condition: ({ linkType }) => linkType !== 'internal', } - baseFields[1].fields.push({ + baseFields.push({ name: 'doc', admin: { - condition: ({ fields }) => { - return fields?.linkType === 'internal' + condition: ({ linkType }) => { + return linkType === 'internal' }, }, // when admin.hidden is a function we need to dynamically call hidden with the user to know if the collection should be shown @@ -116,7 +102,7 @@ export const getBaseFields = ( }) } - baseFields[1].fields.push({ + baseFields.push({ name: 'newTab', type: 'checkbox', label: ({ t }) => t('fields:openInNewTab'), diff --git a/packages/richtext-lexical/src/field/features/link/drawer/types.ts b/packages/richtext-lexical/src/field/features/link/drawer/types.ts index 027345762a..d5bcf260fd 100644 --- a/packages/richtext-lexical/src/field/features/link/drawer/types.ts +++ b/packages/richtext-lexical/src/field/features/link/drawer/types.ts @@ -1,9 +1,9 @@ import type { FormState } from 'payload/types' -import type { LinkPayload } from '../plugins/floatingLinkEditor/types.js' +import type { LinkFields } from '../nodes/types.js' export interface Props { drawerSlug: string handleModalSubmit: (fields: FormState, data: Record) => void - stateData?: LinkPayload + stateData?: LinkFields & { text: string } } diff --git a/packages/richtext-lexical/src/field/features/link/plugins/floatingLinkEditor/LinkEditor/index.tsx b/packages/richtext-lexical/src/field/features/link/plugins/floatingLinkEditor/LinkEditor/index.tsx index e7da1b76d8..909e58798f 100644 --- a/packages/richtext-lexical/src/field/features/link/plugins/floatingLinkEditor/LinkEditor/index.tsx +++ b/packages/richtext-lexical/src/field/features/link/plugins/floatingLinkEditor/LinkEditor/index.tsx @@ -23,6 +23,7 @@ import { import React, { useCallback, useEffect, useRef, useState } from 'react' import type { LinkNode } from '../../../nodes/LinkNode.js' +import type { LinkFields } from '../../../nodes/types.js' import type { LinkPayload } from '../types.js' import { useEditorConfigContext } from '../../../../../lexical/config/client/EditorConfigProvider.js' @@ -40,8 +41,8 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R const [editor] = useLexicalComposerContext() const editorRef = useRef(null) - const [linkUrl, setLinkUrl] = useState('') - const [linkLabel, setLinkLabel] = useState('') + const [linkUrl, setLinkUrl] = useState(null) + const [linkLabel, setLinkLabel] = useState(null) const { uuid } = useEditorConfigContext() @@ -49,7 +50,7 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R const { i18n, t } = useTranslation() - const [stateData, setStateData] = useState(null) + const [stateData, setStateData] = useState(null) const { closeModal, toggleModal } = useModal() const editDepth = useEditDepth() @@ -88,27 +89,25 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R if (focusLinkParent == null || badNode) { setIsLink(false) setIsAutoLink(false) - setLinkUrl('') - setLinkLabel('') + setLinkUrl(null) + setLinkLabel(null) setSelectedNodes([]) return } // Initial state: - const data: LinkPayload = { - fields: { - doc: undefined, - linkType: undefined, - newTab: undefined, - url: '', - ...focusLinkParent.getFields(), - }, + const data: LinkFields & { text: string } = { + doc: undefined, + linkType: undefined, + newTab: undefined, + url: '', + ...focusLinkParent.getFields(), text: focusLinkParent.getTextContent(), } if (focusLinkParent.getFields()?.linkType === 'custom') { - setLinkUrl(focusLinkParent.getFields()?.url ?? '') - setLinkLabel('') + setLinkUrl(focusLinkParent.getFields()?.url ?? null) + setLinkLabel(null) } else { // internal link setLinkUrl( @@ -120,10 +119,21 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R const relatedField = config.collections.find( (coll) => coll.slug === focusLinkParent.getFields()?.doc?.relationTo, ) - const label = t('fields:linkedTo', { - label: getTranslation(relatedField.labels.singular, i18n), - }).replace(/<[^>]*>?/g, '') - setLinkLabel(label) + if (!relatedField) { + // Usually happens if the user removed all default fields. In this case, we let them specify the label or do not display the label at all. + // label could be a virtual field the user added. This is useful if they want to use the link feature for things other than links. + setLinkLabel( + focusLinkParent.getFields()?.label ? String(focusLinkParent.getFields()?.label) : null, + ) + setLinkUrl( + focusLinkParent.getFields()?.url ? String(focusLinkParent.getFields()?.url) : null, + ) + } else { + const label = t('fields:linkedTo', { + label: getTranslation(relatedField.labels.singular, i18n), + }).replace(/<[^>]*>?/g, '') + setLinkLabel(label) + } } setStateData(data) @@ -167,8 +177,8 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R if (rootElement !== null) { setFloatingElemPositionForLinkEditor(null, editorElem, anchorElem) } - setLinkUrl('') - setLinkLabel('') + setLinkUrl(null) + setLinkLabel(null) } return true @@ -264,9 +274,14 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
- - {linkLabel != null && linkLabel.length > 0 ? linkLabel : linkUrl} - + {linkUrl && linkUrl.length > 0 ? ( + + {linkLabel != null && linkLabel.length > 0 ? linkLabel : linkUrl} + + ) : linkLabel != null && linkLabel.length > 0 ? ( + {linkLabel} + ) : null} + {editor.isEditable() && (