import React, { ComponentProps } from "react"; import { RawEditorOptions, TinyMCE, Editor } from "./tinymce"; import { twMerge } from "tailwind-merge"; import twuiSlugToNormalText from "../../utils/slug-to-normal-text"; import Border from "../../elements/Border"; export type TinyMCEEditorProps = { tinyMCE?: TinyMCE | null; options?: RawEditorOptions; editorRef?: React.MutableRefObject; setEditor?: React.Dispatch>; wrapperProps?: React.DetailedHTMLProps< React.HTMLAttributes, HTMLDivElement >; wrapperWrapperProps?: React.DetailedHTMLProps< React.HTMLAttributes, HTMLDivElement >; borderProps?: ComponentProps; defaultValue?: string; name?: KeyType; changeHandler?: (content: string) => void; showLabel?: boolean; useParentCSS?: boolean; placeholder?: string; }; let interval: any; /** * # Tiny MCE Editor Component * @className_wrapper twui-rte-wrapper */ export default function TinyMCEEditor({ options, editorRef, setEditor, tinyMCE, wrapperProps, defaultValue, changeHandler, wrapperWrapperProps, borderProps, name, showLabel, useParentCSS, placeholder, }: TinyMCEEditorProps) { const editorComponentRef = React.useRef(null); const FINAL_HEIGHT = options?.height || 500; const [themeReady, setThemeReady] = React.useState(false); const [ready, setReady] = React.useState(false); const [darkMode, setDarkMode] = React.useState(false); const title = name ? twuiSlugToNormalText(name) : "Rich Text"; React.useEffect(() => { const htmlClassName = document.documentElement.className; if (htmlClassName.match(/dark/i)) setDarkMode(true); setTimeout(() => { setThemeReady(true); }, 200); }, []); React.useEffect(() => { if (!editorComponentRef.current || !themeReady) { return; } tinyMCE?.init({ height: FINAL_HEIGHT, menubar: false, plugins: "advlist lists link image charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime media table code help wordcount", toolbar: "undo redo | blocks | bold italic underline link image | bullist numlist outdent indent | removeformat code searchreplace wordcount preview insertdatetime", content_style: "body { font-family:Helvetica,Arial,sans-serif; font-size:14px; background-color: transparent }", init_instance_callback: (editor) => { setEditor?.(editor as any); if (editorRef) editorRef.current = editor as any; if (defaultValue) editor.setContent(defaultValue); setReady(true); editor.on("input", (e) => { changeHandler?.(editor.getContent()); }); if (useParentCSS) { useParentStyles(editor); } }, base_url: "https://datasquirel.com/tinymce-public", body_class: "twui-tinymce", placeholder, relative_urls: true, remove_script_host: true, convert_urls: false, ...options, license_key: "gpl", target: editorComponentRef.current, content_css: darkMode ? "dark" : undefined, skin: darkMode ? "oxide-dark" : undefined, }); return function () { tinyMCE?.remove(); }; }, [tinyMCE, themeReady]); return (
{showLabel && ( )}
); } function useParentStyles(editor: Editor) { const doc = editor.getDoc(); const parentStylesheets = document.styleSheets; for (const sheet of parentStylesheets) { try { if (sheet.href) { const link = doc.createElement("link"); link.rel = "stylesheet"; link.href = sheet.href; doc.head.appendChild(link); } else { const rules = sheet.cssRules || sheet.rules; if (rules) { const style = doc.createElement("style"); for (const rule of rules) { try { style.appendChild(doc.createTextNode(rule.cssText)); } catch (e) { console.warn("Could not copy CSS rule:", rule, e); } } doc.head.appendChild(style); } } } catch (e) { console.warn("Error processing stylesheet:", sheet, e); } } }