valaxy 0.28.5 → 0.28.7

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,9 +1,9 @@
1
1
  import type { ComputedRef, MaybeRef } from 'vue'
2
2
  import type { CollectionConfig } from '../types'
3
- import collections from '#valaxy/blog/collections'
4
-
5
3
  import { computed, unref } from 'vue'
4
+
6
5
  import { useRoute } from 'vue-router'
6
+ import collections from '#valaxy/blog/collections'
7
7
  import { usePageList } from './post'
8
8
 
9
9
  export interface PostCollectionInfo {
@@ -2,6 +2,98 @@ import mediumZoom from 'medium-zoom'
2
2
  import { useSiteConfig } from 'valaxy'
3
3
  import { onMounted } from 'vue'
4
4
 
5
+ interface ZoomRectLike {
6
+ left: number
7
+ top: number
8
+ width: number
9
+ height: number
10
+ }
11
+
12
+ interface ZoomImageStyle {
13
+ transform: string
14
+ width: string
15
+ height: string
16
+ }
17
+
18
+ interface SavedZoomImageStyle extends ZoomImageStyle {
19
+ element: HTMLElement
20
+ }
21
+
22
+ /**
23
+ * Convert the opened transform into real dimensions while keeping the same viewport rect.
24
+ * This avoids Chrome rendering the zoomed image blurry because of transform scaling.
25
+ *
26
+ * @see https://github.com/francoischalifour/medium-zoom/issues/151
27
+ * @see https://github.com/YunYouJun/valaxy/issues/317
28
+ * @see https://github.com/YunYouJun/valaxy/issues/692
29
+ */
30
+ export function computeZoomRectStyle(
31
+ visualRect: ZoomRectLike,
32
+ layoutOffset: Pick<ZoomRectLike, 'left' | 'top'>,
33
+ scrollOffset = { x: 0, y: 0 },
34
+ ): ZoomImageStyle {
35
+ return {
36
+ transform: `translate3d(${visualRect.left + scrollOffset.x - layoutOffset.left}px, ${visualRect.top + scrollOffset.y - layoutOffset.top}px, 0)`,
37
+ width: `${visualRect.width}px`,
38
+ height: `${visualRect.height}px`,
39
+ }
40
+ }
41
+
42
+ function getDocumentScroll() {
43
+ return {
44
+ x: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,
45
+ y: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0,
46
+ }
47
+ }
48
+
49
+ function getStylePixelValue(value: string, fallback: number) {
50
+ const parsed = Number.parseFloat(value)
51
+ return Number.isFinite(parsed) ? parsed : fallback
52
+ }
53
+
54
+ function applyWithoutTransformTransition(element: HTMLElement, apply: () => void) {
55
+ const transition = element.style.transition
56
+ const transitionPriority = element.style.getPropertyPriority('transition')
57
+
58
+ element.style.setProperty('transition', 'none', 'important')
59
+ apply()
60
+ void element.offsetWidth
61
+
62
+ if (transition)
63
+ element.style.setProperty('transition', transition, transitionPriority)
64
+ else
65
+ element.style.removeProperty('transition')
66
+ }
67
+
68
+ function freezeOpenedImage(element: HTMLElement): SavedZoomImageStyle | null {
69
+ const scaleMatch = element.style.transform.match(/scale\(([^)]+)\)/)
70
+ const scale = scaleMatch ? Number.parseFloat(scaleMatch[1]) : 1
71
+ if (!scale || scale === 1)
72
+ return null
73
+
74
+ const rect = element.getBoundingClientRect()
75
+ const scrollOffset = getDocumentScroll()
76
+ const layoutOffset = {
77
+ left: getStylePixelValue(element.style.left, rect.left + scrollOffset.x),
78
+ top: getStylePixelValue(element.style.top, rect.top + scrollOffset.y),
79
+ }
80
+ const nextStyle = computeZoomRectStyle(rect, layoutOffset, scrollOffset)
81
+ const savedStyle: SavedZoomImageStyle = {
82
+ element,
83
+ transform: element.style.transform,
84
+ width: element.style.width,
85
+ height: element.style.height,
86
+ }
87
+
88
+ applyWithoutTransformTransition(element, () => {
89
+ element.style.transform = nextStyle.transform
90
+ element.style.width = nextStyle.width
91
+ element.style.height = nextStyle.height
92
+ })
93
+
94
+ return savedStyle
95
+ }
96
+
5
97
  /**
6
98
  * @description image preview by medium-zoom
7
99
  */
