might work, basic writing page with linking. no saving.

This commit is contained in:
2026-06-28 10:43:47 -05:00
parent 914c9fbac8
commit c04738f9c4
12 changed files with 745 additions and 3 deletions
+142
View File
@@ -0,0 +1,142 @@
"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>
);
}