Compare commits

...

36 Commits

Author SHA1 Message Date
d2622ac85b Upgrade dependencies. 2025-12-04 23:26:55 +08:00
4ab4cc39d2 Add extra space on both sides. 2025-10-11 21:35:53 +08:00
7be5057d5b Upgrade dependencies. 2025-09-23 15:29:42 +08:00
bc19f9a638 Upgrade dependencies. 2025-03-18 21:13:45 +08:00
498fa8519e Use nvm to activate node in build scrpit. 2025-01-07 10:01:22 +08:00
e7f6738a27 Remove nodejs path from $PATH. 2024-11-10 22:40:42 +08:00
036d98dbd4 Update article license. 2024-10-27 13:17:29 +08:00
8b07e121a5 Migrate translation article source code format to texinfo. 2024-10-13 22:04:33 +08:00
9cf9745d49 Format all code using prettier. 2024-10-12 21:04:57 +08:00
c323698b2a Update a rule. 2024-08-07 18:01:36 +08:00
5d4d388fe8 Add new processing rule and improve comments. 2024-08-06 11:00:42 +08:00
9e33f42d34 Add new processing rule and improve comments. 2024-07-31 20:46:24 +08:00
033b124b90 Add new processing rule. 2024-07-01 19:41:15 +08:00
955bca434c Reorganize unit conversion component.
Rename the weight unit conversion component name and change the unit conversion
component layout.
2024-06-30 14:14:16 +08:00
cf9e51611a Fix layout problem. 2024-06-30 09:32:26 +08:00
1146f3b180 Add weight conversion to unit conversion tool. 2024-06-30 09:22:44 +08:00
5131b9396b Fix UI problem. 2024-06-30 09:22:44 +08:00
a8f0a86e07 Join lines broken by newlines to a long line.
Join paragraph lines that are broken by newlines from a long paragraph to a
long line. If the current line is a part of a block area, like pre, code,
blockquote, skip it.
2024-06-30 09:13:57 +08:00
3ed03f0496 Hide HTML h tag of table of content. 2024-06-30 09:13:56 +08:00
05c1b0d29b Add remark-toc plugin.
Add plugin for generating table of content automatically for markdown files.
2024-06-30 09:12:43 +08:00
2f54cf304a Fix layout probelm in using different fonts. 2024-06-30 09:12:41 +08:00
109357e8e2 Convert vue file from ts to js. 2024-06-30 09:12:41 +08:00
8563930e68 Disable 'markdown.smartypants' option.
Because it renders right and left quotes wrong.
2024-06-30 09:11:08 +08:00
8d07d60566 Remove deleted article files.
Delete non-existing files in the article dir from the target dir when rsync
transfers article files.
2024-06-30 09:05:48 +08:00
4cdce770d8 Add article definition to suppress the '[ERROR] The build was canceled.' log. 2024-06-30 09:05:23 +08:00
b87816c5eb Add missing parts and remove unused parts. 2024-06-21 22:37:12 +08:00
3deefdccff Update dependencies. 2024-06-21 21:09:02 +08:00
a643397716 Add 'tool' tag to nav and unit conversion tool. 2024-06-20 10:13:48 +08:00
740fc03778 Update footer license. 2024-06-20 09:59:43 +08:00
ed1a6fe662 Make the code easier to read. 2024-06-16 13:22:49 +08:00
37f035709d Add more media query steps for small-screen devices. 2024-06-15 17:48:45 +08:00
b39c3499c3 Remove flex layout on nav bar. 2024-06-15 17:46:14 +08:00
cb7ce1361d Fix blockquote overflow problem. 2024-06-11 10:31:07 +08:00
2cb3299402 Add style for blockquote and table. 2024-06-10 15:04:30 +08:00
6127d5f7ed Fix article sort problem in non-zh environment. 2024-06-10 14:38:38 +08:00
83d584c529 Ignore test articles. 2024-06-10 12:42:53 +08:00
37 changed files with 5188 additions and 3372 deletions

8
.gitignore vendored
View File

@@ -22,3 +22,11 @@ pnpm-debug.log*
# jetbrains setting folder # jetbrains setting folder
.idea/ .idea/
# ignore test articles except for article definitions
src/content/*
!src/content/config.ts
# ignore generated html from makeinfo
src/pages/article/translation/*.html

12
.prettierrc.mjs Normal file
View File

@@ -0,0 +1,12 @@
/** @type {import("prettier").Config} */
export default {
plugins: ['prettier-plugin-astro'],
overrides: [
{
files: '*.astro',
options: {
parser: 'astro',
},
},
],
};

11
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"[json]": {
"editor.tabSize": 2
},
"[javascript]": {
"editor.tabSize": 2
},
"[typescript]": {
"editor.tabSize": 2
}
}

View File

@@ -1 +1,12 @@
# 李守中的个人站
个人资料、文章与工具站点。由 Astro 框架构建,使用 typescript 编写。 个人资料、文章与工具站点。由 Astro 框架构建,使用 typescript 编写。
## 关于翻译类文章的发布方式
翻译类文章不由 Astro 生成,而是预先使用 texinfo 编写后编译到 HTML 文件,将其放于固定位置,再在 Astro 页面中引用这些文件。
这意味着每有一个新的翻译类文章要发布时,需要:
1. 更新 pages/article/translation/index.astro 文件中翻译类文章的列表;
2. 更新 astro.config.mjs 文件中的 sitemap 插件配置,填入 texinfo 编译出的 HTML 文件的 URL 以使这些文章可以进入 sitemap.xml 中,从而可以被搜索引擎收录。

View File

@@ -1,10 +1,12 @@
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import sitemap from "@astrojs/sitemap"; import sitemap from "@astrojs/sitemap";
import vue from "@astrojs/vue";
import remarkToc from 'remark-toc';
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
markdown: { markdown: {
smartypants: false,
syntaxHighlight: 'shiki', syntaxHighlight: 'shiki',
shikiConfig: { shikiConfig: {
// Choose from Shiki's built-in themes (or add your own) // Choose from Shiki's built-in themes (or add your own)
@@ -25,16 +27,38 @@ export default defineConfig({
// Add custom transformers: https://shiki.style/guide/transformers // Add custom transformers: https://shiki.style/guide/transformers
// Find common transformers: https://shiki.style/packages/transformers // Find common transformers: https://shiki.style/packages/transformers
transformers: [] transformers: []
} },
remarkPlugins: [
[remarkToc, { heading: "(table[ -]of[ -])?contents?|toc|目录" }]
],
}, },
site: 'https://lishouzhong.com', site: 'https://lishouzhong.com',
integrations: [sitemap({ integrations: [
// entryLimit: 10000, sitemap({
// customPages: [], customPages: [
// filter: (page) => { }, "https://lishouzhong.com/file_share/translation/FreeBSD 13 NFS exports man(5) page 2021 中文译本.html",
// lastmod: new Date('2022-02-24'), "https://lishouzhong.com/file_share/translation/FreeBSD 13 NFS mountd man(8) page 2020 中文译本.html",
// serialize(item) { "https://lishouzhong.com/file_share/translation/FreeBSD 13 NFS nfsd man(8) page 2019 中文译本.html",
// return item; "https://lishouzhong.com/file_share/translation/FreeBSD 13 NFS showmount man(8) page 2016 中文译本.html",
// } "https://lishouzhong.com/file_share/translation/FreeBSD 13 NFSv4 man(4) page 2019 中文译本.html",
})] "https://lishouzhong.com/file_share/translation/FreeBSD 13 NFSv4 nfscbd man(8) page 2009 中文译本.html",
"https://lishouzhong.com/file_share/translation/FreeBSD 13 NFSv4 nfsrevoke man(8) page 2009 中文译本.html",
"https://lishouzhong.com/file_share/translation/FreeBSD 13 NFSv4 nfsuserd man(8) page 2019 中文译本.html",
"https://lishouzhong.com/file_share/translation/FreeBSD 13 rpcbind man(8) page 2017 中文译本.html",
"https://lishouzhong.com/file_share/translation/EasyRSA Intro-To-PKI v3.08 中文译本.html",
"https://lishouzhong.com/file_share/translation/EasyRSA-Advanced v3.08 中文译本.html",
"https://lishouzhong.com/file_share/translation/EasyRSA-Readme v3.08 中文译本.html",
"https://lishouzhong.com/file_share/translation/EasyRSA-Upgrade-Notes v3.08 中文译本.html",
"https://lishouzhong.com/file_share/translation/iperf3 v3.9 man page 中文译本.html",
"https://lishouzhong.com/file_share/translation/PostgreSQL Don't Do This 中文译本.html",
"https://lishouzhong.com/file_share/translation/rsync v3.2.7 man(1) page 中文译本.html",
],
// entryLimit: 10000,
// filter: (page) => { },
// lastmod: new Date('2022-02-24'),
// serialize(item) {
// return item;
// }
}),
vue()]
}); });

