Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 21 additions & 45 deletions web-client/src/views/LandingView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function stripFrontmatter(md: string): string {
const aboutHtml = DOMPurify.sanitize(marked(stripFrontmatter(aboutRaw)) as string)
const contactHtml = DOMPurify.sanitize(marked(stripFrontmatter(contactRaw)) as string)

const tabOptions = ['About', 'Contact', 'SlideRule Releases', 'Web Client Releases']
const tabOptions = ['About', 'Contact', 'Release Notes']
const selectedTab = ref('About')

const panelHtml = computed(() => {
Expand All @@ -30,11 +30,6 @@ const panelHtml = computed(() => {
})

// --- Release Notes ---
//
// Two release-note sources share the list/detail UI below:
// - 'remote' = the SlideRule platform notes scraped from the docs site
// - 'local' = this repo's web-client notes, bundled at build time (see the
// import.meta.glob of src/assets/content/release-notes/*.md)

const RELEASE_NOTES_INDEX_URL = DOCS.releaseNotes.index
const RELEASE_NOTES_BASE_URL = DOCS.releaseNotes.base
Expand All @@ -53,11 +48,7 @@ const releaseHtml = ref('')
const releaseLoading = ref(false)
const releaseError = ref('')

const releaseMode = computed<'remote' | 'local' | null>(() => {
if (selectedTab.value === 'SlideRule Releases') return 'remote'
if (selectedTab.value === 'Web Client Releases') return 'local'
return null
})
const showReleaseNotes = computed(() => selectedTab.value === 'Release Notes')

// --- Local web-client release notes (bundled markdown) ---

Expand All @@ -67,24 +58,6 @@ const localNoteModules = import.meta.glob('@/assets/content/release-notes/*.md',
eager: true
}) as Record<string, string>

function versionKey(v: string): number[] {
return v
.replace(/^v/, '')
.split('.')
.map((n) => parseInt(n, 10) || 0)
}

// Sort newest-first by semantic version (v4.5.10 after v4.5.2).
function compareVersionDesc(a: string, b: string): number {
const av = versionKey(a)
const bv = versionKey(b)
for (let i = 0; i < Math.max(av.length, bv.length); i++) {
const diff = (bv[i] ?? 0) - (av[i] ?? 0)
if (diff !== 0) return diff
}
return 0
}

const localReleaseNotes = computed<ReleaseNote[]>(() => {
const notes = Object.entries(localNoteModules).map(([path, raw]) => {
const version = path.split('/').pop()?.replace(/\.md$/, '') ?? ''
Expand All @@ -97,33 +70,38 @@ const localReleaseNotes = computed<ReleaseNote[]>(() => {
const bullet = body.match(/^\s*[-*]\s+(.+)$/m)
const snippet = bullet?.[1] ?? ''
const html = DOMPurify.sanitize(marked(body) as string)
return { title, date, url: DOCS.webClient.tags + version, snippet, html }
return {
title: 'Web Client - ' + title,
date,
url: DOCS.webClient.tags + version,
snippet,
html
}
})
return notes.sort((a, b) => compareVersionDesc(a.title, b.title) || b.date.localeCompare(a.date))
return notes.sort((a, b) => b.date.localeCompare(a.date))
})

const currentNotes = computed<ReleaseNote[]>(() => {
if (releaseMode.value === 'local') return localReleaseNotes.value
if (releaseMode.value === 'remote') return releaseNotes.value
return []
if (!showReleaseNotes.value) return []
const combined = [...releaseNotes.value, ...localReleaseNotes.value]
return combined.sort((a, b) => b.date.localeCompare(a.date))
})

const externalLink = computed(() => {
if (!selectedRelease.value) return ''
return releaseMode.value === 'local'
return selectedRelease.value.html
? selectedRelease.value.url
: RELEASE_NOTES_BASE_URL + selectedRelease.value.url
})

const externalLinkLabel = computed(() =>
releaseMode.value === 'local' ? 'View on GitHub ↗' : 'View on docs site ↗'
selectedRelease.value?.html ? 'View on GitHub ↗' : 'View on docs site ↗'
)

function openRelease(note: ReleaseNote) {
if (releaseMode.value === 'local') {
// Local notes are already rendered — no network fetch needed.
if (note.html) {
selectedRelease.value = note
releaseHtml.value = note.html ?? ''
releaseHtml.value = note.html
} else {
void fetchRelease(note)
}
Expand All @@ -146,7 +124,7 @@ async function fetchReleaseNotesIndex() {
// Matches "Release v5.4.x", "Release v5.4.1", "Web Client Release v4.5.0", etc.
const match = text.match(/^(?:Web Client )?Release v[\d.x]+$/)
if (match) {
notes.push({ date: '', title: text, url: href })
notes.push({ date: '', title: 'Server - ' + text, url: href })
}
})
releaseNotes.value = notes
Expand Down Expand Up @@ -237,12 +215,10 @@ onMounted(() => {
})

watch(selectedTab, (tab) => {
// Reset the detail view whenever the tab changes; only the remote source needs
// an on-switch fetch (local notes are bundled and rendered eagerly).
selectedRelease.value = null
releaseHtml.value = ''
releaseError.value = ''
if (tab === 'SlideRule Releases') {
if (tab === 'Release Notes') {
void fetchReleaseNotesIndex()
}
})
Expand All @@ -261,7 +237,7 @@ watch(selectedTab, (tab) => {
</div>
</div>
<!-- About / Contact -->
<div v-if="releaseMode === null" ref="panelRef" class="sr-landing-panel">
<div v-if="!showReleaseNotes" ref="panelRef" class="sr-landing-panel">
<div v-if="panelHtml" class="sr-landing-panel-content">
<div v-html="panelHtml" />
</div>
Expand All @@ -270,7 +246,7 @@ watch(selectedTab, (tab) => {
</div>
</div>

<!-- Release Notes (SlideRule platform = remote, Web Client = local) -->
<!-- Release Notes (combined: server + web client) -->
<div v-else class="sr-landing-panel">
<div v-if="releaseLoading" class="sr-news-status">Loading...</div>
<div v-else-if="releaseError" class="sr-news-status sr-news-error">{{ releaseError }}</div>
Expand Down
Loading