initial commit 3??
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { type Component, createEffect, untrack } from "solid-js";
|
||||
import { createTiptapEditor } from "solid-tiptap";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import TaskList from "@tiptap/extension-task-list";
|
||||
import TaskItem from "@tiptap/extension-task-item";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { SlashCommands } from "@/lib/slash-command";
|
||||
import { suggestion } from "@/lib/slash-renderer";
|
||||
|
||||
interface TaskEditorProps {
|
||||
content?: string;
|
||||
onUpdate: (html: string) => void;
|
||||
onEditorReady?: (editor: any) => void;
|
||||
editable?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export const TaskEditor: Component<TaskEditorProps> = (props) => {
|
||||
let editorRef: HTMLDivElement | undefined;
|
||||
|
||||
const editor = createTiptapEditor(() => ({
|
||||
element: editorRef!,
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
heading: {
|
||||
levels: [1, 2, 3],
|
||||
},
|
||||
}),
|
||||
Placeholder.configure({
|
||||
placeholder: "Type '/' for commands or start writing...",
|
||||
emptyEditorClass: "is-editor-empty before:content-[attr(data-placeholder)] before:text-muted-foreground before:float-left before:pointer-events-none before:h-0",
|
||||
}),
|
||||
TaskList.configure({
|
||||
HTMLAttributes: {
|
||||
class: "not-prose pl-2",
|
||||
},
|
||||
}),
|
||||
TaskItem.configure({
|
||||
nested: true,
|
||||
HTMLAttributes: {
|
||||
class: "flex gap-2 items-start my-1",
|
||||
},
|
||||
}),
|
||||
SlashCommands.configure({
|
||||
suggestion,
|
||||
}),
|
||||
],
|
||||
// Use untrack so the editor doesn't re-initialize when props.content changes
|
||||
content: untrack(() => props.content) || "",
|
||||
editable: props.editable ?? true,
|
||||
onUpdate: ({ editor }) => {
|
||||
props.onUpdate(editor.getHTML());
|
||||
},
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: cn(
|
||||
"prose prose-sm dark:prose-invert max-w-none focus:outline-none min-h-[150px]",
|
||||
"prose-headings:font-semibold prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg",
|
||||
"prose-p:my-1 prose-headings:my-2 prose-ul:my-1 prose-ol:my-1",
|
||||
"prose-pre:bg-muted prose-pre:rounded-lg prose-pre:p-4",
|
||||
"prose-img:rounded-lg prose-img:border prose-img:border-border",
|
||||
// Custom Task List Styling
|
||||
"[&_ul[data-type='taskList']]:list-none [&_ul[data-type='taskList']]:p-0",
|
||||
"[&_li[data-type='taskItem']]:flex [&_li[data-type='taskItem']]:items-start",
|
||||
"[&_li[data-type='taskItem']>label]:mr-2 [&_li[data-type='taskItem']>label]:mt-0.5 [&_li[data-type='taskItem']>label]:select-none",
|
||||
"[&_li[data-type='taskItem']>div]:flex-1",
|
||||
props.class
|
||||
),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Expose editor to parent
|
||||
createEffect(() => {
|
||||
const e = editor();
|
||||
if (e && props.onEditorReady) {
|
||||
props.onEditorReady(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Sync content updates from outside if needed (but only if NOT focused to avoid jumps)
|
||||
createEffect(() => {
|
||||
const content = props.content;
|
||||
const e = editor();
|
||||
if (e && content !== undefined && !e.isFocused && content !== e.getHTML()) {
|
||||
e.commands.setContent(content, { emitUpdate: false });
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={editorRef}
|
||||
class="w-full h-full cursor-text"
|
||||
onClick={() => editor()?.chain().focus().run()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user