linking works
This commit is contained in:
+26
-10
@@ -1,12 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, Suspense } from "react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import TextEditor from "@/components/text-editor";
|
import TextEditor from "@/components/text-editor";
|
||||||
import { HomelabApp } from "@/types"; // Adjust path if needed
|
import { HomelabApp } from "@/types";
|
||||||
|
|
||||||
export default function WritePage() {
|
function WritePageContent() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const appQuery = searchParams.get("app");
|
const appQuery = searchParams.get("app");
|
||||||
|
|
||||||
@@ -21,12 +21,13 @@ export default function WritePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch apps to find the one matching the query parameter string
|
|
||||||
const apps = (await invoke("get_app")) as HomelabApp[];
|
const apps = (await invoke("get_app")) as HomelabApp[];
|
||||||
|
|
||||||
|
// Match checking both UUID structure and sanitized string names
|
||||||
const matchedApp = apps.find(
|
const matchedApp = apps.find(
|
||||||
(app) =>
|
(app) =>
|
||||||
app.id === appQuery ||
|
app.id === appQuery ||
|
||||||
app.name.toLowerCase() === appQuery.toLowerCase(),
|
app.name.toLowerCase().trim() === appQuery.toLowerCase().trim(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (matchedApp) {
|
if (matchedApp) {
|
||||||
@@ -56,13 +57,28 @@ export default function WritePage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextEditor
|
||||||
|
content=""
|
||||||
|
onSave={onSave}
|
||||||
|
initialLinkedAppIds={initialAppIds}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Main Page export wrapped in a Suspense boundary
|
||||||
|
export default function WritePage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col p-2">
|
<div className="flex flex-col p-2">
|
||||||
<TextEditor
|
<Suspense
|
||||||
content=""
|
fallback={
|
||||||
onSave={onSave}
|
<div className="p-4 text-sm text-zinc-400">
|
||||||
initialLinkedAppIds={initialAppIds}
|
Loading router state...
|
||||||
/>
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<WritePageContent />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import DeleteDialog from "./delete-dialog";
|
import DeleteDialog from "./delete-dialog";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -22,6 +23,8 @@ export default function ActionMenu({ id }: Props) {
|
|||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TooltipProvider delayDuration={150}>
|
<TooltipProvider delayDuration={150}>
|
||||||
@@ -69,7 +72,9 @@ export default function ActionMenu({ id }: Props) {
|
|||||||
<button
|
<button
|
||||||
className={buttonClass}
|
className={buttonClass}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Blog
|
let link = `/write/new?app=${id}`;
|
||||||
|
console.log(link);
|
||||||
|
router.push(link);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Pencil className="h-5 w-5" />
|
<Pencil className="h-5 w-5" />
|
||||||
|
|||||||
@@ -90,11 +90,7 @@ export default function AppCard({ app }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-2xl font-bold">{app.name}</h3>
|
<h3 className="text-2xl font-bold">{app.name}</h3>
|
||||||
<a
|
<a target="_blank" href={`http://${app.address}`}>
|
||||||
className={`${hovered && "text-blue-600"} transition-colors duration-200`}
|
|
||||||
target="_blank"
|
|
||||||
href={`http://${app.address}`}
|
|
||||||
>
|
|
||||||
{app.address}
|
{app.address}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,21 @@ const getApps = async (): Promise<HomelabApp[]> => {
|
|||||||
return (await invoke("get_app")) as 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[]>([]);
|
const [availableApps, setAvailableApps] = useState<HomelabApp[]>([]);
|
||||||
// Track document-wide linked apps here
|
const [linkedAppIds, setLinkedAppIds] =
|
||||||
const [linkedAppIds, setLinkedAppIds] = useState<string[]>([]);
|
useState<string[]>(initialLinkedAppIds);
|
||||||
|
|
||||||
|
// 🔄 FIX: Watch for updates to initialLinkedAppIds once fetched from the URL
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialLinkedAppIds.length > 0) {
|
||||||
|
setLinkedAppIds(initialLinkedAppIds);
|
||||||
|
}
|
||||||
|
}, [initialLinkedAppIds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchApps = async () => {
|
const fetchApps = async () => {
|
||||||
@@ -33,6 +44,8 @@ export default function TextEditor({ content, onSave }: TextEditorProps) {
|
|||||||
fetchApps();
|
fetchApps();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// ... rest of your TextEditor component stays the same
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
|
|||||||
@@ -14,80 +14,84 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Toggle } from "../ui/toggle";
|
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) {
|
if (!editor) {
|
||||||
return null;
|
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({
|
const editorState = useEditorState({
|
||||||
editor,
|
editor, // Now safely guaranteed never to be null or undefined
|
||||||
selector: (ctx) => {
|
selector: (ctx) => {
|
||||||
return {
|
return {
|
||||||
// Text formatting
|
isBold: ctx.editor.isActive("bold"),
|
||||||
isBold: ctx.editor.isActive("bold") ?? false,
|
isItalic: ctx.editor.isActive("italic"),
|
||||||
isItalic: ctx.editor.isActive("italic") ?? false,
|
isStrike: ctx.editor.isActive("strike"),
|
||||||
isStrike: ctx.editor.isActive("strike") ?? false,
|
isHighlight: ctx.editor.isActive("highlight"),
|
||||||
isHighlight: ctx.editor.isActive("highlight") ?? false,
|
|
||||||
|
|
||||||
// Text alignment
|
isAlignLeft: ctx.editor.isActive({ textAlign: "left" }),
|
||||||
isAlignLeft: ctx.editor.isActive({ textAlign: "left" }) ?? false,
|
isAlignCenter: ctx.editor.isActive({ textAlign: "center" }),
|
||||||
isAlignCenter: ctx.editor.isActive({ textAlign: "center" }) ?? false,
|
isAlignRight: ctx.editor.isActive({ textAlign: "right" }),
|
||||||
isAlignRight: ctx.editor.isActive({ textAlign: "right" }) ?? false,
|
isAlignJustify: ctx.editor.isActive({ textAlign: "justify" }),
|
||||||
isAlignJustify: ctx.editor.isActive({ textAlign: "justify" }) ?? false,
|
|
||||||
|
|
||||||
// Block types
|
isParagraph: ctx.editor.isActive("paragraph"),
|
||||||
isParagraph: ctx.editor.isActive("paragraph") ?? false,
|
isHeading1: ctx.editor.isActive("heading", { level: 1 }),
|
||||||
isHeading1: ctx.editor.isActive("heading", { level: 1 }) ?? false,
|
isHeading2: ctx.editor.isActive("heading", { level: 2 }),
|
||||||
isHeading2: ctx.editor.isActive("heading", { level: 2 }) ?? false,
|
isHeading3: ctx.editor.isActive("heading", { level: 3 }),
|
||||||
isHeading3: ctx.editor.isActive("heading", { level: 3 }) ?? false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const options: {
|
const options = [
|
||||||
icon: React.ReactNode;
|
|
||||||
onClick: () => void;
|
|
||||||
pressed: boolean;
|
|
||||||
}[] = [
|
|
||||||
{
|
{
|
||||||
icon: <Heading1 className="size-4" />,
|
icon: <Heading1 className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
onClick: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
||||||
pressed: editor.isActive("heading", { level: 1 }),
|
pressed: editorState.isHeading1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Heading2 className="size-4" />,
|
icon: <Heading2 className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||||
pressed: editor.isActive("heading", { level: 2 }),
|
pressed: editorState.isHeading2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Heading3 className="size-4" />,
|
icon: <Heading3 className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
||||||
pressed: editor.isActive("heading", { level: 3 }),
|
pressed: editorState.isHeading3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Bold className="size-4" />,
|
icon: <Bold className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleBold().run(),
|
onClick: () => editor.chain().focus().toggleBold().run(),
|
||||||
pressed: editor.isActive("bold"),
|
pressed: editorState.isBold,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Italic className="size-4" />,
|
icon: <Italic className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleItalic().run(),
|
onClick: () => editor.chain().focus().toggleItalic().run(),
|
||||||
pressed: editor.isActive("italic"),
|
pressed: editorState.isItalic,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <AlignLeft className="size-4" />,
|
icon: <AlignLeft className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleTextAlign("left").run(),
|
onClick: () => editor.chain().focus().toggleTextAlign("left").run(),
|
||||||
pressed: editor.isActive({ textAlign: "left" }),
|
pressed: editorState.isAlignLeft,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <AlignCenter className="size-4" />,
|
icon: <AlignCenter className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleTextAlign("center").run(),
|
onClick: () => editor.chain().focus().toggleTextAlign("center").run(),
|
||||||
pressed: editor.isActive({ textAlign: "center" }),
|
pressed: editorState.isAlignCenter,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <AlignRight className="size-4" />,
|
icon: <AlignRight className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleTextAlign("right").run(),
|
onClick: () => editor.chain().focus().toggleTextAlign("right").run(),
|
||||||
pressed: editor.isActive({ textAlign: "right" }),
|
pressed: editorState.isAlignRight,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <List className="size-4" />,
|
icon: <List className="size-4" />,
|
||||||
@@ -97,17 +101,17 @@ export default function Menubar({ editor }: { editor: Editor | null }) {
|
|||||||
{
|
{
|
||||||
icon: <ListOrdered className="size-4" />,
|
icon: <ListOrdered className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleOrderedList().run(),
|
onClick: () => editor.chain().focus().toggleOrderedList().run(),
|
||||||
pressed: editor.isActive("orderList"),
|
pressed: editor.isActive("orderedList"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Highlighter className="size-4" />,
|
icon: <Highlighter className="size-4" />,
|
||||||
onClick: () => editor.chain().focus().toggleHighlight().run(),
|
onClick: () => editor.chain().focus().toggleHighlight().run(),
|
||||||
pressed: editor.isActive("highlight"),
|
pressed: editorState.isHighlight,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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) => (
|
{options.map((option, index) => (
|
||||||
<Toggle key={index} pressed={option.pressed} onClick={option.onClick}>
|
<Toggle key={index} pressed={option.pressed} onClick={option.onClick}>
|
||||||
{option.icon}
|
{option.icon}
|
||||||
|
|||||||
Reference in New Issue
Block a user