20 Commits

Author SHA1 Message Date
6156b512e9 Merge pull request 'dev' (#4) from dev into main
Reviewed-on: #4
build 1.1.2 version
2025-05-13 00:22:22 +08:00
9e5d98e2e2 🆗 增加shikijs踩坑记录blog
🆗 调整markdown代码块copy success的图标颜色
🆗 去除首页blog图片的左边距
2025-05-13 00:06:48 +08:00
101db5aa83 增加copy按钮添加时改的shikijs包代码说明 2025-05-12 18:57:01 +08:00
9ef5139d93 代码块添加复制按钮 2025-05-12 18:51:21 +08:00
f999f0912e 添加网易云音乐vip歌曲的自建backup服务地址 2025-05-12 16:08:05 +08:00
75cd6ce315 Merge branch 'dev' of code.2ha.me:dev.2ha.me/dev.2ha.me into dev 2025-05-09 15:53:04 +08:00
fe3978ddbf 添加缺少的gif资源 2025-05-09 15:53:01 +08:00
f76e5719a2 Merge pull request 'main' (#3) from main into dev
Reviewed-on: #3
2025-05-06 15:21:46 +08:00
79db601591 fix linux build error 2025-05-06 10:52:10 +08:00
3b12d07b51 fix loading display 2025-04-29 23:40:44 +08:00
6e13092027 修复loadingbug 2025-04-29 23:39:52 +08:00
ee31225f15 删除冗余代码 2025-04-29 22:58:49 +08:00
cbe2957f80 优化首页动画背景加载loading显示 2025-04-29 22:34:41 +08:00
c3e50ccff2 调整首页视频背景与loading的交互方式 2025-04-29 18:30:05 +08:00
e0a0576ee1 调整动画背景为播放完成后切换,而不是循环播放 2025-04-27 23:49:06 +08:00
b60cdafc0a add 莉可莉丝 bg 2025-04-27 18:35:02 +08:00
fcf44c1d15 修改主页背景图为mp4格式 2025-04-27 15:46:01 +08:00
246eb86d8a 增加猫娘随机Bg 2025-04-26 00:09:46 +08:00
db2e8936f3 修改about页图片展示比例 2025-04-25 19:12:22 +08:00
5e9389f5de 更新about小猫女仆说明 2025-04-25 17:33:56 +08:00
36 changed files with 668 additions and 44 deletions

View File

@@ -18,6 +18,7 @@ import remarkToc from 'remark-toc'
import sectionize from '@hbsnow/rehype-sectionize' import sectionize from '@hbsnow/rehype-sectionize'
import { transformerNotationSkip } from './src/lib/transformerNotationSkip' import { transformerNotationSkip } from './src/lib/transformerNotationSkip'
import { transformerDiffHighlight } from './src/lib/transformerDiffHighlight' import { transformerDiffHighlight } from './src/lib/transformerDiffHighlight'
import { transformerCopyButton } from './src/lib/transformerCopyButton'
import icon from 'astro-icon' import icon from 'astro-icon'
@@ -64,6 +65,11 @@ export default defineConfig({
transformerRenderWhitespace(), transformerRenderWhitespace(),
transformerNotationSkip(), transformerNotationSkip(),
transformerDiffHighlight(), transformerDiffHighlight(),
transformerCopyButton({
duration: 1000,
successIcon: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(5,223,114,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E",
copyIcon: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E",
})
], ],
}, },
], ],

View File

@@ -86,5 +86,8 @@
} }
} }
] ]
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "*"
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

View File

Before

Width:  |  Height:  |  Size: 4.7 MiB

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

View File

