valaxy 0.23.6 → 0.24.1

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.
package/client/App.vue CHANGED
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- // import it in setup/main.ts for ssg
2
+ // vite-ssg build for client
3
3
  import 'virtual:group-icons.css'
4
4
  // import { defineAsyncComponent } from 'vue'
5
5
  /**
@@ -1,26 +1,32 @@
1
1
  <script setup lang="ts">
2
2
  // https://router.vuejs.org/guide/advanced/extending-router-link.html#extending-routerlink
3
3
  import { computed } from 'vue'
4
+ import { RouterLink } from 'vue-router'
5
+ import { EXTERNAL_URL_RE } from '../../node/constants'
4
6
 
5
7
  const props = defineProps<{
6
8
  showExternalIcon?: boolean
7
9
  to?: string
8
10
  href?: string
11
+ target?: string
9
12
  }>()
10
13
 
11
14
  const link = computed(() => props.href || props.to || '#')
12
15
 
13
16
  const isExternalLink = computed(() => {
14
- return typeof link.value === 'string' && link.value.startsWith('http')
17
+ return (link.value && EXTERNAL_URL_RE.test(link.value)) || props.target === '_blank'
15
18
  })
16
19
  </script>
17
20
 
18
21
  <template>
19
- <a v-if="isExternalLink" v-bind="$attrs" :href="link" target="_blank">
22
+ <a
23
+ v-if="isExternalLink" class="va-link" v-bind="$attrs" :href="link"
24
+ :target="target ?? (isExternalLink ? '_blank' : undefined)"
25
+ >
20
26
  <slot />
21
27
  <div v-if="showExternalIcon" class="icon-link inline-block" i-ri-arrow-right-up-line />
22
28
  </a>
23
- <RouterLink v-else v-bind="$attrs" :to="link">
29
+ <RouterLink v-else class="va-link" v-bind="$attrs" :to="link">
24
30
  <slot />
25
31
  </RouterLink>
26
32
  </template>
@@ -31,7 +31,7 @@ const licenseHtml = computed(() => {
31
31
  <strong>
32
32
  {{ t('post.copyright.author') + t('symbol.colon') }}
33
33
  </strong>
34
- <span>{{ siteConfig.author.name }}</span>
34
+ <span>{{ t(siteConfig.author.name) }}</span>
35
35
  </li>
36
36
  <li v-if="url" class="post-copyright-link">
37
37
  <strong>
@@ -5,9 +5,10 @@ import { useSeoMeta } from '@unhead/vue'
5
5
  import { computed } from 'vue'
6
6
  import { useI18n } from 'vue-i18n'
7
7
 
8
+ import { tObject } from '../../../shared/utils/i18n'
8
9
  import { useFrontmatter, useValaxyHead } from '../../composables'
9
- import { useTimezone } from '../../composables/global'
10
10
 
11
+ import { useTimezone } from '../../composables/global'
11
12
  // https://github.com/vueuse/head
12
13
  // you can use this to manipulate the document head in any components,
13
14
  // they will be rendered correctly in the html results with vite-ssg
@@ -20,7 +21,7 @@ export function useValaxyApp() {
20
21
 
21
22
  const { locale } = useI18n()
22
23
 
23
- const title = computed(() => fm.value[`title_${locale.value}`] || fm.value.title)
24
+ const title = computed(() => tObject(fm.value.title || '', locale.value))
24
25
 
25
26
  // seo
26
27
  // todo: get first image url from markdown
@@ -33,7 +34,7 @@ export function useValaxyApp() {
33
34
  ogLocale: computed(() => locale.value || fm.value.lang || siteConfig.value.lang || 'en'),
34
35
  ogLocaleAlternate: computed(() => siteConfig.value.languages.filter(l => l !== locale.value)),
35
36
  ogSiteName: computed(() => siteConfig.value.title),
36
- ogTitle: computed(() => fm.value.title || siteConfig.value.title),
37
+ ogTitle: computed(() => tObject(fm.value.title || siteConfig.value.title, locale.value)),
37
38
  ogImage: computed(() => fm.value.ogImage || fm.value.cover || siteConfig.value.favicon),
38
39
  ogType: 'website',
39
40
  ogUrl: siteUrl,
@@ -5,15 +5,16 @@ import { useI18n } from 'vue-i18n'
5
5
 
6
6
  import { useFrontmatter } from '../../composables'
7
7
  import { useSiteConfig } from '../../config'
8
+ import { tObject } from '../../utils'
8
9
 
9
10
  export function useValaxyHead() {
10
11
  const { locale } = useI18n()
11
12
 
12
13
  const fm = useFrontmatter()
13
14
  const siteConfig = useSiteConfig()
14
- const title = computed<string>(() => fm.value[`title_${locale.value}`] || fm.value.title)
15
+ const $title = computed(() => tObject(fm.value.title || '', locale.value))
15
16
  useHead({
16
- title,
17
+ title: $title,
17
18
  titleTemplate: (title) => {
18
19
  return fm.value.titleTemplate || (title ? `${title} - ${siteConfig.value.title}` : siteConfig.value.title)
19
20
  },
@@ -1,8 +1,11 @@
1
+ import type { Ref } from 'vue'
1
2
  import { isClient, useStorage } from '@vueuse/core'
2
3
  import dayjs from 'dayjs'
4
+ import { computed } from 'vue'
5
+
3
6
  // not optimize deps all locales
4
7
  import { useI18n } from 'vue-i18n'
5
-
8
+ import { tObject } from '../../shared/utils/i18n'
6
9
  import 'dayjs/locale/en'
7
10
  import 'dayjs/locale/zh-cn'
8
11
 
@@ -34,3 +37,29 @@ export function useLocale() {
34
37
  toggleLocales,
35
38
  }
36
39
  }
40
+
41
+ /**
42
+ * get locale title
43
+ *
44
+ * ```md
45
+ * ---
46
+ * title:
47
+ * zh-CN: Valaxy - Vue 驱动的静态站点生成器
48
+ * en: Valaxy - Vue Powered Static Site Generator
49
+ * ---
50
+ * ```
51
+ *
52
+ * @param fm
53
+ */
54
+ export function useLocaleTitle(fm: Ref<{
55
+ title?: string | Record<string, string>
56
+ } | null>) {
57
+ const { locale } = useI18n()
58
+ return computed(() => {
59
+ if (!fm.value)
60
+ return ''
61
+
62
+ const lang = locale.value
63
+ return tObject(fm.value.title || '', lang) || ''
64
+ })
65
+ }
@@ -3,7 +3,7 @@ import type { ComputedRef } from 'vue'
3
3
  import { computed } from 'vue'
