110 lines
3.0 KiB
TypeScript
110 lines
3.0 KiB
TypeScript
|
|
"use client"
|
||
|
|
|
||
|
|
import { useMemo, useState } from "react"
|
||
|
|
import type { ComponentProps, ComponentType, ReactNode } from "react"
|
||
|
|
import { LexicalComposer } from "@lexical/react/LexicalComposer"
|
||
|
|
import { TextNode } from "lexical"
|
||
|
|
import { I18nProvider, useI18nProvider } from "@workspace/i18n"
|
||
|
|
import { cn } from "@workspace/ui/lib/utils"
|
||
|
|
|
||
|
|
import { LexicalTextNode } from "./nodes/text-node"
|
||
|
|
import { lexicalTheme } from "./theme"
|
||
|
|
import { LexicalEmbedProvider } from "./embed"
|
||
|
|
import { LexicalHtmlPlugin } from "./plugins/html-value-plugin"
|
||
|
|
import {
|
||
|
|
compileLexicalActions,
|
||
|
|
findLexicalActions,
|
||
|
|
} from "./action-declarations"
|
||
|
|
import { LexicalActionsProvider } from "./actions-context"
|
||
|
|
import { messages as englishMessages } from "./locales/en"
|
||
|
|
|
||
|
|
const pluginKeys = new WeakMap<ComponentType, string>()
|
||
|
|
let nextPluginKey = 0
|
||
|
|
|
||
|
|
function getPluginKey(plugin: ComponentType) {
|
||
|
|
const existingKey = pluginKeys.get(plugin)
|
||
|
|
if (existingKey) return existingKey
|
||
|
|
|
||
|
|
const key = `${plugin.displayName || plugin.name || "plugin"}:${nextPluginKey}`
|
||
|
|
nextPluginKey += 1
|
||
|
|
pluginKeys.set(plugin, key)
|
||
|
|
return key
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface LexicalRootProps extends Omit<
|
||
|
|
ComponentProps<"div">,
|
||
|
|
"onChange"
|
||
|
|
> {
|
||
|
|
namespace?: string
|
||
|
|
onChange: (html: string) => void
|
||
|
|
value?: string
|
||
|
|
}
|
||
|
|
|
||
|
|
function LexicalI18nBoundary({ children }: { children: ReactNode }) {
|
||
|
|
const hasI18nProvider = useI18nProvider()
|
||
|
|
|
||
|
|
if (hasI18nProvider) return children
|
||
|
|
|
||
|
|
return (
|
||
|
|
<I18nProvider catalogs={{ en: englishMessages }} locale="en">
|
||
|
|
{children}
|
||
|
|
</I18nProvider>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function LexicalRoot({
|
||
|
|
className,
|
||
|
|
children,
|
||
|
|
namespace = "lexical-editor",
|
||
|
|
onChange,
|
||
|
|
value,
|
||
|
|
...props
|
||
|
|
}: LexicalRootProps) {
|
||
|
|
const [compiledActions] = useState(() =>
|
||
|
|
compileLexicalActions(findLexicalActions(children))
|
||
|
|
)
|
||
|
|
const initialConfig = useMemo(
|
||
|
|
() => ({
|
||
|
|
namespace,
|
||
|
|
nodes: [
|
||
|
|
LexicalTextNode,
|
||
|
|
{
|
||
|
|
replace: TextNode,
|
||
|
|
with: (node: TextNode) => new LexicalTextNode(node.getTextContent()),
|
||
|
|
withKlass: LexicalTextNode,
|
||
|
|
},
|
||
|
|
...compiledActions.nodes,
|
||
|
|
],
|
||
|
|
onError: (error: Error) => {
|
||
|
|
throw error
|
||
|
|
},
|
||
|
|
theme: lexicalTheme,
|
||
|
|
}),
|
||
|
|
[compiledActions.nodes, namespace]
|
||
|
|
)
|
||
|
|
|
||
|
|
return (
|
||
|
|
<LexicalI18nBoundary>
|
||
|
|
<LexicalComposer initialConfig={initialConfig}>
|
||
|
|
<LexicalActionsProvider actions={compiledActions.actions}>
|
||
|
|
<LexicalEmbedProvider definitions={compiledActions.embeds}>
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
"overflow-hidden rounded-lg border bg-background",
|
||
|
|
className
|
||
|
|
)}
|
||
|
|
{...props}
|
||
|
|
>
|
||
|
|
{children}
|
||
|
|
</div>
|
||
|
|
{compiledActions.plugins.map((Plugin) => (
|
||
|
|
<Plugin key={getPluginKey(Plugin)} />
|
||
|
|
))}
|
||
|
|
<LexicalHtmlPlugin value={value} onChange={onChange} />
|
||
|
|
</LexicalEmbedProvider>
|
||
|
|
</LexicalActionsProvider>
|
||
|
|
</LexicalComposer>
|
||
|
|
</LexicalI18nBoundary>
|
||
|
|
)
|
||
|
|
}
|