valaxy 0.25.8 → 0.25.10

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.10";
8
8
 
9
9
  // node/modules/fuse.ts
10
10
  import path4 from "path";
@@ -523,7 +523,7 @@ var EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
523
523
 
524
524
  // shared/utils/i18n.ts
525
525
  function tObject(data, lang) {
526
- if (typeof data === "object") {
526
+ if (data && typeof data === "object") {
527
527
  return data[lang] || Object.values(data)[0] || "";
528
528
  }
529
529
  return data;
@@ -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 {
@@ -900,7 +900,8 @@ async function isPagesDirExist(root) {
900
900
  // node/modules/fuse.ts
901
901
  async function generateFuseList(options) {
902
902
  consola9.start(`Generate List for Fuse Search by (${colors7.cyan("fuse.js")}) ...`);
903
- const files = await fg2(`${options.userRoot}/pages/posts/**/*.md`);
903
+ const pattern = options.config.siteConfig.fuse.pattern || path4.join(options.userRoot, "pages/**/*.md");
904
+ const files = await fg2(pattern);
904
905
  const posts = [];
905
906
  for await (const i of files) {
906
907
  const raw = fs7.readFileSync(i, "utf-8");
@@ -1095,19 +1096,19 @@ async function getPosts(params, options) {
1095
1096
  });
1096
1097
  const posts = [];
1097
1098
  for (const rawPost of filteredPosts) {
1098
- const { data, path: path17, content, excerpt } = rawPost;
1099
+ const { data, path: path18, content, excerpt } = rawPost;
1099
1100
  if (!data.date)
1100
- data.date = await getCreatedTime(path17);
1101
+ data.date = await getCreatedTime(path18);
1101
1102
  if (siteConfig.lastUpdated) {
1102
1103
  if (!data.updated)
1103
- data.updated = await getUpdatedTime(path17);
1104
+ data.updated = await getUpdatedTime(path18);
1104
1105
  }
1105
1106
  const fullText = options.config.modules.rss.fullText;
1106
1107
  const rssContent = fullText ? content : excerpt || content.slice(0, 100);
1107
1108
  const html = markdown.render(rssContent).replace('src="/', `src="${DOMAIN}/`);
1108
1109
  if (data.image?.startsWith("/"))
1109
1110
  data.image = DOMAIN + data.image;
1110
- const link = DOMAIN + path17.replace(`${options.userRoot}/pages`, "").replace(/\.md$/, "");
1111
+ const link = DOMAIN + path18.replace(`${options.userRoot}/pages`, "").replace(/\.md$/, "");
1111
1112
  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
1113
  posts.push({
1113
1114
  ...data,
@@ -1128,7 +1129,7 @@ async function writeFeed(feedOptions, posts, options, feedNameMap) {
1128
1129
  const feed = new Feed(feedOptions);
1129
1130
  posts.forEach((item) => feed.addItem(item));
1130
1131
  await fs9.ensureDir(dirname3(`./dist/${feedNameMap.atom}`));
1131
- const path17 = resolve5(options.userRoot, "./dist");
1132
+ const path18 = resolve5(options.userRoot, "./dist");
1132
1133
  const publicFolder = resolve5(options.userRoot, "public");
1133
1134
  const { config } = options;
1134
1135
  const siteConfig = config.siteConfig;
@@ -1141,7 +1142,7 @@ async function writeFeed(feedOptions, posts, options, feedNameMap) {
1141
1142
  const types = ["rss", "atom", "json"];
1142
1143
  for (const type of types) {
1143
1144
  let data = "";
1144
- const distFeedPath = `${path17}/${feedNameMap[type]}`;
1145
+ const distFeedPath = `${path18}/${feedNameMap[type]}`;
1145
1146
  if (type === "rss")
1146
1147
  data = feed.rss2();
1147
1148
  else if (type === "atom")
@@ -1214,7 +1215,7 @@ var rssModule = defineValaxyModule({
1214
1215
  });
1215
1216
 
1216
1217
  // node/cli/build.ts
1217
- import path13 from "path";
1218
+ import path14 from "path";
1218
1219
  import process9 from "process";
1219
1220
  import { consola as consola16 } from "consola";
1220
1221
  import { colors as colors15 } from "consola/utils";
@@ -1224,7 +1225,7 @@ import { mergeConfig as mergeConfig4 } from "vite";
1224
1225
  import { join as join8, resolve as resolve13 } from "path";
1225
1226
  import { consola as consola14 } from "consola";
1226
1227
  import { colors as colors12 } from "consola/utils";
1227
- import fs21 from "fs-extra";
1228
+ import fs22 from "fs-extra";
1228
1229
  import { mergeConfig as mergeViteConfig2, build as viteBuild } from "vite";
1229
1230
  import generateSitemap from "vite-ssg-sitemap";
1230
1231
  import { build as viteSsgBuild } from "vite-ssg/node";
@@ -1303,10 +1304,10 @@ async function getIndexHtml({ clientRoot, themeRoot, userRoot: userRoot2, config
1303
1304
  `;
1304
1305
  }
1305
1306
  for (const root of roots) {
1306
- const path17 = join2(root, "index.html");
1307
- if (!fs10.existsSync(path17))
1307
+ const path18 = join2(root, "index.html");
1308
+ if (!fs10.existsSync(path18))
1308
1309
  continue;
1309
- const indexHtml = await fs10.readFile(path17, "utf-8");
1310
+ const indexHtml = await fs10.readFile(path18, "utf-8");
1310
1311
  head += `
1311
1312
  ${(indexHtml.match(/<head>([\s\S]*?)<\/head>/i)?.[1] || "").trim()}`;
1312
1313
  body += `
@@ -1435,16 +1436,18 @@ function getDefine(_options) {
1435
1436
  }
1436
1437
  async function getAlias(options) {
1437
1438
  const alias = [
1439
+ /**
1440
+ * virtual module alias
1441
+ * `/` 开头无法 declare module 类型
1442
+ *
1443
+ * #valaxy/* => /@valaxyjs/*
1444
+ */
1445
+ { find: /^#valaxy\/(.*)/, replacement: "/@valaxyjs/$1" },
1438
1446
  { find: "~/", replacement: `${toAtFS(options.userRoot)}/` },
1439
1447
  { find: "valaxy/client/", replacement: `${toAtFS(options.clientRoot)}/` },
1440
1448
  { find: "valaxy/package.json", replacement: toAtFS(resolve6(options.clientRoot, "../package.json")) },
1441
1449
  { find: /^valaxy$/, replacement: toAtFS(resolve6(options.clientRoot, "index.ts")) },
1442
1450
  { find: "@valaxyjs/client/", replacement: `${toAtFS(options.clientRoot)}/` },
1443
- // virtual module alias
1444
- {
1445
- find: /^#valaxy\/(.*)/,
1446
- replacement: "/@valaxyjs/$1"
1447
- },
1448
1451
  // import theme
1449
1452
  { find: "virtual:valaxy-theme", replacement: `${toAtFS(options.themeRoot)}/client/index.ts` },
1450
1453
  { find: `valaxy-theme-${options.theme}/client`, replacement: `${toAtFS(resolve6(options.themeRoot))}/client/index.ts` },
@@ -1999,8 +2002,8 @@ function linkPlugin(md3, externalAttrs, base) {
1999
2002
  let url = hrefAttr[1];
2000
2003
  const indexMatch = url.match(indexRE);
2001
2004
  if (indexMatch) {
2002
- const [, path17, hash] = indexMatch;
2003
- url = path17 + hash;
2005
+ const [, path18, hash] = indexMatch;
2006
+ url = path18 + hash;
2004
2007
  } else {
2005
2008
  let cleanUrl = url.replace(/[?#].*$/, "");
2006
2009
  if (cleanUrl.endsWith(".md")) {
@@ -2860,10 +2863,10 @@ function createClientSetupPlugin({ clientRoot, themeRoot, userRoot: userRoot2 })
2860
2863
  themeRoot,
2861
2864
  userRoot2
2862
2865
  ]).map((i) => join4(i, "setup", name));
2863
- setups.forEach((path17, idx) => {
2864
- if (!existsSync(path17))
2866
+ setups.forEach((path18, idx) => {
2867
+ if (!existsSync(path18))
2865
2868
  return;
2866
- imports.push(`import __n${idx} from '${toAtFS(path17)}'`);
2869
+ imports.push(`import __n${idx} from '${toAtFS(path18)}'`);
2867
2870
  let fn = `__n${idx}`;
2868
2871
  if (/\binjection_return\b/.test(code))
2869
2872
  fn = `injection_return = ${fn}`;
@@ -2872,7 +2875,7 @@ function createClientSetupPlugin({ clientRoot, themeRoot, userRoot: userRoot2 })
2872
2875
  else
2873
2876
  fn += "()";
2874
2877
  injections.push(
2875
- `// ${path17}`,
2878
+ `// ${path18}`,
2876
2879
  fn
2877
2880
  );
2878
2881
  });
@@ -2920,9 +2923,9 @@ function deepMerge(a, b, rootPath = "") {
2920
2923
  async function loadSetups(roots, name, arg, initial, merge = true) {
2921
2924
  let returns = initial;
2922
2925
  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 });
2926
+ const path18 = resolve8(root, "setup", name);
2927
+ if (fs13.existsSync(path18)) {
2928
+ const setup = await jiti2.import(path18, { default: true });
2926
2929
  const result = await setup(arg);
2927
2930
  if (result !== null) {
2928
2931
  returns = merge ? deepMerge(returns, result) : result;
@@ -3055,7 +3058,7 @@ async function createUnocssPlugin(options) {
3055
3058
  // node/plugins/valaxy/index.ts
3056
3059
  import { consola as consola12 } from "consola";
3057
3060
  import { colors as colors11 } from "consola/utils";
3058
- import fs19 from "fs-extra";
3061
+ import fs20 from "fs-extra";
3059
3062
  import { join as join7, relative, resolve as resolve10 } from "pathe";
3060
3063
 
3061
3064
  // node/virtual/addons.ts
@@ -3074,6 +3077,35 @@ export default [${components}]`;
3074
3077
  }
3075
3078
  };
3076
3079
 
3080
+ // node/virtual/blogs.ts
3081
+ import path8 from "path";
3082
+ import fs16 from "fs-extra";
3083
+ function createBlogTemplate(name) {
3084
+ return {
3085
+ id: `/@valaxyjs/blog/${name}s`,
3086
+ async getContent({ userRoot: userRoot2 }) {
3087
+ const root = path8.resolve(userRoot2, "pages", "collections");
3088
+ if (!await fs16.pathExists(root)) {
3089
+ return `export default []`;
3090
+ }
3091
+ const isDir = (file) => fs16.statSync(path8.join(root, file)).isDirectory();
3092
+ const files = fs16.readdirSync(root).filter((file) => isDir(file)).map((file) => path8.join(root, file, "index.ts"));
3093
+ const imports = [];
3094
+ const getImportedName = (idx) => `__valaxy_${name}_${idx + 1}`;
3095
+ files.forEach((file, idx) => {
3096
+ const importedName = getImportedName(idx);
3097
+ imports.push(`import ${importedName} from '${toAtFS(file)}'`);
3098
+ });
3099
+ imports.push(`export default [${files.map((_, idx) => getImportedName(idx)).join(", ")}]`);
3100
+ return imports.join("\n");
3101
+ }
3102
+ };
3103
+ }
3104
+ var configs = [
3105
+ "collection"
3106
+ ];
3107
+ var templateBlogs = configs.map(createBlogTemplate);
3108
+
3077
3109
  // node/virtual/config.ts
3078
3110
  var templateConfig = {
3079
3111
  id: "/@valaxyjs/config",
@@ -3093,7 +3125,7 @@ var templateConfig = {
3093
3125
  };
3094
3126
 
3095
3127
  // node/virtual/locales.ts
3096
- import fs16 from "fs-extra";
3128
+ import fs17 from "fs-extra";
3097
3129
  var templateLocales = {
3098
3130
  id: "/@valaxyjs/locales",
3099
3131
  async getContent({ roots, config }) {
@@ -3113,7 +3145,7 @@ var templateLocales = {
3113
3145
  roots.forEach((root, i) => {
3114
3146
  languages.forEach((lang) => {
3115
3147
  const langYml = `${root}/locales/${lang}.yml`;
3116
- if (fs16.existsSync(langYml) && fs16.readFileSync(langYml, "utf-8")) {
3148
+ if (fs17.existsSync(langYml) && fs17.readFileSync(langYml, "utf-8")) {
3117
3149
  const varName = lang.replace("-", "") + i;
3118
3150
  imports.unshift(`import ${varName} from "${toAtFS(langYml)}"`);
3119
3151
  imports.push(`messages['${lang}'] = replaceArrMerge(${varName}, messages['${lang}'])`);
@@ -3162,13 +3194,14 @@ var templates = [
3162
3194
  templateAddons,
3163
3195
  templateConfig,
3164
3196
  templateLocales,
3165
- templateStyles
3197
+ templateStyles,
3198
+ ...templateBlogs
3166
3199
  ];
3167
3200
 
3168
3201
  // node/plugins/markdown/markdownToVue.ts
3169
3202
  import _debug2 from "debug";
3170
3203
  import { LRUCache } from "lru-cache";
3171
- import path11 from "pathe";
3204
+ import path12 from "pathe";
3172
3205
 
3173
3206
  // node/app/index.ts
3174
3207
  import { createHooks } from "hookable";
@@ -3227,10 +3260,10 @@ function createTransformCodeBlock(options) {
3227
3260
 
3228
3261
  // node/plugins/markdown/transform/dead-links.ts
3229
3262
  import { slash as slash4 } from "@antfu/utils";
3230
- import fs17 from "fs-extra";
3231
- import path8 from "pathe";
3263
+ import fs18 from "fs-extra";
3264
+ import path9 from "pathe";
3232
3265
  function createScanDeadLinks(options) {
3233
- const srcDir = path8.resolve(options.userRoot, "pages");
3266
+ const srcDir = path9.resolve(options.userRoot, "pages");
3234
3267
  const { ignoreDeadLinks } = options.config.build;
3235
3268
  const publicDir = options.config.vite?.publicDir || "public";
3236
3269
  return (code, id) => {
@@ -3240,7 +3273,7 @@ function createScanDeadLinks(options) {
3240
3273
  const file = id;
3241
3274
  const deadLinks = [];
3242
3275
  const recordDeadLink = (url) => {
3243
- deadLinks.push({ url, file: path8.relative(srcDir, fileOrig) });
3276
+ deadLinks.push({ url, file: path9.relative(srcDir, fileOrig) });
3244
3277
  };
3245
3278
  function shouldIgnoreDeadLink(url) {
3246
3279
  if (!ignoreDeadLinks)
@@ -3260,7 +3293,7 @@ function createScanDeadLinks(options) {
3260
3293
  });
3261
3294
  }
3262
3295
  if (links) {
3263
- const dir = path8.dirname(file);
3296
+ const dir = path9.dirname(file);
3264
3297
  for (let url of links) {
3265
3298
  const { pathname } = new URL(url, "http://a.com");
3266
3299
  if (!treatAsHtml(pathname))
@@ -3270,11 +3303,11 @@ function createScanDeadLinks(options) {
3270
3303
  url += `index`;
3271
3304
  const resolved = decodeURIComponent(
3272
3305
  slash4(
3273
- url.startsWith("/") ? url.slice(1) : path8.relative(srcDir, path8.resolve(dir, url))
3306
+ url.startsWith("/") ? url.slice(1) : path9.relative(srcDir, path9.resolve(dir, url))
3274
3307
  )
3275
3308
  // /index => /
3276
3309
  ).replace(/\/index$/, "");
3277
- if (!options.pages.includes(resolved) && !fs17.existsSync(path8.resolve(dir, publicDir, `${resolved}.html`)) && !shouldIgnoreDeadLink(url)) {
3310
+ if (!options.pages.includes(resolved) && !fs18.existsSync(path9.resolve(dir, publicDir, `${resolved}.html`)) && !shouldIgnoreDeadLink(url)) {
3278
3311
  recordDeadLink(url);
3279
3312
  }
3280
3313
  }
@@ -3436,8 +3469,8 @@ function transformHexoTags(code, id) {
3436
3469
  }
3437
3470
 
3438
3471
  // node/plugins/markdown/transform/markdown.ts
3439
- import path9 from "path";
3440
- import fs18 from "fs-extra";
3472
+ import path10 from "path";
3473
+ import fs19 from "fs-extra";
3441
3474
  function genProvideCode(name, data) {
3442
3475
  return [
3443
3476
  `const $${name} = ${transformObject(data)}`,
@@ -3468,8 +3501,8 @@ function injectPageDataCode(pageData) {
3468
3501
  return vueContextImports;
3469
3502
  }
3470
3503
  function createTransformMarkdown(options) {
3471
- const loaderVuePath = path9.resolve(options.clientRoot, "templates", "loader.vue");
3472
- const loaderVue = fs18.readFileSync(loaderVuePath, "utf-8");
3504
+ const loaderVuePath = path10.resolve(options.clientRoot, "templates", "loader.vue");
3505
+ const loaderVue = fs19.readFileSync(loaderVuePath, "utf-8");
3473
3506
  return (code, id, pageData) => {
3474
3507
  const isDev = options.mode === "dev";
3475
3508
  if (!isDev) {
@@ -3508,7 +3541,7 @@ ${code.slice(injectB)}`;
3508
3541
  }
3509
3542
 
3510
3543
  // node/plugins/markdown/transform/page-data.ts
3511
- import path10 from "pathe";
3544
+ import path11 from "pathe";
3512
3545
  function getHeadMetaContent(head, name) {
3513
3546
  if (!head || !head.length)
3514
3547
  return void 0;
@@ -3525,7 +3558,7 @@ function inferDescription(frontmatter) {
3525
3558
  }
3526
3559
  async function generatePageData(code, id, options) {
3527
3560
  const fileInfo = Valaxy.state.idMap.get(id);
3528
- const relativePath = path10.relative(options.userRoot, id);
3561
+ const relativePath = path11.relative(options.userRoot, id);
3529
3562
  const fm = JSON.parse(JSON.stringify(fileInfo?.frontmatter));
3530
3563
  const pageData = {
3531
3564
  title: fm.title || fileInfo?.title || "",
@@ -3582,7 +3615,7 @@ async function createMarkdownToVueRenderFn(options, _viteConfig) {
3582
3615
  const isBuild = options.mode === "build";
3583
3616
  return async (code, id) => {
3584
3617
  const file = id;
3585
- const relativePath = path11.relative(srcDir, file);
3618
+ const relativePath = path12.relative(srcDir, file);
3586
3619
  const deadLinks = scanDeadLinks(code, id);
3587
3620
  const cacheKey = JSON.stringify({ code, id });
3588
3621
  if (isBuild) {
@@ -3621,7 +3654,7 @@ async function createMarkdownToVueRenderFn(options, _viteConfig) {
3621
3654
  var nullVue = 'import { defineComponent } from "vue"; export default defineComponent({ render: () => null });';
3622
3655
  function generateAppVue(root) {
3623
3656
  const appVue = join7(root, "App.vue");
3624
- if (!fs19.existsSync(appVue))
3657
+ if (!fs20.existsSync(appVue))
3625
3658
  return nullVue;
3626
3659
  const scripts = [
3627
3660
  `import AppVue from "${toAtFS(appVue)}"`,
@@ -3751,9 +3784,9 @@ async function createValaxyPlugin(options, serverOptions = {}) {
3751
3784
  const endCount = countPerformanceTime();
3752
3785
  const content = await read();
3753
3786
  const { code, pageData } = await markdownToVue(content, file);
3754
- const path17 = `/${relative(`${options.userRoot}/pages`, file)}`;
3787
+ const path18 = `/${relative(`${options.userRoot}/pages`, file)}`;
3755
3788
  const payload = {
3756
- path: path17,
3789
+ path: path18,
3757
3790
  pageData
3758
3791
  };
3759
3792
  server.hot.send({
@@ -3780,7 +3813,7 @@ async function createValaxyPlugin(options, serverOptions = {}) {
3780
3813
  }
3781
3814
 
3782
3815
  // node/plugins/vueRouter.ts
3783
- import fs20 from "fs-extra";
3816
+ import fs21 from "fs-extra";
3784
3817
  import matter3 from "gray-matter";
3785
3818
  import { convert } from "html-to-text";
3786
3819
  import { MarkdownItAsync } from "markdown-it-async";
@@ -3891,10 +3924,16 @@ async function createRouterPlugin(valaxyApp) {
3891
3924
  layout: "post"
3892
3925
  });
3893
3926
  }
3927
+ } else if (route.fullPath.startsWith("/collections/")) {
3928
+ if (route.fullPath.split("/").length > 3) {
3929
+ route.addToMeta({
3930
+ layout: "collection"
3931
+ });
3932
+ }
3894
3933
  }
3895
- const path17 = route.components.get("default") || "";
3896
- if (path17.endsWith(".md")) {
3897
- const md3 = fs20.readFileSync(path17, "utf-8");
3934
+ const path18 = route.components.get("default") || "";
3935
+ if (path18.endsWith(".md")) {
3936
+ const md3 = fs21.readFileSync(path18, "utf-8");
3898
3937
  const { data, excerpt, content } = matter3(md3, matterOptions);
3899
3938
  const mdFm = data;
3900
3939
  const lastUpdated = options.config.siteConfig.lastUpdated;
@@ -3904,10 +3943,10 @@ async function createRouterPlugin(valaxyApp) {
3904
3943
  delete mdFm.photos;
3905
3944
  }
3906
3945
  if (!mdFm.date)
3907
- mdFm.date = (await fs20.stat(path17)).mtime;
3946
+ mdFm.date = (await fs21.stat(path18)).mtime;
3908
3947
  if (lastUpdated) {
3909
3948
  if (!mdFm.updated)
3910
- mdFm.updated = (await fs20.stat(path17)).ctime;
3949
+ mdFm.updated = (await fs21.stat(path18)).ctime;
3911
3950
  }
3912
3951
  if (mdFm.from) {
3913
3952
  if (Array.isArray(mdFm.from)) {
@@ -3964,7 +4003,7 @@ async function createRouterPlugin(valaxyApp) {
3964
4003
  data,
3965
4004
  excerpt,
3966
4005
  content,
3967
- path: path17
4006
+ path: path18
3968
4007
  };
3969
4008
  valaxyConfig.extendMd?.(ctx);
3970
4009
  }
@@ -4178,13 +4217,13 @@ async function ssgBuild(valaxyApp, viteConfig = {}) {
4178
4217
  if (options.config.build.ssgForPagination) {
4179
4218
  defaultConfig.ssgOptions.includedRoutes = (paths, _routes) => {
4180
4219
  const newPaths = paths;
4181
- const posts = paths.filter((path17) => path17.startsWith("/posts/"));
4220
+ const posts = paths.filter((path18) => path18.startsWith("/posts/"));
4182
4221
  const pageNumber = Math.ceil(posts.length / options.config.siteConfig.pageSize);
4183
4222
  consola14.info(`Generate ${colors12.yellow(pageNumber)} pages for pagination.`);
4184
4223
  for (let i = 1; i <= pageNumber; i++)
4185
4224
  newPaths.push(`/page/${i}`);
4186
4225
  if (!options.config.vite?.ssgOptions?.includeAllRoutes)
4187
- return newPaths.filter((path17) => !path17.split("/").some((p) => p.startsWith(":")));
4226
+ return newPaths.filter((path18) => !path18.split("/").some((p) => p.startsWith(":")));
4188
4227
  else
4189
4228
  return newPaths;
4190
4229
  };
@@ -4195,14 +4234,14 @@ async function ssgBuild(valaxyApp, viteConfig = {}) {
4195
4234
  async function postProcessForSSG(options) {
4196
4235
  const { userRoot: userRoot2 } = options;
4197
4236
  const indexPath = resolve13(userRoot2, "dist/index.html");
4198
- if (fs21.existsSync(indexPath)) {
4237
+ if (fs22.existsSync(indexPath)) {
4199
4238
  consola14.info("post process for ssg...");
4200
- const indexFile = await fs21.readFile(indexPath, "utf-8");
4239
+ const indexFile = await fs22.readFile(indexPath, "utf-8");
4201
4240
  const htmlTag = "</html>";
4202
4241
  if (!indexFile.endsWith(htmlTag)) {
4203
4242
  consola14.warn("fix incomplete index.html...");
4204
4243
  const htmlTagStart = indexFile.lastIndexOf(htmlTag);
4205
- await fs21.writeFile(indexPath, indexFile.slice(0, htmlTagStart + htmlTag.length), "utf-8");
4244
+ await fs22.writeFile(indexPath, indexFile.slice(0, htmlTagStart + htmlTag.length), "utf-8");
4206
4245
  }
4207
4246
  }
4208
4247
  if (!options.config.siteConfig.redirects?.useVueRouter)
@@ -4215,7 +4254,7 @@ async function generateClientRedirects(options) {
4215
4254
  const task = redirectRules.map(async (rule) => {
4216
4255
  const fromPath = join8(outputPath, `${rule.from}.html`);
4217
4256
  const toPath = join8(outputPath, `${rule.to}.html`);
4218
- const routeExist = await fs21.pathExists(toPath);
4257
+ const routeExist = await fs22.pathExists(toPath);
4219
4258
  if (!routeExist)
4220
4259
  throw new Error(`the route of '${rule.to}' not exists`);
4221
4260
  await writeRedirectFiles(rule.to, fromPath);
@@ -4258,7 +4297,7 @@ async function createServer(valaxyApp, viteConfig = {}, serverOptions = {}) {
4258
4297
 
4259
4298
  // node/cli/utils/cli.ts
4260
4299
  import os from "os";
4261
- import path12 from "path";
4300
+ import path13 from "path";
4262
4301
  import process8 from "process";
4263
4302
  import { consola as consola15 } from "consola";
4264
4303
  import { colors as colors14 } from "consola/utils";
@@ -4278,7 +4317,7 @@ function printInfo(options, port, remote) {
4278
4317
  console.log(` ${colors14.bold("\u{1F30C} Valaxy")} ${colors14.blue(`v${version}`)}`);
4279
4318
  console.log();
4280
4319
  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))}`);
4320
+ console.log(` ${colors14.dim("\u{1F4C1}")} ${colors14.dim(path13.resolve(options.userRoot))}`);
4282
4321
  if (port) {
4283
4322
  console.log();
4284
4323
  console.log(`${colors14.dim(" Preview ")} > ${colors14.cyan(`http://localhost:${colors14.bold(port)}/`)}`);
@@ -4356,7 +4395,7 @@ async function execBuild({ ssg, root, output, log }) {
4356
4395
  setEnvProd();
4357
4396
  if (!isPagesDirExist(root))
4358
4397
  process9.exit(0);
4359
- const userRoot2 = path13.resolve(root);
4398
+ const userRoot2 = path14.resolve(root);
4360
4399
  const options = await resolveOptions({ userRoot: userRoot2 }, "build");
4361
4400
  setTimezone(options.config.siteConfig.timezone);
4362
4401
  printInfo(options);
@@ -4376,11 +4415,11 @@ async function execBuild({ ssg, root, output, log }) {
4376
4415
  valaxyViteConfig,
4377
4416
  {
4378
4417
  // avoid load userRoot/vite.config.ts repeatedly
4379
- configFile: path13.resolve(options.clientRoot, "vite.config.ts"),
4418
+ configFile: path14.resolve(options.clientRoot, "vite.config.ts"),
4380
4419
  build: {
4381
4420
  // make out dir empty, https://vitejs.dev/config/#build-emptyoutdir
4382
4421
  emptyOutDir: true,
4383
- outDir: path13.resolve(options.userRoot, output)
4422
+ outDir: path14.resolve(options.userRoot, output)
4384
4423
  },
4385
4424
  logLevel: log
4386
4425
  }
@@ -4437,18 +4476,18 @@ function registerBuildCommand(cli2) {
4437
4476
  }
4438
4477
 
4439
4478
  // node/cli/clean.ts
4440
- import path14 from "path";
4479
+ import path15 from "path";
4441
4480
  import process10 from "process";
4442
4481
  import { consola as consola17 } from "consola";
4443
- import fs22 from "fs-extra";
4482
+ import fs23 from "fs-extra";
4444
4483
  async function cleanDist() {
4445
- const distDir = path14.join(process10.cwd(), "dist");
4446
- const cacheDir = path14.join(process10.cwd(), ".valaxy");
4484
+ const distDir = path15.join(process10.cwd(), "dist");
4485
+ const cacheDir = path15.join(process10.cwd(), ".valaxy");
4447
4486
  consola17.box("\u{1F9F9} Starting clean...");
4448
- if (await fs22.exists(distDir)) {
4487
+ if (await fs23.exists(distDir)) {
4449
4488
  consola17.info("dist directory exists, removing...");
4450
4489
  try {
4451
- await fs22.rm(distDir, { recursive: true, force: true });
4490
+ await fs23.rm(distDir, { recursive: true, force: true });
4452
4491
  consola17.success("dist directory has been successfully removed.");
4453
4492
  } catch (error) {
4454
4493
  consola17.error("Failed to remove dist directory.");
@@ -4457,10 +4496,10 @@ async function cleanDist() {
4457
4496
  } else {
4458
4497
  consola17.info("No dist directory found, nothing to clean.");
4459
4498
  }
4460
- if (await fs22.exists(cacheDir)) {
4499
+ if (await fs23.exists(cacheDir)) {
4461
4500
  consola17.info(".valaxy cache directory exists, removing...");
4462
4501
  try {
4463
- await fs22.rm(cacheDir, { recursive: true, force: true });
4502
+ await fs23.rm(cacheDir, { recursive: true, force: true });
4464
4503
  consola17.success(".valaxy cache directory has been successfully removed.");
4465
4504
  } catch (error) {
4466
4505
  consola17.error("Failed to remove .valaxy cache directory.");
@@ -4539,7 +4578,7 @@ function registerDeployCommand(cli2) {
4539
4578
  }
4540
4579
 
4541
4580
  // node/cli/dev.ts
4542
- import path15 from "path";
4581
+ import path16 from "path";
4543
4582
  import process13 from "process";
4544
4583
  import { mergeConfig as mergeConfig5 } from "vite";
4545
4584
 
@@ -4668,7 +4707,7 @@ async function startValaxyDev({
4668
4707
  // initial vite config
4669
4708
  ...defaultViteConfig,
4670
4709
  // avoid load userRoot/vite.config.ts repeatedly
4671
- configFile: path15.resolve(resolvedOptions.clientRoot, "vite.config.ts"),
4710
+ configFile: path16.resolve(resolvedOptions.clientRoot, "vite.config.ts"),
4672
4711
  server: {
4673
4712
  watch: {
4674
4713
  // watch theme updated
@@ -4731,7 +4770,7 @@ import { consola as consola19 } from "consola";
4731
4770
  import { colors as colors17 } from "consola/utils";
4732
4771
  import dayjs2 from "dayjs";
4733
4772
  import { render } from "ejs";
4734
- import fs24 from "fs-extra";
4773
+ import fs25 from "fs-extra";
4735
4774
 
4736
4775
  // node/cli/utils/constants.ts
4737
4776
  import process14 from "process";
@@ -4745,14 +4784,14 @@ date: <%=date%>
4745
4784
 
4746
4785
  // node/cli/utils/scaffold.ts
4747
4786
  import { readFile as readFile2 } from "fs/promises";
4748
- import path16 from "path";
4749
- import fs23 from "fs-extra";
4787
+ import path17 from "path";
4788
+ import fs24 from "fs-extra";
4750
4789
  async function getTemplate(layout) {
4751
4790
  const { clientRoot, themeRoot } = await resolveOptions({ userRoot });
4752
4791
  const roots = [userRoot, themeRoot, clientRoot];
4753
4792
  for (const root of roots) {
4754
- const scaffoldPath = path16.resolve(root, "scaffolds", `${layout}.md`);
4755
- if (await fs23.exists(scaffoldPath))
4793
+ const scaffoldPath = path17.resolve(root, "scaffolds", `${layout}.md`);
4794
+ if (await fs24.exists(scaffoldPath))
4756
4795
  return readFile2(scaffoldPath, "utf-8");
4757
4796
  }
4758
4797
  return false;
@@ -4766,11 +4805,11 @@ async function create(params) {
4766
4805
  const postFileName = `${params.title}${counter ? `-${counter}` : ""}`;
4767
4806
  const postFilePath = params.folder ? join9(postFileName, "index.md") : `${postFileName}.md`;
4768
4807
  const targetPath = resolve14(pagesPath, "posts", postFilePath);
4769
- if (!await fs24.exists(targetPath)) {
4770
- await fs24.ensureDir(dirname5(targetPath));
4808
+ if (!await fs25.exists(targetPath)) {
4809
+ await fs25.ensureDir(dirname5(targetPath));
4771
4810
  const content = await genLayoutTemplate(params);
4772
4811
  try {
4773
- await fs24.writeFile(targetPath, content, "utf-8");
4812
+ await fs25.writeFile(targetPath, content, "utf-8");
4774
4813
  consola19.success(`[valaxy new]: successfully generated file ${colors17.magenta(targetPath)}`);
4775
4814
  } catch (e) {
4776
4815
  console.log(e);
@@ -4823,12 +4862,12 @@ function registerNewCommand(cli2) {
4823
4862
  describe: "Generate post with the current date"
4824
4863
  }).strict().help();
4825
4864
  },
4826
- async ({ title, folder, path: path17, date, layout }) => {
4865
+ async ({ title, folder, path: path18, date, layout }) => {
4827
4866
  await create({
4828
4867
  title,
4829
4868
  date,
4830
4869
  layout,
4831
- path: path17,
4870
+ path: path18,
4832
4871
  folder
4833
4872
  });
4834
4873
  }
@@ -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
  */
@@ -522,6 +566,14 @@ interface SiteConfig {
522
566
  * @description 搜索结果列表数据所在路径
523
567
  */
524
568
  dataPath: string;
569
+ /**
570
+ * fast-glob pattern to match Fuse List Data
571
+ * @default `pages\/**\/*.md`
572
+ * ```ts
573
+ * await fg(`${userRoot}/pages/posts/**\/*.md`)
574
+ * ```
575
+ */
576
+ pattern?: string;
525
577
  /**
526
578
  * @see https://fusejs.io/api/options.html
527
579
  */
@@ -3,7 +3,7 @@ import {
3
3
  registerDevCommand,
4
4
  run,
5
5
  startValaxyDev
6
- } from "../../chunk-5GRPXDJR.js";
6
+ } from "../../chunk-JHXXCWZC.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-bf4WqwPK.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-JHXXCWZC.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-bf4WqwPK.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-bf4WqwPK.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.10",
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",
@@ -125,14 +125,14 @@
125
125
  "vite-plugin-vue-devtools": "^7.7.7",
126
126
  "vite-plugin-vue-layouts": "^0.11.0",
127
127
  "vite-ssg": "^28.0.0",
128
- "vite-ssg-sitemap": "^0.9.0",
128
+ "vite-ssg-sitemap": "^0.10.0",
129
129
  "vitepress-plugin-group-icons": "^1.6.1",
130
130
  "vue": "^3.5.17",
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.10",
135
+ "@valaxyjs/utils": "0.25.10"
136
136
  },
137
137
  "devDependencies": {
138
138
  "@mdit-vue/plugin-component": "^2.1.4",
@@ -10,7 +10,7 @@
10
10
  * ```
11
11
  */
12
12
  export function tObject<T = string>(data: string | Record<string, T>, lang: string): T | string {
13
- if (typeof data === 'object') {
13
+ if (data && typeof data === 'object') {
14
14
  return data[lang] || Object.values(data)[0] || ''
15
15
  }
16
16
  return data
package/types/config.ts CHANGED
@@ -180,6 +180,14 @@ export interface SiteConfig {
180
180
  * @description 搜索结果列表数据所在路径
181
181
  */
182
182
  dataPath: string
183
+ /**
184
+ * fast-glob pattern to match Fuse List Data
185
+ * @default `pages\/**\/*.md`
186
+ * ```ts
187
+ * await fg(`${userRoot}/pages/posts/**\/*.md`)
188
+ * ```
189
+ */
190
+ pattern?: string
183
191
  /**
184
192
  * @see https://fusejs.io/api/options.html
185
193
  */
@@ -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