4
4
  import { useI18n } from 'vue-i18n'
5
5
  import { useRouterStore } from '../../stores'
6
- import { sortByDate } from '../../utils'
6
+ import { sortByDate, tObject } from '../../utils'
7
7
 
8
8
  export * from './usePagination'
9
9
  export * from './usePrevNext'
@@ -11,8 +11,7 @@ export * from './usePrevNext'
11
11
  export function usePostTitle(post: ComputedRef<Post>) {
12
12
  const { locale } = useI18n()
13
13
  return computed(() => {
14
- const lang = locale.value === 'zh-CN' ? 'zh' : locale.value
15
- return post.value[`title_${lang}`] || post.value.title
14
+ return tObject(post.value.title || '', locale.value)
16
15
  })
17
16
  }
18
17
 
@@ -48,6 +48,8 @@ export const i18n = createI18n({
48
48
  legacy: false,
49
49
  locale: '',
50
50
  messages: valaxyMessages,
51
+ // use key
52
+ missingWarn: false,
51
53
  })
52
54
 
53
55
  export async function install({ app, router }: ViteSSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
@@ -1,3 +1,4 @@
1
+ export * from '../../shared'
1
2
  export * from './cdn'
2
3
  export * from './code'
3
4
  export * from './content'
@@ -5,4 +6,5 @@ export * from './helper'
5
6
  export * from './router'
6
7
  export * from './time'
7
8
  export * from './types'
9
+
8
10
  export * from './wrap'
@@ -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.23.6";
7
+ var version = "0.24.1";
8
8
 
9
9
  // node/modules/fuse.ts
10
10
  import path4 from "path";
@@ -120,6 +120,9 @@ function getRollupOptions(options) {
120
120
  if (id.includes("plugin-vue:export-helper")) {
121
121
  return "framework";
122
122
  }
123
+ if (id.includes("virtual:group-icons.css")) {
124
+ return "group-icons";
125
+ }
123
126
  const libs = [
124
127
  "@vueuse/motion",
125
128
  "dayjs",
@@ -514,7 +517,7 @@ function getGitTimestamp(file, type = "updated") {
514
517
 
515
518
  // node/constants/index.ts
516
519
  var EXCERPT_SEPARATOR = "<!-- more -->";
517
- var EXTERNAL_URL_RE = /^https?:/i;
520
+ var EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
518
521
  var PATHNAME_PROTOCOL_RE = /^pathname:\/\//;
519
522
  var ALL_ROUTE = "/:all(.*)*";
520
523
  var customElements = /* @__PURE__ */ new Set([
@@ -973,12 +976,20 @@ import { consola as consola10 } from "consola";
973
976
  import { colors as colors8 } from "consola/utils";
974
977
  import dayjs from "dayjs";
975
978
  import fg3 from "fast-glob";
979
+ import { Feed } from "feed";
976
980
  import fs9 from "fs-extra";
977
981
  import matter2 from "gray-matter";
978
982
  import MarkdownIt from "markdown-it";
979
983
  import ora3 from "ora";
980
984
  import { getBorderCharacters, table } from "table";
981
- import { createFeed, generateAtom1, generateJson1, generateRss2 } from "zfeed";
985
+
986
+ // shared/utils/i18n.ts
987
+ function tObject(data, lang) {
988
+ if (typeof data === "object") {
989
+ return data[lang] || Object.values(data)[0] || "";
990
+ }
991
+ return data;
992
+ }
982
993
 
983
994
  // node/utils/date.ts
984
995
  import fs8 from "fs-extra";
@@ -999,6 +1010,7 @@ async function build(options) {
999
1010
  const s = ora3("RSS Generating ...").start();
1000
1011
  const { config } = options;
1001
1012
  const siteConfig = config.siteConfig;
1013
+ const lang = siteConfig.lang || "en";
1002
1014
  if (!siteConfig.url || siteConfig.url === "/") {
1003
1015
  consola10.error("You must set `url` (like `https://example.com`) in `site.config.ts` to generate rss.");
1004
1016
  return;
@@ -1015,15 +1027,19 @@ async function build(options) {
1015
1027
  json: `${siteConfig.feed?.name || "feed"}.json`,
1016
1028
  rss: `${siteConfig.feed?.name || "feed"}.xml`
1017
1029
  };
1030
+ const feedLinks = {};
1031
+ Object.keys(feedNameMap).forEach((key) => {
1032
+ feedLinks[key] = `${siteUrl}${feedNameMap[key]}`;
1033
+ });
1034
+ const title = tObject(siteConfig.title, lang);
1035
+ const description = tObject(siteConfig.description, lang);
1018
1036
  const feedOptions = {
1019
- title: siteConfig.title || "Valaxy Blog",
1020
- description: siteConfig.description,
1037
+ title: title || "Valaxy Blog",
1038
+ description,
1021
1039
  id: siteUrl || "valaxy",
1022
1040
  link: siteUrl,
1023
1041
  copyright: `CC ${siteConfig.license?.type?.toUpperCase()} ${ccVersion} ${(/* @__PURE__ */ new Date()).getFullYear()} \xA9 ${siteConfig.author?.name}`,
1024
- feed: Object.fromEntries(
1025
- Object.entries(feedNameMap).map(([key, value]) => [key, `${siteUrl}${value}`])
1026
- )
1042
+ feedLinks
1027
1043
  };
1028
1044
  const DOMAIN = siteConfig.url.slice(0, -1);
1029
1045
  const files = await fg3(`${options.userRoot}/pages/posts/**/*.md`);
@@ -1082,23 +1098,23 @@ async function getPosts(params, options) {
1082
1098
  const link = DOMAIN + path17.replace(`${options.userRoot}/pages`, "").replace(/\.md$/, "");
1083
1099
  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>`;
1084
1100
  posts.push({
1085
- title: data.title,
1086
- updatedAt: new Date(data.date),
1087
- publishedAt: new Date(data.updated || data.date),
1101
+ ...data,
1102
+ title: tObject(data.title, lang),
1103
+ description: tObject(data.description, lang),
1104
+ date: new Date(data.date),
1105
+ published: new Date(data.updated || data.date),
1088
1106
  content: html + tip,
1089
- author,
1107
+ author: [author],
1090
1108
  id: data.id || link,
1091
1109
  link
1092
1110
  });
1093
1111
  }
1094
- posts.sort((a, b) => +(b.publishedAt || b.updatedAt) - +(a.publishedAt || a.updatedAt));
1112
+ posts.sort((a, b) => +(b.published || b.date) - +(a.published || a.date));
1095
1113
  return posts;
1096
1114
  }
1097
1115
  async function writeFeed(feedOptions, posts, options, feedNameMap) {
1098
- const feed = createFeed({
1099
- ...feedOptions,
1100
- items: posts
1101
- });
1116
+ const feed = new Feed(feedOptions);
1117
+ posts.forEach((item) => feed.addItem(item));
1102
1118
  await fs9.ensureDir(dirname3(`./dist/${feedNameMap.atom}`));
1103
1119
  const path17 = resolve5(options.userRoot, "./dist");
1104
1120
  const publicFolder = resolve5(options.userRoot, "public");
@@ -1115,11 +1131,11 @@ async function writeFeed(feedOptions, posts, options, feedNameMap) {
1115
1131
  let data = "";
1116
1132
  const distFeedPath = `${path17}/${feedNameMap[type]}`;
1117
1133
  if (type === "rss")
1118
- data = generateRss2(feed);
1134
+ data = feed.rss2();
1119
1135
  else if (type === "atom")
1120
- data = generateAtom1(feed);
1136
+ data = feed.atom1();
1121
1137
  else if (type === "json")
1122
- data = generateJson1(feed);
1138
+ data = feed.json1();
1123
1139
  await fs9.writeFile(distFeedPath, data, "utf-8");
1124
1140
  consola10.debug(`[${colors8.cyan(type)}] dist: ${colors8.dim(distFeedPath)}`);
1125
1141
  tableData.push([colors8.cyan(type), colors8.yellow("dist"), colors8.dim(distFeedPath)]);
@@ -1208,6 +1224,7 @@ import { consola as consola13 } from "consola";
1208
1224
  import { resolve as resolve12 } from "pathe";
1209
1225
  import Components from "unplugin-vue-components/vite";
1210
1226
  import Layouts from "vite-plugin-vue-layouts";
1227
+ import { groupIconVitePlugin } from "vitepress-plugin-group-icons";
1211
1228
 
1212
1229
  // node/plugins/extendConfig.ts
1213
1230
  import { dirname as dirname4, join as join3, resolve as resolve6 } from "path";
@@ -1459,11 +1476,6 @@ import { colors as colors9 } from "consola/utils";
1459
1476
 
1460
1477
  // ../../node_modules/.pnpm/nanoid@5.1.5/node_modules/nanoid/index.js
1461
1478
  import { webcrypto as crypto } from "crypto";
1462
-
1463
- // ../../node_modules/.pnpm/nanoid@5.1.5/node_modules/nanoid/url-alphabet/index.js
1464
- var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
1465
-
1466
- // ../../node_modules/.pnpm/nanoid@5.1.5/node_modules/nanoid/index.js
1467
1479
  var POOL_SIZE_MULTIPLIER = 128;
1468
1480
  var pool;
1469
1481
  var poolOffset;
@@ -1500,14 +1512,6 @@ function customRandom(alphabet, defaultSize, getRandom) {
1500
1512
  function customAlphabet(alphabet, size = 21) {
1501
1513
  return customRandom(alphabet, size, random);
1502
1514
  }
1503
- function nanoid(size = 21) {
1504
- fillPool(size |= 0);
1505
- let id = "";
1506
- for (let i = poolOffset - size; i < poolOffset; i++) {
1507
- id += urlAlphabet[pool[i] & 63];
1508
- }
1509
- return id;
1510
- }
1511
1515
 
1512
1516
  // node/plugins/markdown/plugins/highlight.ts
1513
1517
  import {
@@ -1515,7 +1519,7 @@ import {
1515
1519
  createHighlighter,
1516
1520
  isSpecialLang
1517
1521
  } from "shiki";
1518
- var nanoid2 = customAlphabet("abcdefghijklmnopqrstuvwxyz", 10);
1522
+ var nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz", 10);
1519
1523
  function attrsToLines(attrs) {
1520
1524
  attrs = attrs.replace(/^(?:\[.*?\])?.*?([\d,-]+).*/, "$1").trim();
1521
1525
  const result = [];
@@ -1602,7 +1606,7 @@ The language '${lang}' is not loaded, falling back to '${defaultLang}' for synta
1602
1606
  return s.replace(mustacheRE, (match) => {
1603
1607
  let marker = mustaches.get(match);
1604
1608
  if (!marker) {
1605
- marker = nanoid2();
1609
+ marker = nanoid();
1606
1610
  mustaches.set(match, marker);
1607
1611
  }
1608
1612
  return marker;
@@ -1947,6 +1951,7 @@ import { full as emojiPlugin } from "markdown-it-emoji";
1947
1951
  import footnotePlugin from "markdown-it-footnote";
1948
1952
  import imageFigures from "markdown-it-image-figures";
1949
1953
  import TaskLists from "markdown-it-task-lists";
1954
+ import { groupIconMdPlugin } from "vitepress-plugin-group-icons";
1950
1955
 
1951
1956
  // node/plugins/markdown/plugins/link.ts
1952
1957
  import { URL as URL2 } from "url";
@@ -2014,9 +2019,6 @@ import container from "markdown-it-container";
2014
2019
  function extractLang(info) {
2015
2020
  return info.trim().replace(/=(\d*)/, "").replace(/:(no-)?line-numbers(\{| |$|=\d*).*/, "").replace(/(-vue|\{| ).*$/, "").replace(/^vue-html$/, "template").replace(/^ansi$/, "");
2016
2021
  }
2017
- function getAdaptiveThemeMarker(options) {
2018
- return options.hasSingleTheme ? "" : " vp-adaptive-theme";
2019
- }
2020
2022
  function extractTitle(info, html = false) {
2021
2023
  if (html) {
2022
2024
  return info.replace(/<!--[\s\S]*?-->/g, "").match(/data-title="(.*?)"/)?.[1] || "";
@@ -2027,7 +2029,7 @@ function getCodeHeightLimitStyle(options, env) {
2027
2029
  const codeHeightLimit = env.frontmatter?.codeHeightLimit || options?.siteConfig?.codeHeightLimit;
2028
2030
  if (codeHeightLimit === void 0 || codeHeightLimit <= 0)
2029
2031
  return "";
2030
- return `style="max-height: ${codeHeightLimit}px;"`;
2032
+ return ` max-h-${codeHeightLimit}px`;
2031
2033
  }
2032
2034
  function preWrapperPlugin(md3, options) {
2033
2035
  const fence = md3.renderer.rules.fence;
@@ -2035,40 +2037,42 @@ function preWrapperPlugin(md3, options) {
2035
2037
  const [tokens, idx, _, env] = args;
2036
2038
  const token = tokens[idx];
2037
2039
  token.info = token.info.replace(/\[.*\]/, "");
2040
+ const active = / active( |$)/.test(token.info) ? " active" : "";
2041
+ token.info = token.info.replace(/ active$/, "").replace(/ active /, " ");
2038
2042
  const lang = extractLang(token.info);
2039
2043
  const rawCode = fence(...args);
2040
- return `
2041
- <div ${getCodeHeightLimitStyle(options, env)} class="language-${lang}${getAdaptiveThemeMarker(options)}${// eslint-disable-next-line regexp/no-unused-capturing-group
2042
- / active( |$)/.test(token.info) ? " active" : ""}">
2043
- <button title="Copy Code" class="copy"></button><span class="lang">${lang}</span>${rawCode}<button class="collapse"></button>
2044
- </div>`;
2044
+ const codeHeightLimitClass = getCodeHeightLimitStyle(options, env);
2045
+ return `<div class="language-${lang}${active}${codeHeightLimitClass}"><button title="${options.codeCopyButtonTitle}" class="copy"></button><span class="lang">${lang}</span>${rawCode}<button class="collapse"></button></div>`;
2045
2046
  };
2046
2047
  }
2047
2048
 
2048
2049
  // node/plugins/markdown/plugins/markdown-it/container.ts
2049
- function createContainer(classes, { icon, color, text: defaultTitle, langs } = {}) {
2050
+ function createContainer(classes, { icon, color, text: defaultTitle, langs } = {}, md3) {
2050
2051
  return [
2051
2052
  container,
2052
2053
  classes,
2053
2054
  {
2054
2055
  render(tokens, idx) {
2055
2056
  const token = tokens[idx];
2056
- const info = token.info.trim().slice(classes.length).trim();
2057
2057
  if (token.nesting === 1) {
2058
- if (classes === "details") {
2059
- return `<details class="${classes} custom-block">${`<summary>${info}</summary>`}
2060
- `;
2061
- }
2058
+ token.attrJoin("class", `${classes} custom-block`);
2059
+ const attrs = md3.renderer.renderAttrs(token);
2060
+ const info = token.info.trim().slice(classes.length).trim();
2062
2061
  let iconTag = "";
2063
2062
  if (icon)
2064
2063
  iconTag = `<i class="icon ${icon}" ${color ? `style="color: ${color}"` : ""}></i>`;
2065
- let title = `<span lang="en">${info || defaultTitle}</span>`;
2064
+ let titleWithLang = `<span lang="en">${info || defaultTitle}</span>`;
2066
2065
  if (langs) {
2067
2066
  Object.keys(langs).forEach((lang) => {
2068
- title += `<span lang="${lang}">${info || langs[lang]}</span>`;
2067
+ titleWithLang += `<span lang="${lang}">${info || langs[lang]}</span>`;
2069
2068
  });
2070
2069
  }
2071
- return `<div class="${classes} custom-block"><p class="custom-block-title">${iconTag}${title}</p>
2070
+ const title = md3.renderInline(titleWithLang, {});
2071
+ const titleClass = `custom-block-title${info ? "" : " custom-block-title-default"}`;
2072
+ if (classes === "details")
2073
+ return `<details ${attrs}><summary>${title}</summary>
2074
+ `;
2075
+ return `<div ${attrs}><p class="${titleClass}">${iconTag}${title}</p>
2072
2076
  `;
2073
2077
  } else {
2074
2078
  return classes === "details" ? "</details>\n" : "</div>\n";
@@ -2109,16 +2113,25 @@ var defaultBlocksOptions = {
2109
2113
  }
2110
2114
  }
2111
2115
  };
2112
- function containerPlugin(md3, options, containerOptions = {}) {
2113
- const blockKeys = Object.keys(Object.assign(defaultBlocksOptions, containerOptions));
2116
+ function containerPlugin(md3, containerOptions = {}) {
2117
+ const blockKeys = new Set(Object.keys(Object.assign(defaultBlocksOptions, containerOptions)));
2114
2118
  blockKeys.forEach((optionKey) => {
2115
2119
  const option = {
2116
2120
  ...defaultBlocksOptions[optionKey],
2117
2121
  ...containerOptions[optionKey] || {}
2118
2122
  };
2119
- md3.use(...createContainer(optionKey, option));
2123
+ md3.use(...createContainer(optionKey, option, md3));
2124
+ });
2125
+ md3.use(container, "v-pre", {
2126
+ render: (tokens, idx) => tokens[idx].nesting === 1 ? `<div v-pre>
2127
+ ` : `</div>
2128
+ `
2129
+ });
2130
+ md3.use(container, "raw", {
2131
+ render: (tokens, idx) => tokens[idx].nesting === 1 ? `<div class="vp-raw">
2132
+ ` : `</div>
2133
+ `
2120
2134
  });
2121
- md3.use(...createCodeGroup(options));
2122
2135
  const languages = ["zh-CN", "en"];
2123
2136
  languages.forEach((lang) => {
2124
2137
  md3.use(container, lang, {
@@ -2126,17 +2139,17 @@ function containerPlugin(md3, options, containerOptions = {}) {
2126
2139
  ` : "</div>\n"
2127
2140
  });
2128
2141
  });
2142
+ md3.use(...createCodeGroup(md3));
2129
2143
  }
2130
- function createCodeGroup(options) {
2144
+ function createCodeGroup(md3) {
2131
2145
  return [
2132
2146
  container,
2133
2147
  "code-group",
2134
2148
  {
2135
2149
  render(tokens, idx) {
2136
2150
  if (tokens[idx].nesting === 1) {
2137
- const name = nanoid(5);
2138
2151
  let tabs = "";
2139
- let checked = 'checked="checked"';
2152
+ let checked = "checked";
2140
2153
  for (let i = idx + 1; !(tokens[i].nesting === -1 && tokens[i].type === "container_code-group_close"); ++i) {
2141
2154
  const isHtml = tokens[i].type === "html_block";
2142
2155
  if (tokens[i].type === "fence" && tokens[i].tag === "code" || isHtml) {
@@ -2145,17 +2158,15 @@ function createCodeGroup(options) {
2145
2158
  isHtml
2146
2159
  );
2147
2160
  if (title) {
2148
- const id = nanoid(7);
2149
- tabs += `<input type="radio" name="group-${name}" id="tab-${id}" ${checked}><label for="tab-${id}">${title}</label>`;
2161
+ tabs += `<input type="radio" name="group-${idx}" id="tab-${i}" ${checked}/>`;
2162
+ tabs += `<label data-title="${md3.utils.escapeHtml(title)}" for="tab-${i}">${title}</label>`;
2150
2163
  if (checked && !isHtml)
2151
2164
  tokens[i].info += " active";
2152
2165
  checked = "";
2153
2166
  }
2154
2167
  }
2155
2168
  }
2156
- return `<div class="vp-code-group${getAdaptiveThemeMarker(
2157
- options
2158
- )}"><div class="tabs">${tabs}</div><div class="blocks">
2169
+ return `<div class="vp-code-group"><div class="tabs">${tabs}</div><div class="blocks">
2159
2170
  `;
2160
2171
  }
2161
2172
  return `</div></div>
@@ -2533,13 +2544,10 @@ var defaultCodeTheme = { light: "github-light", dark: "github-dark" };
2533
2544
  async function setupMarkdownPlugins(md3, options, base = "/") {
2534
2545
  const mdOptions = options?.config.markdown || {};
2535
2546
  const theme = mdOptions.theme ?? defaultCodeTheme;
2536
- const hasSingleTheme = typeof theme === "string" || "name" in theme;
2537
2547
  const siteConfig = options?.config.siteConfig || {};
2538
2548
  if (mdOptions.preConfig)
2539
2549
  mdOptions.preConfig(md3);
2540
2550
  md3.use(highlightLinePlugin).use(preWrapperPlugin, { theme, siteConfig }).use(snippetPlugin, options?.userRoot).use(containerPlugin, {
2541
- hasSingleTheme
2542
- }, {
2543
2551
  ...mdOptions.blocks,
2544
2552
  ...mdOptions?.container
2545
2553
  }).use(cssI18nContainer, {
@@ -2559,6 +2567,9 @@ async function setupMarkdownPlugins(md3, options, base = "/") {
2559
2567
  md3.use(emojiPlugin).use(footnotePlugin).use(footnoteTooltipPlugin);
2560
2568
  md3.use(anchorPlugin, {
2561
2569
  slugify,
2570
+ getTokensText: (tokens) => {
2571
+ return tokens.filter((t) => !["html_inline", "emoji"].includes(t.type)).map((t) => t.content).join("");
2572
+ },
2562
2573
  permalink: anchorPlugin.permalink.linkInsideHeader({
2563
2574
  symbol: "&ZeroWidthSpace;",
2564
2575
  renderAttrs: (slug, state) => {
@@ -2576,11 +2587,13 @@ async function setupMarkdownPlugins(md3, options, base = "/") {
2576
2587
  ...mdOptions.anchor
2577
2588
  });
2578
2589
  md3.use(headersPlugin, {
2590
+ level: [2, 3, 4, 5, 6],
2579
2591
  slugify,
2580
2592
  ...typeof mdOptions.headers === "boolean" ? void 0 : mdOptions.headers
2581
2593
  }).use(sfcPlugin, {
2582
2594
  ...mdOptions.sfc
2583
2595
  }).use(titlePlugin).use(tocPlugin, {
2596
+ slugify,
2584
2597
  ...mdOptions.toc
2585
2598
  });
2586
2599
  md3.use(math_plugin, mdOptions.katex);
@@ -2602,12 +2615,9 @@ async function setupMarkdownPlugins(md3, options, base = "/") {
2602
2615
  ...mdOptions.imageFigures
2603
2616
  });
2604
2617
  md3.use(TaskLists);
2605
- if (options?.config.groupIcons) {
2606
- const { groupIconMdPlugin } = await import("vitepress-plugin-group-icons");
2607
- md3.use(groupIconMdPlugin, {
2608
- titleBar: { includeSnippet: true }
2609
- });
2610
- }
2618
+ md3.use(groupIconMdPlugin, {
2619
+ titleBar: { includeSnippet: true }
2620
+ });
2611
2621
  if (mdOptions.config)
2612
2622
  mdOptions.config(md3);
2613
2623
  return md3;
@@ -4027,38 +4037,6 @@ async function ViteValaxyPlugins(valaxyApp, serverOptions = {}) {
4027
4037
  }),
4028
4038
  createFixPlugins(options)
4029
4039
  ];
4030
- if (valaxyConfig.groupIcons) {
4031
- const { groupIconVitePlugin } = await import("vitepress-plugin-group-icons");
4032
- plugins.push(
4033
- groupIconVitePlugin({
4034
- customIcon: {
4035
- nodejs: "vscode-icons:file-type-node",
4036
- playwright: "vscode-icons:file-type-playwright",
4037
- typedoc: "vscode-icons:file-type-typedoc",
4038
- eslint: "vscode-icons:file-type-eslint"
4039
- },
4040
- ...valaxyConfig.groupIcons
4041
- })
4042
- );
4043
- } else {
4044
- const virtualCssId = "virtual:group-icons.css";
4045
- const resolvedVirtualCssId = `\0${virtualCssId}`;
4046
- plugins.push({
4047
- name: "valaxy:virtual:group-icons.css",
4048
- resolveId(id) {
4049
- if (id === virtualCssId) {
4050
- return resolvedVirtualCssId;
4051
- }
4052
- return void 0;
4053
- },
4054
- async load(id) {
4055
- if (id === resolvedVirtualCssId) {
4056
- return "";
4057
- }
4058
- return void 0;
4059
- }
4060
- });
4061
- }
4062
4040
  if (valaxyConfig.visualizer) {
4063
4041
  try {
4064
4042
  const visualizer = (await import("rollup-plugin-visualizer")).visualizer;
@@ -4078,6 +4056,22 @@ async function ViteValaxyPlugins(valaxyApp, serverOptions = {}) {
4078
4056
  console.log();
4079
4057
  }
4080
4058
  }
4059
+ const customIcon = {
4060
+ nodejs: "vscode-icons:file-type-node",
4061
+ playwright: "vscode-icons:file-type-playwright",
4062
+ typedoc: "vscode-icons:file-type-typedoc",
4063
+ eslint: "vscode-icons:file-type-eslint"
4064
+ };
4065
+ plugins.push(
4066
+ groupIconVitePlugin({
4067
+ customIcon,
4068
+ ...valaxyConfig.groupIcons,
4069
+ defaultLabels: [
4070
+ ...valaxyConfig.groupIcons?.defaultLabels || [],
4071
+ ...Object.keys(valaxyConfig.groupIcons?.customIcon || {})
4072
+ ]
4073
+ })
4074
+ );
4081
4075
  return plugins;
4082
4076
  }
4083
4077
 
@@ -4383,24 +4377,11 @@ import path14 from "path";
4383
4377
  import process10 from "process";
4384
4378
  import { consola as consola17 } from "consola";
4385
4379
  import fs22 from "fs-extra";
4386
-
4387
- // node/cli/utils/fs.ts
4388
- import { access } from "fs/promises";
4389
- async function exists(path17) {
4390
- try {
4391
- await access(path17);
4392
- return true;
4393
- } catch (e) {
4394
- return false;
4395
- }
4396
- }
4397
-
4398
- // node/cli/clean.ts
4399
4380
  async function cleanDist() {
4400
4381
  const distDir = path14.join(process10.cwd(), "dist");
4401
4382
  const cacheDir = path14.join(process10.cwd(), ".valaxy");
4402
4383
  consola17.box("\u{1F9F9} Starting clean...");
4403
- if (await exists(distDir)) {
4384
+ if (await fs22.exists(distDir)) {
4404
4385
  consola17.info("dist directory exists, removing...");
4405
4386
  try {
4406
4387
  await fs22.rm(distDir, { recursive: true, force: true });
@@ -4412,7 +4393,7 @@ async function cleanDist() {
4412
4393
  } else {
4413
4394
  consola17.info("No dist directory found, nothing to clean.");
4414
4395
  }
4415
- if (await exists(cacheDir)) {
4396
+ if (await fs22.exists(cacheDir)) {
4416
4397
  consola17.info(".valaxy cache directory exists, removing...");
4417
4398
  try {
4418
4399
  await fs22.rm(cacheDir, { recursive: true, force: true });
@@ -4681,13 +4662,12 @@ function registerDevCommand(cli2) {
4681
4662
  }
4682
4663
 
4683
4664
  // node/cli/utils/post.ts
4684
- import { writeFile as writeFile2 } from "fs/promises";
4685
- import { join as join9, resolve as resolve14 } from "path";
4686
- import { ensureSuffix as ensureSuffix2 } from "@antfu/utils";
4665
+ import { dirname as dirname5, join as join9, resolve as resolve14 } from "path";
4687
4666
  import { consola as consola19 } from "consola";
4688
4667
  import { colors as colors17 } from "consola/utils";
4689
4668
  import dayjs2 from "dayjs";
4690
4669
  import { render } from "ejs";
4670
+ import fs24 from "fs-extra";
4691
4671
 
4692
4672
  // node/cli/utils/constants.ts
4693
4673
  import process14 from "process";
@@ -4702,42 +4682,38 @@ date: <%=date%>
4702
4682
  // node/cli/utils/scaffold.ts
4703
4683
  import { readFile as readFile2 } from "fs/promises";
4704
4684
  import path16 from "path";
4685
+ import fs23 from "fs-extra";
4705
4686
  async function getTemplate(layout) {
4706
4687
  const { clientRoot, themeRoot } = await resolveOptions({ userRoot });
4707
4688
  const roots = [userRoot, themeRoot, clientRoot];
4708
4689
  for (const root of roots) {
4709
4690
  const scaffoldPath = path16.resolve(root, "scaffolds", `${layout}.md`);
4710
- if (await exists(scaffoldPath))
4691
+ if (await fs23.exists(scaffoldPath))
4711
4692
  return readFile2(scaffoldPath, "utf-8");
4712
4693
  }
4713
4694
  return false;
4714
4695
  }
4715
4696
 
4716
4697
  // node/cli/utils/post.ts
4717
- async function create(data) {
4718
- const pagesPath = resolve14(userRoot, "pages");
4719
- const {
4720
- path: path17,
4721
- title
4722
- } = data;
4723
- const postPath = path17 || join9("posts", title);
4698
+ async function create(params) {
4699
+ const pagesPath = resolve14(userRoot, params.path || "pages");
4724
4700
  let counter = 0;
4725
4701
  while (true) {
4726
- let destinationPath = resolve14(pagesPath, postPath);
4727
- if (counter)
4728
- destinationPath = `${destinationPath}-${counter}`;
4729
- destinationPath = ensureSuffix2(".md", destinationPath);
4730
- if (!await exists(destinationPath)) {
4731
- const content = await genLayoutTemplate(data);
4702
+ const postFileName = `${params.title}${counter ? `-${counter}` : ""}`;
4703
+ const postFilePath = params.folder ? join9(postFileName, "index.md") : `${postFileName}.md`;
4704
+ const targetPath = resolve14(pagesPath, "posts", postFilePath);
4705
+ if (!await fs24.exists(targetPath)) {
4706
+ await fs24.ensureDir(dirname5(targetPath));
4707
+ const content = await genLayoutTemplate(params);
4732
4708
  try {
4733
- await writeFile2(destinationPath, content, "utf-8");
4734
- consola19.success(`[valaxy new]: successfully generated file ${colors17.magenta(destinationPath)}`);
4709
+ await fs24.writeFile(targetPath, content, "utf-8");
4710
+ consola19.success(`[valaxy new]: successfully generated file ${colors17.magenta(targetPath)}`);
4735
4711
  } catch (e) {
4736
4712
  console.log(e);
4737
- consola19.error(`[valaxy new]: failed to write file ${destinationPath}`);
4713
+ consola19.error(`[valaxy new]: failed to write file ${targetPath}`);
4738
4714
  consola19.warn(`You should run ${colors17.green("valaxy new")} in your valaxy project root directory.`);
4739
4715
  }
4740
- return destinationPath;
4716
+ return targetPath;
4741
4717
  }
4742
4718
  counter++;
4743
4719
  }
@@ -4763,6 +4739,11 @@ function registerNewCommand(cli2) {
4763
4739
  args.usage("$0 <title> -p [path] -l [layout]").positional("title", {
4764
4740
  describe: "The title of the new post",
4765
4741
  required: true
4742
+ }).option("folder", {
4743
+ alias: "f",
4744
+ type: "boolean",
4745
+ default: false,
4746
+ describe: "Generate a new post in a folder"
4766
4747
  }).option("path", {
4767
4748
  alias: "p",
4768
4749
  type: "string",
@@ -4778,12 +4759,13 @@ function registerNewCommand(cli2) {
4778
4759
  describe: "Generate post with the current date"
4779
4760
  }).strict().help();
4780
4761
  },
4781
- async ({ title, path: path17, date, layout }) => {
4762
+ async ({ title, folder, path: path17, date, layout }) => {
4782
4763
  await create({
4783
4764
  title,
4784
4765
  date,
4785
4766
  layout,
4786
- path: path17
4767
+ path: path17,
4768
+ folder
4787
4769
  });
4788
4770
  }
4789
4771
  );
@@ -103,8 +103,22 @@ interface PageFrontMatter extends Record<string, any> {
103
103
  /**
104
104
  * Title
105
105
  * @description 文章标题
106
- */
107
- title: string;
106
+ *
107
+ * ```md
108
+ * ---
109
+ * title: Post Title
110
+ * ---
111
+ * ```
112
+ *
113
+ * ```md
114
+ * ---
115
+ * title:
116
+ * en: Post Title
117
+ * zh-CN: 文章标题
118
+ * ---
119
+ * ```
120
+ */
121
+ title: string | Record<string, string>;
108
122
  date: string | number | Date;
109
123
  /**
110
124
  * Updated Time
@@ -338,7 +352,7 @@ interface PostFrontMatter extends PageFrontMatter {
338
352
  }
339
353
 
340
354
  interface FuseListItem extends Record<string, any> {
341
- title: string;
355
+ title: string | Record<string, string>;
342
356
  excerpt?: string;
343
357
  author: string;
344
358
  tags: string[];
@@ -3,7 +3,7 @@ import {
3
3
  registerDevCommand,
4
4
  run,
5
5
  startValaxyDev
6
- } from "../../chunk-SBITNE3A.js";
6
+ } from "../../chunk-CY4NJH2Z.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 { S as SiteConfig, D as DefaultTheme, V as ValaxyConfig, a as ValaxyAddon, P as PartialDeep, R as RuntimeConfig, b as RedirectItem, U as UserSiteConfig } from '../config-D40juN_m.js';
5
+ import { S as SiteConfig, D as DefaultTheme, V as ValaxyConfig, a as ValaxyAddon, P as PartialDeep, R as RuntimeConfig, b as RedirectItem, U as UserSiteConfig } from '../config-DHcACRUt.js';
6
6
  import Vue from '@vitejs/plugin-vue';
7
7
  import { Options as Options$3 } from 'beasties';
8
8
  import { Hookable } from 'hookable';
@@ -47,7 +47,7 @@ declare module 'vite' {
47
47
  declare function createValaxyNode(options: ResolvedValaxyOptions): ValaxyNode;
48
48
 
49
49
  interface Options {
50
- hasSingleTheme: boolean;
50
+ codeCopyButtonTitle: string;
51
51
  theme: ThemeOptions;
52
52
  siteConfig?: SiteConfig;
53
53
  }
@@ -310,6 +310,9 @@ interface ValaxyExtendConfig {
310
310
  * @see https://github.com/btd/rollup-plugin-visualizer
311
311
  */
312
312
  visualizer?: PluginVisualizerOptions;
313
+ /**
314
+ * @see https://github.com/yuyinws/vitepress-plugin-group-icons
315
+ */
313
316
  groupIcons?: Partial<Options$2>;
314
317
  /**
315
318
  * unocss presets
@@ -50,7 +50,7 @@ import {
50
50
  startValaxyDev,
51
51
  toAtFS,
52
52
  transformObject
53
- } from "../chunk-SBITNE3A.js";
53
+ } from "../chunk-CY4NJH2Z.js";
54
54
  export {
55
55
  ALL_ROUTE,
56
56
  EXCERPT_SEPARATOR,
@@ -1,5 +1,5 @@
1
- import { c as PostFrontMatter, d as PageFrontMatter } from '../config-D40juN_m.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-D40juN_m.js';
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';
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.23.6",
4
+ "version": "0.24.1",
5
5
  "description": "📄 Vite & Vue powered static blog generator.",
6
6
  "author": {
7
7
  "email": "me@yunyoujun.cn",
@@ -45,6 +45,7 @@
45
45
  "client",
46
46
  "dist",
47
47
  "index.d.ts",
48
+ "shared",
48
49
  "shims.d.ts",
49
50
  "types"
50
51
  ],
@@ -59,14 +60,14 @@
59
60
  "@intlify/unplugin-vue-i18n": "^6.0.8",
60
61
  "@shikijs/transformers": "^3.7.0",
61
62
  "@types/katex": "^0.16.7",
62
- "@unhead/addons": "^2.0.11",
63
- "@unhead/schema-org": "^2.0.11",
64
- "@unhead/vue": "^2.0.11",
63
+ "@unhead/addons": "^2.0.12",
64
+ "@unhead/schema-org": "^2.0.12",
65
+ "@unhead/vue": "^2.0.12",
65
66
  "@vitejs/plugin-vue": "^6.0.0",
66
67
  "@vue/devtools-api": "7.7.2",
67
- "@vueuse/core": "^13.4.0",
68
- "@vueuse/integrations": "^13.4.0",
69
- "beasties": "^0.3.4",
68
+ "@vueuse/core": "^13.5.0",
69
+ "@vueuse/integrations": "^13.5.0",
70
+ "beasties": "^0.3.5",
70
71
  "birpc": "^2.4.0",
71
72
  "consola": "^3.4.2",
72
73
  "cross-spawn": "^7.0.6",
@@ -77,6 +78,7 @@
77
78
  "ejs": "^3.1.10",
78
79
  "escape-html": "^1.0.3",
79
80
  "fast-glob": "^3.3.3",
81
+ "feed": "^5.1.0",
80
82
  "floating-vue": "^5.2.2",
81
83
  "fs-extra": "^11.3.0",
82
84
  "fuse.js": "^7.1.0",
@@ -99,7 +101,7 @@
99
101
  "markdown-it-table-of-contents": "^0.9.0",
100
102
  "markdown-it-task-lists": "^2.1.1",
101
103
  "medium-zoom": "^1.1.0",
102
- "mermaid": "^11.7.0",
104
+ "mermaid": "^11.8.1",
103
105
  "mlly": "^1.7.4",
104
106
  "nprogress": "^0.2.0",
105
107
  "open": "10.1.0",
@@ -113,26 +115,25 @@
113
115
  "shiki": "^3.7.0",
114
116
  "star-markdown-css": "^0.5.3",
115
117
  "table": "^6.9.0",
116
- "unhead": "^2.0.11",
117
- "unocss": "^66.3.2",
118
+ "unhead": "^2.0.12",
119
+ "unocss": "^66.3.3",
118
120
  "unplugin-vue-components": "28.0.0",
119
- "unplugin-vue-markdown": "^29.0.0",
120
- "unplugin-vue-router": "^0.13.0",
121
+ "unplugin-vue-markdown": "^29.1.0",
122
+ "unplugin-vue-router": "^0.14.0",
121
123
  "vanilla-lazyload": "^19.1.3",
122
- "vite": "^7.0.0",
124
+ "vite": "^7.0.4",
123
125
  "vite-dev-rpc": "^1.1.0",
124
126
  "vite-plugin-vue-devtools": "^7.7.7",
125
127
  "vite-plugin-vue-layouts": "^0.11.0",
126
128
  "vite-ssg": "^28.0.0",
127
129
  "vite-ssg-sitemap": "^0.9.0",
128
- "vitepress-plugin-group-icons": "^1.6.0",
130
+ "vitepress-plugin-group-icons": "^1.6.1",
129
131
  "vue": "^3.5.17",
130
- "vue-i18n": "^11.1.7",
132
+ "vue-i18n": "^11.1.9",
131
133
  "vue-router": "^4.5.1",
132
134
  "yargs": "^18.0.0",
133
- "zfeed": "^0.2.3",
134
- "@valaxyjs/devtools": "0.23.6",
135
- "@valaxyjs/utils": "0.23.6"
135
+ "@valaxyjs/devtools": "0.24.1",
136
+ "@valaxyjs/utils": "0.24.1"
136
137
  },
137
138
  "devDependencies": {
138
139
  "@mdit-vue/plugin-component": "^2.1.4",
@@ -160,7 +161,7 @@
160
161
  "rollup-plugin-visualizer": "^6.0.3"
161
162
  },
162
163
  "resolutions": {
163
- "vite": "catalog:"
164
+ "vite": "catalog:build"
164
165
  },
165
166
  "scripts": {
166
167
  "clean": "rimraf dist",
@@ -0,0 +1 @@
1
+ export * from './utils'
@@ -0,0 +1,17 @@
1
+ /**
2
+ * translate object
3
+ *
4
+ * { 'en': 'English', 'zh-CN': '中文' }
5
+ *
6
+ * ```ts
7
+ * tObject({ 'en': 'English', 'zh-CN': '中文' }, 'zh-CN') // 中文
8
+ * tObject({ 'en': 'English', 'zh-CN': '中文' }, 'en') // English
9
+ * tObject({ 'en': 'English', 'zh-CN': '中文' }, 'fr') // English
10
+ * ```
11
+ */
12
+ export function tObject(data: string | Record<string, string>, lang: string): string {
13
+ if (typeof data === 'object') {
14
+ return data[lang] || Object.values(data)[0] || ''
15
+ }
16
+ return data
17
+ }
@@ -0,0 +1 @@
1
+ export * from './i18n'
@@ -44,8 +44,22 @@ export interface PageFrontMatter extends Record<string, any> {
44
44
  /**
45
45
  * Title
46
46
  * @description 文章标题
47
- */
48
- title: string
47
+ *
48
+ * ```md
49
+ * ---
50
+ * title: Post Title
51
+ * ---
52
+ * ```
53
+ *
54
+ * ```md
55
+ * ---
56
+ * title:
57
+ * en: Post Title
58
+ * zh-CN: 文章标题
59
+ * ---
60
+ * ```
61
+ */
62
+ title: string | Record<string, string>
49
63
  date: string | number | Date
50
64
  /**
51
65
  * Updated Time
package/types/node.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export interface FuseListItem extends Record<string, any> {
2
- title: string
2
+ title: string | Record<string, string>
3
3
  excerpt?: string
4
4
  author: string
5
5
  tags: string[]