feat: use app router for site home

This commit is contained in:
DIYgod 2023-05-07 17:12:17 +01:00
parent d3f83e3090
commit 5d9194787b
No known key found for this signature in database
26 changed files with 422 additions and 180 deletions

1
next-env.d.ts vendored
View File

@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -1,5 +1,6 @@
{
"intlDateTime": "{{val, datetime}}",
"ago": "{{time}} ago",
"joined ago": "Joined {{time}} ago"
"joined ago": "Joined {{time}} ago",
"signed and stored on the blockchain": "Ownership of this {{name}} data is guaranteed by blockchain and smart contracts to the creator alone."
}

View File

@ -1,4 +1,3 @@
{
"load more": "There are {{count}} more {{name}}, click to load more",
"signed and stored on the blockchain": "Ownership of this {{name}} data is guaranteed by blockchain and smart contracts to the creator alone."
"load more": "There are {{count}} more {{name}}, click to load more"
}

View File

@ -55,5 +55,6 @@
"Showcase": "ショーケース",
"AI-generated summary": "AIが生成した要約",
"Generating": "生成中",
"Show more": "もっと表示する"
"Show more": "もっと表示する",
"signed and stored on the blockchain": "{{name}}は、創作者によって署名され、ブロックチェーンに安全に保存されています。"
}

View File

@ -4,7 +4,6 @@
"About": "紹介",
"Tags": "タグ",
"load more": "まだ{{count}}件の{{name}}があります。",
"signed and stored on the blockchain": "{{name}}は、創作者によって署名され、ブロックチェーンに安全に保存されています。",
"Write a comment on the blockchain": "ブロックチェーンでコメントする",
"powered by": "<span>このサイトは </span><name/><span> によって作動します</span>"
}

View File

