import { Show } from "solid-js"; import { createTiptapEditor, useEditorHTML } from "solid-tiptap"; import StarterKit from "@tiptap/starter-kit"; import Link from "@tiptap/extension-link"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import Image from "@tiptap/extension-image"; import { Node } from "@tiptap/core"; import { createLowlight, common } from "lowlight"; import css from "highlight.js/lib/languages/css"; import js from "highlight.js/lib/languages/javascript"; import ts from "highlight.js/lib/languages/typescript"; import ocaml from "highlight.js/lib/languages/ocaml"; import rust from "highlight.js/lib/languages/rust"; // Create lowlight instance with common languages const lowlight = createLowlight(common); // Register additional languages lowlight.register("css", css); lowlight.register("js", js); lowlight.register("ts", ts); lowlight.register("ocaml", ocaml); lowlight.register("rust", rust); // IFrame extension interface IframeOptions { allowFullscreen: boolean; HTMLAttributes: { [key: string]: any; }; } declare module "@tiptap/core" { interface Commands { iframe: { setIframe: (options: { src: string }) => ReturnType; }; } } const IframeEmbed = Node.create({ name: "iframe", group: "block", atom: true, addOptions() { return { allowFullscreen: true, HTMLAttributes: { class: "iframe-wrapper" } }; }, addAttributes() { return { src: { default: null }, frameborder: { default: 0 }, allowfullscreen: { default: this.options.allowFullscreen, parseHTML: () => this.options.allowFullscreen } }; }, parseHTML() { return [ { tag: "iframe" } ]; }, renderHTML({ HTMLAttributes }) { return ["div", this.options.HTMLAttributes, ["iframe", HTMLAttributes]]; }, addCommands() { return { setIframe: (options: { src: string }) => ({ tr, dispatch }) => { const { selection } = tr; const node = this.type.create(options); if (dispatch) { tr.replaceRangeWith(selection.from, selection.to, node); } return true; } }; } }); export interface TextEditorProps { updateContent: (content: string) => void; preSet?: string; } export default function TextEditor(props: TextEditorProps) { let editorRef!: HTMLDivElement; const editor = createTiptapEditor(() => ({ element: editorRef, extensions: [ StarterKit, CodeBlockLowlight.configure({ lowlight }), Link.configure({ openOnClick: true }), Image, IframeEmbed ], content: props.preSet || `

Hello! World

`, onUpdate: ({ editor }) => { props.updateContent(editor.getHTML()); } })); const setLink = () => { const instance = editor(); if (!instance) return; const previousUrl = instance.getAttributes("link").href; const url = window.prompt("URL", previousUrl); if (url === null) return; if (url === "") { instance.chain().focus().extendMarkRange("link").unsetLink().run(); return; } instance .chain() .focus() .extendMarkRange("link") .setLink({ href: url }) .run(); }; const addIframe = () => { const instance = editor(); if (!instance) return; const url = window.prompt("URL"); if (url) { instance.commands.setIframe({ src: url }); } }; const addImage = () => { const instance = editor(); if (!instance) return; const url = window.prompt("URL"); if (url) { instance.chain().focus().setImage({ src: url }).run(); } }; return (
{(instance) => ( <> {/* Bubble Menu - appears when text is selected */}
{/* Toolbar - always visible */}
)}
); }