diff --git a/app/(loggedInRoutes)/admin/checklist/[uuid]/page.tsx b/app/(loggedInRoutes)/admin/checklist/[uuid]/page.tsx index fcbfc361..0803bc3a 100644 --- a/app/(loggedInRoutes)/admin/checklist/[uuid]/page.tsx +++ b/app/(loggedInRoutes)/admin/checklist/[uuid]/page.tsx @@ -24,7 +24,7 @@ export const dynamic = "force-dynamic"; export async function generateMetadata(props: AdminChecklistPageProps): Promise { const params = await props.params; const { uuid } = params; - return getMedatadaTitle(Modes.CHECKLISTS, uuid, "Admin"); + return getMedatadaTitle(Modes.CHECKLISTS, uuid); } export default async function AdminChecklistPage(props: AdminChecklistPageProps) { diff --git a/app/(loggedInRoutes)/admin/note/[uuid]/page.tsx b/app/(loggedInRoutes)/admin/note/[uuid]/page.tsx index 666eaf92..9f39649d 100644 --- a/app/(loggedInRoutes)/admin/note/[uuid]/page.tsx +++ b/app/(loggedInRoutes)/admin/note/[uuid]/page.tsx @@ -24,7 +24,7 @@ export const dynamic = "force-dynamic"; export async function generateMetadata(props: AdminNotePageProps): Promise { const params = await props.params; const { uuid } = params; - return getMedatadaTitle(Modes.NOTES, uuid, "Admin"); + return getMedatadaTitle(Modes.NOTES, uuid); } export default async function AdminNotePage(props: AdminNotePageProps) { diff --git a/app/(loggedInRoutes)/checklist/[...categoryPath]/page.tsx b/app/(loggedInRoutes)/checklist/[...categoryPath]/page.tsx index 09d1fee8..c28e21d1 100644 --- a/app/(loggedInRoutes)/checklist/[...categoryPath]/page.tsx +++ b/app/(loggedInRoutes)/checklist/[...categoryPath]/page.tsx @@ -1,17 +1,9 @@ -import { redirect } from "next/navigation"; -import { getListById } from "@/app/_server/actions/checklist"; -import { getCategories } from "@/app/_server/actions/category"; -import { getCurrentUser, canAccessAllContent } from "@/app/_server/actions/users"; -import { ChecklistClient } from "@/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient"; +import { redirect, permanentRedirect } from "next/navigation"; import { Modes } from "@/app/_types/enums"; -import type { Metadata } from "next"; -import { getMedatadaTitle } from "@/app/_server/actions/config"; -import { decodeCategoryPath, decodeId } from "@/app/_utils/global-utils"; -import { PermissionsProvider } from "@/app/_providers/PermissionsProvider"; -import { MetadataProvider } from "@/app/_providers/MetadataProvider"; -import { sanitizeUserForClient } from "@/app/_utils/user-sanitize-utils"; +import { legacyResolve } from "@/app/_server/lib/legacy-lookup"; +import { decodeCategoryPath } from "@/app/_utils/global-utils"; -interface ChecklistPageProps { +interface LegacyChecklistPageProps { params: Promise<{ categoryPath: string[]; }>; @@ -19,71 +11,19 @@ interface ChecklistPageProps { export const dynamic = "force-dynamic"; -export async function generateMetadata(props: ChecklistPageProps): Promise { +export default async function LegacyChecklistPage( + props: LegacyChecklistPageProps, +) { const params = await props.params; const { categoryPath } = params; - const id = decodeId(categoryPath[categoryPath.length - 1]); - const encodedCategoryPath = categoryPath.slice(0, -1).join("/"); - const category = - categoryPath.length === 1 - ? "Uncategorized" - : decodeCategoryPath(encodedCategoryPath); + const id = decodeURIComponent(categoryPath[categoryPath.length - 1]); + const category = decodeCategoryPath(categoryPath.slice(0, -1).join("/")); - return getMedatadaTitle(Modes.CHECKLISTS, id, category); -} - -export default async function ChecklistPage(props: ChecklistPageProps) { - const params = await props.params; - const { categoryPath } = params; - const id = decodeId(categoryPath[categoryPath.length - 1]); - const encodedCategoryPath = categoryPath.slice(0, -1).join("/"); - const category = - categoryPath.length === 1 - ? "Uncategorized" - : decodeCategoryPath(encodedCategoryPath); - const userRecord = await getCurrentUser(); - const username = userRecord?.username || ""; - const hasContentAccess = await canAccessAllContent(); - - const categoriesResult = await getCategories(Modes.CHECKLISTS); - - let checklist = await getListById(id, username, category); + const uuid = await legacyResolve(Modes.CHECKLISTS, category, id); - if (!checklist && hasContentAccess) { - checklist = await getListById(id, undefined, category); + if (uuid) { + permanentRedirect(`/checklist/${uuid}`); } - if (!checklist) { - redirect("/"); - } - - const categories = - categoriesResult.success && categoriesResult.data - ? categoriesResult.data - : []; - - const metadata = { - id: checklist.id, - uuid: checklist.uuid, - title: checklist.title, - category: checklist.category || "Uncategorized", - owner: checklist.owner, - createdAt: checklist.createdAt, - updatedAt: checklist.updatedAt, - type: "checklist" as const, - }; - - const user = sanitizeUserForClient(userRecord); - - return ( - - - - - - ); + redirect("/"); } diff --git a/app/(loggedInRoutes)/checklist/[uuid]/page.tsx b/app/(loggedInRoutes)/checklist/[uuid]/page.tsx new file mode 100644 index 00000000..93189a96 --- /dev/null +++ b/app/(loggedInRoutes)/checklist/[uuid]/page.tsx @@ -0,0 +1,92 @@ +import { redirect, permanentRedirect } from "next/navigation"; +import { getListById } from "@/app/_server/actions/checklist"; +import { getCategories } from "@/app/_server/actions/category"; +import { getCurrentUser, canAccessAllContent } from "@/app/_server/actions/users"; +import { ChecklistClient } from "@/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient"; +import { Modes } from "@/app/_types/enums"; +import type { Metadata } from "next"; +import { getMedatadaTitle } from "@/app/_server/actions/config"; +import { isUuid } from "@/app/_consts/identity"; +import { UNCATEGORIZED } from "@/app/_consts/notes"; +import { PermissionsProvider } from "@/app/_providers/PermissionsProvider"; +import { MetadataProvider } from "@/app/_providers/MetadataProvider"; +import { sanitizeUserForClient } from "@/app/_utils/user-sanitize-utils"; + +interface ChecklistPageProps { + params: Promise<{ + uuid: string; + }>; +} + +export const dynamic = "force-dynamic"; + +export async function generateMetadata(props: ChecklistPageProps): Promise { + const params = await props.params; + return getMedatadaTitle(Modes.CHECKLISTS, params.uuid); +} + +export default async function ChecklistPage(props: ChecklistPageProps) { + const params = await props.params; + const { uuid } = params; + + if (!isUuid(uuid)) { + const { legacyResolve } = await import("@/app/_server/lib/legacy-lookup"); + const resolved = await legacyResolve( + Modes.CHECKLISTS, + UNCATEGORIZED, + decodeURIComponent(uuid), + ); + + if (resolved) { + permanentRedirect(`/checklist/${resolved}`); + } + + redirect("/"); + } + + const userRecord = await getCurrentUser(); + const username = userRecord?.username || ""; + const hasContentAccess = await canAccessAllContent(); + + const categoriesResult = await getCategories(Modes.CHECKLISTS); + + let checklist = await getListById(uuid, username); + + if (!checklist && hasContentAccess) { + checklist = await getListById(uuid); + } + + if (!checklist) { + redirect("/"); + } + + const categories = + categoriesResult.success && categoriesResult.data + ? categoriesResult.data + : []; + + const metadata = { + id: checklist.id, + uuid: checklist.uuid, + title: checklist.title, + category: checklist.category || UNCATEGORIZED, + owner: checklist.owner, + createdAt: checklist.createdAt, + updatedAt: checklist.updatedAt, + type: "checklist" as const, + }; + + const user = sanitizeUserForClient(userRecord); + + return ( + + + + + + ); +} diff --git a/app/(loggedInRoutes)/note/[...categoryPath]/page.tsx b/app/(loggedInRoutes)/note/[...categoryPath]/page.tsx index 46ccab8e..9a93d8ef 100644 --- a/app/(loggedInRoutes)/note/[...categoryPath]/page.tsx +++ b/app/(loggedInRoutes)/note/[...categoryPath]/page.tsx @@ -1,22 +1,9 @@ -import { redirect } from "next/navigation"; -import { - CheckForNeedsMigration, - getNoteById, -} from "@/app/_server/actions/note"; -import { - getCurrentUser, - canAccessAllContent, -} from "@/app/_server/actions/users"; -import { NoteClient } from "@/app/_components/FeatureComponents/Notes/NoteClient"; +import { redirect, permanentRedirect } from "next/navigation"; import { Modes } from "@/app/_types/enums"; -import { getCategories } from "@/app/_server/actions/category"; -import type { Metadata } from "next"; -import { getMedatadaTitle } from "@/app/_server/actions/config"; -import { decodeCategoryPath, decodeId } from "@/app/_utils/global-utils"; -import { PermissionsProvider } from "@/app/_providers/PermissionsProvider"; -import { MetadataProvider } from "@/app/_providers/MetadataProvider"; +import { legacyResolve } from "@/app/_server/lib/legacy-lookup"; +import { decodeCategoryPath } from "@/app/_utils/global-utils"; -interface NotePageProps { +interface LegacyNotePageProps { params: Promise<{ categoryPath: string[]; }>; @@ -24,67 +11,17 @@ interface NotePageProps { export const dynamic = "force-dynamic"; -export async function generateMetadata(props: NotePageProps): Promise { +export default async function LegacyNotePage(props: LegacyNotePageProps) { const params = await props.params; const { categoryPath } = params; - const id = decodeId(categoryPath[categoryPath.length - 1]); - const encodedCategoryPath = categoryPath.slice(0, -1).join("/"); - const category = - categoryPath.length === 1 - ? "Uncategorized" - : decodeCategoryPath(encodedCategoryPath); + const id = decodeURIComponent(categoryPath[categoryPath.length - 1]); + const category = decodeCategoryPath(categoryPath.slice(0, -1).join("/")); - return getMedatadaTitle(Modes.NOTES, id, category); -} - -export default async function NotePage(props: NotePageProps) { - const params = await props.params; - const { categoryPath } = params; - const id = decodeId(categoryPath[categoryPath.length - 1]); - const encodedCategoryPath = categoryPath.slice(0, -1).join("/"); - const category = - categoryPath.length === 1 - ? "Uncategorized" - : decodeCategoryPath(encodedCategoryPath); - const user = await getCurrentUser(); - const username = user?.username || ""; - const hasContentAccess = await canAccessAllContent(); - - await CheckForNeedsMigration(); - - const categoriesResult = await getCategories(Modes.NOTES); + const uuid = await legacyResolve(Modes.NOTES, category, id); - let note = await getNoteById(id, category, username); - - if (!note && hasContentAccess) { - note = await getNoteById(id, category); - } - - if (!note) { - redirect("/"); + if (uuid) { + permanentRedirect(`/note/${uuid}`); } - const docsCategories = - categoriesResult.success && categoriesResult.data - ? categoriesResult.data - : []; - - const metadata = { - id: note.id, - uuid: note.uuid, - title: note.title, - category: note.category || "Uncategorized", - owner: note.owner, - createdAt: note.createdAt, - updatedAt: note.updatedAt, - type: "note" as const, - }; - - return ( - - - - - - ); + redirect("/"); } diff --git a/app/(loggedInRoutes)/note/[uuid]/page.tsx b/app/(loggedInRoutes)/note/[uuid]/page.tsx new file mode 100644 index 00000000..8c81bc09 --- /dev/null +++ b/app/(loggedInRoutes)/note/[uuid]/page.tsx @@ -0,0 +1,93 @@ +import { redirect, permanentRedirect } from "next/navigation"; +import { + CheckForNeedsMigration, + getNoteById, +} from "@/app/_server/actions/note"; +import { + getCurrentUser, + canAccessAllContent, +} from "@/app/_server/actions/users"; +import { NoteClient } from "@/app/_components/FeatureComponents/Notes/NoteClient"; +import { Modes } from "@/app/_types/enums"; +import { getCategories } from "@/app/_server/actions/category"; +import type { Metadata } from "next"; +import { getMedatadaTitle } from "@/app/_server/actions/config"; +import { isUuid } from "@/app/_consts/identity"; +import { UNCATEGORIZED } from "@/app/_consts/notes"; +import { PermissionsProvider } from "@/app/_providers/PermissionsProvider"; +import { MetadataProvider } from "@/app/_providers/MetadataProvider"; + +interface NotePageProps { + params: Promise<{ + uuid: string; + }>; +} + +export const dynamic = "force-dynamic"; + +export async function generateMetadata(props: NotePageProps): Promise { + const params = await props.params; + return getMedatadaTitle(Modes.NOTES, params.uuid); +} + +export default async function NotePage(props: NotePageProps) { + const params = await props.params; + const { uuid } = params; + + if (!isUuid(uuid)) { + const { legacyResolve } = await import("@/app/_server/lib/legacy-lookup"); + const resolved = await legacyResolve( + Modes.NOTES, + UNCATEGORIZED, + decodeURIComponent(uuid), + ); + + if (resolved) { + permanentRedirect(`/note/${resolved}`); + } + + redirect("/"); + } + + const user = await getCurrentUser(); + const username = user?.username || ""; + const hasContentAccess = await canAccessAllContent(); + + await CheckForNeedsMigration(); + + const categoriesResult = await getCategories(Modes.NOTES); + + let note = await getNoteById(uuid, username); + + if (!note && hasContentAccess) { + note = await getNoteById(uuid); + } + + if (!note) { + redirect("/"); + } + + const docsCategories = + categoriesResult.success && categoriesResult.data + ? categoriesResult.data + : []; + + const metadata = { + id: note.id, + uuid: note.uuid, + title: note.title, + category: note.category || UNCATEGORIZED, + owner: note.owner, + createdAt: note.createdAt, + updatedAt: note.updatedAt, + type: "note" as const, + }; + + return ( + + + + + + ); +} diff --git a/app/_components/FeatureComponents/Admin/Parts/AdminContent.tsx b/app/_components/FeatureComponents/Admin/Parts/AdminContent.tsx index 0d3ab6e0..1f800451 100644 --- a/app/_components/FeatureComponents/Admin/Parts/AdminContent.tsx +++ b/app/_components/FeatureComponents/Admin/Parts/AdminContent.tsx @@ -13,7 +13,6 @@ import { AdminContentColumn } from "./AdminContentColumn"; import { ExportContent } from "./AdminExport"; import { Accordion } from "@/app/_components/GlobalComponents/Layout/Accordion"; import { UserAvatar } from "@/app/_components/GlobalComponents/User/UserAvatar"; -import { buildCategoryPath } from "@/app/_utils/global-utils"; import { rebuildLinkIndex } from "@/app/_server/actions/link"; import { updateTagsFromContent } from "@/app/_server/actions/tags"; import { Button } from "@/app/_components/GlobalComponents/Buttons/Button"; diff --git a/app/_components/FeatureComponents/Checklists/ChecklistsPageClient.tsx b/app/_components/FeatureComponents/Checklists/ChecklistsPageClient.tsx index dc037ddf..9fa95a79 100644 --- a/app/_components/FeatureComponents/Checklists/ChecklistsPageClient.tsx +++ b/app/_components/FeatureComponents/Checklists/ChecklistsPageClient.tsx @@ -25,6 +25,7 @@ import { ChecklistListItem } from "@/app/_components/GlobalComponents/Cards/Chec import { ChecklistGridItem } from "@/app/_components/GlobalComponents/Cards/ChecklistGridItem"; import { useChecklistsFilter } from "@/app/_components/FeatureComponents/Checklists/ChecklistsClient"; import { isKanbanType } from "@/app/_types/enums"; +import { itemHref } from "@/app/_utils/global-utils"; interface ChecklistsPageClientProps { initialLists: Checklist[]; @@ -54,12 +55,13 @@ export const ChecklistsPageClient = ({ let filtered = [...initialLists]; if (checklistFilter === "pinned") { - const pinnedPaths = user?.pinnedLists || []; - filtered = filtered.filter((list) => { - const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.id}`; - const idPath = `${list.category || "Uncategorized"}/${list.id}`; - return pinnedPaths.includes(uuidPath) || pinnedPaths.includes(idPath); - }); + const pinnedEntries = user?.pinnedLists || []; + filtered = filtered.filter((list) => + pinnedEntries.some( + (entry) => + entry === list.uuid || entry.split("/").pop() === list.uuid, + ), + ); } else if (checklistFilter === "completed") { filtered = filtered.filter( (list) => @@ -135,15 +137,11 @@ export const ChecklistsPageClient = ({ ]); const handleTogglePin = async (list: Checklist) => { - if (!user || isTogglingPin === list.id) return; + if (!user || !list.uuid || isTogglingPin === list.uuid) return; - setIsTogglingPin(list.id); + setIsTogglingPin(list.uuid); try { - const result = await togglePin( - list.id, - list.category || "Uncategorized", - ItemTypes.CHECKLIST, - ); + const result = await togglePin(list.uuid, ItemTypes.CHECKLIST); if (result.success) { router.refresh(); } @@ -268,14 +266,15 @@ export const ChecklistsPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; - router.push(`/checklist/${categoryPath}`); - }} - isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}`, + onSelect={(list) => + router.push(itemHref(ItemTypes.CHECKLIST, list.uuid || "")) + } + isPinned={user?.pinnedLists?.some( + (entry) => + entry === list.uuid || + entry.split("/").pop() === list.uuid, )} onTogglePin={() => handleTogglePin(list)} /> @@ -287,14 +286,15 @@ export const ChecklistsPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; - router.push(`/checklist/${categoryPath}`); - }} - isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}`, + onSelect={(list) => + router.push(itemHref(ItemTypes.CHECKLIST, list.uuid || "")) + } + isPinned={user?.pinnedLists?.some( + (entry) => + entry === list.uuid || + entry.split("/").pop() === list.uuid, )} onTogglePin={() => handleTogglePin(list)} /> @@ -306,14 +306,15 @@ export const ChecklistsPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; - router.push(`/checklist/${categoryPath}`); - }} - isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}`, + onSelect={(list) => + router.push(itemHref(ItemTypes.CHECKLIST, list.uuid || "")) + } + isPinned={user?.pinnedLists?.some( + (entry) => + entry === list.uuid || + entry.split("/").pop() === list.uuid, )} onTogglePin={() => handleTogglePin(list)} /> diff --git a/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient.tsx b/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient.tsx index e6e1fa05..edb8a04f 100644 --- a/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient.tsx +++ b/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient.tsx @@ -18,7 +18,8 @@ import { useChecklist } from "@/app/_hooks/useChecklist"; import { isKanbanType, Modes } from "@/app/_types/enums"; import { useShortcut } from "@/app/_providers/ShortcutsProvider"; import { toggleArchive } from "@/app/_server/actions/dashboard"; -import { buildCategoryPath } from "@/app/_utils/global-utils"; +import { itemHref } from "@/app/_utils/global-utils"; +import { ItemTypes } from "@/app/_types/enums"; import { useTranslations } from "next-intl"; interface ChecklistClientProps { @@ -84,11 +85,7 @@ export const ChecklistClient = ({ const handleCloneConfirm = async (targetCategory: string) => { const formData = new FormData(); - formData.append("id", localChecklist.id); - formData.append( - "originalCategory", - localChecklist.category || "Uncategorized", - ); + formData.append("uuid", localChecklist.uuid || ""); formData.append("category", targetCategory || "Uncategorized"); if (localChecklist.owner) { formData.append("user", localChecklist.owner); @@ -97,13 +94,8 @@ export const ChecklistClient = ({ const { cloneChecklist } = await import("@/app/_server/actions/checklist"); const result = await cloneChecklist(formData); - if (result.success && result.data) { - router.push( - `/checklist/${buildCategoryPath( - result.data.category || "Uncategorized", - result.data.id, - )}`, - ); + if (result.success && result.data?.uuid) { + router.push(itemHref(ItemTypes.CHECKLIST, result.data.uuid)); router.refresh(); } }; @@ -219,8 +211,8 @@ export const ChecklistClient = ({ setShowCreateModal(false)} onCreated={(newChecklist) => { - if (newChecklist) { - router.push(`/checklist/${newChecklist.id}`); + if (newChecklist?.uuid) { + router.push(itemHref(ItemTypes.CHECKLIST, newChecklist.uuid)); } setShowCreateModal(false); router.refresh(); diff --git a/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader.tsx b/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader.tsx index 69017b6c..f630f65c 100644 --- a/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader.tsx +++ b/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader.tsx @@ -20,7 +20,6 @@ import { Button } from "@/app/_components/GlobalComponents/Buttons/Button"; import { Checklist } from "@/app/_types"; import { useChecklist } from "../../../../../_hooks/useChecklist"; import { DropdownMenu } from "@/app/_components/GlobalComponents/Dropdowns/DropdownMenu"; -import { encodeCategoryPath } from "@/app/_utils/global-utils"; import { useAppMode } from "@/app/_providers/AppModeProvider"; import { sharingInfo } from "@/app/_utils/sharing-utils"; import { ChecklistsTypes } from "@/app/_types/enums"; @@ -62,8 +61,7 @@ export const ChecklistHeader = ({ const { permissions } = usePermissions(); const [showSharedWithModal, setShowSharedWithModal] = useState(false); - const encodedCategory = encodeCategoryPath(metadata.category); - const itemDetails = sharingInfo(globalSharing, metadata.uuid || metadata.id, encodedCategory); + const itemDetails = sharingInfo(globalSharing, metadata.uuid || ""); const isShared = itemDetails.exists && itemDetails.sharedWith.length > 0; const sharedWith = itemDetails.sharedWith; @@ -91,7 +89,7 @@ export const ChecklistHeader = ({ size="sm" onClick={handleCopyId} className="h-6 w-6 p-0" - title={`Copy ID: ${checklist?.uuid || checklist?.id}`} + title={`Copy ID: ${checklist?.uuid}`} > {copied ? ( diff --git a/app/_components/FeatureComponents/Checklists/Parts/Common/LastModifiedCreatedInfo.tsx b/app/_components/FeatureComponents/Checklists/Parts/Common/LastModifiedCreatedInfo.tsx index 3997c70b..5dc9d7fb 100644 --- a/app/_components/FeatureComponents/Checklists/Parts/Common/LastModifiedCreatedInfo.tsx +++ b/app/_components/FeatureComponents/Checklists/Parts/Common/LastModifiedCreatedInfo.tsx @@ -2,7 +2,6 @@ import { Checklist, Item } from "@/app/_types"; import { PencilEdit02Icon, Clock01Icon } from "hugeicons-react"; import { UserAvatar } from "@/app/_components/GlobalComponents/User/UserAvatar"; import { useAppMode } from "@/app/_providers/AppModeProvider"; -import { encodeCategoryPath } from "@/app/_utils/global-utils"; import { usePreferredDateTime } from "@/app/_hooks/usePreferredDateTime"; import { useTranslations } from "next-intl"; @@ -18,14 +17,9 @@ const LastModifiedCreatedInfo = ({ const { allSharedItems } = useAppMode(); const { formatDateTimeString } = usePreferredDateTime(); const t = useTranslations(); - const encodedCategory = encodeCategoryPath( - checklist.category || "Uncategorized" - ); const isShared = allSharedItems?.checklists.some( - (sharedChecklist) => - sharedChecklist.id === checklist.id && - sharedChecklist.category === encodedCategory + (sharedChecklist) => sharedChecklist.uuid === checklist.uuid ); return ( <> diff --git a/app/_components/FeatureComponents/Checklists/Parts/Simple/ChecklistBody.tsx b/app/_components/FeatureComponents/Checklists/Parts/Simple/ChecklistBody.tsx index 2509f431..8a45d199 100644 --- a/app/_components/FeatureComponents/Checklists/Parts/Simple/ChecklistBody.tsx +++ b/app/_components/FeatureComponents/Checklists/Parts/Simple/ChecklistBody.tsx @@ -63,12 +63,11 @@ export const ChecklistBody = ({ return getReferences( linkIndex, localList.uuid, - localList.category, ItemTypes.CHECKLIST, notes, checklists ); - }, [linkIndex, localList.uuid, localList.category, notes, checklists]); + }, [linkIndex, localList.uuid, notes, checklists]); const onDragStart = (event: DragStartEvent) => { const findItem = (items: Item[], id: string): Item | undefined => { diff --git a/app/_components/FeatureComponents/Checklists/TasksPageClient.tsx b/app/_components/FeatureComponents/Checklists/TasksPageClient.tsx index 7aa4369a..0ee6158a 100644 --- a/app/_components/FeatureComponents/Checklists/TasksPageClient.tsx +++ b/app/_components/FeatureComponents/Checklists/TasksPageClient.tsx @@ -10,7 +10,8 @@ import { PlayCircleIcon, } from "hugeicons-react"; import { Checklist, SanitisedUser } from "@/app/_types"; -import { TaskStatus } from "@/app/_types/enums"; +import { ItemTypes, TaskStatus } from "@/app/_types/enums"; +import { itemHref } from "@/app/_utils/global-utils"; import { EmptyState } from "@/app/_components/GlobalComponents/Cards/EmptyState"; import { ChecklistCard } from "@/app/_components/GlobalComponents/Cards/ChecklistCard"; import { ChecklistListItem } from "@/app/_components/GlobalComponents/Cards/ChecklistListItem"; @@ -52,12 +53,13 @@ export const TasksPageClient = ({ let filtered = [...initialLists]; if (taskFilter === "pinned") { - const pinnedPaths = user?.pinnedLists || []; - filtered = filtered.filter((list) => { - const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.id}`; - const idPath = `${list.category || "Uncategorized"}/${list.id}`; - return pinnedPaths.includes(uuidPath) || pinnedPaths.includes(idPath); - }); + const pinnedEntries = user?.pinnedLists || []; + filtered = filtered.filter((list) => + pinnedEntries.some( + (entry) => + entry === list.uuid || entry.split("/").pop() === list.uuid, + ), + ); } else if (taskFilter === "completed") { filtered = filtered.filter( (list) => @@ -278,14 +280,15 @@ export const TasksPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; - router.push(`/checklist/${categoryPath}`); - }} - isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}` + onSelect={(list) => + router.push(itemHref(ItemTypes.CHECKLIST, list.uuid || "")) + } + isPinned={user?.pinnedLists?.some( + (entry) => + entry === list.uuid || + entry.split("/").pop() === list.uuid, )} onTogglePin={() => { }} /> @@ -297,14 +300,15 @@ export const TasksPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; - router.push(`/checklist/${categoryPath}`); - }} - isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}` + onSelect={(list) => + router.push(itemHref(ItemTypes.CHECKLIST, list.uuid || "")) + } + isPinned={user?.pinnedLists?.some( + (entry) => + entry === list.uuid || + entry.split("/").pop() === list.uuid, )} onTogglePin={() => { }} /> @@ -316,14 +320,15 @@ export const TasksPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; - router.push(`/checklist/${categoryPath}`); - }} - isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}` + onSelect={(list) => + router.push(itemHref(ItemTypes.CHECKLIST, list.uuid || "")) + } + isPinned={user?.pinnedLists?.some( + (entry) => + entry === list.uuid || + entry.split("/").pop() === list.uuid, )} onTogglePin={() => { }} /> diff --git a/app/_components/FeatureComponents/Home/HomeClient.tsx b/app/_components/FeatureComponents/Home/HomeClient.tsx index 1d820d81..057505e8 100644 --- a/app/_components/FeatureComponents/Home/HomeClient.tsx +++ b/app/_components/FeatureComponents/Home/HomeClient.tsx @@ -8,8 +8,8 @@ import { Layout } from "@/app/_components/GlobalComponents/Layout/Layout"; import { Checklist, Category, Note, SanitisedUser } from "@/app/_types"; import { useAppMode } from "@/app/_providers/AppModeProvider"; import { useShortcut } from "@/app/_providers/ShortcutsProvider"; -import { Modes } from "@/app/_types/enums"; -import { buildCategoryPath } from "@/app/_utils/global-utils"; +import { ItemTypes, Modes } from "@/app/_types/enums"; +import { itemHref } from "@/app/_utils/global-utils"; import { MobileHeader } from "@/app/_components/GlobalComponents/Layout/MobileHeader"; interface SharingStatus { @@ -70,11 +70,8 @@ export const HomeClient = ({ user={user} onCreateModal={handleOpenCreateModal} onSelectChecklist={(list) => { - const categoryPath = buildCategoryPath( - list.category || "Uncategorized", - list.id - ); - router.push(`/checklist/${categoryPath}`); + if (!list.uuid) return; + router.push(itemHref(ItemTypes.CHECKLIST, list.uuid)); }} /> )} @@ -86,11 +83,8 @@ export const HomeClient = ({ user={user} onCreateModal={handleOpenCreateModal} onSelectNote={(note) => { - const categoryPath = buildCategoryPath( - note.category || "Uncategorized", - note.id - ); - router.push(`/note/${categoryPath}`); + if (!note.uuid) return; + router.push(itemHref(ItemTypes.NOTE, note.uuid)); }} /> )} diff --git a/app/_components/FeatureComponents/Home/Parts/ChecklistHome.tsx b/app/_components/FeatureComponents/Home/Parts/ChecklistHome.tsx index 6021979f..ebef1c59 100644 --- a/app/_components/FeatureComponents/Home/Parts/ChecklistHome.tsx +++ b/app/_components/FeatureComponents/Home/Parts/ChecklistHome.tsx @@ -17,7 +17,6 @@ import { } from "@dnd-kit/sortable"; import { useChecklistHome } from "@/app/_hooks/useChecklistHome"; import { useAppMode } from "@/app/_providers/AppModeProvider"; -import { encodeCategoryPath } from "@/app/_utils/global-utils"; import { useTranslations } from "next-intl"; import { useSettings } from "@/app/_utils/settings-store"; import { ChecklistListItem } from "@/app/_components/GlobalComponents/Cards/ChecklistListItem"; @@ -135,11 +134,8 @@ export const ChecklistHome = ({ selectedCategory?.split("/").pop() || selectedCategory; const getListSharer = (list: Checklist) => { - const encodedCategory = encodeCategoryPath( - list.category || "Uncategorized", - ); const sharedItem = userSharedItems?.checklists?.find( - (item) => item.id === list.id && item.category === encodedCategory, + (item) => item.uuid === list.uuid, ); return sharedItem?.sharer; }; @@ -229,14 +225,14 @@ export const ChecklistHome = ({ onDragEnd={handleDragEnd} > list.uuid || list.id)} + items={pinned.map((list) => list.uuid)} strategy={verticalListSortingStrategy} > {viewMode === "card" && (
{pinned.map((list) => ( {pinned.map((list) => ( {pinned.map((list) => ( { - const encodedCategory = encodeCategoryPath( - note.category || "Uncategorized", - ); const sharedItem = userSharedItems?.notes?.find( - (item) => item.id === note.id && item.category === encodedCategory, + (item) => item.uuid === note.uuid, ); return sharedItem?.sharer; }; @@ -220,7 +216,7 @@ export const NotesHome = ({ onDragEnd={handleDragEnd} > note.uuid || note.id)} + items={pinned.map((note) => note.uuid)} strategy={verticalListSortingStrategy} > {viewMode === "card" && ( @@ -231,7 +227,7 @@ export const NotesHome = ({ > {pinned.map((note) => (
{pinned.map((note) => ( {pinned.map((note) => ( { - const encodedCategory = encodeCategoryPath( - note.category || "Uncategorized", - ); const sharedItem = userSharedItems?.notes?.find( - (item) => item.id === note.id && item.category === encodedCategory, + (item) => item.uuid && item.uuid === note.uuid, ); return sharedItem?.sharer; }; const getListSharer = (list: Checklist) => { - const encodedCategory = encodeCategoryPath( - list.category || "Uncategorized", - ); const sharedItem = userSharedItems?.checklists?.find( - (item) => item.id === list.id && item.category === encodedCategory, + (item) => item.uuid && item.uuid === list.uuid, ); return sharedItem?.sharer; }; const handleSelectNote = (note: Note) => { - const categoryPath = buildCategoryPath( - note.category || "Uncategorized", - note.id, - ); - router.push(`/note/${categoryPath}`); + if (!note.uuid) return; + router.push(itemHref(ItemTypes.NOTE, note.uuid)); }; const handleSelectChecklist = (list: Checklist) => { - const categoryPath = buildCategoryPath( - list.category || "Uncategorized", - list.id, - ); - router.push(`/checklist/${categoryPath}`); + if (!list.uuid) return; + router.push(itemHref(ItemTypes.CHECKLIST, list.uuid)); }; if (combinedItems.length === 0 && !selectedFilter) { @@ -178,7 +164,7 @@ export const TagsHome = ({ if (viewMode === "card") { return (
{ >(null); const { linkIndex, notes, checklists, appSettings, allSharedItems } = useAppMode(); - const encodedCategory = encodeCategoryPath( - checklist.category || "Uncategorized", - ); const isShared = allSharedItems?.checklists.some( - (sharedChecklist) => - sharedChecklist.id === checklist.id && - sharedChecklist.category === encodedCategory, + (sharedChecklist) => sharedChecklist.uuid === checklist.uuid, ) || false; const { permissions } = usePermissions(); const { @@ -140,9 +134,8 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { const handleUnarchive = async (itemId: string) => { const formData = new FormData(); - formData.append("listId", localChecklist.id); + formData.append("uuid", localChecklist.uuid || ""); formData.append("itemId", itemId); - formData.append("category", localChecklist.category || "Uncategorized"); const result = await unarchiveItem(formData); if (result.success && result.data) { @@ -158,9 +151,8 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { for (const item of items) { const formData = new FormData(); - formData.append("listId", localChecklist.id); + formData.append("uuid", localChecklist.uuid || ""); formData.append("itemId", item.id); - formData.append("category", localChecklist.category || "Uncategorized"); const result = await archiveItem(formData); if (!result.success || !result.data) { @@ -198,8 +190,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { } }, [ - localChecklist.id, - localChecklist.category, + localChecklist.uuid, onUpdate, refreshChecklist, showToast, @@ -286,8 +277,6 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { title={column.title} items={items} status={column.status} - checklistId={localChecklist.id} - category={localChecklist.category || "Uncategorized"} onUpdate={handleItemUpdate} onOpenDetail={(item) => setDetailItemId(item.id)} isShared={isShared} @@ -323,18 +312,11 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { return getReferences( linkIndex, localChecklist.uuid, - localChecklist.category, ItemTypes.CHECKLIST, notes, checklists, ); - }, [ - linkIndex, - localChecklist.uuid, - localChecklist.category, - checklists, - notes, - ]); + }, [linkIndex, localChecklist.uuid, checklists, notes]); return (
@@ -462,8 +444,6 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { checklist={localChecklist} item={ghostItem} isDragging - checklistId={localChecklist.id} - category={localChecklist.category || "Uncategorized"} onUpdate={() => { }} onOpenDetail={() => { }} isShared={isShared} @@ -491,8 +471,6 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { isOpen={!!calendarSelectedItem} onClose={() => setCalendarSelectedItem(null)} onUpdate={handleItemUpdate} - checklistId={localChecklist.id} - category={localChecklist.category || "Uncategorized"} /> )} @@ -503,8 +481,6 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { isOpen={!!detailItem} onClose={() => setDetailItemId(null)} onUpdate={handleItemUpdate} - checklistId={localChecklist.id} - category={localChecklist.category || "Uncategorized"} /> )} diff --git a/app/_components/FeatureComponents/Kanban/KanbanCard.tsx b/app/_components/FeatureComponents/Kanban/KanbanCard.tsx index c0f6cc8f..178025b8 100644 --- a/app/_components/FeatureComponents/Kanban/KanbanCard.tsx +++ b/app/_components/FeatureComponents/Kanban/KanbanCard.tsx @@ -34,8 +34,6 @@ interface KanbanCardProps { index?: number; listId?: string; isDragging?: boolean; - checklistId: string; - category: string; onUpdate: (updatedChecklist: Checklist) => void; onOpenDetail: (item: Item) => void; isShared: boolean; @@ -49,8 +47,6 @@ const KanbanCardComponent = ({ index = 0, listId, isDragging, - checklistId, - category, onUpdate, onOpenDetail, isShared, @@ -83,8 +79,6 @@ const KanbanCardComponent = ({ const kanbanItemHook = useKanbanItem({ checklist, item, - checklistId, - category, onUpdate, }); @@ -158,9 +152,8 @@ const KanbanCardComponent = ({ isOpen={showTimeEntriesModal} onClose={() => setShowTimeEntriesModal(false)} timeEntries={item.timeEntries} - checklistId={checklistId} + checklistUuid={checklist.uuid || ""} itemId={item.id} - category={category} onUpdate={onUpdate} usersPublicData={usersPublicData} /> diff --git a/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx b/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx index 1a6c26e3..d1bf94b8 100644 --- a/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx +++ b/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx @@ -36,8 +36,6 @@ interface KanbanCardDetailProps { isOpen: boolean; onClose: () => void; onUpdate: (updatedChecklist: Checklist) => void; - checklistId: string; - category: string; } const _sanitizeDescription = (text: string): string => text.replace(/\n/g, "\\n"); @@ -79,9 +77,8 @@ export const KanbanCardDetail = ({ isOpen, onClose, onUpdate, - checklistId, - category, }: KanbanCardDetailProps) => { + const checklistUuid = checklist.uuid || ""; const t = useTranslations(); const { permissions } = usePermissions(); const { usersPublicData } = useAppMode(); @@ -111,8 +108,6 @@ export const KanbanCardDetail = ({ const kanbanItemHook = useKanbanItem({ checklist, item, - checklistId, - category, onUpdate, }); @@ -133,7 +128,7 @@ export const KanbanCardDetail = ({ useEffect(() => { if (!isOpen) return; const _loadUsers = async () => { - const sharedWithUsers = await getUsersWithAccess(checklistId, checklist.uuid); + const sharedWithUsers = await getUsersWithAccess(checklistUuid); if (sharedWithUsers.length === 0) { setBoardIsShared(false); return; @@ -154,7 +149,7 @@ export const KanbanCardDetail = ({ setAvailableUsers(Array.from(userMap.values())); }; _loadUsers(); - }, [isOpen, checklistId, checklist.uuid, checklist.owner]); + }, [isOpen, checklistUuid, checklist.owner]); const descriptionHtml = useMemo(() => { const noDescText = `

${t("checklists.noDescription")}

`; @@ -165,9 +160,8 @@ export const KanbanCardDetail = ({ const _saveField = async (fields: Record) => { const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("itemId", item.id); - formData.append("category", category); Object.entries(fields).forEach(([key, value]) => formData.append(key, value)); const result = await updateItem(checklist, formData); if (result.success && result.data) { @@ -182,12 +176,11 @@ export const KanbanCardDetail = ({ const currentUnsanitized = _unsanitizeDescription(item.description || ""); if (editText.trim() !== item.text || editDescription.trim() !== currentUnsanitized) { const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("itemId", item.id); formData.append("text", editText.trim()); formData.append("description", sanitizedDescription); - formData.append("category", category); - const result = await updateItem(checklist, formData); + const result = await updateItem(checklist, formData); if (result.success && result.data) { onUpdate(result.data); const updatedItem = _findItemInChecklist(result.data, item.id); @@ -203,10 +196,9 @@ export const KanbanCardDetail = ({ const handleAddSubtask = async (text: string) => { const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("parentId", item.id); formData.append("text", text); - formData.append("category", category); const result = await createSubItem(formData); if (result.success && result.data) { onUpdate(result.data); @@ -217,10 +209,9 @@ export const KanbanCardDetail = ({ const handleAddNestedSubtask = async (parentId: string, text: string) => { const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("parentId", parentId); formData.append("text", text); - formData.append("category", category); const result = await createSubItem(formData); if (result.success && result.data) { onUpdate(result.data); @@ -241,10 +232,9 @@ export const KanbanCardDetail = ({ const handleToggleSubtask = async (subtaskId: string, completed: boolean) => { const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("itemId", subtaskId); formData.append("completed", completed.toString()); - formData.append("category", category); const result = await updateItem(checklist, formData); if (result.success && result.data) { onUpdate(result.data); @@ -253,10 +243,9 @@ export const KanbanCardDetail = ({ setItem(updatedItem); if (_autoCompleteStatus && updatedItem.children?.length && _allChildrenComplete(updatedItem.children)) { const statusFormData = new FormData(); - statusFormData.append("listId", checklistId); + statusFormData.append("uuid", checklistUuid); statusFormData.append("itemId", item.id); statusFormData.append("status", _autoCompleteStatus.id); - statusFormData.append("category", category); const statusResult = await updateItemStatus(statusFormData); if (statusResult.success && statusResult.data) { onUpdate(statusResult.data as import("@/app/_types").Checklist); @@ -268,10 +257,9 @@ export const KanbanCardDetail = ({ const handleEditSubtask = async (subtaskId: string, text: string) => { const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("itemId", subtaskId); formData.append("text", text); - formData.append("category", category); const result = await updateItem(checklist, formData); if (result.success && result.data) { onUpdate(result.data); @@ -282,9 +270,8 @@ export const KanbanCardDetail = ({ const handleDeleteSubtask = async (subtaskId: string) => { const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("itemId", subtaskId); - formData.append("category", category); const result = await deleteItem(formData); if (result.success && result.data) { onUpdate(result.data); @@ -307,10 +294,9 @@ export const KanbanCardDetail = ({ const targetItems = _findTargetItems(item.children); if (!targetItems.length) return; const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("completed", String(completed)); formData.append("itemIds", JSON.stringify(targetItems.map((t) => t.id))); - formData.append("category", category); const result = await bulkToggleItems(formData); if (result.success && result.data) { onUpdate(result.data); @@ -327,10 +313,9 @@ export const KanbanCardDetail = ({ const handleStatusChange = async (status: string) => { setStatusInput(status); const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("itemId", item.id); formData.append("status", status); - formData.append("category", category); const result = await updateItemStatus(formData); if (result.success && result.data) { onUpdate(result.data); @@ -427,9 +412,8 @@ export const KanbanCardDetail = ({ isOpen={showTimeEntriesModal} onClose={() => setShowTimeEntriesModal(false)} timeEntries={item.timeEntries} - checklistId={checklistId} + checklistUuid={checklistUuid} itemId={item.id} - category={category} onUpdate={onUpdate} usersPublicData={usersPublicData} /> @@ -579,7 +563,7 @@ export const KanbanCardDetail = ({ title: t("notifications.assignmentTitle"), message: t("notifications.assignmentMessage", { task: item.text, board: checklist.title }), data: { - itemId: checklist.uuid || checklistId, + itemId: checklistUuid, itemType: "checklist", taskId: item.id, }, diff --git a/app/_components/FeatureComponents/Kanban/KanbanColumn.tsx b/app/_components/FeatureComponents/Kanban/KanbanColumn.tsx index 4feebdad..9b333042 100644 --- a/app/_components/FeatureComponents/Kanban/KanbanColumn.tsx +++ b/app/_components/FeatureComponents/Kanban/KanbanColumn.tsx @@ -17,8 +17,6 @@ interface KanbanColumnProps { title: string; items: Item[]; status: string; - checklistId: string; - category: string; onUpdate: (updatedChecklist: Checklist) => void; onOpenDetail: (item: Item) => void; isShared: boolean; @@ -81,8 +79,6 @@ const KanbanColumnComponent = ({ title, items, status, - checklistId, - category, isShared, onUpdate, onOpenDetail, @@ -222,8 +218,6 @@ const KanbanColumnComponent = ({ item={item} index={index} listId={status} - checklistId={checklistId} - category={category} onUpdate={onUpdate} onOpenDetail={onOpenDetail} isShared={isShared} diff --git a/app/_components/FeatureComponents/Kanban/KanbanPageClient.tsx b/app/_components/FeatureComponents/Kanban/KanbanPageClient.tsx index 8b9d8dd1..6bd3d687 100644 --- a/app/_components/FeatureComponents/Kanban/KanbanPageClient.tsx +++ b/app/_components/FeatureComponents/Kanban/KanbanPageClient.tsx @@ -10,7 +10,8 @@ import { PlayCircleIcon, } from "hugeicons-react"; import { Checklist, SanitisedUser } from "@/app/_types"; -import { TaskStatus } from "@/app/_types/enums"; +import { ItemTypes, TaskStatus } from "@/app/_types/enums"; +import { itemHref } from "@/app/_utils/global-utils"; import { EmptyState } from "@/app/_components/GlobalComponents/Cards/EmptyState"; import { ChecklistCard } from "@/app/_components/GlobalComponents/Cards/ChecklistCard"; import { ChecklistListItem } from "@/app/_components/GlobalComponents/Cards/ChecklistListItem"; @@ -53,12 +54,13 @@ export const KanbanPageClient = ({ let filtered = [...initialLists]; if (taskFilter === "pinned") { - const pinnedPaths = user?.pinnedLists || []; - filtered = filtered.filter((list) => { - const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.id}`; - const idPath = `${list.category || "Uncategorized"}/${list.id}`; - return pinnedPaths.includes(uuidPath) || pinnedPaths.includes(idPath); - }); + const pinnedEntries = user?.pinnedLists || []; + filtered = filtered.filter((list) => + pinnedEntries.some( + (entry) => + entry === list.uuid || entry.split("/").pop() === list.uuid, + ), + ); } else if (taskFilter === "completed") { filtered = filtered.filter( (list) => @@ -175,15 +177,17 @@ export const KanbanPageClient = ({ } const renderList = (list: Checklist) => { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; - const isPinned = user?.pinnedLists?.includes(categoryPath); + const listHref = itemHref(ItemTypes.CHECKLIST, list.uuid || ""); + const isPinned = user?.pinnedLists?.some( + (entry) => entry === list.uuid || entry.split("/").pop() === list.uuid, + ); if (viewMode === 'list') { return ( router.push(`/checklist/${categoryPath}`)} + onSelect={() => router.push(listHref)} isPinned={isPinned} onTogglePin={() => {}} /> @@ -193,9 +197,9 @@ export const KanbanPageClient = ({ if (viewMode === 'grid') { return ( router.push(`/checklist/${categoryPath}`)} + onSelect={() => router.push(listHref)} isPinned={isPinned} onTogglePin={() => {}} /> @@ -204,9 +208,9 @@ export const KanbanPageClient = ({ return ( router.push(`/checklist/${categoryPath}`)} + onSelect={() => router.push(listHref)} isPinned={isPinned} onTogglePin={() => {}} /> diff --git a/app/_components/FeatureComponents/Kanban/TimeEntriesModal.tsx b/app/_components/FeatureComponents/Kanban/TimeEntriesModal.tsx index c915e3f0..c34d680c 100644 --- a/app/_components/FeatureComponents/Kanban/TimeEntriesModal.tsx +++ b/app/_components/FeatureComponents/Kanban/TimeEntriesModal.tsx @@ -15,9 +15,8 @@ interface TimeEntriesModalProps { isOpen: boolean; onClose: () => void; timeEntries: TimeEntry[]; - checklistId: string; + checklistUuid: string; itemId: string; - category: string; onUpdate: (updatedChecklist: Checklist) => void; usersPublicData?: { username?: string; avatarUrl?: string }[]; } @@ -60,9 +59,8 @@ const TimeEntriesModalComponent = ({ isOpen, onClose, timeEntries, - checklistId, + checklistUuid, itemId, - category, onUpdate, usersPublicData, }: TimeEntriesModalProps) => { @@ -88,10 +86,9 @@ const TimeEntriesModalComponent = ({ if (!editing) return; setSaving(true); const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("itemId", itemId); formData.append("entryId", editing.entryId); - formData.append("category", category); if (editing.startTime) formData.append("startTime", new Date(editing.startTime).toISOString()); if (editing.endTime) { @@ -116,10 +113,9 @@ const TimeEntriesModalComponent = ({ if (!deleteTarget) return; setSaving(true); const formData = new FormData(); - formData.append("listId", checklistId); + formData.append("uuid", checklistUuid); formData.append("itemId", itemId); formData.append("entryId", deleteTarget); - formData.append("category", category); const result = await deleteTimeEntry(formData); if (result.success && result.data) { onUpdate(result.data); diff --git a/app/_components/FeatureComponents/Notes/NoteClient.tsx b/app/_components/FeatureComponents/Notes/NoteClient.tsx index 2e90caaa..4fc2b195 100644 --- a/app/_components/FeatureComponents/Notes/NoteClient.tsx +++ b/app/_components/FeatureComponents/Notes/NoteClient.tsx @@ -10,7 +10,8 @@ import { useShortcut } from "@/app/_providers/ShortcutsProvider"; import { useShortcuts } from "@/app/_hooks/useShortcuts"; import { useNoteEditor } from "@/app/_hooks/useNoteEditor"; import { useAppMode } from "@/app/_providers/AppModeProvider"; -import { buildCategoryPath } from "@/app/_utils/global-utils"; +import { itemHref } from "@/app/_utils/global-utils"; +import { ItemTypes } from "@/app/_types/enums"; import { CloneCategoryModal } from "@/app/_components/GlobalComponents/Modals/ConfirmationModals/CloneCategoryModal"; import { SwipeNavigationWrapper } from "@/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper"; @@ -57,9 +58,7 @@ export const NoteClient = ({ note, categories }: NoteClientProps) => { const handleCloneConfirm = async (targetCategory: string) => { const formData = new FormData(); - formData.append("id", localNote.id); formData.append("uuid", localNote.uuid || ""); - formData.append("originalCategory", localNote.category || "Uncategorized"); formData.append("category", targetCategory || "Uncategorized"); if (localNote.owner) { formData.append("user", localNote.owner); @@ -68,13 +67,8 @@ export const NoteClient = ({ note, categories }: NoteClientProps) => { const { cloneNote } = await import("@/app/_server/actions/note"); const result = await cloneNote(formData); - if (result.success && result.data) { - router.push( - `/note/${buildCategoryPath( - result.data.category || "Uncategorized", - result.data.id, - )}`, - ); + if (result.success && result.data?.uuid) { + router.push(itemHref(ItemTypes.NOTE, result.data.uuid)); router.refresh(); } }; @@ -117,8 +111,7 @@ export const NoteClient = ({ note, categories }: NoteClientProps) => { isEditorInEditMode={viewModel.isEditing} > { - const uuidPath = `${note.category || "Uncategorized"}/${note.uuid || note.id}`; - const idPath = `${note.category || "Uncategorized"}/${note.id}`; - return pinnedPaths.includes(uuidPath) || pinnedPaths.includes(idPath); - }); + const pinnedEntries = user?.pinnedNotes || []; + filtered = filtered.filter((note) => + pinnedEntries.some( + (entry) => + entry === note.uuid || entry.split("/").pop() === note.uuid, + ), + ); } else if (noteFilter === "recent") { filtered = filtered .sort( @@ -102,15 +104,11 @@ export const NotesPageClient = ({ }, [currentPage, totalPages, totalItems, goToPage, handleItemsPerPageChange, setPaginationInfo]); const handleTogglePin = async (note: Note) => { - if (!user || isTogglingPin === note.id) return; + if (!user || !note.uuid || isTogglingPin === note.uuid) return; - setIsTogglingPin(note.id); + setIsTogglingPin(note.uuid); try { - const result = await togglePin( - note.id, - note.category || "Uncategorized", - ItemTypes.NOTE - ); + const result = await togglePin(note.uuid, ItemTypes.NOTE); if (result.success) { router.refresh(); } @@ -164,15 +162,16 @@ export const NotesPageClient = ({ columnClassName="pl-4 bg-clip-padding" > {paginatedItems.map((note) => ( -
+
{ - const categoryPath = `${note.category || "Uncategorized"}/${note.id}`; - router.push(`/note/${categoryPath}`); - }} - isPinned={user?.pinnedNotes?.includes( - `${note.category || "Uncategorized"}/${note.id}` + onSelect={(note) => + router.push(itemHref(ItemTypes.NOTE, note.uuid || "")) + } + isPinned={user?.pinnedNotes?.some( + (entry) => + entry === note.uuid || + entry.split("/").pop() === note.uuid, )} onTogglePin={() => handleTogglePin(note)} /> @@ -185,14 +184,15 @@ export const NotesPageClient = ({
{paginatedItems.map((note) => ( { - const categoryPath = `${note.category || "Uncategorized"}/${note.id}`; - router.push(`/note/${categoryPath}`); - }} - isPinned={user?.pinnedNotes?.includes( - `${note.category || "Uncategorized"}/${note.id}` + onSelect={(note) => + router.push(itemHref(ItemTypes.NOTE, note.uuid || "")) + } + isPinned={user?.pinnedNotes?.some( + (entry) => + entry === note.uuid || + entry.split("/").pop() === note.uuid, )} onTogglePin={() => handleTogglePin(note)} /> @@ -204,14 +204,15 @@ export const NotesPageClient = ({
{paginatedItems.map((note) => ( { - const categoryPath = `${note.category || "Uncategorized"}/${note.id}`; - router.push(`/note/${categoryPath}`); - }} - isPinned={user?.pinnedNotes?.includes( - `${note.category || "Uncategorized"}/${note.id}` + onSelect={(note) => + router.push(itemHref(ItemTypes.NOTE, note.uuid || "")) + } + isPinned={user?.pinnedNotes?.some( + (entry) => + entry === note.uuid || + entry.split("/").pop() === note.uuid, )} onTogglePin={() => handleTogglePin(note)} /> diff --git a/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditor.tsx b/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditor.tsx index 3f92718c..55cf50dd 100644 --- a/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditor.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditor.tsx @@ -62,7 +62,6 @@ export const NoteEditor = ({ editorContent={viewModel.editorContent} onEditorContentChange={viewModel.handleEditorContentChange} noteId={note.uuid} - noteCategory={note.category} encrypted={note.encrypted} onOpenDecryptModal={() => decryptModalRef.current?.()} onOpenViewModal={() => viewModalRef.current?.()} diff --git a/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorContent.tsx b/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorContent.tsx index 76e0cb6b..6584cbe4 100644 --- a/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorContent.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorContent.tsx @@ -19,6 +19,7 @@ import { isEncrypted, } from "@/app/_utils/encryption-utils"; import { useTranslations } from "next-intl"; +import { ItemTypes } from "@/app/_types/enums"; interface NoteEditorContentProps { isEditing: boolean; @@ -30,7 +31,6 @@ interface NoteEditorContentProps { isDirty: boolean, ) => void; noteId?: string; - noteCategory?: string; encrypted?: boolean; onOpenDecryptModal?: () => void; onOpenViewModal?: () => void; @@ -43,7 +43,6 @@ export const NoteEditorContent = ({ editorContent, onEditorContentChange, noteId, - noteCategory, encrypted, onOpenDecryptModal, onOpenViewModal, @@ -64,12 +63,11 @@ export const NoteEditorContent = ({ return getReferences( linkIndex, noteId, - noteCategory, - "note", + ItemTypes.NOTE, notes, checklists, ); - }, [linkIndex, noteId, noteCategory, notes, checklists]); + }, [linkIndex, noteId, notes, checklists]); useEffect(() => { if ( diff --git a/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorHeader.tsx b/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorHeader.tsx index 15c0d273..e0424051 100644 --- a/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorHeader.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorHeader.tsx @@ -38,11 +38,7 @@ import { useRouter } from "next/navigation"; import { useAppMode } from "@/app/_providers/AppModeProvider"; import { toggleArchive } from "@/app/_server/actions/dashboard"; import { Modes } from "@/app/_types/enums"; -import { - copyTextToClipboard, - encodeCategoryPath, - buildCategoryPath, -} from "@/app/_utils/global-utils"; +import { copyTextToClipboard } from "@/app/_utils/global-utils"; import { sharingInfo } from "@/app/_utils/sharing-utils"; import { usePermissions } from "@/app/_providers/PermissionsProvider"; import { SharedWithModal } from "@/app/_components/GlobalComponents/Modals/SharingModals/SharedWithModal"; @@ -163,13 +159,7 @@ export const NoteEditorHeader = ({ }; const handleCopyId = async () => { - const success = await copyTextToClipboard( - `${note?.uuid - ? note?.uuid - : `${encodeCategoryPath(note?.category || "Uncategorized")}/${note?.id - }` - }` - ); + const success = await copyTextToClipboard(note?.uuid || ""); if (success) { setCopied(true); setTimeout(() => setCopied(false), 2000); @@ -216,69 +206,34 @@ export const NoteEditorHeader = ({ const handlePermanentDecryption = async (newContent: string) => { const formData = new FormData(); - formData.append("id", note.id); formData.append("title", title); formData.append("content", newContent); formData.append("category", category); - formData.append("originalCategory", note.category || "Uncategorized"); - if (note.uuid) { - formData.append("uuid", note.uuid); - } + formData.append("uuid", note.uuid!); const result = await updateNote(formData); if (result.success && result.data) { - const categoryPath = buildCategoryPath( - result.data.category || t("notes.uncategorized"), - result.data.id - ); - const newPath = `/note/${categoryPath}`; - const currentPath = window.location.pathname; - - if (newPath === currentPath) { - window.location.reload(); - } else { - router.push(newPath); - } + window.location.reload(); } }; const handleEncryptionSuccess = async (newContent: string) => { const formData = new FormData(); - formData.append("id", note.id); formData.append("title", title); formData.append("content", newContent); formData.append("category", category); - formData.append("originalCategory", note.category || "Uncategorized"); - if (note.uuid) { - formData.append("uuid", note.uuid); - } + formData.append("uuid", note.uuid!); const result = await updateNote(formData); if (result.success && result.data) { - const categoryPath = buildCategoryPath( - result.data.category || t("notes.uncategorized"), - result.data.id - ); - const newPath = `/note/${categoryPath}`; - const currentPath = window.location.pathname; - - if (newPath === currentPath) { - window.location.reload(); - } else { - router.push(newPath); - } + window.location.reload(); } }; const { globalSharing } = useAppMode(); - const encodedCategory = encodeCategoryPath(metadata.category); - const itemDetails = sharingInfo( - globalSharing, - metadata.uuid || metadata.id, - encodedCategory - ); + const itemDetails = sharingInfo(globalSharing, metadata.uuid || ""); const isShared = itemDetails.exists && itemDetails.sharedWith.length > 0; const sharedWith = itemDetails.sharedWith; const isPubliclyShared = itemDetails.isPublic; @@ -327,12 +282,7 @@ export const NoteEditorHeader = ({ handleCopyId(); }} className="h-6 w-6 p-0" - title={`Copy ID: ${note?.uuid - ? note?.uuid - : `${encodeCategoryPath( - note?.category || t("notes.uncategorized") - )}/${note?.id}` - }`} + title={`Copy ID: ${note?.uuid || ""}`} > {copied ? ( diff --git a/app/_components/FeatureComponents/Notes/Parts/ReferencedBySection.tsx b/app/_components/FeatureComponents/Notes/Parts/ReferencedBySection.tsx index 3979f9aa..094a7309 100644 --- a/app/_components/FeatureComponents/Notes/Parts/ReferencedBySection.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/ReferencedBySection.tsx @@ -1,7 +1,7 @@ "use client"; import { File02Icon, CheckmarkSquare04Icon } from "hugeicons-react"; -import { encodeCategoryPath } from "@/app/_utils/global-utils"; +import { itemHref } from "@/app/_utils/global-utils"; import { useRouter } from "next/navigation"; import { ItemType } from "@/app/_types"; import { ItemTypes } from "@/app/_types/enums"; @@ -9,7 +9,7 @@ import { useTranslations } from "next-intl"; interface ReferencingItem { type: ItemType; - path: string; + uuid: string; title: string; category: string; } @@ -26,8 +26,7 @@ export const ReferencedBySection = ({ if (referencingItems.length === 0) return null; const handleItemClick = (item: ReferencingItem) => { - const url = `/${item.type}/${encodeCategoryPath(item.path)}`; - router.push(url); + router.push(itemHref(item.type as ItemTypes, item.uuid)); }; return ( @@ -42,7 +41,7 @@ export const ReferencedBySection = ({
{referencingItems.map((item) => (
handleItemClick(item)} className="bg-muted/50 border border-border rounded-jotty p-3 cursor-pointer hover:shadow-sm hover:border-primary/30 transition-all duration-200 group" > diff --git a/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper.tsx b/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper.tsx index 8d61a15e..a50c2e1b 100644 --- a/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper.tsx @@ -6,25 +6,24 @@ import { Note } from "@/app/_types"; import { useAdjacentNotes } from "@/app/_hooks/useAdjacentNotes"; import { useSwipeNavigation } from "@/app/_hooks/useSwipeNavigation"; import { useNavigationGuard } from "@/app/_providers/NavigationGuardProvider"; -import { isMobileDevice, buildCategoryPath } from "@/app/_utils/global-utils"; +import { isMobileDevice, itemHref } from "@/app/_utils/global-utils"; +import { ItemTypes } from "@/app/_types/enums"; interface SwipeNavigationWrapperProps { children: ReactNode; - noteId: string; - noteCategory?: string; + noteUuid: string; enabled: boolean; } const getNoteUrl = (note: Partial | null, embed = false): string | null => { - if (!note?.id) return null; - const base = `/note/${buildCategoryPath(note.category || "Uncategorized", note.id)}`; + if (!note?.uuid) return null; + const base = itemHref(ItemTypes.NOTE, note.uuid); return embed ? `${base}?embed=true` : base; }; export const SwipeNavigationWrapper = ({ children, - noteId, - noteCategory, + noteUuid, enabled, }: SwipeNavigationWrapperProps) => { const router = useRouter(); @@ -35,7 +34,7 @@ export const SwipeNavigationWrapper = ({ const nextRef = useRef(null); const [isMobile, setIsMobile] = useState(false); - const { prev, next } = useAdjacentNotes(noteId); + const { prev, next } = useAdjacentNotes(noteUuid); const prevUrl = getNoteUrl(prev, true); const nextUrl = getNoteUrl(next, true); diff --git a/app/_components/FeatureComponents/Notes/Parts/TipTap/CustomExtensions/InternalLinkComponent.tsx b/app/_components/FeatureComponents/Notes/Parts/TipTap/CustomExtensions/InternalLinkComponent.tsx index 79bf16a9..c479d26d 100644 --- a/app/_components/FeatureComponents/Notes/Parts/TipTap/CustomExtensions/InternalLinkComponent.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/TipTap/CustomExtensions/InternalLinkComponent.tsx @@ -12,18 +12,13 @@ import { import { useRouter } from "next/navigation"; import { getNoteById } from "@/app/_server/actions/note"; import { getListById } from "@/app/_server/actions/checklist"; -import { - buildCategoryPath, - decodeCategoryPath, - encodeCategoryPath, -} from "@/app/_utils/global-utils"; +import { decodeCategoryPath, itemHref } from "@/app/_utils/global-utils"; import { capitalize } from "lodash"; import { useAppMode } from "@/app/_providers/AppModeProvider"; import { NoteCard } from "@/app/_components/GlobalComponents/Cards/NoteCard"; import { ChecklistCard } from "@/app/_components/GlobalComponents/Cards/ChecklistCard"; import { Checklist, Note } from "@/app/_types"; import { isKanbanType, ItemTypes } from "@/app/_types/enums"; -import { encodeId } from "@/app/_utils/global-utils"; import { useTranslations } from "next-intl"; interface InternalLinkComponentProps { @@ -45,13 +40,8 @@ interface InternalLinkComponentProps { const _returnNote = async (uuid: string, router: any, note?: Note) => { const finalNote = note || (await getNoteById(uuid)); - if (finalNote) { - router.push( - `/note/${buildCategoryPath( - finalNote.category || "Uncategorized", - finalNote.id, - )}`, - ); + if (finalNote?.uuid) { + router.push(itemHref(ItemTypes.NOTE, finalNote.uuid)); return; } @@ -65,13 +55,8 @@ const _returnChecklist = async ( ) => { const finalChecklist = checklist || (await getListById(uuid)); - if (finalChecklist) { - router.push( - `/checklist/${buildCategoryPath( - finalChecklist.category || "Uncategorized", - finalChecklist.id, - )}`, - ); + if (finalChecklist?.uuid) { + router.push(itemHref(ItemTypes.CHECKLIST, finalChecklist.uuid)); return; } return undefined; @@ -147,16 +132,14 @@ export const InternalLinkComponent = ({ if (href.startsWith("/jotty/")) { const uuidFromPath = href.replace("/jotty/", ""); - if (fullItem && fullItem.id) { + if (fullItem?.uuid) { router.push( - `/${ - fullItem && "type" in fullItem && fullItem.type + itemHref( + "type" in fullItem && fullItem.type ? ItemTypes.CHECKLIST - : ItemTypes.NOTE - }/${buildCategoryPath( - fullItem.category || "Uncategorized", - fullItem.id, - )}`, + : ItemTypes.NOTE, + fullItem.uuid, + ), ); return; } @@ -184,67 +167,37 @@ export const InternalLinkComponent = ({ e.stopPropagation(); if (isJottyLink) { - if (fullItem && fullItem.id) { - const pathPrefix = - fullItem && "type" in fullItem && fullItem.type - ? "/checklist/" - : "/note/"; - const newHref = `${pathPrefix}${buildCategoryPath( - fullItem.category || "Uncategorized", - fullItem.id, - )}`; - updateAttributes({ - href: newHref, - type: - fullItem && "type" in fullItem && fullItem.type - ? "checklist" - : "note", - category: fullItem.category || "Uncategorized", - itemId: fullItem.id, - convertToBidirectional: false, - }); - } else if (itemId && category) { - const pathPrefix = type === "checklist" ? "/checklist/" : "/note/"; - const newHref = `${pathPrefix}${buildCategoryPath(category, itemId)}`; + const isChecklist = fullItem && "type" in fullItem && fullItem.type; + const targetUuid = fullItem?.uuid || uuid || href.replace("/jotty/", ""); + + if (targetUuid) { updateAttributes({ - href: newHref, + href: itemHref( + isChecklist ? ItemTypes.CHECKLIST : ItemTypes.NOTE, + targetUuid, + ), + type: isChecklist ? "checklist" : "note", + category: fullItem?.category || category || "Uncategorized", convertToBidirectional: false, }); } else { console.warn("Cannot convert jotty to path - missing data"); } } else if (isPathBasedLink) { - if (uuid) { + const resolvedUuid = + uuid || + fullItem?.uuid || + notes.find((n) => n.id === itemId && (n.category || "") === (category || ""))?.uuid || + checklists.find((c) => c.id === itemId && (c.category || "") === (category || ""))?.uuid; + + if (resolvedUuid) { updateAttributes({ - href: `/jotty/${uuid}`, + href: `/jotty/${resolvedUuid}`, + uuid: resolvedUuid, convertToBidirectional: false, }); - } else if (itemId && category) { - const foundItem = - notes.find( - (n) => - encodeId(n.id || "") === encodeId(itemId) && - encodeCategoryPath(n?.category || "") === - encodeCategoryPath(category), - ) || - checklists.find( - (c) => - encodeId(c.id || "") === encodeId(itemId) && - encodeCategoryPath(c?.category || "") === - encodeCategoryPath(category), - ); - - if (foundItem?.uuid) { - updateAttributes({ - href: `/jotty/${foundItem.uuid}`, - uuid: foundItem.uuid, - convertToBidirectional: false, - }); - } else { - console.log("Could not find item to convert"); - } } else { - console.log("Missing itemId or category"); + console.log("Could not find item to convert"); } } }; diff --git a/app/_components/FeatureComponents/Notes/Parts/UnifiedMarkdownRenderer.tsx b/app/_components/FeatureComponents/Notes/Parts/UnifiedMarkdownRenderer.tsx index c440fee6..356b8c8a 100644 --- a/app/_components/FeatureComponents/Notes/Parts/UnifiedMarkdownRenderer.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/UnifiedMarkdownRenderer.tsx @@ -27,7 +27,7 @@ import { InternalLinkComponent } from "./TipTap/CustomExtensions/InternalLinkCom import { TagLinkViewComponent } from "@/app/_components/FeatureComponents/Tags/TagLinkComponent"; import { ItemTypes } from "@/app/_types/enums"; import { extractYamlMetadata } from "@/app/_utils/yaml-metadata-utils"; -import { decodeCategoryPath, decodeId } from "@/app/_utils/global-utils"; +import { decodeCategoryPath } from "@/app/_utils/global-utils"; import { NoteFooterStats } from "@/app/_components/GlobalComponents/Statistics/NoteFooterStats"; import { useTranslations } from "next-intl"; import { @@ -227,7 +227,7 @@ export const UnifiedMarkdownRenderer = ({ ?.replace("/checklist/", "") .replace("/note/", "") .split("/"); - linkItemId = decodeId(pathParts?.[pathParts.length - 1] || ""); + linkItemId = decodeURIComponent(pathParts?.[pathParts.length - 1] || ""); linkCategory = decodeCategoryPath( pathParts?.slice(0, -1).join("/") || "" ); diff --git a/app/_components/FeatureComponents/Profile/Parts/ConnectionsGraph/graph-data.ts b/app/_components/FeatureComponents/Profile/Parts/ConnectionsGraph/graph-data.ts index 12d01dad..1399d390 100644 --- a/app/_components/FeatureComponents/Profile/Parts/ConnectionsGraph/graph-data.ts +++ b/app/_components/FeatureComponents/Profile/Parts/ConnectionsGraph/graph-data.ts @@ -1,6 +1,7 @@ import { Checklist, ItemType, LinkIndex, Note } from "@/app/_types"; import { ItemTypes } from "@/app/_types/enums"; -import { buildCategoryPath } from "@/app/_utils/global-utils"; +import { itemHref } from "@/app/_utils/global-utils"; +import { isUuid } from "@/app/_consts/identity"; export const MAX_GRAPH_NODES = 600; export const MAX_TAG_LINK_FANOUT = 8; @@ -50,14 +51,6 @@ export interface ConnectionGraphFilters { type ItemLike = Partial | Partial; -const isUuidLike = (value: string) => - /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( - value, - ); - -const itemTypePath = (type: ItemType) => - type === ItemTypes.CHECKLIST ? "checklist" : "note"; - const fallbackTitle = (type: ItemType, id: string) => `${type === ItemTypes.CHECKLIST ? "Checklist" : "Note"} ${id.slice(0, 8)}`; @@ -83,7 +76,7 @@ export const resolveGraphId = ( rawId: string, uuidByLegacyKey: Map, ) => { - if (isUuidLike(rawId)) return rawId; + if (isUuid(rawId)) return rawId; return uuidByLegacyKey.get(rawId) || rawId; }; @@ -232,7 +225,7 @@ export const buildConnectionGraphData = ( const connectionCount = inboundCount + outboundCount + tagConnectionCount; const category = item.category || "Uncategorized"; const itemId = item.id || uuid; - const path = buildCategoryPath(category, itemId); + const path = `${category}/${itemId}`; return { id: uuid, @@ -242,7 +235,7 @@ export const buildConnectionGraphData = ( category, itemId, path, - url: `/${itemTypePath(type)}/${path}`, + url: itemHref(type, uuid), tags: item.tags || [], createdAt: item.createdAt, updatedAt: item.updatedAt, diff --git a/app/_components/FeatureComponents/Search/Parts/SearchResults.tsx b/app/_components/FeatureComponents/Search/Parts/SearchResults.tsx index 8d80716d..f6544088 100644 --- a/app/_components/FeatureComponents/Search/Parts/SearchResults.tsx +++ b/app/_components/FeatureComponents/Search/Parts/SearchResults.tsx @@ -8,6 +8,7 @@ import { useTranslations } from "next-intl"; interface SearchResult { id: string; + uuid?: string; title: string; type: ItemType; content?: string; @@ -73,7 +74,7 @@ export const SearchResults = ({
{results.map((result, index) => (