@ -1,87 +1,88 @@
{
"Connect": "連線",
"Followers": "粉絲",
"Following": "追蹤中",
"Followings": "追蹤中",
"Follow": "追蹤",
"Unfollow": "取消追蹤",
"intlDateTime": "{{val, datetime}}",
"post": "文章",
"posts": "文章",
"page": "頁面",
"pages": "頁面",
"comment": "留言",
"comments": "留言",
"Comment": "留言",
"Comments": "留言",
"reply": "回覆",
"replies": "回覆",
"like": "讚",
"likes": "讚",
"blog": "部落格",
"Owner": "擁有者",
"Transaction Hash": "交易雜湊值",
"IPFS Address": "IPFS 位址",
"BNB Greenfield Address": "BNB Greenfield 位址",
"Creation": "創建",
"Last Update": "最後更新",
"Create Character": "創建身份",
"Submit": "提交",
"Reply": "回覆",
"Cancel Reply": "取消回覆",
"Edit": "編輯",
"Cancel Edit": "取消編輯",
"Confirm Modification": "確認編輯",
"ago": "{{time}}前",
"joined ago": "{{time}}前加入",
"obtained ago": "{{time}}前獲得",
"Like": "點讚",
"Mint to an NFT": "珍藏為 NFT",
"Like successfully": "點讚成功",
"Mint successfully": "珍藏完成~",
"like stored": "你的點讚已被安全地儲存在區塊鏈上,可以在 <2>Crossbell Scan</2> 上查看",
"mint stored": "你已將這篇文章珍藏成 NFT可以在 <2>xChar</2> 或 <6>Crossbell Scan</6> 上查看",
"Got it, thanks!": "知道啦,感恩的心~",
"Like List": "按讚列表",
"Mint List": "珍藏列表",
"Revert": "收回",
"Confirm to revert": "確定收回",
"like revert": "請確定是否想要收回這個點讚操作?",
"Cancel": "算了",
"Confirm": "確定",
"No Content Yet.": "什麼都沒",
"Close": "關閉",
"Dashboard": "主控臺",
"Copied!": "拷貝完成",
"Operator Sign": "簽名授權",
"Switch Characters": "切換身份",
"Upgrade to Wallet": "升級為錢包認證",
"Disconnect": "中斷連線",
"Loading": "載入中...",
"Showcase": "展示櫃",
"AI-generated summary": "AI 生成的摘要",
"Generating": "生成中...",
"Show more": "顯示更多",
"Patron": "贊助",
"Become a patron of {{name}}": "成為 {{name}} 的贊助者",
"Latest patrons": "最新贊助者",
"Latest tipper": "最新贊助者",
"You are here to be the first patron.": "你是第一位贊助者。",
"You are here to be the first tipper.": "你是第一位贊助者。",
"Select a tier": "選擇一個等級",
"One-time": "一次性",
"Monthly and NFT Rewards": "每月和 NFT 獎勵",
"Coming soon": "即將推出",
"What is MIRA? Where can I get some?": "MIRA 是什麼?能吃嗎?我在哪裡可以獲得?",
"Tip": "贊助",
"Tip the post: {{name}}": "贊助文章:{{name}}",
"Custom": "自訂",
"Mintable": "Mintable",
"Successfully followed": "嘿!你已經開始<2>追蹤他們的最新動態</2>囉。",
"Enable AI Filtering": "AI 過濾",
"Filter out possible low-quality content based on AI ratings.": "基於 AI 評分過濾可能不佳的內容",
"Refreshing": "重新載入中",
"Search for your interest": "搜你所想",
"results": "項結果",
"My xLog": "你的 xLog"
"Connect": "連線",
"Followers": "粉絲",
"Following": "追蹤中",
"Followings": "追蹤中",
"Follow": "追蹤",
"Unfollow": "取消追蹤",
"intlDateTime": "{{val, datetime}}",
"post": "文章",
"posts": "文章",
"page": "頁面",
"pages": "頁面",
"comment": "留言",
"comments": "留言",
"Comment": "留言",
"Comments": "留言",
"reply": "回覆",
"replies": "回覆",
"like": "讚",
"likes": "讚",
"blog": "部落格",
"Owner": "擁有者",
"Transaction Hash": "交易雜湊值",
"IPFS Address": "IPFS 位址",
"BNB Greenfield Address": "BNB Greenfield 位址",
"Creation": "創建",
"Last Update": "最後更新",
"Create Character": "創建身份",
"Submit": "提交",
"Reply": "回覆",
"Cancel Reply": "取消回覆",
"Edit": "編輯",
"Cancel Edit": "取消編輯",
"Confirm Modification": "確認編輯",
"ago": "{{time}}前",
"joined ago": "{{time}}前加入",
"obtained ago": "{{time}}前獲得",
"Like": "點讚",
"Mint to an NFT": "珍藏為 NFT",
"Like successfully": "點讚成功",
"Mint successfully": "珍藏完成~",
"like stored": "你的點讚已被安全地儲存在區塊鏈上,可以在 <2>Crossbell Scan</2> 上查看",
"mint stored": "你已將這篇文章珍藏成 NFT可以在 <2>xChar</2> 或 <6>Crossbell Scan</6> 上查看",
"Got it, thanks!": "知道啦,感恩的心~",
"Like List": "按讚列表",
"Mint List": "珍藏列表",
"Revert": "收回",
"Confirm to revert": "確定收回",
"like revert": "請確定是否想要收回這個點讚操作?",
"Cancel": "算了",
"Confirm": "確定",
"No Content Yet.": "什麼都沒",
"Close": "關閉",
"Dashboard": "主控臺",
"Copied!": "拷貝完成",
"Operator Sign": "簽名授權",
"Switch Characters": "切換身份",
"Upgrade to Wallet": "升級為錢包認證",
"Disconnect": "中斷連線",
"Loading": "載入中...",
"Showcase": "展示櫃",
"AI-generated summary": "AI 生成的摘要",
"Generating": "生成中...",
"Show more": "顯示更多",
"Patron": "贊助",
"Become a patron of {{name}}": "成為 {{name}} 的贊助者",
"Latest patrons": "最新贊助者",
"Latest tipper": "最新贊助者",
"You are here to be the first patron.": "你是第一位贊助者。",
"You are here to be the first tipper.": "你是第一位贊助者。",
"Select a tier": "選擇一個等級",
"One-time": "一次性",
"Monthly and NFT Rewards": "每月和 NFT 獎勵",
"Coming soon": "即將推出",
"What is MIRA? Where can I get some?": "MIRA 是什麼?能吃嗎?我在哪裡可以獲得?",
"Tip": "贊助",
"Tip the post: {{name}}": "贊助文章:{{name}}",
"Custom": "自訂",
"Mintable": "Mintable",
"Successfully followed": "嘿!你已經開始<2>追蹤他們的最新動態</2>囉。",
"Enable AI Filtering": "AI 過濾",
"Filter out possible low-quality content based on AI ratings.": "基於 AI 評分過濾可能不佳的內容",
"Refreshing": "重新載入中",
"Search for your interest": "搜你所想",
"results": "項結果",
"My xLog": "你的 xLog",
"signed and stored on the blockchain": "此{{name}}數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。"
}

