mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-16 19:51:07 +02:00
feat: enhance post listing and reading time display
- Implement dynamic post listing grouped by year with improved date formatting - Add reading time display for each post - Update index pages for both default and localized routes - Modify content utility functions to support reading time metadata - Refactor global styles and type definitions to support new features
This commit is contained in:
parent
e9e318e02d
commit
ae39d7b08c
18 changed files with 558 additions and 117 deletions
|
@ -2,9 +2,9 @@
|
||||||
import themeConfig from '@/config'
|
import themeConfig from '@/config'
|
||||||
|
|
||||||
const { title, subtitle } = themeConfig.site
|
const { title, subtitle } = themeConfig.site
|
||||||
const { titleSpace = 2 } = themeConfig.global
|
const { titleSpace } = themeConfig.global
|
||||||
|
|
||||||
const marginClass = {
|
const marginBottom = {
|
||||||
1: 'mb-1',
|
1: 'mb-1',
|
||||||
2: 'mb-2',
|
2: 'mb-2',
|
||||||
3: 'mb-3',
|
3: 'mb-3',
|
||||||
|
@ -13,18 +13,13 @@ const marginClass = {
|
||||||
---
|
---
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1
|
<h1 class={`${marginBottom} mt--5.2 text-12 c-primary font-bold font-title`}>
|
||||||
class={`${marginClass} mt--5.2 text-12 c-primary font-bold font-title`}
|
|
||||||
aria-label="Title Tag"
|
|
||||||
>
|
|
||||||
<a href="/">
|
<a href="/">
|
||||||
{title}
|
{title}
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<h2
|
|
||||||
class="text-5.6 c-secondary font-navbar"
|
<h2 class="text-5.6 c-secondary font-navbar">
|
||||||
aria-label="Meta Description"
|
|
||||||
>
|
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</h2>
|
</h2>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -6,26 +6,30 @@ const langs = ['', ...themeConfig.global.moreLocale]
|
||||||
const currentLocale = themeConfig.global.locale
|
const currentLocale = themeConfig.global.locale
|
||||||
|
|
||||||
function getLanguageDisplayName(code: string) {
|
function getLanguageDisplayName(code: string) {
|
||||||
if (!code)
|
if (!code) {
|
||||||
return 'Default'
|
return 'Default'
|
||||||
|
}
|
||||||
return new Intl.DisplayNames(['en'], { type: 'language' }).of(code) || code
|
return new Intl.DisplayNames(['en'], { type: 'language' }).of(code) || code
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
id="language-switcher"
|
id="language-switcher"
|
||||||
class="absolute right-25.6 top-20 z-99 aspect-square w-6.6 c-secondary active:scale-92"
|
class="absolute right-25.6 top-20 z-9 aspect-square w-6.6 c-secondary active:scale-90"
|
||||||
aria-label="Switch Language"
|
aria-label={`Current Language: ${getLanguageDisplayName(currentLocale)}. Click to switch to next language.`}
|
||||||
title={`Current Language: ${getLanguageDisplayName(currentLocale)}`}
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
class="h-full w-full"
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-full w-full">
|
|
||||||
<path d="M19 21 12.3 2h-1L4.7 21l-2.5.2v.8h6.3v-.8L5.7 21l2-5.9h7.5l2 5.9-3.3.2v.8h7.9v-.8zM8 14.3l3.4-10.1 3.5 10.1z" fill="currentColor" />
|
<path d="M19 21 12.3 2h-1L4.7 21l-2.5.2v.8h6.3v-.8L5.7 21l2-5.9h7.5l2 5.9-3.3.2v.8h7.9v-.8zM8 14.3l3.4-10.1 3.5 10.1z" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="sr-only">Switch Language</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<script is:inline define:vars={{ langs }}>
|
<script is:inline define:vars={{ langs }}>
|
||||||
// Move event binding into astro:page-load event
|
|
||||||
document.addEventListener('astro:page-load', () => {
|
document.addEventListener('astro:page-load', () => {
|
||||||
const langSwitch = document.getElementById('language-switcher')
|
const langSwitch = document.getElementById('language-switcher')
|
||||||
|
|
||||||
|
@ -33,24 +37,32 @@ document.addEventListener('astro:page-load', () => {
|
||||||
const { pathname, search, hash } = window.location
|
const { pathname, search, hash } = window.location
|
||||||
const segments = pathname.split('/').filter(Boolean)
|
const segments = pathname.split('/').filter(Boolean)
|
||||||
const firstSegment = segments[0] || ''
|
const firstSegment = segments[0] || ''
|
||||||
// Check if first segment is a valid language code
|
|
||||||
const currentLang = langs.includes(firstSegment) ? firstSegment : ''
|
// Get current language or empty string if invalid
|
||||||
// Get next language in rotation (empty string means default locale)
|
const currentLang = langs.includes(firstSegment)
|
||||||
|
? firstSegment
|
||||||
|
: ''
|
||||||
|
|
||||||
const currentIndex = langs.indexOf(currentLang)
|
const currentIndex = langs.indexOf(currentLang)
|
||||||
const nextLang = langs[(currentIndex + 1) % langs.length]
|
const nextLang = langs[(currentIndex + 1) % langs.length]
|
||||||
|
|
||||||
const newPath = buildNewPath(currentLang, nextLang, segments, pathname) || '/'
|
const newPath = buildNewPath(currentLang, nextLang, segments, pathname) || '/'
|
||||||
window.location.href = `${newPath}${search}${hash}`
|
window.location.href = `${newPath}${search}${hash}`
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle path construction for both cases:
|
|
||||||
// 1. Current path has language prefix
|
|
||||||
// 2. Current path has no language prefix
|
|
||||||
function buildNewPath(currentLang, nextLang, segments, pathname) {
|
function buildNewPath(currentLang, nextLang, segments, pathname) {
|
||||||
if (currentLang) {
|
if (currentLang) {
|
||||||
segments[0] = nextLang || segments[0]
|
segments[0] = nextLang || segments[0]
|
||||||
return nextLang ? `/${segments.join('/')}` : `/${segments.slice(1).join('/')}`
|
|
||||||
|
// Handle path with or without language code
|
||||||
|
return nextLang
|
||||||
|
? `/${segments.join('/')}`
|
||||||
|
: `/${segments.slice(1).join('/')}`
|
||||||
}
|
}
|
||||||
return nextLang ? `/${nextLang}${pathname}` : pathname
|
|
||||||
|
return nextLang
|
||||||
|
? `/${nextLang}${pathname}`
|
||||||
|
: pathname
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -51,13 +51,13 @@ const isTagActive = isTagPage(currentPath)
|
||||||
const isAboutActive = isAboutPage(currentPath)
|
const isAboutActive = isAboutPage(currentPath)
|
||||||
---
|
---
|
||||||
|
|
||||||
<nav class="mb-20 mt-13 text-5.6 text-secondary font-semibold leading-13 font-navbar">
|
<nav class="mb-16 mt-13 text-5.6 font-semibold leading-13 font-navbar">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href={getLocalizedPath('/')}
|
href={getLocalizedPath('/')}
|
||||||
class:list={[
|
class:list={[
|
||||||
isPostActive ? 'font-bold text-primary' : 'text-secondary',
|
isPostActive && 'font-bold text-primary'
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{currentUI.posts}
|
{currentUI.posts}
|
||||||
|
@ -67,7 +67,7 @@ const isAboutActive = isAboutPage(currentPath)
|
||||||
<a
|
<a
|
||||||
href={getLocalizedPath('/tags')}
|
href={getLocalizedPath('/tags')}
|
||||||
class:list={[
|
class:list={[
|
||||||
isTagActive ? 'font-bold text-primary' : 'text-secondary',
|
isTagActive && 'font-bold text-primary'
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{currentUI.tags}
|
{currentUI.tags}
|
||||||
|
@ -77,7 +77,7 @@ const isAboutActive = isAboutPage(currentPath)
|
||||||
<a
|
<a
|
||||||
href={getLocalizedPath('/about')}
|
href={getLocalizedPath('/about')}
|
||||||
class:list={[
|
class:list={[
|
||||||
isAboutActive ? 'font-bold text-primary' : 'text-secondary',
|
isAboutActive && 'font-bold text-primary'
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{currentUI.about}
|
{currentUI.about}
|
||||||
|
|
|
@ -25,7 +25,7 @@ function toggleTheme() {
|
||||||
// Execute transition
|
// Execute transition
|
||||||
const transition = document.startViewTransition(() => {
|
const transition = document.startViewTransition(() => {
|
||||||
switchTheme()
|
switchTheme()
|
||||||
}) as any
|
})
|
||||||
|
|
||||||
// Clean up after transition
|
// Clean up after transition
|
||||||
transition.finished.then(() => {
|
transition.finished.then(() => {
|
||||||
|
@ -63,9 +63,13 @@ window.addEventListener('pageshow', (event) => {
|
||||||
<button
|
<button
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
aria-label="Theme Toggle Button"
|
aria-label="Theme Toggle Button"
|
||||||
class="absolute right-9.6 top-19.8 z-99 aspect-square w-7 c-secondary active:scale-92"
|
class="absolute right-9.6 top-19.8 z-9 aspect-square w-7 c-secondary active:scale-92"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
|
||||||
<path d="m12 1c-6.1 0-11 4.9-11 11s4.9 11 11 11 11-4.9 11-11-4.9-11-11-11m0 20c-5.8 0-10.5-4-10.5-9s4.7-9 10.5-9 10.5 4 10.5 9-4.7 9-10.5 9" />
|
<path d="m12 1c-6.1 0-11 4.9-11 11s4.9 11 11 11 11-4.9 11-11-4.9-11-11-11m0 20c-5.8 0-10.5-4-10.5-9s4.7-9 10.5-9 10.5 4 10.5 9-4.7 9-10.5 9" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -16,16 +16,16 @@ export const themeConfig: ThemeConfig = {
|
||||||
color: {
|
color: {
|
||||||
mode: 'light', // light, dark. Matches system theme by default, falls back to configured theme mode if not available.
|
mode: 'light', // light, dark. Matches system theme by default, falls back to configured theme mode if not available.
|
||||||
light: {
|
light: {
|
||||||
primary: '#17191A', // title text color in light mode
|
primary: '#17191A', // title font color
|
||||||
secondary: '#505050', // post text color in light mode
|
secondary: '#505050', // post font color
|
||||||
background: '#FAEDE4', // background color in light mode
|
background: '#FAEDE4', // background color
|
||||||
codeTheme: 'github-light', // code block theme in light mode. See more at https://shiki.style/themes and https://vscodethemes.com/
|
codeTheme: 'github-light', // code block theme. See more at https://shiki.style/themes and https://vscodethemes.com/
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
primary: '#BEBEBE', // title text color in dark mode
|
primary: '#BEBEBE', // title font color
|
||||||
secondary: '#A0A09F', // post text color in dark mode
|
secondary: '#A0A09F', // post font color
|
||||||
background: '#161616', // background color in dark mode
|
background: '#161616', // background color
|
||||||
codeTheme: 'github-dark', // code block theme in dark mode. See more at https://shiki.style/themes and https://vscodethemes.com/
|
codeTheme: 'github-dark', // code block theme. See more at https://shiki.style/themes and https://vscodethemes.com/
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// COLOR SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
|
// COLOR SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
|
||||||
|
|
185
src/content/posts/hometown copy 2.md
Normal file
185
src/content/posts/hometown copy 2.md
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
---
|
||||||
|
title: 漫记京都街巷富士山下与 Collector
|
||||||
|
published: 2025-01-18
|
||||||
|
tags: ["鲁迅"]
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
我冒了严寒,回到相隔二千余里,别了二十余年的故乡去。
|
||||||
|
|
||||||
|
时候既然是深冬;渐近故乡时,天气又阴晦了,冷风吹进船舱中,呜呜的响,从蓬隙向外一望,苍黄的天底下,远近横着几个萧索的荒村,没有一些活气。我的心禁不住悲凉起来了。
|
||||||
|
|
||||||
|
阿!这不是我二十年来时时记得的故乡?
|
||||||
|
|
||||||
|
我所记得的故乡全不如此。我的故乡好得多了。但要我记起他的美丽,说出他的佳处来,却又没有影像,没有言辞了。仿佛也就如此。于是我自己解释说:故乡本也如此,——虽然没有进步,也未必有如我所感的悲凉,这只是我自己心情的改变罢了,因为我这次回乡,本没有什么好心绪。
|
||||||
|
|
||||||
|
我这次是专为了别他而来的。我们多年聚族而居的老屋,已经公同卖给别姓了,交屋的期限,只在本年,所以必须赶在正月初一以前,永别了熟识的老屋,而且远离了熟识的故乡,搬家到我在谋食的异地去。
|
||||||
|
|
||||||
|
第二日清早晨我到了我家的门口了。瓦楞上许多枯草的断茎当风抖着,正在说明这老屋难免易主的原因。几房的本家大约已经搬走了,所以很寂静。我到了自家的房外,我的母亲早已迎着出来了,接着便飞出了八岁的侄儿宏儿。
|
||||||
|
|
||||||
|
我的母亲很高兴,但也藏着许多凄凉的神情,教我坐下,歇息,喝茶,且不谈搬家的事。宏儿没有见过我,远远的对面站着只是看。
|
||||||
|
|
||||||
|
但我们终于谈到搬家的事。我说外间的寓所已经租定了,又买了几件家具,此外须将家里所有的木器卖去,再去增添。母亲也说好,而且行李也略已齐集,木器不便搬运的,也小半卖去了,只是收不起钱来。
|
||||||
|
|
||||||
|
「你休息一两天,去拜望亲戚本家一回,我们便可以走了。」母亲说。
|
||||||
|
|
||||||
|
「是的。」
|
||||||
|
|
||||||
|
「还有闰土,他每到我家来时,总问起你,很想见你一回面。我已经将你到家的大约日期通知他,他也许就要来了。」
|
||||||
|
|
||||||
|
这时候,我的脑里忽然闪出一幅神异的图画来:深蓝的天空中挂着一轮金黄的圆月,下面是海边的沙地,都种著一望无际的碧绿的西瓜,其间有一个十一二岁的少年,项带银圈,手捏一柄钢叉,向一匹猹尽力的刺去,那猹却将身一扭,反从他的胯下逃走了。
|
||||||
|
|
||||||
|
这少年便是闰土。我认识他时,也不过十多岁,离现在将有三十年了;那时我的父亲还在世,家景也好,我正是一个少爷。那一年,我家是一件大祭祀的值年。这祭祀,说是三十多年才能轮到一回,所以很郑重;正月里供祖像,供品很多,祭器很讲究,拜的人也很多,祭器也很要防偷去。我家只有一个忙月(我们这里给人做工的分三种:整年给一定人家做工的叫长工;按日给人做工的叫短工;自己也种地,只在过年过节以及收租时候来给一定人家做工的称忙月),忙不过来,他便对父亲说,可以叫他的儿子闰土来管祭器的。
|
||||||
|
|
||||||
|
我的父亲允许了;我也很高兴,因为我早听到闰土这名字,而且知道他和我仿佛年纪,闰月生的,五行缺土,所以他的父亲叫他闰土。他是能装弶捉小鸟雀的。
|
||||||
|
|
||||||
|
我于是日日盼望新年,新年到,闰土也就到了。好容易到了年末,有一日,母亲告诉我,闰土来了,我便飞跑的去看。他正在厨房里,紫色的圆脸,头戴一顶小毡帽,颈上套一个明晃晃的银项圈,这可见他的父亲十分爱他,怕他死去,所以在神佛面前许下愿心,用圈子将他套住了。他见人很怕羞,只是不怕我,没有旁人的时候,便和我说话,于是不到半日,我们便熟识了。
|
||||||
|
|
||||||
|
我们那时候不知道谈些什么,只记得闰土很高兴,说是上城之后,见了许多没有见过的东西。
|
||||||
|
|
||||||
|
第二日,我便要他捕鸟。他说:
|
||||||
|
|
||||||
|
“这不能。须大雪下了才好。我们沙地上,下了雪,我扫出一块空地来,用短棒支起一个大竹匾,撒下秕谷,看鸟雀来吃时,我远远地将缚在棒上的绳子只一拉,那鸟雀就罩在竹匾下了。什么都有:稻鸡,角鸡,鹁鸪,蓝背……”
|
||||||
|
|
||||||
|
我于是又很盼望下雪。
|
||||||
|
|
||||||
|
闰土又对我说:
|
||||||
|
|
||||||
|
“现在太冷,你夏天到我们这里来。我们日里到海边捡贝壳去,红的绿的都有,鬼见怕也有,观音手也有。晚上我和爹管西瓜去,你也去。”
|
||||||
|
|
||||||
|
“管贼么?”
|
||||||
|
|
||||||
|
“不是。走路的人口渴了摘一个瓜吃,我们这里是不算偷的。要管的是獾猪,刺猬,猹。月亮底下,你听,啦啦的响了,猹在咬瓜了。你便捏了胡叉,轻轻地走去……”
|
||||||
|
|
||||||
|
我那时并不知道这所谓猹的是怎么一件东西——便是现在也没有知道——只是无端的觉得状如小狗而很凶猛。
|
||||||
|
|
||||||
|
“他不咬人么?”
|
||||||
|
|
||||||
|
“有胡叉呢。走到了,看见猹了,你便刺。这畜生很伶俐,倒向你奔来,反从胯下窜了。他的皮毛是油一般的滑……”
|
||||||
|
|
||||||
|
我素不知道天下有这许多新鲜事:海边有如许五色的贝壳;西瓜有这样危险的经历,我先前单知道他在水果店里出卖罢了。
|
||||||
|
|
||||||
|
“我们沙地里,潮汛要来的时候,就有许多跳鱼儿只是跳,都有青蛙似的两个脚……”
|
||||||
|
|
||||||
|
阿!闰土的心里有无穷无尽的希奇的事,都是我往常的朋友所不知道的。他们不知道一些事,闰土在海边时,他们都和我一样只看见院子里高墙上的四角的天空。
|
||||||
|
|
||||||
|
可惜正月过去了,闰土须回家里去,我急得大哭,他也躲到厨房里,哭着不肯出门,但终于被他父亲带走了。他后来还托他的父亲带给我一包贝壳和几支很好看的鸟毛,我也曾送他一两次东西,但从此没有再见面。
|
||||||
|
|
||||||
|
现在我的母亲提起了他,我这儿时的记忆,忽而全都闪电似的苏生过来,似乎看到了我的美丽的故乡了。我应声说:
|
||||||
|
|
||||||
|
“这好极!他,——怎样?……”
|
||||||
|
|
||||||
|
“他?……他景况也很不如意……”母亲说着,便向房外看,“这些人又来了。说是买木器,顺手也就随便拿走的,我得去看看。”
|
||||||
|
|
||||||
|
母亲站起身,出去了。门外有几个女人的声音。我便招宏儿走近面前,和他闲话:问他可会写字,可愿意出门。
|
||||||
|
|
||||||
|
“我们坐火车去么?”
|
||||||
|
|
||||||
|
“我们坐火车去。”
|
||||||
|
|
||||||
|
“船呢?”
|
||||||
|
|
||||||
|
“先坐船,……”
|
||||||
|
|
||||||
|
“哈!这模样了!胡子这么长了!”一种尖利的怪声突然大叫起来。
|
||||||
|
|
||||||
|
我吃了一吓,赶忙抬起头,却见一个凸颧骨、薄嘴唇、五十岁上下的女人站在我面前,两手搭在髀间,没有系裙,张着两脚,正像一个画图仪器里细脚伶仃的圆规。
|
||||||
|
|
||||||
|
我愕然了。
|
||||||
|
|
||||||
|
“不认识了么?我还抱过你咧!”
|
||||||
|
|
||||||
|
我愈加愕然了。幸而我的母亲也就进来,从旁说:
|
||||||
|
|
||||||
|
“他多年出门,统忘却了。你该记得罢,”便向着我说,“这是斜对门的杨二嫂,……开豆腐店的。”
|
||||||
|
|
||||||
|
哦,我记得了。我孩子时候,在斜对门的豆腐店里确乎终日坐着一个杨二嫂,人都叫伊“豆腐西施”。但是擦着白粉,颧骨没有这么高,嘴唇也没有这么薄,而且终日坐着,我也从没有见过这圆规式的姿势。那时人说:因为伊,这豆腐店的买卖非常好。但这大约因为年龄的关系,我却并未蒙着一毫感化,所以竟完全忘却了。然而圆规很不平,显出鄙夷的神色,仿佛嗤笑法国人不知道拿破仑,美国人不知道华盛顿似的,冷笑说:
|
||||||
|
|
||||||
|
“忘了?这真是贵人眼高……”
|
||||||
|
|
||||||
|
“那有这事……我……”我惶恐着,站起来说。
|
||||||
|
|
||||||
|
“那么,我对你说。迅哥儿,你阔了,搬动又笨重,你还要什么这些破烂木器,让我拿去罢。我们小户人家,用得着。”
|
||||||
|
|
||||||
|
“我并没有阔哩。我须卖了这些,再去……”
|
||||||
|
|
||||||
|
“阿呀呀,你放了道台了,还说不阔?你现在有三房姨太太;出门便是八抬的大轿,还说不阔?吓,什么都瞒不过我。”
|
||||||
|
|
||||||
|
我知道无话可说了,便闭了口,默默的站着。
|
||||||
|
|
||||||
|
“阿呀阿呀,真是愈有钱,便愈是一毫不肯放松,愈是一毫不肯放松,便愈有钱……”圆规一面愤愤的回转身,一面絮絮的说,慢慢向外走,顺便将我母亲的一副手套塞在裤腰里,出去了。
|
||||||
|
|
||||||
|
此后又有近处的本家和亲戚来访问我。我一面应酬,偷空便收拾些行李,这样的过了三四天。
|
||||||
|
|
||||||
|
一日是天气很冷的午后,我吃过午饭,坐着喝茶,觉得外面有人进来了,便回头去看。我看时,不由的非常出惊,慌忙站起身,迎着走去。
|
||||||
|
|
||||||
|
这来的便是闰土。虽然我一见便知道是闰土,但又不是我这记忆上的闰土了。他身材增加了一倍;先前的紫色的圆脸,已经变作灰黄,而且加上了很深的皱纹;眼睛也像他父亲一样,周围都肿得通红,这我知道,在海边种地的人,终日吹着海风,大抵是这样的。他头上是一顶破毡帽,身上只一件极薄的棉衣,浑身瑟索着;手里提着一个纸包和一支长烟管,那手也不是我所记得的红活圆实的手,却又粗又笨而且开裂,像是松树皮了。
|
||||||
|
|
||||||
|
我这时很兴奋,但不知道怎么说才好,只是说:
|
||||||
|
|
||||||
|
“阿!闰土哥,——你来了?……”
|
||||||
|
|
||||||
|
我接着便有许多话,想要连珠一般涌出:角鸡,跳鱼儿,贝壳,猹,……但又总觉得被什么挡着似的,单在脑里面回旋,吐不出口外去。
|
||||||
|
|
||||||
|
他站住了,脸上现出欢喜和凄凉的神情;动着嘴唇,却没有作声。他的态度终于恭敬起来了,分明的叫道:
|
||||||
|
|
||||||
|
“老爷!……”
|
||||||
|
|
||||||
|
我似乎打了一个寒噤;我就知道,我们之间已经隔了一层可悲的厚障壁了。我也说不出话。
|
||||||
|
|
||||||
|
他回过头去说,“水生,给老爷磕头。”便拖出躲在背后的孩子来,这正是一个廿年前的闰土,只是黄瘦些,颈子上没有银圈罢了。 “这是第五个孩子,没有见过世面,躲躲闪闪……”
|
||||||
|
|
||||||
|
母亲和宏儿下楼来了,他们大约也听到了声音。
|
||||||
|
|
||||||
|
“老太太。信是早收到了。我实在喜欢的不得了,知道老爷回来……”闰土说。
|
||||||
|
|
||||||
|
“阿,你怎的这样客气起来。你们先前不是哥弟称呼么?还是照旧:迅哥儿。”母亲高兴的说。
|
||||||
|
|
||||||
|
“阿呀,老太太真是……这成什么规矩。那时是孩子,不懂事……”闰土说着,又叫水生上来打拱,那孩子却害羞,紧紧的只贴在他背后。
|
||||||
|
|
||||||
|
“他就是水生?第五个?都是生人,怕生也难怪的;还是宏儿和他去走走。”母亲说。
|
||||||
|
|
||||||
|
宏儿听得这话,便来招水生,水生却松松爽爽同他一路出去了。母亲叫闰土坐,他迟疑了一回,终于就了坐,将长烟管靠在桌旁,递过纸包来,说:
|
||||||
|
|
||||||
|
“冬天没有什么东西了。这一点干青豆倒是自家晒在那里的,请老爷……”
|
||||||
|
|
||||||
|
我问问他的景况。他只是摇头。
|
||||||
|
|
||||||
|
“非常难。第六个孩子也会帮忙了,却总是吃不够……又不太平……什么地方都要钱,没有规定……收成又坏。种出东西来,挑去卖,总要捐几回钱,折了本;不去卖,又只能烂掉……”
|
||||||
|
|
||||||
|
他只是摇头;脸上虽然刻着许多皱纹,却全然不动,仿佛石像一般。他大约只是觉得苦,却又形容不出,沉默了片时,便拿起烟管来默默的吸烟了。
|
||||||
|
|
||||||
|
母亲问他,知道他的家里事务忙,明天便得回去;又没有吃过午饭,便叫他自己到厨下炒饭吃去。
|
||||||
|
|
||||||
|
他出去了;母亲和我都叹息他的景况:多子,饥荒,苛税,兵,匪,官,绅,都苦得他像一个木偶人了。母亲对我说,凡是不必搬走的东西,尽可以送他,可以听他自己去拣择。
|
||||||
|
|
||||||
|
下午,他拣好了几件东西:两条长桌,四个椅子,一副香炉和烛台,一杆抬秤。他又要所有的草灰(我们这里煮饭是烧稻草的,那灰,可以做沙地的肥料),待我们启程的时候,他用船来载去。
|
||||||
|
|
||||||
|
夜间,我们又谈些闲天,都是无关紧要的话;第二天早晨,他就领了水生回去了。
|
||||||
|
|
||||||
|
又过了九日,是我们启程的日期。闰土早晨便到了,水生没有同来,却只带着一个五岁的女儿管船只。我们终日很忙碌,再没有谈天的工夫。来客也不少,有送行的,有拿东西的,有送行兼拿东西的。待到傍晚我们上船的时候,这老屋里的所有破旧大小粗细东西,已经一扫而空了。
|
||||||
|
|
||||||
|
我们的船向前走,两岸的青山在黄昏中,都装成了深黛颜色,连着退向船后梢去。
|
||||||
|
|
||||||
|
宏儿和我靠着船窗,同看外面模糊的风景,他忽然问道:
|
||||||
|
|
||||||
|
“大伯!我们什么时候回来?”
|
||||||
|
|
||||||
|
“回来?你怎么还没有走就想回来了。”
|
||||||
|
|
||||||
|
“可是,水生约我到他家玩去咧……”他睁着大的黑眼睛,痴痴的想。
|
||||||
|
|
||||||
|
我和母亲也都有些惘然,于是又提起闰土来。母亲说,那豆腐西施的杨二嫂,自从我家收拾行李以来,本是每日必到的,前天伊在灰堆里,掏出十多个碗碟来,议论之后,便定说是闰土埋着的,他可以在运灰的时候,一齐搬回家里去;杨二嫂发见了这件事,自己很以为功,便拿了那狗气杀(这是我们这里养鸡的器具,木盘上面有着栅栏,内盛食料,鸡可以伸进颈子去啄,狗却不能,只能看着气死),飞也似的跑了,亏伊装着这么高低的小脚,竟跑得这样快。
|
||||||
|
|
||||||
|
老屋离我愈远了;故乡的山水也都渐渐远离了我,但我却并不感到怎样的留恋。我只觉得我四面有看不见的高墙,将我隔成孤身,使我非常气闷;那西瓜地上的银项圈的小英雄的影像,我本来十分清楚,现在却忽地模糊了,又使我非常的悲哀。
|
||||||
|
|
||||||
|
母亲和宏儿都睡着了。
|
||||||
|
|
||||||
|
我躺着,听船底潺潺的水声,知道我在走我的路。我想:我竟与闰土隔绝到这地步了,但我们的后辈还是一气,宏儿不是正在想念水生么。我希望他们不再像我,又大家隔膜起来……然而我又不愿意他们因为要一气,都如我的辛苦展转而生活,也不愿意他们都如闰土的辛苦麻木而生活,也不愿意都如别人的辛苦恣睢而生活。他们应该有新的生活,为我们所未经生活过的。
|
||||||
|
|
||||||
|
我想到希望,忽然害怕起来了。闰土要香炉和烛台的时候,我还暗地里笑他,以为他总是崇拜偶像,什么时候都不忘却。现在我所谓希望,不也是我自己手制的偶像么?只是他的愿望切近,我的愿望茫远罢了。
|
||||||
|
|
||||||
|
我在朦胧中,眼前展开一片海边碧绿的沙地来,上面深蓝的天空中挂着一轮金黄的圆月。我想:希望本是无所谓有,无所谓无的。这正如地上的路;其实地上本没有路,走的人多了,也便成了路。
|
||||||
|
|
||||||
|
一九二一年一月
|
185
src/content/posts/hometown copy.md
Normal file
185
src/content/posts/hometown copy.md
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
---
|
||||||
|
title: 开源的心理建设
|
||||||
|
published: 2025-07-18
|
||||||
|
tags: ["鲁迅"]
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
我冒了严寒,回到相隔二千余里,别了二十余年的故乡去。
|
||||||
|
|
||||||
|
时候既然是深冬;渐近故乡时,天气又阴晦了,冷风吹进船舱中,呜呜的响,从蓬隙向外一望,苍黄的天底下,远近横着几个萧索的荒村,没有一些活气。我的心禁不住悲凉起来了。
|
||||||
|
|
||||||
|
阿!这不是我二十年来时时记得的故乡?
|
||||||
|
|
||||||
|
我所记得的故乡全不如此。我的故乡好得多了。但要我记起他的美丽,说出他的佳处来,却又没有影像,没有言辞了。仿佛也就如此。于是我自己解释说:故乡本也如此,——虽然没有进步,也未必有如我所感的悲凉,这只是我自己心情的改变罢了,因为我这次回乡,本没有什么好心绪。
|
||||||
|
|
||||||
|
我这次是专为了别他而来的。我们多年聚族而居的老屋,已经公同卖给别姓了,交屋的期限,只在本年,所以必须赶在正月初一以前,永别了熟识的老屋,而且远离了熟识的故乡,搬家到我在谋食的异地去。
|
||||||
|
|
||||||
|
第二日清早晨我到了我家的门口了。瓦楞上许多枯草的断茎当风抖着,正在说明这老屋难免易主的原因。几房的本家大约已经搬走了,所以很寂静。我到了自家的房外,我的母亲早已迎着出来了,接着便飞出了八岁的侄儿宏儿。
|
||||||
|
|
||||||
|
我的母亲很高兴,但也藏着许多凄凉的神情,教我坐下,歇息,喝茶,且不谈搬家的事。宏儿没有见过我,远远的对面站着只是看。
|
||||||
|
|
||||||
|
但我们终于谈到搬家的事。我说外间的寓所已经租定了,又买了几件家具,此外须将家里所有的木器卖去,再去增添。母亲也说好,而且行李也略已齐集,木器不便搬运的,也小半卖去了,只是收不起钱来。
|
||||||
|
|
||||||
|
「你休息一两天,去拜望亲戚本家一回,我们便可以走了。」母亲说。
|
||||||
|
|
||||||
|
「是的。」
|
||||||
|
|
||||||
|
「还有闰土,他每到我家来时,总问起你,很想见你一回面。我已经将你到家的大约日期通知他,他也许就要来了。」
|
||||||
|
|
||||||
|
这时候,我的脑里忽然闪出一幅神异的图画来:深蓝的天空中挂着一轮金黄的圆月,下面是海边的沙地,都种著一望无际的碧绿的西瓜,其间有一个十一二岁的少年,项带银圈,手捏一柄钢叉,向一匹猹尽力的刺去,那猹却将身一扭,反从他的胯下逃走了。
|
||||||
|
|
||||||
|
这少年便是闰土。我认识他时,也不过十多岁,离现在将有三十年了;那时我的父亲还在世,家景也好,我正是一个少爷。那一年,我家是一件大祭祀的值年。这祭祀,说是三十多年才能轮到一回,所以很郑重;正月里供祖像,供品很多,祭器很讲究,拜的人也很多,祭器也很要防偷去。我家只有一个忙月(我们这里给人做工的分三种:整年给一定人家做工的叫长工;按日给人做工的叫短工;自己也种地,只在过年过节以及收租时候来给一定人家做工的称忙月),忙不过来,他便对父亲说,可以叫他的儿子闰土来管祭器的。
|
||||||
|
|
||||||
|
我的父亲允许了;我也很高兴,因为我早听到闰土这名字,而且知道他和我仿佛年纪,闰月生的,五行缺土,所以他的父亲叫他闰土。他是能装弶捉小鸟雀的。
|
||||||
|
|
||||||
|
我于是日日盼望新年,新年到,闰土也就到了。好容易到了年末,有一日,母亲告诉我,闰土来了,我便飞跑的去看。他正在厨房里,紫色的圆脸,头戴一顶小毡帽,颈上套一个明晃晃的银项圈,这可见他的父亲十分爱他,怕他死去,所以在神佛面前许下愿心,用圈子将他套住了。他见人很怕羞,只是不怕我,没有旁人的时候,便和我说话,于是不到半日,我们便熟识了。
|
||||||
|
|
||||||
|
我们那时候不知道谈些什么,只记得闰土很高兴,说是上城之后,见了许多没有见过的东西。
|
||||||
|
|
||||||
|
第二日,我便要他捕鸟。他说:
|
||||||
|
|
||||||
|
“这不能。须大雪下了才好。我们沙地上,下了雪,我扫出一块空地来,用短棒支起一个大竹匾,撒下秕谷,看鸟雀来吃时,我远远地将缚在棒上的绳子只一拉,那鸟雀就罩在竹匾下了。什么都有:稻鸡,角鸡,鹁鸪,蓝背……”
|
||||||
|
|
||||||
|
我于是又很盼望下雪。
|
||||||
|
|
||||||
|
闰土又对我说:
|
||||||
|
|
||||||
|
“现在太冷,你夏天到我们这里来。我们日里到海边捡贝壳去,红的绿的都有,鬼见怕也有,观音手也有。晚上我和爹管西瓜去,你也去。”
|
||||||
|
|
||||||
|
“管贼么?”
|
||||||
|
|
||||||
|
“不是。走路的人口渴了摘一个瓜吃,我们这里是不算偷的。要管的是獾猪,刺猬,猹。月亮底下,你听,啦啦的响了,猹在咬瓜了。你便捏了胡叉,轻轻地走去……”
|
||||||
|
|
||||||
|
我那时并不知道这所谓猹的是怎么一件东西——便是现在也没有知道——只是无端的觉得状如小狗而很凶猛。
|
||||||
|
|
||||||
|
“他不咬人么?”
|
||||||
|
|
||||||
|
“有胡叉呢。走到了,看见猹了,你便刺。这畜生很伶俐,倒向你奔来,反从胯下窜了。他的皮毛是油一般的滑……”
|
||||||
|
|
||||||
|
我素不知道天下有这许多新鲜事:海边有如许五色的贝壳;西瓜有这样危险的经历,我先前单知道他在水果店里出卖罢了。
|
||||||
|
|
||||||
|
“我们沙地里,潮汛要来的时候,就有许多跳鱼儿只是跳,都有青蛙似的两个脚……”
|
||||||
|
|
||||||
|
阿!闰土的心里有无穷无尽的希奇的事,都是我往常的朋友所不知道的。他们不知道一些事,闰土在海边时,他们都和我一样只看见院子里高墙上的四角的天空。
|
||||||
|
|
||||||
|
可惜正月过去了,闰土须回家里去,我急得大哭,他也躲到厨房里,哭着不肯出门,但终于被他父亲带走了。他后来还托他的父亲带给我一包贝壳和几支很好看的鸟毛,我也曾送他一两次东西,但从此没有再见面。
|
||||||
|
|
||||||
|
现在我的母亲提起了他,我这儿时的记忆,忽而全都闪电似的苏生过来,似乎看到了我的美丽的故乡了。我应声说:
|
||||||
|
|
||||||
|
“这好极!他,——怎样?……”
|
||||||
|
|
||||||
|
“他?……他景况也很不如意……”母亲说着,便向房外看,“这些人又来了。说是买木器,顺手也就随便拿走的,我得去看看。”
|
||||||
|
|
||||||
|
母亲站起身,出去了。门外有几个女人的声音。我便招宏儿走近面前,和他闲话:问他可会写字,可愿意出门。
|
||||||
|
|
||||||
|
“我们坐火车去么?”
|
||||||
|
|
||||||
|
“我们坐火车去。”
|
||||||
|
|
||||||
|
“船呢?”
|
||||||
|
|
||||||
|
“先坐船,……”
|
||||||
|
|
||||||
|
“哈!这模样了!胡子这么长了!”一种尖利的怪声突然大叫起来。
|
||||||
|
|
||||||
|
我吃了一吓,赶忙抬起头,却见一个凸颧骨、薄嘴唇、五十岁上下的女人站在我面前,两手搭在髀间,没有系裙,张着两脚,正像一个画图仪器里细脚伶仃的圆规。
|
||||||
|
|
||||||
|
我愕然了。
|
||||||
|
|
||||||
|
“不认识了么?我还抱过你咧!”
|
||||||
|
|
||||||
|
我愈加愕然了。幸而我的母亲也就进来,从旁说:
|
||||||
|
|
||||||
|
“他多年出门,统忘却了。你该记得罢,”便向着我说,“这是斜对门的杨二嫂,……开豆腐店的。”
|
||||||
|
|
||||||
|
哦,我记得了。我孩子时候,在斜对门的豆腐店里确乎终日坐着一个杨二嫂,人都叫伊“豆腐西施”。但是擦着白粉,颧骨没有这么高,嘴唇也没有这么薄,而且终日坐着,我也从没有见过这圆规式的姿势。那时人说:因为伊,这豆腐店的买卖非常好。但这大约因为年龄的关系,我却并未蒙着一毫感化,所以竟完全忘却了。然而圆规很不平,显出鄙夷的神色,仿佛嗤笑法国人不知道拿破仑,美国人不知道华盛顿似的,冷笑说:
|
||||||
|
|
||||||
|
“忘了?这真是贵人眼高……”
|
||||||
|
|
||||||
|
“那有这事……我……”我惶恐着,站起来说。
|
||||||
|
|
||||||
|
“那么,我对你说。迅哥儿,你阔了,搬动又笨重,你还要什么这些破烂木器,让我拿去罢。我们小户人家,用得着。”
|
||||||
|
|
||||||
|
“我并没有阔哩。我须卖了这些,再去……”
|
||||||
|
|
||||||
|
“阿呀呀,你放了道台了,还说不阔?你现在有三房姨太太;出门便是八抬的大轿,还说不阔?吓,什么都瞒不过我。”
|
||||||
|
|
||||||
|
我知道无话可说了,便闭了口,默默的站着。
|
||||||
|
|
||||||
|
“阿呀阿呀,真是愈有钱,便愈是一毫不肯放松,愈是一毫不肯放松,便愈有钱……”圆规一面愤愤的回转身,一面絮絮的说,慢慢向外走,顺便将我母亲的一副手套塞在裤腰里,出去了。
|
||||||
|
|
||||||
|
此后又有近处的本家和亲戚来访问我。我一面应酬,偷空便收拾些行李,这样的过了三四天。
|
||||||
|
|
||||||
|
一日是天气很冷的午后,我吃过午饭,坐着喝茶,觉得外面有人进来了,便回头去看。我看时,不由的非常出惊,慌忙站起身,迎着走去。
|
||||||
|
|
||||||
|
这来的便是闰土。虽然我一见便知道是闰土,但又不是我这记忆上的闰土了。他身材增加了一倍;先前的紫色的圆脸,已经变作灰黄,而且加上了很深的皱纹;眼睛也像他父亲一样,周围都肿得通红,这我知道,在海边种地的人,终日吹着海风,大抵是这样的。他头上是一顶破毡帽,身上只一件极薄的棉衣,浑身瑟索着;手里提着一个纸包和一支长烟管,那手也不是我所记得的红活圆实的手,却又粗又笨而且开裂,像是松树皮了。
|
||||||
|
|
||||||
|
我这时很兴奋,但不知道怎么说才好,只是说:
|
||||||
|
|
||||||
|
“阿!闰土哥,——你来了?……”
|
||||||
|
|
||||||
|
我接着便有许多话,想要连珠一般涌出:角鸡,跳鱼儿,贝壳,猹,……但又总觉得被什么挡着似的,单在脑里面回旋,吐不出口外去。
|
||||||
|
|
||||||
|
他站住了,脸上现出欢喜和凄凉的神情;动着嘴唇,却没有作声。他的态度终于恭敬起来了,分明的叫道:
|
||||||
|
|
||||||
|
“老爷!……”
|
||||||
|
|
||||||
|
我似乎打了一个寒噤;我就知道,我们之间已经隔了一层可悲的厚障壁了。我也说不出话。
|
||||||
|
|
||||||
|
他回过头去说,“水生,给老爷磕头。”便拖出躲在背后的孩子来,这正是一个廿年前的闰土,只是黄瘦些,颈子上没有银圈罢了。 “这是第五个孩子,没有见过世面,躲躲闪闪……”
|
||||||
|
|
||||||
|
母亲和宏儿下楼来了,他们大约也听到了声音。
|
||||||
|
|
||||||
|
“老太太。信是早收到了。我实在喜欢的不得了,知道老爷回来……”闰土说。
|
||||||
|
|
||||||
|
“阿,你怎的这样客气起来。你们先前不是哥弟称呼么?还是照旧:迅哥儿。”母亲高兴的说。
|
||||||
|
|
||||||
|
“阿呀,老太太真是……这成什么规矩。那时是孩子,不懂事……”闰土说着,又叫水生上来打拱,那孩子却害羞,紧紧的只贴在他背后。
|
||||||
|
|
||||||
|
“他就是水生?第五个?都是生人,怕生也难怪的;还是宏儿和他去走走。”母亲说。
|
||||||
|
|
||||||
|
宏儿听得这话,便来招水生,水生却松松爽爽同他一路出去了。母亲叫闰土坐,他迟疑了一回,终于就了坐,将长烟管靠在桌旁,递过纸包来,说:
|
||||||
|
|
||||||
|
“冬天没有什么东西了。这一点干青豆倒是自家晒在那里的,请老爷……”
|
||||||
|
|
||||||
|
我问问他的景况。他只是摇头。
|
||||||
|
|
||||||
|
“非常难。第六个孩子也会帮忙了,却总是吃不够……又不太平……什么地方都要钱,没有规定……收成又坏。种出东西来,挑去卖,总要捐几回钱,折了本;不去卖,又只能烂掉……”
|
||||||
|
|
||||||
|
他只是摇头;脸上虽然刻着许多皱纹,却全然不动,仿佛石像一般。他大约只是觉得苦,却又形容不出,沉默了片时,便拿起烟管来默默的吸烟了。
|
||||||
|
|
||||||
|
母亲问他,知道他的家里事务忙,明天便得回去;又没有吃过午饭,便叫他自己到厨下炒饭吃去。
|
||||||
|
|
||||||
|
他出去了;母亲和我都叹息他的景况:多子,饥荒,苛税,兵,匪,官,绅,都苦得他像一个木偶人了。母亲对我说,凡是不必搬走的东西,尽可以送他,可以听他自己去拣择。
|
||||||
|
|
||||||
|
下午,他拣好了几件东西:两条长桌,四个椅子,一副香炉和烛台,一杆抬秤。他又要所有的草灰(我们这里煮饭是烧稻草的,那灰,可以做沙地的肥料),待我们启程的时候,他用船来载去。
|
||||||
|
|
||||||
|
夜间,我们又谈些闲天,都是无关紧要的话;第二天早晨,他就领了水生回去了。
|
||||||
|
|
||||||
|
又过了九日,是我们启程的日期。闰土早晨便到了,水生没有同来,却只带着一个五岁的女儿管船只。我们终日很忙碌,再没有谈天的工夫。来客也不少,有送行的,有拿东西的,有送行兼拿东西的。待到傍晚我们上船的时候,这老屋里的所有破旧大小粗细东西,已经一扫而空了。
|
||||||
|
|
||||||
|
我们的船向前走,两岸的青山在黄昏中,都装成了深黛颜色,连着退向船后梢去。
|
||||||
|
|
||||||
|
宏儿和我靠着船窗,同看外面模糊的风景,他忽然问道:
|
||||||
|
|
||||||
|
“大伯!我们什么时候回来?”
|
||||||
|
|
||||||
|
“回来?你怎么还没有走就想回来了。”
|
||||||
|
|
||||||
|
“可是,水生约我到他家玩去咧……”他睁着大的黑眼睛,痴痴的想。
|
||||||
|
|
||||||
|
我和母亲也都有些惘然,于是又提起闰土来。母亲说,那豆腐西施的杨二嫂,自从我家收拾行李以来,本是每日必到的,前天伊在灰堆里,掏出十多个碗碟来,议论之后,便定说是闰土埋着的,他可以在运灰的时候,一齐搬回家里去;杨二嫂发见了这件事,自己很以为功,便拿了那狗气杀(这是我们这里养鸡的器具,木盘上面有着栅栏,内盛食料,鸡可以伸进颈子去啄,狗却不能,只能看着气死),飞也似的跑了,亏伊装着这么高低的小脚,竟跑得这样快。
|
||||||
|
|
||||||
|
老屋离我愈远了;故乡的山水也都渐渐远离了我,但我却并不感到怎样的留恋。我只觉得我四面有看不见的高墙,将我隔成孤身,使我非常气闷;那西瓜地上的银项圈的小英雄的影像,我本来十分清楚,现在却忽地模糊了,又使我非常的悲哀。
|
||||||
|
|
||||||
|
母亲和宏儿都睡着了。
|
||||||
|
|
||||||
|
我躺着,听船底潺潺的水声,知道我在走我的路。我想:我竟与闰土隔绝到这地步了,但我们的后辈还是一气,宏儿不是正在想念水生么。我希望他们不再像我,又大家隔膜起来……然而我又不愿意他们因为要一气,都如我的辛苦展转而生活,也不愿意他们都如闰土的辛苦麻木而生活,也不愿意都如别人的辛苦恣睢而生活。他们应该有新的生活,为我们所未经生活过的。
|
||||||
|
|
||||||
|
我想到希望,忽然害怕起来了。闰土要香炉和烛台的时候,我还暗地里笑他,以为他总是崇拜偶像,什么时候都不忘却。现在我所谓希望,不也是我自己手制的偶像么?只是他的愿望切近,我的愿望茫远罢了。
|
||||||
|
|
||||||
|
我在朦胧中,眼前展开一片海边碧绿的沙地来,上面深蓝的天空中挂着一轮金黄的圆月。我想:希望本是无所谓有,无所谓无的。这正如地上的路;其实地上本没有路,走的人多了,也便成了路。
|
||||||
|
|
||||||
|
一九二一年一月
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 故乡
|
title: 一次意外的森林徒步
|
||||||
published: 1977-07-18
|
published: 2025-08-18
|
||||||
tags: ["鲁迅"]
|
tags: ["鲁迅"]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 罗生门
|
title: 罗生门
|
||||||
published: 1984-09-17
|
published: 2024-09-17
|
||||||
tags: ["黑泽明"]
|
tags: ["黑泽明"]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import Footer from '@/components/Footer.astro'
|
import Footer from '@/components/Footer.astro'
|
||||||
import Header from '@/components/Header.astro'
|
import Header from '@/components/Header.astro'
|
||||||
import LanguageSwitcher from '@/components/LangSwitch.astro'
|
import LanguageSwitcher from '@/components/LanguageSwitcher.astro'
|
||||||
import Navigation from '@/components/Navbar.astro'
|
import Navigation from '@/components/Navbar.astro'
|
||||||
import PhotoSwipe from '@/components/PhotoSwipe.astro'
|
import PhotoSwipe from '@/components/PhotoSwipe.astro'
|
||||||
import Scrollbar from '@/components/Scrollbar.astro'
|
import Scrollbar from '@/components/Scrollbar.astro'
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
---
|
---
|
||||||
|
import { themeConfig } from '@/config'
|
||||||
import Layout from '@/layouts/Layout.astro'
|
import Layout from '@/layouts/Layout.astro'
|
||||||
import { getPinnedPosts, getPosts } from '@/utils/content'
|
import { getPinnedPosts, getPostsByYear } from '@/utils/content'
|
||||||
import { generateLanguagePaths } from '@/utils/i18n'
|
|
||||||
|
|
||||||
export function getStaticPaths() {
|
export function getStaticPaths() {
|
||||||
return generateLanguagePaths()
|
return themeConfig.global.moreLocale.map(lang => ({
|
||||||
|
params: { lang },
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const { lang } = Astro.params
|
const { lang } = Astro.params
|
||||||
const pinnedPosts = await getPinnedPosts()
|
const pinnedPosts = await getPinnedPosts(lang)
|
||||||
const posts = await getPosts()
|
const postsByYear = await getPostsByYear(lang)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
|
@ -21,7 +23,10 @@ const posts = await getPosts()
|
||||||
<li>
|
<li>
|
||||||
<a href={`/${lang}/posts/${post.data.slug || post.slug}/`}>
|
<a href={`/${lang}/posts/${post.data.slug || post.slug}/`}>
|
||||||
{post.data.title}
|
{post.data.title}
|
||||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
<time class="block lg:inline lg:before:content-['_']">
|
||||||
|
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
||||||
|
{post.remarkPluginFrontmatter?.minutes && <span> · {post.remarkPluginFrontmatter.minutes} min</span>}
|
||||||
|
</time>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -29,17 +34,27 @@ const posts = await getPosts()
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<section>
|
{[...postsByYear.entries()].map(([year, posts]) => (
|
||||||
|
// Year Group
|
||||||
|
<section class="mt-8">
|
||||||
|
{/* Year */}
|
||||||
|
<time class="text-8 font-date">{year}</time>
|
||||||
|
{/* Posts List */}
|
||||||
<ul>
|
<ul>
|
||||||
{posts.map(post => (
|
{posts.map(post => (
|
||||||
<li>
|
// Single Post
|
||||||
<a href={`/${lang}/posts/${post.data.slug || post.slug}/`}>
|
<li class="mt-6">
|
||||||
{post.data.title}
|
{/* Post Title */}
|
||||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
<a href={`/posts/${post.data.slug || post.slug}/`}>{post.data.title}</a>
|
||||||
</a>
|
{/* Post Date */}
|
||||||
|
<time class="text-5.6 leading-7 font-date block lg:inline lg:before:content-['_'] opacity-30" aria-hidden="true">
|
||||||
|
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
||||||
|
{post.remarkPluginFrontmatter?.minutes && <span class="ml-2"> {post.remarkPluginFrontmatter.minutes} min</span>}
|
||||||
|
</time>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
))}
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -16,13 +16,16 @@ export async function getStaticPaths() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { post } = Astro.props
|
const { post } = Astro.props
|
||||||
const { Content } = await post.render()
|
const { Content, remarkPluginFrontmatter } = await post.render()
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<article>
|
<article>
|
||||||
<h1>{post.data.title}</h1>
|
<h1>{post.data.title}</h1>
|
||||||
<time>{post.data.published.toISOString().split('T')[0]}</time>
|
<time>
|
||||||
|
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
||||||
|
{remarkPluginFrontmatter.minutes && <span> · {remarkPluginFrontmatter.minutes} min</span>}
|
||||||
|
</time>
|
||||||
<Content />
|
<Content />
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
import Layout from '@/layouts/Layout.astro'
|
import Layout from '@/layouts/Layout.astro'
|
||||||
import { getPinnedPosts, getPosts } from '@/utils/content'
|
import { getPinnedPosts, getPostsByYear } from '@/utils/content'
|
||||||
|
|
||||||
const posts = await getPosts()
|
|
||||||
const pinnedPosts = await getPinnedPosts()
|
const pinnedPosts = await getPinnedPosts()
|
||||||
|
const postsByYear = await getPostsByYear()
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
|
@ -15,7 +15,10 @@ const pinnedPosts = await getPinnedPosts()
|
||||||
<li>
|
<li>
|
||||||
<a href={`/posts/${post.data.slug || post.slug}/`}>
|
<a href={`/posts/${post.data.slug || post.slug}/`}>
|
||||||
{post.data.title}
|
{post.data.title}
|
||||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
<time class="block lg:inline lg:before:content-['_']">
|
||||||
|
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
||||||
|
{post.remarkPluginFrontmatter?.minutes && <span> {post.remarkPluginFrontmatter.minutes} min</span>}
|
||||||
|
</time>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -23,17 +26,27 @@ const pinnedPosts = await getPinnedPosts()
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<section>
|
{[...postsByYear.entries()].map(([year, posts]) => (
|
||||||
|
// Year Group
|
||||||
|
<section class="mt-8">
|
||||||
|
{/* Year */}
|
||||||
|
<time class="text-8 font-date">{year}</time>
|
||||||
|
{/* Posts List */}
|
||||||
<ul>
|
<ul>
|
||||||
{posts.map(post => (
|
{posts.map(post => (
|
||||||
<li>
|
// Single Post
|
||||||
<a href={`/posts/${post.data.slug || post.slug}/`}>
|
<li class="mt-6">
|
||||||
{post.data.title}
|
{/* Post Title */}
|
||||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
<a class="hover:text-secondary" href={`/posts/${post.data.slug || post.slug}/`}>{post.data.title}</a>
|
||||||
</a>
|
{/* Post Date */}
|
||||||
|
<time class="text-5.6 leading-7 font-date block lg:inline lg:before:content-['_'] opacity-30" aria-hidden="true">
|
||||||
|
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
||||||
|
{post.remarkPluginFrontmatter?.minutes && <span class="ml-2"> {post.remarkPluginFrontmatter.minutes} min</span>}
|
||||||
|
</time>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
))}
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -16,7 +16,7 @@ export async function getStaticPaths() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { post } = Astro.props
|
const { post } = Astro.props
|
||||||
const { Content } = await post.render()
|
const { Content, remarkPluginFrontmatter } = await post.render()
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
@ -26,7 +26,10 @@ const { Content } = await post.render()
|
||||||
>
|
>
|
||||||
<article>
|
<article>
|
||||||
<h1>{post.data.title}</h1>
|
<h1>{post.data.title}</h1>
|
||||||
<time>{post.data.published.toISOString().split('T')[0]}</time>
|
<time>
|
||||||
|
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
||||||
|
{remarkPluginFrontmatter.minutes && <span> · {remarkPluginFrontmatter.minutes} min</span>}
|
||||||
|
</time>
|
||||||
<Content />
|
<Content />
|
||||||
</article>
|
</article>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -4,17 +4,17 @@
|
||||||
--uno-colors-background: theme('colors.background');
|
--uno-colors-background: theme('colors.background');
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
--at-apply: 'scroll-smooth antialiased text-62.5%';
|
--at-apply: 'scroll-smooth antialiased text-62.5% bg-background c-secondary ';
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
--at-apply: 'bg-background c-secondary text-1.6rem';
|
--at-apply: 'text-1.6rem';
|
||||||
}
|
}
|
||||||
* {
|
* {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
h1, h2, h3 {
|
h1, h2, h3 {
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
@ -53,18 +53,27 @@ html:not(.dark) {
|
||||||
--from: 100% 0 0 0;
|
--from: 100% 0 0 0;
|
||||||
}
|
}
|
||||||
::view-transition-new(theme-transition) {
|
::view-transition-new(theme-transition) {
|
||||||
transition: none;
|
|
||||||
animation: reveal 1s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: reveal 1s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
clip-path: inset(0 0 0 0);
|
clip-path: inset(0 0 0 0);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
::view-transition-old(theme-transition) {
|
::view-transition-old(theme-transition) {
|
||||||
transition: none;
|
|
||||||
animation: none;
|
animation: none;
|
||||||
z-index: -1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
@supports not (view-transition-name: none) {
|
@supports not (view-transition-name: none) {
|
||||||
body:not([data-restore-theme]) {
|
body:not([data-restore-theme]) {
|
||||||
--at-apply: 'transition-all duration-500 ease-in-out';
|
--at-apply: 'transition-colors duration-500 ease-in-out';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* iOS browsers flash issue fix */
|
||||||
|
/* @media (prefers-color-scheme: dark) {
|
||||||
|
html {
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
html {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
} */
|
10
src/types/global.d.ts
vendored
10
src/types/global.d.ts
vendored
|
@ -4,11 +4,15 @@ declare global {
|
||||||
namespace astroHTML.JSX {
|
namespace astroHTML.JSX {
|
||||||
interface HTMLAttributes extends AttributifyAttributes {}
|
interface HTMLAttributes extends AttributifyAttributes {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Document {
|
||||||
|
startViewTransition: (updateCallback: () => void) => ViewTransition
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
interface ViewTransition {
|
||||||
interface Document {
|
finished: Promise<void>
|
||||||
startViewTransition: (callback: () => void) => void
|
ready: Promise<void>
|
||||||
|
updateCallbackDone: Promise<void>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,24 @@ import themeConfig from '@/config'
|
||||||
import { langPath } from '@/utils/ui'
|
import { langPath } from '@/utils/ui'
|
||||||
import { getCollection } from 'astro:content'
|
import { getCollection } from 'astro:content'
|
||||||
|
|
||||||
export type Post = CollectionEntry<'posts'>
|
// Type definitions
|
||||||
|
export type Post = CollectionEntry<'posts'> & {
|
||||||
|
remarkPluginFrontmatter?: {
|
||||||
|
minutes?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
export type PostData = Post['data']
|
export type PostData = Post['data']
|
||||||
export type PostsGroupByYear = Map<number, Post[]>
|
export type PostsGroupByYear = Map<number, Post[]>
|
||||||
|
|
||||||
// Check if the slug is duplicated under the same language.
|
// Get post metadata including reading time
|
||||||
|
async function getPostMeta(post: CollectionEntry<'posts'>): Promise<Post> {
|
||||||
|
const { remarkPluginFrontmatter } = await post.render()
|
||||||
|
return { ...post, remarkPluginFrontmatter }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the slug is duplicated under the same language
|
||||||
export async function checkSlugDuplication(posts: Post[]): Promise<string[]> {
|
export async function checkSlugDuplication(posts: Post[]): Promise<string[]> {
|
||||||
const slugMap = new Map<string, Set<string>>() // Map<lang, Set<slug>>
|
const slugMap = new Map<string, Set<string>>()
|
||||||
const duplicates: string[] = []
|
const duplicates: string[] = []
|
||||||
|
|
||||||
posts.forEach((post) => {
|
posts.forEach((post) => {
|
||||||
|
@ -48,7 +59,9 @@ export async function getPosts(lang?: string) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return posts.sort((a: Post, b: Post) =>
|
const postsWithMeta = await Promise.all(posts.map(getPostMeta))
|
||||||
|
|
||||||
|
return postsWithMeta.sort((a: Post, b: Post) =>
|
||||||
b.data.published.valueOf() - a.data.published.valueOf(),
|
b.data.published.valueOf() - a.data.published.valueOf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +78,7 @@ export async function getPinnedPosts(lang?: string) {
|
||||||
return posts.filter(post => post.data.pin)
|
return posts.filter(post => post.data.pin)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get posts group by year (not pinned)
|
// Get posts grouped by year (not pinned)
|
||||||
export async function getPostsByYear(lang?: string): Promise<PostsGroupByYear> {
|
export async function getPostsByYear(lang?: string): Promise<PostsGroupByYear> {
|
||||||
const posts = await getRegularPosts(lang)
|
const posts = await getRegularPosts(lang)
|
||||||
const yearMap = new Map<number, Post[]>()
|
const yearMap = new Map<number, Post[]>()
|
||||||
|
@ -108,7 +121,7 @@ export async function getAllTags(lang?: string) {
|
||||||
return Array.from(tags)
|
return Array.from(tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get posts group by each tag
|
// Get posts grouped by tags
|
||||||
export async function getPostsGroupByTags(lang?: string) {
|
export async function getPostsGroupByTags(lang?: string) {
|
||||||
const posts = await getRegularPosts(lang)
|
const posts = await getRegularPosts(lang)
|
||||||
const tagMap = new Map<string, Post[]>()
|
const tagMap = new Map<string, Post[]>()
|
||||||
|
@ -127,7 +140,7 @@ export async function getPostsGroupByTags(lang?: string) {
|
||||||
return tagMap
|
return tagMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all posts by one tag
|
// Get all posts by specific tag
|
||||||
export async function getPostsByTag(tag: string, lang?: string) {
|
export async function getPostsByTag(tag: string, lang?: string) {
|
||||||
const posts = await getRegularPosts(lang)
|
const posts = await getRegularPosts(lang)
|
||||||
return posts.filter((post: Post) =>
|
return posts.filter((post: Post) =>
|
||||||
|
|
|
@ -34,8 +34,8 @@ export const ui = {
|
||||||
about: 'About',
|
about: 'About',
|
||||||
},
|
},
|
||||||
'es': {
|
'es': {
|
||||||
posts: 'Posts',
|
posts: 'Artículos',
|
||||||
tags: 'Tags',
|
tags: 'Etiquetas',
|
||||||
about: 'Sobre',
|
about: 'Sobre',
|
||||||
},
|
},
|
||||||
'ru': {
|
'ru': {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue