diff --git a/apps/website/public/blog/iptvnator-v18.jpeg b/apps/website/public/blog/iptvnator-v18.jpeg new file mode 100644 index 000000000..52d2da128 Binary files /dev/null and b/apps/website/public/blog/iptvnator-v18.jpeg differ diff --git a/apps/website/public/blog/macos-app-damaged.jpg b/apps/website/public/blog/macos-app-damaged.jpg new file mode 100644 index 000000000..6710a53ff Binary files /dev/null and b/apps/website/public/blog/macos-app-damaged.jpg differ diff --git a/apps/website/public/iptvnator/blog/epg.png b/apps/website/public/iptvnator/blog/epg.png deleted file mode 100644 index 5c7a857f3..000000000 Binary files a/apps/website/public/iptvnator/blog/epg.png and /dev/null differ diff --git a/apps/website/public/iptvnator/blog/player.png b/apps/website/public/iptvnator/blog/player.png deleted file mode 100644 index fa953d889..000000000 Binary files a/apps/website/public/iptvnator/blog/player.png and /dev/null differ diff --git a/apps/website/src/components/blog/Alert.astro b/apps/website/src/components/blog/Alert.astro new file mode 100644 index 000000000..4d5b5dae5 --- /dev/null +++ b/apps/website/src/components/blog/Alert.astro @@ -0,0 +1,44 @@ +--- +interface Props { + type?: 'info' | 'success' | 'warning'; + title?: string; +} + +const { type = 'info', title } = Astro.props; + +const styles = { + info: { + wrapper: 'border-accent-500/35 bg-accent-500/[0.07]', + icon: 'text-accent-300', + title: 'text-accent-200', + }, + success: { + wrapper: 'border-emerald-500/35 bg-emerald-500/[0.08]', + icon: 'text-emerald-300', + title: 'text-emerald-200', + }, + warning: { + wrapper: 'border-amber-500/35 bg-amber-500/[0.09]', + icon: 'text-amber-300', + title: 'text-amber-200', + }, +} as const; + +const variant = styles[type]; +--- + +
+
+ +
+ {title &&

{title}

} +
+ +
+
+
+
diff --git a/apps/website/src/components/blog/ChangeTable.astro b/apps/website/src/components/blog/ChangeTable.astro new file mode 100644 index 000000000..3fefac387 --- /dev/null +++ b/apps/website/src/components/blog/ChangeTable.astro @@ -0,0 +1,34 @@ +--- +interface ChangeRow { + area: string; + change: string; + impact?: string; +} + +interface Props { + rows: ChangeRow[]; +} + +const { rows = [] } = Astro.props; +--- + +
+ + + + + + + + + + {rows.map((row) => ( + + + + + + ))} + +
AreaChangeImpact
{row.area}{row.change}{row.impact ?? 'โ€”'}
+
diff --git a/apps/website/src/components/blog/CopyCommand.astro b/apps/website/src/components/blog/CopyCommand.astro new file mode 100644 index 000000000..e6f96f0f8 --- /dev/null +++ b/apps/website/src/components/blog/CopyCommand.astro @@ -0,0 +1,43 @@ +--- +interface Props { + command: string; + title?: string; +} + +const { command, title = 'Command' } = Astro.props; +--- + +
+
+ {title} + +
+ {command} +
+ + diff --git a/apps/website/src/components/blog/FaqAccordion.astro b/apps/website/src/components/blog/FaqAccordion.astro new file mode 100644 index 000000000..c10ac07ac --- /dev/null +++ b/apps/website/src/components/blog/FaqAccordion.astro @@ -0,0 +1,26 @@ +--- +interface FaqItem { + q: string; + a: string; +} + +interface Props { + items: FaqItem[]; +} + +const { items = [] } = Astro.props; +--- + +
+ {items.map((item) => ( +
+ + + {item.q} + + + + +

{item.a}

+
+ ))} +
diff --git a/apps/website/src/components/blog/IconFeatureGrid.astro b/apps/website/src/components/blog/IconFeatureGrid.astro new file mode 100644 index 000000000..fbbab3687 --- /dev/null +++ b/apps/website/src/components/blog/IconFeatureGrid.astro @@ -0,0 +1,37 @@ +--- +interface FeatureItem { + title: string; + text: string; + icon?: 'spark' | 'shield' | 'speed' | 'playlist' | 'epg'; +} + +interface Props { + items: FeatureItem[]; +} + +const { items = [] } = Astro.props; + +const iconPaths = { + spark: 'M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z', + shield: 'M12 2l7 4v6c0 5-3.4 9.7-7 10-3.6-.3-7-5-7-10V6l7-4z', + speed: 'M12 3v2m6.364.636l-1.414 1.414M21 12h-2m-.636 6.364l-1.414-1.414M12 21v-2m-6.364-.636l1.414-1.414M3 12h2m.636-6.364l1.414 1.414M15 12l-4 4', + playlist: 'M4 6h14M4 10h14M4 14h10M4 18h10M18 16l3 2-3 2v-4z', + epg: 'M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z', +} as const; +--- + +
+ {items.map((item) => ( +
+
+ + + + + +

{item.title}

+
+

{item.text}

+
+ ))} +
diff --git a/apps/website/src/components/blog/ImageGallery.astro b/apps/website/src/components/blog/ImageGallery.astro new file mode 100644 index 000000000..753420438 --- /dev/null +++ b/apps/website/src/components/blog/ImageGallery.astro @@ -0,0 +1,99 @@ +--- +interface GalleryImage { + src: string; + alt: string; + caption?: string; +} + +interface Props { + images: GalleryImage[]; + columns?: 2 | 3 | 4; +} + +const { images = [], columns = 3 } = Astro.props; + +const colClass = { + 2: 'sm:grid-cols-2', + 3: 'sm:grid-cols-2 lg:grid-cols-3', + 4: 'sm:grid-cols-2 lg:grid-cols-4', +} as const; +--- + +
+
+ {images.map((img, idx) => ( +
+ + {img.caption &&
{img.caption}
} +
+ ))} +
+
+ + + + diff --git a/apps/website/src/components/blog/LinkCards.astro b/apps/website/src/components/blog/LinkCards.astro new file mode 100644 index 000000000..632e80f00 --- /dev/null +++ b/apps/website/src/components/blog/LinkCards.astro @@ -0,0 +1,51 @@ +--- +interface LinkItem { + label: string; + href: string; + hint?: string; + icon?: 'github' | 'telegram' | 'sponsor' | 'kofi' | 'download' | 'docs'; +} + +interface Props { + links: LinkItem[]; +} + +const { links = [] } = Astro.props; + +const iconPaths = { + github: + 'M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z', + telegram: + 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69.01-.03.01-.14-.07-.2-.08-.06-.19-.04-.27-.02-.12.03-1.99 1.27-5.62 3.72-.53.36-1.01.54-1.44.53-.47-.01-1.38-.27-2.06-.49-.83-.27-1.49-.42-1.43-.88.03-.24.37-.49 1.02-.74 3.99-1.74 6.65-2.89 7.99-3.44 3.81-1.58 4.6-1.86 5.12-1.87.11 0 .37.03.54.17.14.12.18.28.2.45-.01.06.01.24 0 .38z', + sponsor: + 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z', + kofi: 'M4 5h12a4 4 0 0 1 0 8h-1v2.5A4.5 4.5 0 0 1 10.5 20h-1A4.5 4.5 0 0 1 5 15.5V13H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2Zm11 6h1a2 2 0 0 0 0-4h-1v4Z', + download: 'M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4', + docs: 'M12 6.253v13m0-13C10.832 5.483 9.246 5 7.5 5 5.205 5 3.182 5.836 2 7.117v11.41C3.182 17.247 5.205 16.41 7.5 16.41c1.746 0 3.332.484 4.5 1.254m0-11.41C13.168 5.483 14.754 5 16.5 5c2.295 0 4.318.836 5.5 2.117v11.41C20.818 17.247 18.795 16.41 16.5 16.41c-1.746 0-3.332.484-4.5 1.254', +} as const; +--- + +
+ {links.map((item) => ( + +
+ + + {item.icon === 'download' || item.icon === 'docs' ? ( + + ) : ( + + )} + + + {item.label} +
+ {item.hint &&

{item.hint}

} +
+ ))} +
diff --git a/apps/website/src/components/blog/PostButton.astro b/apps/website/src/components/blog/PostButton.astro new file mode 100644 index 000000000..80fa35d90 --- /dev/null +++ b/apps/website/src/components/blog/PostButton.astro @@ -0,0 +1,21 @@ +--- +interface Props { + href: string; + label?: string; + external?: boolean; +} + +const { href, label, external = true } = Astro.props; +--- + + + {label ?? 'Open link'} + + + + diff --git a/apps/website/src/components/blog/ReleaseMeta.astro b/apps/website/src/components/blog/ReleaseMeta.astro new file mode 100644 index 000000000..5d94c45d5 --- /dev/null +++ b/apps/website/src/components/blog/ReleaseMeta.astro @@ -0,0 +1,26 @@ +--- +interface Props { + version: string; + releaseDate: string; + channels?: string[]; +} + +const { version, releaseDate, channels = [] } = Astro.props; +--- + +
+
+
+

