Skip to content

Commit

Permalink
feat: support note header cover image
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Dec 4, 2023
1 parent 6e09012 commit 42f30e2
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/app/notes/[id]/pageExtra.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const NoteTitle = () => {
<GoToAdminEditingButton
type="notes"
id={id!}
className="absolute -top-6 right-0"
className="absolute right-0 top-0"
/>
</>
)
Expand Down
3 changes: 3 additions & 0 deletions src/app/notes/[id]/pageImpl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -36,6 +37,8 @@ const NotePage = function (props: NoteModel) {
return (
<>
<AckRead id={props.id} type="note" />

{props.meta?.cover && <NoteHeadCover image={props.meta.cover} />}
<NoteHeaderMetaInfoSetting />
<IndentArticleContainer>
<header>
Expand Down
6 changes: 3 additions & 3 deletions src/app/notes/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ export default async (props: PropsWithChildren) => {
<div
className={clsx(
'relative mx-auto grid min-h-[calc(100vh-6.5rem-10rem)] max-w-[60rem]',
'gap-4 md:grid-cols-1 lg:max-w-[calc(60rem+400px)] lg:grid-cols-[1fr_minmax(auto,60rem)_1fr]',
'gap-4 md:grid-cols-1 xl:max-w-[calc(60rem+400px)] xl:grid-cols-[1fr_minmax(auto,60rem)_1fr]',
'mt-12',
'print:!block print:!max-w-full md:mt-24',
)}
>
<div className="relative hidden min-w-0 lg:block" data-hide-print>
<div className="relative hidden min-w-0 xl:block" data-hide-print>
<NoteLeftSidebar />
</div>

{props.children}

<LayoutRightSideProvider className="relative hidden print:!hidden lg:block" />
<LayoutRightSideProvider className="relative hidden print:!hidden xl:block" />
</div>
)
}
4 changes: 2 additions & 2 deletions src/components/ui/link-card/LinkCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
}

Expand Down
95 changes: 95 additions & 0 deletions src/components/widgets/note/NoteHeadCover.tsx
Original file line number Diff line number Diff line change
@@ -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<string> {
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<string | null>(null)
useLayoutEffect(() => {
let isMounted = true
cropImageTo16by9(image).then((b) => {
if (!isMounted) return
setImageBlob(b)
})
return () => {
isMounted = false
}
}, [image])
return (
<>
<AutoResizeHeight>
<div
className={clsx(
'z-1 absolute left-0 right-0 top-0',
imageBlob ? 'h-[224px]' : '0',
)}
>
<div
style={{
backgroundImage: `url(${imageBlob})`,
}}
className="cover-mask-b h-full w-full bg-cover bg-center bg-no-repeat"
/>
</div>
</AutoResizeHeight>

<AutoResizeHeight>
<div className={imageBlob ? 'h-[120px]' : 'h-0'} />
</AutoResizeHeight>
</>
)
}
4 changes: 4 additions & 0 deletions src/styles/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,7 @@
rgb(255, 255, 255) 20px
);
}

.cover-mask-b {
mask-image: linear-gradient(180deg, #fff -17.19%, #00000000 92.43%);
}

1 comment on commit 42f30e2

@vercel
Copy link

@vercel vercel bot commented on 42f30e2 Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

shiro – ./

shiro-innei.vercel.app
springtide.vercel.app
shiro-git-main-innei.vercel.app
innei.in

Please sign in to comment.