valaxy 0.25.8 → 0.25.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,8 @@
1
1
  import type { CollectionConfig } from '../define'
2
- import { ref } from 'vue'
2
+ import collections from '#valaxy/blog/collections'
3
+
4
+ import { computed } from 'vue'
5
+ import { useRoute } from 'vue-router'
3
6
 
4
7
  /**
5
8
  * Composable for Collections
@@ -7,22 +10,28 @@ import { ref } from 'vue'
7
10
  * @example /collections/love-letters/1
8
11
  */
9
12
  export function useCollections() {
10
- // TODO
13
+ return {
14
+ collections: computed(() => collections),
15
+ }
16
+ }
17
+
18
+ /**
19
+ * 获取当前集合
20
+ */
21
+ export function useCollection() {
22
+ const route = useRoute()
23
+ const collectionId = computed(() => {
24
+ if (route.path.startsWith('/collections/')) {
25
+ return route.path.split('/')[2] // 获取集合 ID
26
+ }
27
+ return ''
28
+ })
11
29
 
12
- const collections = ref<CollectionConfig[]>([
13
- {
14
- id: 'i-and-she',
15
- name: 'I and She',
16
- description: 'Love letters from the past',
17
- },
18
- {
19
- id: 'love-and-peace',
20
- name: '爱与和平',
21
- description: 'Recipes for a good life',
22
- },
23
- ])
30
+ const collection = computed<CollectionConfig>(() => {
31
+ return collections.find(item => item.key === collectionId.value)
32
+ })
24
33
 
25
34
  return {
26
- collections,
35
+ collection,
27
36
  }
28
37
  }
@@ -1,9 +1,12 @@
1
1
  export interface CollectionConfig {
2
+ title?: string
3
+ key?: string
2
4
  /**
3
- * auto-generated by your collection path
4
- * @example /collections/love-letters/1 => id: 'love-letters'
5
+ * if key is not provided, path is required
6
+ *
7
+ * if key is provided, path = `/collections/${key}`
5
8
  */
6
- id?: string
9
+ path?: string
7
10
  /**
8
11
  * @en
9
12
  * The name of the collection.
@@ -16,6 +19,19 @@ export interface CollectionConfig {
16
19
  description?: string
17
20
  categories?: string[]
18
21
  tags?: string[]
22
+
23
+ /**
24
+ * items
25
+ */
26
+ items?: {
27
+ title?: string
28
+ /**
29
+ * 合集文章的唯一索引
30
+ *
31
+ * 对应路径为 `/collections/${key}/${item.key}`
32
+ */
33
+ key?: string
34
+ }[]
19
35
  }
20
36
 