View File

@ -4,7 +4,6 @@
"About": "關於",
"Tags": "標籤",
"load more": "還有{{count}}篇{{name}},點選載入更多文章",
"signed and stored on the blockchain": "此{{name}}數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。",
"Write a comment on the blockchain": "在區塊鏈上留言。",
"powered by": "<span>由 </span><name/><span> 提供支援</span>",
"This address is in local editing preview mode and cannot be viewed by the public.": "此地址處於本地編輯預覽模式,尚未公開。",

View File

@ -83,5 +83,6 @@
"Refreshing": "刷新中",
"Search for your interest": "搜索你感兴趣的内容",
"results": "条结果",
"My xLog": "我的 xLog"
"My xLog": "我的 xLog",
"signed and stored on the blockchain": "此{{name}}数据所有权由区块链加密技术和智能合约保障仅归创作者所有。"
}

View File

@ -4,7 +4,6 @@
"About": "关于",
"Tags": "标签",
"load more": "还有 {{count}} 篇{{name}},点击加载更多",
"signed and stored on the blockchain": "此{{name}}数据所有权由区块链加密技术和智能合约保障仅归创作者所有。",
"Write a comment on the blockchain": "在区块链上写下你的评论",
"powered by": "<span>由 </span><name/><span> 提供支持</span>",
"This address is in local editing preview mode and cannot be viewed by the public.":

View File