Version

+

{version}

+
+
+

Release date

+

{releaseDate}

+
+
+

Channels

+

{channels.length ? channels.join(' ยท ') : 'N/A'}

+
+
+
diff --git a/apps/website/src/components/blog/Spoiler.astro b/apps/website/src/components/blog/Spoiler.astro new file mode 100644 index 000000000..c082cef9c --- /dev/null +++ b/apps/website/src/components/blog/Spoiler.astro @@ -0,0 +1,22 @@ +--- +interface Props { + title?: string; + open?: boolean; +} + +const { title = 'Show spoiler', open = false } = Astro.props; +--- + +
+ + + + + + {title} + + +
+ +
+
diff --git a/apps/website/src/components/blog/StatusPill.astro b/apps/website/src/components/blog/StatusPill.astro new file mode 100644 index 000000000..72b326427 --- /dev/null +++ b/apps/website/src/components/blog/StatusPill.astro @@ -0,0 +1,20 @@ +--- +interface Props { + type?: 'new' | 'fixed' | 'improved' | 'breaking' | 'beta'; + label?: string; +} + +const { type = 'new', label } = Astro.props; + +const styles = { + new: 'border-cyan-500/35 bg-cyan-500/[0.12] text-cyan-200', + fixed: 'border-emerald-500/35 bg-emerald-500/[0.12] text-emerald-200', + improved: 'border-accent-500/35 bg-accent-500/[0.12] text-accent-200', + breaking: 'border-rose-500/35 bg-rose-500/[0.12] text-rose-200', + beta: 'border-amber-500/35 bg-amber-500/[0.12] text-amber-200', +} as const; +--- + + + {label ?? type} + diff --git a/apps/website/src/components/blog/StepRail.astro b/apps/website/src/components/blog/StepRail.astro new file mode 100644 index 000000000..85ed3c0ab --- /dev/null +++ b/apps/website/src/components/blog/StepRail.astro @@ -0,0 +1,22 @@ +--- +interface Props { + steps: string[]; + title?: string; +} + +const { steps = [], title } = Astro.props; +--- + +
+ {title &&

{title}

} +
    + {steps.map((step, index) => ( +
  1. + + {index + 1} + +

    {step}

    +
  2. + ))} +
