feat(richtext-lexical)!: finalize ClientFeature interface (#6191)
**BREAKING:** If you have own, custom lexical features, there will be a bunch of breaking API changes for you. The saved JSON data is not affected. - `floatingSelectToolbar` has been changed to `toolbarInline` - `slashMenu.dynamicOptions `and `slashMenu.options` have been changed to `slashMenu.groups` and `slashMenu.dynamicGroups` - `toolbarFixed.sections` is now `toolbarFixed.groups` - Slash menu group `options` and toolbar group `entries` have both been renamed to `items` - Toolbar group item `onClick` has been renamed to `onSelect` to match slash menu properties - slashMenu item `onSelect` is no longer auto-wrapped inside an `editor.update`. If you perform editor updates in them, you have to wrap it inside an `editor.update` callback yourself. Within our own features this extra control has removed a good amount of unnecessary, nested `editor.update` calls, which is good - Slash menu items are no longer initialized using the `new` keyword, as they are now types and no longer classes. You can convert them to an object and add the `key` property as an object property instead of an argument to the previous SlashMenuItem constructor - CSS classnames for slash menu and toolbars, as well as their items, have changed - `CheckListFeature` is now exported as and has been renamed to `ChecklistFeature` For guidance on migration, check out how we migrated our own features in this PR's diff: https://github.com/payloadcms/payload/pull/6191/files
This commit is contained in:
@@ -2,5 +2,5 @@ export { RichTextCell } from '../cell/index.js'
|
||||
export { RichTextField } from '../field/index.js'
|
||||
|
||||
export { defaultEditorLexicalConfig } from '../field/lexical/config/client/default.js'
|
||||
export { ToolbarButton } from '../field/lexical/plugins/FloatingSelectToolbar/ToolbarButton/index.js'
|
||||
export { ToolbarDropdown } from '../field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/index.js'
|
||||
export { ToolbarButton } from '../field/lexical/plugins/toolbars/inline/ToolbarButton/index.js'
|
||||
export { ToolbarDropdown } from '../field/lexical/plugins/toolbars/inline/ToolbarDropdown/index.js'
|
||||
|
||||
@@ -9,58 +9,52 @@ import { AlignJustifyIcon } from '../../lexical/ui/icons/AlignJustify/index.js'
|
||||
import { AlignLeftIcon } from '../../lexical/ui/icons/AlignLeft/index.js'
|
||||
import { AlignRightIcon } from '../../lexical/ui/icons/AlignRight/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { AlignDropdownSectionWithEntries } from './floatingSelectToolbarAlignDropdownSection.js'
|
||||
import { alignGroupWithItems } from './inlineToolbarAlignGroup.js'
|
||||
|
||||
const AlignFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
feature: () => ({
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
AlignDropdownSectionWithEntries([
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
alignGroupWithItems([
|
||||
{
|
||||
ChildComponent: AlignLeftIcon,
|
||||
isActive: () => false,
|
||||
key: 'align-left',
|
||||
key: 'alignLeft',
|
||||
label: `Align Left`,
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left')
|
||||
},
|
||||
order: 1,
|
||||
},
|
||||
]),
|
||||
AlignDropdownSectionWithEntries([
|
||||
{
|
||||
ChildComponent: AlignCenterIcon,
|
||||
isActive: () => false,
|
||||
key: 'align-center',
|
||||
key: 'alignCenter',
|
||||
label: `Align Center`,
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center')
|
||||
},
|
||||
order: 2,
|
||||
},
|
||||
]),
|
||||
AlignDropdownSectionWithEntries([
|
||||
{
|
||||
ChildComponent: AlignRightIcon,
|
||||
isActive: () => false,
|
||||
key: 'align-right',
|
||||
key: 'alignRight',
|
||||
label: `Align Right`,
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right')
|
||||
},
|
||||
order: 3,
|
||||
},
|
||||
]),
|
||||
AlignDropdownSectionWithEntries([
|
||||
{
|
||||
ChildComponent: AlignJustifyIcon,
|
||||
isActive: () => false,
|
||||
key: 'align-justify',
|
||||
key: 'alignJustify',
|
||||
label: `Align Justify`,
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'justify')
|
||||
},
|
||||
order: 4,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import type {
|
||||
FloatingToolbarSection,
|
||||
FloatingToolbarSectionEntry,
|
||||
} from '../../lexical/plugins/FloatingSelectToolbar/types.js'
|
||||
|
||||
import { AlignLeftIcon } from '../../lexical/ui/icons/AlignLeft/index.js'
|
||||
|
||||
export const AlignDropdownSectionWithEntries = (
|
||||
entries: FloatingToolbarSectionEntry[],
|
||||
): FloatingToolbarSection => {
|
||||
return {
|
||||
type: 'dropdown',
|
||||
ChildComponent: AlignLeftIcon,
|
||||
entries,
|
||||
key: 'dropdown-align',
|
||||
order: 2,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type {
|
||||
InlineToolbarGroup,
|
||||
InlineToolbarGroupItem,
|
||||
} from '../../lexical/plugins/toolbars/inline/types.js'
|
||||
|
||||
import { AlignLeftIcon } from '../../lexical/ui/icons/AlignLeft/index.js'
|
||||
|
||||
export const alignGroupWithItems = (items: InlineToolbarGroupItem[]): InlineToolbarGroup => {
|
||||
return {
|
||||
type: 'dropdown',
|
||||
ChildComponent: AlignLeftIcon,
|
||||
items,
|
||||
key: 'align',
|
||||
order: 2,
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,9 @@ import { $getSelection } from 'lexical'
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../types.js'
|
||||
|
||||
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { BlockquoteIcon } from '../../lexical/ui/icons/Blockquote/index.js'
|
||||
import { TextDropdownSectionWithEntries } from '../common/floatingSelectToolbarTextDropdownSection/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { inlineToolbarTextDropdownGroupWithItems } from '../shared/inlineToolbar/textDropdownGroup.js'
|
||||
import { MarkdownTransformer } from './markdownTransformer.js'
|
||||
|
||||
const BlockQuoteFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
@@ -17,15 +16,40 @@ const BlockQuoteFeatureClient: FeatureProviderProviderClient<undefined> = (props
|
||||
clientFeatureProps: props,
|
||||
feature: () => ({
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
TextDropdownSectionWithEntries([
|
||||
markdownTransformers: [MarkdownTransformer],
|
||||
nodes: [QuoteNode],
|
||||
|
||||
slashMenu: {
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Basic',
|
||||
items: [
|
||||
{
|
||||
Icon: BlockquoteIcon,
|
||||
displayName: 'Blockquote',
|
||||
key: 'blockquote',
|
||||
keywords: ['quote', 'blockquote'],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection()
|
||||
$setBlocksType(selection, () => $createQuoteNode())
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
key: 'basic',
|
||||
},
|
||||
],
|
||||
},
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarTextDropdownGroupWithItems([
|
||||
{
|
||||
ChildComponent: BlockquoteIcon,
|
||||
isActive: () => false,
|
||||
key: 'blockquote',
|
||||
label: `Blockquote`,
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection()
|
||||
$setBlocksType(selection, () => $createQuoteNode())
|
||||
@@ -36,28 +60,6 @@ const BlockQuoteFeatureClient: FeatureProviderProviderClient<undefined> = (props
|
||||
]),
|
||||
],
|
||||
},
|
||||
markdownTransformers: [MarkdownTransformer],
|
||||
|
||||
nodes: [QuoteNode],
|
||||
slashMenu: {
|
||||
options: [
|
||||
{
|
||||
displayName: 'Basic',
|
||||
key: 'basic',
|
||||
options: [
|
||||
new SlashMenuOption(`blockquote`, {
|
||||
Icon: BlockquoteIcon,
|
||||
displayName: `Blockquote`,
|
||||
keywords: ['quote', 'blockquote'],
|
||||
onSelect: () => {
|
||||
const selection = $getSelection()
|
||||
$setBlocksType(selection, () => $createQuoteNode())
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../types.js'
|
||||
|
||||
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { BlockIcon } from '../../lexical/ui/icons/Block/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { BlockNode } from './nodes/BlocksNode.js'
|
||||
@@ -30,32 +29,31 @@ const BlocksFeatureClient: FeatureProviderProviderClient<BlocksFeatureClientProp
|
||||
},
|
||||
],
|
||||
slashMenu: {
|
||||
options: [
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Blocks',
|
||||
key: 'blocks',
|
||||
options: [
|
||||
...props.reducedBlocks.map((block) => {
|
||||
return new SlashMenuOption('block-' + block.slug, {
|
||||
Icon: BlockIcon,
|
||||
displayName: ({ i18n }) => {
|
||||
if (!block.labels.singular) {
|
||||
return block.slug
|
||||
}
|
||||
items: props.reducedBlocks.map((block) => {
|
||||
return {
|
||||
Icon: BlockIcon,
|
||||
displayName: ({ i18n }) => {
|
||||
if (!block.labels.singular) {
|
||||
return block.slug
|
||||
}
|
||||
|
||||
return getTranslation(block.labels.singular, i18n)
|
||||
},
|
||||
keywords: ['block', 'blocks', block.slug],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_BLOCK_COMMAND, {
|
||||
id: null,
|
||||
blockName: '',
|
||||
blockType: block.slug,
|
||||
})
|
||||
},
|
||||
})
|
||||
}),
|
||||
],
|
||||
return getTranslation(block.labels.singular, i18n)
|
||||
},
|
||||
key: 'block-' + block.slug,
|
||||
keywords: ['block', 'blocks', block.slug],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_BLOCK_COMMAND, {
|
||||
id: null,
|
||||
blockName: '',
|
||||
blockType: block.slug,
|
||||
})
|
||||
},
|
||||
}
|
||||
}),
|
||||
key: 'blocks',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type {
|
||||
FloatingToolbarSection,
|
||||
FloatingToolbarSectionEntry,
|
||||
} from '../../../lexical/plugins/FloatingSelectToolbar/types.js'
|
||||
|
||||
export const FeaturesSectionWithEntries = (
|
||||
entries: FloatingToolbarSectionEntry[],
|
||||
): FloatingToolbarSection => {
|
||||
return {
|
||||
type: 'buttons',
|
||||
entries,
|
||||
key: 'features',
|
||||
order: 5,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import type {
|
||||
FloatingToolbarSection,
|
||||
FloatingToolbarSectionEntry,
|
||||
} from '../../../lexical/plugins/FloatingSelectToolbar/types.js'
|
||||
|
||||
import { TextIcon } from '../../../lexical/ui/icons/Text/index.js'
|
||||
|
||||
export const TextDropdownSectionWithEntries = (
|
||||
entries: FloatingToolbarSectionEntry[],
|
||||
): FloatingToolbarSection => {
|
||||
return {
|
||||
type: 'dropdown',
|
||||
ChildComponent: TextIcon,
|
||||
entries,
|
||||
key: 'dropdown-text',
|
||||
order: 1,
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export const TestRecorderFeature: FeatureProviderProviderServer<undefined, undef
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
},
|
||||
key: 'testrecorder',
|
||||
key: 'testRecorder',
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export const TreeViewFeature: FeatureProviderProviderServer<undefined, undefined
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
},
|
||||
key: 'treeview',
|
||||
key: 'treeView',
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { BoldIcon } from '../../../lexical/ui/icons/Bold/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
|
||||
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
|
||||
import {
|
||||
BOLD_ITALIC_STAR,
|
||||
BOLD_ITALIC_UNDERSCORE,
|
||||
@@ -24,9 +24,10 @@ const BoldFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
SectionWithEntries([
|
||||
markdownTransformers,
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarFormatGroupWithItems([
|
||||
{
|
||||
ChildComponent: BoldIcon,
|
||||
isActive: ({ selection }) => {
|
||||
@@ -36,7 +37,7 @@ const BoldFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
return false
|
||||
},
|
||||
key: 'bold',
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')
|
||||
},
|
||||
order: 1,
|
||||
@@ -44,7 +45,6 @@ const BoldFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
]),
|
||||
],
|
||||
},
|
||||
markdownTransformers,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type {
|
||||
FloatingToolbarSection,
|
||||
FloatingToolbarSectionEntry,
|
||||
} from '../../../lexical/plugins/FloatingSelectToolbar/types.js'
|
||||
|
||||
export const SectionWithEntries = (
|
||||
entries: FloatingToolbarSectionEntry[],
|
||||
): FloatingToolbarSection => {
|
||||
return {
|
||||
type: 'buttons',
|
||||
entries,
|
||||
key: 'format',
|
||||
order: 4,
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { CodeIcon } from '../../../lexical/ui/icons/Code/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
|
||||
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
|
||||
import { INLINE_CODE } from './markdownTransformers.js'
|
||||
|
||||
const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
@@ -15,9 +15,11 @@ const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props
|
||||
feature: () => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
SectionWithEntries([
|
||||
markdownTransformers: [INLINE_CODE],
|
||||
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarFormatGroupWithItems([
|
||||
{
|
||||
ChildComponent: CodeIcon,
|
||||
isActive: ({ selection }) => {
|
||||
@@ -26,8 +28,8 @@ const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props
|
||||
}
|
||||
return false
|
||||
},
|
||||
key: 'code',
|
||||
onClick: ({ editor }) => {
|
||||
key: 'inlineCode',
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code')
|
||||
},
|
||||
order: 7,
|
||||
@@ -35,8 +37,6 @@ const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props
|
||||
]),
|
||||
],
|
||||
},
|
||||
|
||||
markdownTransformers: [INLINE_CODE],
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export const InlineCodeFeature: FeatureProviderProviderServer<undefined, undefin
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
},
|
||||
key: 'inlinecode',
|
||||
key: 'inlineCode',
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { ItalicIcon } from '../../../lexical/ui/icons/Italic/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
|
||||
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
|
||||
import { ITALIC_STAR, ITALIC_UNDERSCORE } from './markdownTransformers.js'
|
||||
|
||||
const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
@@ -16,9 +16,10 @@ const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) =>
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
SectionWithEntries([
|
||||
markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE],
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarFormatGroupWithItems([
|
||||
{
|
||||
ChildComponent: ItalicIcon,
|
||||
isActive: ({ selection }) => {
|
||||
@@ -28,7 +29,7 @@ const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) =>
|
||||
return false
|
||||
},
|
||||
key: 'italic',
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')
|
||||
},
|
||||
order: 2,
|
||||
@@ -36,7 +37,6 @@ const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) =>
|
||||
]),
|
||||
],
|
||||
},
|
||||
markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type {
|
||||
InlineToolbarGroup,
|
||||
InlineToolbarGroupItem,
|
||||
} from '../../../lexical/plugins/toolbars/inline/types.js'
|
||||
|
||||
export const inlineToolbarFormatGroupWithItems = (
|
||||
items: InlineToolbarGroupItem[],
|
||||
): InlineToolbarGroup => {
|
||||
return {
|
||||
type: 'buttons',
|
||||
items,
|
||||
key: 'format',
|
||||
order: 4,
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { StrikethroughIcon } from '../../../lexical/ui/icons/Strikethrough/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
|
||||
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
|
||||
import { STRIKETHROUGH } from './markdownTransformers.js'
|
||||
|
||||
const StrikethroughFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
@@ -16,9 +16,10 @@ const StrikethroughFeatureClient: FeatureProviderProviderClient<undefined> = (pr
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
SectionWithEntries([
|
||||
markdownTransformers: [STRIKETHROUGH],
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarFormatGroupWithItems([
|
||||
{
|
||||
ChildComponent: StrikethroughIcon,
|
||||
isActive: ({ selection }) => {
|
||||
@@ -28,7 +29,7 @@ const StrikethroughFeatureClient: FeatureProviderProviderClient<undefined> = (pr
|
||||
return false
|
||||
},
|
||||
key: 'strikethrough',
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')
|
||||
},
|
||||
order: 4,
|
||||
@@ -36,7 +37,6 @@ const StrikethroughFeatureClient: FeatureProviderProviderClient<undefined> = (pr
|
||||
]),
|
||||
],
|
||||
},
|
||||
markdownTransformers: [STRIKETHROUGH],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { SubscriptIcon } from '../../../lexical/ui/icons/Subscript/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
|
||||
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
|
||||
|
||||
const SubscriptFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
return {
|
||||
@@ -14,9 +14,9 @@ const SubscriptFeatureClient: FeatureProviderProviderClient<undefined> = (props)
|
||||
feature: () => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
SectionWithEntries([
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarFormatGroupWithItems([
|
||||
{
|
||||
ChildComponent: SubscriptIcon,
|
||||
isActive: ({ selection }) => {
|
||||
@@ -26,7 +26,7 @@ const SubscriptFeatureClient: FeatureProviderProviderClient<undefined> = (props)
|
||||
return false
|
||||
},
|
||||
key: 'subscript',
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript')
|
||||
},
|
||||
order: 5,
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { SuperscriptIcon } from '../../../lexical/ui/icons/Superscript/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
|
||||
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
|
||||
|
||||
const SuperscriptFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
return {
|
||||
@@ -14,9 +14,9 @@ const SuperscriptFeatureClient: FeatureProviderProviderClient<undefined> = (prop
|
||||
feature: () => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
SectionWithEntries([
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarFormatGroupWithItems([
|
||||
{
|
||||
ChildComponent: SuperscriptIcon,
|
||||
isActive: ({ selection }) => {
|
||||
@@ -26,7 +26,7 @@ const SuperscriptFeatureClient: FeatureProviderProviderClient<undefined> = (prop
|
||||
return false
|
||||
},
|
||||
key: 'superscript',
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript')
|
||||
},
|
||||
order: 6,
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { UnderlineIcon } from '../../../lexical/ui/icons/Underline/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
|
||||
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
|
||||
|
||||
const UnderlineFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
return {
|
||||
@@ -14,9 +14,9 @@ const UnderlineFeatureClient: FeatureProviderProviderClient<undefined> = (props)
|
||||
feature: () => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
SectionWithEntries([
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarFormatGroupWithItems([
|
||||
{
|
||||
ChildComponent: UnderlineIcon,
|
||||
isActive: ({ selection }) => {
|
||||
@@ -26,7 +26,7 @@ const UnderlineFeatureClient: FeatureProviderProviderClient<undefined> = (props)
|
||||
return false
|
||||
},
|
||||
key: 'underline',
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline')
|
||||
},
|
||||
order: 3,
|
||||
|
||||
@@ -9,15 +9,14 @@ import { $getSelection } from 'lexical'
|
||||
import type { FeatureProviderProviderClient } from '../types.js'
|
||||
import type { HeadingFeatureProps } from './feature.server.js'
|
||||
|
||||
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { H1Icon } from '../../lexical/ui/icons/H1/index.js'
|
||||
import { H2Icon } from '../../lexical/ui/icons/H2/index.js'
|
||||
import { H3Icon } from '../../lexical/ui/icons/H3/index.js'
|
||||
import { H4Icon } from '../../lexical/ui/icons/H4/index.js'
|
||||
import { H5Icon } from '../../lexical/ui/icons/H5/index.js'
|
||||
import { H6Icon } from '../../lexical/ui/icons/H6/index.js'
|
||||
import { TextDropdownSectionWithEntries } from '../common/floatingSelectToolbarTextDropdownSection/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { inlineToolbarTextDropdownGroupWithItems } from '../shared/inlineToolbar/textDropdownGroup.js'
|
||||
import { MarkdownTransformer } from './markdownTransformer.js'
|
||||
|
||||
const setHeading = (headingSize: HeadingTagType) => {
|
||||
@@ -42,47 +41,52 @@ const HeadingFeatureClient: FeatureProviderProviderClient<HeadingFeatureProps> =
|
||||
feature: () => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
...enabledHeadingSizes.map((headingSize, i) =>
|
||||
TextDropdownSectionWithEntries([
|
||||
{
|
||||
ChildComponent: iconImports[headingSize],
|
||||
isActive: () => false,
|
||||
key: headingSize,
|
||||
label: `Heading ${headingSize.charAt(1)}`,
|
||||
onClick: ({ editor }) => {
|
||||
editor.update(() => {
|
||||
setHeading(headingSize)
|
||||
})
|
||||
},
|
||||
order: i + 2,
|
||||
},
|
||||
]),
|
||||
),
|
||||
],
|
||||
},
|
||||
markdownTransformers: [MarkdownTransformer(enabledHeadingSizes)],
|
||||
nodes: [HeadingNode],
|
||||
slashMenu: {
|
||||
options: [
|
||||
...enabledHeadingSizes.map((headingSize) => {
|
||||
return {
|
||||
displayName: 'Basic',
|
||||
key: 'basic',
|
||||
options: [
|
||||
new SlashMenuOption(`heading-${headingSize.charAt(1)}`, {
|
||||
Icon: iconImports[headingSize],
|
||||
displayName: `Heading ${headingSize.charAt(1)}`,
|
||||
keywords: ['heading', headingSize],
|
||||
onSelect: () => {
|
||||
setHeading(headingSize)
|
||||
},
|
||||
groups: enabledHeadingSizes?.length
|
||||
? [
|
||||
{
|
||||
displayName: 'Basic',
|
||||
items: enabledHeadingSizes.map((headingSize) => {
|
||||
return {
|
||||
Icon: iconImports[headingSize],
|
||||
displayName: `Heading ${headingSize.charAt(1)}`,
|
||||
key: `heading-${headingSize.charAt(1)}`,
|
||||
keywords: ['heading', headingSize],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.update(() => {
|
||||
setHeading(headingSize)
|
||||
})
|
||||
},
|
||||
}
|
||||
}),
|
||||
],
|
||||
}
|
||||
}),
|
||||
],
|
||||
key: 'basic',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
toolbarInline: {
|
||||
groups: enabledHeadingSizes?.length
|
||||
? [
|
||||
inlineToolbarTextDropdownGroupWithItems(
|
||||
enabledHeadingSizes.map((headingSize, i) => {
|
||||
return {
|
||||
ChildComponent: iconImports[headingSize],
|
||||
isActive: () => false,
|
||||
key: headingSize,
|
||||
label: `Heading ${headingSize.charAt(1)}`,
|
||||
onSelect: ({ editor }) => {
|
||||
editor.update(() => {
|
||||
setHeading(headingSize)
|
||||
})
|
||||
},
|
||||
order: i + 2,
|
||||
}
|
||||
}),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../types.js'
|
||||
|
||||
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { HorizontalRuleIcon } from '../../lexical/ui/icons/HorizontalRule/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { MarkdownTransformer } from './markdownTransformer.js'
|
||||
@@ -23,20 +22,21 @@ const HorizontalRuleFeatureClient: FeatureProviderProviderClient<undefined> = (p
|
||||
},
|
||||
],
|
||||
slashMenu: {
|
||||
options: [
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Basic',
|
||||
key: 'basic',
|
||||
options: [
|
||||
new SlashMenuOption(`horizontalrule`, {
|
||||
items: [
|
||||
{
|
||||
Icon: HorizontalRuleIcon,
|
||||
displayName: `Horizontal Rule`,
|
||||
key: 'horizontalRule',
|
||||
keywords: ['hr', 'horizontal rule', 'line', 'separator'],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
key: 'basic',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -30,7 +30,7 @@ export const HorizontalRuleFeature: FeatureProviderProviderServer<undefined, und
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
},
|
||||
key: 'horizontalrule',
|
||||
key: 'horizontalRule',
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,16 @@ import type { FeatureProviderProviderClient } from '../types.js'
|
||||
import { IndentDecreaseIcon } from '../../lexical/ui/icons/IndentDecrease/index.js'
|
||||
import { IndentIncreaseIcon } from '../../lexical/ui/icons/IndentIncrease/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { IndentSectionWithEntries } from './floatingSelectToolbarIndentSection.js'
|
||||
import { indentGroupWithItems } from './inlineToolbarIndentGroup.js'
|
||||
|
||||
const IndentFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
feature: () => ({
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
IndentSectionWithEntries([
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
indentGroupWithItems([
|
||||
{
|
||||
ChildComponent: IndentDecreaseIcon,
|
||||
isActive: () => false,
|
||||
@@ -37,21 +37,19 @@ const IndentFeatureClient: FeatureProviderProviderClient<undefined> = (props) =>
|
||||
}
|
||||
return false
|
||||
},
|
||||
key: 'indent-decrease',
|
||||
key: 'indentDecrease',
|
||||
label: `Decrease Indent`,
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined)
|
||||
},
|
||||
order: 1,
|
||||
},
|
||||
]),
|
||||
IndentSectionWithEntries([
|
||||
{
|
||||
ChildComponent: IndentIncreaseIcon,
|
||||
isActive: () => false,
|
||||
key: 'indent-increase',
|
||||
key: 'indentIncrease',
|
||||
label: `Increase Indent`,
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined)
|
||||
},
|
||||
order: 2,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type {
|
||||
FloatingToolbarSection,
|
||||
FloatingToolbarSectionEntry,
|
||||
} from '../../lexical/plugins/FloatingSelectToolbar/types.js'
|
||||
|
||||
export const IndentSectionWithEntries = (
|
||||
entries: FloatingToolbarSectionEntry[],
|
||||
): FloatingToolbarSection => {
|
||||
return {
|
||||
type: 'buttons',
|
||||
entries,
|
||||
key: 'indent',
|
||||
order: 3,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
.floating-select-toolbar-popup__section-indent {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type {
|
||||
InlineToolbarGroup,
|
||||
InlineToolbarGroupItem,
|
||||
} from '../../lexical/plugins/toolbars/inline/types.js'
|
||||
|
||||
export const indentGroupWithItems = (items: InlineToolbarGroupItem[]): InlineToolbarGroup => {
|
||||
return {
|
||||
type: 'buttons',
|
||||
items,
|
||||
key: 'indent',
|
||||
order: 3,
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import type { LinkFields } from './nodes/types.js'
|
||||
|
||||
import { LinkIcon } from '../../lexical/ui/icons/Link/index.js'
|
||||
import { getSelectedNode } from '../../lexical/utils/getSelectedNode.js'
|
||||
import { FeaturesSectionWithEntries } from '../common/floatingSelectToolbarFeaturesButtonsSection/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { inlineToolbarFeatureButtonsGroupWithItems } from '../shared/inlineToolbar/featureButtonsGroup.js'
|
||||
import { AutoLinkNode } from './nodes/AutoLinkNode.js'
|
||||
import { $isLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from './nodes/LinkNode.js'
|
||||
import { AutoLinkPlugin } from './plugins/autoLink/index.js'
|
||||
@@ -26,9 +26,28 @@ const LinkFeatureClient: FeatureProviderProviderClient<ClientProps> = (props) =>
|
||||
clientFeatureProps: props,
|
||||
feature: () => ({
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
FeaturesSectionWithEntries([
|
||||
nodes: [LinkNode, AutoLinkNode],
|
||||
plugins: [
|
||||
{
|
||||
Component: LinkPlugin,
|
||||
position: 'normal',
|
||||
},
|
||||
{
|
||||
Component: AutoLinkPlugin,
|
||||
position: 'normal',
|
||||
},
|
||||
{
|
||||
Component: ClickableLinkPlugin,
|
||||
position: 'normal',
|
||||
},
|
||||
{
|
||||
Component: FloatingLinkEditorPlugin,
|
||||
position: 'floatingAnchorElem',
|
||||
},
|
||||
],
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarFeatureButtonsGroupWithItems([
|
||||
{
|
||||
ChildComponent: LinkIcon,
|
||||
isActive: ({ selection }) => {
|
||||
@@ -41,7 +60,7 @@ const LinkFeatureClient: FeatureProviderProviderClient<ClientProps> = (props) =>
|
||||
},
|
||||
key: 'link',
|
||||
label: `Link`,
|
||||
onClick: ({ editor, isActive }) => {
|
||||
onSelect: ({ editor, isActive }) => {
|
||||
if (!isActive) {
|
||||
let selectedText = null
|
||||
editor.getEditorState().read(() => {
|
||||
@@ -67,25 +86,6 @@ const LinkFeatureClient: FeatureProviderProviderClient<ClientProps> = (props) =>
|
||||
]),
|
||||
],
|
||||
},
|
||||
nodes: [LinkNode, AutoLinkNode],
|
||||
plugins: [
|
||||
{
|
||||
Component: LinkPlugin,
|
||||
position: 'normal',
|
||||
},
|
||||
{
|
||||
Component: AutoLinkPlugin,
|
||||
position: 'normal',
|
||||
},
|
||||
{
|
||||
Component: ClickableLinkPlugin,
|
||||
position: 'normal',
|
||||
},
|
||||
{
|
||||
Component: FloatingLinkEditorPlugin,
|
||||
position: 'floatingAnchorElem',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,14 @@ import { INSERT_CHECK_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/list
|
||||
|
||||
import type { ClientFeature, FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { ChecklistIcon } from '../../../lexical/ui/icons/Checklist/index.js'
|
||||
import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { inlineToolbarTextDropdownGroupWithItems } from '../../shared/inlineToolbar/textDropdownGroup.js'
|
||||
import { LexicalListPlugin } from '../plugin/index.js'
|
||||
import { CHECK_LIST } from './markdownTransformers.js'
|
||||
import { LexicalCheckListPlugin } from './plugin/index.js'
|
||||
|
||||
const CheckListFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
const ChecklistFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
feature: ({ featureProviderMap }) => {
|
||||
@@ -22,7 +21,7 @@ const CheckListFeatureClient: FeatureProviderProviderClient<undefined> = (props)
|
||||
},
|
||||
]
|
||||
|
||||
if (!featureProviderMap.has('unorderedlist') && !featureProviderMap.has('orderedlist')) {
|
||||
if (!featureProviderMap.has('unorderedList') && !featureProviderMap.has('orderedList')) {
|
||||
plugins.push({
|
||||
Component: LexicalListPlugin,
|
||||
position: 'normal',
|
||||
@@ -31,15 +30,40 @@ const CheckListFeatureClient: FeatureProviderProviderClient<undefined> = (props)
|
||||
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
TextDropdownSectionWithEntries([
|
||||
markdownTransformers: [CHECK_LIST],
|
||||
nodes:
|
||||
featureProviderMap.has('unorderedList') || featureProviderMap.has('orderedList')
|
||||
? []
|
||||
: [ListNode, ListItemNode],
|
||||
plugins,
|
||||
slashMenu: {
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Lists',
|
||||
items: [
|
||||
{
|
||||
Icon: ChecklistIcon,
|
||||
displayName: 'Check List',
|
||||
key: 'checklist',
|
||||
keywords: ['check list', 'check', 'checklist', 'cl'],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined)
|
||||
},
|
||||
},
|
||||
],
|
||||
key: 'lists',
|
||||
},
|
||||
],
|
||||
},
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarTextDropdownGroupWithItems([
|
||||
{
|
||||
ChildComponent: ChecklistIcon,
|
||||
isActive: () => false,
|
||||
key: 'checkList',
|
||||
key: 'checklist',
|
||||
label: `Check List`,
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined)
|
||||
},
|
||||
order: 12,
|
||||
@@ -47,33 +71,9 @@ const CheckListFeatureClient: FeatureProviderProviderClient<undefined> = (props)
|
||||
]),
|
||||
],
|
||||
},
|
||||
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)
|
||||
export const ChecklistFeatureClientComponent = createClientComponent(ChecklistFeatureClient)
|
||||
|
||||
@@ -4,17 +4,17 @@ import type { FeatureProviderProviderServer } from '../../types.js'
|
||||
|
||||
import { createNode } from '../../typeUtilities.js'
|
||||
import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter.js'
|
||||
import { CheckListFeatureClientComponent } from './feature.client.js'
|
||||
import { ChecklistFeatureClientComponent } from './feature.client.js'
|
||||
import { CHECK_LIST } from './markdownTransformers.js'
|
||||
|
||||
export const CheckListFeature: FeatureProviderProviderServer<undefined, undefined> = (props) => {
|
||||
export const ChecklistFeature: FeatureProviderProviderServer<undefined, undefined> = (props) => {
|
||||
return {
|
||||
feature: ({ featureProviderMap }) => {
|
||||
return {
|
||||
ClientComponent: CheckListFeatureClientComponent,
|
||||
ClientComponent: ChecklistFeatureClientComponent,
|
||||
markdownTransformers: [CHECK_LIST],
|
||||
nodes:
|
||||
featureProviderMap.has('unorderedlist') || featureProviderMap.has('orderedlist')
|
||||
featureProviderMap.has('unorderedList') || featureProviderMap.has('orderedList')
|
||||
? []
|
||||
: [
|
||||
createNode({
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ElementTransformer } from '@lexical/markdown'
|
||||
|
||||
import { $isListNode, ListItemNode, ListNode } from '@lexical/list'
|
||||
|
||||
import { listExport, listReplace } from '../common/markdown.js'
|
||||
import { listExport, listReplace } from '../shared/markdown.js'
|
||||
|
||||
export const CHECK_LIST: ElementTransformer = {
|
||||
type: 'element',
|
||||
|
||||
@@ -3,10 +3,9 @@ import { INSERT_ORDERED_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/li
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { OrderedListIcon } from '../../../lexical/ui/icons/OrderedList/index.js'
|
||||
import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { inlineToolbarTextDropdownGroupWithItems } from '../../shared/inlineToolbar/textDropdownGroup.js'
|
||||
import { LexicalListPlugin } from '../plugin/index.js'
|
||||
import { ORDERED_LIST } from './markdownTransformer.js'
|
||||
|
||||
@@ -16,25 +15,9 @@ const OrderedListFeatureClient: FeatureProviderProviderClient<undefined> = (prop
|
||||
feature: ({ featureProviderMap }) => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
TextDropdownSectionWithEntries([
|
||||
{
|
||||
ChildComponent: OrderedListIcon,
|
||||
isActive: () => false,
|
||||
key: 'orderedList',
|
||||
label: `Ordered List`,
|
||||
onClick: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)
|
||||
},
|
||||
order: 10,
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
markdownTransformers: [ORDERED_LIST],
|
||||
nodes: featureProviderMap.has('unorderedlist') ? [] : [ListNode, ListItemNode],
|
||||
plugins: featureProviderMap.has('unorderedlist')
|
||||
nodes: featureProviderMap.has('orderedList') ? [] : [ListNode, ListItemNode],
|
||||
plugins: featureProviderMap.has('orderedList')
|
||||
? []
|
||||
: [
|
||||
{
|
||||
@@ -43,23 +26,40 @@ const OrderedListFeatureClient: FeatureProviderProviderClient<undefined> = (prop
|
||||
},
|
||||
],
|
||||
slashMenu: {
|
||||
options: [
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Lists',
|
||||
key: 'lists',
|
||||
options: [
|
||||
new SlashMenuOption('orderedlist', {
|
||||
items: [
|
||||
{
|
||||
Icon: OrderedListIcon,
|
||||
displayName: 'Ordered List',
|
||||
key: 'orderedList',
|
||||
keywords: ['ordered list', 'ol'],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
key: 'lists',
|
||||
},
|
||||
],
|
||||
},
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarTextDropdownGroupWithItems([
|
||||
{
|
||||
ChildComponent: OrderedListIcon,
|
||||
isActive: () => false,
|
||||
key: 'orderedList',
|
||||
label: `Ordered List`,
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)
|
||||
},
|
||||
order: 10,
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export const OrderedListFeature: FeatureProviderProviderServer<undefined, undefi
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
},
|
||||
key: 'orderedlist',
|
||||
key: 'orderedList',
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { ElementTransformer } from '@lexical/markdown'
|
||||
|
||||
import { $isListNode, ListItemNode, ListNode } from '@lexical/list'
|
||||
|
||||
import { listExport, listReplace } from '../common/markdown.js'
|
||||
import { listExport, listReplace } from '../shared/markdown.js'
|
||||
|
||||
export const ORDERED_LIST: ElementTransformer = {
|
||||
type: 'element',
|
||||
@@ -4,10 +4,10 @@ import { INSERT_UNORDERED_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../../types.js'
|
||||
|
||||
import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { SlashMenuItem } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { UnorderedListIcon } from '../../../lexical/ui/icons/UnorderedList/index.js'
|
||||
import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection/index.js'
|
||||
import { createClientComponent } from '../../createClientComponent.js'
|
||||
import { inlineToolbarTextDropdownGroupWithItems } from '../../shared/inlineToolbar/textDropdownGroup.js'
|
||||
import { LexicalListPlugin } from '../plugin/index.js'
|
||||
import { UNORDERED_LIST } from './markdownTransformer.js'
|
||||
|
||||
@@ -17,22 +17,6 @@ const UnorderedListFeatureClient: FeatureProviderProviderClient<undefined> = (pr
|
||||
feature: () => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
TextDropdownSectionWithEntries([
|
||||
{
|
||||
ChildComponent: UnorderedListIcon,
|
||||
isActive: () => false,
|
||||
key: 'unorderedList',
|
||||
label: `Unordered List`,
|
||||
onClick: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)
|
||||
},
|
||||
order: 11,
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
markdownTransformers: [UNORDERED_LIST],
|
||||
nodes: [ListNode, ListItemNode],
|
||||
plugins: [
|
||||
@@ -42,23 +26,40 @@ const UnorderedListFeatureClient: FeatureProviderProviderClient<undefined> = (pr
|
||||
},
|
||||
],
|
||||
slashMenu: {
|
||||
options: [
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Lists',
|
||||
key: 'lists',
|
||||
options: [
|
||||
new SlashMenuOption('unorderedlist', {
|
||||
items: [
|
||||
{
|
||||
Icon: UnorderedListIcon,
|
||||
displayName: 'Unordered List',
|
||||
key: 'unorderedList',
|
||||
keywords: ['unordered list', 'ul'],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
key: 'lists',
|
||||
},
|
||||
],
|
||||
},
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarTextDropdownGroupWithItems([
|
||||
{
|
||||
ChildComponent: UnorderedListIcon,
|
||||
isActive: () => false,
|
||||
key: 'unorderedList',
|
||||
label: `Unordered List`,
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)
|
||||
},
|
||||
order: 11,
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export const UnorderedListFeature: FeatureProviderProviderServer<undefined, unde
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
},
|
||||
key: 'unorderedlist',
|
||||
key: 'unorderedList',
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { ElementTransformer } from '@lexical/markdown'
|
||||
|
||||
import { $isListNode, ListItemNode, ListNode } from '@lexical/list'
|
||||
|
||||
import { listExport, listReplace } from '../common/markdown.js'
|
||||
import { listExport, listReplace } from '../shared/markdown.js'
|
||||
|
||||
export const UNORDERED_LIST: ElementTransformer = {
|
||||
type: 'element',
|
||||
@@ -5,25 +5,46 @@ import { $createParagraphNode, $getSelection } from 'lexical'
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../types.js'
|
||||
|
||||
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { TextIcon } from '../../lexical/ui/icons/Text/index.js'
|
||||
import { TextDropdownSectionWithEntries } from '../common/floatingSelectToolbarTextDropdownSection/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { inlineToolbarTextDropdownGroupWithItems } from '../shared/inlineToolbar/textDropdownGroup.js'
|
||||
|
||||
const ParagraphFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||
return {
|
||||
clientFeatureProps: props,
|
||||
feature: () => ({
|
||||
clientFeatureProps: props,
|
||||
floatingSelectToolbar: {
|
||||
sections: [
|
||||
TextDropdownSectionWithEntries([
|
||||
slashMenu: {
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Basic',
|
||||
items: [
|
||||
{
|
||||
Icon: TextIcon,
|
||||
displayName: 'Paragraph',
|
||||
key: 'paragraph',
|
||||
keywords: ['normal', 'paragraph', 'p', 'text'],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection()
|
||||
$setBlocksType(selection, () => $createParagraphNode())
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
key: 'basic',
|
||||
},
|
||||
],
|
||||
},
|
||||
toolbarInline: {
|
||||
groups: [
|
||||
inlineToolbarTextDropdownGroupWithItems([
|
||||
{
|
||||
ChildComponent: TextIcon,
|
||||
isActive: () => false,
|
||||
key: 'normal-text',
|
||||
key: 'paragraph',
|
||||
label: 'Normal Text',
|
||||
onClick: ({ editor }) => {
|
||||
onSelect: ({ editor }) => {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection()
|
||||
$setBlocksType(selection, () => $createParagraphNode())
|
||||
@@ -34,27 +55,6 @@ const ParagraphFeatureClient: FeatureProviderProviderClient<undefined> = (props)
|
||||
]),
|
||||
],
|
||||
},
|
||||
slashMenu: {
|
||||
options: [
|
||||
{
|
||||
displayName: 'Basic',
|
||||
key: 'basic',
|
||||
options: [
|
||||
new SlashMenuOption('paragraph', {
|
||||
Icon: TextIcon,
|
||||
displayName: 'Paragraph',
|
||||
keywords: ['normal', 'paragraph', 'p', 'text'],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection()
|
||||
$setBlocksType(selection, () => $createParagraphNode())
|
||||
})
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { withMergedProps } from '@payloadcms/ui/elements/withMergedProps'
|
||||
import type { FeatureProviderProviderClient } from '../types.js'
|
||||
import type { RelationshipFeatureProps } from './feature.server.js'
|
||||
|
||||
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { RelationshipIcon } from '../../lexical/ui/icons/Relationship/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND } from './drawer/commands.js'
|
||||
@@ -30,14 +29,14 @@ const RelationshipFeatureClient: FeatureProviderProviderClient<RelationshipFeatu
|
||||
},
|
||||
],
|
||||
slashMenu: {
|
||||
options: [
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Basic',
|
||||
key: 'basic',
|
||||
options: [
|
||||
new SlashMenuOption('relationship', {
|
||||
items: [
|
||||
{
|
||||
Icon: RelationshipIcon,
|
||||
displayName: 'Relationship',
|
||||
key: 'relationship',
|
||||
keywords: ['relationship', 'relation', 'rel'],
|
||||
onSelect: ({ editor }) => {
|
||||
// dispatch INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND
|
||||
@@ -45,8 +44,9 @@ const RelationshipFeatureClient: FeatureProviderProviderClient<RelationshipFeatu
|
||||
replace: false,
|
||||
})
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
key: 'basic',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type {
|
||||
InlineToolbarGroup,
|
||||
InlineToolbarGroupItem,
|
||||
} from '../../../lexical/plugins/toolbars/inline/types.js'
|
||||
|
||||
export const inlineToolbarFeatureButtonsGroupWithItems = (
|
||||
items: InlineToolbarGroupItem[],
|
||||
): InlineToolbarGroup => {
|
||||
return {
|
||||
type: 'buttons',
|
||||
items,
|
||||
key: 'features',
|
||||
order: 5,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type {
|
||||
InlineToolbarGroup,
|
||||
InlineToolbarGroupItem,
|
||||
} from '../../../lexical/plugins/toolbars/inline/types.js'
|
||||
|
||||
import { TextIcon } from '../../../lexical/ui/icons/Text/index.js'
|
||||
|
||||
export const inlineToolbarTextDropdownGroupWithItems = (
|
||||
items: InlineToolbarGroupItem[],
|
||||
): InlineToolbarGroup => {
|
||||
return {
|
||||
type: 'dropdown',
|
||||
ChildComponent: TextIcon,
|
||||
items,
|
||||
key: 'text',
|
||||
order: 1,
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,9 @@ import type React from 'react'
|
||||
|
||||
import type { AdapterProps } from '../../types.js'
|
||||
import type { ClientEditorConfig, ServerEditorConfig } from '../lexical/config/types.js'
|
||||
import type { FloatingToolbarSection } from '../lexical/plugins/FloatingSelectToolbar/types.js'
|
||||
import type { SlashMenuGroup } from '../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import type { FixedToolbarGroup } from '../lexical/plugins/toolbars/fixed/types.js'
|
||||
import type { InlineToolbarGroup } from '../lexical/plugins/toolbars/inline/types.js'
|
||||
import type { HTMLConverter } from './converters/html/converter/types.js'
|
||||
|
||||
export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexicalNode> = ({
|
||||
@@ -131,10 +132,6 @@ export type ClientFeature<ClientFeatureProps> = {
|
||||
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
|
||||
*/
|
||||
clientFeatureProps: ClientComponentProps<ClientFeatureProps>
|
||||
|
||||
floatingSelectToolbar?: {
|
||||
sections: FloatingToolbarSection[]
|
||||
}
|
||||
hooks?: {
|
||||
load?: ({
|
||||
incomingEditorState,
|
||||
@@ -149,6 +146,9 @@ export type ClientFeature<ClientFeatureProps> = {
|
||||
}
|
||||
markdownTransformers?: Transformer[]
|
||||
nodes?: Array<Klass<LexicalNode> | LexicalNodeReplacement>
|
||||
/**
|
||||
* Plugins are react component which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
|
||||
*/
|
||||
plugins?: Array<
|
||||
| {
|
||||
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
|
||||
@@ -172,14 +172,39 @@ export type ClientFeature<ClientFeatureProps> = {
|
||||
}
|
||||
>
|
||||
slashMenu?: {
|
||||
dynamicOptions?: ({
|
||||
/**
|
||||
* Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash).
|
||||
* Thus, to re-calculate the available groups, this function will be called every time you type after the /.
|
||||
*
|
||||
* The groups provided by dynamicGroups will be merged with the static groups provided by the groups property.
|
||||
*/
|
||||
dynamicGroups?: ({
|
||||
editor,
|
||||
queryString,
|
||||
}: {
|
||||
editor: LexicalEditor
|
||||
queryString: string
|
||||
}) => SlashMenuGroup[]
|
||||
options?: SlashMenuGroup[]
|
||||
/**
|
||||
* Static array of groups together with the items in them. These will always be present.
|
||||
* While typing after the /, they will be filtered by the query string and the keywords, key and display name of the items.
|
||||
*/
|
||||
groups?: SlashMenuGroup[]
|
||||
}
|
||||
/**
|
||||
* An opt-in, classic fixed toolbar which stays at the top of the editor
|
||||
*/
|
||||
toolbarFixed?: {
|
||||
groups: FixedToolbarGroup[]
|
||||
}
|
||||
/**
|
||||
* The default, floating toolbar which appears when you select text.
|
||||
*/
|
||||
toolbarInline?: {
|
||||
/**
|
||||
* Array of toolbar groups / sections. Each section can contain multiple toolbar items.
|
||||
*/
|
||||
groups: InlineToolbarGroup[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +327,9 @@ export type ResolvedClientFeatureMap = Map<string, ResolvedClientFeature<unknown
|
||||
export type ServerFeatureProviderMap = Map<string, FeatureProviderServer<unknown, unknown>>
|
||||
export type ClientFeatureProviderMap = Map<string, FeatureProviderClient<unknown>>
|
||||
|
||||
/**
|
||||
* Plugins are react component which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
|
||||
*/
|
||||
export type SanitizedPlugin =
|
||||
| {
|
||||
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
|
||||
@@ -381,13 +409,13 @@ export type SanitizedServerFeatures = Required<
|
||||
}
|
||||
|
||||
export type SanitizedClientFeatures = Required<
|
||||
Pick<ResolvedClientFeature<unknown>, 'markdownTransformers' | 'nodes'>
|
||||
Pick<
|
||||
ResolvedClientFeature<unknown>,
|
||||
'markdownTransformers' | 'nodes' | 'toolbarFixed' | 'toolbarInline'
|
||||
>
|
||||
> & {
|
||||
/** The keys of all enabled features */
|
||||
enabledFeatures: string[]
|
||||
floatingSelectToolbar: {
|
||||
sections: FloatingToolbarSection[]
|
||||
}
|
||||
hooks: {
|
||||
load: Array<
|
||||
({
|
||||
@@ -404,11 +432,24 @@ export type SanitizedClientFeatures = Required<
|
||||
}) => SerializedEditorState
|
||||
>
|
||||
}
|
||||
/**
|
||||
* Plugins are react component which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
|
||||
*/
|
||||
plugins?: Array<SanitizedPlugin>
|
||||
slashMenu: {
|
||||
dynamicOptions: Array<
|
||||
/**
|
||||
* Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash).
|
||||
* Thus, to re-calculate the available groups, this function will be called every time you type after the /.
|
||||
*
|
||||
* The groups provided by dynamicGroups will be merged with the static groups provided by the groups property.
|
||||
*/
|
||||
dynamicGroups: Array<
|
||||
({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => SlashMenuGroup[]
|
||||
>
|
||||
groupsWithOptions: SlashMenuGroup[]
|
||||
/**
|
||||
* Static array of groups together with the items in them. These will always be present.
|
||||
* While typing after the /, they will be filtered by the query string and the keywords, key and display name of the items.
|
||||
*/
|
||||
groups: SlashMenuGroup[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../types.js'
|
||||
|
||||
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
import { UploadIcon } from '../../lexical/ui/icons/Upload/index.js'
|
||||
import { createClientComponent } from '../createClientComponent.js'
|
||||
import { INSERT_UPLOAD_WITH_DRAWER_COMMAND } from './drawer/commands.js'
|
||||
@@ -30,22 +29,23 @@ const UploadFeatureClient: FeatureProviderProviderClient<UploadFeaturePropsClien
|
||||
},
|
||||
],
|
||||
slashMenu: {
|
||||
options: [
|
||||
groups: [
|
||||
{
|
||||
displayName: 'Basic',
|
||||
key: 'basic',
|
||||
options: [
|
||||
new SlashMenuOption('upload', {
|
||||
items: [
|
||||
{
|
||||
Icon: UploadIcon,
|
||||
displayName: 'Upload',
|
||||
key: 'upload',
|
||||
keywords: ['upload', 'image', 'file', 'img', 'picture', 'photo', 'media'],
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_UPLOAD_WITH_DRAWER_COMMAND, {
|
||||
replace: false,
|
||||
})
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
key: 'basic',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '../../scss/styles';
|
||||
|
||||
.rich-text-lexical {
|
||||
.editor {
|
||||
position: relative;
|
||||
@@ -23,7 +25,7 @@
|
||||
.editor-placeholder {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 0px;
|
||||
left: 0;
|
||||
font-size: 15px;
|
||||
color: var(--theme-elevation-500);
|
||||
/* Prevent text selection */
|
||||
@@ -36,21 +38,3 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.floating-select-toolbar-popup__section-dropdown-align {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.floating-select-toolbar-popup__section-features {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.floating-select-toolbar-popup__section-dropdown-text {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.floating-select-toolbar-popup__section-format {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ import type { LexicalProviderProps } from './LexicalProvider.js'
|
||||
|
||||
import { EditorPlugin } from './EditorPlugin.js'
|
||||
import './LexicalEditor.scss'
|
||||
import { FloatingSelectToolbarPlugin } from './plugins/FloatingSelectToolbar/index.js'
|
||||
import { MarkdownShortcutPlugin } from './plugins/MarkdownShortcut/index.js'
|
||||
import { SlashMenuPlugin } from './plugins/SlashMenu/index.js'
|
||||
import { AddBlockHandlePlugin } from './plugins/handles/AddBlockHandlePlugin/index.js'
|
||||
import { DraggableBlockPlugin } from './plugins/handles/DraggableBlockPlugin/index.js'
|
||||
import { FloatingSelectToolbarPlugin } from './plugins/toolbars/inline/Toolbar/index.js'
|
||||
import { LexicalContentEditable } from './ui/ContentEditable.js'
|
||||
|
||||
export const LexicalEditor: React.FC<Pick<LexicalProviderProps, 'editorConfig' | 'onChange'>> = (
|
||||
|
||||
@@ -10,9 +10,6 @@ export const sanitizeClientFeatures = (
|
||||
): SanitizedClientFeatures => {
|
||||
const sanitized: SanitizedClientFeatures = {
|
||||
enabledFeatures: [],
|
||||
floatingSelectToolbar: {
|
||||
sections: [],
|
||||
},
|
||||
hooks: {
|
||||
load: [],
|
||||
save: [],
|
||||
@@ -21,8 +18,14 @@ export const sanitizeClientFeatures = (
|
||||
nodes: [],
|
||||
plugins: [],
|
||||
slashMenu: {
|
||||
dynamicOptions: [],
|
||||
groupsWithOptions: [],
|
||||
dynamicGroups: [],
|
||||
groups: [],
|
||||
},
|
||||
toolbarFixed: {
|
||||
groups: [],
|
||||
},
|
||||
toolbarInline: {
|
||||
groups: [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -53,60 +56,82 @@ export const sanitizeClientFeatures = (
|
||||
})
|
||||
}
|
||||
|
||||
if (feature.floatingSelectToolbar?.sections?.length) {
|
||||
for (const section of feature.floatingSelectToolbar.sections) {
|
||||
// 1. find the section with the same key or create new one
|
||||
let foundSection = sanitized.floatingSelectToolbar.sections.find(
|
||||
(sanitizedSection) => sanitizedSection.key === section.key,
|
||||
if (feature.toolbarInline?.groups?.length) {
|
||||
for (const group of feature.toolbarInline.groups) {
|
||||
// 1. find the group with the same key or create new one
|
||||
let foundGroup = sanitized.toolbarInline.groups.find(
|
||||
(sanitizedGroup) => sanitizedGroup.key === group.key,
|
||||
)
|
||||
if (!foundSection) {
|
||||
foundSection = {
|
||||
...section,
|
||||
entries: [],
|
||||
if (!foundGroup) {
|
||||
foundGroup = {
|
||||
...group,
|
||||
items: [],
|
||||
}
|
||||
} else {
|
||||
sanitized.floatingSelectToolbar.sections =
|
||||
sanitized.floatingSelectToolbar.sections.filter(
|
||||
(sanitizedSection) => sanitizedSection.key !== section.key,
|
||||
)
|
||||
sanitized.toolbarInline.groups = sanitized.toolbarInline.groups.filter(
|
||||
(sanitizedGroup) => sanitizedGroup.key !== group.key,
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Add options to group options array and add to sanitized.slashMenu.groupsWithOptions
|
||||
if (section?.entries?.length) {
|
||||
foundSection.entries = foundSection.entries.concat(section.entries)
|
||||
if (group?.items?.length) {
|
||||
foundGroup.items = foundGroup.items.concat(group.items)
|
||||
}
|
||||
sanitized.floatingSelectToolbar?.sections.push(foundSection)
|
||||
sanitized.toolbarInline?.groups.push(foundGroup)
|
||||
}
|
||||
}
|
||||
|
||||
if (feature.slashMenu?.options) {
|
||||
if (feature.slashMenu.dynamicOptions?.length) {
|
||||
sanitized.slashMenu.dynamicOptions = sanitized.slashMenu.dynamicOptions.concat(
|
||||
feature.slashMenu.dynamicOptions,
|
||||
if (feature.toolbarFixed?.groups?.length) {
|
||||
for (const group of feature.toolbarFixed.groups) {
|
||||
// 1. find the group with the same key or create new one
|
||||
let foundGroup = sanitized.toolbarFixed.groups.find(
|
||||
(sanitizedGroup) => sanitizedGroup.key === group.key,
|
||||
)
|
||||
if (!foundGroup) {
|
||||
foundGroup = {
|
||||
...group,
|
||||
items: [],
|
||||
}
|
||||
} else {
|
||||
sanitized.toolbarFixed.groups = sanitized.toolbarFixed.groups.filter(
|
||||
(sanitizedGroup) => sanitizedGroup.key !== group.key,
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Add options to group options array and add to sanitized.slashMenu.groupsWithOptions
|
||||
if (group?.items?.length) {
|
||||
foundGroup.items = foundGroup.items.concat(group.items)
|
||||
}
|
||||
sanitized.toolbarFixed?.groups.push(foundGroup)
|
||||
}
|
||||
}
|
||||
|
||||
if (feature.slashMenu?.groups) {
|
||||
if (feature.slashMenu.dynamicGroups?.length) {
|
||||
sanitized.slashMenu.dynamicGroups = sanitized.slashMenu.dynamicGroups.concat(
|
||||
feature.slashMenu.dynamicGroups,
|
||||
)
|
||||
}
|
||||
|
||||
for (const optionGroup of feature.slashMenu.options) {
|
||||
for (const optionGroup of feature.slashMenu.groups) {
|
||||
// 1. find the group with the same name or create new one
|
||||
let group = sanitized.slashMenu.groupsWithOptions.find(
|
||||
(group) => group.key === optionGroup.key,
|
||||
)
|
||||
let group = sanitized.slashMenu.groups.find((group) => group.key === optionGroup.key)
|
||||
if (!group) {
|
||||
group = {
|
||||
...optionGroup,
|
||||
options: [],
|
||||
items: [],
|
||||
}
|
||||
} else {
|
||||
sanitized.slashMenu.groupsWithOptions = sanitized.slashMenu.groupsWithOptions.filter(
|
||||
sanitized.slashMenu.groups = sanitized.slashMenu.groups.filter(
|
||||
(group) => group.key !== optionGroup.key,
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Add options to group options array and add to sanitized.slashMenu.groupsWithOptions
|
||||
if (optionGroup?.options?.length) {
|
||||
group.options = group.options.concat(optionGroup.options)
|
||||
if (optionGroup?.items?.length) {
|
||||
group.items = group.items.concat(optionGroup.items)
|
||||
}
|
||||
sanitized.slashMenu.groupsWithOptions.push(group)
|
||||
sanitized.slashMenu.groups.push(group)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +143,20 @@ export const sanitizeClientFeatures = (
|
||||
sanitized.enabledFeatures.push(feature.key)
|
||||
})
|
||||
|
||||
// Sort sanitized.floatingSelectToolbar.sections by order property
|
||||
sanitized.floatingSelectToolbar.sections.sort((a, b) => {
|
||||
// Sort sanitized.toolbarInline.groups by order property
|
||||
sanitized.toolbarInline.groups.sort((a, b) => {
|
||||
if (a.order && b.order) {
|
||||
return a.order - b.order
|
||||
} else if (a.order) {
|
||||
return -1
|
||||
} else if (b.order) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
// Sort sanitized.toolbarFixed.groups by order property
|
||||
sanitized.toolbarFixed.groups.sort((a, b) => {
|
||||
if (a.order && b.order) {
|
||||
return a.order - b.order
|
||||
} else if (a.order) {
|
||||
@@ -131,9 +168,24 @@ export const sanitizeClientFeatures = (
|
||||
}
|
||||
})
|
||||
|
||||
// Sort sanitized.floatingSelectToolbar.sections.[section].entries by order property
|
||||
for (const section of sanitized.floatingSelectToolbar.sections) {
|
||||
section.entries.sort((a, b) => {
|
||||
// Sort sanitized.toolbarInline.groups.[group].entries by order property
|
||||
for (const group of sanitized.toolbarInline.groups) {
|
||||
group.items.sort((a, b) => {
|
||||
if (a.order && b.order) {
|
||||
return a.order - b.order
|
||||
} else if (a.order) {
|
||||
return -1
|
||||
} else if (b.order) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Sort sanitized.toolbarFixed.groups.[group].entries by order property
|
||||
for (const group of sanitized.toolbarFixed.groups) {
|
||||
group.items.sort((a, b) => {
|
||||
if (a.order && b.order) {
|
||||
return a.order - b.order
|
||||
} else if (a.order) {
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import type { EditorConfig as LexicalEditorConfig } from 'lexical'
|
||||
|
||||
import type { FeatureProviderServer } from '../../../features/types.js'
|
||||
import type { SanitizedServerEditorConfig, ServerEditorConfig } from '../types.js'
|
||||
import type { ServerEditorConfig } from '../types.js'
|
||||
|
||||
import { AlignFeature } from '../../../features/align/feature.server.js'
|
||||
import { BlockQuoteFeature } from '../../../features/blockquote/feature.server.js'
|
||||
import { BoldFeature } from '../../../features/format/bold/feature.server.js'
|
||||
import { InlineCodeFeature } from '../../../features/format/inlinecode/feature.server.js'
|
||||
import { InlineCodeFeature } from '../../../features/format/inlineCode/feature.server.js'
|
||||
import { ItalicFeature } from '../../../features/format/italic/feature.server.js'
|
||||
import { StrikethroughFeature } from '../../../features/format/strikethrough/feature.server.js'
|
||||
import { SubscriptFeature } from '../../../features/format/subscript/feature.server.js'
|
||||
import { SuperscriptFeature } from '../../../features/format/superscript/feature.server.js'
|
||||
import { UnderlineFeature } from '../../../features/format/underline/feature.server.js'
|
||||
import { HeadingFeature } from '../../../features/heading/feature.server.js'
|
||||
import { HorizontalRuleFeature } from '../../../features/horizontalrule/feature.server.js'
|
||||
import { HorizontalRuleFeature } from '../../../features/horizontalRule/feature.server.js'
|
||||
import { IndentFeature } from '../../../features/indent/feature.server.js'
|
||||
import { LinkFeature } from '../../../features/link/feature.server.js'
|
||||
import { CheckListFeature } from '../../../features/lists/checklist/feature.server.js'
|
||||
import { OrderedListFeature } from '../../../features/lists/orderedlist/feature.server.js'
|
||||
import { UnorderedListFeature } from '../../../features/lists/unorderedlist/feature.server.js'
|
||||
import { ChecklistFeature } from '../../../features/lists/checklist/feature.server.js'
|
||||
import { OrderedListFeature } from '../../../features/lists/orderedList/feature.server.js'
|
||||
import { UnorderedListFeature } from '../../../features/lists/unorderedList/feature.server.js'
|
||||
import { ParagraphFeature } from '../../../features/paragraph/feature.server.js'
|
||||
import { RelationshipFeature } from '../../../features/relationship/feature.server.js'
|
||||
import { UploadFeature } from '../../../features/upload/feature.server.js'
|
||||
@@ -43,7 +43,7 @@ export const defaultEditorFeatures: FeatureProviderServer<unknown, unknown>[] =
|
||||
IndentFeature(),
|
||||
UnorderedListFeature(),
|
||||
OrderedListFeature(),
|
||||
CheckListFeature(),
|
||||
ChecklistFeature(),
|
||||
LinkFeature(),
|
||||
RelationshipFeature(),
|
||||
BlockQuoteFeature(),
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
const baseClass = 'floating-select-toolbar-popup__dropdown'
|
||||
|
||||
import type { LexicalEditor } from 'lexical'
|
||||
|
||||
import type { FloatingToolbarSectionEntry } from '../types.js'
|
||||
|
||||
import { DropDown, DropDownItem } from './DropDown.js'
|
||||
import './index.scss'
|
||||
|
||||
export const ToolbarEntry = ({
|
||||
anchorElem,
|
||||
editor,
|
||||
entry,
|
||||
}: {
|
||||
anchorElem: HTMLElement
|
||||
editor: LexicalEditor
|
||||
entry: FloatingToolbarSectionEntry
|
||||
}) => {
|
||||
if (entry.Component) {
|
||||
return (
|
||||
entry?.Component && (
|
||||
<entry.Component anchorElem={anchorElem} editor={editor} entry={entry} key={entry.key} />
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DropDownItem entry={entry} key={entry.key}>
|
||||
{entry?.ChildComponent && <entry.ChildComponent />}
|
||||
<span className="text">{entry.label}</span>
|
||||
</DropDownItem>
|
||||
)
|
||||
}
|
||||
|
||||
export const ToolbarDropdown = ({
|
||||
Icon,
|
||||
anchorElem,
|
||||
classNames,
|
||||
editor,
|
||||
entries,
|
||||
sectionKey,
|
||||
}: {
|
||||
Icon?: React.FC
|
||||
anchorElem: HTMLElement
|
||||
classNames?: string[]
|
||||
editor: LexicalEditor
|
||||
entries: FloatingToolbarSectionEntry[]
|
||||
sectionKey: string
|
||||
}) => {
|
||||
return (
|
||||
<DropDown
|
||||
Icon={Icon}
|
||||
buttonAriaLabel={`${sectionKey} dropdown`}
|
||||
buttonClassName={[baseClass, `${baseClass}-${sectionKey}`, ...(classNames || [])]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
key={sectionKey}
|
||||
>
|
||||
{entries.length &&
|
||||
entries.map((entry) => {
|
||||
return (
|
||||
<ToolbarEntry anchorElem={anchorElem} editor={editor} entry={entry} key={entry.key} />
|
||||
)
|
||||
})}
|
||||
</DropDown>
|
||||
)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import type { MenuTextMatch } from '../useMenuTriggerMatch.js'
|
||||
import type { SlashMenuGroup, SlashMenuOption } from './types.js'
|
||||
import type { SlashMenuGroupInternal, SlashMenuItem, SlashMenuItemInternal } from './types.js'
|
||||
|
||||
export type MenuResolution = {
|
||||
getRect: () => DOMRect
|
||||
@@ -30,10 +30,10 @@ const baseClass = 'slash-menu-popup'
|
||||
export type MenuRenderFn = (
|
||||
anchorElementRef: MutableRefObject<HTMLElement | null>,
|
||||
itemProps: {
|
||||
groupsWithOptions: Array<SlashMenuGroup>
|
||||
selectOptionAndCleanUp: (selectedOption: SlashMenuOption) => void
|
||||
selectedOptionKey: null | string
|
||||
setSelectedOptionKey: (optionKey: string) => void
|
||||
groups: Array<SlashMenuGroupInternal>
|
||||
selectItemAndCleanUp: (selectedItem: SlashMenuItem) => void
|
||||
selectedItemKey: null | string
|
||||
setSelectedItemKey: (itemKey: string) => void
|
||||
},
|
||||
matchingString: null | string,
|
||||
) => JSX.Element | ReactPortal | null
|
||||
@@ -63,10 +63,10 @@ const scrollIntoViewIfNeeded = (target: HTMLElement) => {
|
||||
* Walk backwards along user input and forward through entity title to try
|
||||
* and replace more of the user's text with entity.
|
||||
*/
|
||||
function getFullMatchOffset(documentText: string, entryText: string, offset: number): number {
|
||||
function getFullMatchOffset(documentText: string, entryText: string, offset: number) {
|
||||
let triggerOffset = offset
|
||||
for (let i = triggerOffset; i <= entryText.length; i++) {
|
||||
if (documentText.substr(-i) === entryText.substr(0, i)) {
|
||||
if (documentText.substring(documentText.length - i) === entryText.substring(0, i)) {
|
||||
triggerOffset = i
|
||||
}
|
||||
}
|
||||
@@ -194,27 +194,27 @@ export function useDynamicPositioning(
|
||||
|
||||
export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
|
||||
index: number
|
||||
option: SlashMenuOption
|
||||
item: SlashMenuItemInternal
|
||||
}> = createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND')
|
||||
|
||||
export function LexicalMenu({
|
||||
anchorElementRef,
|
||||
close,
|
||||
editor,
|
||||
// groupsWithOptions filtering is already handled in SlashMenu/index.tsx. Thus, groupsWithOptions always contains the matching options.
|
||||
groupsWithOptions,
|
||||
// groups filtering is already handled in SlashMenu/index.tsx. Thus, groups always contains the matching items.
|
||||
groups,
|
||||
menuRenderFn,
|
||||
onSelectOption,
|
||||
onSelectItem,
|
||||
resolution,
|
||||
shouldSplitNodeWithQuery = false,
|
||||
}: {
|
||||
anchorElementRef: MutableRefObject<HTMLElement>
|
||||
close: () => void
|
||||
editor: LexicalEditor
|
||||
groupsWithOptions: Array<SlashMenuGroup>
|
||||
groups: Array<SlashMenuGroupInternal>
|
||||
menuRenderFn: MenuRenderFn
|
||||
onSelectOption: (
|
||||
option: SlashMenuOption,
|
||||
onSelectItem: (
|
||||
item: SlashMenuItem,
|
||||
textNodeContainingQuery: TextNode | null,
|
||||
closeMenu: () => void,
|
||||
matchingString: string,
|
||||
@@ -222,55 +222,55 @@ export function LexicalMenu({
|
||||
resolution: MenuResolution
|
||||
shouldSplitNodeWithQuery?: boolean
|
||||
}): JSX.Element | null {
|
||||
const [selectedOptionKey, setSelectedOptionKey] = useState<null | string>(null)
|
||||
const [selectedItemKey, setSelectedItemKey] = useState<null | string>(null)
|
||||
|
||||
const matchingString = (resolution.match && resolution.match.matchingString) || ''
|
||||
|
||||
const updateSelectedOption = useCallback(
|
||||
(option: SlashMenuOption) => {
|
||||
const updateSelectedItem = useCallback(
|
||||
(item: SlashMenuItem) => {
|
||||
const rootElem = editor.getRootElement()
|
||||
if (rootElem !== null) {
|
||||
rootElem.setAttribute('aria-activedescendant', `${baseClass}__item-${option.key}`)
|
||||
setSelectedOptionKey(option.key)
|
||||
rootElem.setAttribute('aria-activedescendant', `${baseClass}__item-${item.key}`)
|
||||
setSelectedItemKey(item.key)
|
||||
}
|
||||
},
|
||||
[editor],
|
||||
)
|
||||
|
||||
const setSelectedOptionKeyToFirstMatchingOption = useCallback(() => {
|
||||
// set selected option to the first of the matching ones
|
||||
if (groupsWithOptions !== null && matchingString != null) {
|
||||
// groupsWithOptions filtering is already handled in SlashMenu/index.tsx. Thus, groupsWithOptions always contains the matching options.
|
||||
const allOptions = groupsWithOptions.flatMap((group) => group.options)
|
||||
const setSelectedItemKeyToFirstMatchingItem = useCallback(() => {
|
||||
// set selected item to the first of the matching ones
|
||||
if (groups !== null && matchingString != null) {
|
||||
// groups filtering is already handled in SlashMenu/index.tsx. Thus, groups always contains the matching items.
|
||||
const allItems = groups.flatMap((group) => group.items)
|
||||
|
||||
if (allOptions.length) {
|
||||
const firstMatchingOption = allOptions[0]
|
||||
updateSelectedOption(firstMatchingOption)
|
||||
if (allItems.length) {
|
||||
const firstMatchingItem = allItems[0]
|
||||
updateSelectedItem(firstMatchingItem)
|
||||
}
|
||||
}
|
||||
}, [groupsWithOptions, updateSelectedOption, matchingString])
|
||||
}, [groups, updateSelectedItem, matchingString])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedOptionKeyToFirstMatchingOption()
|
||||
}, [matchingString, setSelectedOptionKeyToFirstMatchingOption])
|
||||
setSelectedItemKeyToFirstMatchingItem()
|
||||
}, [matchingString, setSelectedItemKeyToFirstMatchingItem])
|
||||
|
||||
const selectOptionAndCleanUp = useCallback(
|
||||
(selectedOption: SlashMenuOption) => {
|
||||
const selectItemAndCleanUp = useCallback(
|
||||
(selectedItem: SlashMenuItem) => {
|
||||
editor.update(() => {
|
||||
const textNodeContainingQuery =
|
||||
resolution.match != null && shouldSplitNodeWithQuery
|
||||
? $splitNodeContainingQuery(resolution.match)
|
||||
: null
|
||||
|
||||
onSelectOption(
|
||||
selectedOption,
|
||||
onSelectItem(
|
||||
selectedItem,
|
||||
textNodeContainingQuery,
|
||||
close,
|
||||
resolution.match ? resolution.match.matchingString : '',
|
||||
)
|
||||
})
|
||||
},
|
||||
[editor, shouldSplitNodeWithQuery, resolution.match, onSelectOption, close],
|
||||
[editor, shouldSplitNodeWithQuery, resolution.match, onSelectItem, close],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -283,25 +283,20 @@ export function LexicalMenu({
|
||||
}, [editor])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (groupsWithOptions === null) {
|
||||
setSelectedOptionKey(null)
|
||||
} else if (selectedOptionKey === null) {
|
||||
setSelectedOptionKeyToFirstMatchingOption()
|
||||
if (groups === null) {
|
||||
setSelectedItemKey(null)
|
||||
} else if (selectedItemKey === null) {
|
||||
setSelectedItemKeyToFirstMatchingItem()
|
||||
}
|
||||
}, [
|
||||
groupsWithOptions,
|
||||
selectedOptionKey,
|
||||
updateSelectedOption,
|
||||
setSelectedOptionKeyToFirstMatchingOption,
|
||||
])
|
||||
}, [groups, selectedItemKey, updateSelectedItem, setSelectedItemKeyToFirstMatchingItem])
|
||||
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND,
|
||||
({ option }) => {
|
||||
if (option.ref && option.ref.current != null) {
|
||||
scrollIntoViewIfNeeded(option.ref.current)
|
||||
({ item }) => {
|
||||
if (item.ref && item.ref.current != null) {
|
||||
scrollIntoViewIfNeeded(item.ref.current)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -310,7 +305,7 @@ export function LexicalMenu({
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
)
|
||||
}, [editor, updateSelectedOption])
|
||||
}, [editor, updateSelectedItem])
|
||||
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
@@ -318,23 +313,19 @@ export function LexicalMenu({
|
||||
KEY_ARROW_DOWN_COMMAND,
|
||||
(payload) => {
|
||||
const event = payload
|
||||
if (
|
||||
groupsWithOptions !== null &&
|
||||
groupsWithOptions.length &&
|
||||
selectedOptionKey !== null
|
||||
) {
|
||||
const allOptions = groupsWithOptions.flatMap((group) => group.options)
|
||||
const selectedIndex = allOptions.findIndex((option) => option.key === selectedOptionKey)
|
||||
if (groups !== null && groups.length && selectedItemKey !== null) {
|
||||
const allItems = groups.flatMap((group) => group.items)
|
||||
const selectedIndex = allItems.findIndex((item) => item.key === selectedItemKey)
|
||||
|
||||
const newSelectedIndex = selectedIndex !== allOptions.length - 1 ? selectedIndex + 1 : 0
|
||||
const newSelectedIndex = selectedIndex !== allItems.length - 1 ? selectedIndex + 1 : 0
|
||||
|
||||
const newSelectedOption = allOptions[newSelectedIndex]
|
||||
const newSelectedItem = allItems[newSelectedIndex]
|
||||
|
||||
updateSelectedOption(newSelectedOption)
|
||||
if (newSelectedOption.ref != null && newSelectedOption.ref.current) {
|
||||
updateSelectedItem(newSelectedItem)
|
||||
if (newSelectedItem.ref != null && newSelectedItem.ref.current) {
|
||||
editor.dispatchCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, {
|
||||
index: newSelectedIndex,
|
||||
option: newSelectedOption,
|
||||
item: newSelectedItem,
|
||||
})
|
||||
}
|
||||
event.preventDefault()
|
||||
@@ -348,21 +339,17 @@ export function LexicalMenu({
|
||||
KEY_ARROW_UP_COMMAND,
|
||||
(payload) => {
|
||||
const event = payload
|
||||
if (
|
||||
groupsWithOptions !== null &&
|
||||
groupsWithOptions.length &&
|
||||
selectedOptionKey !== null
|
||||
) {
|
||||
const allOptions = groupsWithOptions.flatMap((group) => group.options)
|
||||
const selectedIndex = allOptions.findIndex((option) => option.key === selectedOptionKey)
|
||||
if (groups !== null && groups.length && selectedItemKey !== null) {
|
||||
const allItems = groups.flatMap((group) => group.items)
|
||||
const selectedIndex = allItems.findIndex((item) => item.key === selectedItemKey)
|
||||
|
||||
const newSelectedIndex = selectedIndex !== 0 ? selectedIndex - 1 : allOptions.length - 1
|
||||
const newSelectedIndex = selectedIndex !== 0 ? selectedIndex - 1 : allItems.length - 1
|
||||
|
||||
const newSelectedOption = allOptions[newSelectedIndex]
|
||||
const newSelectedItem = allItems[newSelectedIndex]
|
||||
|
||||
updateSelectedOption(newSelectedOption)
|
||||
if (newSelectedOption.ref != null && newSelectedOption.ref.current) {
|
||||
scrollIntoViewIfNeeded(newSelectedOption.ref.current)
|
||||
updateSelectedItem(newSelectedItem)
|
||||
if (newSelectedItem.ref != null && newSelectedItem.ref.current) {
|
||||
scrollIntoViewIfNeeded(newSelectedItem.ref.current)
|
||||
}
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
@@ -387,18 +374,18 @@ export function LexicalMenu({
|
||||
(payload) => {
|
||||
const event = payload
|
||||
|
||||
if (groupsWithOptions === null || selectedOptionKey === null) {
|
||||
if (groups === null || selectedItemKey === null) {
|
||||
return false
|
||||
}
|
||||
const allOptions = groupsWithOptions.flatMap((group) => group.options)
|
||||
const selectedOption = allOptions.find((option) => option.key === selectedOptionKey)
|
||||
if (!selectedOption) {
|
||||
const allItems = groups.flatMap((group) => group.items)
|
||||
const selectedItem = allItems.find((item) => item.key === selectedItemKey)
|
||||
if (!selectedItem) {
|
||||
return false
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
selectOptionAndCleanUp(selectedOption)
|
||||
selectItemAndCleanUp(selectedItem)
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_LOW,
|
||||
@@ -406,12 +393,12 @@ export function LexicalMenu({
|
||||
editor.registerCommand(
|
||||
KEY_ENTER_COMMAND,
|
||||
(event: KeyboardEvent | null) => {
|
||||
if (groupsWithOptions === null || selectedOptionKey === null) {
|
||||
if (groups === null || selectedItemKey === null) {
|
||||
return false
|
||||
}
|
||||
const allOptions = groupsWithOptions.flatMap((group) => group.options)
|
||||
const selectedOption = allOptions.find((option) => option.key === selectedOptionKey)
|
||||
if (!selectedOption) {
|
||||
const allItems = groups.flatMap((group) => group.items)
|
||||
const selectedItem = allItems.find((item) => item.key === selectedItemKey)
|
||||
if (!selectedItem) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -419,29 +406,22 @@ export function LexicalMenu({
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
}
|
||||
selectOptionAndCleanUp(selectedOption)
|
||||
selectItemAndCleanUp(selectedItem)
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
)
|
||||
}, [
|
||||
selectOptionAndCleanUp,
|
||||
close,
|
||||
editor,
|
||||
groupsWithOptions,
|
||||
selectedOptionKey,
|
||||
updateSelectedOption,
|
||||
])
|
||||
}, [selectItemAndCleanUp, close, editor, groups, selectedItemKey, updateSelectedItem])
|
||||
|
||||
const listItemProps = useMemo(
|
||||
() => ({
|
||||
groupsWithOptions,
|
||||
selectOptionAndCleanUp,
|
||||
selectedOptionKey,
|
||||
setSelectedOptionKey,
|
||||
groups,
|
||||
selectItemAndCleanUp,
|
||||
selectedItemKey,
|
||||
setSelectedItemKey,
|
||||
}),
|
||||
[selectOptionAndCleanUp, selectedOptionKey, groupsWithOptions],
|
||||
[selectItemAndCleanUp, selectedItemKey, groups],
|
||||
)
|
||||
|
||||
return menuRenderFn(
|
||||
|
||||
@@ -21,7 +21,7 @@ import * as React from 'react'
|
||||
|
||||
import type { MenuTextMatch, TriggerFn } from '../useMenuTriggerMatch.js'
|
||||
import type { MenuRenderFn, MenuResolution } from './LexicalMenu.js'
|
||||
import type { SlashMenuGroup, SlashMenuOption } from './types.js'
|
||||
import type { SlashMenuGroup, SlashMenuGroupInternal, SlashMenuItem } from './types.js'
|
||||
|
||||
import { LexicalMenu, useMenuAnchorRef } from './LexicalMenu.js'
|
||||
|
||||
@@ -128,13 +128,13 @@ export { useDynamicPositioning } from './LexicalMenu.js'
|
||||
export type TypeaheadMenuPluginProps = {
|
||||
anchorClassName?: string
|
||||
anchorElem: HTMLElement
|
||||
groupsWithOptions: Array<SlashMenuGroup>
|
||||
groups: Array<SlashMenuGroupInternal>
|
||||
menuRenderFn: MenuRenderFn
|
||||
onClose?: () => void
|
||||
onOpen?: (resolution: MenuResolution) => void
|
||||
onQueryChange: (matchingString: null | string) => void
|
||||
onSelectOption: (
|
||||
option: SlashMenuOption,
|
||||
onSelectItem: (
|
||||
item: SlashMenuItem,
|
||||
textNodeContainingQuery: TextNode | null,
|
||||
closeMenu: () => void,
|
||||
matchingString: string,
|
||||
@@ -149,12 +149,12 @@ export const ENABLE_SLASH_MENU_COMMAND: LexicalCommand<{
|
||||
export function LexicalTypeaheadMenuPlugin({
|
||||
anchorClassName,
|
||||
anchorElem,
|
||||
groupsWithOptions,
|
||||
groups,
|
||||
menuRenderFn,
|
||||
onClose,
|
||||
onOpen,
|
||||
onQueryChange,
|
||||
onSelectOption,
|
||||
onSelectItem,
|
||||
triggerFn,
|
||||
}: TypeaheadMenuPluginProps): JSX.Element | null {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
@@ -270,9 +270,9 @@ export function LexicalTypeaheadMenuPlugin({
|
||||
anchorElementRef={anchorElementRef}
|
||||
close={closeTypeahead}
|
||||
editor={editor}
|
||||
groupsWithOptions={groupsWithOptions}
|
||||
groups={groups}
|
||||
menuRenderFn={menuRenderFn}
|
||||
onSelectOption={onSelectOption}
|
||||
onSelectItem={onSelectItem}
|
||||
resolution={resolution}
|
||||
shouldSplitNodeWithQuery
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { LexicalEditor } from 'lexical'
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type React from 'react'
|
||||
|
||||
export class SlashMenuOption {
|
||||
export type SlashMenuItem = {
|
||||
// Icon for display
|
||||
Icon: React.FC
|
||||
|
||||
@@ -13,41 +13,22 @@ export class SlashMenuOption {
|
||||
// TBD
|
||||
keyboardShortcut?: string
|
||||
// For extra searching.
|
||||
keywords: Array<string>
|
||||
// What happens when you select this option?
|
||||
keywords?: Array<string>
|
||||
// What happens when you select this item?
|
||||
onSelect: ({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => void
|
||||
|
||||
ref?: MutableRefObject<HTMLElement | null>
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
options: {
|
||||
Icon: React.FC
|
||||
displayName?: (({ i18n }: { i18n: I18n }) => string) | string
|
||||
keyboardShortcut?: string
|
||||
keywords?: Array<string>
|
||||
onSelect: ({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => void
|
||||
},
|
||||
) {
|
||||
this.key = key
|
||||
this.ref = { current: null }
|
||||
this.setRefElement = this.setRefElement.bind(this)
|
||||
|
||||
this.displayName = options.displayName
|
||||
this.keywords = options.keywords || []
|
||||
this.Icon = options.Icon
|
||||
this.keyboardShortcut = options.keyboardShortcut
|
||||
this.onSelect = options.onSelect.bind(this)
|
||||
}
|
||||
|
||||
setRefElement(element: HTMLElement | null) {
|
||||
this.ref = { current: element }
|
||||
}
|
||||
}
|
||||
|
||||
export class SlashMenuGroup {
|
||||
export type SlashMenuGroup = {
|
||||
// Used for class names and, if displayName is not provided, for display.
|
||||
displayName?: (({ i18n }: { i18n: I18n }) => string) | string
|
||||
items: Array<SlashMenuItem>
|
||||
key: string
|
||||
options: Array<SlashMenuOption>
|
||||
}
|
||||
|
||||
export type SlashMenuItemInternal = SlashMenuItem & {
|
||||
ref: MutableRefObject<HTMLButtonElement | null>
|
||||
}
|
||||
|
||||
export type SlashMenuGroupInternal = SlashMenuGroup & {
|
||||
items: Array<SlashMenuItemInternal>
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@ import { useCallback, useMemo, useState } from 'react'
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
|
||||
import type { SlashMenuGroup, SlashMenuOption } from './LexicalTypeaheadMenuPlugin/types.js'
|
||||
import type {
|
||||
SlashMenuGroup,
|
||||
SlashMenuGroupInternal,
|
||||
SlashMenuItemInternal,
|
||||
SlashMenuItem as SlashMenuItemType,
|
||||
} from './LexicalTypeaheadMenuPlugin/types.js'
|
||||
|
||||
import { useEditorConfigContext } from '../../config/client/EditorConfigProvider.js'
|
||||
import { LexicalTypeaheadMenuPlugin } from './LexicalTypeaheadMenuPlugin/index.js'
|
||||
@@ -18,27 +23,26 @@ const baseClass = 'slash-menu-popup'
|
||||
|
||||
function SlashMenuItem({
|
||||
isSelected,
|
||||
item,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
option,
|
||||
}: {
|
||||
index: number
|
||||
isSelected: boolean
|
||||
item: SlashMenuItemInternal
|
||||
onClick: () => void
|
||||
onMouseEnter: () => void
|
||||
option: SlashMenuOption
|
||||
}) {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
let className = `${baseClass}__item ${baseClass}__item-${option.key}`
|
||||
let className = `${baseClass}__item ${baseClass}__item-${item.key}`
|
||||
if (isSelected) {
|
||||
className += ` ${baseClass}__item--selected`
|
||||
}
|
||||
|
||||
let title = option.key
|
||||
if (option.displayName) {
|
||||
title =
|
||||
typeof option.displayName === 'function' ? option.displayName({ i18n }) : option.displayName
|
||||
let title = item.key
|
||||
if (item.displayName) {
|
||||
title = typeof item.displayName === 'function' ? item.displayName({ i18n }) : item.displayName
|
||||
}
|
||||
// Crop title to max. 50 characters
|
||||
if (title.length > 25) {
|
||||
@@ -49,16 +53,16 @@ function SlashMenuItem({
|
||||
<button
|
||||
aria-selected={isSelected}
|
||||
className={className}
|
||||
id={baseClass + '__item-' + option.key}
|
||||
key={option.key}
|
||||
id={baseClass + '__item-' + item.key}
|
||||
key={item.key}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
ref={option.setRefElement}
|
||||
role="option"
|
||||
ref={item.ref}
|
||||
role="item"
|
||||
tabIndex={-1}
|
||||
type="button"
|
||||
>
|
||||
{option?.Icon && <option.Icon />}
|
||||
{item?.Icon && <item.Icon />}
|
||||
|
||||
<span className={`${baseClass}__item-text`}>{title}</span>
|
||||
</button>
|
||||
@@ -69,7 +73,7 @@ export function SlashMenuPlugin({
|
||||
anchorElem = document.body,
|
||||
}: {
|
||||
anchorElem?: HTMLElement
|
||||
}): JSX.Element {
|
||||
}): React.ReactElement {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const [queryString, setQueryString] = useState<null | string>(null)
|
||||
const { editorConfig } = useEditorConfigContext()
|
||||
@@ -79,94 +83,97 @@ export function SlashMenuPlugin({
|
||||
minLength: 0,
|
||||
})
|
||||
|
||||
const getDynamicOptions = useCallback(() => {
|
||||
let groupWithOptions: Array<SlashMenuGroup> = []
|
||||
const getDynamicItems = useCallback(() => {
|
||||
let groupWithItems: Array<SlashMenuGroup> = []
|
||||
|
||||
for (const dynamicOption of editorConfig.features.slashMenu.dynamicOptions) {
|
||||
const dynamicGroupWithOptions = dynamicOption({
|
||||
for (const dynamicItem of editorConfig.features.slashMenu.dynamicGroups) {
|
||||
const dynamicGroupWithItems = dynamicItem({
|
||||
editor,
|
||||
queryString,
|
||||
})
|
||||
groupWithOptions = groupWithOptions.concat(dynamicGroupWithOptions)
|
||||
groupWithItems = groupWithItems.concat(dynamicGroupWithItems)
|
||||
}
|
||||
|
||||
return groupWithOptions
|
||||
return groupWithItems
|
||||
}, [editor, queryString, editorConfig?.features])
|
||||
|
||||
const groups: SlashMenuGroup[] = useMemo(() => {
|
||||
let groupsWithOptions: SlashMenuGroup[] = []
|
||||
for (const groupWithOption of editorConfig?.features.slashMenu.groupsWithOptions ?? []) {
|
||||
groupsWithOptions.push(groupWithOption)
|
||||
let groupsWithItems: SlashMenuGroup[] = []
|
||||
for (const groupWithItem of editorConfig?.features.slashMenu.groups ?? []) {
|
||||
groupsWithItems.push(groupWithItem)
|
||||
}
|
||||
|
||||
if (queryString) {
|
||||
// Filter current groups first
|
||||
groupsWithOptions = groupsWithOptions.map((group) => {
|
||||
const filteredOptions = group.options.filter((option) => {
|
||||
let optionTitle = option.key
|
||||
if (option.displayName) {
|
||||
optionTitle =
|
||||
typeof option.displayName === 'function'
|
||||
? option.displayName({ i18n })
|
||||
: option.displayName
|
||||
groupsWithItems = groupsWithItems.map((group) => {
|
||||
const filteredItems = group.items.filter((item) => {
|
||||
let itemTitle = item.key
|
||||
if (item.displayName) {
|
||||
itemTitle =
|
||||
typeof item.displayName === 'function' ? item.displayName({ i18n }) : item.displayName
|
||||
}
|
||||
|
||||
return new RegExp(queryString, 'gi').exec(optionTitle) || option.keywords != null
|
||||
? option.keywords.some((keyword) => new RegExp(queryString, 'gi').exec(keyword))
|
||||
: false
|
||||
if (new RegExp(queryString, 'gi').exec(itemTitle)) {
|
||||
return true
|
||||
}
|
||||
if (item.keywords != null) {
|
||||
return item.keywords.some((keyword) => new RegExp(queryString, 'gi').exec(keyword))
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (filteredOptions.length) {
|
||||
if (filteredItems.length) {
|
||||
return {
|
||||
...group,
|
||||
options: filteredOptions,
|
||||
items: filteredItems,
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
groupsWithOptions = groupsWithOptions.filter((group) => group != null)
|
||||
groupsWithItems = groupsWithItems.filter((group) => group != null)
|
||||
|
||||
// Now add dynamic groups
|
||||
const dynamicOptionGroups = getDynamicOptions()
|
||||
const dynamicItemGroups = getDynamicItems()
|
||||
|
||||
// merge dynamic options into groups
|
||||
for (const dynamicGroup of dynamicOptionGroups) {
|
||||
// merge dynamic items into groups
|
||||
for (const dynamicGroup of dynamicItemGroups) {
|
||||
// 1. find the group with the same name or create new one
|
||||
let group = groupsWithOptions.find((group) => group.key === dynamicGroup.key)
|
||||
let group = groupsWithItems.find((group) => group.key === dynamicGroup.key)
|
||||
if (!group) {
|
||||
group = {
|
||||
...dynamicGroup,
|
||||
options: [],
|
||||
items: [],
|
||||
}
|
||||
} else {
|
||||
groupsWithOptions = groupsWithOptions.filter((group) => group.key !== dynamicGroup.key)
|
||||
groupsWithItems = groupsWithItems.filter((group) => group.key !== dynamicGroup.key)
|
||||
}
|
||||
|
||||
// 2. Add options to group options array and add to sanitized.slashMenu.groupsWithOptions
|
||||
if (group?.options?.length) {
|
||||
group.options = group.options.concat(group.options)
|
||||
// 2. Add items to group items array and add to sanitized.slashMenu.groupsWithItems
|
||||
if (group?.items?.length) {
|
||||
group.items = group.items.concat(group.items)
|
||||
}
|
||||
groupsWithOptions.push(group)
|
||||
groupsWithItems.push(group)
|
||||
}
|
||||
}
|
||||
|
||||
return groupsWithOptions
|
||||
}, [getDynamicOptions, queryString, editorConfig?.features, i18n])
|
||||
return groupsWithItems
|
||||
}, [getDynamicItems, queryString, editorConfig?.features, i18n])
|
||||
|
||||
const onSelectOption = useCallback(
|
||||
const onSelectItem = useCallback(
|
||||
(
|
||||
selectedOption: SlashMenuOption,
|
||||
selectedItem: SlashMenuItemType,
|
||||
nodeToRemove: TextNode | null,
|
||||
closeMenu: () => void,
|
||||
matchingString: string,
|
||||
) => {
|
||||
editor.update(() => {
|
||||
if (nodeToRemove) {
|
||||
if (nodeToRemove) {
|
||||
editor.update(() => {
|
||||
nodeToRemove.remove()
|
||||
}
|
||||
selectedOption.onSelect({ editor, queryString: matchingString })
|
||||
closeMenu()
|
||||
})
|
||||
})
|
||||
}
|
||||
selectedItem.onSelect({ editor, queryString: matchingString })
|
||||
|
||||
closeMenu()
|
||||
},
|
||||
[editor],
|
||||
)
|
||||
@@ -175,10 +182,10 @@ export function SlashMenuPlugin({
|
||||
<React.Fragment>
|
||||
<LexicalTypeaheadMenuPlugin
|
||||
anchorElem={anchorElem}
|
||||
groupsWithOptions={groups}
|
||||
groups={groups as SlashMenuGroupInternal[]}
|
||||
menuRenderFn={(
|
||||
anchorElementRef,
|
||||
{ selectOptionAndCleanUp, selectedOptionKey, setSelectedOptionKey },
|
||||
{ selectItemAndCleanUp, selectedItemKey, setSelectedItemKey },
|
||||
) =>
|
||||
anchorElementRef.current && groups.length
|
||||
? ReactDOM.createPortal(
|
||||
@@ -198,19 +205,19 @@ export function SlashMenuPlugin({
|
||||
key={group.key}
|
||||
>
|
||||
<div className={`${baseClass}__group-title`}>{groupTitle}</div>
|
||||
{group.options.map((option, oi: number) => (
|
||||
{group.items.map((item, oi: number) => (
|
||||
<SlashMenuItem
|
||||
index={oi}
|
||||
isSelected={selectedOptionKey === option.key}
|
||||
key={option.key}
|
||||
isSelected={selectedItemKey === item.key}
|
||||
item={item as SlashMenuItemInternal}
|
||||
key={item.key}
|
||||
onClick={() => {
|
||||
setSelectedOptionKey(option.key)
|
||||
selectOptionAndCleanUp(option)
|
||||
setSelectedItemKey(item.key)
|
||||
selectItemAndCleanUp(item)
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setSelectedOptionKey(option.key)
|
||||
setSelectedItemKey(item.key)
|
||||
}}
|
||||
option={option}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -222,7 +229,7 @@ export function SlashMenuPlugin({
|
||||
: null
|
||||
}
|
||||
onQueryChange={setQueryString}
|
||||
onSelectOption={onSelectOption}
|
||||
onSelectItem={onSelectItem}
|
||||
triggerFn={checkForTriggerMatch}
|
||||
/>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import type { BaseSelection, LexicalEditor } from 'lexical'
|
||||
import type React from 'react'
|
||||
|
||||
export type FloatingToolbarSection =
|
||||
export type FixedToolbarGroup =
|
||||
| {
|
||||
ChildComponent?: React.FC
|
||||
entries: Array<FloatingToolbarSectionEntry>
|
||||
items: Array<FixedToolbarGroupItem>
|
||||
key: string
|
||||
order?: number
|
||||
type: 'dropdown'
|
||||
}
|
||||
| {
|
||||
entries: Array<FloatingToolbarSectionEntry>
|
||||
items: Array<FixedToolbarGroupItem>
|
||||
key: string
|
||||
order?: number
|
||||
type: 'buttons'
|
||||
}
|
||||
|
||||
export type FloatingToolbarSectionEntry = {
|
||||
export type FixedToolbarGroupItem = {
|
||||
ChildComponent?: React.FC
|
||||
/** Use component to ignore the children and onClick properties. It does not use the default, pre-defined format Button component */
|
||||
Component?: React.FC<{
|
||||
anchorElem: HTMLElement
|
||||
editor: LexicalEditor
|
||||
entry: FloatingToolbarSectionEntry
|
||||
item: FixedToolbarGroupItem
|
||||
}>
|
||||
isActive?: ({ editor, selection }: { editor: LexicalEditor; selection: BaseSelection }) => boolean
|
||||
isEnabled?: ({
|
||||
@@ -33,7 +33,7 @@ export type FloatingToolbarSectionEntry = {
|
||||
selection: BaseSelection
|
||||
}) => boolean
|
||||
key: string
|
||||
/** The label is displayed as text if the entry is part of a dropdown section */
|
||||
/** The label is displayed as text if the item is part of a dropdown group */
|
||||
label?: string
|
||||
onClick?: ({ editor, isActive }: { editor: LexicalEditor; isActive: boolean }) => void
|
||||
order?: number
|
||||
@@ -1,12 +1,12 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
@import '../../../../../../scss/styles';
|
||||
|
||||
html[data-theme='light'] {
|
||||
.floating-select-toolbar-popup {
|
||||
.inline-toolbar-popup {
|
||||
@include shadow-m;
|
||||
}
|
||||
}
|
||||
|
||||
.floating-select-toolbar-popup {
|
||||
.inline-toolbar-popup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--color-base-0);
|
||||
@@ -31,9 +31,10 @@ html[data-theme='light'] {
|
||||
border-top-color: var(--color-base-0);
|
||||
}
|
||||
|
||||
&__section {
|
||||
&__group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
.icon {
|
||||
min-width: 20px;
|
||||
@@ -14,93 +14,88 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import * as React from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import type { FloatingToolbarSection, FloatingToolbarSectionEntry } from './types.js'
|
||||
import type { InlineToolbarGroup, InlineToolbarGroupItem } from '../types.js'
|
||||
|
||||
import { useEditorConfigContext } from '../../config/client/EditorConfigProvider.js'
|
||||
import { getDOMRangeRect } from '../../utils/getDOMRangeRect.js'
|
||||
import { setFloatingElemPosition } from '../../utils/setFloatingElemPosition.js'
|
||||
import { ToolbarButton } from './ToolbarButton/index.js'
|
||||
import { ToolbarDropdown } from './ToolbarDropdown/index.js'
|
||||
import { useEditorConfigContext } from '../../../../config/client/EditorConfigProvider.js'
|
||||
import { getDOMRangeRect } from '../../../../utils/getDOMRangeRect.js'
|
||||
import { setFloatingElemPosition } from '../../../../utils/setFloatingElemPosition.js'
|
||||
import { ToolbarButton } from '../ToolbarButton/index.js'
|
||||
import { ToolbarDropdown } from '../ToolbarDropdown/index.js'
|
||||
import './index.scss'
|
||||
|
||||
function ButtonSectionEntry({
|
||||
function ButtonGroupItem({
|
||||
anchorElem,
|
||||
editor,
|
||||
entry,
|
||||
item,
|
||||
}: {
|
||||
anchorElem: HTMLElement
|
||||
editor: LexicalEditor
|
||||
entry: FloatingToolbarSectionEntry
|
||||
item: InlineToolbarGroupItem
|
||||
}): React.ReactNode {
|
||||
if (entry.Component) {
|
||||
if (item.Component) {
|
||||
return (
|
||||
entry?.Component && (
|
||||
<entry.Component anchorElem={anchorElem} editor={editor} entry={entry} key={entry.key} />
|
||||
item?.Component && (
|
||||
<item.Component anchorElem={anchorElem} editor={editor} item={item} key={item.key} />
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolbarButton entry={entry} key={entry.key}>
|
||||
{entry?.ChildComponent && <entry.ChildComponent />}
|
||||
<ToolbarButton item={item} key={item.key}>
|
||||
{item?.ChildComponent && <item.ChildComponent />}
|
||||
</ToolbarButton>
|
||||
)
|
||||
}
|
||||
|
||||
function ToolbarSection({
|
||||
function ToolbarGroup({
|
||||
anchorElem,
|
||||
editor,
|
||||
group,
|
||||
index,
|
||||
section,
|
||||
}: {
|
||||
anchorElem: HTMLElement
|
||||
editor: LexicalEditor
|
||||
group: InlineToolbarGroup
|
||||
index: number
|
||||
section: FloatingToolbarSection
|
||||
}): React.ReactNode {
|
||||
const { editorConfig } = useEditorConfigContext()
|
||||
|
||||
const Icon =
|
||||
section?.type === 'dropdown' && section.entries.length && section.ChildComponent
|
||||
? section.ChildComponent
|
||||
group?.type === 'dropdown' && group.items.length && group.ChildComponent
|
||||
? group.ChildComponent
|
||||
: null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`floating-select-toolbar-popup__section floating-select-toolbar-popup__section-${section.key}`}
|
||||
key={section.key}
|
||||
className={`inline-toolbar-popup__group inline-toolbar-popup__group-${group.key}`}
|
||||
key={group.key}
|
||||
>
|
||||
{section.type === 'dropdown' &&
|
||||
section.entries.length &&
|
||||
{group.type === 'dropdown' &&
|
||||
group.items.length &&
|
||||
(Icon ? (
|
||||
<ToolbarDropdown
|
||||
Icon={Icon}
|
||||
anchorElem={anchorElem}
|
||||
editor={editor}
|
||||
entries={section.entries}
|
||||
sectionKey={section.key}
|
||||
groupKey={group.key}
|
||||
items={group.items}
|
||||
/>
|
||||
) : (
|
||||
<ToolbarDropdown
|
||||
anchorElem={anchorElem}
|
||||
editor={editor}
|
||||
entries={section.entries}
|
||||
sectionKey={section.key}
|
||||
groupKey={group.key}
|
||||
items={group.items}
|
||||
/>
|
||||
))}
|
||||
{section.type === 'buttons' &&
|
||||
section.entries.length &&
|
||||
section.entries.map((entry) => {
|
||||
{group.type === 'buttons' &&
|
||||
group.items.length &&
|
||||
group.items.map((item) => {
|
||||
return (
|
||||
<ButtonSectionEntry
|
||||
anchorElem={anchorElem}
|
||||
editor={editor}
|
||||
entry={entry}
|
||||
key={entry.key}
|
||||
/>
|
||||
<ButtonGroupItem anchorElem={anchorElem} editor={editor} item={item} key={item.key} />
|
||||
)
|
||||
})}
|
||||
{index < editorConfig.features.floatingSelectToolbar?.sections.length - 1 && (
|
||||
{index < editorConfig.features.toolbarInline?.groups.length - 1 && (
|
||||
<div className="divider" />
|
||||
)}
|
||||
</div>
|
||||
@@ -262,19 +257,19 @@ function FloatingSelectToolbar({
|
||||
}, [editor, updateTextFormatFloatingToolbar])
|
||||
|
||||
return (
|
||||
<div className="floating-select-toolbar-popup" ref={floatingToolbarRef}>
|
||||
<div className="inline-toolbar-popup" ref={floatingToolbarRef}>
|
||||
<div className="caret" ref={caretRef} />
|
||||
{editor.isEditable() && (
|
||||
<React.Fragment>
|
||||
{editorConfig?.features &&
|
||||
editorConfig.features?.floatingSelectToolbar?.sections.map((section, i) => {
|
||||
editorConfig.features?.toolbarInline?.groups.map((group, i) => {
|
||||
return (
|
||||
<ToolbarSection
|
||||
<ToolbarGroup
|
||||
anchorElem={anchorElem}
|
||||
editor={editor}
|
||||
group={group}
|
||||
index={i}
|
||||
key={section.key}
|
||||
section={section}
|
||||
key={group.key}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
@@ -1,4 +1,4 @@
|
||||
.floating-select-toolbar-popup__button {
|
||||
.inline-toolbar-popup__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
@@ -4,18 +4,18 @@ import { mergeRegister } from '@lexical/utils'
|
||||
import { $getSelection } from 'lexical'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import type { FloatingToolbarSectionEntry } from '../types.js'
|
||||
import type { InlineToolbarGroupItem } from '../types.js'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'floating-select-toolbar-popup__button'
|
||||
const baseClass = 'inline-toolbar-popup__button'
|
||||
|
||||
export const ToolbarButton = ({
|
||||
children,
|
||||
entry,
|
||||
item,
|
||||
}: {
|
||||
children: React.JSX.Element
|
||||
entry: FloatingToolbarSectionEntry
|
||||
item: InlineToolbarGroupItem
|
||||
}) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const [enabled, setEnabled] = useState<boolean>(true)
|
||||
@@ -25,20 +25,20 @@ export const ToolbarButton = ({
|
||||
const updateStates = useCallback(() => {
|
||||
editor.getEditorState().read(() => {
|
||||
const selection = $getSelection()
|
||||
if (entry.isActive) {
|
||||
const isActive = entry.isActive({ editor, selection })
|
||||
if (item.isActive) {
|
||||
const isActive = item.isActive({ editor, selection })
|
||||
if (active !== isActive) {
|
||||
setActive(isActive)
|
||||
}
|
||||
}
|
||||
if (entry.isEnabled) {
|
||||
const isEnabled = entry.isEnabled({ editor, selection })
|
||||
if (item.isEnabled) {
|
||||
const isEnabled = item.isEnabled({ editor, selection })
|
||||
if (enabled !== isEnabled) {
|
||||
setEnabled(isEnabled)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [active, editor, enabled, entry])
|
||||
}, [active, editor, enabled, item])
|
||||
|
||||
useEffect(() => {
|
||||
updateStates()
|
||||
@@ -65,7 +65,7 @@ export const ToolbarButton = ({
|
||||
baseClass,
|
||||
enabled === false ? 'disabled' : '',
|
||||
active ? 'active' : '',
|
||||
entry?.key ? `${baseClass}-` + entry.key : '',
|
||||
item?.key ? `${baseClass}-` + item.key : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' '),
|
||||
@@ -77,7 +77,7 @@ export const ToolbarButton = ({
|
||||
className={className}
|
||||
onClick={() => {
|
||||
if (enabled !== false) {
|
||||
entry.onClick({
|
||||
item.onSelect({
|
||||
editor,
|
||||
isActive: active,
|
||||
})
|
||||
@@ -6,9 +6,9 @@ import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } fro
|
||||
import React from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import type { FloatingToolbarSectionEntry } from '../types.js'
|
||||
import type { InlineToolbarGroupItem } from '../types.js'
|
||||
|
||||
const baseClass = 'floating-select-toolbar-popup__dropdown-item'
|
||||
const baseClass = 'inline-toolbar-popup__dropdown-item'
|
||||
|
||||
interface DropDownContextType {
|
||||
registerItem: (ref: React.RefObject<HTMLButtonElement>) => void
|
||||
@@ -18,11 +18,11 @@ const DropDownContext = React.createContext<DropDownContextType | null>(null)
|
||||
|
||||
export function DropDownItem({
|
||||
children,
|
||||
entry,
|
||||
item,
|
||||
title,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
entry: FloatingToolbarSectionEntry
|
||||
item: InlineToolbarGroupItem
|
||||
title?: string
|
||||
}): React.ReactNode {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
@@ -33,20 +33,20 @@ export function DropDownItem({
|
||||
const updateStates = useCallback(() => {
|
||||
editor.getEditorState().read(() => {
|
||||
const selection = $getSelection()
|
||||
if (entry.isActive) {
|
||||
const isActive = entry.isActive({ editor, selection })
|
||||
if (item.isActive) {
|
||||
const isActive = item.isActive({ editor, selection })
|
||||
if (active !== isActive) {
|
||||
setActive(isActive)
|
||||
}
|
||||
}
|
||||
if (entry.isEnabled) {
|
||||
const isEnabled = entry.isEnabled({ editor, selection })
|
||||
if (item.isEnabled) {
|
||||
const isEnabled = item.isEnabled({ editor, selection })
|
||||
if (enabled !== isEnabled) {
|
||||
setEnabled(isEnabled)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [active, editor, enabled, entry])
|
||||
}, [active, editor, enabled, item])
|
||||
|
||||
useEffect(() => {
|
||||
updateStates()
|
||||
@@ -73,12 +73,12 @@ export function DropDownItem({
|
||||
baseClass,
|
||||
enabled === false ? 'disabled' : '',
|
||||
active ? 'active' : '',
|
||||
entry?.key ? `${baseClass}-${entry.key}` : '',
|
||||
item?.key ? `${baseClass}-${item.key}` : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' '),
|
||||
)
|
||||
}, [enabled, active, className, entry.key])
|
||||
}, [enabled, active, className, item.key])
|
||||
|
||||
const ref = useRef<HTMLButtonElement>(null)
|
||||
|
||||
@@ -101,7 +101,7 @@ export function DropDownItem({
|
||||
className={className}
|
||||
onClick={() => {
|
||||
if (enabled !== false) {
|
||||
entry.onClick({
|
||||
item.onSelect({
|
||||
editor,
|
||||
isActive: active,
|
||||
})
|
||||
@@ -185,7 +185,7 @@ function DropDownItems({
|
||||
return (
|
||||
<DropDownContext.Provider value={contextValue}>
|
||||
<div
|
||||
className="floating-select-toolbar-popup__dropdown-items"
|
||||
className="inline-toolbar-popup__dropdown-items"
|
||||
onKeyDown={handleKeyDown}
|
||||
ref={dropDownRef}
|
||||
>
|
||||
@@ -277,7 +277,7 @@ export function DropDown({
|
||||
type="button"
|
||||
>
|
||||
<Icon />
|
||||
<i className="floating-select-toolbar-popup__dropdown-caret" />
|
||||
<i className="inline-toolbar-popup__dropdown-caret" />
|
||||
</button>
|
||||
|
||||
{showDropDown &&
|
||||
@@ -1,6 +1,6 @@
|
||||
@import '../../../../../scss/styles.scss';
|
||||
@import '../../../../../../scss/styles';
|
||||
|
||||
.floating-select-toolbar-popup__dropdown {
|
||||
.inline-toolbar-popup__dropdown {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
@@ -29,7 +29,7 @@
|
||||
&.active {
|
||||
background-color: var(--color-base-100);
|
||||
|
||||
.floating-select-toolbar-popup__dropdown-caret {
|
||||
.inline-toolbar-popup__dropdown-caret {
|
||||
&:after {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
height: 4px;
|
||||
|
||||
opacity: 0.3;
|
||||
background-image: url(../../../ui/icons/Caret/index.svg);
|
||||
background-image: url(../../../../ui/icons/Caret/index.svg);
|
||||
background-position-y: 0px;
|
||||
background-position-x: 0px;
|
||||
}
|
||||
@@ -63,7 +63,7 @@
|
||||
width: 132.5px;
|
||||
z-index: 100;
|
||||
|
||||
.floating-select-toolbar-popup__dropdown-item {
|
||||
.inline-toolbar-popup__dropdown-item {
|
||||
all: unset; // reset all default button styles
|
||||
cursor: pointer;
|
||||
color: var(--color-base-900);
|
||||
@@ -91,7 +91,7 @@
|
||||
}
|
||||
|
||||
html[data-theme='light'] {
|
||||
.floating-select-toolbar-popup__dropdown {
|
||||
.inline-toolbar-popup__dropdown {
|
||||
&-items {
|
||||
position: absolute;
|
||||
@include shadow-m;
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
const baseClass = 'inline-toolbar-popup__dropdown'
|
||||
|
||||
import type { LexicalEditor } from 'lexical'
|
||||
|
||||
import type { InlineToolbarGroupItem } from '../types.js'
|
||||
|
||||
import { DropDown, DropDownItem } from './DropDown.js'
|
||||
import './index.scss'
|
||||
|
||||
export const ToolbarItem = ({
|
||||
anchorElem,
|
||||
editor,
|
||||
item,
|
||||
}: {
|
||||
anchorElem: HTMLElement
|
||||
editor: LexicalEditor
|
||||
item: InlineToolbarGroupItem
|
||||
}) => {
|
||||
if (item.Component) {
|
||||
return (
|
||||
item?.Component && (
|
||||
<item.Component anchorElem={anchorElem} editor={editor} item={item} key={item.key} />
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DropDownItem item={item} key={item.key}>
|
||||
{item?.ChildComponent && <item.ChildComponent />}
|
||||
<span className="text">{item.label}</span>
|
||||
</DropDownItem>
|
||||
)
|
||||
}
|
||||
|
||||
export const ToolbarDropdown = ({
|
||||
Icon,
|
||||
anchorElem,
|
||||
classNames,
|
||||
editor,
|
||||
groupKey,
|
||||
items,
|
||||
}: {
|
||||
Icon?: React.FC
|
||||
anchorElem: HTMLElement
|
||||
classNames?: string[]
|
||||
editor: LexicalEditor
|
||||
groupKey: string
|
||||
items: InlineToolbarGroupItem[]
|
||||
}) => {
|
||||
return (
|
||||
<DropDown
|
||||
Icon={Icon}
|
||||
buttonAriaLabel={`${groupKey} dropdown`}
|
||||
buttonClassName={[baseClass, `${baseClass}-${groupKey}`, ...(classNames || [])]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
key={groupKey}
|
||||
>
|
||||
{items.length &&
|
||||
items.map((item) => {
|
||||
return <ToolbarItem anchorElem={anchorElem} editor={editor} item={item} key={item.key} />
|
||||
})}
|
||||
</DropDown>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { BaseSelection, LexicalEditor } from 'lexical'
|
||||
import type React from 'react'
|
||||
|
||||
export type InlineToolbarGroup =
|
||||
| {
|
||||
ChildComponent?: React.FC
|
||||
items: Array<InlineToolbarGroupItem>
|
||||
key: string
|
||||
order?: number
|
||||
type: 'dropdown'
|
||||
}
|
||||
| {
|
||||
items: Array<InlineToolbarGroupItem>
|
||||
key: string
|
||||
order?: number
|
||||
type: 'buttons'
|
||||
}
|
||||
|
||||
export type InlineToolbarGroupItem = {
|
||||
ChildComponent?: React.FC
|
||||
/** Use component to ignore the children and onClick properties. It does not use the default, pre-defined format Button component */
|
||||
Component?: React.FC<{
|
||||
anchorElem: HTMLElement
|
||||
editor: LexicalEditor
|
||||
item: InlineToolbarGroupItem
|
||||
}>
|
||||
isActive?: ({ editor, selection }: { editor: LexicalEditor; selection: BaseSelection }) => boolean
|
||||
isEnabled?: ({
|
||||
editor,
|
||||
selection,
|
||||
}: {
|
||||
editor: LexicalEditor
|
||||
selection: BaseSelection
|
||||
}) => boolean
|
||||
key: string
|
||||
/** The label is displayed as text if the item is part of a dropdown group */
|
||||
label?: string
|
||||
onSelect?: ({ editor, isActive }: { editor: LexicalEditor; isActive: boolean }) => void
|
||||
order?: number
|
||||
}
|
||||
@@ -16,9 +16,7 @@
|
||||
}
|
||||
|
||||
&__quote {
|
||||
margin: 0;
|
||||
margin-left: 20px;
|
||||
margin-bottom: 10px;
|
||||
margin: 0 0 10px 20px;
|
||||
font-size: 15px;
|
||||
color: rgb(101, 103, 107);
|
||||
border-left-color: rgb(206, 208, 212);
|
||||
@@ -115,9 +113,7 @@
|
||||
padding: 8px 8px 8px 52px;
|
||||
line-height: 1.53;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
margin: 8px 0;
|
||||
tab-size: 2;
|
||||
/* white-space: pre; */
|
||||
overflow-x: auto;
|
||||
|
||||
@@ -29,7 +29,7 @@ let defaultSanitizedServerEditorConfig: SanitizedServerEditorConfig = null
|
||||
|
||||
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
||||
return async ({ config }) => {
|
||||
let resolvedFeatureMap: ResolvedServerFeatureMap = null
|
||||
let resolvedFeatureMap: ResolvedServerFeatureMap
|
||||
|
||||
let finalSanitizedEditorConfig: SanitizedServerEditorConfig // For server only
|
||||
if (!props || (!props.features && !props.lexical)) {
|
||||
@@ -315,11 +315,10 @@ export {
|
||||
type SerializedBlockNode,
|
||||
} from './field/features/blocks/nodes/BlocksNode.js'
|
||||
|
||||
export { TextDropdownSectionWithEntries } from './field/features/common/floatingSelectToolbarTextDropdownSection/index.js'
|
||||
export { LinebreakHTMLConverter } from './field/features/converters/html/converter/converters/linebreak.js'
|
||||
export { ParagraphHTMLConverter } from './field/features/converters/html/converter/converters/paragraph.js'
|
||||
|
||||
export { TextHTMLConverter } from './field/features/converters/html/converter/converters/text.js'
|
||||
|
||||
export { defaultHTMLConverters } from './field/features/converters/html/converter/defaultConverters.js'
|
||||
export {
|
||||
convertLexicalNodesToHTML,
|
||||
@@ -335,13 +334,13 @@ export {
|
||||
lexicalHTML,
|
||||
} from './field/features/converters/html/field/index.js'
|
||||
export { createClientComponent } from './field/features/createClientComponent.js'
|
||||
export { TestRecorderFeature } from './field/features/debug/testrecorder/feature.server.js'
|
||||
export { TreeViewFeature } from './field/features/debug/treeview/feature.server.js'
|
||||
export { TestRecorderFeature } from './field/features/debug/testRecorder/feature.server.js'
|
||||
export { TreeViewFeature } from './field/features/debug/treeView/feature.server.js'
|
||||
export { BoldFeature } from './field/features/format/bold/feature.server.js'
|
||||
export { InlineCodeFeature } from './field/features/format/inlineCode/feature.server.js'
|
||||
|
||||
export { SectionWithEntries as FormatSectionWithEntries } from './field/features/format/common/floatingSelectToolbarSection.js'
|
||||
export { InlineCodeFeature } from './field/features/format/inlinecode/feature.server.js'
|
||||
export { ItalicFeature } from './field/features/format/italic/feature.server.js'
|
||||
export { inlineToolbarFormatGroupWithItems } from './field/features/format/shared/inlineToolbarFormatGroup.js'
|
||||
export { StrikethroughFeature } from './field/features/format/strikethrough/feature.server.js'
|
||||
export { SubscriptFeature } from './field/features/format/subscript/feature.server.js'
|
||||
export { SuperscriptFeature } from './field/features/format/superscript/feature.server.js'
|
||||
@@ -350,11 +349,11 @@ export {
|
||||
HeadingFeature,
|
||||
type HeadingFeatureProps,
|
||||
} from './field/features/heading/feature.server.js'
|
||||
export { HorizontalRuleFeature } from './field/features/horizontalRule/feature.server.js'
|
||||
|
||||
export { HorizontalRuleFeature } from './field/features/horizontalrule/feature.server.js'
|
||||
export { IndentFeature } from './field/features/indent/feature.server.js'
|
||||
|
||||
export { LinkFeature, type LinkFeatureServerProps } from './field/features/link/feature.server.js'
|
||||
|
||||
export {
|
||||
$createAutoLinkNode,
|
||||
$isAutoLinkNode,
|
||||
@@ -371,9 +370,9 @@ export type {
|
||||
SerializedAutoLinkNode,
|
||||
SerializedLinkNode,
|
||||
} from './field/features/link/nodes/types.js'
|
||||
export { CheckListFeature } from './field/features/lists/checklist/feature.server.js'
|
||||
export { OrderedListFeature } from './field/features/lists/orderedlist/feature.server.js'
|
||||
export { UnorderedListFeature } from './field/features/lists/unorderedlist/feature.server.js'
|
||||
export { ChecklistFeature } from './field/features/lists/checklist/feature.server.js'
|
||||
export { OrderedListFeature } from './field/features/lists/orderedList/feature.server.js'
|
||||
export { UnorderedListFeature } from './field/features/lists/unorderedList/feature.server.js'
|
||||
export { LexicalPluginToLexicalFeature } from './field/features/migrations/lexicalPluginToLexical/feature.server.js'
|
||||
export { SlateBlockquoteConverter } from './field/features/migrations/slateToLexical/converter/converters/blockquote/index.js'
|
||||
export { SlateHeadingConverter } from './field/features/migrations/slateToLexical/converter/converters/heading/index.js'
|
||||
@@ -384,18 +383,18 @@ export { SlateOrderedListConverter } from './field/features/migrations/slateToLe
|
||||
export { SlateRelationshipConverter } from './field/features/migrations/slateToLexical/converter/converters/relationship/index.js'
|
||||
export { SlateUnknownConverter } from './field/features/migrations/slateToLexical/converter/converters/unknown/index.js'
|
||||
export { SlateUnorderedListConverter } from './field/features/migrations/slateToLexical/converter/converters/unorderedList/index.js'
|
||||
|
||||
export { SlateUploadConverter } from './field/features/migrations/slateToLexical/converter/converters/upload/index.js'
|
||||
|
||||
export { defaultSlateConverters } from './field/features/migrations/slateToLexical/converter/defaultConverters.js'
|
||||
export {
|
||||
convertSlateNodesToLexical,
|
||||
convertSlateToLexical,
|
||||
} from './field/features/migrations/slateToLexical/converter/index.js'
|
||||
|
||||
export type {
|
||||
SlateNode,
|
||||
SlateNodeConverter,
|
||||
} from './field/features/migrations/slateToLexical/converter/types.js'
|
||||
|
||||
export { SlateToLexicalFeature } from './field/features/migrations/slateToLexical/feature.server.js'
|
||||
export { ParagraphFeature } from './field/features/paragraph/feature.server.js'
|
||||
export {
|
||||
@@ -409,6 +408,8 @@ export {
|
||||
RelationshipNode,
|
||||
type SerializedRelationshipNode,
|
||||
} from './field/features/relationship/nodes/RelationshipNode.js'
|
||||
export { inlineToolbarFeatureButtonsGroupWithItems } from './field/features/shared/inlineToolbar/featureButtonsGroup.js'
|
||||
export { inlineToolbarTextDropdownGroupWithItems } from './field/features/shared/inlineToolbar/textDropdownGroup.js'
|
||||
export { createNode } from './field/features/typeUtilities.js'
|
||||
export type {
|
||||
ClientComponentProps,
|
||||
@@ -474,17 +475,17 @@ export type {
|
||||
} from './field/lexical/config/types.js'
|
||||
export { getEnabledNodes } from './field/lexical/nodes/index.js'
|
||||
|
||||
export {
|
||||
type FloatingToolbarSection,
|
||||
type FloatingToolbarSectionEntry,
|
||||
} from './field/lexical/plugins/FloatingSelectToolbar/types.js'
|
||||
export { ENABLE_SLASH_MENU_COMMAND } from './field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/index.js'
|
||||
export type {
|
||||
SlashMenuGroup,
|
||||
SlashMenuItem,
|
||||
} from './field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
export type { AdapterProps }
|
||||
|
||||
export {
|
||||
SlashMenuGroup,
|
||||
SlashMenuOption,
|
||||
} from './field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
export type {
|
||||
InlineToolbarGroup,
|
||||
InlineToolbarGroupItem,
|
||||
} from './field/lexical/plugins/toolbars/inline/types.js'
|
||||
export { CAN_USE_DOM } from './field/lexical/utils/canUseDOM.js'
|
||||
export { cloneDeep } from './field/lexical/utils/cloneDeep.js'
|
||||
export { getDOMRangeRect } from './field/lexical/utils/getDOMRangeRect.js'
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
BlockQuoteFeature,
|
||||
BlocksFeature,
|
||||
BoldFeature,
|
||||
CheckListFeature,
|
||||
ChecklistFeature,
|
||||
HeadingFeature,
|
||||
IndentFeature,
|
||||
InlineCodeFeature,
|
||||
@@ -90,7 +90,7 @@ export async function buildConfigWithDefaults(
|
||||
},
|
||||
],
|
||||
}),
|
||||
CheckListFeature(),
|
||||
ChecklistFeature(),
|
||||
UnorderedListFeature(),
|
||||
OrderedListFeature(),
|
||||
AlignFeature(),
|
||||
|
||||
@@ -209,16 +209,14 @@ describe('lexical', () => {
|
||||
}
|
||||
// The following text should now be selected: Node
|
||||
|
||||
const floatingToolbar_formatSection = page.locator(
|
||||
'.floating-select-toolbar-popup__section-format',
|
||||
)
|
||||
const floatingToolbar_formatSection = page.locator('.inline-toolbar-popup__group-format')
|
||||
|
||||
await expect(floatingToolbar_formatSection).toBeVisible()
|
||||
|
||||
await expect(page.locator('.floating-select-toolbar-popup__button').first()).toBeVisible()
|
||||
await expect(page.locator('.inline-toolbar-popup__button').first()).toBeVisible()
|
||||
|
||||
const boldButton = floatingToolbar_formatSection
|
||||
.locator('.floating-select-toolbar-popup__button')
|
||||
.locator('.inline-toolbar-popup__button')
|
||||
.first()
|
||||
|
||||
await expect(boldButton).toBeVisible()
|
||||
@@ -441,16 +439,14 @@ describe('lexical', () => {
|
||||
}
|
||||
// The following text should now be selectedelationship node 1
|
||||
|
||||
const floatingToolbar_formatSection = page.locator(
|
||||
'.floating-select-toolbar-popup__section-format',
|
||||
)
|
||||
const floatingToolbar_formatSection = page.locator('.inline-toolbar-popup__group-format')
|
||||
|
||||
await expect(floatingToolbar_formatSection).toBeVisible()
|
||||
|
||||
await expect(page.locator('.floating-select-toolbar-popup__button').first()).toBeVisible()
|
||||
await expect(page.locator('.inline-toolbar-popup__button').first()).toBeVisible()
|
||||
|
||||
const boldButton = floatingToolbar_formatSection
|
||||
.locator('.floating-select-toolbar-popup__button')
|
||||
.locator('.inline-toolbar-popup__button')
|
||||
.first()
|
||||
|
||||
await expect(boldButton).toBeVisible()
|
||||
@@ -521,13 +517,11 @@ describe('lexical', () => {
|
||||
}
|
||||
// The following text should now be "Node"
|
||||
|
||||
const floatingToolbar = page.locator('.floating-select-toolbar-popup')
|
||||
const floatingToolbar = page.locator('.inline-toolbar-popup')
|
||||
|
||||
await expect(floatingToolbar).toBeVisible()
|
||||
|
||||
const linkButton = floatingToolbar
|
||||
.locator('.floating-select-toolbar-popup__button-link')
|
||||
.first()
|
||||
const linkButton = floatingToolbar.locator('.inline-toolbar-popup__button-link').first()
|
||||
|
||||
await expect(linkButton).toBeVisible()
|
||||
await linkButton.click()
|
||||
|
||||
Reference in New Issue
Block a user