From fc23ef4f5567712fecf1e50cb76901895f38c9a7 Mon Sep 17 00:00:00 2001 From: JP Swinski Date: Mon, 8 Jun 2026 12:34:57 +0000 Subject: [PATCH 1/2] updated landing page to pull release notes instead of articles --- web-client/src/utils/docLinks.ts | 6 +- web-client/src/views/LandingView.vue | 135 +++++++++++++++------------ 2 files changed, 76 insertions(+), 65 deletions(-) diff --git a/web-client/src/utils/docLinks.ts b/web-client/src/utils/docLinks.ts index 6e649cb2..ac57b1c6 100644 --- a/web-client/src/utils/docLinks.ts +++ b/web-client/src/utils/docLinks.ts @@ -38,8 +38,8 @@ export const DOCS = { gedi: `${DOCS_BASE}/api_reference/gedi.html#gedi`, earthdataCmr: `${DOCS_BASE}/api_reference/earthdata.html#cmr` }, - articles: { - index: `${DOCS_BASE}/user_guide/articles/articles.html`, - base: `${DOCS_BASE}/user_guide/articles/` + releaseNotes: { + index: `${DOCS_BASE}/developer_guide/release_notes/release_notes.html`, + base: `${DOCS_BASE}/developer_guide/release_notes/` } } as const diff --git a/web-client/src/views/LandingView.vue b/web-client/src/views/LandingView.vue index b55923bf..f019119c 100644 --- a/web-client/src/views/LandingView.vue +++ b/web-client/src/views/LandingView.vue @@ -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', 'News'] +const tabOptions = ['About', 'Contact', 'Release Notes'] const selectedTab = ref('About') const panelHtml = computed(() => { @@ -29,92 +29,98 @@ const panelHtml = computed(() => { } }) -// --- News --- +// --- Release Notes --- -const ARTICLES_INDEX_URL = DOCS.articles.index -const ARTICLES_BASE_URL = DOCS.articles.base +const RELEASE_NOTES_INDEX_URL = DOCS.releaseNotes.index +const RELEASE_NOTES_BASE_URL = DOCS.releaseNotes.base -interface NewsArticle { +interface ReleaseNote { title: string date: string url: string snippet?: string } -const newsArticles = ref([]) -const selectedArticle = ref(null) -const articleHtml = ref('') -const newsLoading = ref(false) -const newsError = ref('') +const releaseNotes = ref([]) +const selectedRelease = ref(null) +const releaseHtml = ref('') +const releaseLoading = ref(false) +const releaseError = ref('') -async function fetchNewsIndex() { - newsLoading.value = true - newsError.value = '' - newsArticles.value = [] +async function fetchReleaseNotesIndex() { + releaseLoading.value = true + releaseError.value = '' + releaseNotes.value = [] try { - const res = await fetch(ARTICLES_INDEX_URL) + const res = await fetch(RELEASE_NOTES_INDEX_URL) if (!res.ok) throw new Error(`HTTP ${res.status}`) const html = await res.text() const doc = new DOMParser().parseFromString(html, 'text/html') - const articles: NewsArticle[] = [] + const notes: ReleaseNote[] = [] doc.querySelectorAll('[role="main"] a[href]').forEach((el) => { const href = el.getAttribute('href') ?? '' if (!href.endsWith('.html') || href.startsWith('http')) return + // Exclude Web Client release notes (hrefs like web-release-vXX-YY-ZZ.html) + if (href.startsWith('web-')) return const text = el.textContent?.trim() ?? '' - // Expected format: "YYYY-MM-DD: Title" - const match = text.match(/^(\d{4}-\d{2}-\d{2}):\s*(.+)$/) + // Expected format: "Release vX.Y.x" + const match = text.match(/^Release v([\d.]+(?:\.x)?)$/) if (match) { - articles.push({ date: match[1], title: match[2], url: href }) + notes.push({ date: '', title: text, url: href }) } }) - // Sort newest first - articles.sort((a, b) => b.date.localeCompare(a.date)) - newsArticles.value = articles - void fetchSnippets() + releaseNotes.value = notes + void fetchEntryDetails() } catch { - newsError.value = 'Failed to load articles. Please try again later.' + releaseError.value = 'Failed to load release notes. Please try again later.' } finally { - newsLoading.value = false + releaseLoading.value = false } } -async function fetchSnippets() { +async function fetchEntryDetails() { const MAX_SNIPPET_LENGTH = 200 + const DATE_RE = /\b(\d{4}-\d{2}-\d{2})\b/ await Promise.allSettled( - newsArticles.value.map(async (article, index) => { + releaseNotes.value.map(async (note, index) => { try { - const res = await fetch(ARTICLES_BASE_URL + article.url) + const res = await fetch(RELEASE_NOTES_BASE_URL + note.url) if (!res.ok) return const html = await res.text() const doc = new DOMParser().parseFromString(html, 'text/html') const main = doc.querySelector('[role="main"]') if (!main) return + let date = '' + let snippet = '' + const dateMatch = main.textContent?.match(DATE_RE) + if (dateMatch) date = dateMatch[1] const paragraphs = Array.from(main.querySelectorAll('p')) for (const p of paragraphs) { const text = p.textContent?.trim() - if (text && text.length > 10) { - newsArticles.value[index] = { - ...article, - snippet: - text.length > MAX_SNIPPET_LENGTH ? text.slice(0, MAX_SNIPPET_LENGTH) + '...' : text - } + if (text && text.length > 10 && !DATE_RE.test(text)) { + snippet = + text.length > MAX_SNIPPET_LENGTH ? text.slice(0, MAX_SNIPPET_LENGTH) + '...' : text break } } + releaseNotes.value[index] = { ...note, date, snippet } } catch { - // Snippet fetch failures are non-critical — just leave snippet empty + // Detail fetch failures are non-critical — leave date/snippet empty } }) ) + // Re-sort newest first once dates are populated; entries without a date + // sort to the end via empty-string compare. + releaseNotes.value = [...releaseNotes.value].sort((a, b) => b.date.localeCompare(a.date)) } -async function fetchArticle(article: NewsArticle) { - selectedArticle.value = article - articleHtml.value = '' - newsLoading.value = true - newsError.value = '' +async function fetchRelease(note: ReleaseNote) { + selectedRelease.value = note + releaseHtml.value = '' + releaseLoading.value = true + releaseError.value = '' try { - const res = await fetch(ARTICLES_BASE_URL + article.url) + const res = await fetch(RELEASE_NOTES_BASE_URL + note.url) if (!res.ok) throw new Error(`HTTP ${res.status}`) const html = await res.text() const doc = new DOMParser().parseFromString(html, 'text/html') @@ -123,15 +129,15 @@ async function fetchArticle(article: NewsArticle) { // Remove prev/next navigation links common in Sphinx main.querySelectorAll('.rst-footer-buttons, .footer, nav').forEach((n) => n.remove()) main.querySelectorAll('a.headerlink').forEach((n) => n.remove()) - articleHtml.value = DOMPurify.sanitize(main.innerHTML) + releaseHtml.value = DOMPurify.sanitize(main.innerHTML) } else { - articleHtml.value = '

