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:
Alessio Gravili
2024-11-20 21:19:18 -07:00
committed by GitHub
parent 90e37fe78d
commit 48b60fc905
25 changed files with 197 additions and 168 deletions

View File

@@ -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()

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 && (

View File

@@ -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"
/>
)}
</>

View File

@@ -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
}

View File

@@ -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,
}),

View File

@@ -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,
}),

View File

@@ -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,
}),

View File

@@ -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 {

View File

@@ -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}

View File

@@ -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" />
)}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>
)
}

View File

@@ -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[]

View File

@@ -12,7 +12,6 @@ import type {
Field,
FieldSchemaMap,
JsonObject,
Payload,
PayloadComponent,
PayloadRequest,
PopulateType,

View File

@@ -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,
}),

View File

@@ -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 },

View File

@@ -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,
}),

View File

@@ -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,
})

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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",

View File

@@ -121,9 +121,9 @@ export interface Config {
user: User & {
collection: 'users';
};
jobs?: {
jobs: {
tasks: unknown;
workflows?: unknown;
workflows: unknown;
};
}
export interface UserAuthOperations {