UNPKG

text-editor-studio-ts

Version:

A powerful mobile-responsive rich text editor built with Lexical and React

1 lines 15.3 kB
{"version":3,"file":"excalidraw-component-C5Za8rqo.cjs","sources":["../src/components/editor/editor-ui/excalidraw-image.tsx","../src/components/editor/editor-ui/excalidraw-component.tsx"],"sourcesContent":["import { useEffect, useState, JSX } from \"react\";\n\nimport { exportToSvg } from \"@excalidraw/excalidraw\";\nimport type { AppState, BinaryFiles } from \"@excalidraw/excalidraw/types\";\n// @ts-ignore\nimport type { ExcalidrawElement } from \"@excalidraw/excalidraw/types/element/types\";\n\ntype NonDeleted<T> = T & { isDeleted?: false };\n\ntype ImageType = \"svg\" | \"canvas\";\n\ntype Dimension = \"inherit\" | number;\n\ntype Props = {\n /**\n * Configures the export setting for SVG/Canvas\n */\n appState: AppState;\n /**\n * The css class applied to image to be rendered\n */\n className?: string;\n /**\n * The Excalidraw elements to be rendered as an image\n */\n elements: NonDeleted<ExcalidrawElement>[];\n /**\n * The Excalidraw files associated with the elements\n */\n files: BinaryFiles;\n /**\n * The height of the image to be rendered\n */\n height?: Dimension;\n /**\n * The ref object to be used to render the image\n */\n imageContainerRef: React.MutableRefObject<HTMLDivElement | null>;\n /**\n * The type of image to be rendered\n */\n imageType?: ImageType;\n /**\n * The css class applied to the root element of this component\n */\n rootClassName?: string | null;\n /**\n * The width of the image to be rendered\n */\n width?: Dimension;\n};\n\n// exportToSvg has fonts from excalidraw.com\n// We don't want them to be used in open source\nconst removeStyleFromSvg_HACK = (svg: SVGElement) => {\n const styleTag = svg?.firstElementChild?.firstElementChild;\n\n // Generated SVG is getting double-sized by height and width attributes\n // We want to match the real size of the SVG element\n const viewBox = svg.getAttribute(\"viewBox\");\n if (viewBox != null) {\n const viewBoxDimensions = viewBox.split(\" \");\n svg.setAttribute(\"width\", viewBoxDimensions[2]);\n svg.setAttribute(\"height\", viewBoxDimensions[3]);\n }\n\n if (styleTag && styleTag.tagName === \"style\") {\n styleTag.remove();\n }\n};\n\n/**\n * @explorer-desc\n * A component for rendering Excalidraw elements as a static image\n */\nexport default function ExcalidrawImage({\n elements,\n files,\n imageContainerRef,\n appState,\n rootClassName = null,\n width = \"inherit\",\n height = \"inherit\",\n}: Props): JSX.Element {\n const [Svg, setSvg] = useState<SVGElement | null>(null);\n\n useEffect(() => {\n const setContent = async () => {\n const svg: SVGElement = await exportToSvg({\n appState,\n elements,\n files,\n });\n removeStyleFromSvg_HACK(svg);\n\n svg.setAttribute(\"width\", \"100%\");\n svg.setAttribute(\"height\", \"100%\");\n svg.setAttribute(\"display\", \"block\");\n\n setSvg(svg);\n };\n setContent();\n }, [elements, files, appState]);\n\n const containerStyle: React.CSSProperties = {};\n if (width !== \"inherit\") {\n containerStyle.width = `${width}px`;\n }\n if (height !== \"inherit\") {\n containerStyle.height = `${height}px`;\n }\n\n return (\n <div\n ref={(node) => {\n if (node) {\n if (imageContainerRef) {\n imageContainerRef.current = node;\n }\n }\n }}\n className={rootClassName ?? \"\"}\n style={containerStyle}\n dangerouslySetInnerHTML={{ __html: Svg?.outerHTML ?? \"\" }}\n />\n );\n}\n","import { useCallback, useEffect, useMemo, useRef, useState, JSX } from \"react\";\n\nimport { AppState, BinaryFiles } from \"@excalidraw/excalidraw/types\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useLexicalEditable } from \"@lexical/react/useLexicalEditable\";\nimport { useLexicalNodeSelection } from \"@lexical/react/useLexicalNodeSelection\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport type { NodeKey } from \"lexical\";\nimport {\n $getNodeByKey,\n CLICK_COMMAND,\n COMMAND_PRIORITY_LOW,\n KEY_BACKSPACE_COMMAND,\n KEY_DELETE_COMMAND,\n} from \"lexical\";\n\nimport { $isExcalidrawNode } from \"@/components/editor/nodes/excalidraw-node\";\nimport ExcalidrawImage from \"@/components/editor/editor-ui/excalidraw-image\";\nimport type { ExcalidrawInitialElements } from \"@/components/editor/editor-ui/excalidraw-modal\";\nimport { ExcalidrawModal } from \"@/components/editor/editor-ui/excalidraw-modal\";\nimport { ImageResizer } from \"@/components/editor/editor-ui/image-resizer\";\n\nexport default function ExcalidrawComponent({\n nodeKey,\n data,\n width,\n height,\n}: {\n data: string;\n nodeKey: NodeKey;\n width: \"inherit\" | number;\n height: \"inherit\" | number;\n}): JSX.Element {\n const [editor] = useLexicalComposerContext();\n const isEditable = useLexicalEditable();\n const [isModalOpen, setModalOpen] = useState<boolean>(\n data === \"[]\" && editor.isEditable()\n );\n const imageContainerRef = useRef<HTMLDivElement | null>(null);\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n const captionButtonRef = useRef<HTMLButtonElement | null>(null);\n const [isSelected, setSelected, clearSelection] =\n useLexicalNodeSelection(nodeKey);\n const [isResizing, setIsResizing] = useState<boolean>(false);\n\n const $onDelete = useCallback(\n (event: KeyboardEvent) => {\n if (isSelected) {\n event.preventDefault();\n editor.update(() => {\n const node = $getNodeByKey(nodeKey);\n if (node) {\n node.remove();\n }\n });\n }\n return false;\n },\n [editor, isSelected, nodeKey]\n );\n\n useEffect(() => {\n if (!isEditable) {\n if (isSelected) {\n clearSelection();\n }\n return;\n }\n return mergeRegister(\n editor.registerCommand(\n CLICK_COMMAND,\n (event: MouseEvent) => {\n const buttonElem = buttonRef.current;\n const eventTarget = event.target;\n\n if (isResizing) {\n return true;\n }\n\n if (buttonElem !== null && buttonElem.contains(eventTarget as Node)) {\n if (!event.shiftKey) {\n clearSelection();\n }\n setSelected(!isSelected);\n if (event.detail > 1) {\n setModalOpen(true);\n }\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_LOW\n ),\n editor.registerCommand(\n KEY_DELETE_COMMAND,\n $onDelete,\n COMMAND_PRIORITY_LOW\n ),\n editor.registerCommand(\n KEY_BACKSPACE_COMMAND,\n $onDelete,\n COMMAND_PRIORITY_LOW\n )\n );\n }, [\n clearSelection,\n editor,\n isSelected,\n isResizing,\n $onDelete,\n setSelected,\n isEditable,\n ]);\n\n const deleteNode = useCallback(() => {\n setModalOpen(false);\n return editor.update(() => {\n const node = $getNodeByKey(nodeKey);\n if (node) {\n node.remove();\n }\n });\n }, [editor, nodeKey]);\n\n const setData = (\n els: ExcalidrawInitialElements,\n aps: Partial<AppState>,\n fls: BinaryFiles\n ) => {\n return editor.update(() => {\n const node = $getNodeByKey(nodeKey);\n if ($isExcalidrawNode(node)) {\n if ((els && els.length > 0) || Object.keys(fls).length > 0) {\n node.setData(\n JSON.stringify({\n appState: aps,\n elements: els,\n files: fls,\n })\n );\n } else {\n node.remove();\n }\n }\n });\n };\n\n const onResizeStart = () => {\n setIsResizing(true);\n };\n\n const onResizeEnd = (\n nextWidth: \"inherit\" | number,\n nextHeight: \"inherit\" | number\n ) => {\n // Delay hiding the resize bars for click case\n setTimeout(() => {\n setIsResizing(false);\n }, 200);\n\n editor.update(() => {\n const node = $getNodeByKey(nodeKey);\n\n if ($isExcalidrawNode(node)) {\n node.setWidth(nextWidth);\n node.setHeight(nextHeight);\n }\n });\n };\n\n const openModal = useCallback(() => {\n setModalOpen(true);\n }, []);\n\n const {\n elements = [],\n files = {},\n appState = {},\n } = useMemo(() => JSON.parse(data), [data]);\n\n const closeModal = useCallback(() => {\n setModalOpen(false);\n if (elements.length === 0) {\n editor.update(() => {\n const node = $getNodeByKey(nodeKey);\n if (node) {\n node.remove();\n }\n });\n }\n }, [editor, nodeKey, elements.length]);\n\n return (\n <>\n {isEditable && isModalOpen && (\n <ExcalidrawModal\n initialElements={elements}\n initialFiles={files}\n initialAppState={appState}\n isShown={isModalOpen}\n onDelete={deleteNode}\n onClose={closeModal}\n onSave={(els, aps, fls) => {\n setData(els, aps, fls);\n setModalOpen(false);\n }}\n closeOnClickOutside={false}\n />\n )}\n {elements.length > 0 && (\n <button\n ref={buttonRef}\n className={`m-0 border-0 bg-transparent p-0 ${\n isSelected\n ? \"user-select-none ring-2 ring-primary ring-offset-2\"\n : \"\"\n }`}\n >\n <ExcalidrawImage\n imageContainerRef={imageContainerRef}\n className=\"image\"\n elements={elements}\n files={files}\n appState={appState}\n width={width}\n height={height}\n />\n {isSelected && isEditable && (\n <div\n className=\"image-edit-button\"\n role=\"button\"\n tabIndex={0}\n onMouseDown={(event) => event.preventDefault()}\n onClick={openModal}\n />\n )}\n {(isSelected || isResizing) && isEditable && (\n <ImageResizer\n buttonRef={captionButtonRef}\n showCaption={true}\n setShowCaption={() => null}\n imageRef={imageContainerRef}\n editor={editor}\n onResizeStart={onResizeStart}\n onResizeEnd={onResizeEnd}\n captionsEnabled={true}\n />\n )}\n </button>\n )}\n </>\n );\n}\n"],"names":["ExcalidrawImage","elements","files","imageContainerRef","appState","rootClassName","width","height","Svg","setSvg","useState","useEffect","async","svg","exportToSvg","styleTag","firstElementChild","viewBox","getAttribute","viewBoxDimensions","split","setAttribute","tagName","remove","removeStyleFromSvg_HACK","setContent","containerStyle","jsxRuntime","jsx","ref","node","current","className","style","dangerouslySetInnerHTML","__html","outerHTML","nodeKey","data","editor","useLexicalComposerContext","isEditable","useLexicalEditable","isModalOpen","setModalOpen","useRef","buttonRef","captionButtonRef","isSelected","setSelected","clearSelection","useLexicalNodeSelection","isResizing","setIsResizing","$onDelete","useCallback","event","preventDefault","update","$getNodeByKey","mergeRegister","registerCommand","CLICK_COMMAND","buttonElem","eventTarget","target","contains","shiftKey","detail","COMMAND_PRIORITY_LOW","KEY_DELETE_COMMAND","KEY_BACKSPACE_COMMAND","deleteNode","openModal","useMemo","JSON","parse","closeModal","length","jsxs","Fragment","children","ExcalidrawModal","initialElements","initialFiles","initialAppState","isShown","onDelete","onClose","onSave","els","aps","fls","$isExcalidrawNode","Object","keys","setData","stringify","closeOnClickOutside","role","tabIndex","onMouseDown","onClick","ImageResizer","showCaption","setShowCaption","imageRef","onResizeStart","onResizeEnd","nextWidth","nextHeight","setTimeout","setWidth","setHeight","captionsEnabled"],"mappings":"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"}