feat: use nextjs image

This commit is contained in:
DIYgod 2022-09-27 00:59:23 +01:00
parent ca40896de8
commit d6d7993234
No known key found for this signature in database
GPG Key ID: D159328F47A80DCA
20 changed files with 197 additions and 70 deletions

View File

@ -44,5 +44,10 @@ module.exports = withBundleAnalyzer(
ipfsGateway: {
gatewayPath: "/_ipfs/:cid/:pathToResource*",
},
images: {
dangerouslyAllowSVG: true,
domains: ["4everland.xyz"],
},
}),
)

View File

@ -74,6 +74,7 @@
"refractor": "^4.8.0",
"rehype-prism-plus": "^1.5.0",
"rehype-raw": "^6.1.1",
"rehype-react": "^7.1.1",
"rehype-sanitize": "^5.0.1",
"rehype-stringify": "^9.0.3",
"remark-frontmatter": "^4.0.1",

View File

@ -94,6 +94,7 @@ specifiers:
refractor: ^4.8.0
rehype-prism-plus: ^1.5.0
rehype-raw: ^6.1.1
rehype-react: ^7.1.1
rehype-sanitize: ^5.0.1
rehype-stringify: ^9.0.3
remark-frontmatter: ^4.0.1
@ -176,6 +177,7 @@ dependencies:
refractor: 4.8.0
rehype-prism-plus: 1.5.0
rehype-raw: 6.1.1
rehype-react: 7.1.1_@types+react@18.0.21
rehype-sanitize: 5.0.1
rehype-stringify: 9.0.3
remark-frontmatter: 4.0.1
@ -1563,6 +1565,13 @@ packages:
'@lezer/highlight': 1.1.0
dev: false
/@mapbox/hast-util-table-cell-style/0.2.0:
resolution: {integrity: sha512-gqaTIGC8My3LVSnU38IwjHVKJC94HSonjvFHDk8/aSrApL8v4uWgm8zJkK7MJIIbHuNOr/+Mv2KkQKcxs6LEZA==}
engines: {node: '>=12'}
dependencies:
unist-util-visit: 1.4.1
dev: false
/@metamask/safe-event-emitter/2.0.0:
resolution: {integrity: sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==}
dev: false
@ -8669,6 +8678,19 @@ packages:
unified: 10.1.2
dev: false
/rehype-react/7.1.1_@types+react@18.0.21:
resolution: {integrity: sha512-6yaitxM95JFsuTA74OH54OyvTAeRqR4/A6f45S2sEk2FEG04iGgsObcmEIFQRsjK9pfAhc74lR4iGf/W2G/sBw==}
peerDependencies:
'@types/react': '>=17'
dependencies:
'@mapbox/hast-util-table-cell-style': 0.2.0
'@types/hast': 2.3.4
'@types/react': 18.0.21
hast-to-hyperscript: 10.0.1
hast-util-whitespace: 2.0.0
unified: 10.1.2
dev: false
/rehype-sanitize/5.0.1:
resolution: {integrity: sha512-da/jIOjq8eYt/1r9GN6GwxIR3gde7OZ+WV8pheu1tL8K0D9KxM2AyMh+UEfke+FfdM3PvGHeYJU0Td5OWa7L5A==}
dependencies:
@ -9692,6 +9714,10 @@ packages:
resolution: {integrity: sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==}
dev: false
/unist-util-is/3.0.0:
resolution: {integrity: sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==}
dev: false
/unist-util-is/5.1.1:
resolution: {integrity: sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==}
dev: false
@ -9708,6 +9734,12 @@ packages:
'@types/unist': 2.0.6
dev: false
/unist-util-visit-parents/2.1.2:
resolution: {integrity: sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==}
dependencies:
unist-util-is: 3.0.0
dev: false
/unist-util-visit-parents/5.1.0:
resolution: {integrity: sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==}
dependencies:
@ -9722,6 +9754,12 @@ packages:
unist-util-is: 5.1.1
dev: false
/unist-util-visit/1.4.1:
resolution: {integrity: sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==}
dependencies:
unist-util-visit-parents: 2.1.2
dev: false
/unist-util-visit/4.1.1:
resolution: {integrity: sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==}
dependencies:

View File

@ -1,16 +1,27 @@
import clsx from "clsx"
import { useCodeCopy } from "~/hooks/useCodeCopy"
import { renderPageContent } from "~/markdown"
export const PageContent: React.FC<{
contentHTML: string
content?: string
className?: string
}> = ({ contentHTML, className }) => {
}> = ({ className, content }) => {
useCodeCopy()
// TODO
// const [element, setElement] = useState<ReactElement>()
// const renderMarkdown = useCallback((doc: string) => {
// setElement(renderPageContent(doc).element)
// }, [])
// useEffect(() => {
// if (content) {
// renderMarkdown(content)
// }
// }, [content, renderMarkdown])
return (
<div
className={clsx("xlog-post-content", `prose`, className)}
dangerouslySetInnerHTML={{ __html: contentHTML }}
></div>
<div className={clsx("xlog-post-content", `prose`, className)}>
{content && renderPageContent(content).element}
</div>
)
}

View File

@ -279,9 +279,9 @@ export const PagesManager: React.FC<{
const file = e.target?.files?.[0]
const reader = new FileReader()
reader.readAsText(file, "UTF-8")
reader.onload = async (evt) => {
reader.onload = (evt) => {
if (evt.target?.result) {
const pageContent = await renderPageContent(
const pageContent = renderPageContent(
evt.target.result as string,
)

View File

@ -25,6 +25,7 @@ import type { Link } from "unidata.js"
import { getSiteSubscriptions } from "~/models/site.model"
import { CharacterList } from "~/components/common/CharacterList"
import type { Links } from "unidata.js"
import { Image } from "~/components/ui/Image"
export type HeaderLinkType = {
icon?: React.ReactNode
@ -184,8 +185,7 @@ export const SiteHeader: React.FC<{
<header className="xlog-header border-b relative">
<div className="xlog-banner absolute top-0 bottom-0 left-0 right-0 -z-10 overflow-hidden">
{site?.banners?.[0]?.mime_type.split("/")[0] === "image" && (
/* eslint-disable-next-line @next/next/no-img-element */
<img
<Image
className="max-w-screen-md mx-auto object-cover h-full w-full"
src={site?.banners?.[0]?.address}
alt="banner"

View File

@ -3,6 +3,7 @@ import { formatDate } from "~/lib/date"
import { Paginated, type PostOnSiteHome, Notes } from "~/lib/types"
import { EmptyState } from "../ui/EmptyState"
import { useRouter } from "next/router"
import { Image } from "~/components/ui/Image"
export const SiteHome: React.FC<{
posts?: Notes
@ -20,7 +21,7 @@ export const SiteHome: React.FC<{
const excerpt = post.summary?.content
return (
<Link key={post.id} href={`/${post.slug || post.id}`}>
<a className="xlog-post hover:bg-zinc-100 transition-colors p-5 -mx-5 first:-mt-5 md:rounded-xl flex">
<a className="xlog-post hover:bg-zinc-100 transition-colors p-5 -mx-5 first:-mt-5 md:rounded-xl flex items-center">
<div className="flex-1 flex justify-center flex-col">
<h3 className="xlog-post-title text-2xl font-bold">
{post.title}
@ -56,16 +57,16 @@ export const SiteHome: React.FC<{
{excerpt && "..."}
</div>
</div>
<div className="xlog-post-cover flex items-center">
{post.cover && (
/* eslint-disable-next-line @next/next/no-img-element */
<img
className="object-cover w-24 h-24 rounded ml-4"
{post.cover && (
<div className="xlog-post-cover flex items-center relative w-24 h-24 ml-4">
<Image
className="object-cover rounded w-24 h-24"
alt="cover"
layout="fill"
src={post.cover}
></img>
)}
</div>
></Image>
</div>
)}
</a>
</Link>
)

View File

@ -38,7 +38,7 @@ export const SiteLayout: React.FC<SiteLayoutProps> = ({ children, title }) => {
title={title || tag || page.data?.title || ""}
siteName={site.data?.name || ""}
description={
(page.data?.summary?.content || page.data?.body?.content) ??
page.data?.summary?.content ??
site.data?.description?.replace(/<[^>]*>/g, "")
}
image={page.data?.cover || getUserContentsUrl(site.data?.avatars?.[0])}

View File

@ -18,7 +18,10 @@ export const SitePage: React.FC<{
)}
{page?.tags?.includes("post") && <PostMeta page={page} />}
</div>
<PageContent contentHTML={page?.body?.content || ""} className="mt-10" />
<PageContent
className="mt-10"
content={page?.body?.content}
></PageContent>
<PostFooter page={page} />
</>
)

View File

@ -1,6 +1,7 @@
import clsx from "clsx"
import React, { useMemo } from "react"
import { toGateway } from "~/lib/ipfs-parser"
import { Image } from "~/components/ui/Image"
export const Avatar: React.FC<
{
@ -61,8 +62,7 @@ export const Avatar: React.FC<
height: `${size}px`,
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
<Image
className="overflow-hidden"
src={image}
width={size}

View File

@ -1,23 +0,0 @@
import { FC, useCallback, useEffect, useState } from "react"
import { renderPageContent } from "~/markdown"
import { PageContent } from "../common/PageContent"
export interface EditorPreviewProps {
className?: string
content: string
}
export const EditorPreview: FC<EditorPreviewProps> = ({
className,
content,
}) => {
const [html, setHtml] = useState("")
const renderMarkdown = useCallback(async (doc: string) => {
const pageContent = await renderPageContent(doc)
setHtml(pageContent.contentHTML)
}, [])
useEffect(() => {
renderMarkdown(content)
}, [content, renderMarkdown])
return <PageContent className={className} contentHTML={html ?? ""} />
}

View File

@ -0,0 +1,63 @@
import React from "react"
import { default as NextImage, ImageProps } from "next/image"
import { toGateway, toIPFS } from "~/lib/ipfs-parser"
import { isIpfsUrl } from "@crossbell/ipfs-gateway"
import clsx from "clsx"
export const Image: React.FC<
{
className?: string
src?: string
width?: number | string
height?: number | string
} & React.HTMLAttributes<HTMLImageElement> &
ImageProps
> = ({ layout, className, alt, src, width, height, ...props }) => {
src = toIPFS(src)
const [paddingTop, setPaddingTop] = React.useState("0")
return (
<>
{isIpfsUrl(src) ? (
<span
className={clsx(
"relative inline-flex justify-center",
!width && !width && !layout ? "w-full h-full" : "",
)}
style={{ paddingTop }}
>
<NextImage
{...props}
src={toGateway(src, {
forceFallback: true,
})}
className={className}
alt={alt}
width={width}
height={height}
layout={!width && !height ? "fill" : layout}
onLoad={({ target }) => {
if (!width && !width && !layout) {
const { naturalWidth, naturalHeight } =
target as HTMLImageElement
setPaddingTop(
`calc(100% / (${naturalWidth} / ${naturalHeight})`,
)
}
}}
/>
</span>
) : (
// eslint-disable-next-line @next/next/no-img-element
<img
{...props}
src={toGateway(src, {
forceFallback: true,
})}
className={className}
alt={alt}
/>
)}
</>
)
}

View File

@ -7,13 +7,17 @@ const IPFS_PREFIX = "ipfs://"
export type ToGatewayConfig = {
needRequestAtServerSide?: boolean
forceFallback?: boolean
}
export const toGateway = (url: string, config?: ToGatewayConfig) => {
const ipfsUrl = toIPFS(url)
if (isIpfsUrl(ipfsUrl)) {
if (config?.needRequestAtServerSide && typeof window === "undefined") {
if (
(config?.needRequestAtServerSide && typeof window === "undefined") ||
config?.forceFallback
) {
return ipfsGateway.getFallbackWeb2Url(ipfsUrl)
} else {
return ipfsGateway.getSwWeb2Url(ipfsUrl)
@ -31,4 +35,5 @@ export const toIPFS = (url: string) => {
.replace("https://ipfs.io/ipfs/", IPFS_PREFIX)
.replace("https://cf-ipfs.com/ipfs/", IPFS_PREFIX)
.replace("https://ipfs.4everland.xyz/ipfs/", IPFS_PREFIX)
.replace("https://rss3.mypinata.cloud/ipfs/", IPFS_PREFIX)
}

View File

@ -1,4 +1,5 @@
import type { Note as UniNote, Profile as UniProfile } from "unidata.js"
import { ReactElement } from "react"
export type Site = {
id: string
@ -75,6 +76,12 @@ export type Note = UniNote & {
slug?: string
character?: Profile
cover?: string
body?: {
content?: string
address?: string
mime_type?: string
element?: ReactElement
}
}
export type Notes = {

View File

@ -15,6 +15,9 @@ import { allowedBlockquoteAttrs, remarkCallout } from "./remark-callout"
import { rehypeExternalLink } from "./rehyper-external-link"
import { rehypeWrapCode } from "./rehype-wrap-code"
import jsYaml from "js-yaml"
import rehypeReact from "rehype-react"
import { createElement, ReactElement } from "react"
import { Image } from "~/components/ui/Image"
export type MarkdownEnv = {
excerpt: string
@ -25,6 +28,7 @@ export type MarkdownEnv = {
export type Rendered = {
contentHTML: string
element?: ReactElement
excerpt: string
frontMatter: Record<string, any>
cover: string
@ -34,7 +38,10 @@ refractor.alias("html", ["svelte", "vue"])
const rehypePrism = rehypePrismGenerator(refractor)
export const renderPageContent = async (content: string): Promise<Rendered> => {
export const renderPageContent = (
content: string,
html?: boolean,
): Rendered => {
const env: MarkdownEnv = {
excerpt: "",
__internal: {},
@ -42,7 +49,7 @@ export const renderPageContent = async (content: string): Promise<Rendered> => {
cover: "",
}
const result = await unified()
const result = unified()
.use(remarkParse)
.use(rehypeStringify)
.use(remarkFrontmatter, ["yaml"])
@ -98,12 +105,19 @@ export const renderPageContent = async (content: string): Promise<Rendered> => {
.use(rehypeTable)
.use(rehypeExternalLink)
.use(rehypeWrapCode)
.process(content)
.use(html ? () => (tree: any) => {} : rehypeReact, {
createElement: createElement,
components: {
img: Image as any,
},
})
.processSync(content)
const contentHTML = result.toString()
return {
contentHTML,
element: result.result,
excerpt: env.excerpt,
frontMatter: env.frontMatter,
cover: env.cover,

View File

@ -91,16 +91,16 @@ export async function createOrUpdatePage(input: {
)
}
const expandPage = async (page: Note, render: boolean) => {
const expandPage = (page: Note, render: boolean) => {
if (
page.body?.content &&
page.body?.mime_type === "text/markdown" &&
render
) {
const rendered = await renderPageContent(page.body.content)
const rendered = renderPageContent(page.body.content, true)
page.body = {
content: rendered.contentHTML,
mime_type: "text/html",
content: page.body.content,
mime_type: "text/markdown",
}
if (!page.summary) {
page.summary = {
@ -237,12 +237,10 @@ export async function getPagesBySite(input: {
.sort((a, b) => +new Date(b.date_published) - +new Date(a.date_published))
pages.total = pages.list.length
pages.list = await Promise.all(
pages?.list.map(async (page) => {
await expandPage(page, input.render || false)
return page
}),
)
pages?.list.map((page) => {
expandPage(page, input.render || false)
return page
})
}
return pages

View File

@ -37,7 +37,7 @@ const expandSite = async (site: Profile) => {
(a: any) => a.trait_type === "xlog_custom_domain",
)?.value || ""
site.name = site.name || site.username
site.description = (await renderPageContent(site.bio || "")).contentHTML
site.description = renderPageContent(site.bio || "", true).contentHTML
if (site.avatars) {
site.avatars = site.avatars.map((avatar) => toGateway(avatar))

View File

@ -4,6 +4,7 @@ import { fetchGetPagesBySite } from "~/queries/page.server"
import { PageVisibilityEnum } from "~/lib/types"
import { getSiteLink } from "~/lib/helpers"
import { QueryClient } from "@tanstack/react-query"
import { renderPageContent } from "~/markdown"
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const queryClient = new QueryClient()
@ -36,7 +37,9 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
items: pages.list?.map((page) => ({
id: page.id,
title: page.title,
content_html: page.body?.content,
content_html:
page.body?.content &&
renderPageContent(page.body?.content, true).contentHTML,
summary: page.summary?.content,
url: `${link}/${page.slug || page.id}`,
image: page.cover,

View File

@ -9,7 +9,6 @@ import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
import { DashboardMain } from "~/components/dashboard/DashboardMain"
import { PublishButton } from "~/components/dashboard/PublishButton"
import { useEditor } from "~/components/ui/Editor"
import { EditorPreview } from "~/components/ui/EditorPreview"
import { EditorToolbar } from "~/components/ui/EditorToolbar"
import { Input } from "~/components/ui/Input"
import { UniLink } from "~/components/ui/UniLink"
@ -23,6 +22,7 @@ import { useGetSite } from "~/queries/site"
import { getStorage, setStorage, delStorage } from "~/lib/storage"
import { nanoid } from "nanoid"
import { useQueryClient } from "@tanstack/react-query"
import { PageContent } from "~/components/common/PageContent"
const getInputDatetimeValue = (date: Date | string) => {
const str = dayjs(date).format()
@ -265,10 +265,10 @@ export default function SubdomainEditor() {
className="px-5 h-full border-r w-1/2 overflow-scroll"
ref={editorRef}
></div>
<EditorPreview
<PageContent
className="px-5 w-1/2 overflow-scroll pb-[200px]"
content={content}
></EditorPreview>
></PageContent>
</div>
</div>
</div>

View File

@ -5,7 +5,6 @@ import { MainLayout } from "~/components/main/MainLayout"
import { UniLink } from "~/components/ui/UniLink"
import { FLY_REGION } from "~/lib/env.server"
import { useAccount } from "wagmi"
import Image from "next/image"
import { EllipsisHorizontalIcon } from "@heroicons/react/20/solid"
import { LoveIcon } from "~/components/icons/LoveIcon"
import { BlockchainIcon } from "~/components/icons/BlockchainIcon"
@ -15,6 +14,7 @@ import { useRouter } from "next/router"
import { GITHUB_LINK, APP_NAME, CSB_SCAN } from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { Link, Element } from "react-scroll"
import { Image } from "~/components/ui/Image"
export const getServerSideProps: GetServerSideProps = async (ctx) => {
return {
@ -234,12 +234,13 @@ export default function Home({ region }: { region: string | null }) {
subdomain: item.username,
})}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
className="rounded-full w-14 h-14"
<Image
className="rounded-full"
src={`/_ipfs/${item.avartar}`}
alt={item.username}
></img>
width="56"
height="56"
></Image>
</UniLink>
</li>
))}