Skip to content

Commit e98596f

Browse files
committed
Add Beta changelogs
1 parent 49aa5ef commit e98596f

File tree

12 files changed

+240
-15
lines changed

12 files changed

+240
-15
lines changed

website/src/.vitepress/config.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,37 @@ export default defineConfig({
7676
pageData.description = pageData.frontmatter.description
7777
}
7878
}
79+
else if (pageData.filePath === 'changelogs/beta/[tag].md') {
80+
const tag = (pageData as any).params?.tag as string | undefined
81+
if (tag) {
82+
pageData.frontmatter ||= {}
83+
pageData.frontmatter.title = tag
84+
85+
let publishedAt = releaseDateCache.get(`beta-${tag}`)
86+
if (!publishedAt) {
87+
try {
88+
const { data } = await octokit.repos.getReleaseByTag({ owner: 'mihonapp', repo: 'mihon-preview', tag })
89+
publishedAt = data.published_at || data.created_at || ''
90+
if (publishedAt)
91+
releaseDateCache.set(`beta-${tag}`, publishedAt)
92+
}
93+
catch {}
94+
}
95+
96+
const prettyDate = publishedAt
97+
? new Date(publishedAt).toLocaleDateString('en', { month: 'short', day: 'numeric', year: 'numeric' })
98+
: undefined
99+
100+
const versionLabel = tag
101+
const desc = prettyDate
102+
? `Beta changelog for Mihon ${versionLabel}, released on ${prettyDate}`
103+
: `Beta changelog for Mihon ${versionLabel}`
104+
105+
pageData.frontmatter.description = pageData.frontmatter.description || desc
106+
pageData.title = pageData.frontmatter.title
107+
pageData.description = pageData.frontmatter.description
108+
}
109+
}
79110
return pageData
80111
},
81112
transformHead: async context => generateMeta(context, hostname),

website/src/.vitepress/config/hooks/generateOgImages.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,41 @@ async function generateOgImages(config: SiteConfig) {
5757
}
5858