36
content.config.ts Normal file
View File

@@ -0,0 +1,36 @@
import { z, defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
const blogCollection = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
description: z.string(),
category: z.string(),
lastUpdate: z.string()
}),
});
const noteCollection = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/note" }),
schema: z.object({
title: z.string(),
description: z.string(),
category: z.string(),
lastUpdate: z.string()
}),
});
const translationCollection = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/translation" }),
schema: z.object({
title: z.string(),
description: z.string(),
category: z.string(),
lastUpdate: z.string()
}),
});
export const collections = {
'blog': blogCollection,
'note': noteCollection,
'translation': translationCollection,
};

7241
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,16 +3,23 @@
"type": "module", "type": "module",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev --host",
"start": "astro dev", "start": "astro dev --host",
"build": "astro check && astro build", "build": "astro check && astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/check": "^0.7.0", "@astrojs/check": "^0.9.4",
"@astrojs/sitemap": "^3.1.5", "@astrojs/sitemap": "^3.6.0",
"astro": "^4.9.2", "@astrojs/vue": "^5.1.3",
"typescript": "^5.4.5" "astro": "^5.15.9",
"bignumber.js": "^9.3.1",
"pinyin-pro": "^3.27.0",
"prettier": "^3.7.4",
"prettier-plugin-astro": "^0.14.1",
"remark-toc": "^9.0.0",
"typescript": "^5.8.2",
"vue": "^3.5.13"
} }
} }

View File

@@ -2,14 +2,27 @@
# cron # cron
# 0 4 * * * . /etc/profile; /bin/sh /opt/ld-site/script/build_on_linux.sh >/dev/null 2>&1 # 0 4 * * * . /etc/profile; /bin/sh /opt/ld-site/script/build_on_linux.sh >/dev/null 2>&1
export PATH=$PATH:/opt/dev_tools/node-v20.12.2-linux-x64/bin NVM_DIR="/opt/dev_tools/nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
script_path="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" script_path="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
original_article_home="/home/ld/Documents/ld_article" original_article_home="/home/ld/Documents/ld_article"
original_article_destination="/opt/ld-site/src/content" original_article_destination="/opt/ld-site/src/content"
ld_site_dist_target="/opt/nginx_targets/ld_site_dist" ld_site_dist_target="/opt/nginx_targets/ld_site_dist"
translation_article_path="/opt/nginx_targets/file_share/translation"
rsync -a "${original_article_home}/" "${original_article_destination}/" rsync -a --delete-after -f"P config.ts" \
"${original_article_home}/" \
"${original_article_destination}/"
cd "${original_article_destination}/translation"
makeinfo --html --no-split --css-include=theme.css ./*.texi
if [ $? -ne 0 ]; then
echo "cannot generate translation article, check 'makeinfo' command output."
exit 1
fi
rm -rf "${translation_article_path}/*.html"
mv ./*.html "${translation_article_path}/"
cd "${script_path}/../" cd "${script_path}/../"
npm run build npm run build
@@ -19,6 +32,6 @@ if [ $? -ne 0 ]; then
exit 1 exit 1
fi fi
mv ${ld_site_dist_target} "${ld_site_dist_target}-old" mv ${ld_site_dist_target} "${ld_site_dist_target}_old"
mv dist ${ld_site_dist_target} mv dist ${ld_site_dist_target}
rm -rf "${ld_site_dist_target}-old" ${original_article_destination} rm -rf "${ld_site_dist_target}_old"

View File

@@ -1,4 +1,5 @@
--- ---
--- ---
<div class="footer"> <div class="footer">
@@ -10,8 +11,8 @@
<div> <div>
<small class="licence">若正文中无特殊说明,本站内容遵循:</small> <small class="licence">若正文中无特殊说明,本站内容遵循:</small>
<small class="licence"> <small class="licence">
<a href="http://creativecommons.org/licenses/by-nc-sa/4.0/"> <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank">
署名-非商业使用-相同方式共享 4.0 国际许可协议 署名-非商业使用 4.0 协议国际版
</a> </a>
</small> </small>
</div> </div>

View File

@@ -7,7 +7,7 @@ const { title } = Astro.props;
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" type="image/x-icon"> <link rel="icon" href="/favicon.ico" type="image/x-icon" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<link rel="sitemap" href="/sitemap-index.xml" /> <link rel="sitemap" href="/sitemap-index.xml" />
<title>{title}</title> <title>{title}</title>

View File

@@ -1,48 +1,37 @@
--- ---
--- ---
<div class="nav" id="nav"> <div class="nav" id="nav">
<span class="nav-item site-title" id="site-title"> <div class="global-nav">
<a class="nav-link" href="/">李守中</a> <span class="site-title"><a class="nav-link" href="/">李守中</a></span>
</span> <span class="category-item">
<span class="nav-item" id="category"> <a class="nav-link" href="/article/blog/">博客</a>
<ul class="nav-list"> </span>
<li class="nav-list-item"> <span class="category-item">
<a class="nav-link" href="/article/blog/">博客</a> <a class="nav-link" href="/article/note/">笔记</a>
</li> </span>
<li class="nav-list-item"> <span class="category-item">
<a class="nav-link" href="/article/note/">笔记</a> <a class="nav-link" href="/article/translation/">翻译</a>
</li> </span>
<li class="nav-list-item"> <span class="category-item">
<a class="nav-link" href="/article/translation/">翻译</a> <a class="nav-link" href="/tool/">工具</a>
</li> </span>
</ul> </div>
</span>
</div> </div>
<style> <style>
.nav { .nav {
font-size: 1em; margin: 0.2em 0 0 0;
margin: 10px 0 0 0;
display: flex;
align-items: flex-end;
} }
#site-title { .site-title {
font-size: 1.8em; font-size: 1.8em;
margin: 0 20px 0 0; margin: 0 10px 0 0;
padding: 0 0 2px 0;
line-height: 100%;
} }
.nav-list { .category-item {
padding: 0; margin: 0 10px 0 0;
margin: 6px 0 0 0;
} }
.nav-list .nav-list-item { .global-nav:last-child {
float: left;
list-style-type: none;
margin: 0 20px 0 0;
}
.nav-list .nav-list-item:last-child {
margin: 0; margin: 0;
} }
.nav-link { .nav-link {

View File

@@ -14,7 +14,8 @@ const { collectionName, categoryName, posts } = Astro.props;
{ {
posts.map((post) => ( posts.map((post) => (
<li> <li>
<a href={`/article/${collectionName}/${post.slug}`} target="_blank"> {/* Hide .md extension from URL */}
<a href={`/article/${collectionName}/${post.id.replace(".md", "")}`} target="_blank">
{post.data.title} {post.data.title}
</a> </a>
</li> </li>