21
37
  /**
@@ -26,6 +26,7 @@ menu:
26
26
  categories: Categories
27
27
  tags: Tags
28
28
  posts: Posts
29
+ collections: Collections
29
30
  about: About
30
31
  search: Search
31
32
 
@@ -26,6 +26,7 @@ menu:
26
26
  categories: 分类
27
27
  tags: 标签
28
28
  posts: 博客文章
29
+ collections: 合集
29
30
  about: 关于
30
31
  search: 搜索
31
32
 
package/client/shims.d.ts CHANGED
@@ -7,4 +7,12 @@ declare module 'virtual:valaxy-theme' {
7
7
 
8
8
  declare module '#valaxy/styles' {
9
9
  // side-effects only
10
+ export default string
11
+ }
12
+
13
+ // valaxy features
14
+ declare module '#valaxy/blog/collections' {
15
+
16
+ const collections: Collection[]
17
+ export default collections
10
18
  }
@@ -4,7 +4,7 @@ import yargs from "yargs";
4
4
  import { hideBin } from "yargs/helpers";
5
5
 
6
6
  // package.json
7
- var version = "0.25.8";
7
+ var version = "0.25.9";
8
8
 
9
9
  // node/modules/fuse.ts
10
10
  import path4 from "path";
@@ -554,8 +554,8 @@ var isInstalledGlobally = {};
554
554
  async function resolveImportUrl(id) {
555
555
  return toAtFS(await resolveImportPath(id, true));
556
556
  }
557
- function toAtFS(path17) {
558
- return `/@fs${ensurePrefix("/", slash(path17))}`;
557
+ function toAtFS(path18) {
558
+ return `/@fs${ensurePrefix("/", slash(path18))}`;
559
559
  }
560
560
  async function resolveImportPath(importName, ensure = false) {
561
561
  try {
@@ -1095,19 +1095,19 @@ async function getPosts(params, options) {
1095
1095
  });
1096
1096
  const posts = [];
1097
1097
  for (const rawPost of filteredPosts) {
1098
- const { data, path: path17, content, excerpt } = rawPost;
1098
+ const { data, path: path18, content, excerpt } = rawPost;
1099
1099
  if (!data.date)
1100
- data.date = await getCreatedTime(path17);
1100
+ data.date = await getCreatedTime(path18);
1101
1101
  if (siteConfig.lastUpdated) {
1102
1102
  if (!data.updated)
1103
- data.updated = await getUpdatedTime(path17);
1103
+ data.updated = await getUpdatedTime(path18);
1104
1104
  }
1105
1105
  const fullText = options.config.modules.rss.fullText;
1106
1106
  const rssContent = fullText ? content : excerpt || content.slice(0, 100);
1107
1107
  const html = markdown.render(rssContent).replace('src="/', `src="${DOMAIN}/`);
1108
1108
  if (data.image?.startsWith("/"))
1109
1109
  data.image = DOMAIN + data.image;
1110
- const link = DOMAIN + path17.replace(`${options.userRoot}/pages`, "").replace(/\.md$/, "");
1110
+ const link = DOMAIN + path18.replace(`${options.userRoot}/pages`, "").replace(/\.md$/, "");
1111
1111
  const tip = `<br/><p>${lang === "zh-CN" ? `\u8BBF\u95EE <a href="${link}" target="_blank">${link}</a> ${fullText ? "\u67E5\u770B\u539F\u6587" : "\u9605\u8BFB\u5168\u6587"}\u3002` : `Visit <a href="${link}" target="_blank">${link}</a> to ${fullText ? "view original article" : "read more"}.`}</p>`;
1112
1112
  posts.push({
1113
1113
  ...data,
@@ -1128,7 +1128,7 @@ async function writeFeed(feedOptions, posts, options, feedNameMap) {
1128
1128
  const feed = new Feed(feedOptions);
1129
1129
  posts.forEach((item) => feed.addItem(item));
1130
1130
  await fs9.ensureDir(dirname3(`./dist/${feedNameMap.atom}`));
1131
- const path17 = resolve5(options.userRoot, "./dist");
1131
+ const path18 = resolve5(options.userRoot, "./dist");
1132
1132
  const publicFolder = resolve5(options.userRoot, "public");
1133
1133
  const { config } = options;
1134
1134
  const siteConfig = config.siteConfig;
@@ -1141,7 +1141,7 @@ async function writeFeed(feedOptions, posts, options, feedNameMap) {
1141
1141
  const types = ["rss", "atom", "json"];
1142
1142
  for (const type of types) {
1143
1143
  let data = "";
1144
- const distFeedPath = `${path17}/${feedNameMap[type]}`;
1144
+ const distFeedPath = `${path18}/${feedNameMap[type]}`;
1145
1145
  if (type === "rss")
1146
1146
  data = feed.rss2();
1147
1147
  else if (type === "atom")
@@ -1214,7 +1214,7 @@ var rssModule = defineValaxyModule({
1214
1214
  });
1215
1215
 
1216
1216
  // node/cli/build.ts
1217
- import path13 from "path";
1217
+ import path14 from "path";
1218
1218
  import process9 from "process";
1219
1219
  import { consola as consola16 } from "consola";
1220
1220
  import { colors as colors15 } from "consola/utils";
@@ -1224,7 +1224,7 @@ import { mergeConfig as mergeConfig4 } from "vite";
1224
1224
  import { join as join8, resolve as resolve13 } from "path";
1225
1225
  import { consola as consola14 } from "consola";
1226
1226
  import { colors as colors12 } from "consola/utils";
1227
- import fs21 from "fs-extra";
1227
+ import fs22 from "fs-extra";
1228
1228
  import { mergeConfig as mergeViteConfig2, build as viteBuild } from "vite";
1229
1229
  import generateSitemap from "vite-ssg-sitemap";
1230
1230
  import { build as viteSsgBuild } from "vite-ssg/node";
@@ -1303,10 +1303,10 @@ async function getIndexHtml({ clientRoot, themeRoot, userRoot: userRoot2, config
1303
1303
  `;
1304
1304
  }
1305
1305
  for (const root of roots) {
1306
- const path17 = join2(root, "index.html");
1307
- if (!fs10.existsSync(path17))
1306
+ const path18 = join2(root, "index.html");
1307
+ if (!fs10.existsSync(path18))
1308
1308
  continue;
1309
- const indexHtml = await fs10.readFile(path17, "utf-8");
1309
+ const indexHtml = await fs10.readFile(path18, "utf-8");
1310
1310
  head += `
1311
1311
  ${(indexHtml.match(/<head>([\s\S]*?)<\/head>/i)?.[1] || "").trim()}`;
1312
1312
  body += `
@@ -1435,16 +1435,18 @@ function getDefine(_options) {
1435
1435
  }
1436
1436
  async function getAlias(options) {
1437
1437
  const alias = [
1438
+ /**
1439
+ * virtual module alias
1440
+ * `/` 开头无法 declare module 类型
1441
+ *
1442
+ * #valaxy/* => /@valaxyjs/*
1443
+ */
1444
+ { find: /^#valaxy\/(.*)/, replacement: "/@valaxyjs/$1" },
1438
1445
  { find: "~/", replacement: `${toAtFS(options.userRoot)}/` },
1439
1446
  { find: "valaxy/client/", replacement: `${toAtFS(options.clientRoot)}/` },
1440
1447
  { find: "valaxy/package.json", replacement: toAtFS(resolve6(options.clientRoot, "../package.json")) },
1441
1448
  { find: /^valaxy$/, replacement: toAtFS(resolve6(options.clientRoot, "index.ts")) },
1442
1449
  { find: "@valaxyjs/client/", replacement: `${toAtFS(options.clientRoot)}/` },
1443
- // virtual module alias
1444
- {
1445
- find: /^#valaxy\/(.*)/,
1446
- replacement: "/@valaxyjs/$1"
1447
- },
1448
1450
  // import theme
1449
1451
  { find: "virtual:valaxy-theme", replacement: `${toAtFS(options.themeRoot)}/client/index.ts` },
1450
1452
  { find: `valaxy-theme-${options.theme}/client`, replacement: `${toAtFS(resolve6(options.themeRoot))}/client/index.ts` },
@@ -1999,8 +2001,8 @@ function linkPlugin(md3, externalAttrs, base) {
1999
2001
  let url = hrefAttr[1];
2000
2002
  const indexMatch = url.match(indexRE);
2001
2003
  if (indexMatch) {
2002
- const [, path17, hash] = indexMatch;
2003
- url = path17 + hash;
2004
+ const [, path18, hash] = indexMatch;
2005
+ url = path18 + hash;
2004
2006
  } else {
2005
2007
  let cleanUrl = url.replace(/[?#].*$/, "");
2006
2008
  if (cleanUrl.endsWith(".md")) {
@@ -2860,10 +2862,10 @@ function createClientSetupPlugin({ clientRoot, themeRoot, userRoot: userRoot2 })
2860
2862
  themeRoot,
2861
2863
  userRoot2
2862
2864
  ]).map((i) => join4(i, "setup", name));
2863
- setups.forEach((path17, idx) => {
2864
- if (!existsSync(path17))
2865
+ setups.forEach((path18, idx) => {
2866
+ if (!existsSync(path18))
2865
2867
  return;
2866
- imports.push(`import __n${idx} from '${toAtFS(path17)}'`);
2868
+ imports.push(`import __n${idx} from '${toAtFS(path18)}'`);
2867
2869
  let fn = `__n${idx}`;
2868
2870
  if (/\binjection_return\b/.test(code))
2869
2871
  fn = `injection_return = ${fn}`;
@@ -2872,7 +2874,7 @@ function createClientSetupPlugin({ clientRoot, themeRoot, userRoot: userRoot2 })
2872
2874
  else
2873
2875
  fn += "()";
2874
2876
  injections.push(
2875
- `// ${path17}`,
2877
+ `// ${path18}`,
2876
2878
  fn
2877
2879
  );
2878
2880
  });
@@ -2920,9 +2922,9 @@ function deepMerge(a, b, rootPath = "") {
2920
2922
  async function loadSetups(roots, name, arg, initial, merge = true) {
2921
2923
  let returns = initial;
2922
2924
  for (const root of roots) {
2923
- const path17 = resolve8(root, "setup", name);
2924
- if (fs13.existsSync(path17)) {
2925
- const setup = await jiti2.import(path17, { default: true });
2925
+ const path18 = resolve8(root, "setup", name);
2926
+ if (fs13.existsSync(path18)) {
2927
+ const setup = await jiti2.import(path18, { default: true });
2926
2928
  const result = await setup(arg);
2927
2929
  if (result !== null) {
2928
2930
  returns = merge ? deepMerge(returns, result) : result;
@@ -3055,7 +3057,7 @@ async function createUnocssPlugin(options) {
3055
3057
  // node/plugins/valaxy/index.ts
3056
3058
  import { consola as consola12 } from "consola";
3057
3059
  import { colors as colors11 } from "consola/utils";
3058
- import fs19 from "fs-extra";
3060
+ import fs20 from "fs-extra";
3059
3061
  import { join as join7, relative, resolve as resolve10 } from "pathe";
3060
3062
 
3061
3063
  // node/virtual/addons.ts
@@ -3074,6 +3076,35 @@ export default [${components}]`;
3074
3076
  }
3075
3077
  };
3076
3078
 
3079
+ // node/virtual/blogs.ts
3080
+ import path8 from "path";
3081
+ import fs16 from "fs-extra";
3082
+ function createBlogTemplate(name) {
3083
+ return {
3084
+ id: `/@valaxyjs/blog/${name}s`,
3085
+ async getContent({ userRoot: userRoot2 }) {
3086
+ const root = path8.resolve(userRoot2, "pages", "collections");
3087
+ if (!await fs16.pathExists(root)) {
3088
+ return `export default []`;
3089
+ }
3090
+ const isDir = (file) => fs16.statSync(path8.join(root, file)).isDirectory();
3091
+ const files = fs16.readdirSync(root).filter((file) => isDir(file)).map((file) => path8.join(root, file, "index.ts"));
3092
+ const imports = [];
3093
+ const getImportedName = (idx) => `__valaxy_${name}_${idx + 1}`;
3094
+ files.forEach((file, idx) => {
3095
+ const importedName = getImportedName(idx);
3096
+ imports.push(`import ${importedName} from '${toAtFS(file)}'`);
3097
+ });
3098
+ imports.push(`export default [${files.map((_, idx) => getImportedName(idx)).join(", ")}]`);
3099
+ return imports.join("\n");
3100
+ }
3101
+ };
3102
+ }
3103
+ var configs = [
3104
+ "collection"
3105
+ ];
3106
+ var templateBlogs = configs.map(createBlogTemplate);
3107
+
3077
3108
  // node/virtual/config.ts
3078
3109
  var templateConfig = {
3079
3110
  id: "/@valaxyjs/config",
@@ -3093,7 +3124,7 @@ var templateConfig = {
3093
3124
  };
3094
3125
 
3095
3126
  // node/virtual/locales.ts
3096
- import fs16 from "fs-extra";
3127
+ import fs17 from "fs-extra";
3097
3128
  var templateLocales = {
3098
3129
  id: "/@valaxyjs/locales",
3099
3130
  async getContent({ roots, config }) {
@@ -3113,7 +3144,7 @@ var templateLocales = {
3113
3144
  roots.forEach((root, i) => {
3114
3145
  languages.forEach((lang) => {
3115
3146
  const langYml = `${root}/locales/${lang}.yml`;
3116
- if (fs16.existsSync(langYml) && fs16.readFileSync(langYml, "utf-8")) {
3147
+ if (fs17.existsSync(langYml) && fs17.readFileSync(langYml, "utf-8")) {
3117
3148
  const varName = lang.replace("-", "") + i;
3118
3149
  imports.unshift(`import ${varName} from "${toAtFS(langYml)}"`);
3119
3150
  imports.push(`messages['${lang}'] = replaceArrMerge(${varName}, messages['${lang}'])`);
@@ -3162,13 +3193,14 @@ var templates = [
3162
3193
  templateAddons,
3163
3194
  templateConfig,
3164
3195
  templateLocales,
3165
- templateStyles
3196
+ templateStyles,
3197
+ ...templateBlogs
3166
3198
  ];
3167
3199
 
3168
3200
  // node/plugins/markdown/markdownToVue.ts
3169
3201
  import _debug2 from "debug";
3170
3202
  import { LRUCache } from "lru-cache";
3171
- import path11 from "pathe";
3203
+ import path12 from "pathe";
3172
3204
 
3173
3205
  // node/app/index.ts
3174
3206
  import { createHooks } from "hookable";
@@ -3227,10 +3259,10 @@ function createTransformCodeBlock(options) {
3227
3259
 
3228
3260
  // node/plugins/markdown/transform/dead-links.ts
3229
3261
  import { slash as slash4 } from "@antfu/utils";
3230
- import fs17 from "fs-extra";
3231
- import path8 from "pathe";
3262
+ import fs18 from "fs-extra";
3263
+ import path9 from "pathe";
3232
3264
  function createScanDeadLinks(options) {
3233
- const srcDir = path8.resolve(options.userRoot, "pages");
3265
+ const srcDir = path9.resolve(options.userRoot, "pages");
3234
3266
  const { ignoreDeadLinks } = options.config.build;
3235
3267
  const publicDir = options.config.vite?.publicDir || "public";
3236
3268
  return (code, id) => {
@@ -3240,7 +3272,7 @@ function createScanDeadLinks(options) {
3240
3272
  const file = id;
3241
3273
  const deadLinks = [];
3242
3274
  const recordDeadLink = (url) => {
3243
- deadLinks.push({ url, file: path8.relative(srcDir, fileOrig) });
3275
+ deadLinks.push({ url, file: path9.relative(srcDir, fileOrig) });
3244
3276
  };
3245
3277
  function shouldIgnoreDeadLink(url) {
3246
3278
  if (!ignoreDeadLinks)
@@ -3260,7 +3292,7 @@ function createScanDeadLinks(options) {
3260
3292
  });
3261
3293
  }
3262
3294
  if (links) {
3263
- const dir = path8.dirname(file);
3295
+ const dir = path9.dirname(file);
3264
3296
  for (let url of links) {
3265
3297
  const { pathname } = new URL(url, "http://a.com");
3266
3298
  if (!treatAsHtml(pathname))
@@ -3270,11 +3302,11 @@ function createScanDeadLinks(options) {
3270
3302
  url += `index`;
3271
3303
  const resolved = decodeURIComponent(
3272
3304
  slash4(
3273
- url.startsWith("/") ? url.slice(1) : path8.relative(srcDir, path8.resolve(dir, url))
3305
+ url.startsWith("/") ? url.slice(1) : path9.relative(srcDir, path9.resolve(dir, url))
3274
3306
  )
3275
3307
  // /index => /
3276
3308
  ).replace(/\/index$/, "");
3277
- if (!options.pages.includes(resolved) && !fs17.existsSync(path8.resolve(dir, publicDir, `${resolved}.html`)) && !shouldIgnoreDeadLink(url)) {
3309
+ if (!options.pages.includes(resolved) && !fs18.existsSync(path9.resolve(dir, publicDir, `${resolved}.html`)) && !shouldIgnoreDeadLink(url)) {
3278
3310
  recordDeadLink(url);
3279
3311
  }
3280
3312
  }
@@ -3436,8 +3468,8 @@ function transformHexoTags(code, id) {
3436
3468
  }
3437
3469
 
3438
3470
  // node/plugins/markdown/transform/markdown.ts
3439
- import path9 from "path";
3440
- import fs18 from "fs-extra";
3471
+ import path10 from "path";
3472
+ import fs19 from "fs-extra";
3441
3473
  function genProvideCode(name, data) {
3442
3474
  return [
3443
3475
  `const $${name} = ${transformObject(data)}`,
@@ -3468,8 +3500,8 @@ function injectPageDataCode(pageData) {
3468
3500
  return vueContextImports;
3469
3501
  }
3470
3502
  function createTransformMarkdown(options) {
3471
- const loaderVuePath = path9.resolve(options.clientRoot, "templates", "loader.vue");
3472
- const loaderVue = fs18.readFileSync(loaderVuePath, "utf-8");
3503
+ const loaderVuePath = path10.resolve(options.clientRoot, "templates", "loader.vue");
3504
+ const loaderVue = fs19.readFileSync(loaderVuePath, "utf-8");
3473
3505
  return (code, id, pageData) => {
3474
3506
  const isDev = options.mode === "dev";
3475
3507
  if (!isDev) {
@@ -3508,7 +3540,7 @@ ${code.slice(injectB)}`;
3508
3540
  }
3509
3541
 
3510
3542
  // node/plugins/markdown/transform/page-data.ts
3511
- import path10 from "pathe";
3543
+ import path11 from "pathe";
3512
3544
  function getHeadMetaContent(head, name) {
3513
3545
  if (!head || !head.length)
3514
3546
  return void 0;
@@ -3525,7 +3557,7 @@ function inferDescription(frontmatter) {
3525
3557
  }
3526
3558
  async function generatePageData(code, id, options) {
3527
3559
  const fileInfo = Valaxy.state.idMap.get(id);
3528
- const relativePath = path10.relative(options.userRoot, id);
3560
+ const relativePath = path11.relative(options.userRoot, id);
3529
3561
  const fm = JSON.parse(JSON.stringify(fileInfo?.frontmatter));
3530
3562
  const pageData = {
3531
3563
  title: fm.title || fileInfo?.title || "",
@@ -3582,7 +3614,7 @@ async function createMarkdownToVueRenderFn(options, _viteConfig) {
3582
3614
  const isBuild = options.mode === "build";
3583
3615
  return async (code, id) => {
3584
3616
  const file = id;
3585
- const relativePath = path11.relative(srcDir, file);
3617
+ const relativePath = path12.relative(srcDir, file);
3586
3618
  const deadLinks = scanDeadLinks(code, id);
3587
3619
  const cacheKey = JSON.stringify({ code, id });
3588
3620
  if (isBuild) {
@@ -3621,7 +3653,7 @@ async function createMarkdownToVueRenderFn(options, _viteConfig) {
3621
3653
  var nullVue = 'import { defineComponent } from "vue"; export default defineComponent({ render: () => null });';
3622
3654
  function generateAppVue(root) {
3623
3655
  const appVue = join7(root, "App.vue");
3624
- if (!fs19.existsSync(appVue))
3656
+ if (!fs20.existsSync(appVue))
3625
3657
  return nullVue;
3626
3658
  const scripts = [
3627
3659
  `import AppVue from "${toAtFS(appVue)}"`,
@@ -3751,9 +3783,9 @@ async function createValaxyPlugin(options, serverOptions = {}) {
3751
3783
  const endCount = countPerformanceTime();
3752
3784
  const content = await read();
3753
3785
  const { code, pageData } = await markdownToVue(content, file);
3754
- const path17 = `/${relative(`${options.userRoot}/pages`, file)}`;
3786
+ const path18 = `/${relative(`${options.userRoot}/pages`, file)}`;
3755
3787
  const payload = {
3756
- path: path17,
3788
+ path: path18,
3757
3789
  pageData
3758
3790
  };
3759
3791
  server.hot.send({
@@ -3780,7 +3812,7 @@ async function createValaxyPlugin(options, serverOptions = {}) {
3780
3812
  }
3781
3813
 
3782
3814
  // node/plugins/vueRouter.ts
3783
- import fs20 from "fs-extra";
3815
+ import fs21 from "fs-extra";
3784
3816
  import matter3 from "gray-matter";
3785
3817
  import { convert } from "html-to-text";
3786
3818
  import { MarkdownItAsync } from "markdown-it-async";
@@ -3891,10 +3923,16 @@ async function createRouterPlugin(valaxyApp) {
3891
3923
  layout: "post"
3892
3924
  });
3893
3925
  }
3926
+ } else if (route.fullPath.startsWith("/collections/")) {
3927
+ if (route.fullPath.split("/").length > 3) {
3928
+ route.addToMeta({
3929
+ layout: "collection"
3930
+ });
3931
+ }
3894
3932
  }
3895
- const path17 = route.components.get("default") || "";
3896
- if (path17.endsWith(".md")) {
3897
- const md3 = fs20.readFileSync(path17, "utf-8");
3933
+ const path18 = route.components.get("default") || "";
3934
+ if (path18.endsWith(".md")) {
3935
+ const md3 = fs21.readFileSync(path18, "utf-8");
3898
3936
  const { data, excerpt, content } = matter3(md3, matterOptions);
3899
3937
  const mdFm = data;
3900
3938
  const lastUpdated = options.config.siteConfig.lastUpdated;
@@ -3904,10 +3942,10 @@ async function createRouterPlugin(valaxyApp) {
3904
3942
  delete mdFm.photos;
3905
3943
  }
3906
3944
  if (!mdFm.date)
3907
- mdFm.date = (await fs20.stat(path17)).mtime;
3945
+ mdFm.date = (await fs21.stat(path18)).mtime;
3908
3946
  if (lastUpdated) {
3909
3947
  if (!mdFm.updated)
3910
- mdFm.updated = (await fs20.stat(path17)).ctime;
3948
+ mdFm.updated = (await fs21.stat(path18)).ctime;
3911
3949
  }
3912
3950
  if (mdFm.from) {
3913
3951
  if (Array.isArray(mdFm.from)) {
@@ -3964,7 +4002,7 @@ async function createRouterPlugin(valaxyApp) {
3964
4002
  data,
3965
4003
  excerpt,
3966
4004
  content,
3967
- path: path17
4005
+ path: path18
3968
4006
  };
3969
4007
  valaxyConfig.extendMd?.(ctx);
3970
4008
  }
@@ -4178,13 +4216,13 @@ async function ssgBuild(valaxyApp, viteConfig = {}) {
4178
4216
  if (options.config.build.ssgForPagination) {
4179
4217
  defaultConfig.ssgOptions.includedRoutes = (paths, _routes) => {
4180
4218
  const newPaths = paths;
4181
- const posts = paths.filter((path17) => path17.startsWith("/posts/"));
4219
+ const posts = paths.filter((path18) => path18.startsWith("/posts/"));
4182
4220
  const pageNumber = Math.ceil(posts.length / options.config.siteConfig.pageSize);
4183
4221
  consola14.info(`Generate ${colors12.yellow(pageNumber)} pages for pagination.`);
4184
4222
  for (let i = 1; i <= pageNumber; i++)
4185
4223
  newPaths.push(`/page/${i}`);
4186
4224
  if (!options.config.vite?.ssgOptions?.includeAllRoutes)
4187
- return newPaths.filter((path17) => !path17.split("/").some((p) => p.startsWith(":")));
4225
+ return newPaths.filter((path18) => !path18.split("/").some((p) => p.startsWith(":")));
4188
4226
  else
4189
4227
  return newPaths;
4190
4228
  };
@@ -4195,14 +4233,14 @@ async function ssgBuild(valaxyApp, viteConfig = {}) {
4195
4233
  async function postProcessForSSG(options) {
4196
4234
  const { userRoot: userRoot2 } = options;
4197
4235
  const indexPath = resolve13(userRoot2, "dist/index.html");
4198
- if (fs21.existsSync(indexPath)) {
4236
+ if (fs22.existsSync(indexPath)) {
4199
4237
  consola14.info("post process for ssg...");
4200
- const indexFile = await fs21.readFile(indexPath, "utf-8");
4238
+ const indexFile = await fs22.readFile(indexPath, "utf-8");
4201
4239
  const htmlTag = "</html>";
4202
4240
  if (!indexFile.endsWith(htmlTag)) {
4203
4241
  consola14.warn("fix incomplete index.html...");
4204
4242
  const htmlTagStart = indexFile.lastIndexOf(htmlTag);
4205
- await fs21.writeFile(indexPath, indexFile.slice(0, htmlTagStart + htmlTag.length), "utf-8");
4243
+ await fs22.writeFile(indexPath, indexFile.slice(0, htmlTagStart + htmlTag.length), "utf-8");
4206
4244
  }
4207
4245
  }
4208
4246
  if (!options.config.siteConfig.redirects?.useVueRouter)
@@ -4215,7 +4253,7 @@ async function generateClientRedirects(options) {
4215
4253
  const task = redirectRules.map(async (rule) => {
4216
4254
  const fromPath = join8(outputPath, `${rule.from}.html`);
4217
4255
  const toPath = join8(outputPath, `${rule.to}.html`);
4218
- const routeExist = await fs21.pathExists(toPath);
4256
+ const routeExist = await fs22.pathExists(toPath);
4219
4257
  if (!routeExist)
4220
4258
  throw new Error(`the route of '${rule.to}' not exists`);
4221
4259
  await writeRedirectFiles(rule.to, fromPath);
@@ -4258,7 +4296,7 @@ async function createServer(valaxyApp, viteConfig = {}, serverOptions = {}) {
4258
4296
 
4259
4297
  // node/cli/utils/cli.ts
4260
4298
  import os from "os";
4261
- import path12 from "path";
4299
+ import path13 from "path";
4262
4300
  import process8 from "process";
4263
4301
  import { consola as consola15 } from "consola";
4264
4302
  import { colors as colors14 } from "consola/utils";
@@ -4278,7 +4316,7 @@ function printInfo(options, port, remote) {
4278
4316
  console.log(` ${colors14.bold("\u{1F30C} Valaxy")} ${colors14.blue(`v${version}`)}`);
4279
4317
  console.log();
4280
4318
  console.log(`${colors14.dim(" \u{1FA90} theme ")} > ${options.theme ? colors14.green(options.theme) : colors14.gray("none")} (${themeVersion})`);
4281
- console.log(` ${colors14.dim("\u{1F4C1}")} ${colors14.dim(path12.resolve(options.userRoot))}`);
4319
+ console.log(` ${colors14.dim("\u{1F4C1}")} ${colors14.dim(path13.resolve(options.userRoot))}`);
4282
4320
  if (port) {
4283
4321
  console.log();
4284
4322
  console.log(`${colors14.dim(" Preview ")} > ${colors14.cyan(`http://localhost:${colors14.bold(port)}/`)}`);
@@ -4356,7 +4394,7 @@ async function execBuild({ ssg, root, output, log }) {
4356
4394
  setEnvProd();
4357
4395
  if (!isPagesDirExist(root))
4358
4396
  process9.exit(0);
4359
- const userRoot2 = path13.resolve(root);
4397
+ const userRoot2 = path14.resolve(root);
4360
4398
  const options = await resolveOptions({ userRoot: userRoot2 }, "build");
4361
4399
  setTimezone(options.config.siteConfig.timezone);
4362
4400
  printInfo(options);
@@ -4376,11 +4414,11 @@ async function execBuild({ ssg, root, output, log }) {
4376
4414
  valaxyViteConfig,
4377
4415
  {
4378
4416
  // avoid load userRoot/vite.config.ts repeatedly
4379
- configFile: path13.resolve(options.clientRoot, "vite.config.ts"),
4417
+ configFile: path14.resolve(options.clientRoot, "vite.config.ts"),
4380
4418
  build: {
4381
4419
  // make out dir empty, https://vitejs.dev/config/#build-emptyoutdir
4382
4420
  emptyOutDir: true,
4383
- outDir: path13.resolve(options.userRoot, output)
4421
+ outDir: path14.resolve(options.userRoot, output)
4384
4422
  },
4385
4423
  logLevel: log
4386
4424
  }
@@ -4437,18 +4475,18 @@ function registerBuildCommand(cli2) {
4437
4475
  }
4438
4476
 
4439
4477
  // node/cli/clean.ts
4440
- import path14 from "path";
4478
+ import path15 from "path";
4441
4479
  import process10 from "process";
4442
4480
  import { consola as consola17 } from "consola";
4443
- import fs22 from "fs-extra";
4481
+ import fs23 from "fs-extra";
4444
4482
  async function cleanDist() {
4445
- const distDir = path14.join(process10.cwd(), "dist");
4446
- const cacheDir = path14.join(process10.cwd(), ".valaxy");
4483
+ const distDir = path15.join(process10.cwd(), "dist");
4484
+ const cacheDir = path15.join(process10.cwd(), ".valaxy");
4447
4485
  consola17.box("\u{1F9F9} Starting clean...");
4448
- if (await fs22.exists(distDir)) {
4486
+ if (await fs23.exists(distDir)) {
4449
4487
  consola17.info("dist directory exists, removing...");
4450
4488
  try {
4451
- await fs22.rm(distDir, { recursive: true, force: true });
4489
+ await fs23.rm(distDir, { recursive: true, force: true });
4452
4490
  consola17.success("dist directory has been successfully removed.");
4453
4491
  } catch (error) {
4454
4492
  consola17.error("Failed to remove dist directory.");
@@ -4457,10 +4495,10 @@ async function cleanDist() {
4457
4495
  } else {
4458
4496
  consola17.info("No dist directory found, nothing to clean.");
4459
4497
  }
4460
- if (await fs22.exists(cacheDir)) {
4498
+ if (await fs23.exists(cacheDir)) {
4461
4499
  consola17.info(".valaxy cache directory exists, removing...");
4462
4500
  try {
4463
- await fs22.rm(cacheDir, { recursive: true, force: true });
4501
+ await fs23.rm(cacheDir, { recursive: true, force: true });
4464
4502
  consola17.success(".valaxy cache directory has been successfully removed.");
4465
4503
  } catch (error) {
4466
4504
  consola17.error("Failed to remove .valaxy cache directory.");
@@ -4539,7 +4577,7 @@ function registerDeployCommand(cli2) {
4539
4577
  }
4540
4578
 
4541
4579
  // node/cli/dev.ts
4542
- import path15 from "path";
4580
+ import path16 from "path";
4543
4581
  import process13 from "process";
4544
4582
  import { mergeConfig as mergeConfig5 } from "vite";
4545
4583
 
@@ -4668,7 +4706,7 @@ async function startValaxyDev({
4668
4706
  // initial vite config
4669
4707
  ...defaultViteConfig,
4670
4708
  // avoid load userRoot/vite.config.ts repeatedly
4671
- configFile: path15.resolve(resolvedOptions.clientRoot, "vite.config.ts"),
4709
+ configFile: path16.resolve(resolvedOptions.clientRoot, "vite.config.ts"),
4672
4710
  server: {
4673
4711
  watch: {
4674
4712
  // watch theme updated
@@ -4731,7 +4769,7 @@ import { consola as consola19 } from "consola";
4731
4769
  import { colors as colors17 } from "consola/utils";
4732
4770
  import dayjs2 from "dayjs";
4733
4771
  import { render } from "ejs";
4734
- import fs24 from "fs-extra";
4772
+ import fs25 from "fs-extra";
4735
4773
 
4736
4774
  // node/cli/utils/constants.ts
4737
4775
  import process14 from "process";
@@ -4745,14 +4783,14 @@ date: <%=date%>
4745
4783
 
4746
4784
  // node/cli/utils/scaffold.ts
4747
4785
  import { readFile as readFile2 } from "fs/promises";
4748
- import path16 from "path";
4749
- import fs23 from "fs-extra";
4786
+ import path17 from "path";
4787
+ import fs24 from "fs-extra";
4750
4788
  async function getTemplate(layout) {
4751
4789
  const { clientRoot, themeRoot } = await resolveOptions({ userRoot });
4752
4790
  const roots = [userRoot, themeRoot, clientRoot];
4753
4791
  for (const root of roots) {
4754
- const scaffoldPath = path16.resolve(root, "scaffolds", `${layout}.md`);
4755
- if (await fs23.exists(scaffoldPath))
4792
+ const scaffoldPath = path17.resolve(root, "scaffolds", `${layout}.md`);
4793
+ if (await fs24.exists(scaffoldPath))
4756
4794
  return readFile2(scaffoldPath, "utf-8");
4757
4795
  }
4758
4796
  return false;
@@ -4766,11 +4804,11 @@ async function create(params) {
4766
4804
  const postFileName = `${params.title}${counter ? `-${counter}` : ""}`;
4767
4805
  const postFilePath = params.folder ? join9(postFileName, "index.md") : `${postFileName}.md`;
4768
4806
  const targetPath = resolve14(pagesPath, "posts", postFilePath);
4769
- if (!await fs24.exists(targetPath)) {
4770
- await fs24.ensureDir(dirname5(targetPath));
4807
+ if (!await fs25.exists(targetPath)) {
4808
+ await fs25.ensureDir(dirname5(targetPath));
4771
4809
  const content = await genLayoutTemplate(params);
4772
4810
  try {
4773
- await fs24.writeFile(targetPath, content, "utf-8");
4811
+ await fs25.writeFile(targetPath, content, "utf-8");
4774
4812
  consola19.success(`[valaxy new]: successfully generated file ${colors17.magenta(targetPath)}`);
4775
4813
  } catch (e) {
4776
4814
  console.log(e);
@@ -4823,12 +4861,12 @@ function registerNewCommand(cli2) {
4823
4861
  describe: "Generate post with the current date"
4824
4862
  }).strict().help();
4825
4863
  },
4826
- async ({ title, folder, path: path17, date, layout }) => {
4864
+ async ({ title, folder, path: path18, date, layout }) => {
4827
4865
  await create({
4828
4866
  title,
4829
4867
  date,
4830
4868
  layout,
4831
- path: path17,
4869
+ path: path18,
4832
4870
  folder
4833
4871
  });
4834
4872
  }
@@ -61,6 +61,41 @@ declare namespace DefaultTheme {
61
61
  }
62
62
  }
63
63
 
64
+ interface CollectionConfig {
65
+ title?: string;
66
+ key?: string;
67
+ /**
68
+ * if key is not provided, path is required
69
+ *
70
+ * if key is provided, path = `/collections/${key}`
71
+ */
72
+ path?: string;
73
+ /**
74
+ * @en
75
+ * The name of the collection.
76
+ *
77
+ * @zh
78
+ * 合集名称
79
+ */
80
+ name?: string;
81
+ cover?: string;
82
+ description?: string;
83
+ categories?: string[];
84
+ tags?: string[];
85
+ /**
86
+ * items
87
+ */
88
+ items?: {
89
+ title?: string;
90
+ /**
91
+ * 合集文章的唯一索引
92
+ *
93
+ * 对应路径为 `/collections/${key}/${item.key}`
94
+ */
95
+ key?: string;
96
+ }[];
97
+ }
98
+
64
99
  interface Album {
65
100
  /**
66
101
  * @description:en-US Album Link
@@ -159,6 +194,11 @@ interface PageFrontMatter extends Record<string, any> {
159
194
  * @description 是否显示右侧侧边栏
160
195
  */
161
196
  aside: boolean;
197
+ /**
198
+ * display left sidebar
199
+ * @description 是否显示左侧侧边栏
200
+ */
201
+ sidebar: boolean;
162
202
  /**
163
203
  * @description:en-US Custom Markdown class
164
204
  * @description:zh-CN 自定义 Markdown 样式
@@ -214,6 +254,10 @@ interface PageFrontMatter extends Record<string, any> {
214
254
  * @description:en-US Photos
215
255
  */
216
256
  photos: Photo[];
257
+ /**
258
+ * for collections
259
+ */
260
+ collections: CollectionConfig[];
217
261
  /**
218
262
  * @description:zh-CN 是否启用加密,password 存在时默认为 true
219
263
  */
@@ -3,7 +3,7 @@ import {
3
3
  registerDevCommand,
4
4
  run,
5
5
  startValaxyDev
6
- } from "../../chunk-5GRPXDJR.js";
6
+ } from "../../chunk-MSK4KQXZ.js";
7
7
  export {
8
8
  cli,
9
9
  registerDevCommand,
@@ -2,7 +2,7 @@ import { ViteSSGOptions } from 'vite-ssg';
2
2
  import * as vite from 'vite';
3
3
  import { UserConfig, InlineConfig, PluginOption, Plugin } from 'vite';
4
4
  import { MarkdownEnv } from 'unplugin-vue-markdown/types';
5
- import { D as DefaultTheme, V as ValaxyConfig, a as ValaxyAddon, P as PartialDeep, R as RuntimeConfig, b as RedirectItem, S as SiteConfig, U as UserSiteConfig } from '../config-DHcACRUt.js';
5
+ import { D as DefaultTheme, V as ValaxyConfig, a as ValaxyAddon, P as PartialDeep, R as RuntimeConfig, b as RedirectItem, S as SiteConfig, U as UserSiteConfig } from '../config-CSOxBD5d.js';
6
6
  import Vue from '@vitejs/plugin-vue';
7
7
  import { Options as Options$2 } from 'beasties';
8
8
  import { Hookable } from 'hookable';
@@ -50,7 +50,7 @@ import {
50
50
  startValaxyDev,
51
51
  toAtFS,
52
52
  transformObject
53
- } from "../chunk-5GRPXDJR.js";
53
+ } from "../chunk-MSK4KQXZ.js";
54
54
  export {
55
55
  $t,
56
56
  ALL_ROUTE,
@@ -1,5 +1,5 @@
1
- import { c as PostFrontMatter, d as PageFrontMatter } from '../config-DHcACRUt.js';
2
- export { A as Album, D as DefaultTheme, E as ExcerptType, F as FuseListItem, P as PartialDeep, i as Photo, g as Pkg, b as RedirectItem, f as RedirectRule, R as RuntimeConfig, S as SiteConfig, e as SocialLink, U as UserSiteConfig, h as UserValaxyConfig, a as ValaxyAddon, V as ValaxyConfig } from '../config-DHcACRUt.js';
1
+ import { c as PostFrontMatter, d as PageFrontMatter } from '../config-CSOxBD5d.js';
2
+ export { A as Album, D as DefaultTheme, E as ExcerptType, F as FuseListItem, P as PartialDeep, i as Photo, g as Pkg, b as RedirectItem, f as RedirectRule, R as RuntimeConfig, S as SiteConfig, e as SocialLink, U as UserSiteConfig, h as UserValaxyConfig, a as ValaxyAddon, V as ValaxyConfig } from '../config-CSOxBD5d.js';
3
3
  import { Header } from '@valaxyjs/utils';
4
4
  import '@vueuse/integrations/useFuse';
5
5
  import 'medium-zoom';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "valaxy",
3
3
  "type": "module",
4
- "version": "0.25.8",
4
+ "version": "0.25.9",
5
5
  "description": "📄 Vite & Vue powered static blog generator.",
6
6
  "author": {
7
7
  "email": "me@yunyoujun.cn",
@@ -58,7 +58,7 @@
58
58
  "@clack/prompts": "^0.11.0",
59
59
  "@iconify-json/ri": "^1.2.5",
60
60
  "@intlify/unplugin-vue-i18n": "^6.0.8",
61
- "@shikijs/transformers": "^3.8.0",
61
+ "@shikijs/transformers": "^3.8.1",
62
62
  "@types/katex": "^0.16.7",
63
63
  "@unhead/addons": "^2.0.12",
64
64
  "@unhead/schema-org": "^2.0.12",
@@ -111,7 +111,7 @@
111
111
  "qrcode": "^1.5.4",
112
112
  "resolve-global": "^2.0.0",
113
113
  "sass": "^1.89.2",
114
- "shiki": "^3.8.0",
114
+ "shiki": "^3.8.1",
115
115
  "star-markdown-css": "^0.5.3",
116
116
  "table": "^6.9.0",
117
117
  "unhead": "^2.0.12",
@@ -131,8 +131,8 @@
131
131
  "vue-i18n": "^11.1.10",
132
132
  "vue-router": "^4.5.1",
133
133
  "yargs": "^18.0.0",
134
- "@valaxyjs/devtools": "0.25.8",
135
- "@valaxyjs/utils": "0.25.8"
134
+ "@valaxyjs/devtools": "0.25.9",
135
+ "@valaxyjs/utils": "0.25.9"
136
136
  },
137
137
  "devDependencies": {
138
138
  "@mdit-vue/plugin-component": "^2.1.4",
@@ -1,4 +1,5 @@
1
1
  import type { ImageObject, NodeRelations } from '@unhead/schema-org'
2
+ import type { CollectionConfig } from '../../client/define'
2
3
 
3
4
  export interface Album {
4
5
  /**
@@ -105,6 +106,11 @@ export interface PageFrontMatter extends Record<string, any> {
105
106
  * @description 是否显示右侧侧边栏
106
107
  */
107
108
  aside: boolean
109
+ /**
110
+ * display left sidebar
111
+ * @description 是否显示左侧侧边栏
112
+ */
113
+ sidebar: boolean
108
114
 
109
115
  /**
110
116
  * @description:en-US Custom Markdown class
@@ -155,17 +161,21 @@ export interface PageFrontMatter extends Record<string, any> {
155
161
  */
156
162
  medium_zoom: boolean
157
163
 
164
+ // --- layout ---
158
165
  /**
159
166
  * @description:en-US Albums
160
167
  * @description:zh-CN 相册
161
168
  */
162
169
  albums: Album[]
163
-
164
170
  /**
165
171
  * For layout Gallery
166
172
  * @description:en-US Photos
167
173
  */
168
174
  photos: Photo[]
175
+ /**
176
+ * for collections
177
+ */
178
+ collections: CollectionConfig[]
169
179
 
170
180
  /**
171
181
  * @description:zh-CN 是否启用加密,password 存在时默认为 true