import { Fragment, useEffect, useMemo, useState } from "react" import { getPageVisibility } from "~/lib/page-helpers" import { formatDate } from "~/lib/date" import { TabItem, Tabs } from "../ui/Tabs" import { Menu } from "@headlessui/react" import clsx from "clsx" import { PageVisibilityEnum } from "~/lib/types" import { DashboardMain } from "./DashboardMain" import { useRouter } from "next/router" import Link from "next/link" import toast from "react-hot-toast" import { EmptyState } from "../ui/EmptyState" import type { Note } from "unidata.js" import { useGetPagesBySite, useDeletePage, useCreateOrUpdatePage, } from "~/queries/page" import { EllipsisHorizontalIcon } from "@heroicons/react/20/solid" import { delStorage, getStorage, setStorage } from "~/lib/storage" import { useQueryClient } from "@tanstack/react-query" import { Button } from "../ui/Button" import { UniLink } from "../ui/UniLink" import { nanoid } from "nanoid" import { renderPageContent } from "~/markdown" import { Tooltip } from "../ui/Tooltip" import { APP_NAME } from "~/lib/env" export const PagesManager: React.FC<{ isPost: boolean }> = ({ isPost }) => { const router = useRouter() const subdomain = router.query.subdomain as string const visibility = useMemo( () => router.query.visibility ? (router.query.visibility as PageVisibilityEnum) : PageVisibilityEnum.All, [router.query.visibility], ) const deletePage = useDeletePage() const createOrUpdatePage = useCreateOrUpdatePage() const [convertToastId, setConvertToastId] = useState("") const [deleteToastId, setDeleteToastId] = useState("") useEffect(() => { if (deletePage.isSuccess) { toast.success("Deleted!", { id: deleteToastId, }) } }, [deletePage.isSuccess, deleteToastId]) useEffect(() => { if (createOrUpdatePage.isSuccess) { toast.success("Converted!", { id: convertToastId, }) } else if (createOrUpdatePage.isError) { toast.error("Failed to convert.", { id: convertToastId, }) } }, [createOrUpdatePage.isSuccess, createOrUpdatePage.isError, convertToastId]) const pages = useGetPagesBySite({ type: isPost ? "post" : "page", site: subdomain!, take: 100, visibility, }) const tabItems: TabItem[] = [ { value: PageVisibilityEnum.All, text: `All ${isPost ? "Posts" : "Pages"}`, }, { value: PageVisibilityEnum.Published, text: "Published", }, { value: PageVisibilityEnum.Draft, text: "Draft", }, { value: PageVisibilityEnum.Scheduled, text: "Scheduled", }, { value: PageVisibilityEnum.Crossbell, text: "Others on Crossbell", }, ].map((item) => ({ text: item.text, onClick: () => { const newQuery: Record = { ...router.query, visibility: item.value, } if (item.value === PageVisibilityEnum.All) { delete newQuery["visibility"] } const search = new URLSearchParams(newQuery).toString() router.push({ search, }) }, active: item.value === visibility, })) const getPageEditLink = (page: { id: string }) => { return `/dashboard/${subdomain}/editor?id=${page.id}&type=${ isPost ? "post" : "page" }` } const queryClient = useQueryClient() const getPageMenuItems = (page: Note) => { const isCrossbell = !page.applications?.includes("xlog") return [ { text: "Edit", icon: ( ), onClick() { router.push(getPageEditLink(page)) }, }, { text: "Convert to " + (isCrossbell ? `${APP_NAME} ${isPost ? "Post" : "Page"}` : isPost ? "Page" : "Post"), icon: , onClick() { const toastId = toast.loading("Converting...") if (isCrossbell) { setConvertToastId(toastId) createOrUpdatePage.mutate({ published: true, pageId: page.id, siteId: subdomain, tags: page.tags ?.filter((tag) => tag !== "post" && tag !== "page") ?.join(", "), isPost: isPost, applications: page.applications, }) } else { if (!page.metadata) { const data = getStorage(`draft-${subdomain}-${page.id}`) data.isPost = !isPost setStorage(`draft-${subdomain}-${page.id}`, data) queryClient.invalidateQueries(["getPagesBySite", subdomain]) queryClient.invalidateQueries(["getPage", page.id]) toast.success("Converted!", { id: toastId, }) } else { setConvertToastId(toastId) createOrUpdatePage.mutate({ published: true, pageId: page.id, siteId: subdomain, tags: page.tags ?.filter((tag) => tag !== "post" && tag !== "page") ?.join(", "), isPost: !isPost, applications: page.applications, }) } } }, }, { text: "Delete", icon: ( ), onClick() { if (!page.metadata) { const toastId = toast.loading("Deleting...") delStorage(`draft-${subdomain}-${page.id}`) queryClient.invalidateQueries(["getPagesBySite", subdomain]) queryClient.invalidateQueries(["getPage", page.id]) toast.success("Deleted!", { id: toastId, }) } else { setDeleteToastId(toast.loading("Deleting...")) deletePage.mutate({ site: subdomain, id: page.id, }) } }, }, ] } const importFile = () => { const input = document.createElement("input") input.type = "file" input.accept = ".md" input.addEventListener("change", async (e: any) => { const file = e.target?.files?.[0] const reader = new FileReader() reader.readAsText(file, "UTF-8") reader.onload = (evt) => { if (evt.target?.result) { const pageContent = renderPageContent(evt.target.result as string) const id = nanoid() const key = `draft-${subdomain}-local-${id}` const tags = pageContent.frontMatter.tags || pageContent.frontMatter.categories setStorage(key, { date: +new Date(), values: { content: evt.target.result, published: false, publishedAt: ( pageContent.frontMatter.date || file.lastModifiedDate || new Date() ).toISOString(), slug: pageContent.frontMatter.permalink || file.name.split(".").slice(0, -1).join("."), tags: tags?.join?.(", ") || tags, title: pageContent.frontMatter.title, }, isPost: isPost, }) queryClient.invalidateQueries(["getPagesBySite", subdomain]) router.push( `/dashboard/${subdomain}/editor?id=local-${id}&type=${ isPost ? "post" : "page" }`, ) } } reader.onerror = (evt) => { toast.error("Error reading file") } }) input.click() } const title = isPost ? "Posts" : "Pages" const description = isPost ? ( <>

Post vs. Page . Posts are entries listed in reverse chronological order on your site. Think of them as articles or updates that you share to offer up new content to your readers.

) : ( <>

Post vs. Page . Pages are static and are not affected by date. Think of them as more permanent fixtures of your site — an About page, and a Contact page are great examples of this.

After you create a page, you can{" "} add it to your site's navigation menu {" "} so your visitors can find it.

) let currentLength = 0 return (

{title}

{description}
{!pages.data?.pages?.[0].total && ( )} {pages.data?.pages.map((page) => page.list?.map((page) => { currentLength++ return (
{page.title ? (
{page.title}
) : (
{page.summary?.content}
)}
{getPageVisibility(page).toLowerCase()} · {getPageVisibility(page) === PageVisibilityEnum.Draft ? formatDate(page.date_updated) : formatDate(page.date_published)}
{({ open }: { open: boolean }) => ( <> {getPageMenuItems(page).map((item) => { return ( ) })} )}
) }), )} {pages.hasNextPage && ( )}
) }