-
Notifications
You must be signed in to change notification settings - Fork 289
Add monthly newsletter page #4082
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
ewels
wants to merge
57
commits into
main
Choose a base branch
from
claude/nf-core-newsletter-page-XyTgs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
540d36a
Add monthly newsletter page
claude e3ded22
[automated] Fix code linting
nf-core-bot 20c609b
Improve newsletter page: reorder sections, add logo, fix dark mode
claude c877a3f
Add dark mode support with logo swap and CSS classes
claude 38d4259
Replace newsletter logos with correct versions from oldsite
ewels 855475b
Fix dark mode to use site's data-bs-theme instead of prefers-color-sc…
ewels 4a8da60
Fix dark mode: add !important and :global() for scoped class overrides
ewels 3469787
Deduplicate pipeline releases by grouping per pipeline
ewels efd2584
Improve proposals section: categorize, show status, strip prefixes
ewels 816d975
Widen newsletter layout, add 2-column grid, blog images, require head…
ewels f9aed6b
Reorder newsletter sections, fix missing headerImage in 3 blog posts
ewels 02eebcc
Add optional headerImage to events, show in newsletter
ewels 0f8003b
Only show event image in newsletter when headerImage is set
ewels f14882b
Fix local asset images in newsletter by resolving through Astro glob
ewels 2d88c25
Fix asset glob path depth for newsletter page location
ewels 3beb562
Remove baseUrl fallback for local asset images
ewels 5722685
Fix async image resolution: pre-resolve URLs in frontmatter
ewels 70c2fb5
Rename "Events This Month" to clarify they are past events
ewels 2cf1420
Tone down dark mode green: make links light, keep green only where in…
ewels bb25bfa
Fix section headings, use Image component, 2-col upcoming events
ewels 8ef0b0f
Fix image glob: use Vite root-relative /src/assets/** path
ewels c5d24f6
Only show proposals closed as 'completed', not 'not_planned'
ewels d73661f
Increase newsletter image resolution for retina displays
ewels 8f4d3f5
Remove max-height from upcoming event images
ewels 281cc0a
Move blog/event images to right column instead of full-width banner
ewels ea130ee
Sentence case headings and increase heading font sizes
ewels 9246d10
Add small thumbnail images to 'In case you missed it' blog posts
ewels 181ca5b
Add prev/next navigation below newsletter content
ewels 5940a34
Enforce landscape aspect ratio on all newsletter blog/event images
ewels 2cc703e
Add email preview text and 'view in browser' link
ewels b3cdfbd
Style preview text: centre-aligned, very muted, simpler link
ewels 8c051d8
Restructure newsletter as retrospective: content from previous month
ewels 8ff4c15
Fix newsletter wording for retrospective tone, show '1st Month Year' …
ewels 8a64bfc
Show event subtitle in upcoming events section of newsletter
ewels 9cdf329
Add month select dropdown to bottom navigation of newsletter
ewels fc2295a
Keep natural aspect ratio for event images, only force landscape on b…
ewels b7701b0
Rearrange upcoming events layout: tag + title with date right-aligned…
ewels 80b0e16
Move event image below subtitle, keep date right-aligned across full …
ewels 3757fc7
Fix dark mode dividers for upcoming events and footer separator
ewels d00655e
Fix footer divider: use border-bottom on td for dark mode compatibility
ewels 6d75b4f
Add horizontal spacing between 2-column items in newsletter
ewels 65d54b1
Add newsletter features: advisories, RSS feed, email HTML, nav link
ewels 6bdcee1
Fix RSS feed imports: use path aliases instead of relative paths
ewels 8f74934
Fix: move pipelines const inside getStaticPaths for build compatibility
ewels 88611a1
Add older advisories to 'In case you missed it', remove subheadings
ewels 0bad5d9
Improve email HTML compatibility
ewels d88ce5b
Use fluid hybrid layout for Gmail-compatible responsive 2-column
ewels 4ee31c8
Replace <Image> with plain <img> for consistent absolute URLs
ewels 4b16f3b
Fix esbuild parse error: pre-compute advisory styles and URLs
ewels debfd7e
Use div-based fluid hybrid layout for responsive 2-column
ewels 8fa118a
Fix dark mode dividers for 2-column div-based layout
ewels ca7a589
Fix image link accessibility and missing height attributes
ewels e13cdd0
Add gap between 2-column items
ewels 1b0e3b7
Fix responsive stacking on mobile: use max-width + display block
ewels bc8c78a
Fix 2-column gap: use box-sizing border-box with padding-right
ewels b452175
Make blog post layout responsive: stack image below text on mobile
ewels a34c3d9
Fix blog post responsive layout: stack image below text on mobile
ewels File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+72.1 KB
sites/main-site/src/assets/images/logo/nf-core-newsletter-lightbg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
788 changes: 788 additions & 0 deletions
788
sites/main-site/src/components/newsletter/NewsletterContent.astro
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
sites/main-site/src/content/blog/2026/configs-strict-syntax.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
153 changes: 153 additions & 0 deletions
153
sites/main-site/src/pages/newsletter/[year]/[month].astro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| --- | ||
| import PageLayout from "@layouts/PageLayout.astro"; | ||
| import { getCollection } from "astro:content"; | ||
| import pipelines_json from "@public/pipelines.json"; | ||
| import { getNewsletterStaticPathsData, getNewsletterContentData, getMonthName } from "@utils/newsletter"; | ||
| import type { NewsletterMonth } from "@utils/newsletter"; | ||
| import NewsletterContent from "@components/newsletter/NewsletterContent.astro"; | ||
|
|
||
| const images = import.meta.glob("/src/assets/**"); | ||
| export async function getStaticPaths() { | ||
| const pipelines = pipelines_json.remote_workflows; | ||
| const { months, allProposals } = await getNewsletterStaticPathsData(getCollection, pipelines); | ||
|
|
||
| return months.map(({ year, month }, index) => ({ | ||
| params: { | ||
| year: String(year), | ||
| month: String(month).padStart(2, "0"), | ||
| }, | ||
| props: { | ||
| year, | ||
| month, | ||
| allMonths: months, | ||
| allProposals, | ||
| prevMonth: months[index + 1] || null, | ||
| nextMonth: months[index - 1] || null, | ||
| }, | ||
| })); | ||
| } | ||
|
|
||
| const { year, month, allMonths, allProposals, prevMonth, nextMonth } = Astro.props as { | ||
| year: number; | ||
| month: number; | ||
| allMonths: NewsletterMonth[]; | ||
| allProposals: any[]; | ||
| prevMonth: NewsletterMonth | null; | ||
| nextMonth: NewsletterMonth | null; | ||
| }; | ||
|
|
||
| const pipelines = pipelines_json.remote_workflows; | ||
| const contentData = await getNewsletterContentData(getCollection, pipelines, year, month, allProposals, images); | ||
|
|
||
| function monthUrl(m: NewsletterMonth): string { | ||
| return `/newsletter/${m.year}/${String(m.month).padStart(2, "0")}`; | ||
| } | ||
| --- | ||
|
|
||
| <PageLayout | ||
| title={`Newsletter - ${contentData.monthName} ${year}`} | ||
| subtitle={`nf-core community newsletter for ${contentData.monthName} ${year}`} | ||
| mainpage_container={true} | ||
| subfooter={false} | ||
| > | ||
| {/* ===== NAVIGATION (excluded from email) ===== */} | ||
| <nav class="mb-4 newsletter-nav"> | ||
| <div class="d-flex justify-content-between align-items-center flex-wrap gap-2"> | ||
| { | ||
| prevMonth ? ( | ||
| <a href={monthUrl(prevMonth)} class="btn btn-outline-secondary btn-sm"> | ||
| ← {getMonthName(prevMonth.month)} {prevMonth.year} | ||
| </a> | ||
| ) : ( | ||
| <span /> | ||
| ) | ||
| } | ||
|
|
||
| <div class="d-flex align-items-center gap-2"> | ||
| <a href="/newsletter/rss.xml" class="btn btn-outline-secondary btn-sm" title="RSS Feed"> | ||
| <i class="fa-solid fa-rss"></i> | ||
| </a> | ||
| <a | ||
| href={`${monthUrl({ year, month })}/email`} | ||
| class="btn btn-outline-secondary btn-sm" | ||
| title="Email HTML version" | ||
| > | ||
| <i class="fa-solid fa-envelope"></i> | ||
| </a> | ||
| <select class="newsletter-month-select form-select form-select-sm w-auto"> | ||
| { | ||
| allMonths.map((m) => ( | ||
| <option value={monthUrl(m)} selected={m.year === year && m.month === month}> | ||
| {getMonthName(m.month)} {m.year} | ||
| </option> | ||
| )) | ||
| } | ||
| </select> | ||
| </div> | ||
|
|
||
| { | ||
| nextMonth ? ( | ||
| <a href={monthUrl(nextMonth)} class="btn btn-outline-secondary btn-sm"> | ||
| {getMonthName(nextMonth.month)} {nextMonth.year} → | ||
| </a> | ||
| ) : ( | ||
| <span /> | ||
| ) | ||
| } | ||
| </div> | ||
| </nav> | ||
|
|
||
| {/* ===== NEWSLETTER CONTENT ===== */} | ||
| <NewsletterContent data={contentData} /> | ||
|
|
||
| {/* ===== BOTTOM NAVIGATION (excluded from email) ===== */} | ||
| <hr class="my-4" /> | ||
| <nav class="mb-4"> | ||
| <div class="d-flex justify-content-between align-items-center flex-wrap gap-2"> | ||
| { | ||
| prevMonth ? ( | ||
| <a href={monthUrl(prevMonth)} class="btn btn-outline-secondary btn-sm"> | ||
| ← {getMonthName(prevMonth.month)} {prevMonth.year} | ||
| </a> | ||
| ) : ( | ||
| <span /> | ||
| ) | ||
| } | ||
|
|
||
| <select class="newsletter-month-select form-select form-select-sm w-auto"> | ||
| { | ||
| allMonths.map((m) => ( | ||
| <option value={monthUrl(m)} selected={m.year === year && m.month === month}> | ||
| {getMonthName(m.month)} {m.year} | ||
| </option> | ||
| )) | ||
| } | ||
| </select> | ||
|
|
||
| { | ||
| nextMonth ? ( | ||
| <a href={monthUrl(nextMonth)} class="btn btn-outline-secondary btn-sm"> | ||
| {getMonthName(nextMonth.month)} {nextMonth.year} → | ||
| </a> | ||
| ) : ( | ||
| <span /> | ||
| ) | ||
| } | ||
| </div> | ||
| </nav> | ||
| </PageLayout> | ||
|
|
||
| <script> | ||
| document.querySelectorAll<HTMLSelectElement>(".newsletter-month-select").forEach((select) => { | ||
| select.addEventListener("change", () => { | ||
| window.location.href = select.value; | ||
| }); | ||
| }); | ||
| </script> | ||
|
|
||
| <style> | ||
| .newsletter-nav { | ||
| border-bottom: 1px solid var(--bs-border-color); | ||
| padding-bottom: 1rem; | ||
| } | ||
| </style> |
68 changes: 68 additions & 0 deletions
68
sites/main-site/src/pages/newsletter/[year]/[month]/email.astro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| --- | ||
| import { getCollection } from "astro:content"; | ||
| import pipelines_json from "@public/pipelines.json"; | ||
| import { getNewsletterStaticPathsData, getNewsletterContentData, getMonthName } from "@utils/newsletter"; | ||
| import NewsletterContent from "@components/newsletter/NewsletterContent.astro"; | ||
|
|
||
| const images = import.meta.glob("/src/assets/**"); | ||
|
|
||
| export async function getStaticPaths() { | ||
| const pipelines = pipelines_json.remote_workflows; | ||
| const { months, allProposals } = await getNewsletterStaticPathsData(getCollection, pipelines); | ||
|
|
||
| return months.map(({ year, month }) => ({ | ||
| params: { | ||
| year: String(year), | ||
| month: String(month).padStart(2, "0"), | ||
| }, | ||
| props: { | ||
| year, | ||
| month, | ||
| allProposals, | ||
| }, | ||
| })); | ||
| } | ||
|
|
||
| const { year, month, allProposals } = Astro.props as { | ||
| year: number; | ||
| month: number; | ||
| allProposals: any[]; | ||
| }; | ||
|
|
||
| const pipelines = pipelines_json.remote_workflows; | ||
| const monthName = getMonthName(month); | ||
| const contentData = await getNewsletterContentData(getCollection, pipelines, year, month, allProposals, images); | ||
| --- | ||
|
|
||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>nf-core Newsletter - {monthName} {year}</title> | ||
| <style> | ||
| body { | ||
| margin: 0; | ||
| padding: 20px; | ||
| background-color: #f5f5f5; | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||
| } | ||
| @media (prefers-color-scheme: dark) { | ||
| body { | ||
| background-color: #121212; | ||
| } | ||
| } | ||
| @media screen and (max-width: 600px) { | ||
| #newsletter-content .nl-col, | ||
| #newsletter-content .nl-blog-text, | ||
| #newsletter-content .nl-blog-img { | ||
| max-width: 100% !important; | ||
| display: block !important; | ||
| } | ||
| } | ||
| </style> | ||
| </head> | ||
| <body style="margin: 0; padding: 0;"> | ||
| <NewsletterContent data={contentData} /> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| --- | ||
| import PageLayout from "@layouts/PageLayout.astro"; | ||
| import { getCollection } from "astro:content"; | ||
| import pipelines_json from "@public/pipelines.json"; | ||
| import { getNewsletterMonths, getMonthName } from "@utils/newsletter"; | ||
|
|
||
| let blogPosts = await getCollection("blog"); | ||
| blogPosts = blogPosts.filter((post) => new Date(post.data.pubDate) < new Date()); | ||
|
|
||
| let events = await getCollection("events"); | ||
| events = events.filter((e) => e.id.split("/").length === 2); | ||
|
|
||
| const pipelines = pipelines_json.remote_workflows; | ||
| const advisories = await getCollection("advisories"); | ||
| const months = getNewsletterMonths(blogPosts, events, pipelines, advisories); | ||
|
|
||
| // Group by year | ||
| const byYear = new Map<number, { year: number; month: number }[]>(); | ||
| for (const m of months) { | ||
| if (!byYear.has(m.year)) byYear.set(m.year, []); | ||
| byYear.get(m.year)!.push(m); | ||
| } | ||
| const years = [...byYear.keys()].sort((a, b) => b - a); | ||
|
|
||
| // Latest newsletter for redirect | ||
| const latest = months[0]; | ||
| --- | ||
|
|
||
| <PageLayout | ||
| title="nf-core Newsletter" | ||
| subtitle="Monthly community newsletters from nf-core" | ||
| mainpage_container={true} | ||
| subfooter={false} | ||
| > | ||
| { | ||
| latest && ( | ||
| <div class="alert alert-success mb-4"> | ||
| <strong>Latest newsletter:</strong> | ||
| <a href={`/newsletter/${latest.year}/${String(latest.month).padStart(2, "0")}`}> | ||
| {getMonthName(latest.month)} {latest.year} | ||
| </a> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| { | ||
| years.map((year) => ( | ||
| <div class="mb-4"> | ||
| <h2>{year}</h2> | ||
| <div class="list-group"> | ||
| {byYear.get(year)!.map((m) => ( | ||
| <a | ||
| href={`/newsletter/${m.year}/${String(m.month).padStart(2, "0")}`} | ||
| class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" | ||
| > | ||
| {getMonthName(m.month)} {m.year} | ||
| <span class="badge bg-success rounded-pill">→</span> | ||
| </a> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| )) | ||
| } | ||
| </PageLayout> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the alt text is not describing what is in the photo...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll let you know when I've reviewed my own code 😅
I haven't read it at all yet - just trying to get the end result to roughly the right place first before going back, simplifying, reviewing and getting ready for review/merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, sorry, I know, I will stop looking at this draft 🙂. I just got confused while looking at the deploy preview 😀