Commit 7507609
Changed files (10)
src
components
aside
siteinfo
i18n
pages
posts
types
utils
src/components/aside/siteinfo/Stats.astro
@@ -1,5 +1,5 @@
---
-import { asideConfig, siteConfig } from '@/config';
+import { asideConfig, buildConfig, siteConfig } from '@/config';
import I18nKey from '@i18n/I18nKey';
import { i18n } from '@i18n/translation';
import { getPostsCount } from '@utils/content-utils';
@@ -46,7 +46,11 @@ import { getCollection, render } from 'astro:content';
</div>
<div class="stat-value text-base">
{(async () => {
- const entries = await getCollection('posts');
+ const posts = await getCollection('posts');
+ const drafts = await getCollection('drafts');
+ const onDev = import.meta.env.DEV;
+ const entries =
+ onDev && buildConfig.showDraftsOnDev ? [...posts, ...drafts] : posts;
const words = await Promise.all(
entries.map(async (entry) => {
const { remarkPluginFrontmatter } = await render(entry);
src/i18n/langs/en.ts
@@ -46,4 +46,7 @@ export const en: Translation = {
[Key.author]: 'Author',
[Key.publishedAt]: 'Published at',
[Key.license]: 'License',
+
+ [Key.draftDevNote]:
+ 'This is a draft and will only be displayed in `DEV` mode. To disable draft preview, please modify `buildConfig.showDraftsOnDev` to `false` in `src/config.ts`.',
};
src/i18n/langs/zh_CN.ts
@@ -46,4 +46,7 @@ export const zh_CN: Translation = {
[Key.author]: '作者',
[Key.publishedAt]: '发布于',
[Key.license]: '许可协议',
+
+ [Key.draftDevNote]:
+ '这是一篇草稿,只会在 `DEV` 模式下显示。关闭草稿预览,请修改 `src/config.ts` 中的 `buildConfig.showDraftsOnDev` 为 `false`。',
};
src/i18n/langs/zh_TW.ts
@@ -46,4 +46,7 @@ export const zh_TW: Translation = {
[Key.author]: '作者',
[Key.publishedAt]: '發佈於',
[Key.license]: '許可協議',
+
+ [Key.draftDevNote]:
+ '這是一篇草稿,只會在 `DEV` 模式下顯示。關閉草稿預覽,請修改 `src/config.ts` 中的 `buildConfig.showDraftsOnDev` 為 `false`。',
};
src/i18n/I18nKey.ts
@@ -43,6 +43,9 @@ enum I18nKey {
author = 'author',
publishedAt = 'publishedAt',
license = 'license',
+
+ /** Note in the top of drafts content in dev mode. This key supports markdown syntax, using `markdown-it`. */
+ draftDevNote = 'draftDevNote',
}
export default I18nKey;
src/pages/posts/[article].astro
@@ -1,22 +1,36 @@
---
-import { siteConfig } from '@/config';
+import { buildConfig, siteConfig } from '@/config';
import License from '@components/misc/License.astro';
import PostInfo from '@components/misc/PostInfo.astro';
import ImageWrapper from '@components/utils/ImageWrapper.astro';
import Markdown from '@components/utils/Markdown.astro';
+import I18nKey from '@i18n/I18nKey';
+import { i18n } from '@i18n/translation';
import PostPageLayout from '@layouts/PostPageLayout.astro';
+import { Icon } from 'astro-icon/components';
import { getCollection, render } from 'astro:content';
+import dayjs from 'dayjs';
+import MarkdownIt from 'markdown-it';
import path from 'path';
export async function getStaticPaths() {
- const articles = await getCollection('posts');
+ const posts = (await getCollection('posts')).map((post) => ({
+ article: post,
+ isDraft: false,
+ }));
+ const drafts = (await getCollection('drafts')).map((post) => ({
+ article: post,
+ isDraft: true,
+ }));
+ const onDev = import.meta.env.DEV;
+ const articles = onDev && buildConfig.showDraftsOnDev ? [...posts, ...drafts] : posts;
return articles.map((article) => ({
- params: { article: article.data.slug },
- props: { article },
+ params: { article: article.article.data.slug },
+ props: { article: article.article, isDraft: article.isDraft },
}));
}
-const { article } = Astro.props;
+const { article, isDraft } = Astro.props;
const { Content, headings, remarkPluginFrontmatter } = await render(article);
const hasCover =
article.data.cover !== '' && article.data.cover !== undefined && article.data.cover !== null;
@@ -26,6 +40,10 @@ const coverSrc = hasCover
: article.data.cover
: undefined;
const description = article.data.description || remarkPluginFrontmatter.excerpt;
+const publishTime =
+ 'published' in article.data
+ ? article.data.published
+ : dayjs(remarkPluginFrontmatter.createAt).toDate();
---
<PostPageLayout
@@ -39,7 +57,7 @@ const description = article.data.description || remarkPluginFrontmatter.excerpt;
<Fragment slot="header-content">
<PostInfo
title={article.data.title}
- publishedAt={article.data.published}
+ publishedAt={publishTime}
category={article.data.category}
tags={article.data.tags}
wordCount={remarkPluginFrontmatter.words}
@@ -54,7 +72,18 @@ const description = article.data.description || remarkPluginFrontmatter.excerpt;
)
}
<Markdown>
+ {
+ isDraft && (
+ <div class="admonition admonition-note">
+ <p class="admonition-title">
+ <Icon name="material-symbols:info-outline-rounded" />
+ NOTE
+ </p>
+ <Fragment set:html={new MarkdownIt().render(i18n(I18nKey.draftDevNote)!)} />
+ </div>
+ )
+ }
<Content />
</Markdown>
- <License time={article.data.published} lang={article.data.lang} />
+ <License time={publishTime} lang={article.data.lang} />
</PostPageLayout>
src/types/config.ts
@@ -179,6 +179,15 @@ export type SiteConfig = {
};
};
+export type BuildConfig = {
+ /**
+ * Whether to show drafts on development mode.
+ *
+ * 是否在开发模式下显示草稿。
+ */
+ showDraftsOnDev: boolean;
+};
+
export type ProfileConfig = {
/**
* The avatar of the profile.
src/utils/content-utils.ts
@@ -1,12 +1,31 @@
+import { buildConfig } from '@/config';
import type { BlogPostData } from '@/types/data';
import type { BlogPost } from '@/types/data';
import I18nKey from '@i18n/I18nKey';
import { i18n } from '@i18n/translation';
-import { getCollection } from 'astro:content';
+import { getCollection, render } from 'astro:content';
+import dayjs from 'dayjs';
import path from 'path';
export async function getSortedPosts(): Promise<BlogPost[]> {
- const allBlogPosts = (await getCollection('posts')) as unknown as BlogPost[];
+ let allBlogPosts;
+ const posts = (await getCollection('posts')) as unknown as BlogPost[];
+ const onDev = import.meta.env.DEV;
+
+ if (onDev && buildConfig.showDraftsOnDev) {
+ const draftEntries = await getCollection('drafts');
+ const drafts = await Promise.all(
+ draftEntries.map(async (draft) => {
+ const { remarkPluginFrontmatter } = await render(draft);
+ const published = dayjs(remarkPluginFrontmatter.createAt as string).toDate();
+ const data = Object.assign(draft.data, { published });
+ return Object.assign(draft, { data }) as unknown as BlogPost;
+ })
+ );
+ allBlogPosts = [...posts, ...drafts];
+ } else {
+ allBlogPosts = posts;
+ }
const sortedBlogPosts = allBlogPosts.sort(
(a: { data: BlogPostData }, b: { data: BlogPostData }) => {
const dateA = new Date(a.data.published);
src/config.ts
@@ -1,6 +1,7 @@
import type {
ArticleConfig,
AsideConfig,
+ BuildConfig,
CommentConfig,
FooterConfig,
LicenseConfig,
@@ -38,6 +39,10 @@ export const siteConfig: SiteConfig = {
},
};
+export const buildConfig: BuildConfig = {
+ showDraftsOnDev: true,
+};
+
export const profileConfig: ProfileConfig = {
avatar: 'assets/img/avatar.jpg',
name: 'Lorem Ipsum',
src/content.config.ts
@@ -10,7 +10,23 @@ const postsCollection = defineCollection({
title: z.string(),
slug: z.string(),
published: z.date(),
- draft: z.boolean().optional().default(false),
+ description: z.string().optional().default(''),
+ cover: z.string().optional().default(''),
+ tags: z.array(z.string()).optional().default([]),
+ category: z.string().optional().default(''),
+ lang: z.string().optional().default(''),
+ comment: z.boolean().optional().default(true),
+ }),
+});
+
+const draftsCollection = defineCollection({
+ loader: glob({
+ pattern: '**/*.{md,mdx}',
+ base: 'src/content/drafts',
+ }),
+ schema: z.object({
+ title: z.string(),
+ slug: z.string(),
description: z.string().optional().default(''),
cover: z.string().optional().default(''),
tags: z.array(z.string()).optional().default([]),
@@ -34,5 +50,6 @@ const specCollection = defineCollection({
export const collections = {
posts: postsCollection,
+ drafts: draftsCollection,
spec: specCollection,
};