This commit is contained in:
2026-01-19 17:41:22 +08:00
parent 6b5d6e6ca5
commit dce351a9da
49 changed files with 827 additions and 605 deletions

View File

@@ -1,17 +1,16 @@
<p class="text-xs uppercase text-muted-foreground">comment</p>
<h3 class="text-xl font-semibold mb-3">留言 / 评论</h3>
<p class="text-muted-foreground text-xs uppercase">comment</p>
<h3 class="mb-3 text-xl font-semibold">留言 / 评论</h3>
<div id="tcomment" class="space-y-2">
<p class="text-sm text-muted-foreground">
如果暂时没有看到评论,请点击下方按钮重新加载。
</p>
<button
type="button"
class="inline-flex items-center justify-center rounded-md border border-input bg-background px-3 py-1.5 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
onclick="window.runTwikooComments && window.runTwikooComments()"
>
重新加载评论
</button>
<p class="text-muted-foreground text-sm">
如果暂时没有看到评论,请点击下方按钮重新加载。
</p>
<button
type="button"
class="border-input bg-background hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring inline-flex items-center justify-center rounded-md border px-3 py-1.5 text-sm font-medium shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
onclick="window.runTwikooComments && window.runTwikooComments()"
>
重新加载评论
</button>
</div>
<script src="/js/twikoo-loader.js" defer></script>

View File

@@ -1,18 +1,19 @@
---
import type * as interfaces from '../lib/interfaces.ts'
import Bookmark from './notion/Bookmark.astro'
import BulletedListItems from './notion/BulletedListItems.astro'
import Callout from './notion/Callout.astro'
import Code from './notion/Code.astro'
import ColumnList from './notion/ColumnList.astro'
import Divider from './notion/Divider.astro'
import Bookmark from './notion/Bookmark.astro'
import Embed from './notion/Embed.astro'
import Equation from './notion/Equation.astro'
import File from './notion/File.astro'
import Heading1 from './notion/Heading1.astro'
import Heading2 from './notion/Heading2.astro'
import Heading3 from './notion/Heading3.astro'
import Image from './notion/Image.astro'
import LinkToPage from './notion/LinkToPage.astro'
import Equation from './notion/Equation.astro'
import NumberedListItems from './notion/NumberedListItems.astro'
import Paragraph from './notion/Paragraph.astro'
import Quote from './notion/Quote.astro'
@@ -22,7 +23,6 @@ import TableOfContents from './notion/TableOfContents.astro'
import ToDo from './notion/ToDo.astro'
import Toggle from './notion/Toggle.astro'
import Video from './notion/Video.astro'
import type * as interfaces from '../lib/interfaces.ts'
export interface Props {
blocks: interfaces.Block[]
@@ -52,19 +52,11 @@ const {
return <Heading3 block={block} headings={headings} />
case 'bulleted_list':
return (
<BulletedListItems
block={block}
level={level}
headings={headings}
/>
<BulletedListItems block={block} level={level} headings={headings} />
)
case 'numbered_list':
return (
<NumberedListItems
block={block}
level={level}
headings={headings}
/>
<NumberedListItems block={block} level={level} headings={headings} />
)
case 'to_do':
return <ToDo block={block} headings={headings} />

View File

@@ -32,12 +32,16 @@ const canonicalUrl = Astro.url
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImageUrl} />
<meta property="og:image:alt" content={title} />
{ogImageWidth && (
<meta property="og:image:width" content={String(ogImageWidth)} />
)}
{ogImageHeight && (
<meta property="og:image:height" content={String(ogImageHeight)} />
)}
{
ogImageWidth && (
<meta property="og:image:width" content={String(ogImageWidth)} />
)
}
{
ogImageHeight && (
<meta property="og:image:height" content={String(ogImageHeight)} />
)
}
{ogImageType && <meta property="og:image:type" content={ogImageType} />}
<meta property="og:type" content="website" />
<meta property="og:locale" content={SITE.locale} />

View File

@@ -36,19 +36,26 @@ const canonicalUrl = Astro.url
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage.url} />
<meta property="og:image:alt" content={title} />
{ogImage.width && (
<meta property="og:image:width" content={String(ogImage.width)} />
)}
{ogImage.height && (
<meta property="og:image:height" content={String(ogImage.height)} />
)}
{
ogImage.width && (
<meta property="og:image:width" content={String(ogImage.width)} />
)
}
{
ogImage.height && (
<meta property="og:image:height" content={String(ogImage.height)} />
)
}
{ogImage.type && <meta property="og:image:type" content={ogImage.type} />}
<meta property="og:type" content="article" />
<meta property="og:locale" content={SITE.locale} />
<meta property="og:site_name" content={SITE.title} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:author" content={author} />
<meta property="article:published_time" content={post.data.date.toISOString()} />
<meta
property="article:published_time"
content={post.data.date.toISOString()}
/>
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />

View File

@@ -1,7 +1,6 @@
---
import { ScrollArea } from '@/components/ui/scroll-area'
import {
getCombinedReadingTime,
getParentId,
getParentPost,
getPostById,

View File

@@ -2,7 +2,6 @@
import Link from '@/components/Link.astro'
import { ScrollArea } from '@/components/ui/scroll-area'
import {
getCombinedReadingTime,
getParentId,
getParentPost,
getPostById,

View File

@@ -78,8 +78,9 @@ const { headings } = Astro.props
>
<a
href={`#${heading.slug}`}
class="mobile-toc-item underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
class="mobile-toc-item truncate underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
data-heading-id={heading.slug}
title={heading.text}
>
{heading.text}
</a>

View File

@@ -63,8 +63,9 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
? `/blog/${parentId}#${heading.slug}`
: `#${heading.slug}`
}
class="marker:text-foreground/30 list-none underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
class="marker:text-foreground/30 list-none truncate underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
data-heading-link={heading.slug}
title={heading.text}
>
{heading.text}
</a>
@@ -93,12 +94,13 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
? '#'
: `/blog/${section.subpostId}`
}
class="marker:text-foreground/30 list-none underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
class="marker:text-foreground/30 list-none truncate underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
data-heading-link={
section.subpostId === currentPostId
? 'top'
: `${section.subpostId}-top`
}
title={section.title}
>
{section.title}
</a>
@@ -119,12 +121,13 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
? `#${heading.slug}`
: `/blog/${section.subpostId}#${heading.slug}`
}
class="marker:text-foreground/30 hover:text-foreground/60 list-none underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
class="marker:text-foreground/30 hover:text-foreground/60 list-none truncate underline decoration-transparent underline-offset-[3px] transition-colors duration-200 hover:decoration-inherit"
data-heading-link={
section.subpostId === currentPostId
? heading.slug
: `${section.subpostId}-${heading.slug}`
}
title={heading.text}
>
{heading.text}
</a>

View File

