Files
desktop-app/components/text-editor/index.tsx
T
2026-06-28 11:14:23 -05:00

159 lines
5.3 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"; // Added menubar import back
interface TextEditorProps {
content: string;
onSave: (content: string, linkedAppIds: string[]) => void;
initialLinkedAppIds?: string[];
}
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[]>([]);
const [linkedAppIds, setLinkedAppIds] =
useState<string[]>(initialLinkedAppIds);
// 🔄 FIX: Watch for updates to initialLinkedAppIds once fetched from the URL
useEffect(() => {
if (initialLinkedAppIds.length > 0) {
setLinkedAppIds(initialLinkedAppIds);
}
}, [initialLinkedAppIds]);
useEffect(() => {
const fetchApps = async () => {
const apps = await getApps();
setAvailableApps(apps);
};
fetchApps();
}, []);
// ... rest of your TextEditor component stays the same
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) // Unlink if already added
: [...prev, appId], // Link if not added
);
};
const handleSave = () => {
if (!editor) return;
const htmlOutput = editor.getHTML();
// Pass both the layout structure and the entire linked app set
onSave(htmlOutput, linkedAppIds);
};
return (
<div className="flex flex-col gap-4 w-full">
{/* Top action bar */}
<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>
{/* Editor Controls */}
<Menubar editor={editor} />
{/* Main split-view workspace */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 items-start">
{/* Left Side: Text Canvas */}
<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>
{/* Right Side: Document-Wide Reference Panel */}
<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"
}`}
>
{/* Subtle active border indicator */}
{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>
);
}