@@ -10,10 +10,11 @@ interface Track {
image: { '#text': string }[] // image: { '#text': string }[] //
url: string url: string
'@attr'?: { nowplaying: string }, '@attr'?: { nowplaying: string },
outerurl: string outerurl: string,
backupurl: string
} }
const SpotifyPresence = () => { const Music163Player = () => {
const [displayData, setDisplayData] = useState<Track | null>(null) const [displayData, setDisplayData] = useState<Track | null>(null)
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
@@ -47,9 +48,10 @@ const SpotifyPresence = () => {
album: { album: {
'#text': lastweekFirstSong.al.name '#text': lastweekFirstSong.al.name
}, },
image: [{'#text': lastweekFirstSong.al.picUrl}], image: [{'#text': lastweekFirstSong.al.picUrl.replace('http:', 'https:')}],
url: 'https://music.163.com/song?id=' + lastweekFirstSong.id, url: 'https://music.163.com/song?id=' + lastweekFirstSong.id,
outerurl: "https://music.163.com/song/media/outer/url?id=" + lastweekFirstSong.id + ".mp3" outerurl: "https://music.163.com/song/media/outer/url?id=" + lastweekFirstSong.id + ".mp3",
backupurl: "https://f.2ha.me/music/" + lastweekFirstSong.id + ".mp3"
} }
setDisplayData(track) setDisplayData(track)
setIsLoading(false) setIsLoading(false)
@@ -82,7 +84,7 @@ const SpotifyPresence = () => {
if (!displayData) return <p>Something absolutely horrible has gone wrong</p> if (!displayData) return <p>Something absolutely horrible has gone wrong</p>
const { name: song, artist, album, image, url, outerurl } = displayData const { name: song, artist, album, image, url, outerurl, backupurl } = displayData
return ( return (
<> <>
@@ -139,7 +141,10 @@ const SpotifyPresence = () => {
<span className="w-[85%] truncate text-xs text-muted-foreground"> <span className="w-[85%] truncate text-xs text-muted-foreground">
<span className="font-semibold text-secondary-foreground"> <span className="font-semibold text-secondary-foreground">
<div> <div>
<audio ref={audioRef} src={outerurl} /> <audio ref={audioRef} >
<source src={outerurl} type="audio/mp3" />
<source src={backupurl} type="audio/mp3" />
</audio>
</div> </div>
</span> </span>
</span> </span>
@@ -160,4 +165,4 @@ const SpotifyPresence = () => {
) )
} }
export default SpotifyPresence export default Music163Player

View File

@@ -0,0 +1,81 @@
import { useEffect, useRef, useState } from "react";
export const videoBackgrounds: string[] = [
'225.mp4',
'830.mp4',
'guduyaogun.mp4',
'guduyaogun1.mp4',
'guduyaogun2.mp4',
'lige.mp4',
'maoliang.mp4',
// 'miku.mp4',
'miku2.mp4',
'sanlian.mp4',
'lycoris2.mp4',
]
const RandomAnimeBackground = () => {
const [index, setIndex] = useState<number>(0)
const [isLoading, setIsLoading] = useState(true)
const videoRef = useRef<HTMLVideoElement | null>(null);
const [bindEvent, setBindEvent] = useState<boolean>(true);
const handleVideoEnded = () => {
setIsLoading(true)
setIndex(getRandomIndex())
if (bindEvent && videoRef.current) {
videoRef.current.addEventListener('canplay', handleCanPlayThrough);
setBindEvent(false)
}
};
const handleCanPlayThrough = () => {
setTimeout(() => {
setIsLoading(false)
}, 200);
}
const getRandomIndex = () => {
return Math.floor(Math.random() * videoBackgrounds.length);
};
useEffect(() => {
setIndex(getRandomIndex())
// setTimeout(() => {
setIsLoading(false)
// }, 100);
}, [])
// if (isLoading) {
// return (
// <video className="no-repeat relative w-full justify-center rounded-[1.4em] object-cover"
// src='/static/anime-bg/loading.mp4'
// autoPlay muted loop>
// Your browser does not support the video tag.
// </video>
// );
// }
return (
<>
{
isLoading ?
<video className="no-repeat relative w-full justify-center rounded-[1.4em] object-cover"
src='/static/anime-bg/loading.mp4'
autoPlay muted loop>
Your browser does not support the video tag.
</video>
:
<video ref={videoRef} className="no-repeat relative w-full justify-center rounded-[1.4em] object-cover"
src={'/static/anime-bg/' + videoBackgrounds[index]}
onEnded={handleVideoEnded}
autoPlay muted>
Your browser does not support the video tag.
</video>
}
</>
)
}
export default RandomAnimeBackground

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

View File

@@ -0,0 +1,404 @@
---
title: 'Astro集成Shikijs 和RehypePrettyCode踩坑'
description: '1. shikijs重复依赖打包失败; 2. 使用Rehype Pretty Code/Copy Button 给markdown代码块添加复制按钮时发生错误Cannot read properties of undefined (reading "type")'
date: 2025-05-12
tags: ['typescript', 'astro', 'shiki']
image: 'assets/shikicodecopybutton.png'
authors: ['jimlee']
---
import FileTree from '@/components/starlight/FileTree.astro'
# Shikijs重复依赖导致代码报错打包失败
## 执行 npm run build 后发生以下错误
```log
src/lib/transformerNotationSkip.ts - error ts(2322): Type 'import("/var/jenkins_home/workspace/dev.2ha.me/node_modules/@shikijs/transformers/node_modules/@shikijs/types/dist/index").ShikiTransformer' is not assignable to type 'import("/var/jenkins_home/workspace/dev.2ha.me/node_modules/@shikijs/types/dist/index").ShikiTransformer'.
Types of property 'preprocess' are incompatible.
return createCommentNotationTransformer(
~~~~~~
Result (78 files):
- 1 error
- 0 warnings
- 0 hints
```
## 检查src/lib/transformerNotationSkip.ts代码
这里引用的ShikiTransformer是/node_modules/@shikijs/types 中的 ShikiTransformer, 与 node_modules/@shikijs/transformers/node_modules/@shikijs/types 中的 ShikiTransformer 代码相同,但是引用不同
```typescript
import { type ShikiTransformer } from '@shikijs/types';
import { createCommentNotationTransformer } from '@shikijs/transformers'
export interface TransformerNotationSkipOptions {
/**
* Class for skipped lines
*/
classActiveSkip?: string
/**
* Class added to the root element when the code has skipped lines
*/
classActivePre?: string
}
export function transformerNotationSkip(
options: TransformerNotationSkipOptions = {},
): ShikiTransformer {
const { classActiveSkip = 'skip', classActivePre = undefined } = options
return createCommentNotationTransformer(
'skip-lines',
// comment-start | marker | range | comment-end
/^\s*(?:\/\/|\/\*|<!--|#)\s+\[!code skip:(\d+):(\d+)\]\s*(?:\*\/|-->)?/,
function ([_, start, end], _line) {
_line.children = [{ type: 'text', value: `${start}-${end}` }]
_line.properties = { style: `counter-set:line ${end}` }
if (classActiveSkip) this.addClassToHast(_line, classActiveSkip)
if (classActivePre) this.addClassToHast(this.pre, classActivePre)
return false
},
undefined, // remove empty lines
)
}
```
## 检查安装好依赖后的@shikijs目录
<FileTree>
- node_modules
- @shikijs
- engine-javascript
- dist
- README.md
- package.json
- LICENSE
- types
- dist
- README.md
- package.json
- LICENSE
- langs
- dist
- README.md
- package.json
- LICENSE
- engine-oniguruma
- dist
- README.md
- package.json
- LICENSE
- transformers
- dist
- README.md
- package.json
- node_modules // 重复依赖最外层 node_modules 下的 @shikijs shiki
- shiki // 与 node_modules/shiki 重复
- dist
- README.md
- package.json
- LICENSE
- oniguruma-to-es
- dist
- README.md
- package.json
- types
- LICENSE
- @shikijs // 与 node_modules/@shikijs 重复
- engine-javascript
- types
- engine-oniguruma
- vscode-textmate
- core
- LICENSE
- themes
- dist
- README.md
- package.json
- LICENSE
- vscode-textmate
- dist
- README.md
- package.json
- LICENSE.md
- core
- dist
- README.md
- package.json
- LICENSE
</FileTree>
## 删除node_modules中的重复依赖
```shellscript
rm -rf node_modules/@shikijs/transformers/node_modules/
```
# 给Astro博客Markdown代码块添加复制按钮
通过查找[Rehype Pretty Code文档](https://rehype-pretty.pages.dev/)找到 [Rehype Pretty Code/Copy Button](https://rehype-pretty.pages.dev/plugins/copy-button/)这个实验性功能
## 1. 安装依赖
```shellscript
npm install @rehype-pretty/transformers
```
## 2. 添加transformerCopyButton.ts
这个ts文件是基于node_modules\@rehype-pretty\transformers\dist\copy-button.js修改而来
```typescript
import type { ShikiTransformer } from "shiki";
import { h } from "hastscript";
export interface CopyButtonOptions {
duration?: number;
copyIcon?: string;
successIcon?: string
}
export const transformerCopyButton = (
options: CopyButtonOptions = {
duration: 1000
}
): ShikiTransformer => {
return {
name: 'shiki-transformer-copy-button',
code(node) {
const button = h('button', {
class: 'shiki-transformer-button-copy',
'data-code': this.source,
onclick: `
navigator.clipboard.writeText(this.dataset.code);
this.classList.add('shiki-transformer-button-copied');
setTimeout(() => this.classList.remove('shiki-transformer-button-copied'), ${options.duration})
`
}, [
h('span', { class: 'ready' }),
h('span', { class: 'success' })
]);
node.children.push(button)
node.children.push({
type: 'element',
tagName: 'style',
properties: {},
children: [
{
type: 'text',
value: buttonStyles({
successIcon: options.successIcon,
copyIcon: options.copyIcon
})
}
]
})
}
}
}
function buttonStyles({
successIcon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='rgba(5,223,114,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 3h2.6A2.4 2.4 0 0 1 21 5.4v15.2a2.4 2.4 0 0 1-2.4 2.4H5.4A2.4 2.4 0 0 1 3 20.6V5.4A2.4 2.4 0 0 1 5.4 3H8m0 11l3 3l5-7M8.8 1h6.4a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-.8.8H8.8a.8.8 0 0 1-.8-.8V1.8a.8.8 0 0 1 .8-.8'/%3E%3C/svg%3E",
copyIcon = "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20fill='none'%20stroke='rgba(128,128,128,1)'%20stroke-linecap='round'%20stroke-linejoin='round'%20stroke-width='2'%20viewBox='0%200%2024%2024'%3E%3Crect%20width='8'%20height='4'%20x='8'%20y='2'%20rx='1'%20ry='1'/%3E%3Cpath%20d='M16%204h2a2%202%200%200%201%202%202v14a2%202%200%200%201-2%202H6a2%202%200%200%201-2-2V6a2%202%200%200%201%202-2h2'/%3E%3C/svg%3E",
}: {
successIcon?: string,
copyIcon?: string
}) {
let buttonStyle =
`
:root {
--border-color: #e2e2e3;
--background-color: #f6f6f7;
--hover-background-color: #ffff
}
pre:has(code) {
position: relative;
}
pre button.shiki-transformer-button-copy {
position: absolute;
top: 12px;
right: 12px;
z-index: 3;
border: 1px solid var(--border-color);
border-radius: 4px;
width: 30px;
height: 30px;
display: flex;
justify-content: center;
place-items: center;
background-color: var(--background-color);
cursor: pointer;
background-repeat: no-repeat;
transition: var(--border-color) .25s, var(--background-color) .25s, opacity .25s;
&:hover {
background-color: var(--hover-background-color);
}
& span {
width: 100%;
aspect-ratio: 1 / 1;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
& .ready {
width: 20px;
height: 20px;
background-image: url("${copyIcon}");
}
& .success {
display: none;
width: 20px;
height: 20px;
background-image: url("${successIcon}");
}
&.shiki-transformer-button-copied {
& .success {
display: block;
}
& .ready {
display: none;
}
}
}`
return buttonStyle
}
```
## 3. 添加插件
```typescript
+ import { transformerCopyButton } from './src/lib/transformerCopyButton'
export default defineConfig({
site: 'https://dev.2ha.me',
integrations: [
tailwind({
applyBaseStyles: false,
}),
sitemap(),
mdx(),
react(),
icon(),
],
markdown: {
syntaxHighlight: false,
rehypePlugins: [
[
rehypeExternalLinks,
{
target: '_blank',
rel: ['nofollow', 'noreferrer', 'noopener'],
},
],
rehypeHeadingIds,
[
rehypeKatex,
{
strict: false,
},
],
sectionize as any,
[
rehypePrettyCode,
{
theme: {
light: 'everforest-dark',
dark: 'everforest-dark',
},
transformers: [
transformerNotationDiff(),
transformerMetaHighlight(),
transformerRenderWhitespace(),
transformerNotationSkip(),
transformerDiffHighlight(),
+ transformerCopyButton({
+ duration: 1000,
+ successIcon: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(5,223,114,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E",
+ copyIcon: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E",
+ })
],
},
],
],
remarkPlugins: [remarkToc, remarkMath, remarkEmoji],
},
server: {
port: 1234,
host: true,
},
devToolbar: {
enabled: false,
},
})
```
## 3. 启动项目后访问blog后报错
```log
TypeError: Cannot read properties of undefined (reading 'type')
at /vscodeProjects/dev.2ha.me/node_modules/@shikijs/transformers/dist/index.mjs:516:22
at Array.flatMap (<anonymous>)
at /vscodeProjects/dev.2ha.me/node_modules/@shikijs/transformers/dist/index.mjs:507:41
at Array.forEach (<anonymous>)
at Object.root (/vscodeProjects/dev.2ha.me/node_modules/@shikijs/transformers/dist/index.mjs:501:21)
at tokensToHast (/vscodeProjects/dev.2ha.me/node_modules/@shikijs/core/dist/index.mjs:1313:33)
at codeToHast (/vscodeProjects/dev.2ha.me/node_modules/@shikijs/core/dist/index.mjs:1188:10… …
```
## 5. 修改node_modules/@shikijs/transformers依赖中的transformerRenderWhitespace方法
```javascript
function transformerRenderWhitespace(options = {}) {
const classMap = {
" ": options.classSpace ?? "space",
" ": options.classTab ?? "tab"
};
const position = options.position ?? "all";
const keys = Object.keys(classMap);
return {
name: "@shikijs/transformers:render-whitespace",
// We use `root` hook here to ensure it runs after all other transformers
root(root) {
const pre = root.children[0];
const code = pre.children[0];
code.children.forEach(
(line) => {
if (line.type !== "element")
return;
const elements = line.children.filter((token) => token.type === "element");
const last = elements.length - 1;
line.children = line.children.flatMap((token) => {
if (token.type !== "element")
return token;
const index = elements.indexOf(token);
if (position === "boundary" && index !== 0 && index !== last)
return token;
if (position === "trailing" && index !== last)
return token;
+ if (token.children.length === 0) {
+ return token;
+ }
const node = token.children[0];
if (node.type !== "text" || !node.value)
return token;
const parts = splitSpaces(
node.value.split(/([ \t])/).filter((i) => i.length),
position === "boundary" && index === last && last !== 0 ? "trailing" : position,
position !== "trailing"
);
if (parts.length <= 1)
return token;
return parts.map((part) => {
const clone = {
...token,
properties: { ...token.properties }
};
clone.children = [{ type: "text", value: part }];
if (keys.includes(part)) {
this.addClassToHast(clone, classMap[part]);
delete clone.properties.style;
}
return clone;
});
});
}
);
}
};
}
```

View File

@@ -0,0 +1,133 @@
import type { ShikiTransformer } from "shiki";
import { h } from "hastscript";
/**
* shikijs transformerRenderWhitespace 会报错 Cannot read properties of undefined (reading 'type'), 解决办法是
* @shikijs/transformers/dist/index.mjs:516:22 加一个判断 提前返回
* 如下
* if (token.children.length === 0) {
return token;
}
在这行代码之前加上面这段代码
const node = token.children[0];
if (node.type !== "text" || !node.value)
return token;
*/
export interface CopyButtonOptions {
duration?: number;
copyIcon?: string;
successIcon?: string
}
export const transformerCopyButton = (
options: CopyButtonOptions = {
duration: 1000
}
): ShikiTransformer => {
return {
name: 'shiki-transformer-copy-button',
code(node) {
const button = h('button', {
class: 'shiki-transformer-button-copy',
'data-code': this.source,
onclick: `
navigator.clipboard.writeText(this.dataset.code);
this.classList.add('shiki-transformer-button-copied');
setTimeout(() => this.classList.remove('shiki-transformer-button-copied'), ${options.duration})
`
}, [
h('span', { class: 'ready' }),
h('span', { class: 'success' })
]);
node.children.push(button)
node.children.push({
type: 'element',
tagName: 'style',
properties: {},
children: [
{
type: 'text',
value: buttonStyles({
successIcon: options.successIcon,
copyIcon: options.copyIcon
})
}
]
})
}
}
}
function buttonStyles({
successIcon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='rgba(5,223,114,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 3h2.6A2.4 2.4 0 0 1 21 5.4v15.2a2.4 2.4 0 0 1-2.4 2.4H5.4A2.4 2.4 0 0 1 3 20.6V5.4A2.4 2.4 0 0 1 5.4 3H8m0 11l3 3l5-7M8.8 1h6.4a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-.8.8H8.8a.8.8 0 0 1-.8-.8V1.8a.8.8 0 0 1 .8-.8'/%3E%3C/svg%3E",
copyIcon = "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20fill='none'%20stroke='rgba(128,128,128,1)'%20stroke-linecap='round'%20stroke-linejoin='round'%20stroke-width='2'%20viewBox='0%200%2024%2024'%3E%3Crect%20width='8'%20height='4'%20x='8'%20y='2'%20rx='1'%20ry='1'/%3E%3Cpath%20d='M16%204h2a2%202%200%200%201%202%202v14a2%202%200%200%201-2%202H6a2%202%200%200%201-2-2V6a2%202%200%200%201%202-2h2'/%3E%3C/svg%3E",
}: {
successIcon?: string,
copyIcon?: string
}) {
let buttonStyle =
`
:root {
--border-color: #e2e2e3;
--background-color: #f6f6f7;
--hover-background-color: #ffff
}
pre:has(code) {
position: relative;
}
pre button.shiki-transformer-button-copy {
position: absolute;
top: 12px;
right: 12px;
z-index: 3;
border: 1px solid var(--border-color);
border-radius: 4px;
width: 30px;
height: 30px;
display: flex;
justify-content: center;
place-items: center;
background-color: var(--background-color);
cursor: pointer;
background-repeat: no-repeat;
transition: var(--border-color) .25s, var(--background-color) .25s, opacity .25s;
&:hover {
background-color: var(--hover-background-color);
}
& span {
width: 100%;
aspect-ratio: 1 / 1;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
& .ready {
width: 20px;
height: 20px;
background-image: url("${copyIcon}");
}
& .success {
display: none;
width: 20px;
height: 20px;
background-image: url("${successIcon}");
}
&.shiki-transformer-button-copied {
& .success {
display: block;
}
& .ready {
display: none;
}
}
}`
return buttonStyle
}

View File

@@ -29,7 +29,7 @@ export function transformerNotationSkip(
if (classActivePre) this.addClassToHast(this.pre, classActivePre) if (classActivePre) this.addClassToHast(this.pre, classActivePre)
return false return false
}, },
false, // remove empty lines undefined, // remove empty lines
) )
} }

