valaxy 0.23.5 → 0.24.0

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,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
 
package/client/main.ts CHANGED
@@ -20,6 +20,7 @@ import { setupValaxyDevTools } from './utils/dev'
20
20
  */
21
21
  import '#valaxy/styles'
22
22
  import 'uno.css'
23
+ import 'virtual:group-icons.css'
23
24
 
24
25
  const valaxyConfig = initValaxyConfig()
25
26
 
@@ -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,5 +1,6 @@
1
1
  import { onUnmounted } from 'vue'
2
2
 
3
+ // eslint-disable-next-line import/no-mutable-exports
3
4
  export let contentUpdatedCallbacks: (() => any)[] = []
4
5
 
5
6
  /**
@@ -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.5";
7
+ var version = "0.24.0";
8
8
 
9
9
  // node/modules/fuse.ts
10
10
  import path4 from "path";
@@ -514,7 +514,7 @@ function getGitTimestamp(file, type = "updated") {
514
514
 
515
515
  // node/constants/index.ts
516
516
  var EXCERPT_SEPARATOR = "<!-- more -->";
517
- var EXTERNAL_URL_RE = /^https?:/i;
517
+ var EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
518
518
  var PATHNAME_PROTOCOL_RE = /^pathname:\/\//;
519
519
  var ALL_ROUTE = "/:all(.*)*";
520
520
  var customElements = /* @__PURE__ */ new Set([
@@ -547,15 +547,7 @@ var customElements = /* @__PURE__ */ new Set([
547
547
  "munderover",
548
548
  "semantics"
549
549
  ]);
550
- var defaultViteConfig = {
551
- css: {
552
- preprocessorOptions: {
553
- scss: {
554
- api: "modern-compiler"
555
- }
556
- }
557
- }
558
- };
550
+ var defaultViteConfig = {};
559
551
 
560
552
  // node/utils/helper.ts
561
553
  function isExternal(str) {
@@ -981,12 +973,20 @@ import { consola as consola10 } from "consola";
981
973
  import { colors as colors8 } from "consola/utils";
982
974
  import dayjs from "dayjs";
983
975
  import fg3 from "fast-glob";
976
+ import { Feed } from "feed";
984
977
  import fs9 from "fs-extra";
985
978
  import matter2 from "gray-matter";
986
979
  import MarkdownIt from "markdown-it";
987
980
  import ora3 from "ora";
988
981
  import { getBorderCharacters, table } from "table";
989
- import { createFeed, generateAtom1, generateJson1, generateRss2 } from "zfeed";
982
+
983
+ // shared/utils/i18n.ts
984
+ function tObject(data, lang) {
985
+ if (typeof data === "object") {
986
+ return data[lang] || Object.values(data)[0] || "";
987
+ }
988
+ return data;
989
+ }
990
990
 
991
991
  // node/utils/date.ts
992
992
  import fs8 from "fs-extra";
@@ -1007,6 +1007,7 @@ async function build(options) {
1007
1007
  const s = ora3("RSS Generating ...").start();
1008
1008
  const { config } = options;
1009
1009
  const siteConfig = config.siteConfig;
1010
+ const lang = siteConfig.lang || "en";
1010
1011
  if (!siteConfig.url || siteConfig.url === "/") {
1011
1012
  consola10.error("You must set `url` (like `https://example.com`) in `site.config.ts` to generate rss.");
1012
1013
  return;
@@ -1023,15 +1024,19 @@ async function build(options) {
1023
1024
  json: `${siteConfig.feed?.name || "feed"}.json`,
1024
1025
  rss: `${siteConfig.feed?.name || "feed"}.xml`
1025
1026
  };
1027
+ const feedLinks = {};
1028
+ Object.keys(feedNameMap).forEach((key) => {
1029
+ feedLinks[key] = `${siteUrl}${feedNameMap[key]}`;
1030
+ });
1031
+ const title = tObject(siteConfig.title, lang);
1032
+ const description = tObject(siteConfig.description, lang);
1026
1033
  const feedOptions = {
1027
- title: siteConfig.title || "Valaxy Blog",
1028
- description: siteConfig.description,
1034
+ title: title || "Valaxy Blog",
1035
+ description,
1029
1036
  id: siteUrl || "valaxy",
1030
1037
  link: siteUrl,
1031
1038
  copyright: `CC ${siteConfig.license?.type?.toUpperCase()} ${ccVersion} ${(/* @__PURE__ */ new Date()).getFullYear()} \xA9 ${siteConfig.author?.name}`,
1032
- feed: Object.fromEntries(
1033
- Object.entries(feedNameMap).map(([key, value]) => [key, `${siteUrl}${value}`])
1034
- )
1039
+ feedLinks
1035
1040
  };
1036
1041
  const DOMAIN = siteConfig.url.slice(0, -1);
1037
1042
  const files = await fg3(`${options.userRoot}/pages/posts/**/*.md`);
@@ -1090,23 +1095,23 @@ async function getPosts(params, options) {
1090
1095
  const link = DOMAIN + path17.replace(`${options.userRoot}/pages`, "").replace(/\.md$/, "");
1091
1096
  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>`;
1092
1097
  posts.push({
1093
- title: data.title,
1094
- updatedAt: new Date(data.date),
1095
- publishedAt: new Date(data.updated || data.date),
1098
+ ...data,
1099
+ title: tObject(data.title, lang),
1100
+ description: tObject(data.description, lang),
1101
+ date: new Date(data.date),
1102
+ published: new Date(data.updated || data.date),
1096
1103
  content: html + tip,
1097
- author,
1104
+ author: [author],
1098
1105
  id: data.id || link,
1099
1106
  link
1100
1107
  });
1101
1108
  }
1102
- posts.sort((a, b) => +(b.publishedAt || b.updatedAt) - +(a.publishedAt || a.updatedAt));
1109
+ posts.sort((a, b) => +(b.published || b.date) - +(a.published || a.date));
1103
1110
  return posts;
1104
1111
  }
1105
1112
  async function writeFeed(feedOptions, posts, options, feedNameMap) {
1106
- const feed = createFeed({
1107
- ...feedOptions,
1108
- items: posts
1109
- });
1113
+ const feed = new Feed(feedOptions);
1114
+ posts.forEach((item) => feed.addItem(item));
1110
1115
  await fs9.ensureDir(dirname3(`./dist/${feedNameMap.atom}`));
1111
1116
  const path17 = resolve5(options.userRoot, "./dist");
1112
1117
  const publicFolder = resolve5(options.userRoot, "public");
@@ -1123,11 +1128,11 @@ async function writeFeed(feedOptions, posts, options, feedNameMap) {
1123
1128
  let data = "";
1124
1129
  const distFeedPath = `${path17}/${feedNameMap[type]}`;
1125
1130
  if (type === "rss")
1126
- data = generateRss2(feed);
1131
+ data = feed.rss2();
1127
1132
  else if (type === "atom")
1128
- data = generateAtom1(feed);
1133
+ data = feed.atom1();
1129
1134
  else if (type === "json")
1130
- data = generateJson1(feed);
1135
+ data = feed.json1();
1131
1136
  await fs9.writeFile(distFeedPath, data, "utf-8");
1132
1137
  consola10.debug(`[${colors8.cyan(type)}] dist: ${colors8.dim(distFeedPath)}`);
1133
1138
  tableData.push([colors8.cyan(type), colors8.yellow("dist"), colors8.dim(distFeedPath)]);
@@ -1467,11 +1472,6 @@ import { colors as colors9 } from "consola/utils";
1467
1472
 
1468
1473
  // ../../node_modules/.pnpm/nanoid@5.1.5/node_modules/nanoid/index.js
1469
1474
  import { webcrypto as crypto } from "crypto";
1470
-
1471
- // ../../node_modules/.pnpm/nanoid@5.1.5/node_modules/nanoid/url-alphabet/index.js
1472
- var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
1473
-
1474
- // ../../node_modules/.pnpm/nanoid@5.1.5/node_modules/nanoid/index.js
1475
1475
  var POOL_SIZE_MULTIPLIER = 128;
1476
1476
  var pool;
1477
1477
  var poolOffset;
@@ -1508,14 +1508,6 @@ function customRandom(alphabet, defaultSize, getRandom) {
1508
1508
  function customAlphabet(alphabet, size = 21) {
1509
1509
  return customRandom(alphabet, size, random);
1510
1510
  }
1511
- function nanoid(size = 21) {
1512
- fillPool(size |= 0);
1513
- let id = "";
1514
- for (let i = poolOffset - size; i < poolOffset; i++) {
1515
- id += urlAlphabet[pool[i] & 63];
1516
- }
1517
- return id;
1518
- }
1519
1511
 
1520
1512
  // node/plugins/markdown/plugins/highlight.ts
1521
1513
  import {
@@ -1523,7 +1515,7 @@ import {
1523
1515
  createHighlighter,
1524
1516
  isSpecialLang
1525
1517
  } from "shiki";
1526
- var nanoid2 = customAlphabet("abcdefghijklmnopqrstuvwxyz", 10);
1518
+ var nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz", 10);
1527
1519
  function attrsToLines(attrs) {
1528
1520
  attrs = attrs.replace(/^(?:\[.*?\])?.*?([\d,-]+).*/, "$1").trim();
1529
1521
  const result = [];
@@ -1610,7 +1602,7 @@ The language '${lang}' is not loaded, falling back to '${defaultLang}' for synta
1610
1602
  return s.replace(mustacheRE, (match) => {
1611
1603
  let marker = mustaches.get(match);
1612
1604
  if (!marker) {
1613
- marker = nanoid2();
1605
+ marker = nanoid();
1614
1606
  mustaches.set(match, marker);
1615
1607
  }
1616
1608
  return marker;
@@ -2022,9 +2014,6 @@ import container from "markdown-it-container";
2022
2014
  function extractLang(info) {
2023
2015
  return info.trim().replace(/=(\d*)/, "").replace(/:(no-)?line-numbers(\{| |$|=\d*).*/, "").replace(/(-vue|\{| ).*$/, "").replace(/^vue-html$/, "template").replace(/^ansi$/, "");
2024
2016
  }
2025
- function getAdaptiveThemeMarker(options) {
2026
- return options.hasSingleTheme ? "" : " vp-adaptive-theme";
2027
- }
2028
2017
  function extractTitle(info, html = false) {
2029
2018
  if (html) {
2030
2019
  return info.replace(/<!--[\s\S]*?-->/g, "").match(/data-title="(.*?)"/)?.[1] || "";
@@ -2035,7 +2024,7 @@ function getCodeHeightLimitStyle(options, env) {
2035
2024
  const codeHeightLimit = env.frontmatter?.codeHeightLimit || options?.siteConfig?.codeHeightLimit;
2036
2025
  if (codeHeightLimit === void 0 || codeHeightLimit <= 0)
2037
2026
  return "";
2038
- return `style="max-height: ${codeHeightLimit}px;"`;
2027
+ return ` max-h-${codeHeightLimit}px`;
2039
2028
  }
2040
2029
  function preWrapperPlugin(md3, options) {
2041
2030
  const fence = md3.renderer.rules.fence;
@@ -2043,40 +2032,42 @@ function preWrapperPlugin(md3, options) {
2043
2032
  const [tokens, idx, _, env] = args;
2044
2033
  const token = tokens[idx];
2045
2034
  token.info = token.info.replace(/\[.*\]/, "");
2035
+ const active = / active( |$)/.test(token.info) ? " active" : "";
2036
+ token.info = token.info.replace(/ active$/, "").replace(/ active /, " ");
2046
2037
  const lang = extractLang(token.info);
2047
2038
  const rawCode = fence(...args);
2048
- return `
2049
- <div ${getCodeHeightLimitStyle(options, env)} class="language-${lang}${getAdaptiveThemeMarker(options)}${// eslint-disable-next-line regexp/no-unused-capturing-group
2050
- / active( |$)/.test(token.info) ? " active" : ""}">
2051
- <button title="Copy Code" class="copy"></button><span class="lang">${lang}</span>${rawCode}<button class="collapse"></button>
2052
- </div>`;
2039
+ const codeHeightLimitClass = getCodeHeightLimitStyle(options, env);
2040
+ 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>`;
2053
2041
  };
2054
2042
  }
2055
2043
 
2056
2044
  // node/plugins/markdown/plugins/markdown-it/container.ts
2057
- function createContainer(classes, { icon, color, text: defaultTitle, langs } = {}) {
2045
+ function createContainer(classes, { icon, color, text: defaultTitle, langs } = {}, md3) {
2058
2046
  return [
2059
2047
  container,
2060
2048
  classes,
2061
2049
  {
2062
2050
  render(tokens, idx) {
2063
2051
  const token = tokens[idx];
2064
- const info = token.info.trim().slice(classes.length).trim();
2065
2052
  if (token.nesting === 1) {
2066
- if (classes === "details") {
2067
- return `<details class="${classes} custom-block">${`<summary>${info}</summary>`}
2068
- `;
2069
- }
2053
+ token.attrJoin("class", `${classes} custom-block`);
2054
+ const attrs = md3.renderer.renderAttrs(token);
2055
+ const info = token.info.trim().slice(classes.length).trim();
2070
2056
  let iconTag = "";
2071
2057
  if (icon)
2072
2058
  iconTag = `<i class="icon ${icon}" ${color ? `style="color: ${color}"` : ""}></i>`;
2073
- let title = `<span lang="en">${info || defaultTitle}</span>`;
2059
+ let titleWithLang = `<span lang="en">${info || defaultTitle}</span>`;
2074
2060
  if (langs) {
2075
2061
  Object.keys(langs).forEach((lang) => {
2076
- title += `<span lang="${lang}">${info || langs[lang]}</span>`;
2062
+ titleWithLang += `<span lang="${lang}">${info || langs[lang]}</span>`;
2077
2063
  });
2078
2064
  }
2079
- return `<div class="${classes} custom-block"><p class="custom-block-title">${iconTag}${title}</p>
2065
+ const title = md3.renderInline(titleWithLang, {});
2066
+ const titleClass = `custom-block-title${info ? "" : " custom-block-title-default"}`;
2067
+ if (classes === "details")
2068
+ return `<details ${attrs}><summary>${title}</summary>
2069
+ `;
2070
+ return `<div ${attrs}><p class="${titleClass}">${iconTag}${title}</p>
2080
2071
  `;
2081
2072
  } else {
2082
2073
  return classes === "details" ? "</details>\n" : "</div>\n";
@@ -2117,15 +2108,25 @@ var defaultBlocksOptions = {
2117
2108
  }
2118
2109
  }
2119
2110
  };
2120
- function containerPlugin(md3, options, containerOptions = {}) {
2121
- Object.keys(defaultBlocksOptions).forEach((optionKey) => {
2111
+ function containerPlugin(md3, containerOptions = {}) {
2112
+ const blockKeys = new Set(Object.keys(Object.assign(defaultBlocksOptions, containerOptions)));
2113
+ blockKeys.forEach((optionKey) => {
2122
2114
  const option = {
2123
2115
  ...defaultBlocksOptions[optionKey],
2124
2116
  ...containerOptions[optionKey] || {}
2125
2117
  };
2126
- md3.use(...createContainer(optionKey, option));
2118
+ md3.use(...createContainer(optionKey, option, md3));
2119
+ });
2120
+ md3.use(container, "v-pre", {
2121
+ render: (tokens, idx) => tokens[idx].nesting === 1 ? `<div v-pre>
2122
+ ` : `</div>
2123
+ `
2124
+ });
2125
+ md3.use(container, "raw", {
2126
+ render: (tokens, idx) => tokens[idx].nesting === 1 ? `<div class="vp-raw">
2127
+ ` : `</div>
2128
+ `
2127
2129
  });
2128
- md3.use(...createCodeGroup(options));
2129
2130
  const languages = ["zh-CN", "en"];
2130
2131
  languages.forEach((lang) => {
2131
2132
  md3.use(container, lang, {
@@ -2133,17 +2134,17 @@ function containerPlugin(md3, options, containerOptions = {}) {
2133
2134
  ` : "</div>\n"
2134
2135
  });
2135
2136
  });
2137
+ md3.use(...createCodeGroup(md3));
2136
2138
  }
2137
- function createCodeGroup(options) {
2139
+ function createCodeGroup(md3) {
2138
2140
  return [
2139
2141
  container,
2140
2142
  "code-group",
2141
2143
  {
2142
2144
  render(tokens, idx) {
2143
2145
  if (tokens[idx].nesting === 1) {
2144
- const name = nanoid(5);
2145
2146
  let tabs = "";
2146
- let checked = 'checked="checked"';
2147
+ let checked = "checked";
2147
2148
  for (let i = idx + 1; !(tokens[i].nesting === -1 && tokens[i].type === "container_code-group_close"); ++i) {
2148
2149
  const isHtml = tokens[i].type === "html_block";
2149
2150
  if (tokens[i].type === "fence" && tokens[i].tag === "code" || isHtml) {
@@ -2152,17 +2153,15 @@ function createCodeGroup(options) {
2152
2153
  isHtml
2153
2154
  );
2154
2155
  if (title) {
2155
- const id = nanoid(7);
2156
- tabs += `<input type="radio" name="group-${name}" id="tab-${id}" ${checked}><label for="tab-${id}">${title}</label>`;
2156
+ tabs += `<input type="radio" name="group-${idx}" id="tab-${i}" ${checked}/>`;
2157
+ tabs += `<label data-title="${md3.utils.escapeHtml(title)}" for="tab-${i}">${title}</label>`;
2157
2158
  if (checked && !isHtml)
2158
2159
  tokens[i].info += " active";
2159
2160
  checked = "";
2160
2161
  }
2161
2162
  }
2162
2163
  }
2163
- return `<div class="vp-code-group${getAdaptiveThemeMarker(
2164
- options
2165
- )}"><div class="tabs">${tabs}</div><div class="blocks">
2164
+ return `<div class="vp-code-group"><div class="tabs">${tabs}</div><div class="blocks">
2166
2165
  `;
2167
2166
  }
2168
2167
  return `</div></div>
@@ -2540,13 +2539,10 @@ var defaultCodeTheme = { light: "github-light", dark: "github-dark" };
2540
2539
  async function setupMarkdownPlugins(md3, options, base = "/") {
2541
2540
  const mdOptions = options?.config.markdown || {};
2542
2541
  const theme = mdOptions.theme ?? defaultCodeTheme;
2543
- const hasSingleTheme = typeof theme === "string" || "name" in theme;
2544
2542
  const siteConfig = options?.config.siteConfig || {};
2545
2543
  if (mdOptions.preConfig)
2546
2544
  mdOptions.preConfig(md3);
2547
2545
  md3.use(highlightLinePlugin).use(preWrapperPlugin, { theme, siteConfig }).use(snippetPlugin, options?.userRoot).use(containerPlugin, {
2548
- hasSingleTheme
2549
- }, {
2550
2546
  ...mdOptions.blocks,
2551
2547
  ...mdOptions?.container
2552
2548
  }).use(cssI18nContainer, {
@@ -2566,6 +2562,9 @@ async function setupMarkdownPlugins(md3, options, base = "/") {
2566
2562
  md3.use(emojiPlugin).use(footnotePlugin).use(footnoteTooltipPlugin);
2567
2563
  md3.use(anchorPlugin, {
2568
2564
  slugify,
2565
+ getTokensText: (tokens) => {
2566
+ return tokens.filter((t) => !["html_inline", "emoji"].includes(t.type)).map((t) => t.content).join("");
2567
+ },
2569
2568
  permalink: anchorPlugin.permalink.linkInsideHeader({
2570
2569
  symbol: "&ZeroWidthSpace;",
2571
2570
  renderAttrs: (slug, state) => {
@@ -2583,11 +2582,13 @@ async function setupMarkdownPlugins(md3, options, base = "/") {
2583
2582
  ...mdOptions.anchor
2584
2583
  });
2585
2584
  md3.use(headersPlugin, {
2585
+ level: [2, 3, 4, 5, 6],
2586
2586
  slugify,
2587
2587
  ...typeof mdOptions.headers === "boolean" ? void 0 : mdOptions.headers
2588
2588
  }).use(sfcPlugin, {
2589
2589
  ...mdOptions.sfc
2590
2590
  }).use(titlePlugin).use(tocPlugin, {
2591
+ slugify,
2591
2592
  ...mdOptions.toc
2592
2593
  });
2593
2594
  md3.use(math_plugin, mdOptions.katex);
@@ -2609,12 +2610,10 @@ async function setupMarkdownPlugins(md3, options, base = "/") {
2609
2610
  ...mdOptions.imageFigures
2610
2611
  });
2611
2612
  md3.use(TaskLists);
2612
- if (options?.config.groupIcons) {
2613
- const { groupIconMdPlugin } = await import("vitepress-plugin-group-icons");
2614
- md3.use(groupIconMdPlugin, {
2615
- titleBar: { includeSnippet: true }
2616
- });
2617
- }
2613
+ const { groupIconMdPlugin } = await import("vitepress-plugin-group-icons");
2614
+ md3.use(groupIconMdPlugin, {
2615
+ titleBar: { includeSnippet: true }
2616
+ });
2618
2617
  if (mdOptions.config)
2619
2618
  mdOptions.config(md3);
2620
2619
  return md3;
@@ -4034,38 +4033,18 @@ async function ViteValaxyPlugins(valaxyApp, serverOptions = {}) {
4034
4033
  }),
4035
4034
  createFixPlugins(options)
4036
4035
  ];
4037
- if (valaxyConfig.groupIcons) {
4038
- const { groupIconVitePlugin } = await import("vitepress-plugin-group-icons");
4039
- plugins.push(
4040
- groupIconVitePlugin({
4041
- customIcon: {
4042
- nodejs: "vscode-icons:file-type-node",
4043
- playwright: "vscode-icons:file-type-playwright",
4044
- typedoc: "vscode-icons:file-type-typedoc",
4045
- eslint: "vscode-icons:file-type-eslint"
4046
- },
4047
- ...valaxyConfig.groupIcons
4048
- })
4049
- );
4050
- } else {
4051
- const virtualCssId = "virtual:group-icons.css";
4052
- const resolvedVirtualCssId = `\0${virtualCssId}`;
4053
- plugins.push({
4054
- name: "valaxy:virtual:group-icons.css",
4055
- resolveId(id) {
4056
- if (id === virtualCssId) {
4057
- return resolvedVirtualCssId;
4058
- }
4059
- return void 0;
4036
+ const { groupIconVitePlugin } = await import("vitepress-plugin-group-icons");
4037
+ plugins.push(
4038
+ groupIconVitePlugin({
4039
+ customIcon: {
4040
+ nodejs: "vscode-icons:file-type-node",
4041
+ playwright: "vscode-icons:file-type-playwright",
4042
+ typedoc: "vscode-icons:file-type-typedoc",
4043
+ eslint: "vscode-icons:file-type-eslint"
4060
4044
  },
4061
- async load(id) {
4062
- if (id === resolvedVirtualCssId) {
4063
- return "";
4064
- }
4065
- return void 0;
4066
- }
4067
- });
4068
- }
4045
+ ...valaxyConfig.groupIcons
4046
+ })
4047
+ );
4069
4048
  if (valaxyConfig.visualizer) {
4070
4049
  try {
4071
4050
  const visualizer = (await import("rollup-plugin-visualizer")).visualizer;
@@ -4390,24 +4369,11 @@ import path14 from "path";
4390
4369
  import process10 from "process";
4391
4370
  import { consola as consola17 } from "consola";
4392
4371
  import fs22 from "fs-extra";
4393
-
4394
- // node/cli/utils/fs.ts
4395
- import { access } from "fs/promises";
4396
- async function exists(path17) {
4397
- try {
4398
- await access(path17);
4399
- return true;
4400
- } catch (e) {
4401
- return false;
4402
- }
4403
- }
4404
-
4405
- // node/cli/clean.ts
4406
4372
  async function cleanDist() {
4407
4373
  const distDir = path14.join(process10.cwd(), "dist");
4408
4374
  const cacheDir = path14.join(process10.cwd(), ".valaxy");
4409
4375
  consola17.box("\u{1F9F9} Starting clean...");
4410
- if (await exists(distDir)) {
4376
+ if (await fs22.exists(distDir)) {
4411
4377
  consola17.info("dist directory exists, removing...");
4412
4378
  try {
4413
4379
  await fs22.rm(distDir, { recursive: true, force: true });
@@ -4419,7 +4385,7 @@ async function cleanDist() {
4419
4385
  } else {
4420
4386
  consola17.info("No dist directory found, nothing to clean.");
4421
4387
  }
4422
- if (await exists(cacheDir)) {
4388
+ if (await fs22.exists(cacheDir)) {
4423
4389
  consola17.info(".valaxy cache directory exists, removing...");
4424
4390
  try {
4425
4391
  await fs22.rm(cacheDir, { recursive: true, force: true });
@@ -4688,13 +4654,12 @@ function registerDevCommand(cli2) {
4688
4654
  }
4689
4655
 
4690
4656
  // node/cli/utils/post.ts
4691
- import { writeFile as writeFile2 } from "fs/promises";
4692
- import { join as join9, resolve as resolve14 } from "path";
4693
- import { ensureSuffix as ensureSuffix2 } from "@antfu/utils";
4657
+ import { dirname as dirname5, join as join9, resolve as resolve14 } from "path";
4694
4658
  import { consola as consola19 } from "consola";
4695
4659
  import { colors as colors17 } from "consola/utils";
4696
4660
  import dayjs2 from "dayjs";
4697
4661
  import { render } from "ejs";
4662
+ import fs24 from "fs-extra";
4698
4663
 
4699
4664
  // node/cli/utils/constants.ts
4700
4665
  import process14 from "process";
@@ -4709,42 +4674,38 @@ date: <%=date%>
4709
4674
  // node/cli/utils/scaffold.ts
4710
4675
  import { readFile as readFile2 } from "fs/promises";
4711
4676
  import path16 from "path";
4677
+ import fs23 from "fs-extra";
4712
4678
  async function getTemplate(layout) {
4713
4679
  const { clientRoot, themeRoot } = await resolveOptions({ userRoot });
4714
4680
  const roots = [userRoot, themeRoot, clientRoot];
4715
4681
  for (const root of roots) {
4716
4682
  const scaffoldPath = path16.resolve(root, "scaffolds", `${layout}.md`);
4717
- if (await exists(scaffoldPath))
4683
+ if (await fs23.exists(scaffoldPath))
4718
4684
  return readFile2(scaffoldPath, "utf-8");
4719
4685
  }
4720
4686
  return false;
4721
4687
  }
4722
4688
 
4723
4689
  // node/cli/utils/post.ts
4724
- async function create(data) {
4725
- const pagesPath = resolve14(userRoot, "pages");
4726
- const {
4727
- path: path17,
4728
- title
4729
- } = data;
4730
- const postPath = path17 || join9("posts", title);
4690
+ async function create(params) {
4691
+ const pagesPath = resolve14(userRoot, params.path || "pages");
4731
4692
  let counter = 0;
4732
4693
  while (true) {
4733
- let destinationPath = resolve14(pagesPath, postPath);
4734
- if (counter)
4735
- destinationPath = `${destinationPath}-${counter}`;
4736
- destinationPath = ensureSuffix2(".md", destinationPath);
4737
- if (!await exists(destinationPath)) {
4738
- const content = await genLayoutTemplate(data);
4694
+ const postFileName = `${params.title}${counter ? `-${counter}` : ""}`;
4695
+ const postFilePath = params.folder ? join9(postFileName, "index.md") : `${postFileName}.md`;
4696
+ const targetPath = resolve14(pagesPath, "posts", postFilePath);
4697
+ if (!await fs24.exists(targetPath)) {
4698
+ await fs24.ensureDir(dirname5(targetPath));
4699
+ const content = await genLayoutTemplate(params);
4739
4700
  try {
4740
- await writeFile2(destinationPath, content, "utf-8");
4741
- consola19.success(`[valaxy new]: successfully generated file ${colors17.magenta(destinationPath)}`);
4701
+ await fs24.writeFile(targetPath, content, "utf-8");
4702
+ consola19.success(`[valaxy new]: successfully generated file ${colors17.magenta(targetPath)}`);
4742
4703
  } catch (e) {
4743
4704
  console.log(e);
4744
- consola19.error(`[valaxy new]: failed to write file ${destinationPath}`);
4705
+ consola19.error(`[valaxy new]: failed to write file ${targetPath}`);
4745
4706
  consola19.warn(`You should run ${colors17.green("valaxy new")} in your valaxy project root directory.`);
4746
4707
  }
4747
- return destinationPath;
4708
+ return targetPath;
4748
4709
  }
4749
4710
  counter++;
4750
4711
  }
@@ -4770,6 +4731,11 @@ function registerNewCommand(cli2) {
4770
4731
  args.usage("$0 <title> -p [path] -l [layout]").positional("title", {
4771
4732
  describe: "The title of the new post",
4772
4733
  required: true
4734
+ }).option("folder", {
4735
+ alias: "f",
4736
+ type: "boolean",
4737
+ default: false,
4738
+ describe: "Generate a new post in a folder"
4773
4739
  }).option("path", {
4774
4740
  alias: "p",
4775
4741
  type: "string",
@@ -4785,12 +4751,13 @@ function registerNewCommand(cli2) {
4785
4751
  describe: "Generate post with the current date"
4786
4752
  }).strict().help();
4787
4753
  },
4788
- async ({ title, path: path17, date, layout }) => {
4754
+ async ({ title, folder, path: path17, date, layout }) => {
4789
4755
  await create({
4790
4756
  title,
4791
4757
  date,
4792
4758
  layout,
4793
- path: path17
4759
+ path: path17,
4760
+ folder
4794
4761
  });
4795
4762
  }
4796
4763
  );
@@ -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-ZE4AYGG6.js";
6
+ } from "../../chunk-UBPYRQAO.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
  }
@@ -168,7 +168,7 @@ interface MarkdownOptions extends Options$1 {
168
168
  /**
169
169
  * Custom block configurations based on `markdown-it-container`
170
170
  */
171
- blocks?: Blocks;
171
+ blocks?: Record<string, BlockItem> | Blocks;
172
172
  /**
173
173
  * @see [markdown-it-image-figures](https://www.npmjs.com/package/markdown-it-image-figures)
174
174
  */
@@ -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
@@ -612,6 +615,7 @@ declare const ALL_ROUTE = "/:all(.*)*";
612
615
  declare const customElements: Set<string>;
613
616
  /**
614
617
  * @see https://vitejs.dev/config/shared-options.html#css-preprocessoroptions for sass@2
618
+ * vite@7 default use sass modern api
615
619
  */
616
620
  declare const defaultViteConfig: UserConfig;
617
621
 
@@ -50,7 +50,7 @@ import {
50
50
  startValaxyDev,
51
51
  toAtFS,
52
52
  transformObject
53
- } from "../chunk-ZE4AYGG6.js";
53
+ } from "../chunk-UBPYRQAO.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.5",
4
+ "version": "0.24.0",
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
  ],
@@ -57,16 +58,16 @@
57
58
  "@clack/prompts": "^0.11.0",
58
59
  "@iconify-json/ri": "^1.2.5",
59
60
  "@intlify/unplugin-vue-i18n": "^6.0.8",
60
- "@shikijs/transformers": "^3.6.0",
61
+ "@shikijs/transformers": "^3.7.0",
61
62
  "@types/katex": "^0.16.7",
62
- "@unhead/addons": "^2.0.10",
63
- "@unhead/schema-org": "^2.0.10",
64
- "@unhead/vue": "^2.0.10",
65
- "@vitejs/plugin-vue": "^5.2.4",
63
+ "@unhead/addons": "^2.0.12",
64
+ "@unhead/schema-org": "^2.0.12",
65
+ "@unhead/vue": "^2.0.12",
66
+ "@vitejs/plugin-vue": "^6.0.0",
66
67
  "@vue/devtools-api": "7.7.2",
67
- "@vueuse/core": "^13.3.0",
68
- "@vueuse/integrations": "^13.3.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.6.0",
104
+ "mermaid": "^11.8.1",
103
105
  "mlly": "^1.7.4",
104
106
  "nprogress": "^0.2.0",
105
107
  "open": "10.1.0",
@@ -110,29 +112,28 @@
110
112
  "qrcode": "^1.5.4",
111
113
  "resolve-global": "^2.0.0",
112
114
  "sass": "^1.89.2",
113
- "shiki": "^3.6.0",
115
+ "shiki": "^3.7.0",
114
116
  "star-markdown-css": "^0.5.3",
115
117
  "table": "^6.9.0",
116
- "unhead": "^2.0.10",
117
- "unocss": "^66.2.0",
118
+ "unhead": "^2.0.12",
119
+ "unocss": "^66.3.3",
118
120
  "unplugin-vue-components": "28.0.0",
119
- "unplugin-vue-markdown": "^28.3.1",
120
- "unplugin-vue-router": "^0.12.0",
121
+ "unplugin-vue-markdown": "^29.1.0",
122
+ "unplugin-vue-router": "^0.14.0",
121
123
  "vanilla-lazyload": "^19.1.3",
122
- "vite": "^6.3.5",
123
- "vite-dev-rpc": "^1.0.7",
124
+ "vite": "^7.0.4",
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
- "vite-ssg": "^27.0.1",
128
+ "vite-ssg": "^28.0.0",
127
129
  "vite-ssg-sitemap": "^0.9.0",
128
- "vitepress-plugin-group-icons": "^1.6.0",
129
- "vue": "^3.5.16",
130
- "vue-i18n": "^11.1.5",
130
+ "vitepress-plugin-group-icons": "^1.6.1",
131
+ "vue": "^3.5.17",
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.5",
135
- "@valaxyjs/utils": "0.23.5"
135
+ "@valaxyjs/devtools": "0.24.0",
136
+ "@valaxyjs/utils": "0.24.0"
136
137
  },
137
138
  "devDependencies": {
138
139
  "@mdit-vue/plugin-component": "^2.1.4",
@@ -159,6 +160,9 @@
159
160
  "https-localhost": "^4.7.1",
160
161
  "rollup-plugin-visualizer": "^6.0.3"
161
162
  },
163
+ "resolutions": {
164
+ "vite": "catalog:build"
165
+ },
162
166
  "scripts": {
163
167
  "clean": "rimraf dist",
164
168
  "build": "rimraf dist && tsup",
@@ -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[]