Could not extract article content.

' + releaseHtml.value = '

Could not extract release notes content.

' } } catch { - newsError.value = 'Failed to load article. Please try again later.' - selectedArticle.value = null + releaseError.value = 'Failed to load release notes. Please try again later.' + selectedRelease.value = null } finally { - newsLoading.value = false + releaseLoading.value = false } } @@ -151,9 +157,9 @@ onMounted(() => { }) watch(selectedTab, (tab) => { - if (tab === 'News') { - selectedArticle.value = null - void fetchNewsIndex() + if (tab === 'Release Notes') { + selectedRelease.value = null + void fetchReleaseNotesIndex() } }) @@ -171,7 +177,7 @@ watch(selectedTab, (tab) => { -
+
@@ -180,23 +186,28 @@ watch(selectedTab, (tab) => {
- +
-
Loading...
-
{{ newsError }}
-
- -
- - View original article on docs site ↗ +
Loading...
+
{{ releaseError }}
+
+ +
    -
  • - {{ a.date }} +
  • + {{ r.date }}
    - {{ a.title }} - {{ a.snippet }} + {{ r.title }} + {{ r.snippet }}
From 7a16e4e27492ac578537a5c06c8bb531517c30f4 Mon Sep 17 00:00:00 2001 From: JP Swinski Date: Mon, 8 Jun 2026 13:30:42 +0000 Subject: [PATCH 2/2] landing page release both web client and server release notes --- web-client/src/views/LandingView.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web-client/src/views/LandingView.vue b/web-client/src/views/LandingView.vue index f019119c..d2c276b1 100644 --- a/web-client/src/views/LandingView.vue +++ b/web-client/src/views/LandingView.vue @@ -60,11 +60,9 @@ async function fetchReleaseNotesIndex() { doc.querySelectorAll('[role="main"] a[href]').forEach((el) => { const href = el.getAttribute('href') ?? '' if (!href.endsWith('.html') || href.startsWith('http')) return - // Exclude Web Client release notes (hrefs like web-release-vXX-YY-ZZ.html) - if (href.startsWith('web-')) return const text = el.textContent?.trim() ?? '' - // Expected format: "Release vX.Y.x" - const match = text.match(/^Release v([\d.]+(?:\.x)?)$/) + // 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 }) }