View File

@@ -40,7 +40,7 @@ const authors = await getCollection('authors')
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead className="max-w-[80px]">Path</TableHead> <TableHead className="max-w-[80px]">Path</TableHead>
<TableHead className="max-w-[80px] flex items-center">Illustrator</TableHead> <TableHead className="max-w-[80px] flex items-center">Author</TableHead>
<TableHead className="text-right">Resources</TableHead> <TableHead className="text-right">Resources</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -134,6 +134,19 @@ const authors = await getCollection('authors')
</Link> </Link>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow>
<TableCell className="font-medium">Background</TableCell>
<TableCell className="flex items-center">
<Link class="contents" href="https://mall.bilibili.com/neul-next/index.html?page=mall-up_itemDetail&noTitleBar=1&itemsId=1107984035&from=items_share&msource=items_share" target="_blank">
@小猫女仆降临 <svg class="size-8" style="filter: invert(100%);" xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24"><title>Bilibili</title><path d="M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373Z"/></svg>
</Link>
</TableCell>
<TableCell className="text-right">
<Link href="https://www.bilibili.com/video/BV1462uY8Eo4" target="_blank">
<img class="h-12 w-25" src="/static/images/maoliang.gif" />
</Link>
</TableCell>
</TableRow>
</TableBody> </TableBody>
</Table> </Table>
</div> </div>

