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