View File

@@ -3,11 +3,14 @@ import type { CollectionEntry } from "astro:content";
import { getCollection, type CollectionKey } from "astro:content"; import { getCollection, type CollectionKey } from "astro:content";
import CategorySummary from "./CategorySummary.astro"; import CategorySummary from "./CategorySummary.astro";
import * as pinyinpro from "pinyin-pro";
interface Props { interface Props {
collectionName: CollectionKey; collectionName: CollectionKey;
sortedByTimeline: boolean; sortByTimeline: boolean;
sortByArticleTitle: boolean;
} }
const { collectionName, sortedByTimeline } = Astro.props; const { collectionName, sortByTimeline, sortByArticleTitle } = Astro.props;
type CategoriedPosts = { type CategoriedPosts = {
[categoryName: string]: Array<CollectionEntry<CollectionKey>>; [categoryName: string]: Array<CollectionEntry<CollectionKey>>;
@@ -20,33 +23,57 @@ const allCategories: Array<string> = Array.from(
new Set( new Set(
allPosts.map((entry) => { allPosts.map((entry) => {
return entry.data.category; return entry.data.category;
}) }),
) ),
); );
const categoriedPostList: Array<CategoriedPosts> = allCategories.map((key) => { const categoriedPostList: Array<CategoriedPosts> = allCategories.map((key) => {
let object: CategoriedPosts = {}; let object: CategoriedPosts = {};
object[key] = allPosts.filter((post) => post.data.category === key); object[key] = allPosts.filter((post) => post.data.category === key);
sortedByTimeline
? object[key].sort( // article sort function by article title
( function sortPostByTitlePinyin(
postA: CollectionEntry<CollectionKey>, postA: CollectionEntry<CollectionKey>,
postB: CollectionEntry<CollectionKey> postB: CollectionEntry<CollectionKey>,
) => { ): number {
if (postA.data.lastUpdate !== postB.data.lastUpdate) { let postATitle = pinyinpro.convert(pinyinpro.pinyin(postA.data.title), {
return postA.data.lastUpdate < postB.data.lastUpdate ? 1 : -1; format: "symbolToNum",
} });
return postA.id.localeCompare(postB.id); let postBTitle = pinyinpro.convert(pinyinpro.pinyin(postB.data.title), {
} format: "symbolToNum",
) });
: object[key].sort( return postATitle.localeCompare(postBTitle, "en");
( }
postA: CollectionEntry<CollectionKey>,
postB: CollectionEntry<CollectionKey> if (sortByTimeline) {
) => { // sort article by update date
return postA.id.localeCompare(postB.id); object[key].sort((postA, postB) => {
} // if two articles is updated on the same day, sort them by title
); if (postA.data.lastUpdate !== postB.data.lastUpdate) {
return postA.data.lastUpdate < postB.data.lastUpdate ? 1 : -1;
}
return sortPostByTitlePinyin(postA, postB);
});
} else if (sortByArticleTitle) {
// sort article by title
// get articles with titles started in Chinese or the alphabet
let postWithAlphabetStartedTitle: CollectionEntry<CollectionKey>[] =
object[key].filter((post) => /^[a-zA-Z]/.test(post.data.title));
let postWithChineseStartedTitle: CollectionEntry<CollectionKey>[] =
object[key].filter((post) => /^[^a-zA-Z]/.test(post.data.title));
// sort two Array by article title
postWithAlphabetStartedTitle.sort((postA, postB) =>
sortPostByTitlePinyin(postA, postB),
);
postWithChineseStartedTitle.sort((postA, postB) =>
sortPostByTitlePinyin(postA, postB),
);
// articles that have title start with Chinese prior to
// those which have titles start with the alphabet
object[key] = postWithChineseStartedTitle.concat(
postWithAlphabetStartedTitle,
);
}
return object; return object;
}); });
@@ -56,7 +83,7 @@ const categoriedPostListSortedByArticleAmount: Array<CategoriedPosts> =
const aKey: string = Object.keys(a)[0]; const aKey: string = Object.keys(a)[0];
const bKey: string = Object.keys(b)[0]; const bKey: string = Object.keys(b)[0];
return b[bKey].length - a[aKey].length; return b[bKey].length - a[aKey].length;
} },
); );
--- ---
@@ -75,17 +102,6 @@ const categoriedPostListSortedByArticleAmount: Array<CategoriedPosts> =
</div> </div>
<style> <style>
@media screen and (max-width: 125ex) {
.collection-desc-item {
width: 100%;
}
}
@media screen and (min-width: 125ex) {
.collection-desc-item {
width: 50%;
float: left;
}
}
.collection-desc::after { .collection-desc::after {
display: block; display: block;
content: ""; content: "";

View File

@@ -0,0 +1,79 @@
---
// get rendered ArticleBodyContent HTML
const html = await Astro.slots.render("default");
// rule:
// 1. If a line belongs to the area where the line break should be kept,
// do not process this line.
// 2. If a line ends with [a-zA-Z], add a space to the end of this line.
// 3. If a line ends without [a-zA-Z] and its next line starts with [a-zA-Z],
// add a heading space to the next line.
// 4. If a line does not start in ASCII chars, join it to its previous line.
// 5. If a line starts with <strong> or <em> or <a>, add a heading space to
// this line.
// 6. If a line ends with </strong> or </em> or </a>, add a tailing space to
// this line.
// This is important in using one newline character to wrap Chinese.
const arr = html.split("\n");
let articleHTMLFinal = arr[0];
let remainIntactArea = false;
// The first line of the HTML string is a table-of-content head recognized by
// remark-toc so it can be ignored.
for (let i = 1; i < arr.length; i++) {
// rule 1
// Check if the current line belongs to some block area.
if (arr[i].match(/^(<pre|<code|<blockquote|<table)/) !== null) {
remainIntactArea = true;
} else {
remainIntactArea = false;
}
// -------- add space
// rule 2
// If the last character of the current line is [a-zA-Z], add a space to the
// end of the line.
if (arr[i].charAt(arr[i].length - 1).match(/[a-zA-Z]/) !== null) {
arr[i] += " ";
}
// rule 3
// The current is not the last line.
// AND
// The current line ends without [a-zA-Z].
// AND
// The next line starts with [a-zA-Z].
if (
i + 1 < arr.length &&
arr[i].charAt(arr[i].length - 1).match(/[a-zA-Z]/) === null &&
arr[i + 1].charAt(0).match(/[a-zA-Z]/) !== null
) {
arr[i] += " ";
}
// rule 5
if (arr[i].match(/^(<em>|<strong>|<a>)/) !== null) {
arr[i] = " " + arr[i];
}
// rule 6
if (arr[i].match(/(<\/em>|<\/strong>|<\/a>)$/) !== null) {
arr[i] = arr[i] + " ";
}
// -------- combine lines
// rule 4
if (
// (
// If the first character is not ascii character,
// OR
// the final character of previous line is not ascii character.
// ) AND
// Current line should not belong to area that remains intact.
(arr[i].charAt(0).match(/[ -~]/) === null ||
arr[i - 1].charAt(arr[i - 1].length - 1).match(/[ -~]/) === null) &&
!remainIntactArea
) {
articleHTMLFinal += arr[i];
} else {
articleHTMLFinal += "\n";
articleHTMLFinal += arr[i];
}
}
---
<Fragment set:html={articleHTMLFinal} />

View File

@@ -1,15 +1,19 @@
--- ---
import { getEntry, type CollectionKey } from "astro:content"; import { getCollection, type CollectionEntry } from "astro:content";
import { render, type CollectionKey } from "astro:content";
interface Props { interface Props {
articleSlug: string; articleId: string;
collectionName: CollectionKey; collectionName: CollectionKey;
} }
const { articleSlug, collectionName } = Astro.props; const { articleId, collectionName } = Astro.props;
const article = await getEntry(collectionName, articleSlug); const articles = await getCollection(collectionName);
const { Content } = await article!.render(); const article = articles.find(
const articlePrettyName = article!.id.split("/").pop()!.replace(".md", ""); (post: CollectionEntry<CollectionKey>) => post.id === articleId
);
const { Content } = await render(article!);
const articlePrettyName = article!.data.title;
--- ---
<div class="article"> <div class="article">
@@ -45,10 +49,14 @@ const articlePrettyName = article!.id.split("/").pop()!.replace(".md", "");
flex-wrap: wrap; flex-wrap: wrap;
} }
/* reduce indent of TOC list */ /* reduce indent of TOC list */
:global(.article-title + ul) { :global(.article-title ~ ul:nth-of-type(1)) {
margin: 1em 0 1em 0; margin: 1em 0 1em 0;
padding: 0 0 0 20px; padding: 0 0 0 20px;
} }
/* hide TOC headint */
:global(.article-title ~ h2:nth-of-type(1)) {
display: none;
}
/* font for all code */ /* font for all code */
:global(.article code) { :global(.article code) {
font-family: "Consolas", "Tahoma", sans-serif; font-family: "Consolas", "Tahoma", sans-serif;
@@ -68,4 +76,15 @@ const articlePrettyName = article!.id.split("/").pop()!.replace(".md", "");
:global(.article li > code) { :global(.article li > code) {
color: #be4750; color: #be4750;
} }
:global(.article table) {
margin: 1em 0;
}
:global(.article blockquote) {
margin: 0;
padding: 0 1em;
background: rgb(246, 246, 246);
border-radius: 6px;
border: 1px solid #aaa;
overflow-x: auto;
}
</style> </style>

View File

@@ -0,0 +1,18 @@
---
import type { CollectionKey } from "astro:content";
import ArticleBody from "./ArticleBody.astro";
import ArticleBodyContent from "./ArticleBodyContent.astro";
interface Props {
articleId: string;
collectionName: CollectionKey;
}
const { articleId, collectionName } = Astro.props;
---
<ArticleBody>
<ArticleBodyContent
articleId={articleId}
collectionName={collectionName}
/>
</ArticleBody>

View File

@@ -0,0 +1,20 @@
---
---
<ul class="tool-list">
<li class="tool-item"><a href="/tool/unit_conversion">单位换算</a></li>
</ul>
<style>
.tool-list {
list-style: none;
padding: 0;
margin: 0.5em 0;
}
.tool-list li {
float: left;
margin: 0 16px 0 0;
}
</style>

View File

@@ -0,0 +1,231 @@
<script setup>
import { computed, ref } from "vue";
import BigNumber from "bignumber.js";
/**
* @typedef {string} UnitNameStr
*/
/**
* @enum {UnitNameStr}
*/
const UnitName = Object.freeze({
KBYTE: "unit-k-byte",
MBYTE: "unit-m-byte",
GBYTE: "unit-g-byte",
TBYTE: "unit-t-byte",
KIBYTE: "unit-ki-byte",
MIBYTE: "unit-mi-byte",
GIBYTE: "unit-gi-byte",
TIBYTE: "unit-ti-byte",
BYTE: "unit-byte",
BIT: "unit-bit",
KBIT: "unit-k-bit",
MBIT: "unit-m-bit",
GBIT: "unit-g-bit",
TBIT: "unit-t-bit",
});
/**
* @enum {number}
*/
const UnitNameToSize = Object.freeze({
[UnitName.KBYTE]: 1000,
[UnitName.MBYTE]: 1000 * 1000,
[UnitName.GBYTE]: 1000 * 1000 * 1000,
[UnitName.TBYTE]: 1000 * 1000 * 1000 * 1000,
[UnitName.KIBYTE]: 1024,
[UnitName.MIBYTE]: 1024 * 1024,
[UnitName.GIBYTE]: 1024 * 1024 * 1024,
[UnitName.TIBYTE]: 1024 * 1024 * 1024 * 1024,
[UnitName.BYTE]: 1,
[UnitName.BIT]: 1 / 8,
[UnitName.KBIT]: 1000 / 8,
[UnitName.MBIT]: 1000000 / 8,
[UnitName.GBIT]: 1000000000 / 8,
[UnitName.TBIT]: 1000000000000 / 8,
});
const size = ref({
[UnitName.KBYTE]: "",
[UnitName.MBYTE]: "",
[UnitName.GBYTE]: "",
[UnitName.TBYTE]: "",
[UnitName.KIBYTE]: "",
[UnitName.MIBYTE]: "",
[UnitName.GIBYTE]: "",
[UnitName.TIBYTE]: "",
[UnitName.BYTE]: "",
[UnitName.BIT]: "",
[UnitName.KBIT]: "",
[UnitName.MBIT]: "",
[UnitName.GBIT]: "",
[UnitName.TBIT]: "",
});
/**
* Change ref variable when user inputs some characters.
* @param {UnitNameStr} unitName
* @returns {void}
*/
function sizeChanged(unitName) {
/**
* @type {object.<UnitNameStr, string>}
*/
let result = {};
// assign 0 to current input if nothing is passed
if (size.value[unitName].length === 0) result[unitName] = "0";
let inputedBytes = new BigNumber(UnitNameToSize[unitName]).multipliedBy(
size.value[unitName],
);
for (const key in UnitNameToSize) {
result[key] = inputedBytes.div(UnitNameToSize[key]).toString();
}
result[unitName] = size.value[unitName];
for (const key in UnitNameToSize) {
if (result[key] !== "NaN") {
size.value[key] = result[key];
} else if (result[key] === "NaN") {
size.value[key] = "";
}
}
}
const isMultipleOf4KiB = computed(() => {
let userInput = new BigNumber(size.value[UnitName.KIBYTE]);
if (userInput.toNumber() === 0) return false;
return userInput.mod(4).toNumber() === 0 ? true : false;
});
</script>
<template>
<h1>数据单位换算</h1>
<div class="unit-conversion-computation-area 1000-in-byte">
<p>1000 进制并以 Byte :</p>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.KBYTE" type="text" v-model="size[UnitName.KBYTE]"
@input="sizeChanged(UnitName.KBYTE)" />
<span class="unit-name">KB</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.MBYTE" type="text" v-model="size[UnitName.MBYTE]"
@input="sizeChanged(UnitName.MBYTE)" />
<span class="unit-name">MB</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.GBYTE" type="text" v-model="size[UnitName.GBYTE]"
@input="sizeChanged(UnitName.GBYTE)" />
<span class="unit-name">GB</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.TBYTE" type="text" v-model="size[UnitName.TBYTE]"
@input="sizeChanged(UnitName.TBYTE)" />
<span class="unit-name">TB</span>
</div>
</div>
<div class="unit-conversion-computation-area 1024-in-byte">
<p>1024 进制并以 Byte :</p>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.KIBYTE" type="text" v-model="size[UnitName.KIBYTE]"
@input="sizeChanged(UnitName.KIBYTE)" />
<span class="unit-name">KiB</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.MIBYTE" type="text" v-model="size[UnitName.MIBYTE]"
@input="sizeChanged(UnitName.MIBYTE)" />
<span class="unit-name">MiB</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.GIBYTE" type="text" v-model="size[UnitName.GIBYTE]"
@input="sizeChanged(UnitName.GIBYTE)" />
<span class="unit-name">GiB</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.TIBYTE" type="text" v-model="size[UnitName.TIBYTE]"
@input="sizeChanged(UnitName.TIBYTE)" />
<span class="unit-name">TiB</span>
</div>
</div>
<div class="unit-conversion-computation-area byte-bit">
<p> Byte (B) / bit (b) :</p>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.BYTE" type="text" v-model="size[UnitName.BYTE]"
@input="sizeChanged(UnitName.BYTE)" />
<span class="unit-name">B</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.BIT" type="text" v-model="size[UnitName.BIT]"
@input="sizeChanged(UnitName.BIT)" />
<span class="unit-name">b</span>
</div>
</div>
<div class="unit-conversion-computation-area 1000-in-bit">
<p>1000 进制并以 bit :</p>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.KBIT" type="text" v-model="size[UnitName.KBIT]"
@input="sizeChanged(UnitName.KBIT)" />
<span class="unit-name">Kb</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.MBIT" type="text" v-model="size[UnitName.MBIT]"
@input="sizeChanged(UnitName.MBIT)" />
<span class="unit-name">Mb</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.GBIT" type="text" v-model="size[UnitName.GBIT]"
@input="sizeChanged(UnitName.GBIT)" />
<span class="unit-name">Gb</span>
</div>
<div class="unit-conversion-computation-node">
<input class="size-text" :id="UnitName.TBIT" type="text" v-model="size[UnitName.TBIT]"
@input="sizeChanged(UnitName.TBIT)" />
<span class="unit-name">Tb</span>
</div>
</div>
<div class="unit-conversion-computation-result">
<p v-show="isMultipleOf4KiB">该结果值是 4 KiB 的倍数</p>
</div>
</template>
<style>
h1,
p {
margin: 0.5em 0;
}
.unit-conversion-computation-node {
margin: 0.5em 0;
}
.unit-conversion-computation-node {
float: left;
width: 170px;
margin: 0 8px 0 0;
}
.unit-conversion-computation-node .size-text {
margin: 0 6px 0 0;
width: 125px;
}
@media screen and (max-width: 395px) {
.unit-conversion-computation-node {
width: 140px;
}
.unit-conversion-computation-node .size-text {
width: 95px;
}
}
.unit-conversion-computation-area::after {
content: "";
display: block;
clear: both;
}
.unit-conversion-computation-result {
margin: 0.5em 0 0.5em 0;
height: 1em;
}
</style>

View File

@@ -0,0 +1,10 @@
---
import DataUnitConversion from "./DataUnitConversion.vue";
import WeightUnitConversion from "./WeightUnitConversion.vue";
---
<WeightUnitConversion client:load />
<br />
<DataUnitConversion client:load />

View File

@@ -0,0 +1,152 @@
<script setup>
import { computed, ref } from "vue";
import BigNumber from "bignumber.js";
/**
* @typedef {string} UnitNameStr
*/
/**
* @enum {UnitNameStr}
*/
const twToMetricUnitName = Object.freeze({
JIN: "jin",
LIANG: "liang",
QIAN: "qian",
FEN: "fen",
GRAM: "gram",
KILOGRAM: "kilogram",
});
/**
* @enum {number}
*/
const twToMetricUnitNameToWeight = Object.freeze({
[twToMetricUnitName.JIN]: 600,
[twToMetricUnitName.LIANG]: 600 / 16,
[twToMetricUnitName.QIAN]: 600 / 16 / 10,
[twToMetricUnitName.FEN]: 600 / 16 / 10 / 10,
[twToMetricUnitName.GRAM]: 1,
[twToMetricUnitName.KILOGRAM]: 1 * 1000,
});
const weight = ref({
[twToMetricUnitName.JIN]: "",
[twToMetricUnitName.LIANG]: "",
[twToMetricUnitName.QIAN]: "",
[twToMetricUnitName.FEN]: "",
[twToMetricUnitName.GRAM]: "",
[twToMetricUnitName.KILOGRAM]: "",
});
/**
* Change ref variable when user inputs some characters.
* @param {UnitNameStr} unitName
* @returns {void}
*/
function weightChanged(unitName) {
/**
* @type {object.<UnitNameStr, string>}
*/
let result = {};
// assign 0 to current input if nothing is passed
if (weight.value[unitName].length === 0) result[unitName] = "0";
let inputedGram = new BigNumber(
twToMetricUnitNameToWeight[unitName],
).multipliedBy(weight.value[unitName]);
for (const key in twToMetricUnitNameToWeight) {
result[key] = inputedGram.div(twToMetricUnitNameToWeight[key]).toString();
}
result[unitName] = weight.value[unitName];
for (const key in twToMetricUnitNameToWeight) {
if (result[key] !== "NaN") {
weight.value[key] = result[key];
} else if (result[key] === "NaN") {
weight.value[key] = "";
}
}
}
</script>
<template>
<h1>重量换算</h1>
<div class="weight-conversion-computation-area metric">
<p>公制重量:</p>
<div class="weight-conversion-computation-node">
<input class="size-text" :id="twToMetricUnitName.KILOGRAM" type="text"
v-model="weight[twToMetricUnitName.KILOGRAM]" @input="weightChanged(twToMetricUnitName.KILOGRAM)" />
<span class="unit-name">千克</span>
</div>
<div class="weight-conversion-computation-node">
<input class="size-text" :id="twToMetricUnitName.GRAM" type="text" v-model="weight[twToMetricUnitName.GRAM]"
@input="weightChanged(twToMetricUnitName.GRAM)" />
<span class="unit-name"></span>
</div>
</div>
<div class="weight-conversion-computation-area tw">
<p>台湾制重量:</p>
<div class="weight-conversion-computation-node">
<input class="size-text" :id="twToMetricUnitName.JIN" type="text" v-model="weight[twToMetricUnitName.JIN]"
@input="weightChanged(twToMetricUnitName.JIN)" />
<span class="unit-name"></span>
</div>
<div class="weight-conversion-computation-node">
<input class="size-text" :id="twToMetricUnitName.LIANG" type="text" v-model="weight[twToMetricUnitName.LIANG]"
@input="weightChanged(twToMetricUnitName.LIANG)" />
<span class="unit-name"></span>
</div>
<div class="weight-conversion-computation-node">
<input class="size-text" :id="twToMetricUnitName.QIAN" type="text" v-model="weight[twToMetricUnitName.QIAN]"
@input="weightChanged(twToMetricUnitName.QIAN)" />
<span class="unit-name"></span>
</div>
<div class="weight-conversion-computation-node">
<input class="size-text" :id="twToMetricUnitName.FEN" type="text" v-model="weight[twToMetricUnitName.FEN]"
@input="weightChanged(twToMetricUnitName.FEN)" />
<span class="unit-name"></span>
</div>
</div>
</template>
<style>
h1,
p {
margin: 0.5em 0;
}
.weight-conversion-computation-node {
margin: 0.5em 0;
}
.weight-conversion-computation-node {
float: left;
width: 170px;
margin: 0 8px 0 0;
}
.weight-conversion-computation-node .size-text {
margin: 0 6px 0 0;
width: 115px;
}
@media screen and (max-width: 395px) {
.weight-conversion-computation-node {
width: 140px;
}
.weight-conversion-computation-node .size-text {
width: 85px;
}
}
.weight-conversion-computation-area::after {
content: "";
display: block;
clear: both;
}
.weight-conversion-computation-result {
margin: 0.5em 0 0.5em 0;
height: 1em;
}
</style>

View File

@@ -1,6 +1,6 @@
--- ---
import HTMLCommonHead from "../components/HTMLCommonHead.astro"; import HTMLCommonHead from "../components/HTMLCommonHead.astro";
import "../styles/global.css" import "../styles/global.css";
interface Props { interface Props {
title: string; title: string;
@@ -11,7 +11,7 @@ const { title } = Astro.props;
<!doctype html> <!doctype html>
<html lang="zh"> <html lang="zh">
<head> <head>
<HTMLCommonHead title={title}/> <HTMLCommonHead title={title} />
</head> </head>
<body> <body>
<slot /> <slot />

View File

@@ -2,11 +2,11 @@
import DefaultLayout from "../layouts/DefaultLayout.astro"; import DefaultLayout from "../layouts/DefaultLayout.astro";
import Nav from "../components/Nav.astro"; import Nav from "../components/Nav.astro";
import Footer from "../components/Footer.astro"; import Footer from "../components/Footer.astro";
import ArticleBody from "../components/article/ArticleBody.astro"; import ArticleBodyWrapper from "../components/article/article_body/ArticleBodyWrapper.astro";
--- ---
<DefaultLayout title="404"> <DefaultLayout title="404">
<Nav /> <Nav />
<ArticleBody collectionName="site" articleSlug="404" /> <ArticleBodyWrapper collectionName="site" articleId="404.md" />
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>

View File

@@ -1,34 +1,31 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import type { import type { CollectionEntry, CollectionKey } from "astro:content";
CollectionEntry,
CollectionKey,
ContentEntryMap,
} from "astro:content";
import Footer from "../../../components/Footer.astro"; import Footer from "../../../components/Footer.astro";
import Nav from "../../../components/Nav.astro"; import Nav from "../../../components/Nav.astro";
import ArticleBody from "../../../components/article/ArticleBody.astro"; import ArticleBodyWrapper from "../../../components/article/article_body/ArticleBodyWrapper.astro";
import DefaultLayout from "../../../layouts/DefaultLayout.astro"; import DefaultLayout from "../../../layouts/DefaultLayout.astro";
export async function getStaticPaths() { export async function getStaticPaths() {
const collectionName: CollectionKey = "blog"; const collectionName: CollectionKey = "blog";
const blogEntries = await getCollection(collectionName); const blogEntries = await getCollection(collectionName);
return blogEntries.map((entry) => ({ return blogEntries.map((post: CollectionEntry<CollectionKey>) => ({
params: { blog: entry.slug }, // Hide .md extension from URL
props: { entry }, params: { blog: post.id.replace(".md", "") },
props: { post },
})); }));
} }
interface Props { interface Props {
entry: CollectionEntry<keyof ContentEntryMap>; post: CollectionEntry<CollectionKey>;
} }
const { entry } = Astro.props; const { post } = Astro.props;
const articlePrettyName:string = entry.id.split("/").pop()!.replace(".md", ""); const articlePrettyName: string = post.data.title;
--- ---
<DefaultLayout title=`${articlePrettyName} - 李守中`> <DefaultLayout title=`${articlePrettyName} - 李守中`>
<Nav /> <Nav />
<ArticleBody collectionName="blog" articleSlug={entry.slug} /> <ArticleBodyWrapper collectionName="blog" articleId={post.id} />
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>

View File

@@ -1,21 +1,37 @@
--- ---
import Footer from '../../../components/Footer.astro'; import Footer from "../../../components/Footer.astro";
import Nav from '../../../components/Nav.astro'; import Nav from "../../../components/Nav.astro";
import CollectionSummary from '../../../components/article/CollectionSummary.astro'; import CollectionSummary from "../../../components/article/CollectionSummary.astro";
import DefaultLayout from '../../../layouts/DefaultLayout.astro'; import DefaultLayout from "../../../layouts/DefaultLayout.astro";
--- ---
<DefaultLayout title="博客 - 李守中"> <DefaultLayout title="博客 - 李守中">
<Nav /> <Nav />
<div class="options"> <div class="options">
<span>当前: [按日期降序] <a href="/article/blog/sort_by_name">按名字排序</a></span> <span>当前: [按日期降序]</span>
<a href="/article/blog/sort_by_name">按名字排序</a>
</div> </div>
<CollectionSummary collectionName="blog" sortedByTimeline={true}/> <CollectionSummary
collectionName="blog"
sortByTimeline={true}
sortByArticleTitle={false}
/>
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>
<style> <style>
:global(@media screen and (max-width: 768px)) {
.collection-desc-item {
width: 100%;
}
}
:global(@media screen and (min-width: 768px)) {
.collection-desc-item {
width: 50%;
float: left;
}
}
.options { .options {
margin: 1em 0 0 0; margin: 0.5em 0 0 0;
} }
</style> </style>

View File

@@ -1,21 +1,37 @@
--- ---
import Footer from '../../../components/Footer.astro'; import Footer from "../../../components/Footer.astro";
import Nav from '../../../components/Nav.astro'; import Nav from "../../../components/Nav.astro";
import CollectionSummary from '../../../components/article/CollectionSummary.astro'; import CollectionSummary from "../../../components/article/CollectionSummary.astro";
import DefaultLayout from '../../../layouts/DefaultLayout.astro'; import DefaultLayout from "../../../layouts/DefaultLayout.astro";
--- ---
<DefaultLayout title="博客 - 李守中"> <DefaultLayout title="博客 - 李守中">
<Nav /> <Nav />
<div class="options"> <div class="options">
<span>当前: [按名字排序] <a href="/article/blog/">按日期降序</a></span> <span>当前: [按名字排序]</span>
<a href="/article/blog/">按日期降序</a>
</div> </div>
<CollectionSummary collectionName="blog" sortedByTimeline={false}/> <CollectionSummary
collectionName="blog"
sortByTimeline={false}
sortByArticleTitle={true}
/>
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>
<style> <style>
:global(@media screen and (max-width: 768px)) {
.collection-desc-item {
width: 100%;
}
}
:global(@media screen and (min-width: 768px)) {
.collection-desc-item {
width: 50%;
float: left;
}
}
.options { .options {
margin: 1em 0 0 0; margin: 0.5em 0 0 0;
} }
</style> </style>

View File

@@ -1,34 +1,30 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import type { import type { CollectionEntry, CollectionKey } from "astro:content";
CollectionEntry,
CollectionKey,
ContentEntryMap,
} from "astro:content";
import Footer from "../../../components/Footer.astro"; import Footer from "../../../components/Footer.astro";
import Nav from "../../../components/Nav.astro"; import Nav from "../../../components/Nav.astro";
import ArticleBody from "../../../components/article/ArticleBody.astro"; import ArticleBodyWrapper from "../../../components/article/article_body/ArticleBodyWrapper.astro";
import DefaultLayout from "../../../layouts/DefaultLayout.astro"; import DefaultLayout from "../../../layouts/DefaultLayout.astro";
export async function getStaticPaths() { export async function getStaticPaths() {
const collectionName: CollectionKey = "note"; const collectionName: CollectionKey = "note";
const blogEntries = await getCollection(collectionName); const blogEntries = await getCollection(collectionName);
return blogEntries.map((entry) => ({ return blogEntries.map((post: CollectionEntry<CollectionKey>) => ({
params: { note: entry.slug }, params: { note: post.id.replace(".md", "") },
props: { entry }, props: { post },
})); }));
} }
interface Props { interface Props {
entry: CollectionEntry<keyof ContentEntryMap>; post: CollectionEntry<CollectionKey>;
} }
const { entry } = Astro.props; const { post } = Astro.props;
const articlePrettyName:string = entry.id.split("/").pop()!.replace(".md", ""); const articlePrettyName: string = post.data.title;
--- ---
<DefaultLayout title=`${articlePrettyName} - 李守中`> <DefaultLayout title=`${articlePrettyName} - 李守中`>
<Nav /> <Nav />
<ArticleBody collectionName="note" articleSlug={entry.slug} /> <ArticleBodyWrapper collectionName="note" articleId={post.id} />
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>

View File

@@ -1,21 +1,37 @@
--- ---
import Footer from '../../../components/Footer.astro'; import Footer from "../../../components/Footer.astro";
import Nav from '../../../components/Nav.astro'; import Nav from "../../../components/Nav.astro";
import CollectionSummary from '../../../components/article/CollectionSummary.astro'; import CollectionSummary from "../../../components/article/CollectionSummary.astro";
import DefaultLayout from '../../../layouts/DefaultLayout.astro'; import DefaultLayout from "../../../layouts/DefaultLayout.astro";
--- ---
<DefaultLayout title="笔记 - 李守中"> <DefaultLayout title="笔记 - 李守中">
<Nav /> <Nav />
<div class="options"> <div class="options">
<span>当前: [按名字排序] <a href="/article/note/sort_by_timeline">按日期降序</a></span> <span>当前: [按名字排序]</span>
<a href="/article/note/sort_by_timeline">按日期降序</a>
</div> </div>
<CollectionSummary collectionName="note" sortedByTimeline={false}/> <CollectionSummary
collectionName="note"
sortByTimeline={false}
sortByArticleTitle={true}
/>
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>
<style> <style>
:global(@media screen and (max-width: 768px)) {
.collection-desc-item {
width: 100%;
}
}
:global(@media screen and (min-width: 768px)) {
.collection-desc-item {
width: 50%;
float: left;
}
}
.options { .options {
margin: 1em 0 0 0; margin: 0.5em 0 0 0;
} }
</style> </style>

View File

@@ -1,21 +1,37 @@
--- ---
import Footer from '../../../components/Footer.astro'; import Footer from "../../../components/Footer.astro";
import Nav from '../../../components/Nav.astro'; import Nav from "../../../components/Nav.astro";
import CollectionSummary from '../../../components/article/CollectionSummary.astro'; import CollectionSummary from "../../../components/article/CollectionSummary.astro";
import DefaultLayout from '../../../layouts/DefaultLayout.astro'; import DefaultLayout from "../../../layouts/DefaultLayout.astro";
--- ---
<DefaultLayout title="笔记 - 李守中"> <DefaultLayout title="笔记 - 李守中">
<Nav /> <Nav />
<div class="options"> <div class="options">
<span>当前: [按日期降序] <a href="/article/note/">按名字排序</a></span> <span>当前: [按日期降序]</span>
<a href="/article/note/">按名字排序</a>
</div> </div>
<CollectionSummary collectionName="note" sortedByTimeline={true}/> <CollectionSummary
collectionName="note"
sortByTimeline={true}
sortByArticleTitle={false}
/>
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>
<style> <style>
:global(@media screen and (max-width: 768px)) {
.collection-desc-item {
width: 100%;
}
}
:global(@media screen and (min-width: 768px)) {
.collection-desc-item {
width: 50%;
float: left;
}
}
.options { .options {
margin: 1em 0 0 0; margin: 0.5em 0 0 0;
} }
</style> </style>

View File

@@ -1,34 +0,0 @@
---
import { getCollection } from "astro:content";
import type {
CollectionEntry,
CollectionKey,
ContentEntryMap,
} from "astro:content";
import Footer from "../../../components/Footer.astro";
import Nav from "../../../components/Nav.astro";
import ArticleBody from "../../../components/article/ArticleBody.astro";
import DefaultLayout from "../../../layouts/DefaultLayout.astro";
export async function getStaticPaths() {
const collectionName: CollectionKey = "translation";
const blogEntries = await getCollection(collectionName);
return blogEntries.map((entry) => ({
params: { translation: entry.slug },
props: { entry },
}));
}
interface Props {
entry: CollectionEntry<keyof ContentEntryMap>;
}
const { entry } = Astro.props;
const articlePrettyName:string = entry.id.split("/").pop()!.replace(".md", "");
---
<DefaultLayout title=`${articlePrettyName} - 李守中`>
<Nav />
<ArticleBody collectionName="translation" articleSlug={entry.slug} />
<Footer />
</DefaultLayout>

View File

@@ -1,21 +1,172 @@
--- ---
import Footer from '../../../components/Footer.astro'; import Footer from "../../../components/Footer.astro";
import Nav from '../../../components/Nav.astro'; import Nav from "../../../components/Nav.astro";
import CollectionSummary from '../../../components/article/CollectionSummary.astro'; import DefaultLayout from "../../../layouts/DefaultLayout.astro";
import DefaultLayout from '../../../layouts/DefaultLayout.astro';
--- ---
<DefaultLayout title="翻译 - 李守中"> <DefaultLayout title="翻译 - 李守中">
<Nav /> <Nav />
<div class="options"> <div class="collection-desc">
<span>当前: [按名字排序] <a href="/article/translation/sort_by_timeline">按日期降序</a></span> <div class="collection-desc-item">
<h1>freebsd</h1>
<ul>
<li>
<a
href="/file_share/translation/FreeBSD 13 NFS exports man(5) page 2021 中文译本.html"
target="_blank"
>
FreeBSD 13 NFS exports man(5) 2021 中文译本
</a>
</li><li>
<a
href="/file_share/translation/FreeBSD 13 NFS mountd man(8) page 2020 中文译本.html"
target="_blank"
>
FreeBSD 13 NFS mountd man(8) 2020 中文译本
</a>
</li><li>
<a
href="/file_share/translation/FreeBSD 13 NFS nfsd man(8) page 2019 中文译本.html"
target="_blank"
>
FreeBSD 13 NFS nfsd man(8) 2019 中文译本
</a>
</li><li>
<a
href="/file_share/translation/FreeBSD 13 NFS showmount man(8) page 2016 中文译本.html"
target="_blank"
>
FreeBSD 13 NFS showmount man(8) 2016 中文译本
</a>
</li><li>
<a
href="/file_share/translation/FreeBSD 13 NFSv4 man(4) page 2019 中文译本.html"
target="_blank"
>
FreeBSD 13 NFSv4 man(4) 2019 中文译本
</a>
</li><li>
<a
href="/file_share/translation/FreeBSD 13 NFSv4 nfscbd man(8) page 2009 中文译本.html"
target="_blank"
>
FreeBSD 13 NFSv4 nfscbd man(8) 2009 中文译本
</a>
</li><li>
<a
href="/file_share/translation/FreeBSD 13 NFSv4 nfsrevoke man(8) page 2009 中文译本.html"
target="_blank"
>
FreeBSD 13 NFSv4 nfsrevoke man(8) 2020 中文译本
</a>
</li><li>
<a
href="/file_share/translation/FreeBSD 13 NFSv4 nfsuserd man(8) page 2019 中文译本.html"
target="_blank"
>
FreeBSD 13 NFSv4 nfsuserd man(8) 2019 中文译本
</a>
</li><li>
<a
href="/file_share/translation/FreeBSD 13 rpcbind man(8) page 2017 中文译本.html"
target="_blank"
>
FreeBSD 13 rpcbind man(8) 2017 中文译本
</a>
</li>
</ul>
</div><div class="collection-desc-item">
<h1>easyrsa</h1>
<ul>
<li>
<a
href="/file_share/translation/EasyRSA Intro-To-PKI v3.08 中文译本.html"
target="_blank"
>
EasyRSA Intro-To-PKI v3.08 中文译本
</a>
</li><li>
<a
href="/file_share/translation/EasyRSA-Advanced v3.08 中文译本.html"
target="_blank"
>
EasyRSA-Advanced v3.08 中文译本
</a>
</li><li>
<a
href="/file_share/translation/EasyRSA-Readme v3.08 中文译本.html"
target="_blank"
>
EasyRSA-Readme v3.08 中文译本
</a>
</li><li>
<a
href="/file_share/translation/EasyRSA-Upgrade-Notes v3.08 中文译本.html"
target="_blank"
>
EasyRSA-Upgrade-Notes v3.08 中文译本
</a>
</li>
</ul>
</div><div class="collection-desc-item">
<h1>uncategorized</h1>
<ul>
<li>
<a
href="/file_share/translation/iperf3 v3.9 man page 中文译本.html"
target="_blank"
>
iperf3 v3.9 man(1) 中文译本
</a>
</li><li>
<a
href="/file_share/translation/PostgreSQL Don't Do This 中文译本.html"
target="_blank"
>
PostgreSQL Don't Do This 中文译本
</a>
</li><li>
<a
href="/file_share/translation/rsync v3.2.7 man(1) page 中文译本.html"
target="_blank"
>
rsync v3.2.7 man(1) 中文译本
</a>
</li>
</ul>
</div>
</div> </div>
<CollectionSummary collectionName="translation" sortedByTimeline={false}/>
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>
<style> <style>
:global(@media screen and (max-width: 1024px)) {
.collection-desc-item {
width: 100%;
}
}
:global(@media screen and (min-width: 1024px)) {
.collection-desc-item {
width: 50%;
float: left;
}
}
.options { .options {
margin: 1em 0 0 0; margin: 0.5em 0 0 0;
}
.collection-desc::after {
display: block;
content: "";
clear: both;
}
ul {
padding: 0;
list-style-type: none;
}
h1 {
margin: 0.5em 0 0 0;
} }
</style> </style>

View File

@@ -1,21 +0,0 @@
---
import Footer from '../../../components/Footer.astro';
import Nav from '../../../components/Nav.astro';
import CollectionSummary from '../../../components/article/CollectionSummary.astro';
import DefaultLayout from '../../../layouts/DefaultLayout.astro';
---
<DefaultLayout title="翻译 - 李守中">
<Nav />
<div class="options">
<span>当前: [按日期降序] <a href="/article/translation/">按名字排序</a></span>
</div>
<CollectionSummary collectionName="translation" sortedByTimeline={true}/>
<Footer />
</DefaultLayout>
<style>
.options {
margin: 1em 0 0 0;
}
</style>

View File

@@ -2,11 +2,11 @@
import DefaultLayout from "../layouts/DefaultLayout.astro"; import DefaultLayout from "../layouts/DefaultLayout.astro";
import Nav from "../components/Nav.astro"; import Nav from "../components/Nav.astro";
import Footer from "../components/Footer.astro"; import Footer from "../components/Footer.astro";
import ArticleBody from "../components/article/ArticleBody.astro"; import ArticleBodyWrapper from "../components/article/article_body/ArticleBodyWrapper.astro";
--- ---
<DefaultLayout title="李守中"> <DefaultLayout title="李守中">
<Nav /> <Nav />
<ArticleBody collectionName="site" articleSlug="关于本站" /> <ArticleBodyWrapper collectionName="site" articleId="关于本站.md" />
<Footer /> <Footer />
</DefaultLayout> </DefaultLayout>

View File

@@ -0,0 +1,14 @@
---
import DefaultLayout from "../../layouts/DefaultLayout.astro";
import Footer from "../../components/Footer.astro";
import Nav from "../../components/Nav.astro";
import UnitConversionWrapper from "../../components/tool/unit_conversion/UnitConversionWrapper.astro";
import ToolNav from "../../components/tool/ToolNav.astro";
---
<DefaultLayout title="工具 - 李守中">
<Nav />
<ToolNav />
<UnitConversionWrapper />
<Footer />
</DefaultLayout>

View File

@@ -0,0 +1,14 @@
---
import DefaultLayout from "../../layouts/DefaultLayout.astro";
import Footer from "../../components/Footer.astro";
import Nav from "../../components/Nav.astro";
import UnitConversionWrapper from "../../components/tool/unit_conversion/UnitConversionWrapper.astro";
import ToolNav from "../../components/tool/ToolNav.astro";
---
<DefaultLayout title="单位换算 - 李守中">
<Nav />
<ToolNav />
<UnitConversionWrapper />
<Footer />
</DefaultLayout>

View File

@@ -1,12 +1,12 @@
@media screen and (max-width: 125ex) { @media screen and (max-width: 1024px) {
body { body {
width: 95%; width: 95%;
} }
} }
@media screen and (min-width: 125ex) { @media screen and (min-width: 1024px) {
body { body {
width: 125ex; width: 954px;
} }
} }
@@ -17,13 +17,22 @@ html {
body { body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-family: "Consolas", "Hack", -apple-system, font-family:
BlinkMacSystemFont, Tahoma, Arial, "Hiragino Sans GB", "Microsoft YaHei", "Consolas",
"WenQuanYi Micro Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Hack",
"Segoe UI Symbol", "Noto Color Emoji"; Tahoma,
Arial,
"Hiragino Sans GB",
"Microsoft YaHei",
"WenQuanYi Micro Hei",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
font-size: 1em; font-size: 1em;
margin: auto; margin: auto;
overflow-y: scroll; padding: 0 8px 0 8px;
min-height: 100%; min-height: 100%;
line-height: 1.5; line-height: 1.5;
} }

View File

@@ -1,3 +1,8 @@
{ {
"extends": "astro/tsconfigs/strict" "extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"jsx": "preserve"
}
} }