@@ -19,54 +111,40 @@ export function useMediumZoom() {
19
111
  },
20
112
  )
21
113
 
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
114
+ let savedStyles: SavedZoomImageStyle[] = []
30
115
 
31
116
  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`
117
+ savedStyles = Array
118
+ .from(document.querySelectorAll<HTMLElement>('.medium-zoom-image--opened'))
119
+ .map(freezeOpenedImage)
120
+ .filter((style): style is SavedZoomImageStyle => style !== null)
56
121
  })
57
122
 
58
123
  zoom.on('close', () => {
59
- if (!savedStyles)
124
+ if (!savedStyles.length)
60
125
  return
61
126
 
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
127
+ for (const style of savedStyles) {
128
+ const { element } = style
129
+ if (!document.body.contains(element))
130
+ continue
131
+
132
+ // Restore original medium-zoom styles (without transition) so the element
133
+ // is back to the state medium-zoom expects for its close animation.
134
+ applyWithoutTransformTransition(element, () => {
135
+ element.style.transform = style.transform
136
+ element.style.width = style.width
137
+ element.style.height = style.height
138
+ })
139
+
140
+ // medium-zoom sets `transform = ''` BEFORE dispatching the close event,
141
+ // but we just overwrote it above. Re-apply `''` to actually trigger the
142
+ // close transition; otherwise transitionend never fires and the cloned
143
+ // image element is never removed from the DOM.
144
+ element.style.transform = ''
68
145
  }
69
- savedStyles = null
146
+
147
+ savedStyles = []
70
148
  })
71
149
  })
72
150
  }
@@ -1,10 +1,10 @@
1
1
  import type { Post, SiteConfig } from 'valaxy'
2
2
  import type { ComputedRef } from 'vue'
3
3
  import type { CollectionConfig } from '../../types'
4
- import collections from '#valaxy/blog/collections'
5
4
  import { orderByMeta, useSiteConfig } from 'valaxy'
6
5
  import { computed } from 'vue'
7
6
  import { useI18n } from 'vue-i18n'
7
+ import collections from '#valaxy/blog/collections'
8
8
  import { useRouterStore } from '../../stores'
9
9
  import { isLocaleKey, stripLocalePrefix, tObject } from '../../utils'
10
10
 
package/client/config.ts CHANGED
@@ -4,7 +4,7 @@ import type { ComputedRef, InjectionKey } from 'vue'
4
4
  // fix build caused by pnpm
5
5
  // This is likely not portable. A type annotation is necessary.
6
6
  // https://github.com/microsoft/TypeScript/issues/42873
7
- import type { DefaultTheme, ValaxyConfig } from '../types'
7
+ import type { DefaultTheme, ValaxyAddon, ValaxyConfig } from '../types'
8
8
  import type { ValaxyData } from './app/data'
9
9
  import { computed, hasInjectionContext, inject, readonly, shallowRef } from 'vue'
10
10
 
@@ -102,3 +102,36 @@ export function useRuntimeConfig() {
102
102
  const config = useValaxyConfig()
103
103
  return computed(() => config!.value.runtimeConfig)
104
104
  }
105
+
106
+ /**
107
+ * Get addon config from runtime config.
108
+ *
109
+ * A generic, type-safe shortcut that every addon and theme can use instead of
110
+ * manually calling `useRuntimeConfig()` + `computed(...)` + type assertion.
111
+ *
112
+ * Must be called inside `<script setup>` or a Vue lifecycle hook (same
113
+ * constraint as `useRuntimeConfig()`).
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * // Inside an addon — replaces the boilerplate `useAddonXxxConfig()` pattern
118
+ * import type { MyOptions } from '../types'
119
+ * const config = useAddonConfig<MyOptions>('valaxy-addon-xxx')
120
+ * config.value?.options // MyOptions | undefined
121
+ * ```
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * // Inside a theme component — no dynamic import needed
126
+ * const algolia = useAddonConfig<AlgoliaSearchOptions>('valaxy-addon-algolia')
127
+ * if (algolia.value) { /* addon is installed and configured *\/ }
128
+ * ```
129
+ *
130
+ * @public
131
+ */
132
+ export function useAddonConfig<AddonOptions = Record<string, any>>(addonName: string) {
133
+ const runtimeConfig = useRuntimeConfig()
134
+ return computed(() =>
135
+ runtimeConfig.value.addons[addonName] as ValaxyAddon<AddonOptions> | undefined,
136
+ )
137
+ }
package/client/main.ts CHANGED
@@ -13,12 +13,9 @@ import ClientOnly from './components/ClientOnly'
13
13
 
14
14
  import setupMain from './setup/main'
15
15
  import { setupValaxyDevTools } from './utils/dev'
16
- /**
17
- * user styles
18
- * virtual module
19
- */
20
- import '#valaxy/styles'
16
+ // uno.css 先于 #valaxy/styles 加载,使主题/用户样式可以覆盖 utility
21
17
  import 'uno.css'
18
+ import '#valaxy/styles'
22
19
 
23
20
  const valaxyConfig = initValaxyConfig()
24
21
 
@@ -1,7 +1,7 @@
1
1
  import 'node:process';
2
2
  import 'yargs';
3
3
  import 'yargs/helpers';
4
- export { c as cli, J as registerDevCommand, X as run, _ as startValaxyDev } from '../../shared/valaxy.CC8moxgJ.mjs';
4
+ export { c as cli, J as registerDevCommand, X as run, _ as startValaxyDev } from '../../shared/valaxy.BIDC5qac.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 fixViteSsgHtml, r as generateClientRedirects, s as getGitTimestamp, t as getIndexHtml, u as getServerInfoText, v as isExternal, w as isInstalledGlobally, x as isKatexEnabled, y as isKatexPluginNeeded, z as isMathJaxEnabled, B as isPath, C as loadConfigFromFile, D as mergeValaxyConfig, F as mergeViteConfigs, H as postProcessForSSG, I as processValaxyOptions, J as registerDevCommand, K as resolveAddonsConfig, L as resolveImportPath, M as resolveImportUrl, N as resolveOptions, O as resolveSiteConfig, Q as resolveSiteConfigFromRoot, R as resolveThemeConfigFromRoot, S as resolveThemeValaxyConfig, T as resolveUserThemeConfig, U as resolveValaxyConfig, W as resolveValaxyConfigFromRoot, X as run, Y as ssgBuild, Z as ssgBuildLegacy, _ as startValaxyDev, $ as toAtFS, a0 as transformObject, a1 as version } from '../shared/valaxy.CC8moxgJ.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 fixViteSsgHtml, r as generateClientRedirects, s as getGitTimestamp, t as getIndexHtml, u as getServerInfoText, v as isExternal, w as isInstalledGlobally, x as isKatexEnabled, y as isKatexPluginNeeded, z as isMathJaxEnabled, B as isPath, C as loadConfigFromFile, D as mergeValaxyConfig, F as mergeViteConfigs, H as postProcessForSSG, I as processValaxyOptions, J as registerDevCommand, K as resolveAddonsConfig, L as resolveImportPath, M as resolveImportUrl, N as resolveOptions, O as resolveSiteConfig, Q as resolveSiteConfigFromRoot, R as resolveThemeConfigFromRoot, S as resolveThemeValaxyConfig, T as resolveUserThemeConfig, U as resolveValaxyConfig, W as resolveValaxyConfigFromRoot, X as run, Y as ssgBuild, Z as ssgBuildLegacy, _ as startValaxyDev, $ as toAtFS, a0 as transformObject, a1 as version } from '../shared/valaxy.BIDC5qac.mjs';
2
2
  import 'node:path';