@@ -16,7 +16,7 @@ import { Icon } from 'astro-icon/components'
</Button>
<script is:inline data-astro-rerun>
(() => {
;(() => {
const theme = (() => {
const stored = localStorage?.getItem('theme') ?? ''
if (['dark', 'light'].includes(stored)) return stored
@@ -83,9 +83,8 @@ import { Icon } from 'astro-icon/components'
const handleToggleClick = (event) => {
const element = document.documentElement
const currentTheme = element.getAttribute('data-theme') === 'dark'
? 'dark'
: 'light'
const currentTheme =
element.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
const prefersReducedMotion = window.matchMedia(

View File

@@ -1,8 +1,8 @@
---
import type * as interfaces from '../../lib/interfaces.ts'
import { isAmazonURL, isGitHubURL } from '../../lib/blog-helpers.ts'
import GithubLinkPreview from './GitHubLinkPreview.astro'
import type * as interfaces from '../../lib/interfaces.ts'
import Caption from './Caption.astro'
import GithubLinkPreview from './GitHubLinkPreview.astro'
export interface Props {
block: interfaces.Block
@@ -50,7 +50,6 @@ try {
</div>
</div>
</a>
</div>
)}
</>
@@ -80,7 +79,9 @@ try {
display: flex;
overflow: hidden;
user-select: none;
transition: background-color 160ms ease, border-color 160ms ease;
transition:
background-color 160ms ease,
border-color 160ms ease;
}
.bookmark > a:hover {
background: var(--notion-surface-strong);

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block
@@ -21,7 +21,8 @@ const listTypes = ['disc', 'circle', 'square']
{
items
.filter(
(b: interfaces.Block) => b.Type === 'bulleted_list_item' && b.BulletedListItem,
(b: interfaces.Block) =>
b.Type === 'bulleted_list_item' && b.BulletedListItem,
)
.map((b: interfaces.Block) => {
const item = b.BulletedListItem

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,26 +1,26 @@
---
export interface Props {
url: URL
}
const { url } = Astro.props
const param = url.searchParams.get('ctz')
---
<div class="circuit-simulator-applet-wrapper">
<iframe
src={`https://www.falstad.com/circuit/circuitjs.html?ctz=${param}`}
allowfullscreen></iframe>
</div>
<style>
.circuit-simulator-applet-wrapper {
margin: 0.4rem auto;
width: 100%;
aspect-ratio: 4 / 3;
}
.circuit-simulator-applet-wrapper iframe {
width: 100%;
height: 100%;
border: 1px solid var(--fg);
}
</style>
---
export interface Props {
url: URL
}
const { url } = Astro.props
const param = url.searchParams.get('ctz')
---
<div class="circuit-simulator-applet-wrapper">
<iframe
src={`https://www.falstad.com/circuit/circuitjs.html?ctz=${param}`}
allowfullscreen></iframe>
</div>
<style>
.circuit-simulator-applet-wrapper {
margin: 0.4rem auto;
width: 100%;
aspect-ratio: 4 / 3;
}
.circuit-simulator-applet-wrapper iframe {
width: 100%;
height: 100%;
border: 1px solid var(--fg);
}
</style>

View File

@@ -40,9 +40,9 @@ const displayLanguage =
<figure class="code mac">
<div class="chrome">
<div class="dots">
<span class="dot dot-red" />
<span class="dot dot-amber" />
<span class="dot dot-green" />
<span class="dot dot-red"></span>
<span class="dot dot-amber"></span>
<span class="dot dot-green"></span>
</div>
<div class="meta">
<span class="lang">{displayLanguage}</span>
@@ -76,7 +76,8 @@ const displayLanguage =
.then(() => {
const originalText = target.innerText
target.innerText = target.getAttribute('data-done-text') || target.innerText
target.innerText =
target.getAttribute('data-done-text') || target.innerText
setTimeout(() => {
target.innerText = originalText
@@ -100,7 +101,8 @@ const displayLanguage =
border-radius: 12px;
overflow: hidden;
border: 1px solid color-mix(in oklab, var(--border) 70%, transparent);
background: linear-gradient(
background:
linear-gradient(
180deg,
color-mix(in oklab, var(--muted) 12%, transparent),
transparent 30%
@@ -171,7 +173,9 @@ const displayLanguage =
line-height: 1.2rem;
cursor: pointer;
font-size: 0.75rem;
transition: background-color 120ms ease, transform 120ms ease;
transition:
background-color 120ms ease,
transform 120ms ease;
}
.code button.copy:hover {
@@ -199,7 +203,11 @@ const displayLanguage =
background: transparent;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
scrollbar-color: color-mix(in oklab, var(--muted-foreground) 60%, transparent)
scrollbar-color: color-mix(
in oklab,
var(--muted-foreground) 60%,
transparent
)
transparent;
}
@@ -240,6 +248,15 @@ const displayLanguage =
}
.lang {
font-family: var(--font-mono, 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);
font-family: var(
--font-mono,
'SFMono-Regular',
Menlo,
Monaco,
Consolas,
'Liberation Mono',
'Courier New',
monospace
);
}
</style>

View File

@@ -31,7 +31,7 @@ if (!block.ColumnList) {
box-sizing: border-box;
margin: 1rem auto;
gap: 0 1rem;
}
}
.column-list > div {
flex: 1 1 180px;
min-width: 0;

View File

@@ -1,4 +1,5 @@
---
---
<hr class="divider" />

View File

@@ -1,20 +1,20 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import {
isTweetURL,
isTikTokURL,
isCircuitSimulatorAppletURL,
isCodePenURL,
isInstagramURL,
isPinterestURL,
isCodePenURL,
isCircuitSimulatorAppletURL,
isTikTokURL,
isTweetURL,
} from '../../lib/blog-helpers.ts'
import * as interfaces from '../../lib/interfaces.ts'
import Bookmark from './Bookmark.astro'
import TweetEmbed from './TweetEmbed.astro'
import TikTokEmbed from './TikTokEmbed.astro'
import CircuitSimulatorAppletEmbed from './CircuitSimulatorAppletEmbed.astro'
import CodePenEmbed from './CodePenEmbed.astro'
import InstagramEmbed from './InstagramEmbed.astro'
import PinterestEmbed from './PinterestEmbed.astro'
import CodePenEmbed from './CodePenEmbed.astro'
import CircuitSimulatorAppletEmbed from './CircuitSimulatorAppletEmbed.astro'
import TikTokEmbed from './TikTokEmbed.astro'
import TweetEmbed from './TweetEmbed.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,6 +1,6 @@
---
import * as interfaces from '../../lib/interfaces'
import { filePath } from '../../lib/blog-helpers'
import * as interfaces from '../../lib/interfaces'
import Caption from './Caption.astro'
export interface Props {
@@ -26,8 +26,7 @@ try {
}
const filename =
url &&
decodeURIComponent(url.pathname.split('/').slice(-1)[0] || '')
url && decodeURIComponent(url.pathname.split('/').slice(-1)[0] || '')
---
<div class="file">

View File

@@ -1,315 +1,315 @@
---
export interface Props {
url: URL
}
type EmbedStyle =
| 'default'
| 'a11y-dark'
| 'a11y-light'
| 'agate'
| 'an-old-hope'
| 'androidstudio'
| 'arduino-light'
| 'arta'
| 'ascetic'
| 'atom-one-dark'
| 'atom-one-dark-reasonable'
| 'atom-one-light'
| 'brown-paper'
| 'codepen-embed'
| 'color-brewer'
| 'dark'
| 'devibeans'
| 'docco'
| 'far'
| 'felipec'
| 'foundation'
| 'github'
| 'github-dark'
| 'github-dark-dimmed'
| 'gml'
| 'googlecode'
| 'gradient-dark'
| 'gradient-light'
| 'grayscale'
| 'hybrid'
| 'idea'
| 'intellij-light'
| 'ir-black'
| 'isbl-editor-dark'
| 'isbl-editor-light'
| 'kimbie-dark'
| 'kimbie-light'
| 'lightfair'
| 'lioshi'
| 'magula'
| 'mono-blue'
| 'monokai'
| 'monokai-sublime'
| 'night-owl'
| 'nnfx-dark'
| 'nnfx-light'
| 'nord'
| 'obsidian'
| 'panda-syntax-dark'
| 'panda-syntax-light'
| 'paraiso-dark'
| 'paraiso-light'
| 'pojoaque'
| 'purebasic'
| 'qtcreator-dark'
| 'qtcreator-light'
| 'rainbow'
| 'routeros'
| 'school-book'
| 'shades-of-purple'
| 'srcery'
| 'stackoverflow-dark'
| 'stackoverflow-light'
| 'sunburst'
| 'tokyo-night-dark'
| 'tokyo-night-light'
| 'tomorrow-night-blue'
| 'tomorrow-night-bright'
| 'vs'
| 'vs2015'
| 'xcode'
| 'xt256'
| 'base16/3024'
| 'base16/apathy'
| 'base16/apprentice'
| 'base16/ashes'
| 'base16/atelier-cave'
| 'base16/atelier-cave-light'
| 'base16/atelier-dune'
| 'base16/atelier-dune-light'
| 'base16/atelier-estuary'
| 'base16/atelier-estuary-light'
| 'base16/atelier-forest'
| 'base16/atelier-forest-light'
| 'base16/atelier-heath'
| 'base16/atelier-heath-light'
| 'base16/atelier-lakeside'
| 'base16/atelier-lakeside-light'
| 'base16/atelier-plateau'
| 'base16/atelier-plateau-light'
| 'base16/atelier-savanna'
| 'base16/atelier-savanna-light'
| 'base16/atelier-seaside'
| 'base16/atelier-seaside-light'
| 'base16/atelier-sulphurpool'
| 'base16/atelier-sulphurpool-light'
| 'base16/atlas'
| 'base16/bespin'
| 'base16/black-metal'
| 'base16/black-metal-bathory'
| 'base16/black-metal-burzum'
| 'base16/black-metal-dark-funeral'
| 'base16/black-metal-gorgoroth'
| 'base16/black-metal-immortal'
| 'base16/black-metal-khold'
| 'base16/black-metal-marduk'
| 'base16/black-metal-mayhem'
| 'base16/black-metal-nile'
| 'base16/black-metal-venom'
| 'base16/brewer'
| 'base16/bright'
| 'base16/brogrammer'
| 'base16/brush-trees'
| 'base16/brush-trees-dark'
| 'base16/chalk'
| 'base16/circus'
| 'base16/classic-dark'
| 'base16/classic-light'
| 'base16/codeschool'
| 'base16/colors'
| 'base16/cupcake'
| 'base16/cupertino'
| 'base16/danqing'
| 'base16/darcula'
| 'base16/dark-violet'
| 'base16/darkmoss'
| 'base16/darktooth'
| 'base16/decaf'
| 'base16/default-dark'
| 'base16/default-light'
| 'base16/dirtysea'
| 'base16/dracula'
| 'base16/edge-dark'
| 'base16/edge-light'
| 'base16/eighties'
| 'base16/embers'
| 'base16/equilibrium-dark'
| 'base16/equilibrium-gray-dark'
| 'base16/equilibrium-gray-light'
| 'base16/equilibrium-light'
| 'base16/espresso'
| 'base16/eva'
| 'base16/eva-dim'
| 'base16/flat'
| 'base16/framer'
| 'base16/fruit-soda'
| 'base16/gigavolt'
| 'base16/github'
| 'base16/google-dark'
| 'base16/google-light'
| 'base16/grayscale-dark'
| 'base16/grayscale-light'
| 'base16/green-screen'
| 'base16/gruvbox-dark-hard'
| 'base16/gruvbox-dark-medium'
| 'base16/gruvbox-dark-pale'
| 'base16/gruvbox-dark-soft'
| 'base16/gruvbox-light-hard'
| 'base16/gruvbox-light-medium'
| 'base16/gruvbox-light-soft'
| 'base16/hardcore'
| 'base16/harmonic16-dark'
| 'base16/harmonic16-light'
| 'base16/heetch-dark'
| 'base16/heetch-light'
| 'base16/helios'
| 'base16/hopscotch'
| 'base16/horizon-dark'
| 'base16/horizon-light'
| 'base16/humanoid-dark'
| 'base16/humanoid-light'
| 'base16/ia-dark'
| 'base16/ia-light'
| 'base16/icy-dark'
| 'base16/ir-black'
| 'base16/isotope'
| 'base16/kimber'
| 'base16/london-tube'
| 'base16/macintosh'
| 'base16/marrakesh'
| 'base16/materia'
| 'base16/material'
| 'base16/material-darker'
| 'base16/material-lighter'
| 'base16/material-palenight'
| 'base16/material-vivid'
| 'base16/mellow-purple'
| 'base16/mexico-light'
| 'base16/mocha'
| 'base16/monokai'
| 'base16/nebula'
| 'base16/nord'
| 'base16/nova'
| 'base16/ocean'
| 'base16/oceanicnext'
| 'base16/one-light'
| 'base16/onedark'
| 'base16/outrun-dark'
| 'base16/papercolor-dark'
| 'base16/papercolor-light'
| 'base16/paraiso'
| 'base16/pasque'
| 'base16/phd'
| 'base16/pico'
| 'base16/pop'
| 'base16/porple'
| 'base16/qualia'
| 'base16/railscasts'
| 'base16/rebecca'
| 'base16/ros-pine'
| 'base16/ros-pine-dawn'
| 'base16/ros-pine-moon'
| 'base16/sagelight'
| 'base16/sandcastle'
| 'base16/seti-ui'
| 'base16/shapeshifter'
| 'base16/silk-dark'
| 'base16/silk-light'
| 'base16/snazzy'
| 'base16/solar-flare'
| 'base16/solar-flare-light'
| 'base16/solarized-dark'
| 'base16/solarized-light'
| 'base16/spacemacs'
| 'base16/summercamp'
| 'base16/summerfruit-dark'
| 'base16/summerfruit-light'
| 'base16/synth-midnight-terminal-dark'
| 'base16/synth-midnight-terminal-light'
| 'base16/tango'
| 'base16/tender'
| 'base16/tomorrow'
| 'base16/tomorrow-night'
| 'base16/twilight'
| 'base16/unikitty-dark'
| 'base16/unikitty-light'
| 'base16/vulcan'
| 'base16/windows-10'
| 'base16/windows-10-light'
| 'base16/windows-95'
| 'base16/windows-95-light'
| 'base16/windows-high-contrast'
| 'base16/windows-high-contrast-light'
| 'base16/windows-nt'
| 'base16/windows-nt-light'
| 'base16/woodland'
| 'base16/xcode-dusk'
| 'base16/zenburn'
type EmbedParams = {
style: EmbedStyle
type: 'code' | 'markdown' | 'ipynb'
showBorder?: 'on'
showLineNumbers?: 'on'
showFileMeta?: 'on'
showFullPath?: 'on'
showCopy?: 'on'
fetchFromJsDelivr?: 'on'
maxHeight?: number
}
function buildEmbedQuery(params: EmbedParams) {
return Object.entries(params)
.filter(([, v]) => v !== undefined)
.map(
([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`
)
.join('&')
}
/* Edit Params */
const EMBED_PARAMS: EmbedParams = {
style: 'github',
type: 'code',
showBorder: 'on',
showLineNumbers: 'on',
showFileMeta: 'on',
showFullPath: 'on',
showCopy: 'on',
}
const { url } = Astro.props
const PreviewURL = encodeURIComponent(url.toString())
const embedQuery = buildEmbedQuery(EMBED_PARAMS)
const embedScriptSrc = `https://emgithub.com/embed-v2.js?target=${PreviewURL}&${embedQuery}`
---
<div class="github-link-preview-wrapper">
<div class="github-link-preview">
<script is:inline src={embedScriptSrc}></script>
</div>
</div>
<style>
.github-link-preview-wrapper {
display: flex;
}
.github-link-preview {
flex: 1;
width: 0;
table {
white-space: unset;
}
td::after {
display: unset;
}
}
</style>
---
export interface Props {
url: URL
}
type EmbedStyle =
| 'default'
| 'a11y-dark'
| 'a11y-light'
| 'agate'
| 'an-old-hope'
| 'androidstudio'
| 'arduino-light'
| 'arta'
| 'ascetic'
| 'atom-one-dark'
| 'atom-one-dark-reasonable'
| 'atom-one-light'
| 'brown-paper'
| 'codepen-embed'
| 'color-brewer'
| 'dark'
| 'devibeans'
| 'docco'
| 'far'
| 'felipec'
| 'foundation'
| 'github'
| 'github-dark'
| 'github-dark-dimmed'
| 'gml'
| 'googlecode'
| 'gradient-dark'
| 'gradient-light'
| 'grayscale'
| 'hybrid'
| 'idea'
| 'intellij-light'
| 'ir-black'
| 'isbl-editor-dark'
| 'isbl-editor-light'
| 'kimbie-dark'
| 'kimbie-light'
| 'lightfair'
| 'lioshi'
| 'magula'
| 'mono-blue'
| 'monokai'
| 'monokai-sublime'
| 'night-owl'
| 'nnfx-dark'
| 'nnfx-light'
| 'nord'
| 'obsidian'
| 'panda-syntax-dark'
| 'panda-syntax-light'
| 'paraiso-dark'
| 'paraiso-light'
| 'pojoaque'
| 'purebasic'
| 'qtcreator-dark'
| 'qtcreator-light'
| 'rainbow'
| 'routeros'
| 'school-book'
| 'shades-of-purple'
| 'srcery'
| 'stackoverflow-dark'
| 'stackoverflow-light'
| 'sunburst'
| 'tokyo-night-dark'
| 'tokyo-night-light'
| 'tomorrow-night-blue'
| 'tomorrow-night-bright'
| 'vs'
| 'vs2015'
| 'xcode'
| 'xt256'
| 'base16/3024'
| 'base16/apathy'
| 'base16/apprentice'
| 'base16/ashes'
| 'base16/atelier-cave'
| 'base16/atelier-cave-light'
| 'base16/atelier-dune'
| 'base16/atelier-dune-light'
| 'base16/atelier-estuary'
| 'base16/atelier-estuary-light'
| 'base16/atelier-forest'
| 'base16/atelier-forest-light'
| 'base16/atelier-heath'
| 'base16/atelier-heath-light'
| 'base16/atelier-lakeside'
| 'base16/atelier-lakeside-light'
| 'base16/atelier-plateau'
| 'base16/atelier-plateau-light'
| 'base16/atelier-savanna'
| 'base16/atelier-savanna-light'
| 'base16/atelier-seaside'
| 'base16/atelier-seaside-light'
| 'base16/atelier-sulphurpool'
| 'base16/atelier-sulphurpool-light'
| 'base16/atlas'
| 'base16/bespin'
| 'base16/black-metal'
| 'base16/black-metal-bathory'
| 'base16/black-metal-burzum'
| 'base16/black-metal-dark-funeral'
| 'base16/black-metal-gorgoroth'
| 'base16/black-metal-immortal'
| 'base16/black-metal-khold'
| 'base16/black-metal-marduk'
| 'base16/black-metal-mayhem'
| 'base16/black-metal-nile'
| 'base16/black-metal-venom'
| 'base16/brewer'
| 'base16/bright'
| 'base16/brogrammer'
| 'base16/brush-trees'
| 'base16/brush-trees-dark'
| 'base16/chalk'
| 'base16/circus'
| 'base16/classic-dark'
| 'base16/classic-light'
| 'base16/codeschool'
| 'base16/colors'
| 'base16/cupcake'
| 'base16/cupertino'
| 'base16/danqing'
| 'base16/darcula'
| 'base16/dark-violet'
| 'base16/darkmoss'
| 'base16/darktooth'
| 'base16/decaf'
| 'base16/default-dark'
| 'base16/default-light'
| 'base16/dirtysea'
| 'base16/dracula'
| 'base16/edge-dark'
| 'base16/edge-light'
| 'base16/eighties'
| 'base16/embers'
| 'base16/equilibrium-dark'
| 'base16/equilibrium-gray-dark'
| 'base16/equilibrium-gray-light'
| 'base16/equilibrium-light'
| 'base16/espresso'
| 'base16/eva'
| 'base16/eva-dim'
| 'base16/flat'
| 'base16/framer'
| 'base16/fruit-soda'
| 'base16/gigavolt'
| 'base16/github'
| 'base16/google-dark'
| 'base16/google-light'
| 'base16/grayscale-dark'
| 'base16/grayscale-light'
| 'base16/green-screen'
| 'base16/gruvbox-dark-hard'
| 'base16/gruvbox-dark-medium'
| 'base16/gruvbox-dark-pale'
| 'base16/gruvbox-dark-soft'
| 'base16/gruvbox-light-hard'
| 'base16/gruvbox-light-medium'
| 'base16/gruvbox-light-soft'
| 'base16/hardcore'
| 'base16/harmonic16-dark'
| 'base16/harmonic16-light'
| 'base16/heetch-dark'
| 'base16/heetch-light'
| 'base16/helios'
| 'base16/hopscotch'
| 'base16/horizon-dark'
| 'base16/horizon-light'
| 'base16/humanoid-dark'
| 'base16/humanoid-light'
| 'base16/ia-dark'
| 'base16/ia-light'
| 'base16/icy-dark'
| 'base16/ir-black'
| 'base16/isotope'
| 'base16/kimber'
| 'base16/london-tube'
| 'base16/macintosh'
| 'base16/marrakesh'
| 'base16/materia'
| 'base16/material'
| 'base16/material-darker'
| 'base16/material-lighter'
| 'base16/material-palenight'
| 'base16/material-vivid'
| 'base16/mellow-purple'
| 'base16/mexico-light'
| 'base16/mocha'
| 'base16/monokai'
| 'base16/nebula'
| 'base16/nord'
| 'base16/nova'
| 'base16/ocean'
| 'base16/oceanicnext'
| 'base16/one-light'
| 'base16/onedark'
| 'base16/outrun-dark'
| 'base16/papercolor-dark'
| 'base16/papercolor-light'
| 'base16/paraiso'
| 'base16/pasque'
| 'base16/phd'
| 'base16/pico'
| 'base16/pop'
| 'base16/porple'
| 'base16/qualia'
| 'base16/railscasts'
| 'base16/rebecca'
| 'base16/ros-pine'
| 'base16/ros-pine-dawn'
| 'base16/ros-pine-moon'
| 'base16/sagelight'
| 'base16/sandcastle'
| 'base16/seti-ui'
| 'base16/shapeshifter'
| 'base16/silk-dark'
| 'base16/silk-light'
| 'base16/snazzy'
| 'base16/solar-flare'
| 'base16/solar-flare-light'
| 'base16/solarized-dark'
| 'base16/solarized-light'
| 'base16/spacemacs'
| 'base16/summercamp'
| 'base16/summerfruit-dark'
| 'base16/summerfruit-light'
| 'base16/synth-midnight-terminal-dark'
| 'base16/synth-midnight-terminal-light'
| 'base16/tango'
| 'base16/tender'
| 'base16/tomorrow'
| 'base16/tomorrow-night'
| 'base16/twilight'
| 'base16/unikitty-dark'
| 'base16/unikitty-light'
| 'base16/vulcan'
| 'base16/windows-10'
| 'base16/windows-10-light'
| 'base16/windows-95'
| 'base16/windows-95-light'
| 'base16/windows-high-contrast'
| 'base16/windows-high-contrast-light'
| 'base16/windows-nt'
| 'base16/windows-nt-light'
| 'base16/woodland'
| 'base16/xcode-dusk'
| 'base16/zenburn'
type EmbedParams = {
style: EmbedStyle
type: 'code' | 'markdown' | 'ipynb'
showBorder?: 'on'
showLineNumbers?: 'on'
showFileMeta?: 'on'
showFullPath?: 'on'
showCopy?: 'on'
fetchFromJsDelivr?: 'on'
maxHeight?: number
}
function buildEmbedQuery(params: EmbedParams) {
return Object.entries(params)
.filter(([, v]) => v !== undefined)
.map(
([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`,
)
.join('&')
}
/* Edit Params */
const EMBED_PARAMS: EmbedParams = {
style: 'github',
type: 'code',
showBorder: 'on',
showLineNumbers: 'on',
showFileMeta: 'on',
showFullPath: 'on',
showCopy: 'on',
}
const { url } = Astro.props
const PreviewURL = encodeURIComponent(url.toString())
const embedQuery = buildEmbedQuery(EMBED_PARAMS)
const embedScriptSrc = `https://emgithub.com/embed-v2.js?target=${PreviewURL}&${embedQuery}`
---
<div class="github-link-preview-wrapper">
<div class="github-link-preview">
<script is:inline src={embedScriptSrc}></script>
</div>
</div>
<style>
.github-link-preview-wrapper {
display: flex;
}
.github-link-preview {
flex: 1;
width: 0;
table {
white-space: unset;
}
td::after {
display: unset;
}
}
</style>

View File

@@ -1,8 +1,8 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { buildHeadingId } from '../../lib/blog-helpers.ts'
import RichText from './RichText.astro'
import * as interfaces from '../../lib/interfaces.ts'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,8 +1,8 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { buildHeadingId } from '../../lib/blog-helpers.ts'
import RichText from './RichText.astro'
import * as interfaces from '../../lib/interfaces.ts'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,8 +1,8 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { buildHeadingId } from '../../lib/blog-helpers.ts'
import RichText from './RichText.astro'
import * as interfaces from '../../lib/interfaces.ts'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,9 +1,8 @@
---
import { ENABLE_LIGHTBOX } from '@/consts'
import * as interfaces from '../../lib/interfaces'
import { filePath } from '../../lib/blog-helpers'
import * as interfaces from '../../lib/interfaces'
import Caption from './Caption.astro'
import fslightbox from 'fslightbox'
export interface Props {
block: interfaces.Block
@@ -22,7 +21,8 @@ if (block.Image.External) {
image = filePath(new URL(block.Image.File.Url))
}
const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image'
const altText =
block.Image.Caption?.map((c) => c.Text.Content).join('') || 'Image'
---
<figure class="image">
@@ -31,20 +31,25 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
<div class="image-wrapper">
<div class="image-container">
{ENABLE_LIGHTBOX ? (
<a data-fslightbox href={image} data-type="image" class="lightbox-link">
<img
src={image}
<a
data-fslightbox
href={image}
data-type="image"
class="lightbox-link"
>
<img
src={image}
alt={altText}
loading="lazy"
loading="lazy"
decoding="async"
class="image-content"
/>
</a>
) : (
<img
src={image}
<img
src={image}
alt={altText}
loading="lazy"
loading="lazy"
decoding="async"
class="image-content"
/>
@@ -62,29 +67,31 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
margin: 1rem auto;
max-width: 100%;
}
.image-wrapper {
margin: 0 auto;
max-width: 100%;
}
.image-container {
position: relative;
overflow: hidden;
border-radius: 0.5rem;
transition: transform 0.2s ease, box-shadow 0.2s ease;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}
.image-container:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.lightbox-link {
display: block;
cursor: zoom-in;
}
.image-content {
display: block;
max-width: 100%;
@@ -93,17 +100,18 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
object-fit: cover;
transition: opacity 0.3s ease;
}
.image-content[loading="lazy"] {
background: linear-gradient(90deg,
hsl(var(--muted)) 0%,
hsl(var(--muted) / 0.8) 50%,
.image-content[loading='lazy'] {
background: linear-gradient(
90deg,
hsl(var(--muted)) 0%,
hsl(var(--muted) / 0.8) 50%,
hsl(var(--muted)) 100%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
background-position: -200% 0;
@@ -112,12 +120,12 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
background-position: 200% 0;
}
}
@media (max-width: 768px) {
.image {
margin: 0.75rem auto;
}
.image-container:hover {
transform: none;
box-shadow: none;

View File

@@ -1,9 +1,9 @@
---
import arrow from '../../images/icon-arrow-link.svg'
import { getPostLink } from '../../lib/blog-helpers.ts'
import type { Post } from '../../lib/interfaces.ts'
import { getPostByPageId } from '../../lib/notion/client'
import { getPostLink } from '../../lib/blog-helpers.ts'
import '../../styles/notion-color.css'
import arrow from '../../images/icon-arrow-link.svg'
export interface Props {
pageId: string
@@ -22,18 +22,22 @@ if (pageId) {
<a href={getPostLink(post.Slug)} class="link">
<>
<span class="icon">
{post.Icon && post.Icon.Type === 'emoji'
? post.Icon.Emoji
: post.Icon && post.Icon.Type === 'external'
? (
<img
src={post.Icon.Url}
class="notion-icon"
alt="Post title icon in a page link"
/>
)
: '📄'}
<img src={arrow.src} class="icon-link" alt="Arrow icon of a page link" />
{post.Icon && post.Icon.Type === 'emoji' ? (
post.Icon.Emoji
) : post.Icon && post.Icon.Type === 'external' ? (
<img
src={post.Icon.Url}
class="notion-icon"
alt="Post title icon in a page link"
/>
) : (
'📄'
)}
<img
src={arrow.src}
class="icon-link"
alt="Arrow icon of a page link"
/>
</span>
<span class="text">{post.Title}</span>
</>
@@ -42,7 +46,11 @@ if (pageId) {
<a class="link">
<span class="icon">
🚫
<img src={arrow.src} class="icon-link" alt="Arrow icon of a page link" />
<img
src={arrow.src}
class="icon-link"
alt="Arrow icon of a page link"
/>
</span>
<span class="text not-found">Post not found</span>
</a>

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block
@@ -21,7 +21,8 @@ const items = block.ListItems ?? []
{
items
.filter(
(b: interfaces.Block) => b.Type === 'numbered_list_item' && b.NumberedListItem,
(b: interfaces.Block) =>
b.Type === 'numbered_list_item' && b.NumberedListItem,
)
.map((b: interfaces.Block) => {
const item = b.NumberedListItem

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -11,5 +11,4 @@ const { url } = Astro.props
type="text/javascript"
async
defer
src="//assets.pinterest.com/js/pinit.js"
></script>
src="//assets.pinterest.com/js/pinit.js"></script>

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,13 +1,13 @@
---
import katex from 'katex'
import type { RichText } from '../../lib/interfaces.ts'
import Anchor from './annotations/Anchor.astro'
import Bold from './annotations/Bold.astro'
import Code from './annotations/Code.astro'
import Color from './annotations/Color.astro'
import Italic from './annotations/Italic.astro'
import Strikethrough from './annotations/Strikethrough.astro'
import Underline from './annotations/Underline.astro'
import Color from './annotations/Color.astro'
import Code from './annotations/Code.astro'
import Anchor from './annotations/Anchor.astro'
import Mention from './Mention.astro'
export interface Props {
@@ -43,13 +43,13 @@ const { richText } = Astro.props
{content}
</>
)
}
},
)}
{richText.Equation && (
<span
set:html={katex.renderToString(
richText.Equation.Expression,
{ throwOnError: false }
{ throwOnError: false },
)}
/>
)}

View File

@@ -1,6 +1,6 @@
---
import NotionBlocks from '../NotionBlocks.astro'
import type * as interfaces from '../../lib/interfaces'
import NotionBlocks from '../NotionBlocks.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,6 +1,6 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { buildHeadingId } from '../../lib/blog-helpers.ts'
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import '../../styles/notion-color.css'
@@ -39,7 +39,7 @@ if (!block.TableOfContents) {
)} ${indentClass}`}
>
{heading.RichTexts.map(
(richText: interfaces.RichText) => richText.PlainText
(richText: interfaces.RichText) => richText.PlainText,
).join('')}
</a>
)
@@ -52,6 +52,9 @@ if (!block.TableOfContents) {
}
.table-of-contents > a {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.8rem;
font-size: 0.9rem;
font-weight: 500;

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -1,9 +1,9 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { snakeToKebab } from '../../lib/style-helpers.ts'
import RichText from './RichText.astro'
import NotionBlocks from '../NotionBlocks.astro'
import '../../styles/notion-color.css'
import NotionBlocks from '../NotionBlocks.astro'
import RichText from './RichText.astro'
export interface Props {
block: interfaces.Block

View File

@@ -17,7 +17,8 @@ const postURL =
</blockquote>
</div>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"
></script>
<style>
.tweet-embed {

View File

@@ -1,6 +1,6 @@
---
import * as interfaces from '../../lib/interfaces.ts'
import { isYouTubeURL, parseYouTubeVideoId } from '../../lib/blog-helpers.ts'
import * as interfaces from '../../lib/interfaces.ts'
import Caption from './Caption.astro'
export interface Props {

View File

@@ -14,10 +14,8 @@ const buttonVariants = cva(
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
muted:
'bg-muted text-foreground hover:bg-muted/80',
ghost:
'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50',
muted: 'bg-muted text-foreground hover:bg-muted/80',
ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {

View File

@@ -130,7 +130,8 @@ const PaginationComponent: React.FC<PaginationProps> = ({
baseUrl,
}) => {
const buildPages = () => {
if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1)
if (totalPages <= 7)
return Array.from({ length: totalPages }, (_, i) => i + 1)
const pageSet = new Set<number>([
1,

View File

@@ -4,8 +4,7 @@ export const ENABLE_LIGHTBOX = true
export const SITE: Site = {
title: '溴化锂的笔记本',
description:
'Security, Programming, Life',
description: 'Security, Programming, Life',
href: 'https://nvme0n1p.dev',
author: 'libr',
locale: 'zh-CN',

View File

@@ -6,8 +6,8 @@ import Footer from '@/components/Footer.astro'
import Head from '@/components/Head.astro'
import Header from '@/components/Header.astro'
import { ENABLE_LIGHTBOX, SITE } from '@/consts'
import { cn } from '@/lib/utils'
import { getStaticFilePath } from '@/lib/blog-helpers'
import { cn } from '@/lib/utils'
interface Props {
class?: string
}
@@ -38,11 +38,6 @@ const { class: className } = Astro.props
</main>
<Footer />
{
ENABLE_LIGHTBOX && (
<script src={getStaticFilePath('/js/fslightbox.js')} />
)
}
{ENABLE_LIGHTBOX && <script src={getStaticFilePath('/js/fslightbox.js')} />}
</body>
</html>

View File

@@ -11,18 +11,19 @@ import { cn } from '@/lib/utils'
<PageHead slot="head" title="500" />
<Breadcrumbs items={[{ label: 'Error', icon: 'lucide:server-off' }]} />
<section class="flex flex-col items-center justify-center gap-y-4 text-center">
<section
class="flex flex-col items-center justify-center gap-y-4 text-center"
>
<div class="max-w-md">
<h1 class="mb-4 text-3xl font-medium">500: Something went wrong</h1>
<p class="prose">
服务器爆炸了!如果你能联系到博主的话提醒他一声吧!
</p>
<p class="prose">服务器爆炸了!如果你能联系到博主的话提醒他一声吧!</p>
</div>
<Link
href="/"
class={cn(buttonVariants({ variant: 'outline' }), 'flex gap-x-1.5 group')}
>
<span class="transition-transform group-hover:-translate-x-1">&larr;</span>
<span class="transition-transform group-hover:-translate-x-1">&larr;</span
>
Go to home page
</Link>
</section>

View File

@@ -1,9 +1,9 @@
---
import Breadcrumbs from '@/components/Breadcrumbs.astro'
import Comment from '@/components/Comment.astro'
import Link from '@/components/Link.astro'
import PageHead from '@/components/PageHead.astro'
import Layout from '@/layouts/Layout.astro'
import Comment from '@/components/Comment.astro'
---
<Layout class="max-w-3xl">
@@ -13,11 +13,11 @@ import Comment from '@/components/Comment.astro'
<section class="grid gap-6">
<div class="relative rounded-lg border p-6">
<div class="relative flex flex-col gap-4">
<p class="text-xs uppercase tracking-[0.35em] text-muted-foreground">
<p class="text-muted-foreground text-xs tracking-[0.35em] uppercase">
profile
</p>
<h1 class="text-3xl font-semibold">关于我</h1>
<p class="max-w-3xl text-muted-foreground">
<p class="text-muted-foreground max-w-3xl">
啥都写一点,但是啥都不精通。前端后端客户端,运维网安都会那么一点点,尝试写出点什么项目但总是失败。
</p>
</div>
@@ -26,19 +26,19 @@ import Comment from '@/components/Comment.astro'
<div class="grid gap-4 md:grid-cols-3">
<div class="rounded-lg border p-5">
<h3 class="text-lg font-semibold">安全</h3>
<p class="text-sm leading-relaxed text-muted-foreground">
<p class="text-muted-foreground text-sm leading-relaxed">
非典型 Misc啥都做点感觉更像是全栈
</p>
</div>
<div class="rounded-lg border p-5">
<h3 class="text-lg font-semibold">代码</h3>
<p class="text-sm leading-relaxed text-muted-foreground">
<p class="text-muted-foreground text-sm leading-relaxed">
近期在写 C 和 Swift之前写过 Golang、Python、JavaScript
</p>
</div>
<div class="rounded-lg border p-5">
<h3 class="text-lg font-semibold">生活</h3>
<p class="text-sm leading-relaxed text-muted-foreground">
<p class="text-muted-foreground text-sm leading-relaxed">
二次元、骑车、游泳、羽毛球、CS、摄影
</p>
</div>
@@ -47,41 +47,45 @@ import Comment from '@/components/Comment.astro'
<div class="rounded-lg border p-6">
<div class="flex items-center justify-between gap-4">
<div>
<p class="text-xs uppercase text-muted-foreground">setup</p>
<p class="text-muted-foreground text-xs uppercase">setup</p>
<h3 class="text-xl font-semibold">常用设备</h3>
</div>
</div>
<div class="mt-4 grid gap-3 md:grid-cols-2">
<div class="rounded-lg border p-4">
<p class="text-sm font-semibold">PC</p>
<p class="text-sm text-muted-foreground">
<p class="text-muted-foreground text-sm">
M2 Macbook Air · MSI Stealth 14 · iPad Air 5
</p>
</div>
<div class="rounded-lg border p-4">
<p class="text-sm font-semibold">手机</p>
<p class="text-sm text-muted-foreground">iPhone 17 · vivo x100 · iPhone 13 mini</p>
<p class="text-muted-foreground text-sm">
iPhone 17 · vivo x100 · iPhone 13 mini
</p>
</div>
<div class="rounded-lg border p-4 md:col-span-2">
<p class="text-sm font-semibold">影音与记录</p>
<p class="text-sm text-muted-foreground">Sony XM5 · Nikon Zfc</p>
<p class="text-muted-foreground text-sm">Sony XM5 · Nikon Zfc</p>
</div>
</div>
</div>
<section class="rounded-lg border p-6">
<p class="text-xs uppercase text-muted-foreground">site</p>
<p class="text-muted-foreground text-xs uppercase">site</p>
<h3 class="text-xl font-semibold">关于本站</h3>
<p class="mt-2 text-sm leading-relaxed text-muted-foreground">
<p class="text-muted-foreground mt-2 text-sm leading-relaxed">
栈在 <Link href="https://astro.build" external underline>Astro</Link>,
<Link href="https://tailwindcss.com" external underline>Tailwind</Link>,
<Link href="https://ui.shadcn.com" external underline>shadcn/ui</Link> 之上,开源在
<Link href="https://github.com/jktrn/astro-erudite" external underline>GitHub</Link>。
<Link href="https://github.com/jktrn/astro-erudite" external underline
>GitHub</Link
>。
</p>
<h4 class="text-l font-semibold mt-2">大事记</h4>
<h4 class="text-l mt-2 font-semibold">大事记</h4>
<div class="mt-3 overflow-x-auto rounded-lg border">
<table class="w-full text-sm">
<thead class="bg-muted/60 text-left text-muted-foreground">
<thead class="bg-muted/60 text-muted-foreground text-left">
<tr>
<th class="px-4 py-2 font-semibold">时间</th>
<th class="px-4 py-2 font-semibold">事件</th>
@@ -91,26 +95,30 @@ import Comment from '@/components/Comment.astro'
<tr>
<td class="px-4 py-2 whitespace-nowrap">2018</td>
<td class="px-4 py-2">
第一次建站,域名 <code>stevelbr.ga</code>,使用 hexo 和不知道有几种主题
第一次建站,域名 <code>stevelbr.ga</code>,使用 hexo
和不知道有几种主题
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2020</td>
<td class="px-4 py-2">
购入腾讯云轻量服务器,购入域名 <code>stevelbr.top</code> ,使用 typecho
typecho-theme-handsome 建站,接入备案。
购入腾讯云轻量服务器,购入域名 <code>stevelbr.top</code> ,使用 typecho
typecho-theme-handsome 建站,接入备案。
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2023</td>
<td class="px-4 py-2">
停止续费腾讯云轻量,购入域名 <code>nvme0n1p.dev</code>,使用 hugo+serverless service 建站。
停止续费腾讯云轻量,购入域名 <code>nvme0n1p.dev</code>,使用
hugo+serverless service 建站。
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2024.1</td>
<td class="px-4 py-2">
使用 <Link href="http://cali.so" external underline>cali.so</Link> nextjs自建服务运行于 vercel。
使用 <Link href="http://cali.so" external underline
>cali.so</Link
> nextjs自建服务运行于 vercel。
</td>
</tr>
<tr>
@@ -128,18 +136,24 @@ import Comment from '@/components/Comment.astro'
<tr>
<td class="px-4 py-2 whitespace-nowrap">2024.10</td>
<td class="px-4 py-2">
优化 nuxtjs 代码,现在全站支持无刷新页面切换。<del>同时增加 algolia docsearch 魔改版作为站内搜索。</del>好像没开 Server Side Rendering ,炸了
优化 nuxtjs 代码,现在全站支持无刷新页面切换。<del
>同时增加 algolia docsearch 魔改版作为站内搜索。</del
>好像没开 Server Side Rendering ,炸了
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2024.11</td>
<td class="px-4 py-2">
感谢<Link href="https://www.dkdun.cn/" external underline>林枫云</Link>对 ctfer 的支持,切换到了他家的香港服务器。
感谢<Link href="https://www.dkdun.cn/" external underline
>林枫云</Link
>对 ctfer 的支持,切换到了他家的香港服务器。
</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2025.2</td>
<td class="px-4 py-2">用nextjs重写了。优化了访问速度减少了了图片。</td>
<td class="px-4 py-2"
>用nextjs重写了。优化了访问速度减少了了图片。</td
>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap">2025.7</td>

View File

@@ -1,35 +1,34 @@
---
import Breadcrumbs from '@/components/Breadcrumbs.astro'
import Comment from '@/components/Comment.astro'
import Link from '@/components/Link.astro'
import NotionBlocks from '@/components/NotionBlocks.astro'
import PostHead from '@/components/PostHead.astro'
import PostNavigation from '@/components/PostNavigation.astro'
import SubpostsHeader from '@/components/SubpostsHeader.astro'
import SubpostsSidebar from '@/components/SubpostsSidebar.astro'
import TOCHeader from '@/components/TOCHeader.astro'
import TOCSidebar from '@/components/TOCSidebar.astro'
import NotionBlocks from '@/components/NotionBlocks.astro'
import { badgeVariants } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import Layout from '@/layouts/Layout.astro'
import type { TOCSection } from '@/lib/data-utils'
import {
fetchRemotePostContent,
getAdjacentPosts,
getAllPostsAndSubposts,
getParentId,
getParentPost,
getSubpostCount,
getTOCSections,
fetchRemotePostContent,
renderRemoteBlockMap,
hasSubposts,
isSubpost,
parseAuthors,
renderRemoteBlockMap,
} from '@/lib/data-utils'
import type { TOCSection } from '@/lib/data-utils'
import Comment from '@/components/Comment.astro'
import { formatDate } from '@/lib/utils'
import { Icon } from 'astro-icon/components'
import { Image } from 'astro:assets'
import { render } from 'astro:content'
export async function getStaticPaths() {
const posts = await getAllPostsAndSubposts()
@@ -71,9 +70,7 @@ try {
parentPost = isCurrentSubpost ? await getParentPost(currentPostId) : null
hasChildPosts = await hasSubposts(currentPostId)
subpostCount = !isCurrentSubpost
? await getSubpostCount(currentPostId)
: 0
subpostCount = !isCurrentSubpost ? await getSubpostCount(currentPostId) : 0
tocSections = remoteContent
? remoteContent.headings.length > 0
@@ -91,7 +88,7 @@ try {
console.error('blog page render failed:', error)
return Astro.rewrite('/500')
}
export const prerender = false;
export const prerender = false
---
<Layout>

View File

@@ -5,8 +5,7 @@ import PageHead from '@/components/PageHead.astro'
import PaginationComponent from '@/components/ui/pagination'
import { SITE } from '@/consts'
import Layout from '@/layouts/Layout.astro'
import { getAllPosts, groupPostsByYear, normalizePost } from '@/lib/data-utils'
import type { PaginateFunction } from 'astro'
import { groupPostsByYear, normalizePost } from '@/lib/data-utils'
// export async function getStaticPaths({
// paginate,
@@ -16,33 +15,37 @@ import type { PaginateFunction } from 'astro'
// const allPosts = await getAllPosts()
// return paginate(allPosts, { pageSize: SITE.postsPerPage })
// }
const nowPage = parseInt(new URL(Astro.request.url).searchParams.get("p") || "1");
const nowPage = parseInt(
new URL(Astro.request.url).searchParams.get('p') || '1',
)
// use url param
// use url param
// const { page } = Astro.para
const page = await fetch(`https://notion-api.nvme0n1p.dev/v2/posts/?page=${nowPage}&length=${SITE.postsPerPage}`)
const page = await fetch(
`https://notion-api.nvme0n1p.dev/v2/posts/?page=${nowPage}&length=${SITE.postsPerPage}`,
)
.then((res) => {
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`);
return res.json();
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`)
return res.json()
})
.then((allPosts) => {
const totalPages = allPosts.length;
const currentPage = nowPage;
return {
data: allPosts.posts.map((post) => normalizePost(post)),
currentPage,
lastPage: totalPages,
};
});
const totalPages = allPosts.length
const currentPage = nowPage
return {
data: allPosts.posts.map((post) => normalizePost(post)),
currentPage,
lastPage: totalPages,
}
})
// fetch page
if(page.lastPage < page.currentPage) {
console.log("redirect to 404")
return Astro.rewrite("/404")
if (page.lastPage < page.currentPage) {
console.log('redirect to 404')
return Astro.rewrite('/404')
}
const postsByYear = groupPostsByYear(page.data)
const years = Object.keys(postsByYear).sort((a, b) => parseInt(b) - parseInt(a))
export const prerender = false;
export const prerender = false
---
<Layout class="max-w-3xl">

View File

@@ -1,13 +1,10 @@
---
import Breadcrumbs from '@/components/Breadcrumbs.astro'
import Comment from '@/components/Comment.astro'
import Link from '@/components/Link.astro'
import PageHead from '@/components/PageHead.astro'
import Layout from '@/layouts/Layout.astro'
import {
getFriendLinks,
type LinkEntry,
} from '@/lib/data-utils'
import Comment from '@/components/Comment.astro'
import { getFriendLinks } from '@/lib/data-utils'
const placeholderAvatar = '/avatar-placeholder.svg'
const visibleLinks = await getFriendLinks()
---
@@ -19,48 +16,44 @@ const visibleLinks = await getFriendLinks()
<section class="rounded-lg border p-6">
<div class="flex flex-col gap-2">
<h1 class="text-2xl font-semibold">友链 / Friends</h1>
<p class="text-muted-foreground text-sm">
这里收集一些朋友和有趣的网站
</p>
<p class="text-muted-foreground text-sm">这里收集一些朋友和有趣的网站</p>
</div>
</section>
<section class="grid gap-4">
{
visibleLinks.map((friend) => (
<article class="rounded-lg border p-4 transition hover:border-primary/60">
<article class="hover:border-primary/60 rounded-lg border p-4 transition">
<div class="flex items-start gap-3">
{
friend.picLink ? (
<img
src={friend.picLink}
alt={friend.name}
loading="lazy"
class="h-12 w-12 rounded-full border object-cover"
width="48"
height="48"
onerror={`this.onerror=null;this.src='${placeholderAvatar}';`}
/>
) : (
<img
src={placeholderAvatar}
alt="avatar placeholder"
loading="lazy"
class="h-12 w-12 rounded-full border object-cover"
width="48"
height="48"
/>
)
}
{friend.picLink ? (
<img
src={friend.picLink}
alt={friend.name}
loading="lazy"
class="h-12 w-12 rounded-full border object-cover"
width="48"
height="48"
onerror={`this.onerror=null;this.src='${placeholderAvatar}';`}
/>
) : (
<img
src={placeholderAvatar}
alt="avatar placeholder"
loading="lazy"
class="h-12 w-12 rounded-full border object-cover"
width="48"
height="48"
/>
)}
<div class="flex-1 space-y-1">
<div class="flex items-center justify-between gap-3">
<h2 class="text-lg font-semibold leading-tight">
<h2 class="text-lg leading-tight font-semibold">
<Link href={friend.links} external underline>
{friend.name}
</Link>
</h2>
</div>
<p class="text-sm text-muted-foreground">
<p class="text-muted-foreground text-sm">
{friend.Description || '你来到了知识的荒原'}
</p>
</div>
@@ -72,10 +65,11 @@ const visibleLinks = await getFriendLinks()
<section class="rounded-lg border p-6">
<h3 class="text-lg font-semibold">申请方式</h3>
<p class="text-sm text-muted-foreground mt-2">
<p class="text-muted-foreground mt-2 text-sm">
请提供站点名称、链接、一句话介绍。
</p>
<pre class="mt-3 whitespace-pre-wrap break-words rounded-md bg-muted/50 p-3 text-xs text-muted-foreground">
<pre
class="bg-muted/50 text-muted-foreground mt-3 rounded-md p-3 text-xs break-words whitespace-pre-wrap">
name: 溴化锂的笔记本
link: https://nvme0n1p.dev
avatar: https://gravatar.com/avatar/29d64df3ca2a9dac5a7fffa5372fb80fb3270ceb223de2af0c33cdc4b2cbe954?v=1687917579000&size=256&d=initials

View File

@@ -15,9 +15,7 @@ const blog = await getRecentPosts(SITE.featuredPostCount)
<section class="rounded-lg border">
<div class="flex flex-col space-y-1.5 p-6">
<h3 class="text-3xl leading-none font-medium">溴化锂</h3>
<p class="text-muted-foreground text-sm">
LiBr
</p>
<p class="text-muted-foreground text-sm">LiBr</p>
</div>
<div class="p-6 pt-0">
<p class="text-muted-foreground mb-2 text-sm">

View File

@@ -24,7 +24,7 @@ export async function getStaticPaths() {
}
const { tag, posts } = Astro.props
export const prerender = false;
export const prerender = false
---
<Layout class="max-w-3xl">

View File

@@ -8,7 +8,7 @@ import { getSortedTags } from '@/lib/data-utils'
import { Icon } from 'astro-icon/components'
const sortedTags = await getSortedTags()
export const prerender = false;
export const prerender = false
---
<Layout class="max-w-3xl">

View File

@@ -44,13 +44,37 @@
--notion-red: color-mix(in oklab, var(--foreground) 82%, #d44c47);
--notion-gray-background: color-mix(in oklab, var(--background) 82%, #f1f1ef);
--notion-brown-background: color-mix(in oklab, var(--background) 82%, #f4eeee);
--notion-orange-background: color-mix(in oklab, var(--background) 82%, #fbecdd);
--notion-yellow-background: color-mix(in oklab, var(--background) 82%, #fbf3db);
--notion-green-background: color-mix(in oklab, var(--background) 82%, #edf3ec);
--notion-brown-background: color-mix(
in oklab,
var(--background) 82%,
#f4eeee
);
--notion-orange-background: color-mix(
in oklab,
var(--background) 82%,
#fbecdd
);
--notion-yellow-background: color-mix(
in oklab,
var(--background) 82%,
#fbf3db
);
--notion-green-background: color-mix(
in oklab,
var(--background) 82%,
#edf3ec
);
--notion-blue-background: color-mix(in oklab, var(--background) 82%, #e7f3f8);
--notion-purple-background: color-mix(in oklab, var(--background) 82%, rgba(244, 240, 247, 0.8));
--notion-pink-background: color-mix(in oklab, var(--background) 82%, rgba(249, 238, 243, 0.8));
--notion-purple-background: color-mix(
in oklab,
var(--background) 82%,
rgba(244, 240, 247, 0.8)
);
--notion-pink-background: color-mix(
in oklab,
var(--background) 82%,
rgba(249, 238, 243, 0.8)
);
--notion-red-background: color-mix(in oklab, var(--background) 82%, #fdebec);
--notion-tag-foreground: var(--foreground);