From 42f30e2fb29fb1692537a904c4f7a089d965fbf3 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 4 Dec 2023 14:59:22 +0800 Subject: [PATCH] feat: support note header cover image Signed-off-by: Innei --- src/app/notes/[id]/pageExtra.tsx | 2 +- src/app/notes/[id]/pageImpl.tsx | 3 + src/app/notes/layout.tsx | 6 +- src/components/ui/link-card/LinkCard.tsx | 4 +- src/components/widgets/note/NoteHeadCover.tsx | 95 +++++++++++++++++++ src/styles/theme.css | 4 + 6 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/components/widgets/note/NoteHeadCover.tsx diff --git a/src/app/notes/[id]/pageExtra.tsx b/src/app/notes/[id]/pageExtra.tsx index 6b1ad9ed7e..90ef578708 100644 --- a/src/app/notes/[id]/pageExtra.tsx +++ b/src/app/notes/[id]/pageExtra.tsx @@ -51,7 +51,7 @@ export const NoteTitle = () => { ) diff --git a/src/app/notes/[id]/pageImpl.tsx b/src/app/notes/[id]/pageImpl.tsx index e24aa4fe16..cf0d5c484d 100644 --- a/src/app/notes/[id]/pageImpl.tsx +++ b/src/app/notes/[id]/pageImpl.tsx @@ -20,6 +20,7 @@ import { XLogInfoForNote } from '~/components/widgets/xlog' import { LayoutRightSidePortal } from '~/providers/shared/LayoutRightSideProvider' import { WrappedElementProvider } from '~/providers/shared/WrappedElementProvider' +import { NoteHeadCover } from '../../../components/widgets/note/NoteHeadCover' import { NoteHideIfSecret } from '../../../components/widgets/note/NoteHideIfSecret' import { NoteMetaBar } from '../../../components/widgets/note/NoteMetaBar' import { @@ -36,6 +37,8 @@ const NotePage = function (props: NoteModel) { return ( <> + + {props.meta?.cover && }
diff --git a/src/app/notes/layout.tsx b/src/app/notes/layout.tsx index b5c9196d43..0188bd2899 100644 --- a/src/app/notes/layout.tsx +++ b/src/app/notes/layout.tsx @@ -9,18 +9,18 @@ export default async (props: PropsWithChildren) => {
-
+
{props.children} - +
) } diff --git a/src/components/ui/link-card/LinkCard.tsx b/src/components/ui/link-card/LinkCard.tsx index 8cd51d8931..bc131bc13e 100644 --- a/src/components/ui/link-card/LinkCard.tsx +++ b/src/components/ui/link-card/LinkCard.tsx @@ -316,8 +316,8 @@ const fetchMxSpaceData: FetchObject = { setFullUrl(`/posts/${cate}/${slug}`) } else if (type === 'notes') { const [nid] = rest - const response = await apiClient.note.getNoteById(nid) - data = response + const response = await apiClient.note.getNoteById(+nid) + data = response.data setFullUrl(`/notes/${nid}`) } diff --git a/src/components/widgets/note/NoteHeadCover.tsx b/src/components/widgets/note/NoteHeadCover.tsx new file mode 100644 index 0000000000..59765b1312 --- /dev/null +++ b/src/components/widgets/note/NoteHeadCover.tsx @@ -0,0 +1,95 @@ +'use client' + +import { useLayoutEffect, useState } from 'react' +import clsx from 'clsx' + +import { AutoResizeHeight } from '~/components/widgets/shared/AutoResizeHeight' + +function cropImageTo16by9(src: string): Promise { + return new Promise((resolve, reject) => { + const img = new Image() + img.crossOrigin = 'anonymous' + img.onload = () => { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d')! + + const aspectRatio = 838 / 224 + let cropWidth = img.width + let cropHeight = cropWidth / aspectRatio + + if (cropHeight > img.height) { + cropHeight = img.height + cropWidth = cropHeight * aspectRatio + } + + const left = (img.width - cropWidth) / 2 + const top = (img.height - cropHeight) / 2 + + // 设置 canvas 尺寸和绘制裁剪的图像 + canvas.width = cropWidth + canvas.height = cropHeight + ctx.drawImage( + img, + left, + top, + cropWidth, + cropHeight, + 0, + 0, + cropWidth, + cropHeight, + ) + + // 转换 canvas 内容为 blob URL + canvas.toBlob((blob) => { + if (blob) { + const url = URL.createObjectURL(blob) + resolve(url) + } else { + reject('Blob conversion failed') + } + }, 'image/jpeg') + } + img.onerror = reject + + // 设置图像源以开始加载 + img.src = src + }) +} + +export const NoteHeadCover = ({ image }: { image: string }) => { + const [imageBlob, setImageBlob] = useState(null) + useLayoutEffect(() => { + let isMounted = true + cropImageTo16by9(image).then((b) => { + if (!isMounted) return + setImageBlob(b) + }) + return () => { + isMounted = false + } + }, [image]) + return ( + <> + +
+
+
+ + + +
+ + + ) +} diff --git a/src/styles/theme.css b/src/styles/theme.css index 08861d7d94..5868b74f6d 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -164,3 +164,7 @@ rgb(255, 255, 255) 20px ); } + +.cover-mask-b { + mask-image: linear-gradient(180deg, #fff -17.19%, #00000000 92.43%); +}