linking works

This commit is contained in:
2026-06-28 11:14:23 -05:00
parent c04738f9c4
commit 8134ec117f
5 changed files with 86 additions and 52 deletions
+16 -3
View File
@@ -20,10 +20,21 @@ const getApps = async (): Promise<HomelabApp[]> => {
return (await invoke("get_app")) as HomelabApp[];
};
export default function TextEditor({ content, onSave }: TextEditorProps) {
export default function TextEditor({
content,
onSave,
initialLinkedAppIds = [],
}: TextEditorProps) {
const [availableApps, setAvailableApps] = useState<HomelabApp[]>([]);
// Track document-wide linked apps here
const [linkedAppIds, setLinkedAppIds] = useState<string[]>([]);
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 () => {
@@ -33,6 +44,8 @@ export default function TextEditor({ content, onSave }: TextEditorProps) {
fetchApps();
}, []);
// ... rest of your TextEditor component stays the same
const editor = useEditor({
extensions: [
StarterKit.configure({
+37 -33
View File
@@ -14,80 +14,84 @@ import {
} from "lucide-react";
import { Toggle } from "../ui/toggle";
export default function Menubar({ editor }: { editor: Editor | null }) {
interface MenubarProps {
editor: Editor | null;
}
// 1. The main exported component remains safe and handles the null state cleanly
export default function Menubar({ editor }: MenubarProps) {
if (!editor) {
return null;
}
// Only render the inner menu once the editor is explicitly available
return <MenubarInner editor={editor} />;
}
// 2. The inner component runs safely because 'editor' is guaranteed to be an Editor instance
function MenubarInner({ editor }: { editor: Editor }) {
const editorState = useEditorState({
editor,
editor, // Now safely guaranteed never to be null or undefined
selector: (ctx) => {
return {
// Text formatting
isBold: ctx.editor.isActive("bold") ?? false,
isItalic: ctx.editor.isActive("italic") ?? false,
isStrike: ctx.editor.isActive("strike") ?? false,
isHighlight: ctx.editor.isActive("highlight") ?? false,
isBold: ctx.editor.isActive("bold"),
isItalic: ctx.editor.isActive("italic"),
isStrike: ctx.editor.isActive("strike"),
isHighlight: ctx.editor.isActive("highlight"),
// Text alignment
isAlignLeft: ctx.editor.isActive({ textAlign: "left" }) ?? false,
isAlignCenter: ctx.editor.isActive({ textAlign: "center" }) ?? false,
isAlignRight: ctx.editor.isActive({ textAlign: "right" }) ?? false,
isAlignJustify: ctx.editor.isActive({ textAlign: "justify" }) ?? false,
isAlignLeft: ctx.editor.isActive({ textAlign: "left" }),
isAlignCenter: ctx.editor.isActive({ textAlign: "center" }),
isAlignRight: ctx.editor.isActive({ textAlign: "right" }),
isAlignJustify: ctx.editor.isActive({ textAlign: "justify" }),
// Block types
isParagraph: ctx.editor.isActive("paragraph") ?? false,
isHeading1: ctx.editor.isActive("heading", { level: 1 }) ?? false,
isHeading2: ctx.editor.isActive("heading", { level: 2 }) ?? false,
isHeading3: ctx.editor.isActive("heading", { level: 3 }) ?? false,
isParagraph: ctx.editor.isActive("paragraph"),
isHeading1: ctx.editor.isActive("heading", { level: 1 }),
isHeading2: ctx.editor.isActive("heading", { level: 2 }),
isHeading3: ctx.editor.isActive("heading", { level: 3 }),
};
},
});
const options: {
icon: React.ReactNode;
onClick: () => void;
pressed: boolean;
}[] = [
const options = [
{
icon: <Heading1 className="size-4" />,
onClick: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
pressed: editor.isActive("heading", { level: 1 }),
pressed: editorState.isHeading1,
},
{
icon: <Heading2 className="size-4" />,
onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
pressed: editor.isActive("heading", { level: 2 }),
pressed: editorState.isHeading2,
},
{
icon: <Heading3 className="size-4" />,
onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
pressed: editor.isActive("heading", { level: 3 }),
pressed: editorState.isHeading3,
},
{
icon: <Bold className="size-4" />,
onClick: () => editor.chain().focus().toggleBold().run(),
pressed: editor.isActive("bold"),
pressed: editorState.isBold,
},
{
icon: <Italic className="size-4" />,
onClick: () => editor.chain().focus().toggleItalic().run(),
pressed: editor.isActive("italic"),
pressed: editorState.isItalic,
},
{
icon: <AlignLeft className="size-4" />,
onClick: () => editor.chain().focus().toggleTextAlign("left").run(),
pressed: editor.isActive({ textAlign: "left" }),
pressed: editorState.isAlignLeft,
},
{
icon: <AlignCenter className="size-4" />,
onClick: () => editor.chain().focus().toggleTextAlign("center").run(),
pressed: editor.isActive({ textAlign: "center" }),
pressed: editorState.isAlignCenter,
},
{
icon: <AlignRight className="size-4" />,
onClick: () => editor.chain().focus().toggleTextAlign("right").run(),
pressed: editor.isActive({ textAlign: "right" }),
pressed: editorState.isAlignRight,
},
{
icon: <List className="size-4" />,
@@ -97,17 +101,17 @@ export default function Menubar({ editor }: { editor: Editor | null }) {
{
icon: <ListOrdered className="size-4" />,
onClick: () => editor.chain().focus().toggleOrderedList().run(),
pressed: editor.isActive("orderList"),
pressed: editor.isActive("orderedList"),
},
{
icon: <Highlighter className="size-4" />,
onClick: () => editor.chain().focus().toggleHighlight().run(),
pressed: editor.isActive("highlight"),
pressed: editorState.isHighlight,
},
];
return (
<div className="border rounded-md p-1 mb-1 bg-slate-50 space-x-2 z-50">
<div className="border rounded-md p-1 mb-1 bg-slate-50 space-x-2 z-50 flex items-center">
{options.map((option, index) => (
<Toggle key={index} pressed={option.pressed} onClick={option.onClick}>
{option.icon}