release (#1196)
* refactor: unify loading component (#1184) * chore: move themeColor to viewport (#1185) * fix: i18n key word (#1190) * feat: make xlogshorts more general (#1192) * fix: allow magnet protocol in href (#1193) * fix: allow magnet protocol in href * chore: update * chore: add ed2k * docs: update required node version (#1194) * fix: replace div with a tag for better SEO. * fix: can't scroll to top when browsing post (#1197) --------- Co-authored-by: Stephen Zhou <hi@hyoban.cc> Co-authored-by: XLor <yjl9903@onekuma.cn>
This commit is contained in:
parent
758add612a
commit
c0b89dedce
|
|
@ -10,7 +10,7 @@ https://docs.github.com/en/get-started/quickstart/fork-a-repo
|
|||
|
||||
## Requirements
|
||||
|
||||
- [Node.js](https://nodejs.org). Version needs to be >=15.
|
||||
- [Node.js](https://nodejs.org) 18.17 or later.
|
||||
- [pnpm](https://pnpm.io/) package manager.
|
||||
- [Docker](https://www.docker.com/) for running the cache database.
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@
|
|||
"clsx": "2.0.0",
|
||||
"crossbell": "1.10.1",
|
||||
"dayjs": "1.11.10",
|
||||
"deepmerge": "^4.3.1",
|
||||
"dotenv": "16.3.1",
|
||||
"emoji-mart": "5.5.2",
|
||||
"fast-average-color": "9.4.0",
|
||||
|
|
@ -210,7 +211,7 @@
|
|||
"typescript": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=15"
|
||||
"node": ">=18.17.0"
|
||||
},
|
||||
"nextBundleAnalysis": {
|
||||
"budget": 358400,
|
||||
|
|
|
|||
|
|
@ -140,6 +140,9 @@ dependencies:
|
|||
dayjs:
|
||||
specifier: 1.11.10
|
||||
version: 1.11.10
|
||||
deepmerge:
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1
|
||||
dotenv:
|
||||
specifier: 16.3.1
|
||||
version: 16.3.1
|
||||
|
|
@ -6430,6 +6433,11 @@ packages:
|
|||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
dev: true
|
||||
|
||||
/deepmerge@4.3.1:
|
||||
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/define-data-property@1.1.0:
|
||||
resolution: {integrity: sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useEffect } from "react"
|
|||
import { Virtuoso } from "react-virtuoso"
|
||||
|
||||
import { CommentItem } from "~/components/common/CommentItem"
|
||||
import { Loading } from "~/components/common/Loading"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
|
|
@ -65,7 +66,7 @@ export default function CommentsPage() {
|
|||
comments.hasNextPage && comments.fetchNextPage()
|
||||
}
|
||||
components={{
|
||||
Footer: comments.isLoading ? Loader : undefined,
|
||||
Footer: comments.isLoading ? Loading : undefined,
|
||||
}}
|
||||
itemContent={(_, p) =>
|
||||
p?.list?.map((comment, idx) => {
|
||||
|
|
@ -125,15 +126,3 @@ export default function CommentsPage() {
|
|||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
const Loader = () => {
|
||||
const t = useTranslations()
|
||||
return (
|
||||
<div
|
||||
className="relative mt-4 w-full text-sm text-center py-4"
|
||||
key={"loading"}
|
||||
>
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import toast from "react-hot-toast"
|
|||
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import { Loading } from "~/components/common/Loading"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import EditorAutofill from "~/components/dashboard/editor-properties/EditorAutofill"
|
||||
import EditorCover from "~/components/dashboard/editor-properties/EditorCover"
|
||||
|
|
@ -247,9 +248,7 @@ export default function PortfolioEditor() {
|
|||
<>
|
||||
<DashboardMain className="max-w-screen-lg" title="Edit portfolio">
|
||||
{page.isLoading ? (
|
||||
<div className="flex justify-center items-center min-h-[300px]">
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
<Loading className="min-h-[300px]" />
|
||||
) : (
|
||||
<>
|
||||
<EditorExtraProperties updateValue={updateValue} />
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import toast from "react-hot-toast"
|
|||
import type { EditorView } from "@codemirror/view"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import { Loading } from "~/components/common/Loading"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import DualColumnEditor from "~/components/dashboard/DualColumnEditor"
|
||||
import EditorCover from "~/components/dashboard/editor-properties/EditorCover"
|
||||
|
|
@ -385,9 +386,7 @@ export default function PostEditor() {
|
|||
<>
|
||||
<DashboardMain fullWidth>
|
||||
{page.isLoading ? (
|
||||
<div className="flex justify-center items-center min-h-[300px]">
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
<Loading className="min-h-[300px]" />
|
||||
) : (
|
||||
<>
|
||||
<header
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import toast from "react-hot-toast"
|
|||
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import { Loading } from "~/components/common/Loading"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import EditorContent from "~/components/dashboard/editor-properties/EditorContent"
|
||||
import EditorImages from "~/components/dashboard/editor-properties/EditorImages"
|
||||
|
|
@ -249,9 +250,7 @@ export default function ShortEditor() {
|
|||
<>
|
||||
<DashboardMain className="max-w-screen-lg" title="Edit short">
|
||||
{page.isLoading ? (
|
||||
<div className="flex justify-center items-center min-h-[300px]">
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
<Loading className="min-h-[300px]" />
|
||||
) : (
|
||||
<>
|
||||
<EditorExtraProperties updateValue={updateValue} />
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from "@crossbell/notification"
|
||||
|
||||
import { ConnectButton } from "~/components/common/ConnectButton"
|
||||
import { Loading } from "~/components/common/Loading"
|
||||
import { Logo } from "~/components/common/Logo"
|
||||
import { DashboardTopbar } from "~/components/dashboard/DashboardTopbar"
|
||||
import { Avatar } from "~/components/ui/Avatar"
|
||||
|
|
@ -444,8 +445,6 @@ export default function DashboardLayout({
|
|||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="w-screen h-screen flex justify-center items-center">
|
||||
Loading...
|
||||
</div>
|
||||
<Loading className="w-screen h-screen" />
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ export default function SubdomainIndex() {
|
|||
</UniLink>
|
||||
</li>
|
||||
</ul>
|
||||
{t.rich("hello.tips", {
|
||||
{t.rich("hello.community", {
|
||||
p: (chunks) => <p>{chunks}</p>,
|
||||
})}
|
||||
<ul
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Loading } from "~/components/common/Loading"
|
||||
|
||||
import PageComponent from "./page-component"
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<>
|
||||
<PageComponent />
|
||||
<div className="flex items-center justify-center w-full h-60">
|
||||
Loading...
|
||||
</div>
|
||||
<Loading className="h-60" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
import "@crossbell/connect-kit/colors.css"
|
||||
import "@mantine/core/styles.css"
|
||||
import "remark-github-alerts/styles/github-colors-light.css"
|
||||
import "remark-github-alerts/styles/github-colors-dark-class.css"
|
||||
import "remark-github-alerts/styles/github-base.css"
|
||||
import "~/css/main.css"
|
||||
|
||||
import { Viewport } from "next"
|
||||
import { NextIntlClientProvider } from "next-intl"
|
||||
import { getMessages, unstable_setRequestLocale } from "next-intl/server"
|
||||
import { headers } from "next/headers"
|
||||
|
|
@ -17,18 +25,14 @@ import {
|
|||
UMAMI_SCRIPT,
|
||||
} from "~/lib/env"
|
||||
import { getColorScheme } from "~/lib/get-color-scheme"
|
||||
import { withHrefLang } from "~/lib/with-hreflang"
|
||||
|
||||
import { ColorSchemeInjector } from "./ColorSchemeInjector"
|
||||
import Providers, { mantineDefaultColorScheme, mantineTheme } from "./providers"
|
||||
|
||||
import "@crossbell/connect-kit/colors.css"
|
||||
import "@mantine/core/styles.css"
|
||||
import "remark-github-alerts/styles/github-colors-light.css"
|
||||
import "remark-github-alerts/styles/github-colors-dark-class.css"
|
||||
import "remark-github-alerts/styles/github-base.css"
|
||||
import "~/css/main.css"
|
||||
|
||||
import { withHrefLang } from "~/lib/with-hreflang"
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#ffffff",
|
||||
}
|
||||
|
||||
export const generateMetadata = withHrefLang(async () => ({
|
||||
title: `${APP_NAME} - ${APP_SLOGAN}`,
|
||||
|
|
@ -44,7 +48,6 @@ export const generateMetadata = withHrefLang(async () => ({
|
|||
"dapp",
|
||||
"crypto",
|
||||
],
|
||||
themeColor: "#ffffff",
|
||||
alternates: {
|
||||
types: {
|
||||
"application/rss+xml": [
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { ExpandedCharacter } from "~/lib/types"
|
|||
|
||||
import { Button } from "../ui/Button"
|
||||
import CharacterListItem from "./CharacterListItem"
|
||||
import { Loading } from "./Loading"
|
||||
import { PortalProvider } from "./Portal"
|
||||
|
||||
export const CharacterList = ({
|
||||
|
|
@ -57,7 +58,7 @@ export const CharacterList = ({
|
|||
className="max-h-screen"
|
||||
endReached={() => hasMore && loadMore()}
|
||||
components={{
|
||||
Footer: hasMore ? Loader : undefined,
|
||||
Footer: hasMore ? Loading : undefined,
|
||||
}}
|
||||
data={flattenList}
|
||||
itemContent={(index, sub) => {
|
||||
|
|
@ -89,12 +90,3 @@ export const CharacterList = ({
|
|||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const Loader = () => {
|
||||
const t = useTranslations()
|
||||
return (
|
||||
<div className="text-sm py-3 text-center" key={0}>
|
||||
{t("Loading")} ...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { ExpandedNote } from "~/lib/types"
|
|||
import { cn } from "~/lib/utils"
|
||||
import { useGetComments } from "~/queries/page"
|
||||
|
||||
import { Loading } from "./Loading"
|
||||
|
||||
const Comment = ({
|
||||
page,
|
||||
className,
|
||||
|
|
@ -64,7 +66,7 @@ const Comment = ({
|
|||
data={data}
|
||||
endReached={() => comments.hasNextPage && comments.fetchNextPage()}
|
||||
components={{
|
||||
Footer: comments.isLoading ? Loader : undefined,
|
||||
Footer: comments.isLoading ? Loading : undefined,
|
||||
}}
|
||||
totalListHeightChanged={(height) => {
|
||||
if (fixHeight && height > 0) {
|
||||
|
|
@ -89,23 +91,7 @@ const Comment = ({
|
|||
))
|
||||
}
|
||||
></Virtuoso>
|
||||
{comments.isLoading && (
|
||||
<div className="relative mt-4 w-full text-sm text-center py-4">
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Loader = () => {
|
||||
const t = useTranslations()
|
||||
return (
|
||||
<div
|
||||
className="relative mt-4 w-full text-sm text-center py-4"
|
||||
key={"loading"}
|
||||
>
|
||||
{t("Loading")}...
|
||||
{comments.isLoading && <Loading />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import { useTranslations } from "next-intl"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
export const Loading = ({
|
||||
className,
|
||||
}: {
|
||||
className?: string
|
||||
context?: unknown
|
||||
}) => {
|
||||
const t = useTranslations()
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex gap-2 justify-center items-center w-full my-6",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="i-mingcute-loading-3-fill animate-spin text-xl"></div>
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import { ModalContentProps, useModalStack } from "../ui/ModalStack"
|
|||
import { Tabs } from "../ui/Tabs"
|
||||
import { UniLink } from "../ui/UniLink"
|
||||
import { CharacterFloatCard } from "./CharacterFloatCard"
|
||||
import { Loading } from "./Loading"
|
||||
|
||||
export const usePatronModal = () => {
|
||||
const { present } = useModalStack()
|
||||
|
|
@ -179,7 +180,7 @@ const PatronModal = ({
|
|||
</div>
|
||||
<div className="text-zinc-500 text-sm mt-2">
|
||||
{tips.isLoading ? (
|
||||
"Loading..."
|
||||
<Loading />
|
||||
) : tips.data?.pages?.[0]?.list?.length ? (
|
||||
<>
|
||||
<ul className="flex items-center justify-center">
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ const Card = ({
|
|||
? externalLink || ""
|
||||
: `${linkPrefix || ""}/${post.metadata?.content?.slug}${queryString}`
|
||||
}
|
||||
scroll={false}
|
||||
scroll={!linkPrefix}
|
||||
className={cn(
|
||||
"xlog-post rounded-2xl flex flex-col items-center group relative",
|
||||
isShort
|
||||
|
|
|
|||
|
|
@ -13,15 +13,13 @@ import { useUploadFile } from "~/hooks/useUploadFile"
|
|||
import { cn } from "~/lib/utils"
|
||||
import { Rendered, renderPageContent } from "~/markdown"
|
||||
|
||||
import { Loading } from "../common/Loading"
|
||||
|
||||
const DynamicCodeMirrorEditor = dynamic(
|
||||
() => import("~/components/ui/CodeMirror"),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="flex-1 h-12 flex items-center justify-center">
|
||||
Loading...
|
||||
</div>
|
||||
),
|
||||
loading: () => <Loading className="flex-1 h-12" />,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -29,11 +27,7 @@ const DynamicPageContent = dynamic(
|
|||
() => import("~/components/common/PageContent"),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="flex-1 h-12 flex items-center justify-center">
|
||||
Loading...
|
||||
</div>
|
||||
),
|
||||
loading: () => <Loading className="flex-1 h-12" />,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { cn } from "~/lib/utils"
|
|||
import { useGetPagesBySite, usePinnedPage } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
import { Loading } from "../common/Loading"
|
||||
import { PlatformsSyncMap } from "../site/Platform"
|
||||
import { Button } from "../ui/Button"
|
||||
import { EmptyState } from "../ui/EmptyState"
|
||||
|
|
@ -317,7 +318,7 @@ export const PagesManager = ({ type }: { type: NoteType }) => {
|
|||
)}
|
||||
|
||||
<div className="-mt-3">
|
||||
{pages.isLoading && <p className="py-4 px-3">{t("Loading")}...</p>}
|
||||
{pages.isLoading && <Loading />}
|
||||
{!pages.isLoading && !pages.data?.pages?.[0].count && (
|
||||
<EmptyState resource={type} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { VirtuosoGrid } from "react-virtuoso"
|
|||
import { useAccountState, useConnectModal } from "@crossbell/connect-kit"
|
||||
import { Switch } from "@headlessui/react"
|
||||
|
||||
import { Loading } from "~/components/common/Loading"
|
||||
import PostCard from "~/components/common/PostCard"
|
||||
import { EmptyState } from "~/components/ui/EmptyState"
|
||||
import { Skeleton } from "~/components/ui/Skeleton"
|
||||
|
|
@ -245,7 +246,7 @@ export const HomeFeed = ({ type }: { type?: FeedType }) => {
|
|||
}}
|
||||
></VirtuosoGrid>
|
||||
|
||||
{feed.isFetching && feed.hasNextPage && isMounted && <Loader />}
|
||||
{feed.isFetching && feed.hasNextPage && isMounted && <Loading />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -253,18 +254,6 @@ export const HomeFeed = ({ type }: { type?: FeedType }) => {
|
|||
)
|
||||
}
|
||||
|
||||
const Loader = () => {
|
||||
const t = useTranslations()
|
||||
return (
|
||||
<div
|
||||
className="relative w-full text-sm text-center py-4 mt-12"
|
||||
key={"loading"}
|
||||
>
|
||||
{t("Loading")} ...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FeedSkeleton = () => {
|
||||
return (
|
||||
<Skeleton.Container
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import { getSiteLink } from "~/lib/helpers"
|
|||
import { cn } from "~/lib/utils"
|
||||
import { useGetShowcase } from "~/queries/home"
|
||||
|
||||
import { Loading } from "../common/Loading"
|
||||
|
||||
export function ShowCase() {
|
||||
const showcaseSites = useGetShowcase()
|
||||
const [showcaseMore, setShowcaseMore] = useState(false)
|
||||
|
|
@ -28,7 +30,7 @@ export function ShowCase() {
|
|||
.map(Number)}
|
||||
siteIds={showcaseSites.data?.map((s: { handle: string }) => s.handle)}
|
||||
/>
|
||||
{showcaseSites.isLoading && <p className="mt-10">{t("Loading")}...</p>}
|
||||
{showcaseSites.isLoading && <Loading className="mt-10" />}
|
||||
<ul
|
||||
className={`pt-10 grid grid-cols-2 md:grid-cols-3 gap-10 overflow-y-hidden relative text-left ${
|
||||
showcaseMore ? "" : "max-h-[540px]"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { NoteType, PagesSortTypes, PageVisibilityEnum } from "~/lib/types"
|
|||
import { useGetPagesBySiteLite, usePinnedPage } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
import { Loading } from "../common/Loading"
|
||||
|
||||
export default function SiteHome({
|
||||
handle,
|
||||
type,
|
||||
|
|
@ -70,7 +72,7 @@ export default function SiteHome({
|
|||
return (
|
||||
<>
|
||||
<Tabs items={tabs} className="mb-6" type="rounded" />
|
||||
{!posts.data?.pages?.length && posts.isLoading && <>Loading...</>}
|
||||
{!posts.data?.pages?.length && posts.isLoading && <Loading />}
|
||||
<PostList
|
||||
posts={posts}
|
||||
pinnedNoteId={pinnedPage.noteId}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import PostList from "~/components/site/PostList"
|
|||
import { useGetSearchPagesBySite } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
import { Loading } from "../common/Loading"
|
||||
|
||||
export const SiteSearch = () => {
|
||||
const t = useTranslations()
|
||||
|
||||
|
|
@ -24,7 +26,7 @@ export const SiteSearch = () => {
|
|||
<h2 className="mb-8 mt-5 text-zinc-500">
|
||||
{posts.data?.pages?.[0].count || "0"} {t("results")}
|
||||
</h2>
|
||||
{posts.isLoading && <>{t("Loading")}...</>}
|
||||
{posts.isLoading && <Loading />}
|
||||
<PostList posts={posts} keyword={keyword} />
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,13 +50,7 @@ import { useIsDark } from "~/hooks/useDarkMode"
|
|||
import { useGetState } from "~/hooks/useGetState"
|
||||
import { useIsUnmounted } from "~/hooks/useLifecycle"
|
||||
|
||||
const LoadingHolder = () => {
|
||||
return (
|
||||
<div className="flex-1 h-12 flex items-center justify-center">
|
||||
Loading...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
import { Loading } from "../common/Loading"
|
||||
|
||||
interface XLogCodeMirrorEditorProps {
|
||||
value?: string
|
||||
|
|
@ -229,7 +223,7 @@ const CodeMirrorEditor = forwardRef<
|
|||
(props.LoadingComponent ? (
|
||||
createElement(props.LoadingComponent)
|
||||
) : (
|
||||
<LoadingHolder />
|
||||
<Loading className="flex-1 h-12" />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
"use client"
|
||||
|
||||
import { useLocale } from "next-intl"
|
||||
import React, { FC, useCallback } from "react"
|
||||
|
||||
import { Language } from "~/lib/types"
|
||||
import { cn, isMobileDevice } from "~/lib/utils"
|
||||
import { useGetPage } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
import { Loading } from "../common/Loading"
|
||||
import { Time } from "../common/Time"
|
||||
import PostCover from "../home/PostCover"
|
||||
import { Avatar } from "./Avatar"
|
||||
import { UniLink } from "./UniLink"
|
||||
|
||||
interface Props {
|
||||
slug: string
|
||||
handle: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const XLogPost: FC<Props> = ({ slug, handle, url }) => {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false)
|
||||
|
||||
const locale = useLocale() as Language
|
||||
// https://xlog.app/site/lca/JOoXQKAtZYYFrnzntDlJ6?content_type=shorts
|
||||
const site = useGetSite(handle)
|
||||
const page = useGetPage({
|
||||
characterId: site.data?.characterId,
|
||||
slug,
|
||||
handle,
|
||||
disableAutofill: true,
|
||||
translateTo: locale,
|
||||
})
|
||||
|
||||
const isMobile = isMobileDevice()
|
||||
const images = page.data?.metadata.content.images || []
|
||||
const isShort = !!page.data?.metadata?.content?.tags?.includes("short")
|
||||
|
||||
const preventNavigate = useCallback(
|
||||
(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const toggleExpand = useCallback(
|
||||
(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
preventNavigate(e)
|
||||
setIsExpanded((prev) => !prev)
|
||||
},
|
||||
[preventNavigate],
|
||||
)
|
||||
|
||||
if (page.isLoading) return <Loading />
|
||||
|
||||
return (
|
||||
<UniLink href={url} className="!no-underline !text-inherit">
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col md:flex-row w-full bg-zinc-50 rounded-xl p-4 gap-x-4 hover:cursor-pointer my-2 transition-all duration-500 ease-in-out",
|
||||
isExpanded ? "sm:h-[350px]" : "sm:h-[150px]",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
onClick={preventNavigate}
|
||||
className={cn(
|
||||
"w-full h-full mb-2 sm:mb-0 align-middle flex items-center justify-center transition-all duration-500 ease-in-out",
|
||||
isExpanded ? "sm:w-[350px]" : "sm:w-[150px]",
|
||||
)}
|
||||
>
|
||||
<PostCover
|
||||
uniqueKey={`short-${page.data?.characterId}-${page.data?.noteId}`}
|
||||
images={images}
|
||||
title={page.data?.metadata?.content?.title}
|
||||
className="rounded-lg w-full aspect-auto border-b-0 h-full"
|
||||
imgClassName="rounded-lg object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col justify-between">
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{page.data?.metadata?.content?.title && (
|
||||
<div className="font-bold mb-2 text-lg truncate">
|
||||
{page.data?.metadata?.content?.title}
|
||||
</div>
|
||||
)}
|
||||
<div className="prose overflow-y-auto">
|
||||
<p
|
||||
className={
|
||||
isExpanded || isMobile ? "line-clamp-6" : "line-clamp-1"
|
||||
}
|
||||
>
|
||||
{isShort
|
||||
? page.data?.metadata?.content?.content
|
||||
: page.data?.metadata?.content?.summary}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row w-full justify-between items-end sm:mt-0 mt-4">
|
||||
<span className="text-sm">
|
||||
<Time isoString={page.data?.metadata?.content?.date_published} />
|
||||
</span>
|
||||
|
||||
<span className="p-2 flex w-auto justify-end items-center transition-colors py-1 rounded-lg">
|
||||
<Avatar
|
||||
cid={site.data?.characterId}
|
||||
className="align-middle"
|
||||
images={site.data?.metadata?.content?.avatars || []}
|
||||
name={site.data?.metadata?.content?.name}
|
||||
size={32}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`flex-1 flex-col min-w-0 ml-2 max-w-[100px] flex sm:mr-6`}
|
||||
>
|
||||
<span
|
||||
className={`text-left leading-none font-medium truncate text-gray-600 text-base`}
|
||||
style={{ marginBottom: "0.15rem" }}
|
||||
>
|
||||
{site.data?.metadata?.content?.name}
|
||||
</span>
|
||||
{site.data?.handle && (
|
||||
<span
|
||||
className={`text-left leading-none text-sm truncate text-gray-400`}
|
||||
>
|
||||
{"@" + site.data?.handle}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<i
|
||||
className={cn(
|
||||
"i-mingcute-arrows-down-line text-xl ml-[2px] text-accent hover:cursor-pointer",
|
||||
isExpanded ? "rotate-180" : "rotate-0",
|
||||
"transition-transform duration-500 ease-in-out",
|
||||
"hidden sm:inline-block",
|
||||
)}
|
||||
onClick={toggleExpand}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UniLink>
|
||||
)
|
||||
}
|
||||
|
||||
export default XLogPost
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useLocale } from "next-intl"
|
||||
import React, { FC, useCallback } from "react"
|
||||
|
||||
import { Language } from "~/lib/types"
|
||||
import { cn, isMobileDevice } from "~/lib/utils"
|
||||
import { useGetPage } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
import { Time } from "../common/Time"
|
||||
import PostCover from "../home/PostCover"
|
||||
import { Avatar } from "./Avatar"
|
||||
import { UniLink } from "./UniLink"
|
||||
|
||||
interface Props {
|
||||
slug: string
|
||||
handle: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const XLogShorts: FC<Props> = ({ slug, handle, url }) => {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false)
|
||||
|
||||
const locale = useLocale() as Language
|
||||
// https://xlog.app/site/lca/JOoXQKAtZYYFrnzntDlJ6?content_type=shorts
|
||||
const site = useGetSite(handle)
|
||||
const page = useGetPage({
|
||||
characterId: site.data?.characterId,
|
||||
slug,
|
||||
handle,
|
||||
disableAutofill: true,
|
||||
translateTo: locale,
|
||||
})
|
||||
|
||||
const images = page.data?.metadata?.content?.attachments
|
||||
?.filter((attachment) => attachment.name === "image")
|
||||
.map((img) => img.address || "")
|
||||
.filter(Boolean)
|
||||
|
||||
const isMobile = isMobileDevice()
|
||||
const isShort = !!page.data?.metadata?.content?.tags?.includes("short")
|
||||
|
||||
const toggleExpand = useCallback(
|
||||
(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
e.stopPropagation()
|
||||
setIsExpanded((prev) => !prev)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const handleRedirect = useCallback(() => {
|
||||
window.open(url, "_blank")
|
||||
}, [url])
|
||||
|
||||
if (page.isLoading) return <div>Loading...</div>
|
||||
|
||||
if (!isShort) return <UniLink href={url}>{url}</UniLink>
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleRedirect}
|
||||
className={cn(
|
||||
"flex flex-col md:flex-row w-full bg-zinc-50 rounded-xl p-4 gap-x-4 hover:cursor-pointer",
|
||||
isExpanded ? "sm:h-[350px]" : "sm:h-[150px]",
|
||||
"transition-all duration-500 ease-in-out",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className={cn(
|
||||
"w-full h-full mb-2 sm:mb-0 align-middle flex items-center justify-center",
|
||||
isExpanded ? "sm:w-[350px]" : "sm:w-[150px]",
|
||||
"transition-all duration-500 ease-in-out",
|
||||
)}
|
||||
>
|
||||
<PostCover
|
||||
uniqueKey={`short-${page.data?.characterId}-${page.data?.noteId}`}
|
||||
images={images}
|
||||
title={page.data?.metadata?.content?.title}
|
||||
className="rounded-lg w-full aspect-auto border-b-0 h-full"
|
||||
imgClassName="rounded-lg object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col justify-between">
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{page.data?.metadata?.content?.title && (
|
||||
<div className="font-bold mb-2 text-lg truncate">
|
||||
{page.data?.metadata?.content?.title}
|
||||
</div>
|
||||
)}
|
||||
<div className="prose overflow-y-auto">
|
||||
<p
|
||||
className={
|
||||
isExpanded || isMobile ? "line-clamp-6" : "line-clamp-1"
|
||||
}
|
||||
>
|
||||
{page.data?.metadata?.content?.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row w-full justify-between items-end sm:mt-0 mt-4">
|
||||
<span className="text-sm">
|
||||
<Time isoString={page.data?.metadata?.content?.date_published} />
|
||||
</span>
|
||||
|
||||
<span className="p-2 flex w-auto justify-end items-center transition-colors py-1 rounded-lg">
|
||||
<Avatar
|
||||
cid={site.data?.characterId}
|
||||
className="align-middle"
|
||||
images={site.data?.metadata?.content?.avatars || []}
|
||||
name={site.data?.metadata?.content?.name}
|
||||
size={32}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`flex-1 flex-col min-w-0 ml-2 max-w-[100px] flex sm:mr-6`}
|
||||
>
|
||||
<span
|
||||
className={`text-left leading-none font-medium truncate text-gray-600 text-base`}
|
||||
style={{ marginBottom: "0.15rem" }}
|
||||
>
|
||||
{site.data?.metadata?.content?.name}
|
||||
</span>
|
||||
{site.data?.handle && (
|
||||
<span
|
||||
className={`text-left leading-none text-sm truncate text-gray-400`}
|
||||
>
|
||||
{"@" + site.data?.handle}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<i
|
||||
className={cn(
|
||||
"i-mingcute-arrows-down-line text-xl ml-[2px] text-accent hover:cursor-pointer",
|
||||
isExpanded ? "rotate-180" : "rotate-0",
|
||||
"transition-transform duration-500 ease-in-out",
|
||||
"hidden sm:inline-block",
|
||||
)}
|
||||
onClick={toggleExpand}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default XLogShorts
|
||||
|
|
@ -28,7 +28,7 @@ function getParamsFromShortsURL(url: URL) {
|
|||
return { slug, handle }
|
||||
}
|
||||
|
||||
export const XLogShortsTransformer: Transformer = {
|
||||
export const XLogPostTransformer: Transformer = {
|
||||
name: "XLogShorts",
|
||||
shouldTransform(url) {
|
||||
return !!getParamsFromShortsURL(url)
|
||||
|
|
@ -36,6 +36,6 @@ export const XLogShortsTransformer: Transformer = {
|
|||
getHTML(url) {
|
||||
const { slug, handle } = getParamsFromShortsURL(url)!
|
||||
|
||||
return `<xlog-shorts slug="${slug}" handle="${handle}" url="${url.toString()}"/>`
|
||||
return `<xlog-post slug="${slug}" handle="${handle}" url="${url.toString()}"/>`
|
||||
},
|
||||
}
|
||||
|
|
@ -4,14 +4,14 @@ import { CodeSandboxTransformer } from "./CodeSandBox"
|
|||
import { GitHubRepoTransformer } from "./GitHub"
|
||||
import { NetEaseMusicTransformer } from "./NetEaseMusic"
|
||||
import { SpotifyTransformer } from "./Spotify"
|
||||
import { XLogShortsTransformer } from "./XLogShorts"
|
||||
import { XLogPostTransformer } from "./XLogPost"
|
||||
import { YouTubeTransformer } from "./YouTube"
|
||||
|
||||
export const transformers: Transformer[] = [
|
||||
CodeSandboxTransformer,
|
||||
GitHubRepoTransformer,
|
||||
YouTubeTransformer,
|
||||
XLogShortsTransformer,
|
||||
XLogPostTransformer,
|
||||
NetEaseMusicTransformer,
|
||||
BilibiliTransformer,
|
||||
SpotifyTransformer,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const Style = dynamic(() => import("~/components/common/Style"))
|
|||
const Mention = dynamic(() => import("~/components/ui/Mention"))
|
||||
const Mermaid = dynamic(() => import("~/components/ui/Mermaid"))
|
||||
const GithubRepo = dynamic(() => import("~/components/ui/GithubRepo"))
|
||||
const XLogShorts = dynamic(() => import("~/components/ui/XLogShorts"))
|
||||
const XLogPost = dynamic(() => import("~/components/ui/XLogPost"))
|
||||
const APlayer = dynamic(() => import("~/components/ui/APlayer"))
|
||||
|
||||
const DPlayer = dynamic(() => import("~/components/ui/DPlayer"))
|
||||
|
|
@ -234,7 +234,7 @@ export const renderPageContent = (
|
|||
audio: APlayer,
|
||||
video: DPlayer,
|
||||
"github-repo": GithubRepo,
|
||||
"xlog-shorts": XLogShorts,
|
||||
"xlog-post": XLogPost,
|
||||
style: Style,
|
||||
} as any,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import deepmerge from "deepmerge"
|
||||
import { defaultSchema } from "rehype-sanitize"
|
||||
|
||||
const scheme = {
|
||||
...defaultSchema,
|
||||
const scheme = deepmerge(defaultSchema, {
|
||||
tagNames: [
|
||||
...(defaultSchema.tagNames || []),
|
||||
"video",
|
||||
"iframe",
|
||||
"style",
|
||||
|
|
@ -18,8 +17,7 @@ const scheme = {
|
|||
...["svg", "path", "circle"],
|
||||
],
|
||||
attributes: {
|
||||
...defaultSchema.attributes,
|
||||
"*": [...(defaultSchema.attributes?.["*"] || []), "className", "style"],
|
||||
"*": ["className", "style"],
|
||||
video: ["src", "controls", "loop", "muted", "autoPlay", "playsInline"],
|
||||
audio: [
|
||||
"src",
|
||||
|
|
@ -48,6 +46,9 @@ const scheme = {
|
|||
path: ["d", "fill"],
|
||||
circle: ["cx", "cy", "r", "fill"],
|
||||
},
|
||||
}
|
||||
protocols: {
|
||||
href: ["magnet", "ed2k"],
|
||||
},
|
||||
})
|
||||
|
||||
export default scheme
|
||||
|
|
|
|||
Loading…
Reference in New Issue