+
diff --git a/apps/website/src/components/blog/TerminalBlock.astro b/apps/website/src/components/blog/TerminalBlock.astro new file mode 100644 index 000000000..ea1dc471d --- /dev/null +++ b/apps/website/src/components/blog/TerminalBlock.astro @@ -0,0 +1,19 @@ +--- +interface Props { + title?: string; +} + +const { title = 'Terminal' } = Astro.props; +--- + +
+
+ + {title} +
+
+
diff --git a/apps/website/src/content.config.ts b/apps/website/src/content.config.ts index 372b3f84f..d9e8b0817 100644 --- a/apps/website/src/content.config.ts +++ b/apps/website/src/content.config.ts @@ -9,6 +9,7 @@ const blog = defineCollection({ pubDate: z.coerce.date(), updatedDate: z.coerce.date().optional(), author: z.string().default('4gray'), + featured: z.boolean().default(false), heroImage: z.string().optional(), tags: z.array(z.string()).default([]), draft: z.boolean().default(false), diff --git a/apps/website/src/content/blog/blog-components-demo.mdx b/apps/website/src/content/blog/blog-components-demo.mdx new file mode 100644 index 000000000..638ce22a8 --- /dev/null +++ b/apps/website/src/content/blog/blog-components-demo.mdx @@ -0,0 +1,207 @@ +--- +title: Blog Components Demo (MDX) +description: Reusable MDX components for IPTVnator blog posts including alerts, CTA buttons, and terminal command blocks. +pubDate: 2026-02-15 +author: 4gray +tags: +- blog +- mdx +- docs +draft: true +--- + +import Alert from '../../components/blog/Alert.astro'; +import PostButton from '../../components/blog/PostButton.astro'; +import TerminalBlock from '../../components/blog/TerminalBlock.astro'; +import StepRail from '../../components/blog/StepRail.astro'; +import LinkCards from '../../components/blog/LinkCards.astro'; +import IconFeatureGrid from '../../components/blog/IconFeatureGrid.astro'; +import ReleaseMeta from '../../components/blog/ReleaseMeta.astro'; +import StatusPill from '../../components/blog/StatusPill.astro'; +import ChangeTable from '../../components/blog/ChangeTable.astro'; +import CopyCommand from '../../components/blog/CopyCommand.astro'; +import ImageGallery from '../../components/blog/ImageGallery.astro'; +import Spoiler from '../../components/blog/Spoiler.astro'; +import FaqAccordion from '../../components/blog/FaqAccordion.astro'; + +This post demonstrates reusable UI components you can use in `.mdx` blog posts. + +## Alert panel + + + Use alerts to highlight important notes without breaking the reading flow. + + + + Back up playlists, credentials, and portal URLs before installing major updates. + + +## Release meta + + + +## Status pills + +
+ + + + + +
+ +## Terminal code block + + +$ xattr -c /Applications/IPTVnator.app + + +## Copy command + + + +## Button / CTA + + + + + +## Step rail + + + +## Link cards + + + +## Icon feature grid + + + +## Change table + + + +## Image gallery + + + +## Spoiler block + + + If launch issues persist, verify the app path and run `xattr -c` on the exact `.app` bundle location. + + +## FAQ accordion + + + +## When to use MDX + +- Use `.md` for plain posts +- Use `.mdx` when you want custom components, richer callouts, or advanced layout blocks diff --git a/apps/website/src/content/blog/epg-guide.md b/apps/website/src/content/blog/epg-guide.mdx similarity index 82% rename from apps/website/src/content/blog/epg-guide.md rename to apps/website/src/content/blog/epg-guide.mdx index eaf37a827..abfa216c3 100644 --- a/apps/website/src/content/blog/epg-guide.md +++ b/apps/website/src/content/blog/epg-guide.mdx @@ -1,7 +1,7 @@ --- title: Understanding the Electronic Program Guide description: Learn how IPTVnator uses XMLTV data to display current and upcoming programs alongside your live TV channels. -pubDate: 2026-02-15 +pubDate: 2026-02-14 author: 4gray heroImage: /iptvnator/blog/epg.png tags: @@ -10,12 +10,19 @@ tags: draft: false --- +import Alert from '../../components/blog/Alert.astro'; +import PostButton from '../../components/blog/PostButton.astro'; + One of the most useful features in IPTVnator is the built-in Electronic Program Guide (EPG). Here is how it works and how to set it up. ## What is EPG? The Electronic Program Guide displays a schedule of current and upcoming TV programs for your channels. IPTVnator supports EPG data in the **XMLTV format**, which is the standard used by most IPTV providers. + + Most providers expose EPG as `.xml` or compressed `.xml.gz` files. + + ## Setting up EPG To enable EPG in IPTVnator: @@ -41,8 +48,11 @@ IPTVnator matches EPG data to channels using the `tvg-id` attribute from your M3 ## Tips -- EPG files can be large โ€” IPTVnator caches parsed data in the database (Electron) +- EPG files can be large โ€” IPTVnator caches parsed data in the database (Electron) - You can add multiple EPG sources per playlist - The EPG automatically refreshes periodically to keep the schedule up to date -Check the [GitHub repository](https://github.com/4gray/iptvnator) for more information on supported EPG formats. + diff --git a/apps/website/src/content/blog/macos-arm-app-damaged-fix.mdx b/apps/website/src/content/blog/macos-arm-app-damaged-fix.mdx new file mode 100644 index 000000000..e4f77e75d --- /dev/null +++ b/apps/website/src/content/blog/macos-arm-app-damaged-fix.mdx @@ -0,0 +1,47 @@ +--- +title: macOS ARM Users - Fix for "App is Damaged" Error +description: If IPTVnator shows "app is damaged and can't be opened" on Apple Silicon Macs, run one Terminal command to remove quarantine and launch safely. +pubDate: 2026-02-15 +author: 4gray +heroImage: /iptvnator/blog/macos-app-damaged.jpg +tags: +- macos +- troubleshooting +- installation +draft: false +--- + +import Alert from '../../components/blog/Alert.astro'; +import PostButton from '../../components/blog/PostButton.astro'; +import TerminalBlock from '../../components/blog/TerminalBlock.astro'; + +If you are on macOS ARM (Apple Silicon) and IPTVnator shows **"app is damaged and can't be opened"**, use this quick fix. + +## TL;DR + + +$ xattr -c /Applications/IPTVnator.app + + +## Why this happens + +IPTVnator is currently not code-signed for macOS. After download, Gatekeeper adds a quarantine attribute, and macOS can block launch with a generic "damaged" message. + +To distribute a fully signed and notarized macOS app, Apple requires an active Apple Developer subscription (currently **$99/year**). As an open-source project, IPTVnator does not currently use that signing pipeline. + +## What the command does + +The command above removes the quarantine attribute from the app bundle: + +- It does **not** modify IPTVnator binaries +- It tells your Mac to trust the app you intentionally downloaded +- It is the standard local workaround for unsigned apps + + + Only run this command for apps you trust and downloaded from a trusted source. + + + diff --git a/apps/website/src/content/blog/v0-18-release-notes.mdx b/apps/website/src/content/blog/v0-18-release-notes.mdx new file mode 100644 index 000000000..26fd361e2 --- /dev/null +++ b/apps/website/src/content/blog/v0-18-release-notes.mdx @@ -0,0 +1,137 @@ +--- +title: v0.18 - Release Notes +description: IPTVnator v0.18 brings a new playlist switcher UI, large EPG improvements, Xtream and Stalker updates, and multiple UX fixes across the app. +featured: true +pubDate: 2026-01-14 +author: 4gray +heroImage: /iptvnator/blog/iptvnator-v18.jpeg +tags: +- release +- release-notes +- v0.18 +draft: false +--- + +import Alert from '../../components/blog/Alert.astro'; +import LinkCards from '../../components/blog/LinkCards.astro'; +import ReleaseMeta from '../../components/blog/ReleaseMeta.astro'; +import StatusPill from '../../components/blog/StatusPill.astro'; +import ChangeTable from '../../components/blog/ChangeTable.astro'; + +IPTVnator **v0.18** is out. + + + +## Support the project + +- **GitHub Sponsors**: [https://github.com/sponsors/4gray](https://github.com/sponsors/4gray) +- **Buy me a coffee**: [https://ko-fi.com/4gray](https://ko-fi.com/4gray) +- **USDT (TRC20)**: `TLz5rTS7QTxYGfRH1Pm6Hs9fewqstDM47c` + + + +## Summary + + + +## General + + + +- New playlist switcher UI: a unified and cleaner way to switch between playlists +- Global favorites moved to the header for quicker access +- Sort and filter options merged into a single header menu + +## M3U + + + +- Improved EPG download and parsing to better handle large EPG files +- Added a popup dialog showing EPG download progress +- Restored quality selector for the Video.js player + +## Xtream & Stalker + + + +- Improved look and feel of the details view for VODs and TV shows +- Fixed opening favorite items with embedded players + +## Xtream + + + +- Category management: hide/show categories +- Display item counts per category +- New separate view for recently added items +- Recently added items are shown when no category is selected +- Fixed empty categories issue +- Restored import progress counter + +## Stalker + + + +- Added support for playlists that require device IDs and signatures +- Some playlists are still not supported; more testing data is needed + + + Please back up your playlists, credentials, URLs, and other important data. + + +## Download + + diff --git a/apps/website/src/content/blog/welcome.md b/apps/website/src/content/blog/welcome.mdx similarity index 68% rename from apps/website/src/content/blog/welcome.md rename to apps/website/src/content/blog/welcome.mdx index aacc852e1..fb0a66b36 100644 --- a/apps/website/src/content/blog/welcome.md +++ b/apps/website/src/content/blog/welcome.mdx @@ -9,7 +9,14 @@ tags: draft: false --- -We are excited to launch the official IPTVnator website and developer blog! +import Alert from '../../components/blog/Alert.astro'; +import PostButton from '../../components/blog/PostButton.astro'; + +We are excited to launch the official IPTVnator website and developer blog. + + + You now have one place for releases, tutorials, troubleshooting guides, and project updates. + ## What is IPTVnator? @@ -26,4 +33,9 @@ This blog will be the home for: ## Get involved -IPTVnator is open source and we welcome contributions! Check out our [GitHub repository](https://github.com/4gray/iptvnator) to get started. +IPTVnator is open source and we welcome contributions. + + diff --git a/apps/website/src/pages/blog/[...slug].astro b/apps/website/src/pages/blog/[...slug].astro index a69a8c737..6e585a294 100644 --- a/apps/website/src/pages/blog/[...slug].astro +++ b/apps/website/src/pages/blog/[...slug].astro @@ -3,9 +3,10 @@ import { getCollection, render } from 'astro:content'; import BlogPost from '../../layouts/BlogPost.astro'; export async function getStaticPaths() { + const includeDrafts = import.meta.env.DEV || import.meta.env.PUBLIC_INCLUDE_DRAFTS === 'true'; const posts = await getCollection('blog'); return posts - .filter((post) => !post.data.draft) + .filter((post) => includeDrafts || !post.data.draft) .map((post) => ({ params: { slug: post.id }, props: post, diff --git a/apps/website/src/pages/blog/index.astro b/apps/website/src/pages/blog/index.astro index 9a354a7a8..2b2f03f7d 100644 --- a/apps/website/src/pages/blog/index.astro +++ b/apps/website/src/pages/blog/index.astro @@ -4,11 +4,14 @@ import BaseLayout from '../../layouts/BaseLayout.astro'; import FeaturedBlogPost from '../../components/FeaturedBlogPost.astro'; import BlogPostCard from '../../components/BlogPostCard.astro'; +const includeDrafts = import.meta.env.DEV || import.meta.env.PUBLIC_INCLUDE_DRAFTS === 'true'; + const posts = (await getCollection('blog')) - .filter((post) => !post.data.draft) + .filter((post) => includeDrafts || !post.data.draft) .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); -const [featuredPost, ...remainingPosts] = posts; +const featuredPost = posts.find((post) => post.data.featured) ?? posts[0]; +const remainingPosts = featuredPost ? posts.filter((post) => post.id !== featuredPost.id) : []; --- !post.data.draft) - .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()) - .slice(0, 3); +const includeDrafts = import.meta.env.DEV || import.meta.env.PUBLIC_INCLUDE_DRAFTS === 'true'; -const [featuredPost, ...otherPosts] = posts; +const allPosts = (await getCollection('blog')) + .filter((post) => includeDrafts || !post.data.draft) + .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); + +const featuredPost = allPosts.find((post) => post.data.featured) ?? allPosts[0]; +const otherPosts = featuredPost ? allPosts.filter((post) => post.id !== featuredPost.id).slice(0, 2) : []; +const posts = featuredPost ? [featuredPost, ...otherPosts] : allPosts.slice(0, 3); ---