@ -0,0 +1,157 @@
import { notFound } from "next/navigation"
import { Hydrate, dehydrate } from "@tanstack/react-query"
import { BlockchainInfo } from "~/components/common/BlockchainInfo"
import { Style } from "~/components/common/Style"
import { BackToTopFAB } from "~/components/site/BackToTopFAB"
import { SiteFooter } from "~/components/site/SiteFooter"
import { SiteHeader } from "~/components/site/SiteHeader"
import { FABContainer } from "~/components/ui/FAB"
import getQueryClient from "~/lib/query-client"
import { ExpandedNote } from "~/lib/types"
import { cn } from "~/lib/utils"
import { fetchGetPage } from "~/queries/page.server"
import {
fetchGetSite,
prefetchGetSiteSubscriptions,
prefetchGetSiteToSubscriptions,
} from "~/queries/site.server"
export default async function SiteLayout({
children,
params,
}: {
children?: React.ReactNode
params: {
site: string
page?: string
tag?: string
}
}) {
const queryClient = getQueryClient()
const site = await fetchGetSite(params.site, queryClient)
let page
if (site?.characterId) {
const result = await Promise.all([
prefetchGetSiteSubscriptions(
{
characterId: site.characterId,
},
queryClient,
),
prefetchGetSiteToSubscriptions(
{
characterId: site.characterId,
},
queryClient,
),
new Promise<ExpandedNote | null>(async (resolve, reject) => {
if (params.page) {
try {
const page = await fetchGetPage(
{
characterId: site.characterId,
slug: params.page,
useStat: true,
},
queryClient,
)
if (
!page ||
new Date(page!.metadata?.content?.date_published || "") >
new Date()
) {
reject(notFound())
} else {
resolve(page)
}
} catch (error) {
reject(error)
}
} else {
// if (!options?.skipPages) {
// await prefetchGetPagesBySite(
// {
// characterId: site.characterId,
// ...(options?.limit && { limit: options.limit }),
// type: "post",
// visibility: PageVisibilityEnum.Published,
// ...(params.tag && { tags: [params.tag] }),
// ...(options?.useStat && {
// useStat: true,
// }),
// },
// queryClient,
// )
// }
resolve(null)
}
}),
])
page = result[2]
} else {
notFound()
}
const dehydratedState = dehydrate(queryClient)
return (
<Hydrate state={dehydratedState}>
<div
className={cn()
// TODO
// {
// "xlog-user": true,
// "xlog-user-login": isConnected,
// "xlog-user-site-owner": userRole?.data === "owner",
// "xlog-user-site-operator": userRole?.data === "operator",
// "xlog-user-site-follower": subscription?.data,
// "xlog-user-post-liker": isLiked,
// "xlog-user-post-minter": isMint?.data?.count,
// },
// `xlog-page-${type}`,
}
>
{/* <SEOHead
title={title || tag || page.data?.metadata?.content?.title || ""}
siteName={site.data?.metadata?.content?.name || ""}
description={
page.data?.metadata?.content?.summary ??
site.data?.metadata?.content?.bio?.replace(/<[^>]*>/g, "")
}
image={
page.data?.metadata?.content?.cover ||
getUserContentsUrl(site.data?.metadata?.content?.avatars?.[0])
}
icon={getUserContentsUrl(site.data?.metadata?.content?.avatars?.[0])}
site={domainOrSubdomain}
/> */}
<Style content={site?.metadata?.content?.css} />
{site && <SiteHeader site={site} />}
<div
className={cn(
"max-w-screen-md mx-auto px-5 pt-12 relative",
page && `xlog-post-id-${page.characterId}-${page.noteId}`,
page?.metadata?.content?.tags?.map((tag) => `xlog-post-tag-${tag}`),
)}
>
{children}
</div>
{site && (
<div className="max-w-screen-md mx-auto pt-12 pb-10">
<BlockchainInfo site={site} page={page || undefined} />
</div>
)}
<SiteFooter site={site || undefined} />
<FABContainer>
<BackToTopFAB />
</FABContainer>
</div>
</Hydrate>
)
}

View File

@ -0,0 +1,38 @@
import { Hydrate, dehydrate } from "@tanstack/react-query"
import SiteHome from "~/components/site/SiteHome"
import getQueryClient from "~/lib/query-client"
import { PageVisibilityEnum } from "~/lib/types"
import { prefetchGetPagesBySite } from "~/queries/page.server"
import { fetchGetSite } from "~/queries/site.server"
async function SiteIndexPage({
params,
}: {
params: {
site: string
}
}) {
const queryClient = getQueryClient()
const site = await fetchGetSite(params.site, queryClient)
prefetchGetPagesBySite(
{
characterId: site?.characterId,
type: "post",
visibility: PageVisibilityEnum.Published,
useStat: true,
},
queryClient,
)
const dehydratedState = dehydrate(queryClient)
return (
<Hydrate state={dehydratedState}>
<SiteHome handle={params.site} />
</Hydrate>
)
}
export default SiteIndexPage

View File

