143 lines
4.7 KiB
TypeScript
143 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEditor, EditorContent } from "@tiptap/react";
|
|
import StarterKit from "@tiptap/starter-kit";
|
|
import Highlight from "@tiptap/extension-highlight";
|
|
import TextAlign from "@tiptap/extension-text-align";
|
|
import { Markdown } from "@tiptap/markdown";
|
|
import { HomelabApp } from "./types";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { useEffect, useState } from "react";
|
|
import Menubar from "./menu-bar";
|
|
|
|
interface TextEditorProps {
|
|
content: string;
|
|
onSave: (content: string, linkedAppIds: string[]) => void;
|
|
initialLinkedAppIds?: string[]; // Added prop
|
|
}
|
|
|
|
const getApps = async (): Promise<HomelabApp[]> => {
|
|
return (await invoke("get_app")) as HomelabApp[];
|
|
};
|
|
|
|
export default function TextEditor({
|
|
content,
|
|
onSave,
|
|
initialLinkedAppIds = [],
|
|
}: TextEditorProps) {
|
|
const [availableApps, setAvailableApps] = useState<HomelabApp[]>([]);
|
|
// Seed state instantly from the prop if it exists
|
|
const [linkedAppIds, setLinkedAppIds] =
|
|
useState<string[]>(initialLinkedAppIds);
|
|
|
|
useEffect(() => {
|
|
const fetchApps = async () => {
|
|
const apps = await getApps();
|
|
setAvailableApps(apps);
|
|
};
|
|
fetchApps();
|
|
}, []);
|
|
|
|
const editor = useEditor({
|
|
extensions: [
|
|
StarterKit.configure({
|
|
bulletList: { HTMLAttributes: { class: "list-disc ml-3" } },
|
|
orderedList: { HTMLAttributes: { class: "list-decimal ml-3" } },
|
|
}),
|
|
Highlight.configure({ HTMLAttributes: { class: "bg-yellow-200" } }),
|
|
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
Markdown,
|
|
],
|
|
immediatelyRender: false,
|
|
content: content || "<p>Start typing your documentation notes here...</p>",
|
|
editorProps: {
|
|
attributes: {
|
|
class: "prose max-w-none focus:outline-none min-h-[300px]",
|
|
},
|
|
},
|
|
});
|
|
|
|
const toggleAppLink = (appId: string) => {
|
|
setLinkedAppIds((prev) =>
|
|
prev.includes(appId)
|
|
? prev.filter((id) => id !== appId)
|
|
: [...prev, appId],
|
|
);
|
|
};
|
|
|
|
const handleSave = () => {
|
|
if (!editor) return;
|
|
const htmlOutput = editor.getHTML();
|
|
onSave(htmlOutput, linkedAppIds);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4 w-full">
|
|
<div className="flex justify-between items-center pb-2 border-b border-zinc-100">
|
|
<span className="text-sm text-zinc-400 font-medium">Draft Session</span>
|
|
<button
|
|
onClick={handleSave}
|
|
className="px-4 py-1.5 bg-emerald-600 hover:bg-emerald-700 text-white font-medium text-sm rounded-md transition-colors"
|
|
>
|
|
Save Changes
|
|
</button>
|
|
</div>
|
|
|
|
<Menubar editor={editor} />
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 items-start">
|
|
<div className="md:col-span-3 border border-zinc-200 rounded-lg p-4 bg-white shadow-sm min-h-100">
|
|
<EditorContent editor={editor} />
|
|
</div>
|
|
|
|
<div className="md:col-span-1 bg-zinc-50 border border-zinc-200 rounded-lg p-4 flex flex-col gap-3">
|
|
<div>
|
|
<h3 className="font-semibold text-zinc-700 text-sm">
|
|
Document Assets
|
|
</h3>
|
|
<p className="text-xs text-zinc-400 mt-0.5">
|
|
Click apps to bind/unbind them to this entire document.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-2 mt-2">
|
|
{availableApps.map((app) => {
|
|
const isLinked = linkedAppIds.includes(app.id);
|
|
return (
|
|
<button
|
|
key={app.id}
|
|
type="button"
|
|
onClick={() => toggleAppLink(app.id)}
|
|
className={`w-full text-left p-3 border rounded-md transition relative overflow-hidden ${
|
|
isLinked
|
|
? "border-emerald-500 bg-emerald-50/30"
|
|
: "bg-white border-zinc-200 hover:border-zinc-300"
|
|
}`}
|
|
>
|
|
{isLinked && (
|
|
<div className="absolute top-0 right-0 h-0 w-0 border-t-[16px] border-t-emerald-500 border-l-[16px] border-l-transparent" />
|
|
)}
|
|
|
|
<div
|
|
className={`font-medium text-xs ${isLinked ? "text-emerald-700" : "text-zinc-800"}`}
|
|
>
|
|
{app.name}
|
|
</div>
|
|
<div className="text-[10px] text-zinc-400 font-mono truncate mt-0.5">
|
|
{app.id}
|
|
</div>
|
|
{app.description && (
|
|
<div className="text-[11px] text-zinc-500 mt-1 line-clamp-1">
|
|
{app.description}
|
|
</div>
|
|
)}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|