mirror of
https://github.com/lbr77/blog-astro.git
synced 2026-04-08 16:11:56 +00:00
format
This commit is contained in:
156
AGENTS.md
Normal file
156
AGENTS.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
This file provides guidelines for agentic coding assistants working on this Astro blog repository.
|
||||||
|
|
||||||
|
## Build & Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development server (runs on port 1234)
|
||||||
|
bun run dev # or bun run start
|
||||||
|
|
||||||
|
# Production build
|
||||||
|
bun run build
|
||||||
|
|
||||||
|
# Preview production build
|
||||||
|
bun run preview
|
||||||
|
|
||||||
|
# Format code (uses Prettier with Astro plugins)
|
||||||
|
bun run prettier
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
bun install
|
||||||
|
|
||||||
|
# Apply patches after install (runs automatically via postinstall)
|
||||||
|
patch-package
|
||||||
|
```
|
||||||
|
|
||||||
|
**Testing**: No test framework is currently configured in this project.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Framework**: Astro 5.7.13 (SSR mode, output: 'server')
|
||||||
|
- **UI**: React 19.0.0 via @astrojs/react
|
||||||
|
- **Styling**: Tailwind CSS v4.1.7 via @tailwindcss/vite
|
||||||
|
- **TypeScript**: 5.8.3 with strict mode enabled
|
||||||
|
- **Content**: Notion API integration for blog posts
|
||||||
|
- **UI Components**: shadcn/ui (new-york style, neutral base color)
|
||||||
|
- **Code Blocks**: astro-expressive-code with collapsible sections
|
||||||
|
- **Math/Code**: KaTeX for math, rehype-pretty-code for syntax highlighting
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### Formatting (Prettier)
|
||||||
|
- Single quotes (`'`), no semicolons, trailing comma (es5 style)
|
||||||
|
- Plugins: `prettier-plugin-astro`, `prettier-plugin-tailwindcss`, `prettier-plugin-astro-organize-imports`
|
||||||
|
- Format with: `bun run prettier`
|
||||||
|
|
||||||
|
### TypeScript Configuration
|
||||||
|
- Strict mode enabled (`strictNullChecks: true`)
|
||||||
|
- Path aliases: `@/*` → `./src/*`
|
||||||
|
- JSX: `react-jsx` with React as import source
|
||||||
|
|
||||||
|
### Imports
|
||||||
|
- Use path alias `@/` for all internal imports:
|
||||||
|
- `@/components/*` → components
|
||||||
|
- `@/lib/*` → utility functions
|
||||||
|
- `@/styles/*` → stylesheets
|
||||||
|
- External imports: named imports preferred
|
||||||
|
- React components: `import { Button } from '@/components/ui/button'`
|
||||||
|
- Astro components: `import Header from '@/components/Header.astro'`
|
||||||
|
|
||||||
|
### Component Patterns
|
||||||
|
|
||||||
|
**Astro Components (.astro)**:
|
||||||
|
- Use for main layout and content components
|
||||||
|
- Props interface defined in frontmatter: `export interface Props { ... }`
|
||||||
|
- Use `class` prop for CSS classes (not `className`)
|
||||||
|
- Extract props: `const { class: className } = Astro.props`
|
||||||
|
|
||||||
|
**React Components (.tsx)**:
|
||||||
|
- Use for interactive UI components (shadcn/ui pattern)
|
||||||
|
- Use `className` for CSS classes
|
||||||
|
- Use `cn()` utility for conditional class merging
|
||||||
|
- Radix UI primitives for accessibility
|
||||||
|
- class-variance-authority (cva) for component variants
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```tsx
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { cva } from 'class-variance-authority'
|
||||||
|
|
||||||
|
const variants = cva('base-classes', {
|
||||||
|
variants: { variant: { default: '...', outline: '...' } }
|
||||||
|
})
|
||||||
|
|
||||||
|
function Component({ className, variant, ...props }) {
|
||||||
|
return <div className={cn(variants({ variant }), className)} {...props} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
**Tailwind CSS**:
|
||||||
|
- Primary styling framework (v4, no config file)
|
||||||
|
- Use `cn()` from `@/lib/utils` for merging conditional classes
|
||||||
|
- Dark mode via `data-theme` attribute (not media query)
|
||||||
|
- CSS variables defined in `src/styles/global.css` for theming
|
||||||
|
- OKLCH color space for all colors
|
||||||
|
|
||||||
|
**CSS Variables**:
|
||||||
|
- Light mode: `:root` selector
|
||||||
|
- Dark mode: `[data-theme='dark']` selector
|
||||||
|
- Semantic tokens: `--background`, `--foreground`, `--primary`, `--muted`, `--border`, `--ring`
|
||||||
|
- Special tokens: `--notion-surface`, `--notion-border`, `--fg`, `--anchor-border`
|
||||||
|
|
||||||
|
**Custom CSS**:
|
||||||
|
- `global.css`: Main stylesheet with Tailwind imports
|
||||||
|
- `notion-color.css`: Notion integration colors
|
||||||
|
- `typography.css`: Text styling
|
||||||
|
- `syntax-coloring.css`: Code highlighting styles
|
||||||
|
|
||||||
|
### Type Definitions
|
||||||
|
- All shared types in `src/lib/interfaces.ts`
|
||||||
|
- Notion API types: Block, Paragraph, Heading1-3, Image, Code, etc.
|
||||||
|
- Use explicit types for all props and function parameters
|
||||||
|
|
||||||
|
### Utility Functions
|
||||||
|
- `cn()` from `@/lib/utils`: Merge CSS classes (clsx + tailwind-merge)
|
||||||
|
- `formatDate()`, `readingTime()`, `calculateWordCountFromHtml()` in `@/lib/utils`
|
||||||
|
- Blog helpers in `@/lib/blog-helpers.ts`: URL parsing, slug generation, Notion image handling
|
||||||
|
- Style helpers in `@/lib/style-helpers.ts`: CSS class transformations
|
||||||
|
|
||||||
|
### Component Structure
|
||||||
|
- Layouts: `src/layouts/Layout.astro`
|
||||||
|
- Pages: `src/pages/*.astro` and `src/pages/**/*.astro`
|
||||||
|
- Components: `src/components/*.astro` and `src/components/ui/*.tsx`
|
||||||
|
- Notion block components: `src/components/notion/*.astro`
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
- Components: PascalCase (`Header.astro`, `Button.tsx`)
|
||||||
|
- Files: PascalCase for components, kebab-case for utilities
|
||||||
|
- CSS classes: Tailwind utilities, custom classes in kebab-case
|
||||||
|
- Functions: camelCase (`cn()`, `formatDate()`)
|
||||||
|
- Constants: UPPER_SNAKE_CASE (`SITE`, `ENABLE_LIGHTBOX`)
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Use try-catch for async operations
|
||||||
|
- Console errors for image URL parsing failures
|
||||||
|
- Graceful null/undefined checks for optional properties
|
||||||
|
- Return early for invalid data: `if (!block.Paragraph) return null`
|
||||||
|
|
||||||
|
### Inline Scripts
|
||||||
|
- Use `is:inline` for client-side scripts that need to execute immediately
|
||||||
|
- Use standard `<script>` tags for scoped client-side logic
|
||||||
|
- Listen to Astro events: `astro:after-swap` for navigation updates
|
||||||
|
|
||||||
|
### Content Pattern (Notion Integration)
|
||||||
|
- Blog posts fetched from Notion API
|
||||||
|
- Block-based rendering: `NotionBlocks.astro` recursively renders block arrays
|
||||||
|
- Each block type has a dedicated component (`Paragraph.astro`, `Heading1.astro`, etc.)
|
||||||
|
- Rich text rendering via `RichText.astro` for text formatting
|
||||||
|
- Children blocks supported for nested content
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
- Vercel adapter configured (`adapter: vercel()`)
|
||||||
|
- SSR output mode
|
||||||
|
- Server-side rendering for all pages
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
|
<p class="text-muted-foreground text-xs uppercase">comment</p>
|
||||||
<p class="text-xs uppercase text-muted-foreground">comment</p>
|
<h3 class="mb-3 text-xl font-semibold">留言 / 评论</h3>
|
||||||
<h3 class="text-xl font-semibold mb-3">留言 / 评论</h3>
|
|
||||||
<div id="tcomment" class="space-y-2">
|
<div id="tcomment" class="space-y-2">
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-muted-foreground text-sm">
|
||||||
如果暂时没有看到评论,请点击下方按钮重新加载。
|
如果暂时没有看到评论,请点击下方按钮重新加载。
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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()"
|
onclick="window.runTwikooComments && window.runTwikooComments()"
|
||||||
>
|
>
|
||||||
重新加载评论
|
重新加载评论
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/twikoo-loader.js" defer></script>
|
<script src="/js/twikoo-loader.js" defer></script>
|
||||||
|
|||||||
@@ -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 BulletedListItems from './notion/BulletedListItems.astro'
|
||||||
import Callout from './notion/Callout.astro'
|
import Callout from './notion/Callout.astro'
|
||||||
import Code from './notion/Code.astro'
|
import Code from './notion/Code.astro'
|
||||||
import ColumnList from './notion/ColumnList.astro'
|
import ColumnList from './notion/ColumnList.astro'
|
||||||
import Divider from './notion/Divider.astro'
|
import Divider from './notion/Divider.astro'
|
||||||
import Bookmark from './notion/Bookmark.astro'
|
|
||||||
import Embed from './notion/Embed.astro'
|
import Embed from './notion/Embed.astro'
|
||||||
|
import Equation from './notion/Equation.astro'
|
||||||
import File from './notion/File.astro'
|
import File from './notion/File.astro'
|
||||||
import Heading1 from './notion/Heading1.astro'
|
import Heading1 from './notion/Heading1.astro'
|
||||||
import Heading2 from './notion/Heading2.astro'
|
import Heading2 from './notion/Heading2.astro'
|
||||||
import Heading3 from './notion/Heading3.astro'
|
import Heading3 from './notion/Heading3.astro'
|
||||||
import Image from './notion/Image.astro'
|
import Image from './notion/Image.astro'
|
||||||
import LinkToPage from './notion/LinkToPage.astro'
|
import LinkToPage from './notion/LinkToPage.astro'
|
||||||
import Equation from './notion/Equation.astro'
|
|
||||||
import NumberedListItems from './notion/NumberedListItems.astro'
|
import NumberedListItems from './notion/NumberedListItems.astro'
|
||||||
import Paragraph from './notion/Paragraph.astro'
|
import Paragraph from './notion/Paragraph.astro'
|
||||||
import Quote from './notion/Quote.astro'
|
import Quote from './notion/Quote.astro'
|
||||||
@@ -22,7 +23,6 @@ import TableOfContents from './notion/TableOfContents.astro'
|
|||||||
import ToDo from './notion/ToDo.astro'
|
import ToDo from './notion/ToDo.astro'
|
||||||
import Toggle from './notion/Toggle.astro'
|
import Toggle from './notion/Toggle.astro'
|
||||||
import Video from './notion/Video.astro'
|
import Video from './notion/Video.astro'
|
||||||
import type * as interfaces from '../lib/interfaces.ts'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
blocks: interfaces.Block[]
|
blocks: interfaces.Block[]
|
||||||
@@ -52,19 +52,11 @@ const {
|
|||||||
return <Heading3 block={block} headings={headings} />
|
return <Heading3 block={block} headings={headings} />
|
||||||
case 'bulleted_list':
|
case 'bulleted_list':
|
||||||
return (
|
return (
|
||||||
<BulletedListItems
|
<BulletedListItems block={block} level={level} headings={headings} />
|
||||||
block={block}
|
|
||||||
level={level}
|
|
||||||
headings={headings}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
case 'numbered_list':
|
case 'numbered_list':
|
||||||
return (
|
return (
|
||||||
<NumberedListItems
|
<NumberedListItems block={block} level={level} headings={headings} />
|
||||||
block={block}
|
|
||||||
level={level}
|
|
||||||
headings={headings}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
case 'to_do':
|
case 'to_do':
|
||||||
return <ToDo block={block} headings={headings} />
|
return <ToDo block={block} headings={headings} />
|
||||||
|
|||||||
@@ -32,12 +32,16 @@ const canonicalUrl = Astro.url
|
|||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
<meta property="og:image" content={ogImageUrl} />
|
<meta property="og:image" content={ogImageUrl} />
|
||||||
<meta property="og:image:alt" content={title} />
|
<meta property="og:image:alt" content={title} />
|
||||||
{ogImageWidth && (
|
{
|
||||||
<meta property="og:image:width" content={String(ogImageWidth)} />
|
ogImageWidth && (
|
||||||
)}
|
<meta property="og:image:width" content={String(ogImageWidth)} />
|
||||||
{ogImageHeight && (
|
)
|
||||||
<meta property="og:image:height" content={String(ogImageHeight)} />
|
}
|
||||||
)}
|
{
|
||||||
|
ogImageHeight && (
|
||||||
|
<meta property="og:image:height" content={String(ogImageHeight)} />
|
||||||
|
)
|
||||||
|
}
|
||||||
{ogImageType && <meta property="og:image:type" content={ogImageType} />}
|
{ogImageType && <meta property="og:image:type" content={ogImageType} />}
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:locale" content={SITE.locale} />
|
<meta property="og:locale" content={SITE.locale} />
|
||||||
|
|||||||
@@ -36,19 +36,26 @@ const canonicalUrl = Astro.url
|
|||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
<meta property="og:image" content={ogImage.url} />
|
<meta property="og:image" content={ogImage.url} />
|
||||||
<meta property="og:image:alt" content={title} />
|
<meta property="og:image:alt" content={title} />
|
||||||
{ogImage.width && (
|
{
|
||||||
<meta property="og:image:width" content={String(ogImage.width)} />
|
ogImage.width && (
|
||||||
)}
|
<meta property="og:image:width" content={String(ogImage.width)} />
|
||||||
{ogImage.height && (
|
)
|
||||||
<meta property="og:image:height" content={String(ogImage.height)} />
|
}
|
||||||
)}
|
{
|
||||||
|
ogImage.height && (
|
||||||
|
<meta property="og:image:height" content={String(ogImage.height)} />
|
||||||
|
)
|
||||||
|
}
|
||||||
{ogImage.type && <meta property="og:image:type" content={ogImage.type} />}
|
{ogImage.type && <meta property="og:image:type" content={ogImage.type} />}
|
||||||
<meta property="og:type" content="article" />
|
<meta property="og:type" content="article" />
|
||||||
<meta property="og:locale" content={SITE.locale} />
|
<meta property="og:locale" content={SITE.locale} />
|
||||||
<meta property="og:site_name" content={SITE.title} />
|
<meta property="og:site_name" content={SITE.title} />
|
||||||
<meta property="og:url" content={canonicalUrl} />
|
<meta property="og:url" content={canonicalUrl} />
|
||||||
<meta property="og:author" content={author} />
|
<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:title" content={title} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import {
|
import {
|
||||||
getCombinedReadingTime,
|
|
||||||
getParentId,
|
getParentId,
|
||||||
getParentPost,
|
getParentPost,
|
||||||
getPostById,
|
getPostById,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import Link from '@/components/Link.astro'
|
import Link from '@/components/Link.astro'
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import {
|
import {
|
||||||
getCombinedReadingTime,
|
|
||||||
getParentId,
|
getParentId,
|
||||||
getParentPost,
|
getParentPost,
|
||||||
getPostById,
|
getPostById,
|
||||||
|
|||||||
@@ -78,8 +78,9 @@ const { headings } = Astro.props
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={`#${heading.slug}`}
|
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}
|
data-heading-id={heading.slug}
|
||||||
|
title={heading.text}
|
||||||
>
|
>
|
||||||
{heading.text}
|
{heading.text}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -63,8 +63,9 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
|
|||||||
? `/blog/${parentId}#${heading.slug}`
|
? `/blog/${parentId}#${heading.slug}`
|
||||||
: `#${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}
|
data-heading-link={heading.slug}
|
||||||
|
title={heading.text}
|
||||||
>
|
>
|
||||||
{heading.text}
|
{heading.text}
|
||||||
</a>
|
</a>
|
||||||
@@ -93,12 +94,13 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
|
|||||||
? '#'
|
? '#'
|
||||||
: `/blog/${section.subpostId}`
|
: `/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={
|
data-heading-link={
|
||||||
section.subpostId === currentPostId
|
section.subpostId === currentPostId
|
||||||
? 'top'
|
? 'top'
|
||||||
: `${section.subpostId}-top`
|
: `${section.subpostId}-top`
|
||||||
}
|
}
|
||||||
|
title={section.title}
|
||||||
>
|
>
|
||||||
{section.title}
|
{section.title}
|
||||||
</a>
|
</a>
|
||||||
@@ -119,12 +121,13 @@ const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId
|
|||||||
? `#${heading.slug}`
|
? `#${heading.slug}`
|
||||||
: `/blog/${section.subpostId}#${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={
|
data-heading-link={
|
||||||
section.subpostId === currentPostId
|
section.subpostId === currentPostId
|
||||||
? heading.slug
|
? heading.slug
|
||||||
: `${section.subpostId}-${heading.slug}`
|
: `${section.subpostId}-${heading.slug}`
|
||||||
}
|
}
|
||||||
|
title={heading.text}
|
||||||
>
|
>
|
||||||
{heading.text}
|
{heading.text}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Icon } from 'astro-icon/components'
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<script is:inline data-astro-rerun>
|
<script is:inline data-astro-rerun>
|
||||||
(() => {
|
;(() => {
|
||||||
const theme = (() => {
|
const theme = (() => {
|
||||||
const stored = localStorage?.getItem('theme') ?? ''
|
const stored = localStorage?.getItem('theme') ?? ''
|
||||||
if (['dark', 'light'].includes(stored)) return stored
|
if (['dark', 'light'].includes(stored)) return stored
|
||||||
@@ -83,9 +83,8 @@ import { Icon } from 'astro-icon/components'
|
|||||||
|
|
||||||
const handleToggleClick = (event) => {
|
const handleToggleClick = (event) => {
|
||||||
const element = document.documentElement
|
const element = document.documentElement
|
||||||
const currentTheme = element.getAttribute('data-theme') === 'dark'
|
const currentTheme =
|
||||||
? 'dark'
|
element.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
|
||||||
: 'light'
|
|
||||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
|
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
|
||||||
|
|
||||||
const prefersReducedMotion = window.matchMedia(
|
const prefersReducedMotion = window.matchMedia(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import type * as interfaces from '../../lib/interfaces.ts'
|
|
||||||
import { isAmazonURL, isGitHubURL } from '../../lib/blog-helpers.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 Caption from './Caption.astro'
|
||||||
|
import GithubLinkPreview from './GitHubLinkPreview.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
@@ -50,7 +50,6 @@ try {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -80,7 +79,9 @@ try {
|
|||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: background-color 160ms ease, border-color 160ms ease;
|
transition:
|
||||||
|
background-color 160ms ease,
|
||||||
|
border-color 160ms ease;
|
||||||
}
|
}
|
||||||
.bookmark > a:hover {
|
.bookmark > a:hover {
|
||||||
background: var(--notion-surface-strong);
|
background: var(--notion-surface-strong);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
||||||
import RichText from './RichText.astro'
|
|
||||||
import NotionBlocks from '../NotionBlocks.astro'
|
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
|
import NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
@@ -21,7 +21,8 @@ const listTypes = ['disc', 'circle', 'square']
|
|||||||
{
|
{
|
||||||
items
|
items
|
||||||
.filter(
|
.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) => {
|
.map((b: interfaces.Block) => {
|
||||||
const item = b.BulletedListItem
|
const item = b.BulletedListItem
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
||||||
import RichText from './RichText.astro'
|
|
||||||
import NotionBlocks from '../NotionBlocks.astro'
|
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
|
import NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ const displayLanguage =
|
|||||||
<figure class="code mac">
|
<figure class="code mac">
|
||||||
<div class="chrome">
|
<div class="chrome">
|
||||||
<div class="dots">
|
<div class="dots">
|
||||||
<span class="dot dot-red" />
|
<span class="dot dot-red"></span>
|
||||||
<span class="dot dot-amber" />
|
<span class="dot dot-amber"></span>
|
||||||
<span class="dot dot-green" />
|
<span class="dot dot-green"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span class="lang">{displayLanguage}</span>
|
<span class="lang">{displayLanguage}</span>
|
||||||
@@ -76,7 +76,8 @@ const displayLanguage =
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
const originalText = target.innerText
|
const originalText = target.innerText
|
||||||
|
|
||||||
target.innerText = target.getAttribute('data-done-text') || target.innerText
|
target.innerText =
|
||||||
|
target.getAttribute('data-done-text') || target.innerText
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
target.innerText = originalText
|
target.innerText = originalText
|
||||||
@@ -100,7 +101,8 @@ const displayLanguage =
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid color-mix(in oklab, var(--border) 70%, transparent);
|
border: 1px solid color-mix(in oklab, var(--border) 70%, transparent);
|
||||||
background: linear-gradient(
|
background:
|
||||||
|
linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
color-mix(in oklab, var(--muted) 12%, transparent),
|
color-mix(in oklab, var(--muted) 12%, transparent),
|
||||||
transparent 30%
|
transparent 30%
|
||||||
@@ -171,7 +173,9 @@ const displayLanguage =
|
|||||||
line-height: 1.2rem;
|
line-height: 1.2rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
transition: background-color 120ms ease, transform 120ms ease;
|
transition:
|
||||||
|
background-color 120ms ease,
|
||||||
|
transform 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code button.copy:hover {
|
.code button.copy:hover {
|
||||||
@@ -199,7 +203,11 @@ const displayLanguage =
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
scrollbar-width: thin;
|
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;
|
transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +248,15 @@ const displayLanguage =
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lang {
|
.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>
|
</style>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ if (!block.ColumnList) {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
gap: 0 1rem;
|
gap: 0 1rem;
|
||||||
}
|
}
|
||||||
.column-list > div {
|
.column-list > div {
|
||||||
flex: 1 1 180px;
|
flex: 1 1 180px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<hr class="divider" />
|
<hr class="divider" />
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
|
||||||
import {
|
import {
|
||||||
isTweetURL,
|
isCircuitSimulatorAppletURL,
|
||||||
isTikTokURL,
|
isCodePenURL,
|
||||||
isInstagramURL,
|
isInstagramURL,
|
||||||
isPinterestURL,
|
isPinterestURL,
|
||||||
isCodePenURL,
|
isTikTokURL,
|
||||||
isCircuitSimulatorAppletURL,
|
isTweetURL,
|
||||||
} from '../../lib/blog-helpers.ts'
|
} from '../../lib/blog-helpers.ts'
|
||||||
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import Bookmark from './Bookmark.astro'
|
import Bookmark from './Bookmark.astro'
|
||||||
import TweetEmbed from './TweetEmbed.astro'
|
import CircuitSimulatorAppletEmbed from './CircuitSimulatorAppletEmbed.astro'
|
||||||
import TikTokEmbed from './TikTokEmbed.astro'
|
import CodePenEmbed from './CodePenEmbed.astro'
|
||||||
import InstagramEmbed from './InstagramEmbed.astro'
|
import InstagramEmbed from './InstagramEmbed.astro'
|
||||||
import PinterestEmbed from './PinterestEmbed.astro'
|
import PinterestEmbed from './PinterestEmbed.astro'
|
||||||
import CodePenEmbed from './CodePenEmbed.astro'
|
import TikTokEmbed from './TikTokEmbed.astro'
|
||||||
import CircuitSimulatorAppletEmbed from './CircuitSimulatorAppletEmbed.astro'
|
import TweetEmbed from './TweetEmbed.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces'
|
|
||||||
import { filePath } from '../../lib/blog-helpers'
|
import { filePath } from '../../lib/blog-helpers'
|
||||||
|
import * as interfaces from '../../lib/interfaces'
|
||||||
import Caption from './Caption.astro'
|
import Caption from './Caption.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -26,8 +26,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filename =
|
const filename =
|
||||||
url &&
|
url && decodeURIComponent(url.pathname.split('/').slice(-1)[0] || '')
|
||||||
decodeURIComponent(url.pathname.split('/').slice(-1)[0] || '')
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="file">
|
<div class="file">
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ function buildEmbedQuery(params: EmbedParams) {
|
|||||||
return Object.entries(params)
|
return Object.entries(params)
|
||||||
.filter(([, v]) => v !== undefined)
|
.filter(([, v]) => v !== undefined)
|
||||||
.map(
|
.map(
|
||||||
([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`
|
([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`,
|
||||||
)
|
)
|
||||||
.join('&')
|
.join('&')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
|
||||||
import { buildHeadingId } from '../../lib/blog-helpers.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 NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
|
||||||
import { buildHeadingId } from '../../lib/blog-helpers.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 NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
|
||||||
import { buildHeadingId } from '../../lib/blog-helpers.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 NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { ENABLE_LIGHTBOX } from '@/consts'
|
import { ENABLE_LIGHTBOX } from '@/consts'
|
||||||
import * as interfaces from '../../lib/interfaces'
|
|
||||||
import { filePath } from '../../lib/blog-helpers'
|
import { filePath } from '../../lib/blog-helpers'
|
||||||
|
import * as interfaces from '../../lib/interfaces'
|
||||||
import Caption from './Caption.astro'
|
import Caption from './Caption.astro'
|
||||||
import fslightbox from 'fslightbox'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
@@ -22,7 +21,8 @@ if (block.Image.External) {
|
|||||||
image = filePath(new URL(block.Image.File.Url))
|
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">
|
<figure class="image">
|
||||||
@@ -31,7 +31,12 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
|
|||||||
<div class="image-wrapper">
|
<div class="image-wrapper">
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
{ENABLE_LIGHTBOX ? (
|
{ENABLE_LIGHTBOX ? (
|
||||||
<a data-fslightbox href={image} data-type="image" class="lightbox-link">
|
<a
|
||||||
|
data-fslightbox
|
||||||
|
href={image}
|
||||||
|
data-type="image"
|
||||||
|
class="lightbox-link"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={image}
|
src={image}
|
||||||
alt={altText}
|
alt={altText}
|
||||||
@@ -72,7 +77,9 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 0.5rem;
|
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 {
|
.image-container:hover {
|
||||||
@@ -94,8 +101,9 @@ const altText = block.Image.Caption?.map(c => c.Text.Content).join('') || 'Image
|
|||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-content[loading="lazy"] {
|
.image-content[loading='lazy'] {
|
||||||
background: linear-gradient(90deg,
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
hsl(var(--muted)) 0%,
|
hsl(var(--muted)) 0%,
|
||||||
hsl(var(--muted) / 0.8) 50%,
|
hsl(var(--muted) / 0.8) 50%,
|
||||||
hsl(var(--muted)) 100%
|
hsl(var(--muted)) 100%
|
||||||
|
|||||||
@@ -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 type { Post } from '../../lib/interfaces.ts'
|
||||||
import { getPostByPageId } from '../../lib/notion/client'
|
import { getPostByPageId } from '../../lib/notion/client'
|
||||||
import { getPostLink } from '../../lib/blog-helpers.ts'
|
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
import arrow from '../../images/icon-arrow-link.svg'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
pageId: string
|
pageId: string
|
||||||
@@ -22,18 +22,22 @@ if (pageId) {
|
|||||||
<a href={getPostLink(post.Slug)} class="link">
|
<a href={getPostLink(post.Slug)} class="link">
|
||||||
<>
|
<>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
{post.Icon && post.Icon.Type === 'emoji'
|
{post.Icon && post.Icon.Type === 'emoji' ? (
|
||||||
? post.Icon.Emoji
|
post.Icon.Emoji
|
||||||
: post.Icon && post.Icon.Type === 'external'
|
) : post.Icon && post.Icon.Type === 'external' ? (
|
||||||
? (
|
<img
|
||||||
<img
|
src={post.Icon.Url}
|
||||||
src={post.Icon.Url}
|
class="notion-icon"
|
||||||
class="notion-icon"
|
alt="Post title icon in a page link"
|
||||||
alt="Post title icon in a page link"
|
/>
|
||||||
/>
|
) : (
|
||||||
)
|
'📄'
|
||||||
: '📄'}
|
)}
|
||||||
<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>
|
||||||
<span class="text">{post.Title}</span>
|
<span class="text">{post.Title}</span>
|
||||||
</>
|
</>
|
||||||
@@ -42,7 +46,11 @@ if (pageId) {
|
|||||||
<a class="link">
|
<a class="link">
|
||||||
<span class="icon">
|
<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>
|
||||||
<span class="text not-found">Post not found</span>
|
<span class="text not-found">Post not found</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
||||||
import RichText from './RichText.astro'
|
|
||||||
import NotionBlocks from '../NotionBlocks.astro'
|
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
|
import NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
@@ -21,7 +21,8 @@ const items = block.ListItems ?? []
|
|||||||
{
|
{
|
||||||
items
|
items
|
||||||
.filter(
|
.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) => {
|
.map((b: interfaces.Block) => {
|
||||||
const item = b.NumberedListItem
|
const item = b.NumberedListItem
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
||||||
import RichText from './RichText.astro'
|
|
||||||
import NotionBlocks from '../NotionBlocks.astro'
|
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
|
import NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -11,5 +11,4 @@ const { url } = Astro.props
|
|||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
async
|
async
|
||||||
defer
|
defer
|
||||||
src="//assets.pinterest.com/js/pinit.js"
|
src="//assets.pinterest.com/js/pinit.js"></script>
|
||||||
></script>
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
||||||
import RichText from './RichText.astro'
|
|
||||||
import NotionBlocks from '../NotionBlocks.astro'
|
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
|
import NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
import type { RichText } from '../../lib/interfaces.ts'
|
import type { RichText } from '../../lib/interfaces.ts'
|
||||||
|
import Anchor from './annotations/Anchor.astro'
|
||||||
import Bold from './annotations/Bold.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 Italic from './annotations/Italic.astro'
|
||||||
import Strikethrough from './annotations/Strikethrough.astro'
|
import Strikethrough from './annotations/Strikethrough.astro'
|
||||||
import Underline from './annotations/Underline.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'
|
import Mention from './Mention.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -43,13 +43,13 @@ const { richText } = Astro.props
|
|||||||
{content}
|
{content}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
{richText.Equation && (
|
{richText.Equation && (
|
||||||
<span
|
<span
|
||||||
set:html={katex.renderToString(
|
set:html={katex.renderToString(
|
||||||
richText.Equation.Expression,
|
richText.Equation.Expression,
|
||||||
{ throwOnError: false }
|
{ throwOnError: false },
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import NotionBlocks from '../NotionBlocks.astro'
|
|
||||||
import type * as interfaces from '../../lib/interfaces'
|
import type * as interfaces from '../../lib/interfaces'
|
||||||
|
import NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
|
||||||
import { buildHeadingId } from '../../lib/blog-helpers.ts'
|
import { buildHeadingId } from '../../lib/blog-helpers.ts'
|
||||||
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ if (!block.TableOfContents) {
|
|||||||
)} ${indentClass}`}
|
)} ${indentClass}`}
|
||||||
>
|
>
|
||||||
{heading.RichTexts.map(
|
{heading.RichTexts.map(
|
||||||
(richText: interfaces.RichText) => richText.PlainText
|
(richText: interfaces.RichText) => richText.PlainText,
|
||||||
).join('')}
|
).join('')}
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
@@ -52,6 +52,9 @@ if (!block.TableOfContents) {
|
|||||||
}
|
}
|
||||||
.table-of-contents > a {
|
.table-of-contents > a {
|
||||||
display: block;
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
line-height: 1.8rem;
|
line-height: 1.8rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
||||||
import RichText from './RichText.astro'
|
|
||||||
import NotionBlocks from '../NotionBlocks.astro'
|
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
|
import NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
import { snakeToKebab } from '../../lib/style-helpers.ts'
|
||||||
import RichText from './RichText.astro'
|
|
||||||
import NotionBlocks from '../NotionBlocks.astro'
|
|
||||||
import '../../styles/notion-color.css'
|
import '../../styles/notion-color.css'
|
||||||
|
import NotionBlocks from '../NotionBlocks.astro'
|
||||||
|
import RichText from './RichText.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: interfaces.Block
|
block: interfaces.Block
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ const postURL =
|
|||||||
</blockquote>
|
</blockquote>
|
||||||
</div>
|
</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>
|
<style>
|
||||||
.tweet-embed {
|
.tweet-embed {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import * as interfaces from '../../lib/interfaces.ts'
|
|
||||||
import { isYouTubeURL, parseYouTubeVideoId } from '../../lib/blog-helpers.ts'
|
import { isYouTubeURL, parseYouTubeVideoId } from '../../lib/blog-helpers.ts'
|
||||||
|
import * as interfaces from '../../lib/interfaces.ts'
|
||||||
import Caption from './Caption.astro'
|
import Caption from './Caption.astro'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
|||||||
@@ -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',
|
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||||
outline:
|
outline:
|
||||||
'border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
'border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||||
muted:
|
muted: 'bg-muted text-foreground hover:bg-muted/80',
|
||||||
'bg-muted text-foreground hover:bg-muted/80',
|
ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50',
|
||||||
ghost:
|
|
||||||
'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50',
|
|
||||||
link: 'text-primary underline-offset-4 hover:underline',
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
|||||||
@@ -130,7 +130,8 @@ const PaginationComponent: React.FC<PaginationProps> = ({
|
|||||||
baseUrl,
|
baseUrl,
|
||||||
}) => {
|
}) => {
|
||||||
const buildPages = () => {
|
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>([
|
const pageSet = new Set<number>([
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ export const ENABLE_LIGHTBOX = true
|
|||||||
|
|
||||||
export const SITE: Site = {
|
export const SITE: Site = {
|
||||||
title: '溴化锂的笔记本',
|
title: '溴化锂的笔记本',
|
||||||
description:
|
description: 'Security, Programming, Life',
|
||||||
'Security, Programming, Life',
|
|
||||||
href: 'https://nvme0n1p.dev',
|
href: 'https://nvme0n1p.dev',
|
||||||
author: 'libr',
|
author: 'libr',
|
||||||
locale: 'zh-CN',
|
locale: 'zh-CN',
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import Footer from '@/components/Footer.astro'
|
|||||||
import Head from '@/components/Head.astro'
|
import Head from '@/components/Head.astro'
|
||||||
import Header from '@/components/Header.astro'
|
import Header from '@/components/Header.astro'
|
||||||
import { ENABLE_LIGHTBOX, SITE } from '@/consts'
|
import { ENABLE_LIGHTBOX, SITE } from '@/consts'
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { getStaticFilePath } from '@/lib/blog-helpers'
|
import { getStaticFilePath } from '@/lib/blog-helpers'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
@@ -38,11 +38,6 @@ const { class: className } = Astro.props
|
|||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
||||||
|
{ENABLE_LIGHTBOX && <script src={getStaticFilePath('/js/fslightbox.js')} />}
|
||||||
{
|
|
||||||
ENABLE_LIGHTBOX && (
|
|
||||||
<script src={getStaticFilePath('/js/fslightbox.js')} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -11,18 +11,19 @@ import { cn } from '@/lib/utils'
|
|||||||
<PageHead slot="head" title="500" />
|
<PageHead slot="head" title="500" />
|
||||||
<Breadcrumbs items={[{ label: 'Error', icon: 'lucide:server-off' }]} />
|
<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">
|
<div class="max-w-md">
|
||||||
<h1 class="mb-4 text-3xl font-medium">500: Something went wrong</h1>
|
<h1 class="mb-4 text-3xl font-medium">500: Something went wrong</h1>
|
||||||
<p class="prose">
|
<p class="prose">服务器爆炸了!如果你能联系到博主的话提醒他一声吧!</p>
|
||||||
服务器爆炸了!如果你能联系到博主的话提醒他一声吧!
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
class={cn(buttonVariants({ variant: 'outline' }), 'flex gap-x-1.5 group')}
|
class={cn(buttonVariants({ variant: 'outline' }), 'flex gap-x-1.5 group')}
|
||||||
>
|
>
|
||||||
<span class="transition-transform group-hover:-translate-x-1">←</span>
|
<span class="transition-transform group-hover:-translate-x-1">←</span
|
||||||
|
>
|
||||||
Go to home page
|
Go to home page
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||||
|
import Comment from '@/components/Comment.astro'
|
||||||
import Link from '@/components/Link.astro'
|
import Link from '@/components/Link.astro'
|
||||||
import PageHead from '@/components/PageHead.astro'
|
import PageHead from '@/components/PageHead.astro'
|
||||||
import Layout from '@/layouts/Layout.astro'
|
import Layout from '@/layouts/Layout.astro'
|
||||||
import Comment from '@/components/Comment.astro'
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout class="max-w-3xl">
|
<Layout class="max-w-3xl">
|
||||||
@@ -13,11 +13,11 @@ import Comment from '@/components/Comment.astro'
|
|||||||
<section class="grid gap-6">
|
<section class="grid gap-6">
|
||||||
<div class="relative rounded-lg border p-6">
|
<div class="relative rounded-lg border p-6">
|
||||||
<div class="relative flex flex-col gap-4">
|
<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
|
profile
|
||||||
</p>
|
</p>
|
||||||
<h1 class="text-3xl font-semibold">关于我</h1>
|
<h1 class="text-3xl font-semibold">关于我</h1>
|
||||||
<p class="max-w-3xl text-muted-foreground">
|
<p class="text-muted-foreground max-w-3xl">
|
||||||
啥都写一点,但是啥都不精通。前端后端客户端,运维网安都会那么一点点,尝试写出点什么项目但总是失败。
|
啥都写一点,但是啥都不精通。前端后端客户端,运维网安都会那么一点点,尝试写出点什么项目但总是失败。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,19 +26,19 @@ import Comment from '@/components/Comment.astro'
|
|||||||
<div class="grid gap-4 md:grid-cols-3">
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
<div class="rounded-lg border p-5">
|
<div class="rounded-lg border p-5">
|
||||||
<h3 class="text-lg font-semibold">安全</h3>
|
<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,啥都做点,(感觉更像是全栈?)
|
非典型 Misc,啥都做点,(感觉更像是全栈?)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border p-5">
|
<div class="rounded-lg border p-5">
|
||||||
<h3 class="text-lg font-semibold">代码</h3>
|
<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
|
近期在写 C 和 Swift,之前写过 Golang、Python、JavaScript
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border p-5">
|
<div class="rounded-lg border p-5">
|
||||||
<h3 class="text-lg font-semibold">生活</h3>
|
<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、摄影
|
二次元、骑车、游泳、羽毛球、CS、摄影
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,41 +47,45 @@ import Comment from '@/components/Comment.astro'
|
|||||||
<div class="rounded-lg border p-6">
|
<div class="rounded-lg border p-6">
|
||||||
<div class="flex items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div>
|
<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>
|
<h3 class="text-xl font-semibold">常用设备</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 grid gap-3 md:grid-cols-2">
|
<div class="mt-4 grid gap-3 md:grid-cols-2">
|
||||||
<div class="rounded-lg border p-4">
|
<div class="rounded-lg border p-4">
|
||||||
<p class="text-sm font-semibold">PC</p>
|
<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
|
M2 Macbook Air · MSI Stealth 14 · iPad Air 5
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border p-4">
|
<div class="rounded-lg border p-4">
|
||||||
<p class="text-sm font-semibold">手机</p>
|
<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>
|
||||||
<div class="rounded-lg border p-4 md:col-span-2">
|
<div class="rounded-lg border p-4 md:col-span-2">
|
||||||
<p class="text-sm font-semibold">影音与记录</p>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="rounded-lg border p-6">
|
<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>
|
<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://astro.build" external underline>Astro</Link>,
|
||||||
<Link href="https://tailwindcss.com" external underline>Tailwind</Link>,
|
<Link href="https://tailwindcss.com" external underline>Tailwind</Link>,
|
||||||
<Link href="https://ui.shadcn.com" external underline>shadcn/ui</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>
|
</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">
|
<div class="mt-3 overflow-x-auto rounded-lg border">
|
||||||
<table class="w-full text-sm">
|
<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>
|
<tr>
|
||||||
<th class="px-4 py-2 font-semibold">时间</th>
|
<th class="px-4 py-2 font-semibold">时间</th>
|
||||||
<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>
|
<tr>
|
||||||
<td class="px-4 py-2 whitespace-nowrap">2018</td>
|
<td class="px-4 py-2 whitespace-nowrap">2018</td>
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
第一次建站,域名 <code>stevelbr.ga</code>,使用 hexo 和不知道有几种主题
|
第一次建站,域名 <code>stevelbr.ga</code>,使用 hexo
|
||||||
|
和不知道有几种主题
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-2 whitespace-nowrap">2020</td>
|
<td class="px-4 py-2 whitespace-nowrap">2020</td>
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
购入腾讯云轻量服务器,购入域名 <code>stevelbr.top</code> ,使用 typecho 和
|
购入腾讯云轻量服务器,购入域名 <code>stevelbr.top</code> ,使用 typecho
|
||||||
typecho-theme-handsome 建站,接入备案。
|
和 typecho-theme-handsome 建站,接入备案。
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-2 whitespace-nowrap">2023</td>
|
<td class="px-4 py-2 whitespace-nowrap">2023</td>
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
停止续费腾讯云轻量,购入域名 <code>nvme0n1p.dev</code>,使用 hugo+serverless service 建站。
|
停止续费腾讯云轻量,购入域名 <code>nvme0n1p.dev</code>,使用
|
||||||
|
hugo+serverless service 建站。
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-2 whitespace-nowrap">2024.1</td>
|
<td class="px-4 py-2 whitespace-nowrap">2024.1</td>
|
||||||
<td class="px-4 py-2">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -128,18 +136,24 @@ import Comment from '@/components/Comment.astro'
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-2 whitespace-nowrap">2024.10</td>
|
<td class="px-4 py-2 whitespace-nowrap">2024.10</td>
|
||||||
<td class="px-4 py-2">
|
<td class="px-4 py-2">
|
||||||
优化 nuxtjs 代码,现在全站支持无刷新页面切换。<del>同时增加 algolia docsearch 魔改版作为站内搜索。</del>好像没开 Server Side Rendering ,炸了
|
优化 nuxtjs 代码,现在全站支持无刷新页面切换。<del
|
||||||
|
>同时增加 algolia docsearch 魔改版作为站内搜索。</del
|
||||||
|
>好像没开 Server Side Rendering ,炸了
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-2 whitespace-nowrap">2024.11</td>
|
<td class="px-4 py-2 whitespace-nowrap">2024.11</td>
|
||||||
<td class="px-4 py-2">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-2 whitespace-nowrap">2025.2</td>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-2 whitespace-nowrap">2025.7</td>
|
<td class="px-4 py-2 whitespace-nowrap">2025.7</td>
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
---
|
---
|
||||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||||
|
import Comment from '@/components/Comment.astro'
|
||||||
import Link from '@/components/Link.astro'
|
import Link from '@/components/Link.astro'
|
||||||
|
import NotionBlocks from '@/components/NotionBlocks.astro'
|
||||||
import PostHead from '@/components/PostHead.astro'
|
import PostHead from '@/components/PostHead.astro'
|
||||||
import PostNavigation from '@/components/PostNavigation.astro'
|
import PostNavigation from '@/components/PostNavigation.astro'
|
||||||
import SubpostsHeader from '@/components/SubpostsHeader.astro'
|
import SubpostsHeader from '@/components/SubpostsHeader.astro'
|
||||||
import SubpostsSidebar from '@/components/SubpostsSidebar.astro'
|
import SubpostsSidebar from '@/components/SubpostsSidebar.astro'
|
||||||
import TOCHeader from '@/components/TOCHeader.astro'
|
import TOCHeader from '@/components/TOCHeader.astro'
|
||||||
import TOCSidebar from '@/components/TOCSidebar.astro'
|
import TOCSidebar from '@/components/TOCSidebar.astro'
|
||||||
import NotionBlocks from '@/components/NotionBlocks.astro'
|
|
||||||
import { badgeVariants } from '@/components/ui/badge'
|
import { badgeVariants } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import Layout from '@/layouts/Layout.astro'
|
import Layout from '@/layouts/Layout.astro'
|
||||||
|
import type { TOCSection } from '@/lib/data-utils'
|
||||||
import {
|
import {
|
||||||
|
fetchRemotePostContent,
|
||||||
getAdjacentPosts,
|
getAdjacentPosts,
|
||||||
getAllPostsAndSubposts,
|
getAllPostsAndSubposts,
|
||||||
getParentId,
|
getParentId,
|
||||||
getParentPost,
|
getParentPost,
|
||||||
getSubpostCount,
|
getSubpostCount,
|
||||||
getTOCSections,
|
getTOCSections,
|
||||||
fetchRemotePostContent,
|
|
||||||
renderRemoteBlockMap,
|
|
||||||
hasSubposts,
|
hasSubposts,
|
||||||
isSubpost,
|
isSubpost,
|
||||||
parseAuthors,
|
parseAuthors,
|
||||||
|
renderRemoteBlockMap,
|
||||||
} from '@/lib/data-utils'
|
} from '@/lib/data-utils'
|
||||||
import type { TOCSection } from '@/lib/data-utils'
|
|
||||||
import Comment from '@/components/Comment.astro'
|
|
||||||
import { formatDate } from '@/lib/utils'
|
import { formatDate } from '@/lib/utils'
|
||||||
import { Icon } from 'astro-icon/components'
|
import { Icon } from 'astro-icon/components'
|
||||||
import { Image } from 'astro:assets'
|
import { Image } from 'astro:assets'
|
||||||
import { render } from 'astro:content'
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = await getAllPostsAndSubposts()
|
const posts = await getAllPostsAndSubposts()
|
||||||
@@ -71,9 +70,7 @@ try {
|
|||||||
parentPost = isCurrentSubpost ? await getParentPost(currentPostId) : null
|
parentPost = isCurrentSubpost ? await getParentPost(currentPostId) : null
|
||||||
|
|
||||||
hasChildPosts = await hasSubposts(currentPostId)
|
hasChildPosts = await hasSubposts(currentPostId)
|
||||||
subpostCount = !isCurrentSubpost
|
subpostCount = !isCurrentSubpost ? await getSubpostCount(currentPostId) : 0
|
||||||
? await getSubpostCount(currentPostId)
|
|
||||||
: 0
|
|
||||||
|
|
||||||
tocSections = remoteContent
|
tocSections = remoteContent
|
||||||
? remoteContent.headings.length > 0
|
? remoteContent.headings.length > 0
|
||||||
@@ -91,7 +88,7 @@ try {
|
|||||||
console.error('blog page render failed:', error)
|
console.error('blog page render failed:', error)
|
||||||
return Astro.rewrite('/500')
|
return Astro.rewrite('/500')
|
||||||
}
|
}
|
||||||
export const prerender = false;
|
export const prerender = false
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import PageHead from '@/components/PageHead.astro'
|
|||||||
import PaginationComponent from '@/components/ui/pagination'
|
import PaginationComponent from '@/components/ui/pagination'
|
||||||
import { SITE } from '@/consts'
|
import { SITE } from '@/consts'
|
||||||
import Layout from '@/layouts/Layout.astro'
|
import Layout from '@/layouts/Layout.astro'
|
||||||
import { getAllPosts, groupPostsByYear, normalizePost } from '@/lib/data-utils'
|
import { groupPostsByYear, normalizePost } from '@/lib/data-utils'
|
||||||
import type { PaginateFunction } from 'astro'
|
|
||||||
|
|
||||||
// export async function getStaticPaths({
|
// export async function getStaticPaths({
|
||||||
// paginate,
|
// paginate,
|
||||||
@@ -16,33 +15,37 @@ import type { PaginateFunction } from 'astro'
|
|||||||
// const allPosts = await getAllPosts()
|
// const allPosts = await getAllPosts()
|
||||||
// return paginate(allPosts, { pageSize: SITE.postsPerPage })
|
// 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 } = 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) => {
|
.then((res) => {
|
||||||
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`);
|
if (!res.ok) throw new Error(`Failed to fetch posts: ${res.status}`)
|
||||||
return res.json();
|
return res.json()
|
||||||
})
|
})
|
||||||
.then((allPosts) => {
|
.then((allPosts) => {
|
||||||
const totalPages = allPosts.length;
|
const totalPages = allPosts.length
|
||||||
const currentPage = nowPage;
|
const currentPage = nowPage
|
||||||
return {
|
return {
|
||||||
data: allPosts.posts.map((post) => normalizePost(post)),
|
data: allPosts.posts.map((post) => normalizePost(post)),
|
||||||
currentPage,
|
currentPage,
|
||||||
lastPage: totalPages,
|
lastPage: totalPages,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
// fetch page
|
// fetch page
|
||||||
if(page.lastPage < page.currentPage) {
|
if (page.lastPage < page.currentPage) {
|
||||||
console.log("redirect to 404")
|
console.log('redirect to 404')
|
||||||
return Astro.rewrite("/404")
|
return Astro.rewrite('/404')
|
||||||
}
|
}
|
||||||
const postsByYear = groupPostsByYear(page.data)
|
const postsByYear = groupPostsByYear(page.data)
|
||||||
const years = Object.keys(postsByYear).sort((a, b) => parseInt(b) - parseInt(a))
|
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">
|
<Layout class="max-w-3xl">
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
---
|
---
|
||||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||||
|
import Comment from '@/components/Comment.astro'
|
||||||
import Link from '@/components/Link.astro'
|
import Link from '@/components/Link.astro'
|
||||||
import PageHead from '@/components/PageHead.astro'
|
import PageHead from '@/components/PageHead.astro'
|
||||||
import Layout from '@/layouts/Layout.astro'
|
import Layout from '@/layouts/Layout.astro'
|
||||||
import {
|
import { getFriendLinks } from '@/lib/data-utils'
|
||||||
getFriendLinks,
|
|
||||||
type LinkEntry,
|
|
||||||
} from '@/lib/data-utils'
|
|
||||||
import Comment from '@/components/Comment.astro'
|
|
||||||
const placeholderAvatar = '/avatar-placeholder.svg'
|
const placeholderAvatar = '/avatar-placeholder.svg'
|
||||||
const visibleLinks = await getFriendLinks()
|
const visibleLinks = await getFriendLinks()
|
||||||
---
|
---
|
||||||
@@ -19,48 +16,44 @@ const visibleLinks = await getFriendLinks()
|
|||||||
<section class="rounded-lg border p-6">
|
<section class="rounded-lg border p-6">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h1 class="text-2xl font-semibold">友链 / Friends</h1>
|
<h1 class="text-2xl font-semibold">友链 / Friends</h1>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">这里收集一些朋友和有趣的网站</p>
|
||||||
这里收集一些朋友和有趣的网站
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="grid gap-4">
|
<section class="grid gap-4">
|
||||||
{
|
{
|
||||||
visibleLinks.map((friend) => (
|
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">
|
<div class="flex items-start gap-3">
|
||||||
{
|
{friend.picLink ? (
|
||||||
friend.picLink ? (
|
<img
|
||||||
<img
|
src={friend.picLink}
|
||||||
src={friend.picLink}
|
alt={friend.name}
|
||||||
alt={friend.name}
|
loading="lazy"
|
||||||
loading="lazy"
|
class="h-12 w-12 rounded-full border object-cover"
|
||||||
class="h-12 w-12 rounded-full border object-cover"
|
width="48"
|
||||||
width="48"
|
height="48"
|
||||||
height="48"
|
onerror={`this.onerror=null;this.src='${placeholderAvatar}';`}
|
||||||
onerror={`this.onerror=null;this.src='${placeholderAvatar}';`}
|
/>
|
||||||
/>
|
) : (
|
||||||
) : (
|
<img
|
||||||
<img
|
src={placeholderAvatar}
|
||||||
src={placeholderAvatar}
|
alt="avatar placeholder"
|
||||||
alt="avatar placeholder"
|
loading="lazy"
|
||||||
loading="lazy"
|
class="h-12 w-12 rounded-full border object-cover"
|
||||||
class="h-12 w-12 rounded-full border object-cover"
|
width="48"
|
||||||
width="48"
|
height="48"
|
||||||
height="48"
|
/>
|
||||||
/>
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
<div class="flex-1 space-y-1">
|
<div class="flex-1 space-y-1">
|
||||||
<div class="flex items-center justify-between gap-3">
|
<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>
|
<Link href={friend.links} external underline>
|
||||||
{friend.name}
|
{friend.name}
|
||||||
</Link>
|
</Link>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-muted-foreground text-sm">
|
||||||
{friend.Description || '你来到了知识的荒原'}
|
{friend.Description || '你来到了知识的荒原'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,10 +65,11 @@ const visibleLinks = await getFriendLinks()
|
|||||||
|
|
||||||
<section class="rounded-lg border p-6">
|
<section class="rounded-lg border p-6">
|
||||||
<h3 class="text-lg font-semibold">申请方式</h3>
|
<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>
|
</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: 溴化锂的笔记本
|
name: 溴化锂的笔记本
|
||||||
link: https://nvme0n1p.dev
|
link: https://nvme0n1p.dev
|
||||||
avatar: https://gravatar.com/avatar/29d64df3ca2a9dac5a7fffa5372fb80fb3270ceb223de2af0c33cdc4b2cbe954?v=1687917579000&size=256&d=initials
|
avatar: https://gravatar.com/avatar/29d64df3ca2a9dac5a7fffa5372fb80fb3270ceb223de2af0c33cdc4b2cbe954?v=1687917579000&size=256&d=initials
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ const blog = await getRecentPosts(SITE.featuredPostCount)
|
|||||||
<section class="rounded-lg border">
|
<section class="rounded-lg border">
|
||||||
<div class="flex flex-col space-y-1.5 p-6">
|
<div class="flex flex-col space-y-1.5 p-6">
|
||||||
<h3 class="text-3xl leading-none font-medium">溴化锂</h3>
|
<h3 class="text-3xl leading-none font-medium">溴化锂</h3>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">LiBr</p>
|
||||||
LiBr
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6 pt-0">
|
<div class="p-6 pt-0">
|
||||||
<p class="text-muted-foreground mb-2 text-sm">
|
<p class="text-muted-foreground mb-2 text-sm">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function getStaticPaths() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { tag, posts } = Astro.props
|
const { tag, posts } = Astro.props
|
||||||
export const prerender = false;
|
export const prerender = false
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout class="max-w-3xl">
|
<Layout class="max-w-3xl">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { getSortedTags } from '@/lib/data-utils'
|
|||||||
import { Icon } from 'astro-icon/components'
|
import { Icon } from 'astro-icon/components'
|
||||||
|
|
||||||
const sortedTags = await getSortedTags()
|
const sortedTags = await getSortedTags()
|
||||||
export const prerender = false;
|
export const prerender = false
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout class="max-w-3xl">
|
<Layout class="max-w-3xl">
|
||||||
|
|||||||
@@ -44,13 +44,37 @@
|
|||||||
--notion-red: color-mix(in oklab, var(--foreground) 82%, #d44c47);
|
--notion-red: color-mix(in oklab, var(--foreground) 82%, #d44c47);
|
||||||
|
|
||||||
--notion-gray-background: color-mix(in oklab, var(--background) 82%, #f1f1ef);
|
--notion-gray-background: color-mix(in oklab, var(--background) 82%, #f1f1ef);
|
||||||
--notion-brown-background: color-mix(in oklab, var(--background) 82%, #f4eeee);
|
--notion-brown-background: color-mix(
|
||||||
--notion-orange-background: color-mix(in oklab, var(--background) 82%, #fbecdd);
|
in oklab,
|
||||||
--notion-yellow-background: color-mix(in oklab, var(--background) 82%, #fbf3db);
|
var(--background) 82%,
|
||||||
--notion-green-background: color-mix(in oklab, var(--background) 82%, #edf3ec);
|
#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-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-purple-background: color-mix(
|
||||||
--notion-pink-background: color-mix(in oklab, var(--background) 82%, rgba(249, 238, 243, 0.8));
|
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-red-background: color-mix(in oklab, var(--background) 82%, #fdebec);
|
||||||
|
|
||||||
--notion-tag-foreground: var(--foreground);
|
--notion-tag-foreground: var(--foreground);
|
||||||
|
|||||||
Reference in New Issue
Block a user