valaxy 0.28.0-beta.2 → 0.28.0-beta.4

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.
@@ -6,6 +6,25 @@ export function isEmptyAddon(addon: any) {
6
6
  return addon && addon.name === emptyAddonName
7
7
  }
8
8
 
9
+ /**
10
+ * Cast a module namespace import to a plain type, breaking Rollup's
11
+ * static binding analysis on `import * as ns` namespace objects.
12
+ *
13
+ * The identity return is intentional — the cast alone is enough to
14
+ * suppress `IMPORT_IS_UNDEFINED` warnings when accessing addon-specific
15
+ * exports that don't exist in the empty addon fallback module.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import * as addonArtalk from 'valaxy-addon-artalk'
20
+ * const mod = getAddonModule<typeof import('valaxy-addon-artalk')>(addonArtalk)
21
+ * mod.useArtalkWithOptions?.()
22
+ * ```
23
+ */
24
+ export function getAddonModule<T = Record<string, any>>(addon: any): T {
25
+ return addon
26
+ }
27
+
9
28
  export default {
10
29
  name,
11
30
  }
@@ -7,6 +7,6 @@ export default defineComponent({
7
7
  onMounted(() => {
8
8
  show.value = true
9
9
  })
10
- return () => show.value ? slots.default?.() : null
10
+ return () => show.value ? slots.default?.() : slots.fallback?.() ?? null
11
11
  },
12
12
  })
@@ -1,4 +1,5 @@
1
1
  <script lang="ts" setup>
2
+ import { isClient } from '@vueuse/core'
2
3
  import { runContentUpdated, useDecrypt, useFrontmatter } from 'valaxy'
3
4
  import { computed, defineComponent, h, ref } from 'vue'
4
5
 
@@ -61,7 +62,7 @@ const ValaxyDeprecatedContent = defineComponent({
61
62
  },
62
63
  })
63
64
 
64
- const hasWarning = computed(() => location.protocol !== 'https:')
65
+ const hasWarning = computed(() => isClient && location.protocol !== 'https:')
65
66
  </script>
66
67
 
67
68
  <template>
@@ -1,14 +1,25 @@
1
1
  <script setup lang="ts">
2
2
  import { Tooltip } from 'floating-vue'
3
- // distance offset for sup
3
+ import { onMounted, ref } from 'vue'
4
+
5
+ // floating-vue Tooltip generates different DOM during SSR vs client hydration.
6
+ // Only mount the Tooltip after the component is mounted on the client to avoid mismatches.
7
+ // During SSR and initial client render, a plain <span> is shown instead.
8
+ const mounted = ref(false)
9
+ onMounted(() => {
10
+ mounted.value = true
11
+ })
4
12
  </script>
5
13
 
6
14
  <template>
7
- <Tooltip class="inline-block" :distance="8">
15
+ <Tooltip v-if="mounted" class="inline-block" :distance="8">
8
16
  <slot />
9
17
 
10
18
  <template #popper>
11
19
  <slot name="popper" />
12
20
  </template>
13
21
  </Tooltip>
22
+ <span v-else class="inline-block">
23
+ <slot />
24
+ </span>
14
25
  </template>