5959
// Extract first H3 section title and its bullet points from a changelog body
60-
function extractChangelogSnippet(body: string | null | undefined): string | undefined {
60+
function extractChangelogSnippet(body: string | null | undefined, options: { includeHeading?: boolean } = {}): string | undefined {
61+
const { includeHeading = true } = options
6162
if (!body)
6263
return undefined
6364
const src = body.replace(/\r/g, '')
6465
// Cut off checksum sections to avoid noise
6566
const cleaned = src.split(/---\n\n###\s*Checksums|---\n\nMD5/i)[0] || src
66-
// Find first H3 section
67+
68+
const esc = (s: string) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;')
69+
70+
if (!includeHeading) {
71+
// For beta changelogs: extract first 3 bullet points from anywhere in the body
72+
let bullets = cleaned
73+
.split('\n')
74+
.map(l => l.trim())
75+
.filter(l => /^[-*]\s+/.test(l))
76+
.map(l => l.replace(/^[-*]\s+/, ''))
77+
78+
// Sanitize bullets: remove images, links (keep label), inline code, and parenthetical content
79+
bullets = bullets.map((b) => {
80+
const t = b
81+
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
82+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
83+
.replace(/`([^`]+)`/g, '$1')
84+
.trim()
85+
return stripParens(t)
86+
}).filter(Boolean)
87+
88+
const top = bullets.slice(0, 3)
89+
if (top.length === 0)
90+
return undefined
91+
return top.map(b => `<div>• ${esc(b)}</div>`).join('')
92+
}
93+
94+
// Find first H3 section for stable changelogs
6795
const match = cleaned.match(/^###\s+(\S[^\n]*)\n([\s\S]*?)(?=^#{1,3}\s|Z)/m)
6896
if (!match)
6997
return undefined
@@ -76,7 +104,6 @@ async function generateOgImages(config: SiteConfig) {
76104
.map(l => l.trim())
77105
.filter(l => /^[-*]\s+/.test(l))
78106
.map(l => l.replace(/^[-*]\s+/, ''))
79-
const esc = (s: string) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;')
80107
if (bullets.length === 0) {
81108
return `<div style="font-weight:700">${esc(heading.slice(0, 220))}</div>`
82109
}
@@ -104,7 +131,7 @@ async function generateOgImages(config: SiteConfig) {
104131
return s.replace(/\s*\([^)]*\)/g, '').trim()
105132
}
106133

107-
// Generate OG images for dynamic changelog pages
134+
// Generate OG images for dynamic changelog pages (stable)
108135
const octokit = new Octokit()
109136
const releases = await octokit.paginate(octokit.repos.listReleases, {
110137
owner: 'mihonapp',
@@ -131,6 +158,33 @@ async function generateOgImages(config: SiteConfig) {
131158
fonts,
132159
})
133160
}
161+
162+
// Generate OG images for dynamic changelog pages (beta)
163+
const betaReleases = await octokit.paginate(octokit.repos.listReleases, {
164+
owner: 'mihonapp',
165+
repo: 'mihon-preview',
166+
per_page: 100,
167+
})
168+
169+
for (const r of betaReleases) {
170+
if (!r.tag_name)
171+
continue
172+
const pageLike: Pick<ContentData, 'url' | 'frontmatter'> = {
173+
url: `/changelogs/beta/${r.tag_name}`,
174+
frontmatter: {
175+
// Prefer release name; fallback to tag
176+
title: r.name || `Mihon ${r.tag_name}`,
177+
description: extractChangelogSnippet(r.body, { includeHeading: false }),
178+
} as any,
179+
}
180+
181+
await generateImage({
182+
page: pageLike,
183+
template,
184+
outDir: config.outDir,
185+
fonts,
186+
})
187+
}
134188
}
135189

136190
export default generateOgImages

website/src/.vitepress/config/navigation/sidebar.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ function defaultSidebar(): DefaultTheme.SidebarItem[] {
2121
{
2222
text: 'Changelogs',
2323
link: '/changelogs/',
24+
collapsed: true,
25+
items: [
26+
{ text: 'Beta', link: '/changelogs/beta/' },
27+
],
2428
},
2529
{
2630
text: 'Forks',

website/src/.vitepress/theme/components/ChangelogByTag.vue

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@
22
import MarkdownIt from 'markdown-it'
33
import moment from 'moment'
44
import { computed, toRefs } from 'vue'
5-
import { data as changelogs } from '../data/changelogs.data'
5+
import { data as betaChangelogs } from '../data/changelogs-beta.data'
6+
import { data as stableChangelogs } from '../data/changelogs.data'
67
import { formatChangelog } from '../utils/formatChangelog'
78
import Contributors from './Contributors.vue'
89
9-
const props = defineProps<{ tag: string }>()
10-
const { tag } = toRefs(props)
10+
const props = defineProps<{ tag: string, repo?: string }>()
11+
const { tag, repo } = toRefs(props)
12+
13+
const changelogs = computed(() => {
14+
return repo?.value === 'mihon-preview' ? betaChangelogs : stableChangelogs
15+
})
1116
1217
const md = new MarkdownIt({ html: true })
1318
1419
function renderMarkdown(string: string | null | undefined) {
1520
return formatChangelog(md, string, { stripChecksums: true })
1621
}
1722
18-
const release = computed(() => changelogs.find(r => r.tag_name === tag.value))
23+
const release = computed(() => changelogs.value.find(r => r.tag_name === tag.value))
1924
const latestStableTag = computed(() => {
20-
const stable = changelogs
25+
const stable = changelogs.value
2126
.filter(r => !r.draft && !r.prerelease)
2227
.toSorted((a, b) => new Date(b.published_at!).getTime() - new Date(a.published_at!).getTime())
2328
return stable[0]?.tag_name
@@ -47,7 +52,7 @@ function assetDate(dateStr?: string) {
4752
<template>
4853
<div v-if="release">
4954
<h1 :id="isLatest ? 'latest' : release.tag_name">
50-
{{ release.tag_name.substring(1) }}
55+
{{ repo === 'mihon-preview' ? release.tag_name : release.tag_name.substring(1) }}
5156
<Badge v-if="isLatest" type="tip" text="Latest" />
5257
<a
5358
class="header-anchor"

website/src/.vitepress/theme/components/ChangelogsList.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
<script setup lang="ts">
22
import MarkdownIt from 'markdown-it'
3-
import { data as changelogs } from '../data/changelogs.data'
3+
import { computed } from 'vue'
4+
import { data as betaChangelogs } from '../data/changelogs-beta.data'
5+
import { data as stableChangelogs } from '../data/changelogs.data'
46
import { formatChangelog } from '../utils/formatChangelog'
57
import Contributors from './Contributors.vue'
68
9+
const props = defineProps<{ repo?: string }>()
10+
11+
const changelogs = computed(() => {
12+
return props.repo === 'mihon-preview' ? betaChangelogs : stableChangelogs
13+
})
14+
715
const md = new MarkdownIt({ html: true })
816
917
function renderMarkdown(string: string | null | undefined) {
@@ -26,9 +34,9 @@ const dateFormatter = new Intl.DateTimeFormat('en', {
2634
>
2735
<h2 :id="index === 0 ? 'latest' : release.tag_name">
2836
<a
29-
:href="`/changelogs/${release.tag_name}`"
37+
:href="props.repo === 'mihon-preview' ? `/changelogs/beta/${release.tag_name}` : `/changelogs/${release.tag_name}`"
3038
>
31-
{{ release.tag_name.substring(1) }}
39+
{{ props.repo === 'mihon-preview' ? release.tag_name : release.tag_name.substring(1) }}
3240
</a>
3341
<Badge v-if="index === 0" type="tip" text="Latest" />
3442
<a

website/src/.vitepress/theme/components/OgImageTemplate.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ defineProps<{ title: string, description?: string, dir?: string }>()
2121
<svg v-else-if="dir === 'Guide'" width="48" height="48" viewBox="0 -960 960 960" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M831-327.5V-558L516-386.5q-17 9-36 9t-36-9l-329.5-180q-10-5.5-14.5-14.25t-4.5-18.75q0-10 4.5-18.75t14.5-14.25l330-179.5q8.5-5 17.25-7t18.25-2q9.5 0 18.25 2t17.25 7l371 202q9 5 14.25 13.75T906-577v249.5q0 15.5-11 26.5t-26.5 11q-15.5 0-26.5-11t-11-26.5Zm-387 173-197-107q-18.5-10-28.75-27.75T208-328v-149.5L444-349q17 9 36 9t36-9l236-128.5V-328q0 21-10.25 38.75T713-261.5l-197 107q-8.5 5-17.5 7t-18.5 2q-9.5 0-18.5-2t-17.5-7Z" /></svg>
2222
<svg v-else-if="dir === 'News'" width="48" height="48" viewBox="0 -960 960 960" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M157.5-122.5q-31 0-53-22t-22-53v-614q0-6.5 5.5-9t10.5 2.5l35.5 35.5q5.5 5.5 13 5.25t13-5.75l40-40q5.5-5.5 13-5.75t13 5.25l41 41q5.5 5.5 13 5.5t13-5.5l41-41q5.5-5.5 13-5.25t13 5.75l40 40q5.5 5.5 13 5.75t13-5.25l41-41q5.5-5.5 13-5.5t13 5.5l41 41q5.5 5.5 13 5.25t13-5.75l40-40q5.5-5.5 13-5.75t13 5.25l41 41q5.5 5.5 13 5.5t13-5.5l41-41q5.5-5.5 13-5.25t13 5.75l40 40q5.5 5.5 13 5.75t13-5.25l35.5-35.5q5-5 10.5-2.5t5.5 9v614q0 31-22 53t-53 22h-645Zm0-75h285v-245h-285v245Zm360 0h285v-85h-285v85Zm0-160h285v-85h-285v85Zm-360-160h645v-125h-645v125Z" /></svg>
2323
<svg v-else-if="dir === 'Changelog'" width="48" height="48" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M20 3H4c-1.11 0-2 .89-2 2v14c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V5c0-1.11-.89-2-2-2M5 7h5v6H5zm14 10H5v-2h14zm0-4h-7v-2h7zm0-4h-7V7h7z" /></svg>
24+
<svg v-else-if="dir === 'Changelog'" width="48" height="48" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M20 3H4c-1.11 0-2 .89-2 2v14c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V5c0-1.11-.89-2-2-2M5 7h5v6H5zm14 10H5v-2h14zm0-4h-7v-2h7zm0-4h-7V7h7z" /></svg>
2425
</div>
2526
</div>
2627
<div
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { GetResponseDataTypeFromEndpointMethod } from '@octokit/types'
2+
import { Octokit } from '@octokit/rest'
3+
import { defineLoader } from 'vitepress'
4+
5+
const octokit = new Octokit()
6+
7+
type GitHubReleaseList = GetResponseDataTypeFromEndpointMethod<typeof octokit.repos.listReleases>
8+
9+
declare const data: GitHubReleaseList
10+
export { data }
11+
12+
export default defineLoader({
13+
async load(): Promise<GitHubReleaseList> {
14+
const releases = await octokit.paginate(octokit.repos.listReleases, {
15+
owner: 'mihonapp',
16+
repo: 'mihon-preview',
17+
per_page: 100,
18+
})
19+
20+
return releases
21+
},
22+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
# This is a dynamic route template. Title/description can be generic; content uses params.
3+
outline: false
4+
lastUpdated: false
5+
editLink: false
6+
prev:
7+
text: 'Beta Changelogs'
8+
link: '/changelogs/beta'
9+
next:
10+
text: 'Download'
11+
link: '/download'
12+
---
13+
14+
<script setup lang="ts">
15+
import { useData } from 'vitepress'
16+
import ChangelogByTag from "@theme/components/ChangelogByTag.vue";
17+
18+
const { page } = useData()
19+
const tag = page.value.params.tag as string
20+
</script>
21+
22+
<!-- markdownlint-disable-file -->
23+
<ChangelogByTag :tag="tag" repo="mihon-preview" />
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Octokit } from '@octokit/rest'
2+
3+
export default {
4+
async paths() {
5+
const octokit = new Octokit()
6+
const releases = await octokit.paginate(octokit.repos.listReleases, {
7+
owner: 'mihonapp',
8+
repo: 'mihon-preview',
9+
per_page: 100,
10+
})
11+
12+
return releases
13+
.filter(r => !!r.tag_name)
14+
.map(r => ({ params: { tag: r.tag_name } }))
15+
},
16+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
title: Changelogs for Beta
3+
description: Changelogs of all Mihon beta releases.
4+
lastUpdated: false
5+
editLink: false
6+
prev: false
7+
next: false
8+
---
9+
10+
<script setup>
11+
import ChangelogsList from "@theme/components/ChangelogsList.vue";
12+
</script>
13+
14+
# Changelogs for Beta
15+
16+
Changelogs of all Mihon beta releases, which are also available [on GitHub](https://github.com/mihonapp/mihon-preview/releases).<br>
17+
Stable releases can be seen [on the main changelogs page](/changelogs/).
18+
19+
<ChangelogsList repo="mihon-preview" />

0 commit comments

Comments
 (0)