chore(richtext-lexical): enable strict: true (#9394)
Thanks to @GermanJablo for doing most of the work by enabling `noImplicitAny` and `strictNullChecks` in previous PRs
This commit is contained in:
@@ -162,7 +162,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
const { i18n, t } = useTranslation<object, string>()
|
||||
|
||||
const onChange = useCallback(
|
||||
async ({ formState: prevFormState, submit }: { formState: FormState; submit: boolean }) => {
|
||||
async ({ formState: prevFormState, submit }: { formState: FormState; submit?: boolean }) => {
|
||||
abortAndIgnore(onChangeAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
|
||||
@@ -9,7 +9,9 @@ import { recursivelyPopulateFieldsForGraphQL } from '../../../populateGraphQL/re
|
||||
export const blockPopulationPromiseHOC = (
|
||||
blocks: Block[],
|
||||
): PopulationPromise<SerializedBlockNode | SerializedInlineBlockNode> => {
|
||||
const blockPopulationPromise: PopulationPromise<SerializedBlockNode> = ({
|
||||
const blockPopulationPromise: PopulationPromise<
|
||||
SerializedBlockNode | SerializedInlineBlockNode
|
||||
> = ({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { LinebreakHTMLConverter } from './converters/linebreak.js'
|
||||
import { ParagraphHTMLConverter } from './converters/paragraph.js'
|
||||
import { TextHTMLConverter } from './converters/text.js'
|
||||
|
||||
export const defaultHTMLConverters: HTMLConverter[] = [
|
||||
export const defaultHTMLConverters: HTMLConverter<any>[] = [
|
||||
ParagraphHTMLConverter,
|
||||
TextHTMLConverter,
|
||||
LinebreakHTMLConverter,
|
||||
|
||||
@@ -314,7 +314,19 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
[activeCell, mouseUpHandler],
|
||||
)
|
||||
|
||||
const getResizers = useCallback(() => {
|
||||
const [resizerStyles, setResizerStyles] = useState<{
|
||||
bottom?: null | React.CSSProperties
|
||||
left?: null | React.CSSProperties
|
||||
right?: null | React.CSSProperties
|
||||
top?: null | React.CSSProperties
|
||||
}>({
|
||||
bottom: null,
|
||||
left: null,
|
||||
right: null,
|
||||
top: null,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (activeCell) {
|
||||
const { height, left, top, width } = activeCell.elem.getBoundingClientRect()
|
||||
const zoom = calculateZoomLevel(activeCell.elem)
|
||||
@@ -324,16 +336,16 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
backgroundColor: 'none',
|
||||
cursor: 'row-resize',
|
||||
height: `${zoneWidth}px`,
|
||||
left: `${window.pageXOffset + left}px`,
|
||||
top: `${window.pageYOffset + top + height - zoneWidth / 2}px`,
|
||||
left: `${window.scrollX + left}px`,
|
||||
top: `${window.scrollY + top + height - zoneWidth / 2}px`,
|
||||
width: `${width}px`,
|
||||
},
|
||||
right: {
|
||||
backgroundColor: 'none',
|
||||
cursor: 'col-resize',
|
||||
height: `${height}px`,
|
||||
left: `${window.pageXOffset + left + width - zoneWidth / 2}px`,
|
||||
top: `${window.pageYOffset + top}px`,
|
||||
left: `${window.scrollX + left + width - zoneWidth / 2}px`,
|
||||
top: `${window.scrollY + top}px`,
|
||||
width: `${zoneWidth}px`,
|
||||
},
|
||||
}
|
||||
@@ -342,13 +354,13 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
|
||||
if (draggingDirection && mouseCurrentPos && tableRect) {
|
||||
if (isHeightChanging(draggingDirection)) {
|
||||
styles[draggingDirection].left = `${window.pageXOffset + tableRect.left}px`
|
||||
styles[draggingDirection].top = `${window.pageYOffset + mouseCurrentPos.y / zoom}px`
|
||||
styles[draggingDirection].left = `${window.scrollX + tableRect.left}px`
|
||||
styles[draggingDirection].top = `${window.scrollY + mouseCurrentPos.y / zoom}px`
|
||||
styles[draggingDirection].height = '3px'
|
||||
styles[draggingDirection].width = `${tableRect.width}px`
|
||||
} else {
|
||||
styles[draggingDirection].top = `${window.pageYOffset + tableRect.top}px`
|
||||
styles[draggingDirection].left = `${window.pageXOffset + mouseCurrentPos.x / zoom}px`
|
||||
styles[draggingDirection].top = `${window.scrollY + tableRect.top}px`
|
||||
styles[draggingDirection].left = `${window.scrollX + mouseCurrentPos.x / zoom}px`
|
||||
styles[draggingDirection].width = '3px'
|
||||
styles[draggingDirection].height = `${tableRect.height}px`
|
||||
}
|
||||
@@ -356,19 +368,17 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
|
||||
styles[draggingDirection].backgroundColor = '#adf'
|
||||
}
|
||||
|
||||
return styles
|
||||
}
|
||||
|
||||
return {
|
||||
setResizerStyles(styles)
|
||||
} else {
|
||||
setResizerStyles({
|
||||
bottom: null,
|
||||
left: null,
|
||||
right: null,
|
||||
top: null,
|
||||
})
|
||||
}
|
||||
}, [activeCell, draggingDirection, mouseCurrentPos])
|
||||
|
||||
const resizerStyles = getResizers()
|
||||
|
||||
return (
|
||||
<div ref={resizerRef}>
|
||||
{activeCell != null && !isMouseDown && (
|
||||
|
||||
@@ -218,6 +218,7 @@ function TableHoverActionsContainer({
|
||||
className={editorConfig.editorConfig.lexical.theme.tableAddRows}
|
||||
onClick={() => insertAction(true)}
|
||||
style={{ ...position }}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
{isShownColumn && (
|
||||
@@ -225,6 +226,7 @@ function TableHoverActionsContainer({
|
||||
className={editorConfig.editorConfig.lexical.theme.tableAddColumns}
|
||||
onClick={() => insertAction(false)}
|
||||
style={{ ...position }}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
'use client'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
import debounce from './debounce.js'
|
||||
|
||||
// Define the type for debounced function that includes cancel method
|
||||
interface DebouncedFunction<T extends (...args: any[]) => any> {
|
||||
(...args: Parameters<T>): ReturnType<T>
|
||||
cancel: () => void
|
||||
}
|
||||
|
||||
export function useDebounce<T extends (...args: never[]) => void>(
|
||||
fn: T,
|
||||
ms: number,
|
||||
maxWait?: number,
|
||||
) {
|
||||
const funcRef = useRef<null | T>(null)
|
||||
funcRef.current = fn
|
||||
// Update the ref type to include cancel method
|
||||
const debouncedRef = useRef<DebouncedFunction<T> | null>(null)
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
debounce(
|
||||
(...args: Parameters<T>) => {
|
||||
if (funcRef.current) {
|
||||
funcRef.current(...args)
|
||||
useEffect(() => {
|
||||
debouncedRef.current = debounce(fn, ms, { maxWait }) as DebouncedFunction<T>
|
||||
|
||||
return () => {
|
||||
debouncedRef.current?.cancel()
|
||||
}
|
||||
},
|
||||
ms,
|
||||
{ maxWait },
|
||||
),
|
||||
[ms, maxWait],
|
||||
)
|
||||
}, [fn, ms, maxWait])
|
||||
|
||||
const callback = useCallback((...args: Parameters<T>) => {
|
||||
if (debouncedRef.current) {
|
||||
debouncedRef.current(...args)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return callback
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const ChecklistFeature = createServerFeature({
|
||||
: [
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListHTMLConverter,
|
||||
html: ListHTMLConverter as any, // ListHTMLConverter uses a different generic type than ListNode[exportJSON], thus we need to cast as any
|
||||
},
|
||||
node: ListNode,
|
||||
}),
|
||||
|
||||
@@ -17,7 +17,7 @@ export const OrderedListFeature = createServerFeature({
|
||||
: [
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListHTMLConverter,
|
||||
html: ListHTMLConverter as any, // ListHTMLConverter uses a different generic type than ListNode[exportJSON], thus we need to cast as any
|
||||
},
|
||||
node: ListNode,
|
||||
}),
|
||||
|
||||
@@ -14,7 +14,7 @@ export const UnorderedListFeature = createServerFeature({
|
||||
nodes: [
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListHTMLConverter,
|
||||
html: ListHTMLConverter as any, // ListHTMLConverter uses a different generic type than ListNode[exportJSON], thus we need to cast as any
|
||||
},
|
||||
node: ListNode,
|
||||
}),
|
||||
|
||||
@@ -104,13 +104,16 @@ export class RelationshipServerNode extends DecoratorBlockNode {
|
||||
return false
|
||||
}
|
||||
|
||||
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element | null {
|
||||
decorate(_editor: LexicalEditor, _config: EditorConfig): JSX.Element | null {
|
||||
return null
|
||||
}
|
||||
|
||||
exportDOM(): DOMExportOutput {
|
||||
const element = document.createElement('div')
|
||||
element.setAttribute('data-lexical-relationship-id', String(this.__data?.value))
|
||||
element.setAttribute(
|
||||
'data-lexical-relationship-id',
|
||||
String(typeof this.__data?.value === 'object' ? this.__data?.value?.id : this.__data?.value),
|
||||
)
|
||||
element.setAttribute('data-lexical-relationship-relationTo', this.__data?.relationTo)
|
||||
|
||||
const text = document.createTextNode(this.getTextContent())
|
||||
@@ -132,7 +135,7 @@ export class RelationshipServerNode extends DecoratorBlockNode {
|
||||
}
|
||||
|
||||
getTextContent(): string {
|
||||
return `${this.__data?.relationTo} relation to ${this.__data?.value}`
|
||||
return `${this.__data?.relationTo} relation to ${typeof this.__data?.value === 'object' ? this.__data?.value?.id : this.__data?.value}`
|
||||
}
|
||||
|
||||
setData(data: RelationshipData): void {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useMemo } from 'react'
|
||||
|
||||
import type { EditorConfigContextType } from '../../../../../lexical/config/client/EditorConfigProvider.js'
|
||||
import type { SanitizedClientEditorConfig } from '../../../../../lexical/config/types.js'
|
||||
import type { PluginComponentWithAnchor } from '../../../../typesClient.js'
|
||||
import type { PluginComponent } from '../../../../typesClient.js'
|
||||
import type { ToolbarGroup, ToolbarGroupItem } from '../../../types.js'
|
||||
import type { FixedToolbarFeatureProps } from '../../server/index.js'
|
||||
|
||||
@@ -58,7 +58,7 @@ function ToolbarGroupComponent({
|
||||
group: ToolbarGroup
|
||||
index: number
|
||||
}): React.ReactNode {
|
||||
const { i18n } = useTranslation()
|
||||
const { i18n } = useTranslation<{}, string>()
|
||||
const {
|
||||
fieldProps: { featureClientSchemaMap, schemaPath },
|
||||
} = useEditorConfigContext()
|
||||
@@ -106,9 +106,8 @@ function ToolbarGroupComponent({
|
||||
|
||||
return (
|
||||
<div className={`fixed-toolbar__group fixed-toolbar__group-${group.key}`} key={group.key}>
|
||||
{group.type === 'dropdown' &&
|
||||
group.items.length &&
|
||||
(DropdownIcon ? (
|
||||
{group.type === 'dropdown' && group.items.length ? (
|
||||
DropdownIcon ? (
|
||||
<ToolbarDropdown
|
||||
anchorElem={anchorElem}
|
||||
editor={editor}
|
||||
@@ -129,14 +128,15 @@ function ToolbarGroupComponent({
|
||||
maxActiveItems={1}
|
||||
onActiveChange={onActiveChange}
|
||||
/>
|
||||
))}
|
||||
{group.type === 'buttons' &&
|
||||
group.items.length &&
|
||||
group.items.map((item) => {
|
||||
)
|
||||
) : null}
|
||||
{group.type === 'buttons' && group.items.length
|
||||
? group.items.map((item) => {
|
||||
return (
|
||||
<ButtonGroupItem anchorElem={anchorElem} editor={editor} item={item} key={item.key} />
|
||||
)
|
||||
})}
|
||||
})
|
||||
: null}
|
||||
{index < editorConfig.features.toolbarFixed?.groups.length - 1 && <div className="divider" />}
|
||||
</div>
|
||||
)
|
||||
@@ -196,14 +196,18 @@ function FixedToolbar({
|
||||
)
|
||||
|
||||
if (overlapping) {
|
||||
currentToolbarElem.className = 'fixed-toolbar fixed-toolbar--overlapping'
|
||||
parentToolbarElem.className = 'fixed-toolbar fixed-toolbar--hide'
|
||||
currentToolbarElem.classList.remove('fixed-toolbar')
|
||||
currentToolbarElem.classList.add('fixed-toolbar', 'fixed-toolbar--overlapping')
|
||||
parentToolbarElem.classList.remove('fixed-toolbar')
|
||||
parentToolbarElem.classList.add('fixed-toolbar', 'fixed-toolbar--hide')
|
||||
} else {
|
||||
if (!currentToolbarElem.classList.contains('fixed-toolbar--overlapping')) {
|
||||
return
|
||||
}
|
||||
currentToolbarElem.className = 'fixed-toolbar'
|
||||
parentToolbarElem.className = 'fixed-toolbar'
|
||||
currentToolbarElem.classList.remove('fixed-toolbar--overlapping')
|
||||
currentToolbarElem.classList.add('fixed-toolbar')
|
||||
parentToolbarElem.classList.remove('fixed-toolbar--hide')
|
||||
parentToolbarElem.classList.add('fixed-toolbar')
|
||||
}
|
||||
},
|
||||
50,
|
||||
@@ -256,10 +260,7 @@ const getParentEditorWithFixedToolbar = (
|
||||
return false
|
||||
}
|
||||
|
||||
export const FixedToolbarPlugin: PluginComponentWithAnchor<FixedToolbarFeatureProps> = ({
|
||||
anchorElem,
|
||||
clientProps,
|
||||
}) => {
|
||||
export const FixedToolbarPlugin: PluginComponent<FixedToolbarFeatureProps> = ({ clientProps }) => {
|
||||
const [currentEditor] = useLexicalComposerContext()
|
||||
const editorConfigContext = useEditorConfigContext()
|
||||
|
||||
@@ -287,7 +288,7 @@ export const FixedToolbarPlugin: PluginComponentWithAnchor<FixedToolbarFeaturePr
|
||||
|
||||
return (
|
||||
<FixedToolbar
|
||||
anchorElem={anchorElem}
|
||||
anchorElem={document.body}
|
||||
editor={editor}
|
||||
editorConfig={editorConfig}
|
||||
parentWithFixedToolbar={parentWithFixedToolbar}
|
||||
|
||||
@@ -95,9 +95,8 @@ function ToolbarGroupComponent({
|
||||
className={`inline-toolbar-popup__group inline-toolbar-popup__group-${group.key}`}
|
||||
key={group.key}
|
||||
>
|
||||
{group.type === 'dropdown' &&
|
||||
group.items.length &&
|
||||
(DropdownIcon ? (
|
||||
{group.type === 'dropdown' && group.items.length ? (
|
||||
DropdownIcon ? (
|
||||
<ToolbarDropdown
|
||||
anchorElem={anchorElem}
|
||||
editor={editor}
|
||||
@@ -114,14 +113,15 @@ function ToolbarGroupComponent({
|
||||
maxActiveItems={1}
|
||||
onActiveChange={onActiveChange}
|
||||
/>
|
||||
))}
|
||||
{group.type === 'buttons' &&
|
||||
group.items.length &&
|
||||
group.items.map((item) => {
|
||||
)
|
||||
) : null}
|
||||
{group.type === 'buttons' && group.items.length
|
||||
? group.items.map((item) => {
|
||||
return (
|
||||
<ButtonGroupItem anchorElem={anchorElem} editor={editor} item={item} key={item.key} />
|
||||
)
|
||||
})}
|
||||
})
|
||||
: null}
|
||||
{index < editorConfig.features.toolbarInline?.groups.length - 1 && (
|
||||
<div className="divider" />
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { LexicalEditor } from 'lexical'
|
||||
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { $getSelection } from 'lexical'
|
||||
import { $addUpdateTag, $getSelection } from 'lexical'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import type { ToolbarGroupItem } from '../../types.js'
|
||||
@@ -84,9 +84,10 @@ export const ToolbarButton = ({
|
||||
className={className}
|
||||
onClick={() => {
|
||||
if (enabled !== false) {
|
||||
editor._updateTags = new Set(['toolbar', ...editor._updateTags]) // without setting the tags, our onSelect will not be able to trigger our onChange as focus onChanges are ignored.
|
||||
|
||||
editor.focus(() => {
|
||||
editor.update(() => {
|
||||
$addUpdateTag('toolbar')
|
||||
})
|
||||
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
||||
item.onSelect?.({
|
||||
editor,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import type { LexicalEditor } from 'lexical'
|
||||
|
||||
import { Button } from '@payloadcms/ui'
|
||||
import { $addUpdateTag, type LexicalEditor } from 'lexical'
|
||||
import React, { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
@@ -74,9 +73,10 @@ export function DropDownItem({
|
||||
iconStyle="none"
|
||||
onClick={() => {
|
||||
if (enabled !== false) {
|
||||
editor._updateTags = new Set(['toolbar', ...editor._updateTags]) // without setting the tags, our onSelect will not be able to trigger our onChange as focus onChanges are ignored.
|
||||
|
||||
editor.focus(() => {
|
||||
editor.update(() => {
|
||||
$addUpdateTag('toolbar')
|
||||
})
|
||||
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
||||
item.onSelect?.({
|
||||
editor,
|
||||
|
||||
@@ -28,7 +28,7 @@ const ToolbarItem = ({
|
||||
enabled?: boolean
|
||||
item: ToolbarGroupItem
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
const { i18n } = useTranslation<{}, string>()
|
||||
const {
|
||||
fieldProps: { featureClientSchemaMap, schemaPath },
|
||||
} = useEditorConfigContext()
|
||||
@@ -173,8 +173,8 @@ export const ToolbarDropdown = ({
|
||||
key={groupKey}
|
||||
label={label}
|
||||
>
|
||||
{items.length &&
|
||||
items.map((item) => {
|
||||
{items.length
|
||||
? items.map((item) => {
|
||||
return (
|
||||
<ToolbarItem
|
||||
active={activeItemKeys.includes(item.key)}
|
||||
@@ -185,7 +185,8 @@ export const ToolbarDropdown = ({
|
||||
key={item.key}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
})
|
||||
: null}
|
||||
</DropDown>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Klass, LexicalEditor, LexicalNode, LexicalNodeReplacement } from 'lexical'
|
||||
import type { RichTextFieldClient } from 'payload'
|
||||
import type React from 'react'
|
||||
import type { JSX } from 'react'
|
||||
|
||||
import type { ClientEditorConfig } from '../lexical/config/types.js'
|
||||
import type { SlashMenuGroup } from '../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
|
||||
@@ -47,6 +48,52 @@ export type PluginComponentWithAnchor<ClientFeatureProps = any> = React.FC<{
|
||||
clientProps: ClientFeatureProps
|
||||
}>
|
||||
|
||||
/**
|
||||
* Plugins are react components 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 =
|
||||
| {
|
||||
clientProps: any
|
||||
// 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
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'bottom' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
// 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
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'normal' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
// 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
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'top' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
// 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
|
||||
Component: PluginComponentWithAnchor
|
||||
desktopOnly?: boolean
|
||||
key: string
|
||||
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'aboveContainer'
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'belowContainer'
|
||||
}
|
||||
|
||||
export type ClientFeature<ClientFeatureProps> = {
|
||||
markdownTransformers?: (
|
||||
| ((props: {
|
||||
@@ -93,7 +140,7 @@ export type ClientFeature<ClientFeatureProps> = {
|
||||
/**
|
||||
* Client Features can register their own providers, which will be nested below the EditorConfigProvider
|
||||
*/
|
||||
providers?: Array<React.FC>
|
||||
providers?: Array<React.FC<{ children: JSX.Element }>>
|
||||
/**
|
||||
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
|
||||
*/
|
||||
@@ -154,52 +201,6 @@ export type ResolvedClientFeatureMap = Map<string, ResolvedClientFeature<any>>
|
||||
|
||||
export type ClientFeatureProviderMap = Map<string, FeatureProviderClient<any, any>>
|
||||
|
||||
/**
|
||||
* Plugins are react components 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 =
|
||||
| {
|
||||
clientProps: any
|
||||
// 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
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'bottom' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
// 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
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'normal' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
// 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
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'top' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
// 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
|
||||
Component: PluginComponentWithAnchor
|
||||
desktopOnly?: boolean
|
||||
key: string
|
||||
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'aboveContainer'
|
||||
}
|
||||
| {
|
||||
clientProps: any
|
||||
Component: PluginComponent
|
||||
key: string
|
||||
position: 'belowContainer'
|
||||
}
|
||||
|
||||
export type SanitizedClientFeatures = {
|
||||
/** The keys of all enabled features */
|
||||
enabledFeatures: string[]
|
||||
|
||||
@@ -12,7 +12,6 @@ import type {
|
||||
Field,
|
||||
FieldSchemaMap,
|
||||
JsonObject,
|
||||
Payload,
|
||||
PayloadComponent,
|
||||
PayloadRequest,
|
||||
PopulateType,
|
||||
|
||||
@@ -57,9 +57,9 @@ export class UploadNode extends UploadServerNode {
|
||||
return super.getType()
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
static importDOM(): DOMConversionMap<HTMLImageElement> {
|
||||
return {
|
||||
img: (node: HTMLImageElement) => ({
|
||||
img: (node) => ({
|
||||
conversion: $convertUploadElement,
|
||||
priority: 0,
|
||||
}),
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from 'lexical'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import type { PluginComponentWithAnchor } from '../../../typesClient.js'
|
||||
import type { PluginComponent } from '../../../typesClient.js'
|
||||
import type { UploadData } from '../../server/nodes/UploadNode.js'
|
||||
import type { UploadFeaturePropsClient } from '../feature.client.js'
|
||||
|
||||
@@ -26,9 +26,7 @@ export type InsertUploadPayload = Readonly<Omit<UploadData, 'id'> & Partial<Pick
|
||||
export const INSERT_UPLOAD_COMMAND: LexicalCommand<InsertUploadPayload> =
|
||||
createCommand('INSERT_UPLOAD_COMMAND')
|
||||
|
||||
export const UploadPlugin: PluginComponentWithAnchor<UploadFeaturePropsClient> = ({
|
||||
clientProps,
|
||||
}) => {
|
||||
export const UploadPlugin: PluginComponent<UploadFeaturePropsClient> = ({ clientProps }) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const {
|
||||
config: { collections },
|
||||
|
||||
@@ -97,9 +97,9 @@ export class UploadServerNode extends DecoratorBlockNode {
|
||||
return 'upload'
|
||||
}
|
||||
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
static importDOM(): DOMConversionMap<HTMLImageElement> {
|
||||
return {
|
||||
img: (node: HTMLImageElement) => ({
|
||||
img: (node) => ({
|
||||
conversion: $convertUploadServerElement,
|
||||
priority: 0,
|
||||
}),
|
||||
|
||||
@@ -49,7 +49,7 @@ export const sanitizeClientFeatures = (
|
||||
feature.plugins.forEach((plugin, i) => {
|
||||
sanitized.plugins?.push({
|
||||
clientProps: feature.sanitizedClientFeatureProps,
|
||||
Component: plugin.Component,
|
||||
Component: plugin.Component as any, // Appeases strict: true
|
||||
key: feature.key + i,
|
||||
position: plugin.position,
|
||||
})
|
||||
|
||||
@@ -147,12 +147,13 @@ function isTriggerVisibleInNearestScrollContainer(
|
||||
// Reposition the menu on scroll, window resize, and element resize.
|
||||
export function useDynamicPositioning(
|
||||
resolution: MenuResolution | null,
|
||||
targetElement: HTMLElement | null,
|
||||
targetElementRef: RefObject<HTMLElement | null>,
|
||||
onReposition: () => void,
|
||||
onVisibilityChange?: (isInView: boolean) => void,
|
||||
) {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
useEffect(() => {
|
||||
const targetElement = targetElementRef.current
|
||||
if (targetElement != null && resolution != null) {
|
||||
const rootElement = editor.getRootElement()
|
||||
const rootScrollParent =
|
||||
@@ -186,12 +187,12 @@ export function useDynamicPositioning(
|
||||
})
|
||||
resizeObserver.observe(targetElement)
|
||||
return () => {
|
||||
resizeObserver.unobserve(targetElement)
|
||||
resizeObserver.disconnect()
|
||||
window.removeEventListener('resize', onReposition)
|
||||
document.removeEventListener('scroll', handleScroll, true)
|
||||
}
|
||||
}
|
||||
}, [targetElement, editor, onVisibilityChange, onReposition, resolution])
|
||||
}, [editor, onVisibilityChange, onReposition, resolution, targetElementRef])
|
||||
}
|
||||
|
||||
export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
|
||||
@@ -529,7 +530,7 @@ export function useMenuAnchorRef(
|
||||
[resolution, setResolution],
|
||||
)
|
||||
|
||||
useDynamicPositioning(resolution, anchorElementRef.current, positionMenu, onVisibilityChange)
|
||||
useDynamicPositioning(resolution, anchorElementRef, positionMenu, onVisibilityChange)
|
||||
|
||||
return anchorElementRef
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
'use client'
|
||||
import type { TextNode } from 'lexical'
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
@@ -26,18 +24,20 @@ function SlashMenuItem({
|
||||
item,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
ref,
|
||||
}: {
|
||||
index: number
|
||||
isSelected: boolean
|
||||
item: SlashMenuItemInternal
|
||||
onClick: () => void
|
||||
onMouseEnter: () => void
|
||||
ref?: React.Ref<HTMLButtonElement>
|
||||
}) {
|
||||
const {
|
||||
fieldProps: { featureClientSchemaMap, schemaPath },
|
||||
} = useEditorConfigContext()
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const { i18n } = useTranslation<{}, string>()
|
||||
|
||||
let className = `${baseClass}__item ${baseClass}__item-${item.key}`
|
||||
if (isSelected) {
|
||||
@@ -64,9 +64,7 @@ function SlashMenuItem({
|
||||
key={item.key}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
ref={(element) => {
|
||||
item.ref = { current: element }
|
||||
}}
|
||||
ref={ref}
|
||||
role="option"
|
||||
tabIndex={-1}
|
||||
type="button"
|
||||
@@ -86,7 +84,7 @@ export function SlashMenuPlugin({
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const [queryString, setQueryString] = useState<null | string>(null)
|
||||
const { editorConfig } = useEditorConfigContext()
|
||||
const { i18n } = useTranslation()
|
||||
const { i18n } = useTranslation<{}, string>()
|
||||
const {
|
||||
fieldProps: { featureClientSchemaMap, schemaPath },
|
||||
} = useEditorConfigContext()
|
||||
@@ -231,6 +229,9 @@ export function SlashMenuPlugin({
|
||||
onMouseEnter={() => {
|
||||
setSelectedItemKey(item.key)
|
||||
}}
|
||||
ref={(el) => {
|
||||
;(item as SlashMenuItemInternal).ref = { current: el }
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"noImplicitAny": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"strict": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
|
||||
@@ -121,9 +121,9 @@ export interface Config {
|
||||
user: User & {
|
||||
collection: 'users';
|
||||
};
|
||||
jobs?: {
|
||||
jobs: {
|
||||
tasks: unknown;
|
||||
workflows?: unknown;
|
||||
workflows: unknown;
|
||||
};
|
||||
}
|
||||
export interface UserAuthOperations {
|
||||
|
||||
Reference in New Issue
Block a user