initial commit 3??

This commit is contained in:
2026-01-30 14:10:11 -06:00
parent 34bbe8e98f
commit f2a75c954f
42 changed files with 3935 additions and 1 deletions
+99
View File
@@ -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()}
/>
);
};