3
3
  import 'consola';
4
4
  import 'fs-extra';
@@ -198,6 +198,7 @@ async function callHookWithLog(hookName, valaxyApp) {
198
198
  const POOL_SIZE_MULTIPLIER = 128;
199
199
  let pool, poolOffset;
200
200
  function fillPool(bytes) {
201
+ if (bytes < 0 || bytes > 1024) throw new RangeError('Wrong ID size')
201
202
  if (!pool || pool.length < bytes) {
202
203
  pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
203
204
  webcrypto.getRandomValues(pool);
@@ -213,8 +214,23 @@ function random(bytes) {
213
214
  return pool.subarray(poolOffset - bytes, poolOffset)
214
215
  }
215
216
  function customRandom(alphabet, defaultSize, getRandom) {
216
- let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1;
217
- let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length);
217
+ let safeByteCutoff = 256 - (256 % alphabet.length);
218
+ if (safeByteCutoff === 256) {
219
+ let mask = alphabet.length - 1;
220
+ return (size = defaultSize) => {
221
+ if (!size) return ''
222
+ let id = '';
223
+ while (true) {
224
+ let bytes = getRandom(size);
225
+ let i = size;
226
+ while (i--) {
227
+ id += alphabet[bytes[i] & mask];
228
+ if (id.length >= size) return id
229
+ }
230
+ }
231
+ }
232
+ }
233
+ let step = Math.ceil((1.6 * 256 * defaultSize) / safeByteCutoff);
218
234
  return (size = defaultSize) => {
219
235
  if (!size) return ''
220
236
  let id = '';
@@ -222,8 +238,10 @@ function customRandom(alphabet, defaultSize, getRandom) {
222
238
  let bytes = getRandom(step);
223
239
  let i = step;
224
240
  while (i--) {
225
- id += alphabet[bytes[i] & mask] || '';
226
- if (id.length >= size) return id
241
+ if (bytes[i] < safeByteCutoff) {
242
+ id += alphabet[bytes[i] % alphabet.length];
243
+ if (id.length >= size) return id
244
+ }
227
245
  }
228
246
  }
229
247
  }
@@ -1733,7 +1751,7 @@ async function setupMarkdownPlugins(md, options, base = "/") {
1733
1751
  return md;
1734
1752
  }
1735
1753
 
1736
- const version = "0.28.5";
1754
+ const version = "0.28.7";
1737
1755
 
1738
1756
  const GLOBAL_STATE = {
1739
1757
  valaxyApp: void 0,
@@ -4224,7 +4242,6 @@ const templateStyles = {
4224
4242
  }
4225
4243
  }
4226
4244
  }
4227
- imports.unshift(`import "${await resolveImportUrl("@unocss/reset/tailwind-compat.css")}"`);
4228
4245
  return imports.join("\n");
4229
4246
  }
4230
4247
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "valaxy",
3
3
  "type": "module",
4
- "version": "0.28.5",
4
+ "version": "0.28.7",
5
5
  "description": "📄 Vite & Vue powered static blog generator.",
6
6
  "author": {
7
7
  "email": "me@yunyoujun.cn",
@@ -62,9 +62,9 @@
62
62
  "dependencies": {
63
63
  "@antfu/install-pkg": "^1.1.0",
64
64
  "@antfu/utils": "^9.3.0",
65
- "@clack/prompts": "^1.2.0",
65
+ "@clack/prompts": "^1.3.0",
66
66
  "@iconify-json/ri": "^1.2.10",
67
- "@intlify/unplugin-vue-i18n": "^11.0.7",
67
+ "@intlify/unplugin-vue-i18n": "^11.1.2",
68
68
  "@shikijs/transformers": "^3.23.0",
69
69
  "@types/katex": "^0.16.8",
70
70
  "@unhead/addons": "^2.1.13",
@@ -72,8 +72,8 @@
72
72
  "@unhead/vue": "^2.1.13",
73
73
  "@vitejs/plugin-vue": "^6.0.6",
74
74
  "@vue/devtools-api": "7.7.2",
75
- "@vueuse/core": "^14.2.1",
76
- "@vueuse/integrations": "^14.2.1",
75
+ "@vueuse/core": "^14.3.0",
76
+ "@vueuse/integrations": "^14.3.0",
77
77
  "beasties": "^0.4.2",
78
78
  "consola": "^3.4.2",
79
79
  "cross-spawn": "^7.0.6",
@@ -85,18 +85,18 @@
85
85
  "ejs": "^5.0.2",
86
86
  "escape-html": "^1.0.3",
87
87
  "fast-glob": "^3.3.3",
88
- "feed": "^5.2.0",
88
+ "feed": "^5.2.1",
89
89
  "floating-vue": "^5.2.2",
90
- "fs-extra": "^11.3.4",
90
+ "fs-extra": "^11.3.5",
91
91
  "fuse.js": "^7.3.0",
92
92
  "gray-matter": "^4.0.3",
93
93
  "hookable": "^6.1.1",
94
94
  "html-to-text": "^9.0.5",
95
- "jiti": "^2.6.1",
95
+ "jiti": "^2.7.0",
96
96
  "js-base64": "^3.7.8",
97
97
  "js-yaml": "^4.1.1",
98
98
  "katex": "^0.16.45",
99
- "lru-cache": "^11.3.5",
99
+ "lru-cache": "^11.3.6",
100
100
  "markdown-it": "^14.1.1",
101
101
  "markdown-it-anchor": "^9.2.0",
102
102
  "markdown-it-async": "^2.2.0",
@@ -113,7 +113,7 @@
113
113
  "mlly": "^1.8.2",
114
114
  "nprogress": "^0.2.0",
115
115
  "open": "10.1.0",
116
- "ora": "^9.3.0",
116
+ "ora": "^9.4.0",
117
117
  "p-map": "^7.0.4",
118
118
  "pascalcase": "^2.0.0",
119
119
  "pathe": "^2.0.3",
@@ -129,18 +129,18 @@
129
129
  "unplugin-vue-components": "28.0.0",
130
130
  "unplugin-vue-markdown": "^30.0.0",
131
131
  "vanilla-lazyload": "^19.1.3",
132
- "vite": "^8.0.8",
132
+ "vite": "^8.0.11",
133
133
  "vite-plugin-vue-devtools": "^8.1.1",
134
134
  "vite-plugin-vue-layouts-next": "^2.1.0",
135
135
  "vite-ssg": "^28.3.0",
136
136
  "vite-ssg-sitemap": "^0.10.0",
137
137
  "vitepress-plugin-group-icons": "^1.7.5",
138
138
  "vue": "3.5.22",
139
- "vue-i18n": "^11.3.2",
140
- "vue-router": "^5.0.4",
139
+ "vue-i18n": "^11.4.2",
140
+ "vue-router": "^5.0.6",
141
141
  "yargs": "^18.0.0",
142
- "@valaxyjs/utils": "0.28.5",
143
- "@valaxyjs/devtools": "0.28.5"
142
+ "@valaxyjs/devtools": "0.28.7",
143
+ "@valaxyjs/utils": "0.28.7"
144
144
  },
145
145
  "devDependencies": {
146
146
  "@mdit-vue/plugin-component": "^3.0.2",
@@ -163,7 +163,7 @@
163
163
  "@types/yargs": "^17.0.35",
164
164
  "gh-pages": "^6.3.0",
165
165
  "https-localhost": "^4.7.1",
166
- "nanoid": "^5.1.7",
166
+ "nanoid": "^5.1.11",
167
167
  "rollup-plugin-visualizer": "^7.0.1",
168
168
  "unbuild": "^3.6.1"
169
169
  },