View File

@@ -2,7 +2,7 @@
import AuthorPresence from '@/components/bento/AuthorPresence' import AuthorPresence from '@/components/bento/AuthorPresence'
import WakatimeGraph from '@/components/bento/WakatimeGraph.tsx' import WakatimeGraph from '@/components/bento/WakatimeGraph.tsx'
import Link from '@/components/Link.astro' import Link from '@/components/Link.astro'
import FuzzyText from '@/components/magicui/fuzzy-text' // import FuzzyText from '@/components/magicui/fuzzy-text'
import GradientText from '@/components/magicui/gradint-text' import GradientText from '@/components/magicui/gradint-text'
import LetterGlitch from '@/components/magicui/letter-glitch' import LetterGlitch from '@/components/magicui/letter-glitch'
import ShortCuts from '@/components/ShortCuts.astro' import ShortCuts from '@/components/ShortCuts.astro'
@@ -10,8 +10,9 @@ import { SITE, SOCIAL_LINKS } from '@/consts'
import Layout from '@/layouts/Layout.astro' import Layout from '@/layouts/Layout.astro'
import { Icon } from 'astro-icon/components' import { Icon } from 'astro-icon/components'
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
import GiteaCalendar from '@/components/bento/GiteaCalendar' import GiteaCalendar from '@/components/custom/GiteaCalendar'
import Music163Player from '@/components/bento/Music163Player' import Music163Player from '@/components/custom/Music163Player'
import RandomAnimeBackground from '@/components/custom/RandomAnimeBackgrounds'
const latestPost = await getCollection('blog').then((posts: any[]) => const latestPost = await getCollection('blog').then((posts: any[]) =>
posts posts
@@ -22,47 +23,20 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
.filter((post) => !post.data.hidden && !post.data.draft) .filter((post) => !post.data.hidden && !post.data.draft)
.at(0), .at(0),
) )
--- ---
<Layout title="主页" description={SITE.DESCRIPTION}> <Layout title="主页" description={SITE.DESCRIPTION}>
<section <section
class="mx-auto grid max-w-[375px] grid-cols-2 gap-4 px-4 [grid-template-areas:'a_a'_'a_a'_'b_b'_'b_b'_'e_e'_'h_i'_'h_c'_'k_c'_'d_d'_'d_d'_'g_g'_'g_g'_'f_f'_'j_j'_'j_j'] *:rounded-3xl *:border *:bg-secondary/25 *:bg-cover *:bg-center *:bg-no-repeat sm:max-w-screen-sm sm:[grid-template-areas:'a_a'_'b_d'_'e_e'_'j_g'_'h_i'_'h_c'_'k_c'_'f_f'] xl:max-w-screen-xl xl:grid-cols-4 xl:[grid-template-areas:'a_a_b_c'_'d_e_e_c'_'h_f_f_g'_'h_i_j_k'] xl:[&:hover:has(>.has-overlay:hover)>.first>.overlay]:opacity-100 xl:[&:hover>*:not(.first):hover_.overlay]:opacity-100" class="mx-auto grid max-w-[375px] grid-cols-2 gap-4 px-4 [grid-template-areas:'a_a'_'a_a'_'b_b'_'b_b'_'e_e'_'h_i'_'h_c'_'k_c'_'d_d'_'d_d'_'g_g'_'g_g'_'f_f'_'j_j'_'j_j'] *:rounded-3xl *:border *:bg-secondary/25 *:bg-cover *:bg-center *:bg-no-repeat sm:max-w-screen-sm sm:[grid-template-areas:'a_a'_'b_d'_'e_e'_'j_g'_'h_i'_'h_c'_'k_c'_'f_f'] xl:max-w-screen-xl xl:grid-cols-4 xl:[grid-template-areas:'a_a_b_c'_'d_e_e_c'_'h_f_f_g'_'h_i_j_k'] xl:[&:hover:has(>.has-overlay:hover)>.first>.overlay]:opacity-100 xl:[&:hover>*:not(.first):hover_.overlay]:opacity-100"
aria-label="Personal information and activity grid" aria-label="Personal information and activity grid"
> >
<div <div
class="first flex flex-row justify-center aspect-square rounded-3xl border bg-[url('/static/images/tou.gif')] bg-cover bg-color bg-center bg-position-inherit bg-no-repeat [grid-area:a] sm:aspect-[2.1/1] sm:bg-[url('/static/images/tou.gif')] xl:aspect-auto" class="first flex flex-row xl:max-h-[298px] grow-[0] justify-center aspect-square rounded-3xl border bg-cover bg-center bg-position-inherit bg-no-repeat [grid-area:a] sm:aspect-[2.1/1] xl:aspect-auto"
role="img" role="img"
aria-label="Introduction" aria-label="Introduction"
style="filter: contrast(2) saturate(0) invert(100) sepia(100); border-color: #afafaf;"
> >
<!-- <div <RandomAnimeBackground client:load/>
class="overlay size-full rounded-3xl bg-[url('/static/404.webp')] bg-cover bg-center bg-no-repeat opacity-0 transition-opacity duration-200"
>
</div> -->
<!-- class="first relative flex aspect-square justify-center rounded-3xl border [grid-area:a] sm:aspect-[2.1/1] xl:aspect-auto" -->
<!-- <img
class="no-repeat relative w-full max-w-fit justify-center rounded-3xl object-cover"
src="/static/404_white_mask.webp"
/> -->
<!-- <div class="absolute self-end p-2 justify-center">
<FuzzyText
client:load
baseIntensity={0.04}
hoverIntensity={0.24}
fontSize={96}
color="#fbb229"
,>4O4</FuzzyText
>
<div class="mb-3 mt-3"></div>
<FuzzyText
client:load
baseIntensity={0.04}
hoverIntensity={0.24}
fontSize={36}
color="#f95038">Not Found</FuzzyText
>
</div> -->
</div> </div>
<div <div
@@ -114,7 +88,7 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
alt={`Featured image for the latest post: ${latestPost.data.title}`} alt={`Featured image for the latest post: ${latestPost.data.title}`}
width={477} width={477}
height={251} height={251}
class="w-full rounded-2xl border border-border sm:ml-1 sm:w-[82%] " class="w-full rounded-2xl border border-border sm:w-[82%] "
/> />
</Link> </Link>

View File

@@ -517,4 +517,9 @@
.bg-color { .bg-color {
background-color: #e9d3b6 !important; background-color: #e9d3b6 !important;
}
.shiki-transformer-button-copy {
border: none !important;
background-color: hsl(0deg 0% 0% / 0%) !important;
} }