diff --git a/packages/richtext-lexical/src/field/features/lists/checklist/feature.client.tsx b/packages/richtext-lexical/src/field/features/lists/checklist/feature.client.tsx new file mode 100644 index 000000000..719654e2a --- /dev/null +++ b/packages/richtext-lexical/src/field/features/lists/checklist/feature.client.tsx @@ -0,0 +1,79 @@ +'use client' +import { INSERT_CHECK_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/list' + +import type { ClientFeature, FeatureProviderProviderClient } from '../../types' + +import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types' +import { ChecklistIcon } from '../../../lexical/ui/icons/Checklist' +import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection' +import { createClientComponent } from '../../createClientComponent' +import { LexicalListPlugin } from '../plugin' +import { CHECK_LIST } from './markdownTransformers' +import { LexicalCheckListPlugin } from './plugin' + +const CheckListFeatureClient: FeatureProviderProviderClient = (props) => { + return { + clientFeatureProps: props, + feature: ({ featureProviderMap }) => { + const plugins: ClientFeature['plugins'] = [ + { + Component: LexicalCheckListPlugin, + position: 'normal', + }, + ] + + if (!featureProviderMap.has('unorderedlist') && !featureProviderMap.has('orderedlist')) { + plugins.push({ + Component: LexicalListPlugin, + position: 'normal', + }) + } + + return { + clientFeatureProps: props, + floatingSelectToolbar: { + sections: [ + TextDropdownSectionWithEntries([ + { + ChildComponent: ChecklistIcon, + isActive: () => false, + key: 'checkList', + label: `Check List`, + onClick: ({ editor }) => { + editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined) + }, + order: 12, + }, + ]), + ], + }, + markdownTransformers: [CHECK_LIST], + nodes: + featureProviderMap.has('unorderedlist') || featureProviderMap.has('orderedlist') + ? [] + : [ListNode, ListItemNode], + plugins, + slashMenu: { + options: [ + { + displayName: 'Lists', + key: 'lists', + options: [ + new SlashMenuOption('checklist', { + Icon: ChecklistIcon, + displayName: 'Check List', + keywords: ['check list', 'check', 'checklist', 'cl'], + onSelect: ({ editor }) => { + editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined) + }, + }), + ], + }, + ], + }, + } + }, + } +} + +export const CheckListFeatureClientComponent = createClientComponent(CheckListFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/lists/checklist/feature.server.ts b/packages/richtext-lexical/src/field/features/lists/checklist/feature.server.ts new file mode 100644 index 000000000..d0063cd80 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/lists/checklist/feature.server.ts @@ -0,0 +1,38 @@ +import { ListItemNode, ListNode } from '@lexical/list' + +import type { FeatureProviderProviderServer } from '../../types' + +import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter' +import { CheckListFeatureClientComponent } from './feature.client' +import { CHECK_LIST } from './markdownTransformers' + +export const CheckListFeature: FeatureProviderProviderServer = (props) => { + return { + feature: ({ featureProviderMap }) => { + return { + ClientComponent: CheckListFeatureClientComponent, + markdownTransformers: [CHECK_LIST], + nodes: + featureProviderMap.has('unorderedlist') || featureProviderMap.has('orderedlist') + ? [] + : [ + { + converters: { + html: ListHTMLConverter, + }, + node: ListNode, + }, + { + converters: { + html: ListItemHTMLConverter, + }, + node: ListItemNode, + }, + ], + serverFeatureProps: props, + } + }, + key: 'checklist', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/lists/checklist/index.ts b/packages/richtext-lexical/src/field/features/lists/checklist/index.ts deleted file mode 100644 index 5749c12e2..000000000 --- a/packages/richtext-lexical/src/field/features/lists/checklist/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { INSERT_CHECK_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/list' - -import type { FeatureProvider } from '../../types' - -import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types' -import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection' -import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter' -import { CHECK_LIST } from './markdownTransformers' - -// 345 -// carbs 7 -export const CheckListFeature = (): FeatureProvider => { - return { - feature: ({ featureProviderMap }) => { - return { - floatingSelectToolbar: { - sections: [ - TextDropdownSectionWithEntries([ - { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Checklist').then( - (module) => module.ChecklistIcon, - ), - isActive: () => false, - key: 'checkList', - label: `Check List`, - onClick: ({ editor }) => { - editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined) - }, - order: 12, - }, - ]), - ], - }, - markdownTransformers: [CHECK_LIST], - nodes: - featureProviderMap.has('unorderedList') || featureProviderMap.has('orderedList') - ? [] - : [ - { - type: ListNode.getType(), - converters: { - html: ListHTMLConverter, - }, - node: ListNode, - }, - { - type: ListItemNode.getType(), - converters: { - html: ListItemHTMLConverter, - }, - node: ListItemNode, - }, - ], - plugins: [ - { - Component: () => - // @ts-expect-error-next-line - import('./plugin').then((module) => module.LexicalCheckListPlugin), - position: 'normal', - }, - ], - props: null, - slashMenu: { - options: [ - { - displayName: 'Lists', - key: 'lists', - options: [ - new SlashMenuOption('checklist', { - Icon: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Checklist').then( - (module) => module.ChecklistIcon, - ), - displayName: 'Check List', - keywords: ['check list', 'check', 'checklist', 'cl'], - onSelect: ({ editor }) => { - editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined) - }, - }), - ], - }, - ], - }, - } - }, - key: 'checklist', - } -} diff --git a/packages/richtext-lexical/src/field/features/lists/orderedlist/index.ts b/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.client.tsx similarity index 52% rename from packages/richtext-lexical/src/field/features/lists/orderedlist/index.ts rename to packages/richtext-lexical/src/field/features/lists/orderedlist/feature.client.tsx index 2387c2f79..3aa1ee0be 100644 --- a/packages/richtext-lexical/src/field/features/lists/orderedlist/index.ts +++ b/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.client.tsx @@ -1,25 +1,27 @@ +'use client' + import { INSERT_ORDERED_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/list' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types' +import { OrderedListIcon } from '../../../lexical/ui/icons/OrderedList' import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection' -import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter' +import { createClientComponent } from '../../createClientComponent' +import { LexicalListPlugin } from '../plugin' import { ORDERED_LIST } from './markdownTransformer' -export const OrderedListFeature = (): FeatureProvider => { +const OrderedListFeatureClient: FeatureProviderProviderClient = (props) => { return { + clientFeatureProps: props, feature: ({ featureProviderMap }) => { return { + clientFeatureProps: props, floatingSelectToolbar: { sections: [ TextDropdownSectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/OrderedList').then( - (module) => module.OrderedListIcon, - ), + ChildComponent: OrderedListIcon, isActive: () => false, key: 'orderedList', label: `Ordered List`, @@ -32,35 +34,15 @@ export const OrderedListFeature = (): FeatureProvider => { ], }, markdownTransformers: [ORDERED_LIST], - nodes: featureProviderMap.has('unorderedList') + nodes: featureProviderMap.has('unorderedlist') ? [] : [ListNode, ListItemNode], + plugins: featureProviderMap.has('unorderedlist') ? [] : [ { - type: ListNode.getType(), - converters: { - html: ListHTMLConverter, - }, - node: ListNode, - }, - { - type: ListItemNode.getType(), - converters: { - html: ListItemHTMLConverter, - }, - node: ListItemNode, - }, - ], - plugins: featureProviderMap.has('unorderedList') - ? [] - : [ - { - Component: () => - // @ts-expect-error-next-line - import('../plugin').then((module) => module.LexicalListPlugin), + Component: LexicalListPlugin, position: 'normal', }, ], - props: null, slashMenu: { options: [ { @@ -68,11 +50,7 @@ export const OrderedListFeature = (): FeatureProvider => { key: 'lists', options: [ new SlashMenuOption('orderedlist', { - Icon: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/OrderedList').then( - (module) => module.OrderedListIcon, - ), + Icon: OrderedListIcon, displayName: 'Ordered List', keywords: ['ordered list', 'ol'], onSelect: ({ editor }) => { @@ -85,6 +63,7 @@ export const OrderedListFeature = (): FeatureProvider => { }, } }, - key: 'orderedlist', } } + +export const OrderedListFeatureClientComponent = createClientComponent(OrderedListFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.server.ts b/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.server.ts new file mode 100644 index 000000000..428bad126 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.server.ts @@ -0,0 +1,37 @@ +import { ListItemNode, ListNode } from '@lexical/list' + +import type { FeatureProviderProviderServer } from '../../types' + +import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter' +import { OrderedListFeatureClientComponent } from './feature.client' +import { ORDERED_LIST } from './markdownTransformer' + +export const OrderedListFeature: FeatureProviderProviderServer = (props) => { + return { + feature: ({ featureProviderMap }) => { + return { + ClientComponent: OrderedListFeatureClientComponent, + markdownTransformers: [ORDERED_LIST], + nodes: featureProviderMap.has('unorderedlist') + ? [] + : [ + { + converters: { + html: ListHTMLConverter, + }, + node: ListNode, + }, + { + converters: { + html: ListItemHTMLConverter, + }, + node: ListItemNode, + }, + ], + serverFeatureProps: props, + } + }, + key: 'orderedlist', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/lists/unorderedlist/index.ts b/packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.client.tsx similarity index 55% rename from packages/richtext-lexical/src/field/features/lists/unorderedlist/index.ts rename to packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.client.tsx index 7bbe7b1d5..0293fe691 100644 --- a/packages/richtext-lexical/src/field/features/lists/unorderedlist/index.ts +++ b/packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.client.tsx @@ -1,25 +1,27 @@ +'use client' + import { INSERT_UNORDERED_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/list' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types' +import { UnorderedListIcon } from '../../../lexical/ui/icons/UnorderedList' import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection' -import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter' +import { createClientComponent } from '../../createClientComponent' +import { LexicalListPlugin } from '../plugin' import { UNORDERED_LIST } from './markdownTransformer' -export const UnorderedListFeature = (): FeatureProvider => { +const UnorderedListFeatureClient: FeatureProviderProviderClient = (props) => { return { + clientFeatureProps: props, feature: () => { return { + clientFeatureProps: props, floatingSelectToolbar: { sections: [ TextDropdownSectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/UnorderedList').then( - (module) => module.UnorderedListIcon, - ), + ChildComponent: UnorderedListIcon, isActive: () => false, key: 'unorderedList', label: `Unordered List`, @@ -32,31 +34,13 @@ export const UnorderedListFeature = (): FeatureProvider => { ], }, markdownTransformers: [UNORDERED_LIST], - nodes: [ - { - type: ListNode.getType(), - converters: { - html: ListHTMLConverter, - }, - node: ListNode, - }, - { - type: ListItemNode.getType(), - converters: { - html: ListItemHTMLConverter, - }, - node: ListItemNode, - }, - ], + nodes: [ListNode, ListItemNode], plugins: [ { - Component: () => - // @ts-expect-error-next-line - import('../plugin').then((module) => module.LexicalListPlugin), + Component: LexicalListPlugin, position: 'normal', }, ], - props: null, slashMenu: { options: [ { @@ -64,11 +48,7 @@ export const UnorderedListFeature = (): FeatureProvider => { key: 'lists', options: [ new SlashMenuOption('unorderedlist', { - Icon: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/UnorderedList').then( - (module) => module.UnorderedListIcon, - ), + Icon: UnorderedListIcon, displayName: 'Unordered List', keywords: ['unordered list', 'ul'], onSelect: ({ editor }) => { @@ -81,6 +61,7 @@ export const UnorderedListFeature = (): FeatureProvider => { }, } }, - key: 'unorderedlist', } } + +export const UnorderedListFeatureClientComponent = createClientComponent(UnorderedListFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.server.ts b/packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.server.ts new file mode 100644 index 000000000..c80a59d55 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.server.ts @@ -0,0 +1,37 @@ +import { ListItemNode, ListNode } from '@lexical/list' + +import type { FeatureProviderProviderServer } from '../../types' + +import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter' +import { UnorderedListFeatureClientComponent } from './feature.client' +import { UNORDERED_LIST } from './markdownTransformer' + +export const UnorderedListFeature: FeatureProviderProviderServer = ( + props, +) => { + return { + feature: () => { + return { + ClientComponent: UnorderedListFeatureClientComponent, + markdownTransformers: [UNORDERED_LIST], + nodes: [ + { + converters: { + html: ListHTMLConverter, + }, + node: ListNode, + }, + { + converters: { + html: ListItemHTMLConverter, + }, + node: ListItemNode, + }, + ], + serverFeatureProps: props, + } + }, + key: 'unorderedlist', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/lexical/config/server/default.ts b/packages/richtext-lexical/src/field/lexical/config/server/default.ts index 8b89fe219..765dd491e 100644 --- a/packages/richtext-lexical/src/field/lexical/config/server/default.ts +++ b/packages/richtext-lexical/src/field/lexical/config/server/default.ts @@ -15,9 +15,9 @@ import { UnderlineFeature } from '../../../features/format/underline/feature.ser import { HeadingFeature } from '../../../features/heading/feature.server' import { IndentFeature } from '../../../features/indent/feature.server' import { LinkFeature } from '../../../features/link/feature.server' -import { CheckListFeature } from '../../../features/lists/checklist' -import { OrderedListFeature } from '../../../features/lists/orderedlist' -import { UnorderedListFeature } from '../../../features/lists/unorderedlist' +import { CheckListFeature } from '../../../features/lists/checklist/feature.server' +import { OrderedListFeature } from '../../../features/lists/orderedlist/feature.server' +import { UnorderedListFeature } from '../../../features/lists/unorderedlist/feature.server' import { ParagraphFeature } from '../../../features/paragraph' import { RelationshipFeature } from '../../../features/relationship' import { UploadFeature } from '../../../features/upload' diff --git a/packages/richtext-lexical/src/index.ts b/packages/richtext-lexical/src/index.ts index 3c64b9ca5..3f6003a56 100644 --- a/packages/richtext-lexical/src/index.ts +++ b/packages/richtext-lexical/src/index.ts @@ -291,9 +291,9 @@ export type { SerializedAutoLinkNode, SerializedLinkNode, } from './field/features/link/nodes/types' -export { CheckListFeature } from './field/features/lists/checklist' -export { OrderedListFeature } from './field/features/lists/orderedlist' -export { UnorderedListFeature } from './field/features/lists/unorderedlist' +export { CheckListFeature } from './field/features/lists/checklist/feature.server' +export { OrderedListFeature } from './field/features/lists/orderedlist/feature.server' +export { UnorderedListFeature } from './field/features/lists/unorderedlist/feature.server' export { LexicalPluginToLexicalFeature } from './field/features/migrations/lexicalPluginToLexical' export { SlateToLexicalFeature } from './field/features/migrations/slateToLexical' export { SlateBlockquoteConverter } from './field/features/migrations/slateToLexical/converter/converters/blockquote' diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index 1f87a456f..78c084736 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -3,16 +3,19 @@ import { BlockQuoteFeature, BlocksFeature, BoldFeature, + CheckListFeature, HeadingFeature, IndentFeature, InlineCodeFeature, ItalicFeature, LinkFeature, + OrderedListFeature, StrikethroughFeature, SubscriptFeature, SuperscriptFeature, TreeViewFeature, UnderlineFeature, + UnorderedListFeature, lexicalEditor, } from '@payloadcms/richtext-lexical' import path from 'path' @@ -91,6 +94,9 @@ export function buildConfigWithDefaults(testConfig?: Partial): Promise