@@ -74,207 +74,9 @@ const versionHistory: PackageVersionInfo[] = [
7474 { version: ' 1.0.28' , time: ' 2016-12-15T10:00:00Z' , hasProvenance: false },
7575]
7676
77- // Changelog markdown strings keyed by version.
78- // In production these would be pre-rendered HTML from the server
79- // (e.g. GitHub release body or CHANGELOG.md, parsed like README).
77+ // TODO: Replace with pre-rendered HTML from the server
8078const mockChangelogs: Record <string , string > = {
81- ' 3.5.0-beta.3' : ` ### Bug Fixes
82-
83- - Fixed \` v-model\` not triggering update when used with custom modifier on the same component
84- - Resolved hydration mismatch for \` <Suspense>\` with async setup components
85-
86- ### Performance
87-
88- - Reduced scheduler flush cost for large component trees with many watchers ` ,
89-
90- ' 3.5.0-rc.1' : ` ## Vue 3.5 RC
91-
92- The API is now stable. This release candidate is intended for final testing before the stable release.
93-
94- ### New Features
95-
96- - \` useTemplateRef()\` — reactive ref bound to a template element by string key
97- - \` useId()\` — SSR-safe unique ID generation for accessibility attributes
98- - Deferred \` <Suspense>\` — suspense boundary no longer blocks parent tree rendering
99-
100- ### Breaking Changes
101-
102- > **Note:** These only affect experimental APIs that were previously behind flags.
103-
104- - Removed \` v-memo\` on component root nodes — use it on inner elements instead
105- - \` defineModel()\` local mutation now requires explicit \` local\` option
106-
107- **Full Changelog**: [v3.4.21...v3.5.0-rc.1](https://github.com/vuejs/core/compare/v3.4.21...v3.5.0-rc.1) ` ,
108-
109- ' 3.4.21' : ` ### Bug Fixes
110-
111- - Fixed \` <KeepAlive>\` failing to restore scroll position on re-activation (#10156)
112- - Corrected \` shallowReadonly\` not preserving array identity on nested access
113- - Fixed compiler warning for \` v-bind\` shorthand used on \` <slot>\` elements
114-
115- **Full Changelog**: [v3.4.20...v3.4.21](https://github.com/vuejs/core/compare/v3.4.20...v3.4.21) ` ,
116-
117- ' 3.4.0' : ` ## Vue 3.4 — "Slam Dunk"
118-
119- ### New Features
120-
121- - **Reactivity transform removed** — the experimental \` $ref\` sugar has been dropped; use \` ref()\` directly
122- - **\` v-bind\` shorthand** — \` :foo\` can now be written as just \` :foo\` when binding a same-name prop
123- - **\` defineModel()\` stable** — two-way binding macro is now stable and no longer requires opt-in
124- - **Parser rewrite** — the template compiler's parser is 2× faster and produces better error messages
125-
126- ### Breaking Changes
127-
128- - \` app.config.compilerOptions.isCustomElement\` now receives the full element tag with namespace prefix
129- - \` @vue/reactivity\` no longer exports \` deferredComputed\` — use \` computed\` with a scheduler instead
130-
131- \`\`\` ts
132- // Before
133- const double = deferredComputed(() => count.value * 2)
134-
135- // After
136- const double = computed(() => count.value * 2, { scheduler: queueMicrotask })
137- \`\`\`
138-
139- **Full Changelog**: [v3.3.13...v3.4.0](https://github.com/vuejs/core/compare/v3.3.13...v3.4.0) ` ,
140-
141- ' 3.0.0' : ` ## Vue 3.0 — "One Piece"
142-
143- The first stable release of Vue 3. Rebuilt from the ground up with the Composition API, TypeScript, and a new reactivity system.
144-
145- ### Highlights
146-
147- - **Composition API** — \` setup()\` , \` ref()\` , \` reactive()\` , \` computed()\` , \` watch()\`
148- - **Fragments** — components can now have multiple root nodes
149- - **Teleport** — render content in a different part of the DOM
150- - **Suspense** — coordinate async dependency resolution in component trees
151- - **Improved TypeScript support** — full type inference for component props and emits
152- - **Tree-shakeable** — global APIs are now ES module exports
153-
154- ### Migration
155-
156- Vue 3 is not backwards compatible with Vue 2. See the [Migration Guide](https://v3-migration.vuejs.org/) for a full list of breaking changes.
157-
158- **Full Changelog**: [github.com/vuejs/core](https://github.com/vuejs/core/blob/main/CHANGELOG.md) ` ,
159- }
160-
161- // ─── Markdown rendering ───────────────────────────────────────────────────────
162- // Minimal block-level markdown parser for changelog content.
163- // Handles headings, lists, paragraphs, blockquotes, and inline formatting.
164- // In production this would be replaced by server-rendered HTML (like README).
165-
166- function parseInline(text : string ): string {
167- return text
168- .replace (/ &/ g , ' &' )
169- .replace (/ </ g , ' <' )
170- .replace (/ >/ g , ' >' )
171- .replace (/ \*\* (. +? )\*\* / g , ' <strong>$1</strong>' )
172- .replace (/ `([^ `] + )`/ g , ' <code>$1</code>' )
173- .replace (
174- / \[ ([^ \] ] + )\]\( (https? :[^ )] + )\) / g ,
175- ' <a href="$2" target="_blank" rel="nofollow noreferrer noopener">$1</a>' ,
176- )
177- }
178-
179- function parseChangelogMarkdown(markdown : string ): string {
180- const lines = markdown .split (' \n ' )
181- const out: string [] = []
182- let inList = false
183- let inBlockquote = false
184- let inCodeBlock = false
185- let codeLang = ' '
186- let codeLines: string [] = []
187-
188- const flushList = () => {
189- if (inList ) {
190- out .push (' </ul>' )
191- inList = false
192- }
193- }
194- const flushBlockquote = () => {
195- if (inBlockquote ) {
196- out .push (' </blockquote>' )
197- inBlockquote = false
198- }
199- }
200-
201- for (const line of lines ) {
202- // Code block fence
203- if (line .startsWith (' ```' )) {
204- if (inCodeBlock ) {
205- out .push (
206- ` <pre><code${codeLang ? ` class="language-${codeLang }" ` : ' ' }>${codeLines .map (l => l .replace (/ &/ g , ' &' ).replace (/ </ g , ' <' ).replace (/ >/ g , ' >' )).join (' \n ' )}</code></pre> ` ,
207- )
208- inCodeBlock = false
209- codeLines = []
210- codeLang = ' '
211- } else {
212- flushList ()
213- flushBlockquote ()
214- inCodeBlock = true
215- codeLang = line .slice (3 ).trim ()
216- }
217- continue
218- }
219- if (inCodeBlock ) {
220- codeLines .push (line )
221- continue
222- }
223-
224- const trimmed = line .trim ()
225-
226- // Blank line
227- if (! trimmed ) {
228- flushList ()
229- flushBlockquote ()
230- continue
231- }
232-
233- // Blockquote
234- if (trimmed .startsWith (' > ' )) {
235- flushList ()
236- if (! inBlockquote ) {
237- out .push (' <blockquote>' )
238- inBlockquote = true
239- }
240- out .push (` <p>${parseInline (trimmed .slice (2 ))}</p> ` )
241- continue
242- }
243- flushBlockquote ()
244-
245- // Headings
246- const h2 = trimmed .match (/ ^ ## (. + )/ )
247- const h3 = trimmed .match (/ ^ ### (. + )/ )
248- if (h2 ) {
249- flushList ()
250- out .push (` <h2>${parseInline (h2 [1 ]! )}</h2> ` )
251- continue
252- }
253- if (h3 ) {
254- flushList ()
255- out .push (` <h3>${parseInline (h3 [1 ]! )}</h3> ` )
256- continue
257- }
258-
259- // List item
260- const li = trimmed .match (/ ^ [-*] (. + )/ )
261- if (li ) {
262- if (! inList ) {
263- out .push (' <ul>' )
264- inList = true
265- }
266- out .push (` <li>${parseInline (li [1 ]! )}</li> ` )
267- continue
268- }
269-
270- // Paragraph
271- flushList ()
272- out .push (` <p>${parseInline (trimmed )}</p> ` )
273- }
274-
275- flushList ()
276- flushBlockquote ()
277- return out .join (' \n ' )
79+ ' 3.5.0-beta.3' : ' Hello world' ,
27880}
27981
28082// ─── Derived data ─────────────────────────────────────────────────────────────
@@ -301,15 +103,14 @@ function getVersionTime(version: string): string | undefined {
301103
302104const selectedChangelogVersion = ref <string | null >(null )
303105
304- const selectedChangelogHtml = computed (() => {
106+ const selectedChangelogContent = computed (() => {
305107 if (! selectedChangelogVersion .value ) return ' '
306- const raw = mockChangelogs [selectedChangelogVersion .value ]
307- return raw ? parseChangelogMarkdown (raw ) : ' '
108+ return mockChangelogs [selectedChangelogVersion .value ] ?? ' '
308109})
309110
310- function toggleChangelog(version : string ) {
311- selectedChangelogVersion .value = selectedChangelogVersion .value === version ? null : version
312- }
111+ // function toggleChangelog(version: string) {
112+ // selectedChangelogVersion.value = selectedChangelogVersion.value === version ? null : version
113+ // }
313114
314115// ─── Jump to version ──────────────────────────────────────────────────────────
315116
@@ -416,7 +217,7 @@ watch(jumpVersion, () => {
416217 <!-- Right: date + provenance -->
417218 <div class =" flex flex-col items-end gap-1.5 shrink-0 relative z-10" >
418219 <ProvenanceBadge
419- v-if =" versionHistory.find(v => v.version === tagRows[0].version)?.hasProvenance"
220+ v-if =" versionHistory.find(v => v.version === tagRows?. [0]? .version)?.hasProvenance"
420221 :package-name =" packageName"
421222 :version =" tagRows[0].version"
422223 compact
@@ -545,6 +346,32 @@ watch(jumpVersion, () => {
545346
546347 <!-- Right side -->
547348 <div class =" flex items-center gap-2 shrink-0 relative z-10" >
349+ <!-- Changelog toggle button -->
350+ <!-- TODO(atriiy): changelog would be implemented later -->
351+ <!-- <button
352+ v-if="v.hasChangelog"
353+ type="button"
354+ class="flex items-center gap-1.5 text-xs px-2 py-1 rounded border transition-colors focus-visible:outline-accent/70"
355+ :class="
356+ selectedChangelogVersion === v.version
357+ ? 'border-accent/50 bg-accent/8 text-accent'
358+ : 'border-border text-fg-subtle hover:text-fg hover:border-border-hover'
359+ "
360+ :aria-expanded="selectedChangelogVersion === v.version"
361+ :aria-label="`Toggle changelog for v${v.version}`"
362+ @click.stop="toggleChangelog(v.version)"
363+ >
364+ <span class="i-lucide:scroll-text w-3.5 h-3.5 shrink-0" aria-hidden="true" />
365+ <span class="hidden sm:inline">Changelog</span>
366+ </button> -->
367+
368+ <!-- Divider -->
369+ <span
370+ v-if =" v.hasChangelog"
371+ class =" w-px h-3.5 bg-border shrink-0 hidden sm:block"
372+ aria-hidden =" true"
373+ />
374+
548375 <!-- Metadata: date + provenance -->
549376 <DateTime
550377 v-if =" v.time"
@@ -561,31 +388,6 @@ watch(jumpVersion, () => {
561388 compact
562389 :linked =" false"
563390 />
564-
565- <!-- Divider -->
566- <span
567- v-if =" v.hasChangelog"
568- class =" w-px h-3.5 bg-border shrink-0 hidden sm:block"
569- aria-hidden =" true"
570- />
571-
572- <!-- Changelog toggle button -->
573- <button
574- v-if =" v.hasChangelog"
575- type =" button"
576- class =" flex items-center gap-1.5 text-xs px-2 py-1 rounded border transition-colors focus-visible:outline-accent/70"
577- :class ="
578- selectedChangelogVersion === v.version
579- ? 'border-accent/50 bg-accent/8 text-accent'
580- : 'border-border text-fg-subtle hover:text-fg hover:border-border-hover'
581- "
582- :aria-expanded =" selectedChangelogVersion === v.version"
583- :aria-label =" `Toggle changelog for v${v.version}`"
584- @click.stop =" toggleChangelog(v.version)"
585- >
586- <span class =" i-lucide:scroll-text w-3.5 h-3.5 shrink-0" aria-hidden =" true" />
587- <span class =" hidden sm:inline" >Changelog</span >
588- </button >
589391 </div >
590392 </div >
591393
@@ -598,10 +400,9 @@ watch(jumpVersion, () => {
598400 "
599401 >
600402 <div class =" overflow-hidden" >
601- <div
602- class =" changelog-body border-t border-border px-4 py-3 text-sm"
603- v-html =" selectedChangelogVersion === v.version ? selectedChangelogHtml : ''"
604- />
403+ <div class =" changelog-body border-t border-border px-4 py-3 text-sm" >
404+ {{ selectedChangelogVersion === v.version ? selectedChangelogContent : '' }}
405+ </div >
605406 </div >
606407 </div >
607408 </div >
@@ -641,8 +442,9 @@ watch(jumpVersion, () => {
641442 <!-- Panel body — scrollable for long content -->
642443 <div
643444 class =" changelog-body overflow-y-auto max-h-[calc(100vh-12rem)] px-4 py-3 text-sm"
644- v-html =" selectedChangelogHtml"
645- />
445+ >
446+ {{ selectedChangelogContent }}
447+ </div >
646448 </div >
647449 </div >
648450 </div >
@@ -660,65 +462,4 @@ watch(jumpVersion, () => {
660462 overflow-wrap : break-word ;
661463 word-break : break-word ;
662464}
663-
664- .changelog-body :deep(h2 ) {
665- @apply font-mono font-medium text-fg text-base mt- 4 mb- 2 pb- 1.5 border-b border-border ;
666- }
667- .changelog-body :deep(h2 :first-child ) {
668- @apply mt- 0;
669- }
670- .changelog-body :deep(h3 ) {
671- @apply font-mono font-medium text-fg text-sm mt- 3 mb- 1.5;
672- }
673- .changelog-body :deep(h3 :first-child ) {
674- @apply mt- 0;
675- }
676-
677- .changelog-body :deep(p ) {
678- @apply mb- 2 last :mb-0;
679- }
680-
681- .changelog-body :deep(ul ) {
682- @apply mb- 2 ps- 4 space-y- 1 last :mb-0;
683- list-style-type : disc ;
684- }
685- .changelog-body :deep(li ::marker ) {
686- color : var (--border-hover );
687- }
688-
689- .changelog-body :deep(blockquote ) {
690- @apply border-s- 2 border-border ps- 3 my- 2 text-fg-subtle italic ;
691- }
692-
693- .changelog-body :deep(code ) {
694- @apply font-mono text-xs ;
695- font-size : 0.8em ;
696- background : var (--bg-muted );
697- padding : 0.15em 0.35em ;
698- border-radius : 3px ;
699- border : 1px solid var (--border );
700- }
701-
702- .changelog-body :deep(pre ) {
703- @apply rounded-md border border-border overflow-x-auto p- 3 my- 2;
704- background : var (--bg-subtle );
705- }
706- .changelog-body :deep(pre code ) {
707- background : transparent ;
708- border : none ;
709- padding : 0 ;
710- font-size : 0.8rem ;
711- color : var (--fg );
712- }
713-
714- .changelog-body :deep(a ) {
715- @apply underline underline-offset- 2 decoration- 1 decoration-fg /30 transition-colors duration- 150;
716- }
717- .changelog-body :deep(a :hover ) {
718- @apply decoration-accent text-accent ;
719- }
720-
721- .changelog-body :deep(strong ) {
722- @apply font-semibold text-fg ;
723- }
724465 </style >
0 commit comments