@@ -64,7 +64,7 @@ defineExpose({
64
64
  </div>
65
65
  </div>
66
66
  <div v-else>
67
- <YunGallery :photos="photos" />
67
+ <slot :photos="photos" />
68
68
  <div w-full text-center mt-8>
69
69
  <button m-auto class="btn" font-bold @click="encryptAgain">
70
70
  Encrypt Again
@@ -11,13 +11,63 @@ export function useMediumZoom() {
11
11
 
12
12
  if (mediumZoomConfig.enable) {
13
13
  onMounted(() => {
14
- mediumZoom(
14
+ const zoom = mediumZoom(
15
15
  mediumZoomConfig.selector || '.markdown-body img',
16
16
  {
17
17
  background: 'var(--medium-zoom-c-bg, rgba(0, 0, 0, 0.8))',
18
18
  ...mediumZoomConfig.options,
19
19
  },
20
20
  )
21
+
22
+ // Fix blurry images after zoom animation completes.
23
+ // medium-zoom uses CSS transform: scale() which causes browsers (especially
24
+ // Chrome) to render scaled images at lower quality.
25
+ // After the open animation, we replace the transform with actual dimensions
26
+ // to force full-resolution rendering. Before closing, we restore the original
27
+ // transform so the close animation works correctly.
28
+ // @see https://github.com/francoischalifour/medium-zoom/issues/151
29
+ let savedStyles: { transform: string, width: string, height: string } | null = null
30
+
31
+ zoom.on('opened', () => {
32
+ const zoomed = document.querySelector('.medium-zoom-image--opened') as HTMLElement | null
33
+ if (!zoomed)
34
+ return
35
+
36
+ const { transform } = zoomed.style
37
+ const scaleMatch = transform.match(/scale\(([^)]+)\)/)
38
+ if (!scaleMatch)
39
+ return
40
+
41
+ const scale = Number.parseFloat(scaleMatch[1])
42
+ if (!scale || scale === 1)
43
+ return
44
+
45
+ savedStyles = {
46
+ transform,
47
+ width: zoomed.style.width,
48
+ height: zoomed.style.height,
49
+ }
50
+ const rect = zoomed.getBoundingClientRect()
51
+
52
+ // Replace scale transform with actual width/height
53
+ zoomed.style.transform = transform.replace(/scale\([^)]+\)/, 'scale(1)')
54
+ zoomed.style.width = `${rect.width}px`
55
+ zoomed.style.height = `${rect.height}px`
56
+ })
57
+
58
+ zoom.on('close', () => {
59
+ if (!savedStyles)
60
+ return
61
+
62
+ const zoomed = document.querySelector('.medium-zoom-image--opened') as HTMLElement | null
63
+ if (zoomed) {
64
+ // Restore original styles for close animation
65
+ zoomed.style.transform = savedStyles.transform
66
+ zoomed.style.width = savedStyles.width
67
+ zoomed.style.height = savedStyles.height
68
+ }
69
+ savedStyles = null
70
+ })
21
71
  })
22
72
  }
23
73
  }
@@ -0,0 +1,45 @@
1
+ import type { Component } from 'vue'
2
+ import { defineAsyncComponent, defineComponent, h, onMounted, ref } from 'vue'
3
+
4
+ /**
5
+ * Define a component that only renders on the client side.
6
+ *
7
+ * Useful for wrapping browser-only third-party libraries that access
8
+ * `window`, `document`, or other browser APIs not available during SSR/SSG.
9
+ *
10
+ * @param loader - Dynamic import function, e.g. `() => import('some-lib')`
11
+ * @param args - Optional tuple of `[props, children]` to pass to the component
12
+ * @param cb - Optional callback invoked with the resolved component module
13
+ */
14
+ export function defineClientComponent(
15
+ loader: () => Promise<Component | { default: Component }>,
16
+ args?: [Record<string, any>?, (Record<string, any> | (() => any))?],
17
+ cb?: (component: any) => void,
18
+ ) {
19
+ const [props, children] = args ?? []
20
+
21
+ const AsyncComp = defineAsyncComponent(async () => {
22
+ const comp = await loader()
23
+ if (cb)
24
+ cb(comp)
25
+ return comp
26
+ })
27
+
28
+ return defineComponent({
29
+ name: 'ValaxyClientComponent',
30
+ setup(_, { slots }) {
31
+ const show = ref(false)
32
+ onMounted(() => {
33
+ show.value = true
34
+ })
35
+
36
+ return () => {
37
+ if (!show.value)
38
+ return slots.fallback?.() ?? null
39
+
40
+ const childContent = typeof children === 'function' ? children() : children
41
+ return h(AsyncComp, props, childContent)
42
+ }
43
+ },
44
+ })
45
+ }
@@ -1 +1,2 @@
1
+ export * from './client-component'
1
2
  export * from './collection'
@@ -48,9 +48,11 @@ export function orderByMeta(posts: Post[], orderBy: 'date' | 'updated' = 'date',
48
48
  return posts.sort((a, b) => {
49
49
  const aDate = +new Date(a[orderBy] || a.date || '')
50
50
  const bDate = +new Date(b[orderBy] || b.date || '')
51
- if (desc)
52
- return bDate - aDate
53
- else
54
- return aDate - bDate
51
+ const diff = desc ? bDate - aDate : aDate - bDate
52
+ // Stable sort: when dates are equal, use path as tie-breaker
53
+ // to ensure consistent order between SSR and client hydration
54
+ if (diff !== 0)
55
+ return diff
56
+ return (a.path || '') < (b.path || '') ? -1 : (a.path || '') > (b.path || '') ? 1 : 0
55
57
  })
56
58
  }
@@ -1,7 +1,7 @@
1
1
  import 'node:process';
2
2
  import 'yargs';
3
3
  import 'yargs/helpers';
4
- export { c as cli, I as registerDevCommand, W as run, Z as startValaxyDev } from '../../shared/valaxy.DXqMwOZX.mjs';
4
+ export { c as cli, I as registerDevCommand, W as run, Z as startValaxyDev } from '../../shared/valaxy.CAIbzGO-.mjs';
5
5
  import 'node:os';
6
6
  import 'node:path';
7
7
  import 'consola';
@@ -1,4 +1,4 @@
1
- export { A as ALL_ROUTE, E as EXCERPT_SEPARATOR, G as GLOBAL_STATE, P as PATHNAME_PROTOCOL_RE, V as ViteValaxyPlugins, b as build, c as cli, a as createServer, d as createValaxyPlugin, e as customElements, f as defaultSiteConfig, g as defaultValaxyConfig, h as defaultViteConfig, i as defineAddon, j as defineConfig, k as defineSiteConfig, l as defineTheme, m as defineValaxyAddon, n as defineValaxyConfig, o as defineValaxyTheme, p as encryptContent, q as generateClientRedirects, r as getGitTimestamp, s as getIndexHtml, t as getServerInfoText, u as isExternal, v as isInstalledGlobally, w as isKatexEnabled, x as isKatexPluginNeeded, y as isMathJaxEnabled, z as isPath, B as loadConfigFromFile, C as mergeValaxyConfig, D as mergeViteConfigs, F as postProcessForSSG, H as processValaxyOptions, I as registerDevCommand, J as resolveAddonsConfig, K as resolveImportPath, L as resolveImportUrl, M as resolveOptions, N as resolveSiteConfig, O as resolveSiteConfigFromRoot, Q as resolveThemeConfigFromRoot, R as resolveThemeValaxyConfig, S as resolveUserThemeConfig, T as resolveValaxyConfig, U as resolveValaxyConfigFromRoot, W as run, X as ssgBuild, Y as ssgBuildLegacy, Z as startValaxyDev, _ as toAtFS, $ as transformObject, a0 as version } from '../shared/valaxy.DXqMwOZX.mjs';
1
+ export { A as ALL_ROUTE, E as EXCERPT_SEPARATOR, G as GLOBAL_STATE, P as PATHNAME_PROTOCOL_RE, V as ViteValaxyPlugins, b as build, c as cli, a as createServer, d as createValaxyPlugin, e as customElements, f as defaultSiteConfig, g as defaultValaxyConfig, h as defaultViteConfig, i as defineAddon, j as defineConfig, k as defineSiteConfig, l as defineTheme, m as defineValaxyAddon, n as defineValaxyConfig, o as defineValaxyTheme, p as encryptContent, q as generateClientRedirects, r as getGitTimestamp, s as getIndexHtml, t as getServerInfoText, u as isExternal, v as isInstalledGlobally, w as isKatexEnabled, x as isKatexPluginNeeded, y as isMathJaxEnabled, z as isPath, B as loadConfigFromFile, C as mergeValaxyConfig, D as mergeViteConfigs, F as postProcessForSSG, H as processValaxyOptions, I as registerDevCommand, J as resolveAddonsConfig, K as resolveImportPath, L as resolveImportUrl, M as resolveOptions, N as resolveSiteConfig, O as resolveSiteConfigFromRoot, Q as resolveThemeConfigFromRoot, R as resolveThemeValaxyConfig, S as resolveUserThemeConfig, T as resolveValaxyConfig, U as resolveValaxyConfigFromRoot, W as run, X as ssgBuild, Y as ssgBuildLegacy, Z as startValaxyDev, _ as toAtFS, $ as transformObject, a0 as version } from '../shared/valaxy.CAIbzGO-.mjs';
2
2
  import 'node:path';
3
3
  import 'fs-extra';
4
4
  import 'consola/utils';
@@ -1675,7 +1675,7 @@ async function setupMarkdownPlugins(md, options, base = "/") {
1675
1675
  return md;
1676
1676
  }
1677
1677
 
1678
- const version = "0.28.0-beta.2";
1678
+ const version = "0.28.0-beta.4";
1679
1679
 
1680
1680
  const GLOBAL_STATE = {
1681
1681
  valaxyApp: void 0,
@@ -2363,7 +2363,8 @@ function createConfigPlugin(options) {
2363
2363
  function getDefine(_options) {
2364
2364
  return {
2365
2365
  __VUE_PROD_DEVTOOLS__: false,
2366
- __INTLIFY_PROD_DEVTOOLS__: false
2366
+ __INTLIFY_PROD_DEVTOOLS__: false,
2367
+ __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false
2367
2368
  };
2368
2369
  }
2369
2370
  async function getAlias(options) {
@@ -3543,11 +3544,19 @@ async function createUnocssPlugin(options) {
3543
3544
  );
3544
3545
  let config = {};
3545
3546
  const configDeps = [];
3546
- for (const configFile of configFiles) {
3547
- if (await fs.exists(configFile)) {
3548
- const uConfig = await jiti.import(configFile, { default: true });
3549
- config = defu(config, uConfig);
3550
- configDeps.push(configFile);
3547
+ const loadResults = await Promise.all(
3548
+ configFiles.map(async (configFile) => {
3549
+ if (await fs.exists(configFile)) {
3550
+ const uConfig = await jiti.import(configFile, { default: true });
3551
+ return { configFile, uConfig };
3552
+ }
3553
+ return null;
3554
+ })
3555
+ );
3556
+ for (const result of loadResults) {
3557
+ if (result) {
3558
+ config = defu(config, result.uConfig);
3559
+ configDeps.push(result.configFile);
3551
3560
  }
3552
3561
  }
3553
3562
  config = await loadSetups(roots, "unocss.ts", {}, config, true);
@@ -3564,15 +3573,22 @@ function defineValaxyAddon(addonFunc) {
3564
3573
  const defineAddon = defineValaxyAddon;
3565
3574
  async function resolveAddonsConfig(addons, options) {
3566
3575
  let valaxyConfig = {};
3567
- for (const addon of addons) {
3568
- const addonConfigPath = path$1.resolve(addon.root, "valaxy.config.ts");
3569
- if (!await fs.exists(addonConfigPath))
3570
- continue;
3571
- const { config, configFile } = await resolveValaxyConfigFromRoot(addon.root, options);
3572
- if (!config)
3576
+ const results = await Promise.all(
3577
+ addons.map(async (addon) => {
3578
+ const addonConfigPath = path$1.resolve(addon.root, "valaxy.config.ts");
3579
+ if (!await fs.exists(addonConfigPath))
3580
+ return null;
3581
+ const { config, configFile } = await resolveValaxyConfigFromRoot(addon.root, options);
3582
+ if (!config)
3583
+ return null;
3584
+ return { addon, config, configFile };
3585
+ })
3586
+ );
3587
+ for (const result of results) {
3588
+ if (!result)
3573
3589
  continue;
3574
- addon.configFile = configFile;
3575
- valaxyConfig = mergeValaxyConfig(config, valaxyConfig);
3590
+ result.addon.configFile = result.configFile;
3591
+ valaxyConfig = mergeValaxyConfig(result.config, valaxyConfig);
3576
3592
  }
3577
3593
  return valaxyConfig;
3578
3594
  }
@@ -4280,7 +4296,7 @@ async function createRouterPlugin(valaxyApp) {
4280
4296
  }
4281
4297
  const path = route.components.get("default") || "";
4282
4298
  if (path.endsWith(".md")) {
4283
- const md = fs.readFileSync(path, "utf-8");
4299
+ const md = await fs.readFile(path, "utf-8");
4284
4300
  const { data, excerpt, content } = matter(md, matterOptions);
4285
4301
  const mdFm = data;
4286
4302
  const lastUpdated = options.config.siteConfig.lastUpdated;
@@ -4432,6 +4448,13 @@ async function ViteValaxyPlugins(valaxyApp, serverOptions = {}) {
4432
4448
  // https://github.com/loicduong/vite-plugin-vue-layouts-next
4433
4449
  Layouts({
4434
4450
  layoutsDirs: roots.map((root) => `${root}/layouts`),
4451
+ // In SSG builds, layout components must be imported synchronously so that
4452
+ // the client-side hydration tree matches the server-rendered HTML. Without
4453
+ // this, non-default layouts (post, home, etc.) are lazy-loaded and haven't
4454
+ // resolved when hydration starts, causing a mismatch. The old vite-ssg
4455
+ // library handled this by setting VITE_SSG=true; the built-in SSG engine
4456
+ // needs an explicit importMode override instead.
4457
+ ...options.mode === "build" ? { importMode: () => "sync" } : {},
4435
4458
  ...valaxyConfig.layouts
4436
4459
  }),
4437
4460
  // https://github.com/antfu/unplugin-vue-components
@@ -5636,19 +5659,6 @@ async function initServer(valaxyApp, viteConfig) {
5636
5659
  process.exit(1);
5637
5660
  }
5638
5661
  }
5639
- if (import.meta.hot) {
5640
- await import.meta.hot.data.stopping;
5641
- let reload = async () => {
5642
- consola.info("HMR: Stop Server");
5643
- await GLOBAL_STATE.server?.close();
5644
- };
5645
- import.meta.hot.on("vite:beforeFullReload", () => {
5646
- const stopping = reload();
5647
- reload = () => Promise.resolve();
5648
- if (import.meta.hot)
5649
- import.meta.hot.data.stopping = stopping;
5650
- });
5651
- }
5652
5662
 
5653
5663
  async function execBuild({ ssg, ssgEngine, root, output, log }) {
5654
5664
  setEnvProd();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "valaxy",
3
3
  "type": "module",
4
- "version": "0.28.0-beta.2",
4
+ "version": "0.28.0-beta.4",
5
5
  "description": "📄 Vite & Vue powered static blog generator.",
6
6
  "author": {
7
7
  "email": "me@yunyoujun.cn",
@@ -90,13 +90,13 @@
90
90
  "fs-extra": "^11.3.4",
91
91
  "fuse.js": "^7.1.0",
92
92
  "gray-matter": "^4.0.3",
93
- "hookable": "^6.0.1",
93
+ "hookable": "^6.1.0",
94
94
  "html-to-text": "^9.0.5",
95
95
  "jiti": "^2.6.1",
96
96
  "js-base64": "^3.7.8",
97
97
  "js-yaml": "^4.1.1",
98
98
  "katex": "^0.16.38",
99
- "lru-cache": "^11.2.6",
99
+ "lru-cache": "^11.2.7",
100
100
  "markdown-it": "^14.1.1",
101
101
  "markdown-it-anchor": "^9.2.0",
102
102
  "markdown-it-async": "^2.2.0",
@@ -140,8 +140,8 @@
140
140
  "vue-i18n": "^11.3.0",
141
141
  "vue-router": "^5.0.3",
142
142
  "yargs": "^18.0.0",
143
- "@valaxyjs/devtools": "0.28.0-beta.2",
144
- "@valaxyjs/utils": "0.28.0-beta.2"
143
+ "@valaxyjs/devtools": "0.28.0-beta.4",
144
+ "@valaxyjs/utils": "0.28.0-beta.4"
145
145
  },
146
146
  "devDependencies": {
147
147
  "@mdit-vue/plugin-component": "^3.0.2",
@@ -164,7 +164,7 @@
164
164
  "@types/yargs": "^17.0.35",
165
165
  "gh-pages": "^6.3.0",
166
166
  "https-localhost": "^4.7.1",
167
- "nanoid": "^5.1.6",
167
+ "nanoid": "^5.1.7",
168
168
  "rollup-plugin-visualizer": "^7.0.1",
169
169
  "unbuild": "^3.6.1"
170
170
  },