@strapi/content-manager 5.50.2 → 5.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/content-manager.js +31 -4
- package/dist/admin/content-manager.js.map +1 -1
- package/dist/admin/content-manager.mjs +31 -4
- package/dist/admin/content-manager.mjs.map +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksEditor.js +23 -7
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksEditor.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksEditor.mjs +23 -7
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksEditor.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.js +5 -13
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.mjs +5 -13
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js +4 -1
- package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs +4 -1
- package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs.map +1 -1
- package/dist/admin/pages/EditView/utils/draftRelationCounts.js +14 -7
- package/dist/admin/pages/EditView/utils/draftRelationCounts.js.map +1 -1
- package/dist/admin/pages/EditView/utils/draftRelationCounts.mjs +14 -8
- package/dist/admin/pages/EditView/utils/draftRelationCounts.mjs.map +1 -1
- package/dist/admin/preview/components/PreviewSidePanel.js +46 -9
- package/dist/admin/preview/components/PreviewSidePanel.js.map +1 -1
- package/dist/admin/preview/components/PreviewSidePanel.mjs +27 -10
- package/dist/admin/preview/components/PreviewSidePanel.mjs.map +1 -1
- package/dist/admin/src/index.d.ts +1 -1
- package/dist/admin/src/pages/EditView/components/FormInputs/Relations/Relations.d.ts +9 -1
- package/dist/admin/src/pages/EditView/utils/draftRelationCounts.d.ts +1 -0
- package/dist/server/src/history/routes/index.d.ts +3 -1
- package/dist/server/src/history/routes/index.d.ts.map +1 -1
- package/dist/server/src/homepage/index.d.ts +6 -2
- package/dist/server/src/homepage/index.d.ts.map +1 -1
- package/dist/server/src/homepage/routes/index.d.ts +3 -1
- package/dist/server/src/homepage/routes/index.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +3 -1
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/preview/routes/index.d.ts +3 -1
- package/dist/server/src/preview/routes/index.d.ts.map +1 -1
- package/dist/server/src/routes/index.d.ts +3 -1
- package/dist/server/src/routes/index.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlocksEditor.js","sources":["../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksEditor.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n createContext,\n useStrapiApp,\n type FieldValue,\n useIsMobile,\n} from '@strapi/admin/strapi-admin';\nimport { IconButton, Divider, VisuallyHidden } from '@strapi/design-system';\nimport { Expand } from '@strapi/icons';\nimport { flushSync } from 'react-dom';\nimport { MessageDescriptor, useIntl } from 'react-intl';\nimport { Editor, type Descendant, createEditor, Transforms, Element } from 'slate';\nimport { withHistory } from 'slate-history';\nimport { type RenderElementProps, Slate, withReact, ReactEditor, useSlate } from 'slate-react';\nimport { styled, type CSSProperties } from 'styled-components';\n\nimport { ContentManagerPlugin } from '../../../../../content-manager';\nimport { getTranslation } from '../../../../../utils/translations';\n\nimport { BlocksContent, type BlocksContentProps } from './BlocksContent';\nimport { BlocksToolbar } from './BlocksToolbar';\nimport { EditorLayout } from './EditorLayout';\nimport { type ModifiersStore, modifiers } from './Modifiers';\nimport { withStrapiSchema } from './plugins/withStrapiSchema';\nimport { isNonNullable } from './utils/types';\n\nimport type { Schema } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksEditorProvider\n * -----------------------------------------------------------------------------------------------*/\n\ninterface CustomNode extends Omit<Schema.Attribute.BlocksNode, 'type'> {\n type: Schema.Attribute.BlocksNode['type'] | string;\n level?: number;\n format?: string;\n}\n\ninterface BaseBlock {\n renderElement: (props: RenderElementProps) => React.JSX.Element;\n /** Function to check if a given node is of this type of block */\n matchNode: (node: Schema.Attribute.BlocksNode | CustomNode) => boolean;\n handleConvert?: (editor: Editor) => void | (() => React.JSX.Element);\n handleEnterKey?: (editor: Editor) => void;\n handleBackspaceKey?: (editor: Editor, event: React.KeyboardEvent<HTMLElement>) => void;\n handleTab?: (editor: Editor) => void;\n handleShiftTab?: (editor: Editor) => void;\n snippets?: string[];\n /** Adjust the vertical positioning of the drag-to-reorder grip icon */\n dragHandleTopMargin?: CSSProperties['marginTop'];\n /** A Slate plugin: function that will wrap the editor creation */\n plugin?: (editor: Editor) => Editor;\n /**\n * Function that checks if an element should be draggable\n * @default () => true */\n isDraggable?: (element: Element) => boolean;\n}\n\nexport interface NonSelectorBlock extends BaseBlock {\n isInBlocksSelector?: false;\n}\n\nexport interface SelectorBlock extends BaseBlock {\n isInBlocksSelector: true;\n icon?: React.ComponentType;\n label: MessageDescriptor;\n}\n\ntype NonSelectorBlockKey = 'list-item' | 'link';\n\nconst selectorBlockKeys = [\n 'paragraph',\n 'heading-one',\n 'heading-two',\n 'heading-three',\n 'heading-four',\n 'heading-five',\n 'heading-six',\n 'list-ordered',\n 'list-unordered',\n 'image',\n 'quote',\n 'code',\n] as const;\n\ntype SelectorBlockKey = (typeof selectorBlockKeys)[number];\n\nconst isSelectorBlockKey = (key: unknown): key is SelectorBlockKey => {\n return typeof key === 'string' && selectorBlockKeys.includes(key as SelectorBlockKey);\n};\n\ntype BlocksStore = {\n [K in SelectorBlockKey]: SelectorBlock;\n} & {\n [K in NonSelectorBlockKey]: NonSelectorBlock;\n};\n\ntype RichTextBlocksStore = Partial<BlocksStore> & Record<string, SelectorBlock | NonSelectorBlock>;\n\ninterface BlocksEditorContextValue {\n blocks: RichTextBlocksStore;\n modifiers: ModifiersStore;\n disabled: boolean;\n name: string;\n setLiveText: (text: string) => void;\n isExpandedMode: boolean;\n /** Push debounced Slate → form sync immediately (e.g. on Editable blur before Save). */\n flushPendingFormSync: () => void;\n}\n\nconst [BlocksEditorProvider, usePartialBlocksEditorContext] =\n createContext<BlocksEditorContextValue>('BlocksEditor');\n\nfunction useBlocksEditorContext(consumerName: string): BlocksEditorContextValue & {\n editor: Editor;\n} {\n const context = usePartialBlocksEditorContext(consumerName, (state) => state);\n const editor = useSlate();\n\n return {\n ...context,\n editor,\n };\n}\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksEditor\n * -----------------------------------------------------------------------------------------------*/\n\nconst EditorDivider = styled(Divider)`\n background: ${({ theme }) => theme.colors.neutral200};\n`;\n\n/**\n * Forces an update of the Slate editor when the value prop changes from outside of Slate.\n * The root cause is that Slate is not a controlled component: https://github.com/ianstormtaylor/slate/issues/4612\n * Why not use JSON.stringify(value) as the key?\n * Because it would force a rerender of the entire editor every time the user types a character.\n * Why not use the entity id as the key, since it's unique for each locale?\n * Because it would not solve the problem when using the \"fill in from other locale\" feature\n */\nfunction useResetKey(\n editor: Editor,\n value?: Schema.Attribute.BlocksValue\n): {\n key: number;\n incrementSlateUpdatesCount: () => void;\n} {\n // Keep track how many times Slate detected a change from a user interaction in the editor\n const slateUpdatesCount = React.useRef(0);\n // Keep track of how many times the value prop was updated, whether from within editor or from outside\n const valueUpdatesCount = React.useRef(0);\n // Use a key to force a rerender of the Slate editor when needed\n const [key, setKey] = React.useState(0);\n\n React.useEffect(() => {\n valueUpdatesCount.current += 1;\n\n // If the 2 refs are not equal, it means the value was updated from outside\n if (valueUpdatesCount.current !== slateUpdatesCount.current) {\n if (editor.selection) {\n Transforms.deselect(editor);\n }\n\n // So we change the key to force a rerender of the Slate editor,\n // which will pick up the new value through its initialValue prop\n setKey((previousKey) => previousKey + 1);\n\n // Then bring the 2 refs back in sync\n slateUpdatesCount.current = valueUpdatesCount.current;\n }\n }, [editor, value]);\n\n const incrementSlateUpdatesCount = React.useCallback(() => {\n slateUpdatesCount.current += 1;\n }, []);\n\n return { key, incrementSlateUpdatesCount };\n}\n\nconst pipe =\n (...fns: ((baseEditor: Editor) => Editor)[]) =>\n (value: Editor) =>\n fns.reduce<Editor>((prev, fn) => fn(prev), value);\n\n/**\n * Normalize the blocks state to null if the editor state is considered empty,\n * otherwise return the state\n */\nconst normalizeBlocksState = (\n editor: Editor,\n value: Schema.Attribute.BlocksValue | Descendant[]\n): Schema.Attribute.BlocksValue | Descendant[] | null => {\n const isEmpty =\n value.length === 1 && Editor.isEmpty(editor, value[0] as Schema.Attribute.BlocksNode);\n\n return isEmpty ? null : value;\n};\n\ninterface BlocksEditorProps\n extends Pick<FieldValue<Schema.Attribute.BlocksValue>, 'onChange' | 'value' | 'error'>,\n BlocksContentProps {\n disabled?: boolean;\n name: string;\n}\n\nconst BlocksEditor = React.forwardRef<{ focus: () => void }, BlocksEditorProps>(\n ({ disabled = false, name, onChange, value, error, ...contentProps }, forwardedRef) => {\n const { formatMessage } = useIntl();\n const isMobile = useIsMobile();\n\n const blocks = useStrapiApp(\n 'BlocksEditor',\n (state) =>\n (\n state.plugins['content-manager']?.apis as\n | ContentManagerPlugin['config']['apis']\n | undefined\n )?.getRichTextBlocks() ?? ({} as RichTextBlocksStore)\n );\n\n const blockRegisteredPlugins = Object.values(blocks)\n .map((block) => block.plugin)\n .filter(isNonNullable);\n\n const [editor] = React.useState(() =>\n pipe(withHistory, withStrapiSchema, withReact, ...blockRegisteredPlugins)(createEditor())\n );\n const [liveText, setLiveText] = React.useState('');\n const ariaDescriptionId = React.useId();\n const [isExpandedMode, handleToggleExpand] = React.useReducer((prev) => !prev, false);\n\n /**\n * Editable is not able to hold the ref, https://github.com/ianstormtaylor/slate/issues/4082\n * so with \"useImperativeHandle\" we can use ReactEditor methods to expose to the parent above\n * also not passing forwarded ref here, gives console warning.\n */\n React.useImperativeHandle(\n forwardedRef,\n () => ({\n focus() {\n ReactEditor.focus(editor);\n },\n }),\n [editor]\n );\n\n const { key, incrementSlateUpdatesCount } = useResetKey(editor, value);\n\n const debounceTimeout = React.useRef<NodeJS.Timeout | null>(null);\n\n const flushPendingFormSync = React.useCallback(() => {\n if (!debounceTimeout.current) {\n return;\n }\n clearTimeout(debounceTimeout.current);\n debounceTimeout.current = null;\n incrementSlateUpdatesCount();\n // Ensure Strapi Form state updates before the next event (e.g. Save click) reads values.\n flushSync(() => {\n onChange(\n name,\n normalizeBlocksState(editor, editor.children) as Schema.Attribute.BlocksValue\n );\n });\n }, [editor, incrementSlateUpdatesCount, name, onChange]);\n\n const handleSlateChange = React.useCallback(\n (state: Descendant[]) => {\n const isAstChange = editor.operations.some((op) => op.type !== 'set_selection');\n\n if (isAstChange) {\n /**\n * Slate handles the state of the editor internally. We just need to keep Strapi's form\n * state in sync with it in order to make sure that things like the \"modified\" state\n * isn't broken. Updating the whole state on every change is very expensive however,\n * so we debounce calls to onChange to mitigate input lag.\n */\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n\n // Set a new debounce timeout\n debounceTimeout.current = setTimeout(() => {\n incrementSlateUpdatesCount();\n\n // Normalize the state (empty editor becomes null)\n onChange(name, normalizeBlocksState(editor, state) as Schema.Attribute.BlocksValue);\n debounceTimeout.current = null;\n }, 300);\n }\n },\n [editor, incrementSlateUpdatesCount, name, onChange]\n );\n\n // Clean up the timeout on unmount\n React.useEffect(() => {\n return () => {\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n };\n }, []);\n\n // Ensure the editor is in sync after discard\n React.useEffect(() => {\n // Never deselect while the editor is actively focused (typing / editing),\n if (ReactEditor.isFocused(editor)) {\n return;\n }\n\n // Normalize empty states for comparison to avoid losing focus on the editor when content is deleted\n const normalizedValue = value?.length ? value : null;\n const normalizedEditorState = normalizeBlocksState(editor, editor.children);\n\n // Compare the field value with the editor state to check for a stale selection\n if (\n normalizedValue &&\n normalizedEditorState &&\n JSON.stringify(normalizedEditorState) !== JSON.stringify(normalizedValue)\n ) {\n // When there is a diff, unset selection to avoid an invalid state\n Transforms.deselect(editor);\n }\n }, [editor, value]);\n\n return (\n <>\n <VisuallyHidden id={ariaDescriptionId}>\n {formatMessage({\n id: getTranslation('components.Blocks.dnd.instruction'),\n defaultMessage: `To reorder blocks, press Command or Control along with Shift and the Up or Down arrow keys`,\n })}\n </VisuallyHidden>\n <VisuallyHidden aria-live=\"assertive\">{liveText}</VisuallyHidden>\n <Slate\n editor={editor}\n initialValue={\n value?.length ? value : [{ type: 'paragraph', children: [{ type: 'text', text: '' }] }]\n }\n onChange={handleSlateChange}\n key={key}\n >\n <BlocksEditorProvider\n blocks={blocks}\n modifiers={modifiers}\n disabled={disabled}\n name={name}\n setLiveText={setLiveText}\n isExpandedMode={isExpandedMode}\n flushPendingFormSync={flushPendingFormSync}\n >\n <EditorLayout\n error={error}\n disabled={disabled}\n onToggleExpand={handleToggleExpand}\n ariaDescriptionId={ariaDescriptionId}\n >\n <BlocksToolbar />\n <EditorDivider width=\"100%\" />\n <BlocksContent {...contentProps} />\n {!isExpandedMode && !isMobile && (\n <IconButton\n position=\"absolute\"\n bottom=\"1.2rem\"\n right=\"1.2rem\"\n shadow=\"filterShadow\"\n label={formatMessage({\n id: getTranslation('components.Blocks.expand'),\n defaultMessage: 'Expand',\n })}\n onClick={handleToggleExpand}\n >\n <Expand />\n </IconButton>\n )}\n </EditorLayout>\n </BlocksEditorProvider>\n </Slate>\n </>\n );\n }\n);\n\nexport {\n type BlocksStore,\n type RichTextBlocksStore,\n type SelectorBlockKey,\n BlocksEditor,\n BlocksEditorProvider,\n useBlocksEditorContext,\n isSelectorBlockKey,\n normalizeBlocksState,\n};\n"],"names":["BlocksEditorProvider","usePartialBlocksEditorContext","createContext","useBlocksEditorContext","consumerName","context","state","editor","useSlate","EditorDivider","styled","Divider","theme","colors","neutral200","useResetKey","value","slateUpdatesCount","React","useRef","valueUpdatesCount","key","setKey","useState","useEffect","current","selection","Transforms","deselect","previousKey","incrementSlateUpdatesCount","useCallback","pipe","fns","reduce","prev","fn","normalizeBlocksState","isEmpty","length","Editor","BlocksEditor","forwardRef","disabled","name","onChange","error","contentProps","forwardedRef","formatMessage","useIntl","isMobile","useIsMobile","blocks","useStrapiApp","plugins","apis","getRichTextBlocks","blockRegisteredPlugins","Object","values","map","block","plugin","filter","isNonNullable","withHistory","withStrapiSchema","withReact","createEditor","liveText","setLiveText","ariaDescriptionId","useId","isExpandedMode","handleToggleExpand","useReducer","useImperativeHandle","focus","ReactEditor","debounceTimeout","flushPendingFormSync","clearTimeout","flushSync","children","handleSlateChange","isAstChange","operations","some","op","type","setTimeout","isFocused","normalizedValue","normalizedEditorState","JSON","stringify","_jsxs","_Fragment","_jsx","VisuallyHidden","id","getTranslation","defaultMessage","aria-live","Slate","initialValue","text","modifiers","EditorLayout","onToggleExpand","BlocksToolbar","width","BlocksContent","IconButton","position","bottom","right","shadow","label","onClick","Expand"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BlocksEditor.js","sources":["../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksEditor.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n createContext,\n useStrapiApp,\n type FieldValue,\n useIsMobile,\n} from '@strapi/admin/strapi-admin';\nimport { IconButton, Divider, VisuallyHidden } from '@strapi/design-system';\nimport { Expand } from '@strapi/icons';\nimport { flushSync } from 'react-dom';\nimport { MessageDescriptor, useIntl } from 'react-intl';\nimport { Editor, type Descendant, createEditor, Transforms, Element } from 'slate';\nimport { withHistory } from 'slate-history';\nimport { type RenderElementProps, Slate, withReact, ReactEditor, useSlate } from 'slate-react';\nimport { styled, type CSSProperties } from 'styled-components';\n\nimport { ContentManagerPlugin } from '../../../../../content-manager';\nimport { getTranslation } from '../../../../../utils/translations';\n\nimport { BlocksContent, type BlocksContentProps } from './BlocksContent';\nimport { BlocksToolbar } from './BlocksToolbar';\nimport { EditorLayout } from './EditorLayout';\nimport { type ModifiersStore, modifiers } from './Modifiers';\nimport { withStrapiSchema } from './plugins/withStrapiSchema';\nimport { isNonNullable } from './utils/types';\n\nimport type { Schema } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksEditorProvider\n * -----------------------------------------------------------------------------------------------*/\n\ninterface CustomNode extends Omit<Schema.Attribute.BlocksNode, 'type'> {\n type: Schema.Attribute.BlocksNode['type'] | string;\n level?: number;\n format?: string;\n}\n\ninterface BaseBlock {\n renderElement: (props: RenderElementProps) => React.JSX.Element;\n /** Function to check if a given node is of this type of block */\n matchNode: (node: Schema.Attribute.BlocksNode | CustomNode) => boolean;\n handleConvert?: (editor: Editor) => void | (() => React.JSX.Element);\n handleEnterKey?: (editor: Editor) => void;\n handleBackspaceKey?: (editor: Editor, event: React.KeyboardEvent<HTMLElement>) => void;\n handleTab?: (editor: Editor) => void;\n handleShiftTab?: (editor: Editor) => void;\n snippets?: string[];\n /** Adjust the vertical positioning of the drag-to-reorder grip icon */\n dragHandleTopMargin?: CSSProperties['marginTop'];\n /** A Slate plugin: function that will wrap the editor creation */\n plugin?: (editor: Editor) => Editor;\n /**\n * Function that checks if an element should be draggable\n * @default () => true */\n isDraggable?: (element: Element) => boolean;\n}\n\nexport interface NonSelectorBlock extends BaseBlock {\n isInBlocksSelector?: false;\n}\n\nexport interface SelectorBlock extends BaseBlock {\n isInBlocksSelector: true;\n icon?: React.ComponentType;\n label: MessageDescriptor;\n}\n\ntype NonSelectorBlockKey = 'list-item' | 'link';\n\nconst selectorBlockKeys = [\n 'paragraph',\n 'heading-one',\n 'heading-two',\n 'heading-three',\n 'heading-four',\n 'heading-five',\n 'heading-six',\n 'list-ordered',\n 'list-unordered',\n 'image',\n 'quote',\n 'code',\n] as const;\n\ntype SelectorBlockKey = (typeof selectorBlockKeys)[number];\n\nconst isSelectorBlockKey = (key: unknown): key is SelectorBlockKey => {\n return typeof key === 'string' && selectorBlockKeys.includes(key as SelectorBlockKey);\n};\n\ntype BlocksStore = {\n [K in SelectorBlockKey]: SelectorBlock;\n} & {\n [K in NonSelectorBlockKey]: NonSelectorBlock;\n};\n\ntype RichTextBlocksStore = Partial<BlocksStore> & Record<string, SelectorBlock | NonSelectorBlock>;\n\ninterface BlocksEditorContextValue {\n blocks: RichTextBlocksStore;\n modifiers: ModifiersStore;\n disabled: boolean;\n name: string;\n setLiveText: (text: string) => void;\n isExpandedMode: boolean;\n /** Push debounced Slate → form sync immediately (e.g. on Editable blur before Save). */\n flushPendingFormSync: () => void;\n}\n\nconst [BlocksEditorProvider, usePartialBlocksEditorContext] =\n createContext<BlocksEditorContextValue>('BlocksEditor');\n\nfunction useBlocksEditorContext(consumerName: string): BlocksEditorContextValue & {\n editor: Editor;\n} {\n const context = usePartialBlocksEditorContext(consumerName, (state) => state);\n const editor = useSlate();\n\n return {\n ...context,\n editor,\n };\n}\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksEditor\n * -----------------------------------------------------------------------------------------------*/\n\nconst EditorDivider = styled(Divider)`\n background: ${({ theme }) => theme.colors.neutral200};\n`;\n\n/**\n * Forces an update of the Slate editor when the value prop changes from outside of Slate.\n * The root cause is that Slate is not a controlled component: https://github.com/ianstormtaylor/slate/issues/4612\n * Why not use JSON.stringify(value) as the key?\n * Because it would force a rerender of the entire editor every time the user types a character.\n * Why not use the entity id as the key, since it's unique for each locale?\n * Because it would not solve the problem when using the \"fill in from other locale\" feature\n */\nfunction useResetKey(\n editor: Editor,\n value?: Schema.Attribute.BlocksValue\n): {\n key: number;\n incrementSlateUpdatesCount: () => void;\n} {\n // Keep track how many times Slate detected a change from a user interaction in the editor\n const slateUpdatesCount = React.useRef(0);\n // Keep track of how many times the value prop was updated, whether from within editor or from outside\n const valueUpdatesCount = React.useRef(0);\n // Use a key to force a rerender of the Slate editor when needed\n const [key, setKey] = React.useState(0);\n\n React.useEffect(() => {\n valueUpdatesCount.current += 1;\n\n // If the 2 refs are not equal, it means the value was updated from outside\n if (valueUpdatesCount.current !== slateUpdatesCount.current) {\n // Bring the 2 refs back in sync\n slateUpdatesCount.current = valueUpdatesCount.current;\n\n // The update may just be an echo of the editor's own content coming back through the form\n // (e.g. the debounced sync, or a re-render while document queries settle). Remounting in\n // that case is pointless and destructive: it wipes pending input and the DOM under the\n // user's caret. Only force a remount when the content is actually different.\n const externalState = value?.length ? normalizeBlocksState(editor, value) : null;\n const editorState = normalizeBlocksState(editor, editor.children);\n if (JSON.stringify(externalState) === JSON.stringify(editorState)) {\n return;\n }\n\n // The remount reuses the same editor instance, so a selection pointing into the old\n // content would survive it and crash slate-react's toDOMPoint when the new content is\n // shorter. Drop the selection before swapping the children.\n if (editor.selection) {\n Transforms.deselect(editor);\n }\n\n // Change the key to force a rerender of the Slate editor,\n // which will pick up the new value through its initialValue prop\n setKey((previousKey) => previousKey + 1);\n }\n }, [editor, value]);\n\n const incrementSlateUpdatesCount = React.useCallback(() => {\n slateUpdatesCount.current += 1;\n }, []);\n\n return { key, incrementSlateUpdatesCount };\n}\n\nconst pipe =\n (...fns: ((baseEditor: Editor) => Editor)[]) =>\n (value: Editor) =>\n fns.reduce<Editor>((prev, fn) => fn(prev), value);\n\n/**\n * Normalize the blocks state to null if the editor state is considered empty,\n * otherwise return the state\n */\nconst normalizeBlocksState = (\n editor: Editor,\n value: Schema.Attribute.BlocksValue | Descendant[]\n): Schema.Attribute.BlocksValue | Descendant[] | null => {\n const isEmpty =\n value.length === 1 && Editor.isEmpty(editor, value[0] as Schema.Attribute.BlocksNode);\n\n return isEmpty ? null : value;\n};\n\ninterface BlocksEditorProps\n extends Pick<FieldValue<Schema.Attribute.BlocksValue>, 'onChange' | 'value' | 'error'>,\n BlocksContentProps {\n disabled?: boolean;\n name: string;\n}\n\nconst BlocksEditor = React.forwardRef<{ focus: () => void }, BlocksEditorProps>(\n ({ disabled = false, name, onChange, value, error, ...contentProps }, forwardedRef) => {\n const { formatMessage } = useIntl();\n const isMobile = useIsMobile();\n\n const blocks = useStrapiApp(\n 'BlocksEditor',\n (state) =>\n (\n state.plugins['content-manager']?.apis as\n | ContentManagerPlugin['config']['apis']\n | undefined\n )?.getRichTextBlocks() ?? ({} as RichTextBlocksStore)\n );\n\n const blockRegisteredPlugins = Object.values(blocks)\n .map((block) => block.plugin)\n .filter(isNonNullable);\n\n const [editor] = React.useState(() =>\n pipe(withHistory, withStrapiSchema, withReact, ...blockRegisteredPlugins)(createEditor())\n );\n const [liveText, setLiveText] = React.useState('');\n const ariaDescriptionId = React.useId();\n const [isExpandedMode, handleToggleExpand] = React.useReducer((prev) => !prev, false);\n\n /**\n * Editable is not able to hold the ref, https://github.com/ianstormtaylor/slate/issues/4082\n * so with \"useImperativeHandle\" we can use ReactEditor methods to expose to the parent above\n * also not passing forwarded ref here, gives console warning.\n */\n React.useImperativeHandle(\n forwardedRef,\n () => ({\n focus() {\n ReactEditor.focus(editor);\n },\n }),\n [editor]\n );\n\n const { key, incrementSlateUpdatesCount } = useResetKey(editor, value);\n\n const debounceTimeout = React.useRef<NodeJS.Timeout | null>(null);\n\n const flushPendingFormSync = React.useCallback(() => {\n if (!debounceTimeout.current) {\n return;\n }\n clearTimeout(debounceTimeout.current);\n debounceTimeout.current = null;\n // Counter was already bumped when the AST changed; only push form state here.\n // Ensure Strapi Form state updates before the next event (e.g. Save click) reads values.\n flushSync(() => {\n onChange(\n name,\n normalizeBlocksState(editor, editor.children) as Schema.Attribute.BlocksValue\n );\n });\n }, [editor, name, onChange]);\n\n const handleSlateChange = React.useCallback(\n (state: Descendant[]) => {\n const isAstChange = editor.operations.some((op) => op.type !== 'set_selection');\n\n if (isAstChange) {\n /**\n * Slate handles the state of the editor internally. We just need to keep Strapi's form\n * state in sync with it in order to make sure that things like the \"modified\" state\n * isn't broken. Updating the whole state on every change is very expensive however,\n * so we debounce calls to onChange to mitigate input lag.\n *\n * Bump the reset-key counter immediately (not inside the debounce). Otherwise any\n * value-prop identity change during the 300ms window — e.g. clicking the blocks\n * toolbar — looks like an external update with stale empty form state and remounts\n * the editor, wiping the pending input (blocks e2e flake).\n */\n incrementSlateUpdatesCount();\n\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n\n // Set a new debounce timeout\n debounceTimeout.current = setTimeout(() => {\n // Normalize the state (empty editor becomes null)\n onChange(name, normalizeBlocksState(editor, state) as Schema.Attribute.BlocksValue);\n debounceTimeout.current = null;\n }, 300);\n }\n },\n [editor, incrementSlateUpdatesCount, name, onChange]\n );\n\n // Clean up the timeout on unmount\n React.useEffect(() => {\n return () => {\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n };\n }, []);\n\n // Ensure the editor is in sync after discard\n React.useEffect(() => {\n // Never deselect while the editor is actively focused (typing / editing),\n if (ReactEditor.isFocused(editor)) {\n return;\n }\n\n // Normalize empty states for comparison to avoid losing focus on the editor when content is deleted\n const normalizedValue = value?.length ? value : null;\n const normalizedEditorState = normalizeBlocksState(editor, editor.children);\n\n // Compare the field value with the editor state to check for a stale selection\n if (\n normalizedValue &&\n normalizedEditorState &&\n JSON.stringify(normalizedEditorState) !== JSON.stringify(normalizedValue)\n ) {\n // When there is a diff, unset selection to avoid an invalid state\n Transforms.deselect(editor);\n }\n }, [editor, value]);\n\n return (\n <>\n <VisuallyHidden id={ariaDescriptionId}>\n {formatMessage({\n id: getTranslation('components.Blocks.dnd.instruction'),\n defaultMessage: `To reorder blocks, press Command or Control along with Shift and the Up or Down arrow keys`,\n })}\n </VisuallyHidden>\n <VisuallyHidden aria-live=\"assertive\">{liveText}</VisuallyHidden>\n <Slate\n editor={editor}\n initialValue={\n value?.length ? value : [{ type: 'paragraph', children: [{ type: 'text', text: '' }] }]\n }\n onChange={handleSlateChange}\n key={key}\n >\n <BlocksEditorProvider\n blocks={blocks}\n modifiers={modifiers}\n disabled={disabled}\n name={name}\n setLiveText={setLiveText}\n isExpandedMode={isExpandedMode}\n flushPendingFormSync={flushPendingFormSync}\n >\n <EditorLayout\n error={error}\n disabled={disabled}\n onToggleExpand={handleToggleExpand}\n ariaDescriptionId={ariaDescriptionId}\n >\n <BlocksToolbar />\n <EditorDivider width=\"100%\" />\n <BlocksContent {...contentProps} />\n {!isExpandedMode && !isMobile && (\n <IconButton\n position=\"absolute\"\n bottom=\"1.2rem\"\n right=\"1.2rem\"\n shadow=\"filterShadow\"\n label={formatMessage({\n id: getTranslation('components.Blocks.expand'),\n defaultMessage: 'Expand',\n })}\n onClick={handleToggleExpand}\n >\n <Expand />\n </IconButton>\n )}\n </EditorLayout>\n </BlocksEditorProvider>\n </Slate>\n </>\n );\n }\n);\n\nexport {\n type BlocksStore,\n type RichTextBlocksStore,\n type SelectorBlockKey,\n BlocksEditor,\n BlocksEditorProvider,\n useBlocksEditorContext,\n isSelectorBlockKey,\n normalizeBlocksState,\n};\n"],"names":["BlocksEditorProvider","usePartialBlocksEditorContext","createContext","useBlocksEditorContext","consumerName","context","state","editor","useSlate","EditorDivider","styled","Divider","theme","colors","neutral200","useResetKey","value","slateUpdatesCount","React","useRef","valueUpdatesCount","key","setKey","useState","useEffect","current","externalState","length","normalizeBlocksState","editorState","children","JSON","stringify","selection","Transforms","deselect","previousKey","incrementSlateUpdatesCount","useCallback","pipe","fns","reduce","prev","fn","isEmpty","Editor","BlocksEditor","forwardRef","disabled","name","onChange","error","contentProps","forwardedRef","formatMessage","useIntl","isMobile","useIsMobile","blocks","useStrapiApp","plugins","apis","getRichTextBlocks","blockRegisteredPlugins","Object","values","map","block","plugin","filter","isNonNullable","withHistory","withStrapiSchema","withReact","createEditor","liveText","setLiveText","ariaDescriptionId","useId","isExpandedMode","handleToggleExpand","useReducer","useImperativeHandle","focus","ReactEditor","debounceTimeout","flushPendingFormSync","clearTimeout","flushSync","handleSlateChange","isAstChange","operations","some","op","type","setTimeout","isFocused","normalizedValue","normalizedEditorState","_jsxs","_Fragment","_jsx","VisuallyHidden","id","getTranslation","defaultMessage","aria-live","Slate","initialValue","text","modifiers","EditorLayout","onToggleExpand","BlocksToolbar","width","BlocksContent","IconButton","position","bottom","right","shadow","label","onClick","Expand"],"mappings":"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"}
|
|
@@ -49,14 +49,26 @@ function useBlocksEditorContext(consumerName) {
|
|
|
49
49
|
valueUpdatesCount.current += 1;
|
|
50
50
|
// If the 2 refs are not equal, it means the value was updated from outside
|
|
51
51
|
if (valueUpdatesCount.current !== slateUpdatesCount.current) {
|
|
52
|
+
// Bring the 2 refs back in sync
|
|
53
|
+
slateUpdatesCount.current = valueUpdatesCount.current;
|
|
54
|
+
// The update may just be an echo of the editor's own content coming back through the form
|
|
55
|
+
// (e.g. the debounced sync, or a re-render while document queries settle). Remounting in
|
|
56
|
+
// that case is pointless and destructive: it wipes pending input and the DOM under the
|
|
57
|
+
// user's caret. Only force a remount when the content is actually different.
|
|
58
|
+
const externalState = value?.length ? normalizeBlocksState(editor, value) : null;
|
|
59
|
+
const editorState = normalizeBlocksState(editor, editor.children);
|
|
60
|
+
if (JSON.stringify(externalState) === JSON.stringify(editorState)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// The remount reuses the same editor instance, so a selection pointing into the old
|
|
64
|
+
// content would survive it and crash slate-react's toDOMPoint when the new content is
|
|
65
|
+
// shorter. Drop the selection before swapping the children.
|
|
52
66
|
if (editor.selection) {
|
|
53
67
|
Transforms.deselect(editor);
|
|
54
68
|
}
|
|
55
|
-
//
|
|
69
|
+
// Change the key to force a rerender of the Slate editor,
|
|
56
70
|
// which will pick up the new value through its initialValue prop
|
|
57
71
|
setKey((previousKey)=>previousKey + 1);
|
|
58
|
-
// Then bring the 2 refs back in sync
|
|
59
|
-
slateUpdatesCount.current = valueUpdatesCount.current;
|
|
60
72
|
}
|
|
61
73
|
}, [
|
|
62
74
|
editor,
|
|
@@ -106,14 +118,13 @@ const BlocksEditor = /*#__PURE__*/ React.forwardRef(({ disabled = false, name, o
|
|
|
106
118
|
}
|
|
107
119
|
clearTimeout(debounceTimeout.current);
|
|
108
120
|
debounceTimeout.current = null;
|
|
109
|
-
|
|
121
|
+
// Counter was already bumped when the AST changed; only push form state here.
|
|
110
122
|
// Ensure Strapi Form state updates before the next event (e.g. Save click) reads values.
|
|
111
123
|
flushSync(()=>{
|
|
112
124
|
onChange(name, normalizeBlocksState(editor, editor.children));
|
|
113
125
|
});
|
|
114
126
|
}, [
|
|
115
127
|
editor,
|
|
116
|
-
incrementSlateUpdatesCount,
|
|
117
128
|
name,
|
|
118
129
|
onChange
|
|
119
130
|
]);
|
|
@@ -125,12 +136,17 @@ const BlocksEditor = /*#__PURE__*/ React.forwardRef(({ disabled = false, name, o
|
|
|
125
136
|
* state in sync with it in order to make sure that things like the "modified" state
|
|
126
137
|
* isn't broken. Updating the whole state on every change is very expensive however,
|
|
127
138
|
* so we debounce calls to onChange to mitigate input lag.
|
|
128
|
-
|
|
139
|
+
*
|
|
140
|
+
* Bump the reset-key counter immediately (not inside the debounce). Otherwise any
|
|
141
|
+
* value-prop identity change during the 300ms window — e.g. clicking the blocks
|
|
142
|
+
* toolbar — looks like an external update with stale empty form state and remounts
|
|
143
|
+
* the editor, wiping the pending input (blocks e2e flake).
|
|
144
|
+
*/ incrementSlateUpdatesCount();
|
|
145
|
+
if (debounceTimeout.current) {
|
|
129
146
|
clearTimeout(debounceTimeout.current);
|
|
130
147
|
}
|
|
131
148
|
// Set a new debounce timeout
|
|
132
149
|
debounceTimeout.current = setTimeout(()=>{
|
|
133
|
-
incrementSlateUpdatesCount();
|
|
134
150
|
// Normalize the state (empty editor becomes null)
|
|
135
151
|
onChange(name, normalizeBlocksState(editor, state));
|
|
136
152
|
debounceTimeout.current = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlocksEditor.mjs","sources":["../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksEditor.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n createContext,\n useStrapiApp,\n type FieldValue,\n useIsMobile,\n} from '@strapi/admin/strapi-admin';\nimport { IconButton, Divider, VisuallyHidden } from '@strapi/design-system';\nimport { Expand } from '@strapi/icons';\nimport { flushSync } from 'react-dom';\nimport { MessageDescriptor, useIntl } from 'react-intl';\nimport { Editor, type Descendant, createEditor, Transforms, Element } from 'slate';\nimport { withHistory } from 'slate-history';\nimport { type RenderElementProps, Slate, withReact, ReactEditor, useSlate } from 'slate-react';\nimport { styled, type CSSProperties } from 'styled-components';\n\nimport { ContentManagerPlugin } from '../../../../../content-manager';\nimport { getTranslation } from '../../../../../utils/translations';\n\nimport { BlocksContent, type BlocksContentProps } from './BlocksContent';\nimport { BlocksToolbar } from './BlocksToolbar';\nimport { EditorLayout } from './EditorLayout';\nimport { type ModifiersStore, modifiers } from './Modifiers';\nimport { withStrapiSchema } from './plugins/withStrapiSchema';\nimport { isNonNullable } from './utils/types';\n\nimport type { Schema } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksEditorProvider\n * -----------------------------------------------------------------------------------------------*/\n\ninterface CustomNode extends Omit<Schema.Attribute.BlocksNode, 'type'> {\n type: Schema.Attribute.BlocksNode['type'] | string;\n level?: number;\n format?: string;\n}\n\ninterface BaseBlock {\n renderElement: (props: RenderElementProps) => React.JSX.Element;\n /** Function to check if a given node is of this type of block */\n matchNode: (node: Schema.Attribute.BlocksNode | CustomNode) => boolean;\n handleConvert?: (editor: Editor) => void | (() => React.JSX.Element);\n handleEnterKey?: (editor: Editor) => void;\n handleBackspaceKey?: (editor: Editor, event: React.KeyboardEvent<HTMLElement>) => void;\n handleTab?: (editor: Editor) => void;\n handleShiftTab?: (editor: Editor) => void;\n snippets?: string[];\n /** Adjust the vertical positioning of the drag-to-reorder grip icon */\n dragHandleTopMargin?: CSSProperties['marginTop'];\n /** A Slate plugin: function that will wrap the editor creation */\n plugin?: (editor: Editor) => Editor;\n /**\n * Function that checks if an element should be draggable\n * @default () => true */\n isDraggable?: (element: Element) => boolean;\n}\n\nexport interface NonSelectorBlock extends BaseBlock {\n isInBlocksSelector?: false;\n}\n\nexport interface SelectorBlock extends BaseBlock {\n isInBlocksSelector: true;\n icon?: React.ComponentType;\n label: MessageDescriptor;\n}\n\ntype NonSelectorBlockKey = 'list-item' | 'link';\n\nconst selectorBlockKeys = [\n 'paragraph',\n 'heading-one',\n 'heading-two',\n 'heading-three',\n 'heading-four',\n 'heading-five',\n 'heading-six',\n 'list-ordered',\n 'list-unordered',\n 'image',\n 'quote',\n 'code',\n] as const;\n\ntype SelectorBlockKey = (typeof selectorBlockKeys)[number];\n\nconst isSelectorBlockKey = (key: unknown): key is SelectorBlockKey => {\n return typeof key === 'string' && selectorBlockKeys.includes(key as SelectorBlockKey);\n};\n\ntype BlocksStore = {\n [K in SelectorBlockKey]: SelectorBlock;\n} & {\n [K in NonSelectorBlockKey]: NonSelectorBlock;\n};\n\ntype RichTextBlocksStore = Partial<BlocksStore> & Record<string, SelectorBlock | NonSelectorBlock>;\n\ninterface BlocksEditorContextValue {\n blocks: RichTextBlocksStore;\n modifiers: ModifiersStore;\n disabled: boolean;\n name: string;\n setLiveText: (text: string) => void;\n isExpandedMode: boolean;\n /** Push debounced Slate → form sync immediately (e.g. on Editable blur before Save). */\n flushPendingFormSync: () => void;\n}\n\nconst [BlocksEditorProvider, usePartialBlocksEditorContext] =\n createContext<BlocksEditorContextValue>('BlocksEditor');\n\nfunction useBlocksEditorContext(consumerName: string): BlocksEditorContextValue & {\n editor: Editor;\n} {\n const context = usePartialBlocksEditorContext(consumerName, (state) => state);\n const editor = useSlate();\n\n return {\n ...context,\n editor,\n };\n}\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksEditor\n * -----------------------------------------------------------------------------------------------*/\n\nconst EditorDivider = styled(Divider)`\n background: ${({ theme }) => theme.colors.neutral200};\n`;\n\n/**\n * Forces an update of the Slate editor when the value prop changes from outside of Slate.\n * The root cause is that Slate is not a controlled component: https://github.com/ianstormtaylor/slate/issues/4612\n * Why not use JSON.stringify(value) as the key?\n * Because it would force a rerender of the entire editor every time the user types a character.\n * Why not use the entity id as the key, since it's unique for each locale?\n * Because it would not solve the problem when using the \"fill in from other locale\" feature\n */\nfunction useResetKey(\n editor: Editor,\n value?: Schema.Attribute.BlocksValue\n): {\n key: number;\n incrementSlateUpdatesCount: () => void;\n} {\n // Keep track how many times Slate detected a change from a user interaction in the editor\n const slateUpdatesCount = React.useRef(0);\n // Keep track of how many times the value prop was updated, whether from within editor or from outside\n const valueUpdatesCount = React.useRef(0);\n // Use a key to force a rerender of the Slate editor when needed\n const [key, setKey] = React.useState(0);\n\n React.useEffect(() => {\n valueUpdatesCount.current += 1;\n\n // If the 2 refs are not equal, it means the value was updated from outside\n if (valueUpdatesCount.current !== slateUpdatesCount.current) {\n if (editor.selection) {\n Transforms.deselect(editor);\n }\n\n // So we change the key to force a rerender of the Slate editor,\n // which will pick up the new value through its initialValue prop\n setKey((previousKey) => previousKey + 1);\n\n // Then bring the 2 refs back in sync\n slateUpdatesCount.current = valueUpdatesCount.current;\n }\n }, [editor, value]);\n\n const incrementSlateUpdatesCount = React.useCallback(() => {\n slateUpdatesCount.current += 1;\n }, []);\n\n return { key, incrementSlateUpdatesCount };\n}\n\nconst pipe =\n (...fns: ((baseEditor: Editor) => Editor)[]) =>\n (value: Editor) =>\n fns.reduce<Editor>((prev, fn) => fn(prev), value);\n\n/**\n * Normalize the blocks state to null if the editor state is considered empty,\n * otherwise return the state\n */\nconst normalizeBlocksState = (\n editor: Editor,\n value: Schema.Attribute.BlocksValue | Descendant[]\n): Schema.Attribute.BlocksValue | Descendant[] | null => {\n const isEmpty =\n value.length === 1 && Editor.isEmpty(editor, value[0] as Schema.Attribute.BlocksNode);\n\n return isEmpty ? null : value;\n};\n\ninterface BlocksEditorProps\n extends Pick<FieldValue<Schema.Attribute.BlocksValue>, 'onChange' | 'value' | 'error'>,\n BlocksContentProps {\n disabled?: boolean;\n name: string;\n}\n\nconst BlocksEditor = React.forwardRef<{ focus: () => void }, BlocksEditorProps>(\n ({ disabled = false, name, onChange, value, error, ...contentProps }, forwardedRef) => {\n const { formatMessage } = useIntl();\n const isMobile = useIsMobile();\n\n const blocks = useStrapiApp(\n 'BlocksEditor',\n (state) =>\n (\n state.plugins['content-manager']?.apis as\n | ContentManagerPlugin['config']['apis']\n | undefined\n )?.getRichTextBlocks() ?? ({} as RichTextBlocksStore)\n );\n\n const blockRegisteredPlugins = Object.values(blocks)\n .map((block) => block.plugin)\n .filter(isNonNullable);\n\n const [editor] = React.useState(() =>\n pipe(withHistory, withStrapiSchema, withReact, ...blockRegisteredPlugins)(createEditor())\n );\n const [liveText, setLiveText] = React.useState('');\n const ariaDescriptionId = React.useId();\n const [isExpandedMode, handleToggleExpand] = React.useReducer((prev) => !prev, false);\n\n /**\n * Editable is not able to hold the ref, https://github.com/ianstormtaylor/slate/issues/4082\n * so with \"useImperativeHandle\" we can use ReactEditor methods to expose to the parent above\n * also not passing forwarded ref here, gives console warning.\n */\n React.useImperativeHandle(\n forwardedRef,\n () => ({\n focus() {\n ReactEditor.focus(editor);\n },\n }),\n [editor]\n );\n\n const { key, incrementSlateUpdatesCount } = useResetKey(editor, value);\n\n const debounceTimeout = React.useRef<NodeJS.Timeout | null>(null);\n\n const flushPendingFormSync = React.useCallback(() => {\n if (!debounceTimeout.current) {\n return;\n }\n clearTimeout(debounceTimeout.current);\n debounceTimeout.current = null;\n incrementSlateUpdatesCount();\n // Ensure Strapi Form state updates before the next event (e.g. Save click) reads values.\n flushSync(() => {\n onChange(\n name,\n normalizeBlocksState(editor, editor.children) as Schema.Attribute.BlocksValue\n );\n });\n }, [editor, incrementSlateUpdatesCount, name, onChange]);\n\n const handleSlateChange = React.useCallback(\n (state: Descendant[]) => {\n const isAstChange = editor.operations.some((op) => op.type !== 'set_selection');\n\n if (isAstChange) {\n /**\n * Slate handles the state of the editor internally. We just need to keep Strapi's form\n * state in sync with it in order to make sure that things like the \"modified\" state\n * isn't broken. Updating the whole state on every change is very expensive however,\n * so we debounce calls to onChange to mitigate input lag.\n */\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n\n // Set a new debounce timeout\n debounceTimeout.current = setTimeout(() => {\n incrementSlateUpdatesCount();\n\n // Normalize the state (empty editor becomes null)\n onChange(name, normalizeBlocksState(editor, state) as Schema.Attribute.BlocksValue);\n debounceTimeout.current = null;\n }, 300);\n }\n },\n [editor, incrementSlateUpdatesCount, name, onChange]\n );\n\n // Clean up the timeout on unmount\n React.useEffect(() => {\n return () => {\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n };\n }, []);\n\n // Ensure the editor is in sync after discard\n React.useEffect(() => {\n // Never deselect while the editor is actively focused (typing / editing),\n if (ReactEditor.isFocused(editor)) {\n return;\n }\n\n // Normalize empty states for comparison to avoid losing focus on the editor when content is deleted\n const normalizedValue = value?.length ? value : null;\n const normalizedEditorState = normalizeBlocksState(editor, editor.children);\n\n // Compare the field value with the editor state to check for a stale selection\n if (\n normalizedValue &&\n normalizedEditorState &&\n JSON.stringify(normalizedEditorState) !== JSON.stringify(normalizedValue)\n ) {\n // When there is a diff, unset selection to avoid an invalid state\n Transforms.deselect(editor);\n }\n }, [editor, value]);\n\n return (\n <>\n <VisuallyHidden id={ariaDescriptionId}>\n {formatMessage({\n id: getTranslation('components.Blocks.dnd.instruction'),\n defaultMessage: `To reorder blocks, press Command or Control along with Shift and the Up or Down arrow keys`,\n })}\n </VisuallyHidden>\n <VisuallyHidden aria-live=\"assertive\">{liveText}</VisuallyHidden>\n <Slate\n editor={editor}\n initialValue={\n value?.length ? value : [{ type: 'paragraph', children: [{ type: 'text', text: '' }] }]\n }\n onChange={handleSlateChange}\n key={key}\n >\n <BlocksEditorProvider\n blocks={blocks}\n modifiers={modifiers}\n disabled={disabled}\n name={name}\n setLiveText={setLiveText}\n isExpandedMode={isExpandedMode}\n flushPendingFormSync={flushPendingFormSync}\n >\n <EditorLayout\n error={error}\n disabled={disabled}\n onToggleExpand={handleToggleExpand}\n ariaDescriptionId={ariaDescriptionId}\n >\n <BlocksToolbar />\n <EditorDivider width=\"100%\" />\n <BlocksContent {...contentProps} />\n {!isExpandedMode && !isMobile && (\n <IconButton\n position=\"absolute\"\n bottom=\"1.2rem\"\n right=\"1.2rem\"\n shadow=\"filterShadow\"\n label={formatMessage({\n id: getTranslation('components.Blocks.expand'),\n defaultMessage: 'Expand',\n })}\n onClick={handleToggleExpand}\n >\n <Expand />\n </IconButton>\n )}\n </EditorLayout>\n </BlocksEditorProvider>\n </Slate>\n </>\n );\n }\n);\n\nexport {\n type BlocksStore,\n type RichTextBlocksStore,\n type SelectorBlockKey,\n BlocksEditor,\n BlocksEditorProvider,\n useBlocksEditorContext,\n isSelectorBlockKey,\n normalizeBlocksState,\n};\n"],"names":["BlocksEditorProvider","usePartialBlocksEditorContext","createContext","useBlocksEditorContext","consumerName","context","state","editor","useSlate","EditorDivider","styled","Divider","theme","colors","neutral200","useResetKey","value","slateUpdatesCount","React","useRef","valueUpdatesCount","key","setKey","useState","useEffect","current","selection","Transforms","deselect","previousKey","incrementSlateUpdatesCount","useCallback","pipe","fns","reduce","prev","fn","normalizeBlocksState","isEmpty","length","Editor","BlocksEditor","forwardRef","disabled","name","onChange","error","contentProps","forwardedRef","formatMessage","useIntl","isMobile","useIsMobile","blocks","useStrapiApp","plugins","apis","getRichTextBlocks","blockRegisteredPlugins","Object","values","map","block","plugin","filter","isNonNullable","withHistory","withStrapiSchema","withReact","createEditor","liveText","setLiveText","ariaDescriptionId","useId","isExpandedMode","handleToggleExpand","useReducer","useImperativeHandle","focus","ReactEditor","debounceTimeout","flushPendingFormSync","clearTimeout","flushSync","children","handleSlateChange","isAstChange","operations","some","op","type","setTimeout","isFocused","normalizedValue","normalizedEditorState","JSON","stringify","_jsxs","_Fragment","_jsx","VisuallyHidden","id","getTranslation","defaultMessage","aria-live","Slate","initialValue","text","modifiers","EditorLayout","onToggleExpand","BlocksToolbar","width","BlocksContent","IconButton","position","bottom","right","shadow","label","onClick","Expand"],"mappings":";;;;;;;;;;;;;;;;;;;AA+GA,MAAM,CAACA,oBAAAA,EAAsBC,6BAAAA,CAA8B,GACzDC,aAAAA,CAAwC,cAAA;AAE1C,SAASC,uBAAuBC,YAAoB,EAAA;AAGlD,IAAA,MAAMC,OAAAA,GAAUJ,6BAAAA,CAA8BG,YAAAA,EAAc,CAACE,KAAAA,GAAUA,KAAAA,CAAAA;AACvE,IAAA,MAAMC,MAAAA,GAASC,QAAAA,EAAAA;IAEf,OAAO;AACL,QAAA,GAAGH,OAAO;AACVE,QAAAA;AACF,KAAA;AACF;AAEA;;AAEkG,qGAElG,MAAME,aAAAA,GAAgBC,MAAAA,CAAOC,OAAAA,CAAQ;cACvB,EAAE,CAAC,EAAEC,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAACC,UAAU,CAAC;AACvD,CAAC;AAED;;;;;;;AAOC,IACD,SAASC,WAAAA,CACPR,MAAc,EACdS,KAAoC,EAAA;;IAMpC,MAAMC,iBAAAA,GAAoBC,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAA;;IAEvC,MAAMC,iBAAAA,GAAoBF,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAA;;AAEvC,IAAA,MAAM,CAACE,GAAAA,EAAKC,MAAAA,CAAO,GAAGJ,KAAAA,CAAMK,QAAQ,CAAC,CAAA,CAAA;AAErCL,IAAAA,KAAAA,CAAMM,SAAS,CAAC,IAAA;AACdJ,QAAAA,iBAAAA,CAAkBK,OAAO,IAAI,CAAA;;AAG7B,QAAA,IAAIL,iBAAAA,CAAkBK,OAAO,KAAKR,iBAAAA,CAAkBQ,OAAO,EAAE;YAC3D,IAAIlB,MAAAA,CAAOmB,SAAS,EAAE;AACpBC,gBAAAA,UAAAA,CAAWC,QAAQ,CAACrB,MAAAA,CAAAA;AACtB,YAAA;;;YAIAe,MAAAA,CAAO,CAACO,cAAgBA,WAAAA,GAAc,CAAA,CAAA;;YAGtCZ,iBAAAA,CAAkBQ,OAAO,GAAGL,iBAAAA,CAAkBK,OAAO;AACvD,QAAA;IACF,CAAA,EAAG;AAAClB,QAAAA,MAAAA;AAAQS,QAAAA;AAAM,KAAA,CAAA;IAElB,MAAMc,0BAAAA,GAA6BZ,KAAAA,CAAMa,WAAW,CAAC,IAAA;AACnDd,QAAAA,iBAAAA,CAAkBQ,OAAO,IAAI,CAAA;AAC/B,IAAA,CAAA,EAAG,EAAE,CAAA;IAEL,OAAO;AAAEJ,QAAAA,GAAAA;AAAKS,QAAAA;AAA2B,KAAA;AAC3C;AAEA,MAAME,IAAAA,GACJ,CAAC,GAAGC,GAAAA,GACJ,CAACjB,KAAAA,GACCiB,GAAAA,CAAIC,MAAM,CAAS,CAACC,IAAAA,EAAMC,EAAAA,GAAOA,GAAGD,IAAAA,CAAAA,EAAOnB,KAAAA,CAAAA;AAE/C;;;IAIA,MAAMqB,oBAAAA,GAAuB,CAC3B9B,MAAAA,EACAS,KAAAA,GAAAA;IAEA,MAAMsB,OAAAA,GACJtB,KAAAA,CAAMuB,MAAM,KAAK,CAAA,IAAKC,MAAAA,CAAOF,OAAO,CAAC/B,MAAAA,EAAQS,KAAK,CAAC,CAAA,CAAE,CAAA;AAEvD,IAAA,OAAOsB,UAAU,IAAA,GAAOtB,KAAAA;AAC1B;AASA,MAAMyB,6BAAevB,KAAAA,CAAMwB,UAAU,CACnC,CAAC,EAAEC,WAAW,KAAK,EAAEC,IAAI,EAAEC,QAAQ,EAAE7B,KAAK,EAAE8B,KAAK,EAAE,GAAGC,cAAc,EAAEC,YAAAA,GAAAA;IACpE,MAAM,EAAEC,aAAa,EAAE,GAAGC,OAAAA,EAAAA;AAC1B,IAAA,MAAMC,QAAAA,GAAWC,WAAAA,EAAAA;AAEjB,IAAA,MAAMC,MAAAA,GAASC,YAAAA,CACb,cAAA,EACA,CAAChD,KAAAA,GAEGA,KAAAA,CAAMiD,OAAO,CAAC,iBAAA,CAAkB,EAAEC,IAAAA,EAGjCC,uBAAwB,EAAC,CAAA;AAGhC,IAAA,MAAMC,sBAAAA,GAAyBC,MAAAA,CAAOC,MAAM,CAACP,MAAAA,CAAAA,CAC1CQ,GAAG,CAAC,CAACC,KAAAA,GAAUA,KAAAA,CAAMC,MAAM,CAAA,CAC3BC,MAAM,CAACC,aAAAA,CAAAA;IAEV,MAAM,CAAC1D,MAAAA,CAAO,GAAGW,KAAAA,CAAMK,QAAQ,CAAC,IAC9BS,IAAAA,CAAKkC,WAAAA,EAAaC,gBAAAA,EAAkBC,SAAAA,EAAAA,GAAcV,sBAAAA,CAAAA,CAAwBW,YAAAA,EAAAA,CAAAA,CAAAA;AAE5E,IAAA,MAAM,CAACC,QAAAA,EAAUC,WAAAA,CAAY,GAAGrD,KAAAA,CAAMK,QAAQ,CAAC,EAAA,CAAA;IAC/C,MAAMiD,iBAAAA,GAAoBtD,MAAMuD,KAAK,EAAA;IACrC,MAAM,CAACC,cAAAA,EAAgBC,kBAAAA,CAAmB,GAAGzD,KAAAA,CAAM0D,UAAU,CAAC,CAACzC,IAAAA,GAAS,CAACA,IAAAA,EAAM,KAAA,CAAA;AAE/E;;;;AAIC,QACDjB,KAAAA,CAAM2D,mBAAmB,CACvB7B,YAAAA,EACA,KAAO;AACL8B,YAAAA,KAAAA,CAAAA,GAAAA;AACEC,gBAAAA,WAAAA,CAAYD,KAAK,CAACvE,MAAAA,CAAAA;AACpB,YAAA;AACF,SAAA,CAAA,EACA;AAACA,QAAAA;AAAO,KAAA,CAAA;AAGV,IAAA,MAAM,EAAEc,GAAG,EAAES,0BAA0B,EAAE,GAAGf,YAAYR,MAAAA,EAAQS,KAAAA,CAAAA;IAEhE,MAAMgE,eAAAA,GAAkB9D,KAAAA,CAAMC,MAAM,CAAwB,IAAA,CAAA;IAE5D,MAAM8D,oBAAAA,GAAuB/D,KAAAA,CAAMa,WAAW,CAAC,IAAA;QAC7C,IAAI,CAACiD,eAAAA,CAAgBvD,OAAO,EAAE;AAC5B,YAAA;AACF,QAAA;AACAyD,QAAAA,YAAAA,CAAaF,gBAAgBvD,OAAO,CAAA;AACpCuD,QAAAA,eAAAA,CAAgBvD,OAAO,GAAG,IAAA;AAC1BK,QAAAA,0BAAAA,EAAAA;;QAEAqD,SAAAA,CAAU,IAAA;AACRtC,YAAAA,QAAAA,CACED,IAAAA,EACAP,oBAAAA,CAAqB9B,MAAAA,EAAQA,MAAAA,CAAO6E,QAAQ,CAAA,CAAA;AAEhD,QAAA,CAAA,CAAA;IACF,CAAA,EAAG;AAAC7E,QAAAA,MAAAA;AAAQuB,QAAAA,0BAAAA;AAA4Bc,QAAAA,IAAAA;AAAMC,QAAAA;AAAS,KAAA,CAAA;AAEvD,IAAA,MAAMwC,iBAAAA,GAAoBnE,KAAAA,CAAMa,WAAW,CACzC,CAACzB,KAAAA,GAAAA;QACC,MAAMgF,WAAAA,GAAc/E,MAAAA,CAAOgF,UAAU,CAACC,IAAI,CAAC,CAACC,EAAAA,GAAOA,EAAAA,CAAGC,IAAI,KAAK,eAAA,CAAA;AAE/D,QAAA,IAAIJ,WAAAA,EAAa;AACf;;;;;cAMA,IAAIN,eAAAA,CAAgBvD,OAAO,EAAE;AAC3ByD,gBAAAA,YAAAA,CAAaF,gBAAgBvD,OAAO,CAAA;AACtC,YAAA;;YAGAuD,eAAAA,CAAgBvD,OAAO,GAAGkE,UAAAA,CAAW,IAAA;AACnC7D,gBAAAA,0BAAAA,EAAAA;;gBAGAe,QAAAA,CAASD,IAAAA,EAAMP,qBAAqB9B,MAAAA,EAAQD,KAAAA,CAAAA,CAAAA;AAC5C0E,gBAAAA,eAAAA,CAAgBvD,OAAO,GAAG,IAAA;YAC5B,CAAA,EAAG,GAAA,CAAA;AACL,QAAA;IACF,CAAA,EACA;AAAClB,QAAAA,MAAAA;AAAQuB,QAAAA,0BAAAA;AAA4Bc,QAAAA,IAAAA;AAAMC,QAAAA;AAAS,KAAA,CAAA;;AAItD3B,IAAAA,KAAAA,CAAMM,SAAS,CAAC,IAAA;QACd,OAAO,IAAA;YACL,IAAIwD,eAAAA,CAAgBvD,OAAO,EAAE;AAC3ByD,gBAAAA,YAAAA,CAAaF,gBAAgBvD,OAAO,CAAA;AACtC,YAAA;AACF,QAAA,CAAA;AACF,IAAA,CAAA,EAAG,EAAE,CAAA;;AAGLP,IAAAA,KAAAA,CAAMM,SAAS,CAAC,IAAA;;QAEd,IAAIuD,WAAAA,CAAYa,SAAS,CAACrF,MAAAA,CAAAA,EAAS;AACjC,YAAA;AACF,QAAA;;QAGA,MAAMsF,eAAAA,GAAkB7E,KAAAA,EAAOuB,MAAAA,GAASvB,KAAAA,GAAQ,IAAA;AAChD,QAAA,MAAM8E,qBAAAA,GAAwBzD,oBAAAA,CAAqB9B,MAAAA,EAAQA,MAAAA,CAAO6E,QAAQ,CAAA;;QAG1E,IACES,eAAAA,IACAC,yBACAC,IAAAA,CAAKC,SAAS,CAACF,qBAAAA,CAAAA,KAA2BC,IAAAA,CAAKC,SAAS,CAACH,eAAAA,CAAAA,EACzD;;AAEAlE,YAAAA,UAAAA,CAAWC,QAAQ,CAACrB,MAAAA,CAAAA;AACtB,QAAA;IACF,CAAA,EAAG;AAACA,QAAAA,MAAAA;AAAQS,QAAAA;AAAM,KAAA,CAAA;IAElB,qBACEiF,IAAA,CAAAC,QAAA,EAAA;;0BACEC,GAAA,CAACC,cAAAA,EAAAA;gBAAeC,EAAAA,EAAI7B,iBAAAA;0BACjBvB,aAAAA,CAAc;AACboD,oBAAAA,EAAAA,EAAIC,cAAAA,CAAe,mCAAA,CAAA;oBACnBC,cAAAA,EAAgB,CAAC,0FAA0F;AAC7G,iBAAA;;0BAEFJ,GAAA,CAACC,cAAAA,EAAAA;gBAAeI,WAAAA,EAAU,WAAA;AAAalC,gBAAAA,QAAAA,EAAAA;;0BACvC6B,GAAA,CAACM,KAAAA,EAAAA;gBACClG,MAAAA,EAAQA,MAAAA;gBACRmG,YAAAA,EACE1F,KAAAA,EAAOuB,SAASvB,KAAAA,GAAQ;AAAC,oBAAA;wBAAE0E,IAAAA,EAAM,WAAA;wBAAaN,QAAAA,EAAU;AAAC,4BAAA;gCAAEM,IAAAA,EAAM,MAAA;gCAAQiB,IAAAA,EAAM;AAAG;AAAE;AAAC;AAAE,iBAAA;gBAEzF9D,QAAAA,EAAUwC,iBAAAA;AAGV,gBAAA,QAAA,gBAAAc,GAAA,CAACnG,oBAAAA,EAAAA;oBACCqD,MAAAA,EAAQA,MAAAA;oBACRuD,SAAAA,EAAWA,SAAAA;oBACXjE,QAAAA,EAAUA,QAAAA;oBACVC,IAAAA,EAAMA,IAAAA;oBACN2B,WAAAA,EAAaA,WAAAA;oBACbG,cAAAA,EAAgBA,cAAAA;oBAChBO,oBAAAA,EAAsBA,oBAAAA;AAEtB,oBAAA,QAAA,gBAAAgB,IAAA,CAACY,YAAAA,EAAAA;wBACC/D,KAAAA,EAAOA,KAAAA;wBACPH,QAAAA,EAAUA,QAAAA;wBACVmE,cAAAA,EAAgBnC,kBAAAA;wBAChBH,iBAAAA,EAAmBA,iBAAAA;;0CAEnB2B,GAAA,CAACY,aAAAA,EAAAA,EAAAA,CAAAA;0CACDZ,GAAA,CAAC1F,aAAAA,EAAAA;gCAAcuG,KAAAA,EAAM;;0CACrBb,GAAA,CAACc,aAAAA,EAAAA;AAAe,gCAAA,GAAGlE;;4BAClB,CAAC2B,cAAAA,IAAkB,CAACvB,QAAAA,kBACnBgD,GAAA,CAACe,UAAAA,EAAAA;gCACCC,QAAAA,EAAS,UAAA;gCACTC,MAAAA,EAAO,QAAA;gCACPC,KAAAA,EAAM,QAAA;gCACNC,MAAAA,EAAO,cAAA;AACPC,gCAAAA,KAAAA,EAAOtE,aAAAA,CAAc;AACnBoD,oCAAAA,EAAAA,EAAIC,cAAAA,CAAe,0BAAA,CAAA;oCACnBC,cAAAA,EAAgB;AAClB,iCAAA,CAAA;gCACAiB,OAAAA,EAAS7C,kBAAAA;AAET,gCAAA,QAAA,gBAAAwB,GAAA,CAACsB,MAAAA,EAAAA,EAAAA;;;;;AAhCJpG,aAAAA,EAAAA,GAAAA;;;AAwCb,CAAA;;;;"}
|
|
1
|
+
{"version":3,"file":"BlocksEditor.mjs","sources":["../../../../../../../admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksEditor.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n createContext,\n useStrapiApp,\n type FieldValue,\n useIsMobile,\n} from '@strapi/admin/strapi-admin';\nimport { IconButton, Divider, VisuallyHidden } from '@strapi/design-system';\nimport { Expand } from '@strapi/icons';\nimport { flushSync } from 'react-dom';\nimport { MessageDescriptor, useIntl } from 'react-intl';\nimport { Editor, type Descendant, createEditor, Transforms, Element } from 'slate';\nimport { withHistory } from 'slate-history';\nimport { type RenderElementProps, Slate, withReact, ReactEditor, useSlate } from 'slate-react';\nimport { styled, type CSSProperties } from 'styled-components';\n\nimport { ContentManagerPlugin } from '../../../../../content-manager';\nimport { getTranslation } from '../../../../../utils/translations';\n\nimport { BlocksContent, type BlocksContentProps } from './BlocksContent';\nimport { BlocksToolbar } from './BlocksToolbar';\nimport { EditorLayout } from './EditorLayout';\nimport { type ModifiersStore, modifiers } from './Modifiers';\nimport { withStrapiSchema } from './plugins/withStrapiSchema';\nimport { isNonNullable } from './utils/types';\n\nimport type { Schema } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksEditorProvider\n * -----------------------------------------------------------------------------------------------*/\n\ninterface CustomNode extends Omit<Schema.Attribute.BlocksNode, 'type'> {\n type: Schema.Attribute.BlocksNode['type'] | string;\n level?: number;\n format?: string;\n}\n\ninterface BaseBlock {\n renderElement: (props: RenderElementProps) => React.JSX.Element;\n /** Function to check if a given node is of this type of block */\n matchNode: (node: Schema.Attribute.BlocksNode | CustomNode) => boolean;\n handleConvert?: (editor: Editor) => void | (() => React.JSX.Element);\n handleEnterKey?: (editor: Editor) => void;\n handleBackspaceKey?: (editor: Editor, event: React.KeyboardEvent<HTMLElement>) => void;\n handleTab?: (editor: Editor) => void;\n handleShiftTab?: (editor: Editor) => void;\n snippets?: string[];\n /** Adjust the vertical positioning of the drag-to-reorder grip icon */\n dragHandleTopMargin?: CSSProperties['marginTop'];\n /** A Slate plugin: function that will wrap the editor creation */\n plugin?: (editor: Editor) => Editor;\n /**\n * Function that checks if an element should be draggable\n * @default () => true */\n isDraggable?: (element: Element) => boolean;\n}\n\nexport interface NonSelectorBlock extends BaseBlock {\n isInBlocksSelector?: false;\n}\n\nexport interface SelectorBlock extends BaseBlock {\n isInBlocksSelector: true;\n icon?: React.ComponentType;\n label: MessageDescriptor;\n}\n\ntype NonSelectorBlockKey = 'list-item' | 'link';\n\nconst selectorBlockKeys = [\n 'paragraph',\n 'heading-one',\n 'heading-two',\n 'heading-three',\n 'heading-four',\n 'heading-five',\n 'heading-six',\n 'list-ordered',\n 'list-unordered',\n 'image',\n 'quote',\n 'code',\n] as const;\n\ntype SelectorBlockKey = (typeof selectorBlockKeys)[number];\n\nconst isSelectorBlockKey = (key: unknown): key is SelectorBlockKey => {\n return typeof key === 'string' && selectorBlockKeys.includes(key as SelectorBlockKey);\n};\n\ntype BlocksStore = {\n [K in SelectorBlockKey]: SelectorBlock;\n} & {\n [K in NonSelectorBlockKey]: NonSelectorBlock;\n};\n\ntype RichTextBlocksStore = Partial<BlocksStore> & Record<string, SelectorBlock | NonSelectorBlock>;\n\ninterface BlocksEditorContextValue {\n blocks: RichTextBlocksStore;\n modifiers: ModifiersStore;\n disabled: boolean;\n name: string;\n setLiveText: (text: string) => void;\n isExpandedMode: boolean;\n /** Push debounced Slate → form sync immediately (e.g. on Editable blur before Save). */\n flushPendingFormSync: () => void;\n}\n\nconst [BlocksEditorProvider, usePartialBlocksEditorContext] =\n createContext<BlocksEditorContextValue>('BlocksEditor');\n\nfunction useBlocksEditorContext(consumerName: string): BlocksEditorContextValue & {\n editor: Editor;\n} {\n const context = usePartialBlocksEditorContext(consumerName, (state) => state);\n const editor = useSlate();\n\n return {\n ...context,\n editor,\n };\n}\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksEditor\n * -----------------------------------------------------------------------------------------------*/\n\nconst EditorDivider = styled(Divider)`\n background: ${({ theme }) => theme.colors.neutral200};\n`;\n\n/**\n * Forces an update of the Slate editor when the value prop changes from outside of Slate.\n * The root cause is that Slate is not a controlled component: https://github.com/ianstormtaylor/slate/issues/4612\n * Why not use JSON.stringify(value) as the key?\n * Because it would force a rerender of the entire editor every time the user types a character.\n * Why not use the entity id as the key, since it's unique for each locale?\n * Because it would not solve the problem when using the \"fill in from other locale\" feature\n */\nfunction useResetKey(\n editor: Editor,\n value?: Schema.Attribute.BlocksValue\n): {\n key: number;\n incrementSlateUpdatesCount: () => void;\n} {\n // Keep track how many times Slate detected a change from a user interaction in the editor\n const slateUpdatesCount = React.useRef(0);\n // Keep track of how many times the value prop was updated, whether from within editor or from outside\n const valueUpdatesCount = React.useRef(0);\n // Use a key to force a rerender of the Slate editor when needed\n const [key, setKey] = React.useState(0);\n\n React.useEffect(() => {\n valueUpdatesCount.current += 1;\n\n // If the 2 refs are not equal, it means the value was updated from outside\n if (valueUpdatesCount.current !== slateUpdatesCount.current) {\n // Bring the 2 refs back in sync\n slateUpdatesCount.current = valueUpdatesCount.current;\n\n // The update may just be an echo of the editor's own content coming back through the form\n // (e.g. the debounced sync, or a re-render while document queries settle). Remounting in\n // that case is pointless and destructive: it wipes pending input and the DOM under the\n // user's caret. Only force a remount when the content is actually different.\n const externalState = value?.length ? normalizeBlocksState(editor, value) : null;\n const editorState = normalizeBlocksState(editor, editor.children);\n if (JSON.stringify(externalState) === JSON.stringify(editorState)) {\n return;\n }\n\n // The remount reuses the same editor instance, so a selection pointing into the old\n // content would survive it and crash slate-react's toDOMPoint when the new content is\n // shorter. Drop the selection before swapping the children.\n if (editor.selection) {\n Transforms.deselect(editor);\n }\n\n // Change the key to force a rerender of the Slate editor,\n // which will pick up the new value through its initialValue prop\n setKey((previousKey) => previousKey + 1);\n }\n }, [editor, value]);\n\n const incrementSlateUpdatesCount = React.useCallback(() => {\n slateUpdatesCount.current += 1;\n }, []);\n\n return { key, incrementSlateUpdatesCount };\n}\n\nconst pipe =\n (...fns: ((baseEditor: Editor) => Editor)[]) =>\n (value: Editor) =>\n fns.reduce<Editor>((prev, fn) => fn(prev), value);\n\n/**\n * Normalize the blocks state to null if the editor state is considered empty,\n * otherwise return the state\n */\nconst normalizeBlocksState = (\n editor: Editor,\n value: Schema.Attribute.BlocksValue | Descendant[]\n): Schema.Attribute.BlocksValue | Descendant[] | null => {\n const isEmpty =\n value.length === 1 && Editor.isEmpty(editor, value[0] as Schema.Attribute.BlocksNode);\n\n return isEmpty ? null : value;\n};\n\ninterface BlocksEditorProps\n extends Pick<FieldValue<Schema.Attribute.BlocksValue>, 'onChange' | 'value' | 'error'>,\n BlocksContentProps {\n disabled?: boolean;\n name: string;\n}\n\nconst BlocksEditor = React.forwardRef<{ focus: () => void }, BlocksEditorProps>(\n ({ disabled = false, name, onChange, value, error, ...contentProps }, forwardedRef) => {\n const { formatMessage } = useIntl();\n const isMobile = useIsMobile();\n\n const blocks = useStrapiApp(\n 'BlocksEditor',\n (state) =>\n (\n state.plugins['content-manager']?.apis as\n | ContentManagerPlugin['config']['apis']\n | undefined\n )?.getRichTextBlocks() ?? ({} as RichTextBlocksStore)\n );\n\n const blockRegisteredPlugins = Object.values(blocks)\n .map((block) => block.plugin)\n .filter(isNonNullable);\n\n const [editor] = React.useState(() =>\n pipe(withHistory, withStrapiSchema, withReact, ...blockRegisteredPlugins)(createEditor())\n );\n const [liveText, setLiveText] = React.useState('');\n const ariaDescriptionId = React.useId();\n const [isExpandedMode, handleToggleExpand] = React.useReducer((prev) => !prev, false);\n\n /**\n * Editable is not able to hold the ref, https://github.com/ianstormtaylor/slate/issues/4082\n * so with \"useImperativeHandle\" we can use ReactEditor methods to expose to the parent above\n * also not passing forwarded ref here, gives console warning.\n */\n React.useImperativeHandle(\n forwardedRef,\n () => ({\n focus() {\n ReactEditor.focus(editor);\n },\n }),\n [editor]\n );\n\n const { key, incrementSlateUpdatesCount } = useResetKey(editor, value);\n\n const debounceTimeout = React.useRef<NodeJS.Timeout | null>(null);\n\n const flushPendingFormSync = React.useCallback(() => {\n if (!debounceTimeout.current) {\n return;\n }\n clearTimeout(debounceTimeout.current);\n debounceTimeout.current = null;\n // Counter was already bumped when the AST changed; only push form state here.\n // Ensure Strapi Form state updates before the next event (e.g. Save click) reads values.\n flushSync(() => {\n onChange(\n name,\n normalizeBlocksState(editor, editor.children) as Schema.Attribute.BlocksValue\n );\n });\n }, [editor, name, onChange]);\n\n const handleSlateChange = React.useCallback(\n (state: Descendant[]) => {\n const isAstChange = editor.operations.some((op) => op.type !== 'set_selection');\n\n if (isAstChange) {\n /**\n * Slate handles the state of the editor internally. We just need to keep Strapi's form\n * state in sync with it in order to make sure that things like the \"modified\" state\n * isn't broken. Updating the whole state on every change is very expensive however,\n * so we debounce calls to onChange to mitigate input lag.\n *\n * Bump the reset-key counter immediately (not inside the debounce). Otherwise any\n * value-prop identity change during the 300ms window — e.g. clicking the blocks\n * toolbar — looks like an external update with stale empty form state and remounts\n * the editor, wiping the pending input (blocks e2e flake).\n */\n incrementSlateUpdatesCount();\n\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n\n // Set a new debounce timeout\n debounceTimeout.current = setTimeout(() => {\n // Normalize the state (empty editor becomes null)\n onChange(name, normalizeBlocksState(editor, state) as Schema.Attribute.BlocksValue);\n debounceTimeout.current = null;\n }, 300);\n }\n },\n [editor, incrementSlateUpdatesCount, name, onChange]\n );\n\n // Clean up the timeout on unmount\n React.useEffect(() => {\n return () => {\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n };\n }, []);\n\n // Ensure the editor is in sync after discard\n React.useEffect(() => {\n // Never deselect while the editor is actively focused (typing / editing),\n if (ReactEditor.isFocused(editor)) {\n return;\n }\n\n // Normalize empty states for comparison to avoid losing focus on the editor when content is deleted\n const normalizedValue = value?.length ? value : null;\n const normalizedEditorState = normalizeBlocksState(editor, editor.children);\n\n // Compare the field value with the editor state to check for a stale selection\n if (\n normalizedValue &&\n normalizedEditorState &&\n JSON.stringify(normalizedEditorState) !== JSON.stringify(normalizedValue)\n ) {\n // When there is a diff, unset selection to avoid an invalid state\n Transforms.deselect(editor);\n }\n }, [editor, value]);\n\n return (\n <>\n <VisuallyHidden id={ariaDescriptionId}>\n {formatMessage({\n id: getTranslation('components.Blocks.dnd.instruction'),\n defaultMessage: `To reorder blocks, press Command or Control along with Shift and the Up or Down arrow keys`,\n })}\n </VisuallyHidden>\n <VisuallyHidden aria-live=\"assertive\">{liveText}</VisuallyHidden>\n <Slate\n editor={editor}\n initialValue={\n value?.length ? value : [{ type: 'paragraph', children: [{ type: 'text', text: '' }] }]\n }\n onChange={handleSlateChange}\n key={key}\n >\n <BlocksEditorProvider\n blocks={blocks}\n modifiers={modifiers}\n disabled={disabled}\n name={name}\n setLiveText={setLiveText}\n isExpandedMode={isExpandedMode}\n flushPendingFormSync={flushPendingFormSync}\n >\n <EditorLayout\n error={error}\n disabled={disabled}\n onToggleExpand={handleToggleExpand}\n ariaDescriptionId={ariaDescriptionId}\n >\n <BlocksToolbar />\n <EditorDivider width=\"100%\" />\n <BlocksContent {...contentProps} />\n {!isExpandedMode && !isMobile && (\n <IconButton\n position=\"absolute\"\n bottom=\"1.2rem\"\n right=\"1.2rem\"\n shadow=\"filterShadow\"\n label={formatMessage({\n id: getTranslation('components.Blocks.expand'),\n defaultMessage: 'Expand',\n })}\n onClick={handleToggleExpand}\n >\n <Expand />\n </IconButton>\n )}\n </EditorLayout>\n </BlocksEditorProvider>\n </Slate>\n </>\n );\n }\n);\n\nexport {\n type BlocksStore,\n type RichTextBlocksStore,\n type SelectorBlockKey,\n BlocksEditor,\n BlocksEditorProvider,\n useBlocksEditorContext,\n isSelectorBlockKey,\n normalizeBlocksState,\n};\n"],"names":["BlocksEditorProvider","usePartialBlocksEditorContext","createContext","useBlocksEditorContext","consumerName","context","state","editor","useSlate","EditorDivider","styled","Divider","theme","colors","neutral200","useResetKey","value","slateUpdatesCount","React","useRef","valueUpdatesCount","key","setKey","useState","useEffect","current","externalState","length","normalizeBlocksState","editorState","children","JSON","stringify","selection","Transforms","deselect","previousKey","incrementSlateUpdatesCount","useCallback","pipe","fns","reduce","prev","fn","isEmpty","Editor","BlocksEditor","forwardRef","disabled","name","onChange","error","contentProps","forwardedRef","formatMessage","useIntl","isMobile","useIsMobile","blocks","useStrapiApp","plugins","apis","getRichTextBlocks","blockRegisteredPlugins","Object","values","map","block","plugin","filter","isNonNullable","withHistory","withStrapiSchema","withReact","createEditor","liveText","setLiveText","ariaDescriptionId","useId","isExpandedMode","handleToggleExpand","useReducer","useImperativeHandle","focus","ReactEditor","debounceTimeout","flushPendingFormSync","clearTimeout","flushSync","handleSlateChange","isAstChange","operations","some","op","type","setTimeout","isFocused","normalizedValue","normalizedEditorState","_jsxs","_Fragment","_jsx","VisuallyHidden","id","getTranslation","defaultMessage","aria-live","Slate","initialValue","text","modifiers","EditorLayout","onToggleExpand","BlocksToolbar","width","BlocksContent","IconButton","position","bottom","right","shadow","label","onClick","Expand"],"mappings":"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"}
|
|
@@ -185,19 +185,11 @@ const BlocksDropdown = ()=>{
|
|
|
185
185
|
}
|
|
186
186
|
const editorIsEmpty = editor.children.length === 1 && slate.Editor.isEmpty(editor, editor.children[0]);
|
|
187
187
|
if (!editor.selection && !editorIsEmpty) {
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
{
|
|
194
|
-
type: 'text',
|
|
195
|
-
text: ''
|
|
196
|
-
}
|
|
197
|
-
]
|
|
198
|
-
}, {
|
|
199
|
-
select: true
|
|
200
|
-
});
|
|
188
|
+
// Toolbar buttons steal focus in Playwright (and sometimes in real browsers), clearing
|
|
189
|
+
// selection. Convert the last existing block — do not insert a new empty block, which
|
|
190
|
+
// would leave the user's content behind and convert a blank node instead.
|
|
191
|
+
const [, lastPath] = slate.Editor.last(editor, []);
|
|
192
|
+
slate.Transforms.select(editor, slate.Editor.range(editor, lastPath));
|
|
201
193
|
} else if (!editor.selection && editorIsEmpty) {
|
|
202
194
|
// When there is no selection and the editor is empty,
|
|
203
195
|
// select the empty paragraph from Slate's initialValue so it gets converted
|