@ -1,9 +1,10 @@
import { useTranslation } from "next-i18next"
"use client"
import { Disclosure } from "@headlessui/react"
import { BlockchainIcon } from "~/components/icons/BlockchainIcon"
import { CSB_SCAN } from "~/lib/env"
import { useTranslation } from "~/lib/i18n/client"
import { toCid, toGateway, toIPFS } from "~/lib/ipfs-parser"
import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
import { cn } from "~/lib/utils"
@ -13,7 +14,7 @@ export const BlockchainInfo: React.FC<{
site?: ExpandedCharacter
page?: ExpandedNote
}> = ({ site, page }) => {
const { t } = useTranslation(["common", "site"])
const { t } = useTranslation("common")
const ipfs = (page ? page.metadata?.uri : site?.metadata?.uri) || ""
const greenfieldId = useGetGreenfieldId(toCid(ipfs))

View File

@ -1,3 +1,5 @@
"use client"
import { useEffect, useState } from "react"
import { throttle } from "~/lib/utils"

View File

@ -1,10 +1,9 @@
import { Trans } from "next-i18next"
import Script from "next/script"
import { useEffect, useState } from "react"
import { Logo } from "~/components/common/Logo"
import { Platform } from "~/components/site/Platform"
import { SITE_URL } from "~/lib/env"
import { Trans } from "~/lib/i18n"
import { ExpandedCharacter } from "~/lib/types"
import { DarkModeSwitch } from "../common/DarkModeSwitch"
@ -13,19 +12,13 @@ import { UniLink } from "../ui/UniLink"
export const SiteFooter: React.FC<{
site?: ExpandedCharacter
}> = ({ site }) => {
const [logoType, setLogoType] = useState<"svg" | "png" | "lottie">("svg")
useEffect(() => {
setLogoType("lottie")
}, [])
const LogoWithLink = () => {
return (
<UniLink
href={SITE_URL}
className="inline-flex items-center align-text-top mx-1"
>
<Logo type={logoType} width={20} height={20} autoplay={false} />
<Logo type="lottie" width={20} height={20} autoplay={false} />
</UniLink>
)
}

View File

@ -1,7 +1,8 @@
"use client"
import chroma from "chroma-js"
import { FastAverageColor } from "fast-average-color"
import { useTranslation } from "next-i18next"
import { useRouter } from "next/router"
import { usePathname } from "next/navigation"
import { MutableRefObject, RefObject, useEffect, useRef, useState } from "react"
import { XCharLogo, XFeedLogo } from "@crossbell/ui"
@ -17,6 +18,7 @@ import { Modal } from "~/components/ui/Modal"
import { Tooltip } from "~/components/ui/Tooltip"
import { useIsDark } from "~/hooks/useDarkMode"
import { CSB_IO, CSB_SCAN, CSB_XCHAR } from "~/lib/env"
import { useTranslation } from "~/lib/i18n/client"
import { ExpandedCharacter } from "~/lib/types"
import { getUserContentsUrl } from "~/lib/user-contents"
import { cn } from "~/lib/utils"
@ -37,9 +39,9 @@ type HeaderLinkType = {
const fac = new FastAverageColor()
const HeaderLink: React.FC<{ link: HeaderLinkType }> = ({ link }) => {
const router = useRouter()
const pathname = usePathname()
const { t } = useTranslation("site")
const active = router.asPath === link.url
const active = pathname === link.url // TODO
return (
<UniLink
href={link.url}

View File

@ -1,29 +1,30 @@
import { useTranslation } from "next-i18next"
"use client"
import Link from "next/link"
import { useRouter } from "next/router"
import { useRouter } from "next/navigation"
import { useEffect, useState } from "react"
import type { InfiniteData } from "@tanstack/react-query"
import { Button } from "~/components/ui/Button"
import { EmptyState } from "~/components/ui/EmptyState"
import { Image } from "~/components/ui/Image"
import { useDate } from "~/hooks/useDate"
import { getSlugUrl } from "~/lib/helpers"
import { ExpandedNote } from "~/lib/types"
import { useTranslation } from "~/lib/i18n/client"
import { PageVisibilityEnum } from "~/lib/types"
import { useGetPagesBySiteLite } from "~/queries/page"
import { useGetSite } from "~/queries/site"
import { EmptyState } from "../ui/EmptyState"
export const SiteHome: React.FC<{
posts?: InfiniteData<{
list: ExpandedNote[]
count: number
}>
fetchNextPage: () => void
hasNextPage?: boolean
isFetchingNextPage?: boolean
}> = ({ posts, fetchNextPage, hasNextPage, isFetchingNextPage }) => {
export default function SiteHome({ handle }: { handle: string }) {
const router = useRouter()
const { t } = useTranslation(["common", "site"])
const site = useGetSite(handle)
const posts = useGetPagesBySiteLite({
characterId: site.data?.characterId,
type: "post",
visibility: PageVisibilityEnum.Published,
useStat: true,
})
const { t } = useTranslation("site")
const date = useDate()
const [isMounted, setIsMounted] = useState(false)
@ -32,16 +33,16 @@ export const SiteHome: React.FC<{
setIsMounted(true)
}, [])
if (!posts?.pages?.length) return null
if (!posts.data?.pages?.length) return null
let currentLength = 0
return (
<>
{!posts.pages[0].count && <EmptyState />}
{!!posts.pages[0].count && (
{!posts.data.pages[0].count && <EmptyState />}
{!!posts.data.pages[0].count && (
<div className="xlog-posts space-y-8">
{posts.pages.map((posts) =>
{posts.data.pages.map((posts) =>
posts.list.map((post) => {
currentLength++
return (
@ -127,20 +128,21 @@ export const SiteHome: React.FC<{
)}
</div>
)}
{hasNextPage && (
{posts.hasNextPage && (
<Button
className="mt-8 w-full bg-zinc-50 text-sm"
variant="text"
onClick={fetchNextPage}
isLoading={isFetchingNextPage}
onClick={() => posts.fetchNextPage()}
isLoading={posts.isFetchingNextPage}
aria-label="load more"
>
{t("load more", {
ns: "site",
name: t(
"post" + (posts?.pages[0].count - currentLength > 1 ? "s" : ""),
"post" +
(posts.data?.pages[0].count - currentLength > 1 ? "s" : ""),
),
count: posts?.pages[0].count - currentLength,
count: posts.data?.pages[0].count - currentLength,
})}
</Button>
)}

View File

@ -1,3 +1,5 @@
"use client"
import React, { memo } from "react"
import { cn } from "~/lib/utils"

View File

@ -1,3 +1,5 @@
"use client"
import React, { FC, PropsWithChildren, useEffect, useState } from "react"
import { useGetState } from "~/hooks/useGetState"

View File

@ -8,9 +8,10 @@ import localizedFormat from "dayjs/plugin/localizedFormat"
import relativeTime from "dayjs/plugin/relativeTime"
import tz from "dayjs/plugin/timezone"
import utc from "dayjs/plugin/utc"
import { useTranslation } from "next-i18next"
import { useMemo } from "react"
import { useTranslation } from "~/lib/i18n/client"
dayjs.extend(localizedFormat)
dayjs.extend(utc)
dayjs.extend(tz)

View File

@ -0,0 +1,42 @@
import dayjs from "dayjs"
import "dayjs/locale/en"
import "dayjs/locale/ja"
import "dayjs/locale/zh"
import "dayjs/locale/zh-tw"
import duration from "dayjs/plugin/duration"
import localizedFormat from "dayjs/plugin/localizedFormat"
import relativeTime from "dayjs/plugin/relativeTime"
import tz from "dayjs/plugin/timezone"
import utc from "dayjs/plugin/utc"
import { useMemo } from "react"
import { useTranslation } from "~/lib/i18n"
dayjs.extend(localizedFormat)
dayjs.extend(utc)
dayjs.extend(tz)
dayjs.extend(duration)
dayjs.extend(relativeTime)
export async function useDate() {
const { i18n } = await useTranslation()
const memoizedDateUtils = useMemo(() => {
dayjs.locale(i18n.resolvedLanguage)
return {
dayjs,
formatDate: (date: string | Date, format = "ll", timezone?: string) => {
return dayjs(date).tz(timezone).format(format)
},
formatToISO: (date: string | Date) => {
return dayjs(date || undefined).toISOString()
},
inLocalTimezone: (date: string | Date) => {
return dayjs(date).tz().toDate()
},
}
}, [i18n.resolvedLanguage])
return memoizedDateUtils
}

View File

@ -1,4 +1,4 @@
import { NoteEntity } from "crossbell.js"
import type { NoteEntity } from "crossbell.js"
import { ExpandedCharacter, ExpandedNote } from "~/lib/types"

View File

@ -9,7 +9,7 @@ import {
import { useLang } from "~/hooks/useLang"
import { getOptions } from "./settings"
import { defaultNS, getOptions } from "./settings"
i18next
.use(initReactI18next)
@ -23,7 +23,7 @@ i18next
...getOptions(),
})
export function useTranslation(ns: string) {
export function useTranslation(ns: string = defaultNS) {
const { lang } = useLang()
if (i18next.resolvedLanguage !== lang) {
i18next.changeLanguage(lang)

View File

@ -5,7 +5,7 @@ import { initReactI18next } from "react-i18next/initReactI18next"
import { useAcceptLang } from "~/hooks/useAcceptLang"
import { getOptions } from "./settings"
import { defaultNS, getOptions } from "./settings"
const initI18next = async (lng: string, ns: string) => {
const i18nInstance = createInstance()
@ -21,7 +21,7 @@ const initI18next = async (lng: string, ns: string) => {
return i18nInstance
}
export async function useTranslation(ns: string) {
export async function useTranslation(ns: string = defaultNS) {
const lang = useAcceptLang()
const i18nextInstance = await initI18next(lang, ns)
return {

View File

@ -1,4 +1,4 @@
import { CharacterEntity, NoteEntity } from "crossbell.js"
import type { CharacterEntity, NoteEntity } from "crossbell.js"
export type Site = {
id: string

View File

@ -78,7 +78,7 @@ export default async function middleware(req: NextRequest) {
if (tenant?.subdomain) {
const url = req.nextUrl.clone()
url.pathname = `/_site/${tenant?.subdomain}${url.pathname}`
url.pathname = `/site/${tenant?.subdomain}${url.pathname}`
return NextResponse.rewrite(url)
}

View File

@ -371,45 +371,46 @@ export function useGetSummary(input: { cid?: string; lang?: string }) {
})
}
export function useGetMirrorXyz(input: { address?: string }) {
return useQuery(["getMirror", input.address], async () => {
const { getDefaultSlug } = await import("~/lib/default-slug")
// TODO
// export function useGetMirrorXyz(input: { address?: string }) {
// return useQuery(["getMirror", input.address], async () => {
// const { getDefaultSlug } = await import("~/lib/default-slug")
if (!input.address) {
return null
}
const response = await (
await fetch(
"/api/import/mirror.xyz?" +
new URLSearchParams({
...input,
} as any),
)
).json()
// if (!input.address) {
// return null
// }
// const response = await (
// await fetch(
// "/api/import/mirror.xyz?" +
// new URLSearchParams({
// ...input,
// } as any),
// )
// ).json()
return response?.data?.projectFeed?.posts?.map((post: any) => {
return {
title: post.title,
date_published: new Date(
post.publishedAtTimestamp * 1000,
).toISOString(),
slug: getDefaultSlug(post.title, post.digest),
tags: ["Mirror.xyz"],
content: post.body,
external_urls: [`https://mirror.xyz/${input.address}/${post.digest}`],
}
}) as {
title: string
type: string
size: number
date_published: string
slug: string
tags: string[]
content: string
external_urls: string[]
}[]
})
}
// return response?.data?.projectFeed?.posts?.map((post: any) => {
// return {
// title: post.title,
// date_published: new Date(
// post.publishedAtTimestamp * 1000,
// ).toISOString(),
// slug: getDefaultSlug(post.title, post.digest),
// tags: ["Mirror.xyz"],
// content: post.body,
// external_urls: [`https://mirror.xyz/${input.address}/${post.digest}`],
// }
// }) as {
// title: string
// type: string
// size: number
// date_published: string
// slug: string
// tags: string[]
// content: string
// external_urls: string[]
// }[]
// })
// }
export function useCheckMirror(characterId?: number) {
return useQuery(["checkMirror", characterId], async () => {