From e1e03c46718d7d6df68c4efe0d31b0360c9e33d3 Mon Sep 17 00:00:00 2001 From: Saloni Tarone Date: Sun, 16 Aug 2026 15:28:04 +0530 Subject: [PATCH 1/2] Add Downloads page with OS auto-detection, per-meeting minutes pages, and site-wide fixes - New /downloads page with per-OS sections that highlight and reorder to the detected platform (Windows/macOS/Linux) - Move community meeting minutes out of the modal popup into their own shareable pages, generated by a Docusaurus plugin - Fix a footer navigation bug where Downloads/Installation/Documentation links resolved relative to the current page instead of site root, 404ing from every non-homepage page - Fix 18 dead self-referential links in old blog posts pointing to retired URL patterns (podman.io/releases/*, podman.io/community/#*, podman.io/getting-started/*) - Fix InfoBanner gradient/background rendering, blog-card author link, CustomCard button/markdown rendering, and other component bugs - Redesign navbar, footer, Get Started page, and docs sidebar/TOC styling to match the site's Next.js reference design Signed-off-by: Saloni Tarone --- ...19-01-14-podman-machine-and-boot2podman.md | 4 +- blog/2019-08-14-new.md | 2 +- blog/2020-04-17-new.md | 2 +- blog/2020-08-10-podman-go-bindings.md | 4 +- blog/2020-10-05-new.md | 2 +- blog/2020-12-14-new.md | 2 +- ...endency-management-with-podman-and-tent.md | 2 +- ...-03-02-podman-support-for-older-distros.md | 2 +- blog/2021-04-02-new.md | 2 +- blog/2021-10-11-multiarch.md | 2 +- blog/2022-02-22-new.md | 2 +- blog/2022-05-09-new.md | 2 +- blog/2022-08-17-new.md | 2 +- blog/2022-10-22-new.md | 2 +- docs/index.md | 2 - docusaurus.config.js | 29 +- package.json | 3 +- .../content/BasicResourcesBox/data.ts | 40 +- .../content/BasicResourcesBox/index.tsx | 42 +-- .../content/BlogArticlesList/index.tsx | 36 +- .../content/ColoringBookSection/data.ts | 4 +- .../content/ColoringBookSection/index.tsx | 29 +- src/components/content/DateTimeBox/index.tsx | 100 +++-- .../content/DownloadsGrid/index.tsx | 100 +++++ .../content/FeaturesCarousel/data.ts | 56 ++- .../content/FeaturesCarousel/index.tsx | 82 ++-- .../content/TestimonialSection/index.tsx | 41 +- .../content/ThankYouSection/data.ts | 16 +- .../content/ThankYouSection/index.tsx | 77 +--- .../content/WelcomeBanner/index.tsx | 12 +- .../CommunityMeetingsCardGrid/index.tsx | 294 ++++++++------- .../CommunityMeetingsCardGrid/styles.css | 40 +- src/components/layout/HeroHeader/index.tsx | 161 ++++---- .../layout/HeroHeader/installOptions.ts | 10 +- src/components/layout/PageHeader/index.tsx | 91 +++-- src/components/layout/SectionHeader/index.tsx | 6 +- src/components/layout/SubcardGrid/index.tsx | 4 +- .../pages/MeetingNotePage/index.tsx | 55 +++ src/components/ui/ArticleCard/index.tsx | 123 +++--- src/components/ui/Card/index.tsx | 4 +- src/components/ui/CustomCard/index.tsx | 81 ++-- src/components/ui/InfoBanner/index.tsx | 34 +- src/components/ui/InfoBox/index.tsx | 15 +- src/components/ui/SmallCard/index.tsx | 17 +- src/components/ui/Testimonial/index.tsx | 68 +++- src/components/ui/ThumbCard/index.tsx | 2 +- src/components/ui/VideoEmbed/index.tsx | 17 + src/components/utilities/Button/index.tsx | 37 +- src/components/utilities/DropDown/index.tsx | 20 +- src/components/utilities/DropDown/styles.css | 2 +- .../utilities/DropdownButton/index.tsx | 23 +- src/components/utilities/IconLink/index.tsx | 42 ++- src/components/utilities/Link/index.tsx | 4 +- .../utilities/PlayOnScroll/index.tsx | 76 ++-- .../utilities/TerminalCard/index.tsx | 57 +++ src/css/main.css | 199 +++++++++- src/hooks/useBlogPosts.ts | 2 +- src/lib/detectOperatingSystem.ts | 23 ++ src/lib/meetings.js | 82 ++++ src/pages/community.tsx | 167 ++++---- src/pages/downloads.tsx | 18 + src/pages/features.tsx | 356 ++++++++++++------ src/pages/get-started.tsx | 219 ++++++----- src/pages/index.tsx | 27 +- src/plugins/meetingNotesPlugin.js | 32 ++ src/theme/Footer/index.tsx | 80 ++++ static/data/community.ts | 45 ++- static/data/features.ts | 16 +- static/data/get-started.ts | 2 +- static/data/global.ts | 6 +- static/data/home.ts | 22 +- .../data/meetings/notes/2026-08-04/index.md | 40 +- yarn.lock | 13 +- 73 files changed, 2170 insertions(+), 1163 deletions(-) create mode 100644 src/components/content/DownloadsGrid/index.tsx create mode 100644 src/components/pages/MeetingNotePage/index.tsx create mode 100644 src/components/ui/VideoEmbed/index.tsx create mode 100644 src/components/utilities/TerminalCard/index.tsx create mode 100644 src/lib/detectOperatingSystem.ts create mode 100644 src/lib/meetings.js create mode 100644 src/pages/downloads.tsx create mode 100644 src/plugins/meetingNotesPlugin.js create mode 100644 src/theme/Footer/index.tsx diff --git a/blog/2019-01-14-podman-machine-and-boot2podman.md b/blog/2019-01-14-podman-machine-and-boot2podman.md index a573a893d8..0274a642ef 100644 --- a/blog/2019-01-14-podman-machine-and-boot2podman.md +++ b/blog/2019-01-14-podman-machine-and-boot2podman.md @@ -14,11 +14,11 @@ tags: [boot2podman, podman+machine] ### Update: September 9, 2021 - Tom Sweeney -This post initially discussed the boot2podman/machine project, which Anders has since deprecated. Starting with Podman v3.3, the `podman machine` command now does that same function and is part of the Podman project. Please see Brent Baude's [update](https://podman.io/blogs/2021/09/06/podman-on-macs.html) or the [podman machine](https://docs.podman.io/en/latest/machine.html) man page on [docs.podman.io](https://docs.podman.io/) for more information on how to run Podman machine. The `podman-machine` command has been deprecated. +This post initially discussed the boot2podman/machine project, which Anders has since deprecated. Starting with Podman v3.3, the `podman machine` command now does that same function and is part of the Podman project. Please see Brent Baude's [update](/blogs/2021/09/06/podman-on-macs) or the [podman machine](https://docs.podman.io/en/latest/markdown/podman-machine.1.html) man page on [docs.podman.io](https://docs.podman.io/) for more information on how to run Podman machine. The `podman-machine` command has been deprecated. In addition, the Podman team is investigating the possibility of creating `Podman Desktop`. Please see the issue on [GitHub](https://github.com/containers/podman/issues/11494), and please add your comments or thoughts to that issue. -More updates are coming, and please keep your eye on the [Podman Mailing List](https://podman.io/community/#mailing-list) and [podman.io](https://podman.io) for further information and developments. +More updates are coming, and please keep your eye on the [Podman Mailing List](/community) and [podman.io](https://podman.io) for further information and developments. Finally, a very big thank you to Anders for his many contributions to Podman, particularly for his work in getting Podman to work smoothly on macOS. diff --git a/blog/2019-08-14-new.md b/blog/2019-08-14-new.md index e4031cce08..a14e0e16aa 100644 --- a/blog/2019-08-14-new.md +++ b/blog/2019-08-14-new.md @@ -4,4 +4,4 @@ title: Podman v1.5.0 Released categories: [new] --- -## [Podman has gone 1.5.0!](https://podman.io/releases/2019/08/14/podman-release-v1.5.0.html) +## [Podman has gone 1.5.0!](/release/2019/08/14/podman-release-v1.5.0) diff --git a/blog/2020-04-17-new.md b/blog/2020-04-17-new.md index 3d1042638f..1fdb7ba49c 100644 --- a/blog/2020-04-17-new.md +++ b/blog/2020-04-17-new.md @@ -4,4 +4,4 @@ title: Podman v1.9.0 Released categories: [new] --- -## [Podman has gone 1.9.0!](https://podman.io/releases/2020/04/17/podman-release-v1.9.0.html) +## [Podman has gone 1.9.0!](/release/2020/04/17/podman-release-v1.9.0) diff --git a/blog/2020-08-10-podman-go-bindings.md b/blog/2020-08-10-podman-go-bindings.md index 0fe2ef6045..70362fa134 100644 --- a/blog/2020-08-10-podman-go-bindings.md +++ b/blog/2020-08-10-podman-go-bindings.md @@ -522,7 +522,7 @@ containers and pods in a way which fits very nicely in many production environme ## References - Podman v2 is available for most major distributions along with MacOS and Windows. - Installation details are available on the [Podman official website](https://podman.io/getting-started/). + Installation details are available on the [Podman official website](/get-started). - Documentation can be found at the [Podman Docs page](https://docs.podman.io). It also includes a section on the [RESTful API](https://docs.podman.io/en/latest/Reference.html). @@ -530,7 +530,7 @@ containers and pods in a way which fits very nicely in many production environme ## Contribute - Any issues with the bindings can be [reported upstream](https://github.com/containers/podman/issues/new/choose). -- Check out the [Podman community page](https://podman.io/community/) for more ways to get in touch with the community. +- Check out the [Podman community page](/community) for more ways to get in touch with the community. ## Acknowledgments diff --git a/blog/2020-10-05-new.md b/blog/2020-10-05-new.md index f024b79f82..99ae5209d3 100644 --- a/blog/2020-10-05-new.md +++ b/blog/2020-10-05-new.md @@ -4,4 +4,4 @@ title: Podman v2.1.0 Released categories: [new] --- -## [Podman has gone 2.1.0!](https://podman.io/releases/2020/10/05/podman-release-v2.1.0.html) +## [Podman has gone 2.1.0!](/release/2020/10/05/podman-release-v2.1.0) diff --git a/blog/2020-12-14-new.md b/blog/2020-12-14-new.md index 0708939cf6..8b4e2832d7 100644 --- a/blog/2020-12-14-new.md +++ b/blog/2020-12-14-new.md @@ -4,4 +4,4 @@ title: Podman v2.2.0 Released categories: [new] --- -## [Podman has gone 2.2.0!](https://podman.io/releases/2020/12/14/podman-release-v2.2.0.html) +## [Podman has gone 2.2.0!](/release/2020/12/14/podman-release-v2.2.0) diff --git a/blog/2021-02-08-easy-development-dependency-management-with-podman-and-tent.md b/blog/2021-02-08-easy-development-dependency-management-with-podman-and-tent.md index 834c8a905e..dcc6ad4349 100644 --- a/blog/2021-02-08-easy-development-dependency-management-with-podman-and-tent.md +++ b/blog/2021-02-08-easy-development-dependency-management-with-podman-and-tent.md @@ -29,7 +29,7 @@ Tent is heavily inspired from [tighten/takeout](https://github.com/tighten/takeo ## Dependencies - Linux -- [Podman](https://podman.io/getting-started/installation) Installed +- [Podman](/get-started) Installed - Podman System Service Running If you have Podman installed, you can start the system service as follows: diff --git a/blog/2021-03-02-podman-support-for-older-distros.md b/blog/2021-03-02-podman-support-for-older-distros.md index 5e1bd213e9..63c0036ef5 100644 --- a/blog/2021-03-02-podman-support-for-older-distros.md +++ b/blog/2021-03-02-podman-support-for-older-distros.md @@ -12,7 +12,7 @@ tags: [containers, podman, distro, linux, centos, ubuntu, debian] ## By Lokesh Mandvekar [GitHub](https://github.com/lsm5) -The Podman Community [builds and supports packages](https://podman.io/getting-started/installation) +The Podman Community [builds and supports packages](/get-started) for a wide variety of Linux distributions and operating systems. These builds are provided in the public Open Build Service hosted by openSUSE. [These pre-built packages](https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/) diff --git a/blog/2021-04-02-new.md b/blog/2021-04-02-new.md index 54fec5bbe7..6df4012a96 100644 --- a/blog/2021-04-02-new.md +++ b/blog/2021-04-02-new.md @@ -4,4 +4,4 @@ title: Podman v3.1.0 Released categories: [new] --- -## [Podman has gone 3.1.0!](https://podman.io/releases/2021/04/02/podman-release-v3.1.0.html) +## [Podman has gone 3.1.0!](/release/2021/04/02/podman-release-v3.1.0) diff --git a/blog/2021-10-11-multiarch.md b/blog/2021-10-11-multiarch.md index 954c809546..0488879c41 100644 --- a/blog/2021-10-11-multiarch.md +++ b/blog/2021-10-11-multiarch.md @@ -188,4 +188,4 @@ is just a matter of practice (or some new bash scripts). Please also remember to pay attention to the tooling versions, as several bugs and deficiencies are present in earlier editions. On that same note, if you do encounter any strange or unexpected behavior, please reach out -to the [upstream community for assistance](https://podman.io/community/#slack-irc-matrix-and-discord). +to the [upstream community for assistance](/community). diff --git a/blog/2022-02-22-new.md b/blog/2022-02-22-new.md index c0ecbe7bf8..52b7e599e5 100644 --- a/blog/2022-02-22-new.md +++ b/blog/2022-02-22-new.md @@ -4,4 +4,4 @@ title: Podman v4.0.0 Released categories: [new] --- -## [Podman has gone 4.0.0!](https://podman.io/releases/2022/02/22/podman-release-v4.0.0.html) +## [Podman has gone 4.0.0!](/release/2022/02/22/podman-release-v4.0.0) diff --git a/blog/2022-05-09-new.md b/blog/2022-05-09-new.md index 220b5fc8a6..18eddeb08f 100644 --- a/blog/2022-05-09-new.md +++ b/blog/2022-05-09-new.md @@ -4,4 +4,4 @@ title: Podman v4.1.0 Released categories: [new] --- -## [Podman has gone 4.1.0!](https://podman.io/releases/2022/05/09/podman-release-v4.1.0.html) +## [Podman has gone 4.1.0!](/release/2022/05/09/podman-release-v4.1.0) diff --git a/blog/2022-08-17-new.md b/blog/2022-08-17-new.md index a26b55dd46..b19fdc7e02 100644 --- a/blog/2022-08-17-new.md +++ b/blog/2022-08-17-new.md @@ -4,4 +4,4 @@ title: Podman v4.2.0 Released categories: [new] --- -## [Podman has gone 4.2.0!](https://podman.io/releases/2022/08/17/podman-release-v4.2.0.html) +## [Podman has gone 4.2.0!](/release/2022/08/17/podman-release-v4.2.0) diff --git a/blog/2022-10-22-new.md b/blog/2022-10-22-new.md index 838bcdb830..bc081239bc 100644 --- a/blog/2022-10-22-new.md +++ b/blog/2022-10-22-new.md @@ -4,4 +4,4 @@ title: Podman v4.3.0 Released categories: [new] --- -## [Podman has gone 4.3.0!](https://podman.io/releases/2022/10/22/podman-release-v4.3.0.html) +## [Podman has gone 4.3.0!](/release/2022/10/22/podman-release-v4.3.0) diff --git a/docs/index.md b/docs/index.md index afc8c391fc..6850888bba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,8 +5,6 @@ title: Getting Started with Podman ![Podman logo](/logos/optimized/podman-logo-orig-884w-293h.webp#gh-light-mode-only)![Podman logo](/logos/optimized/podman-logo-dark-884w-293h.webp#gh-dark-mode-only) -# Getting Started with Podman - Podman is a utility provided as part of the libpod library. It can be used to create and maintain containers. The following tutorial will teach you how to set up Podman and perform some basic commands. diff --git a/docusaurus.config.js b/docusaurus.config.js index 402180af5e..450529c8b8 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -23,6 +23,7 @@ const config = { }, plugins: [ '@docusaurus/theme-live-codeblock', + require.resolve('./src/plugins/meetingNotesPlugin.js'), async function tailwindPlugin() { return { name: 'docusaurus-tailwindcss', @@ -33,7 +34,8 @@ const config = { }, }; }, - ['@docusaurus/plugin-content-blog', + [ + '@docusaurus/plugin-content-blog', { showReadingTime: true, routeBasePath: 'release', @@ -75,10 +77,15 @@ const config = { logo: { alt: 'Podman Logo', src: 'logos/optimized/podman-3-logo-266w-253h.webp', + // Docusaurus's ThemedImage hides the light-only image outright in + // dark mode unless a dark variant is also given — same artwork + // works fine on both, so just point srcDark at the same file. + srcDark: 'logos/optimized/podman-3-logo-266w-253h.webp', }, items: [ { to: 'features', label: 'Features', position: 'right' }, { to: 'get-started', label: 'Get Started', position: 'right' }, + { to: 'downloads', label: 'Downloads', position: 'right' }, { to: 'community', label: 'Community', position: 'right' }, { to: 'https://blog.podman.io', @@ -108,16 +115,20 @@ const config = { title: 'Docs', items: [ { - label: 'Installation Instructions', - to: 'docs/installation', + label: 'Downloads', + to: '/downloads', + }, + { + label: 'Installation', + to: '/docs/installation', }, { label: 'Documentation', - to: 'docs/', + to: '/docs/', }, { - label: 'Podman CLI Commands', + label: 'CLI Commands', href: 'https://docs.podman.io/en/latest/Commands.html', }, ], @@ -151,19 +162,19 @@ const config = { title: 'Projects', items: [ { - label: 'Podman GitHub', + label: 'GitHub', href: 'https://github.com/podman-container-tools/podman', }, { - label: 'Podman Desktop GitHub', + label: 'Desktop GitHub', href: 'https://github.com/podman-desktop/podman-desktop', }, { - label: 'Podman Website GitHub', + label: 'Website GitHub', href: 'https://github.com/containers/podman.io', }, { - label: 'Podman Desktop Website', + label: 'Desktop Website', href: 'https://podman-desktop.io/', }, ], diff --git a/package.json b/package.json index 4effdc3664..8d47fe5df3 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@mdx-js/react": "^1.6.22", "autoprefixer": "^10.5.4", "clsx": "^1.2.1", + "gray-matter": "^4.0.3", "html-react-parser": "^3.0.16", "papaparse": "^5.4.1", "postcss": "^8.5.23", @@ -44,7 +45,7 @@ "eslint-config-prettier": "^8.6.0", "husky": "^8.0.3", "lint-staged": "^13.1.0", - "prettier": "2.8.8", + "prettier": "^3.9.6", "prettier-plugin-tailwindcss": "^0.8.1", "tailwind-scrollbar": "^3.0.0", "typescript": "^4.9.4" diff --git a/src/components/content/BasicResourcesBox/data.ts b/src/components/content/BasicResourcesBox/data.ts index 2f92ba124a..c4a87cab5a 100644 --- a/src/components/content/BasicResourcesBox/data.ts +++ b/src/components/content/BasicResourcesBox/data.ts @@ -1,22 +1,22 @@ const resources = { - title: 'Basic Resources', - buttons: [ - { - text: 'Installation Instructions', - path: 'docs/installation', - icon: 'fa6-solid:book', - }, - { - text: 'Documentation', - path: 'https://docs.podman.io/en/latest/', - icon: 'fa6-solid:book', - }, - { - text: 'Podman Troubleshooting Guide', - path: 'https://github.com/containers/podman/blob/main/troubleshooting.md', - icon: 'fa6-solid:book', - }, - ], - }; + title: 'Basic Resources', + buttons: [ + { + text: 'Installation Instructions', + path: '/docs/installation', + icon: 'fa6-solid:download', + }, + { + text: 'Documentation', + path: 'https://docs.podman.io/en/latest/', + icon: 'fa6-solid:book', + }, + { + text: 'Podman Troubleshooting Guide', + path: 'https://github.com/containers/podman/blob/main/troubleshooting.md', + icon: 'fa6-solid:screwdriver-wrench', + }, + ], +}; - export { resources }; \ No newline at end of file +export { resources }; diff --git a/src/components/content/BasicResourcesBox/index.tsx b/src/components/content/BasicResourcesBox/index.tsx index a865245b5c..e773f644ad 100644 --- a/src/components/content/BasicResourcesBox/index.tsx +++ b/src/components/content/BasicResourcesBox/index.tsx @@ -2,28 +2,28 @@ import React from 'react'; import { Icon } from '@iconify/react'; import { resources } from './data'; -const BasicResourcesBox = () => { +const BasicResourcesBox = ({ showHeader = true }: { showHeader?: boolean }) => { return ( -
-
-

{resources.title}

-
-
- -
+
+ {showHeader && ( +
+

{resources.title}

+
+ )} +
); }; diff --git a/src/components/content/BlogArticlesList/index.tsx b/src/components/content/BlogArticlesList/index.tsx index 3fea61b73e..084139ebd0 100644 --- a/src/components/content/BlogArticlesList/index.tsx +++ b/src/components/content/BlogArticlesList/index.tsx @@ -8,6 +8,7 @@ interface BlogArticlesListProps { displayCount?: number; altLayout?: boolean; title?: string; + description?: string; titleColor?: string; showFooter?: boolean; footerText?: string; @@ -20,6 +21,7 @@ const BlogArticlesList: React.FC = ({ displayCount, altLayout = false, title = 'Latest Articles', + description, titleColor = 'text-blue-700', showFooter = false, footerText = '', @@ -29,16 +31,38 @@ const BlogArticlesList: React.FC = ({ const { data, loading } = useBlogPosts(limit); const actualDisplayCount = displayCount ?? limit; + const containerClasses = + containerLayout === 'vertical' ? 'flex flex-col gap-4' : 'flex flex-wrap justify-center gap-6'; + if (loading) { - return null; + return ( +
+ +
+ {Array.from({ length: actualDisplayCount }).map((_, index) => ( +
+
+
+
+
+
+
+
+ ))} +
+
+ ); } - const containerClasses = - containerLayout === 'vertical' ? 'flex flex-col gap-4' : 'flex flex-wrap justify-center gap-4'; - return (
- +
{data.slice(0, actualDisplayCount).map((card, index) => ( = ({ ))}
{showFooter && footerText && ( -

+

{footerText}{' '} -

-
-

{data.title}

-

{data.description}

-
-
- {data.featureImage.alt} +
+
+ {data.featureImage.alt} +
+

{data.title}

+

{data.description}

+
-
- {data.collageImages.alt} +
+ {data.collageImages.alt}
); diff --git a/src/components/content/DateTimeBox/index.tsx b/src/components/content/DateTimeBox/index.tsx index 056b26c97f..a4884e62a6 100644 --- a/src/components/content/DateTimeBox/index.tsx +++ b/src/components/content/DateTimeBox/index.tsx @@ -1,41 +1,83 @@ import React from 'react'; +import { Icon } from '@iconify/react'; + +function formatTime(date: Date, timeZone: string): string { + return date.toLocaleString('en-US', { timeZone, hour: '2-digit', minute: '2-digit', hour12: true }); +} + +function formatZoneAbbreviation(date: Date, timeZone: string): string { + const long = Intl.DateTimeFormat('en-US', { timeZone, timeZoneName: 'long' }).format(date).split(', ')[1]; + return long + .split(' ') + .map(word => word[0]) + .join(''); +} + +type ZoneRowData = { + label: string; + flag: string; + time: string; + abbreviation: string; +}; + +function ZoneRow({ label, flag, time, abbreviation }: ZoneRowData): JSX.Element { + return ( +
+
+ +
+

{label}

+

{time}

+
+
+ {abbreviation} +
+ ); +} function DateTimeBox(): JSX.Element { const date = new Date(); - const centralEuropeTime = [ - date.toLocaleString('en-US', { - timeZone: 'Europe/Paris', - hour: 'numeric', - minute: 'numeric', - hour12: false, - }), - Intl.DateTimeFormat('en-US', { timeZone: 'Europe/Paris', timeZoneName: 'long' }).format().split(',')[1], - ]; - const easternTime = [ - date.toLocaleString('en-US', { - timeZone: 'America/New_York', - hour: 'numeric', - minute: 'numeric', - hour12: false, - }), - Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', timeZoneName: 'long' }).format().split(',')[1], + + const zones: ZoneRowData[] = [ + { + label: 'US / Eastern Time', + flag: 'circle-flags:us', + time: formatTime(date, 'America/New_York'), + abbreviation: formatZoneAbbreviation(date, 'America/New_York'), + }, + { + label: 'Europe / Central Time', + flag: 'circle-flags:eu', + time: formatTime(date, 'Europe/Paris'), + abbreviation: formatZoneAbbreviation(date, 'Europe/Paris'), + }, ]; return ( -
-
-
-

Current Time

-
-
-

{centralEuropeTime[0]}

-

{centralEuropeTime[1]}

-
-
-

{easternTime[0]}

-

{easternTime[1]}

+
+
+
+
+ +
+

Current Time

+

Community around the world

+
+ + + Live + +
+
+ {zones.map(zone => ( + + ))}
+
+ + Most community members are online now +
); } diff --git a/src/components/content/DownloadsGrid/index.tsx b/src/components/content/DownloadsGrid/index.tsx new file mode 100644 index 0000000000..35aeefecf4 --- /dev/null +++ b/src/components/content/DownloadsGrid/index.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from 'react'; +import { Icon } from '@iconify/react'; +import Button from '@site/src/components/utilities/Button'; +import operatingSystemData from '@site/src/components/layout/HeroHeader/installOptions'; +import { detectOperatingSystem, osLabels, osIcons, type OperatingSystemId } from '@site/src/lib/detectOperatingSystem'; + +type DownloadOption = { + title: string; + subtitle: string; + icon: string; + path: string; +}; + +function DownloadCard({ option }: { option: DownloadOption }): JSX.Element { + return ( +
+ + + +
+

{option.title}

+

{option.subtitle}

+
+
+ ); +} + +function PlatformSection({ + os, + highlighted = false, +}: { + os: (typeof operatingSystemData)[number]; + highlighted?: boolean; +}): JSX.Element { + const options: DownloadOption[] = [os.preferred, os.alt, ...('third' in os && os.third ? [os.third] : [])]; + + return ( +
+
+ + + +

{osLabels[os.id as OperatingSystemId]}

+ {highlighted && ( + + + Recommended for your system + + )} +
+
+ {options.map((option, index) => ( + + ))} +
+
+ More install options for {osLabels[os.id as OperatingSystemId]} + + +
+ ); +} + +function DownloadsGrid(): JSX.Element { + const [detectedOs, setDetectedOs] = useState(null); + + useEffect(() => { + setDetectedOs(detectOperatingSystem()); + }, []); + + const orderedOs = detectedOs + ? [...operatingSystemData].sort((a, b) => (a.id === detectedOs ? -1 : b.id === detectedOs ? 1 : 0)) + : operatingSystemData; + + return ( +
+ {orderedOs.map(os => ( + + ))} +
+ ); +} + +export default DownloadsGrid; diff --git a/src/components/content/FeaturesCarousel/data.ts b/src/components/content/FeaturesCarousel/data.ts index 9342911c0a..e83b8a0a49 100644 --- a/src/components/content/FeaturesCarousel/data.ts +++ b/src/components/content/FeaturesCarousel/data.ts @@ -5,10 +5,19 @@ const tabData = [ commands: ['podman search', 'podman pull'], description: 'Find and pull down containers whether they are on dockerhub.io or quay.io, an internal registry server, or direct from a vendor.', - image: { - src: 'images/optimized/cli-screens/cli-find-image.webp', - alt: 'example of podman commands', - }, + terminal: `$ podman search busybox +INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED +docker.io docker.io/library/busybox Busybox base image. 1882 [OK] +docker.io docker.io/radial/busyboxplus Full-chain, Internet... 30 [OK] +docker.io docker.io/yauritux/busybox-curl Busybox with CURL 8 +... + +$ podman pull docker.io/library/busybox +Trying to pull docker.io/library/busybox:latest... +Getting image source signatures +Copying blob 5b8c72934dfc done +Writing manifest to image destination +Storing signatures`, }, { label: 'Run', @@ -16,10 +25,12 @@ const tabData = [ commands: ['podman run'], description: 'Run containers using images pulled from a registry, or from images you build yourself. Podman lets you run containers as a regular user or as root.', - image: { - src: 'images/optimized/cli-screens/cli-run-image.webp', - alt: 'example of podman commands', - }, + terminal: `$ podman run -dt -p 8080:80/tcp docker.io/library/httpd +b2e4a1c9f3d8 + +$ podman ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +b2e4a1c9f3d8 docker.io/library/httpd:latest httpd-foreground 10 seconds ago Up 10 seconds 0.0.0.0:8080->80/tcp eager_almeida`, }, { label: 'Build', @@ -27,10 +38,17 @@ const tabData = [ commands: ['podman build'], description: 'Build OCI and Docker-compatible container images using a Containerfile or Dockerfile — no daemon required.', - image: { - src: 'images/optimized/cli-screens/cli-build-image.webp', - alt: 'example of podman commands', - }, + terminal: `$ podman build -t myapp . +STEP 1/4: FROM registry.access.redhat.com/ubi9/ubi-minimal +STEP 2/4: COPY . /app +STEP 3/4: WORKDIR /app +STEP 4/4: CMD ["./start.sh"] +COMMIT myapp +Successfully tagged localhost/myapp:latest + +$ podman images +REPOSITORY TAG IMAGE ID CREATED SIZE +localhost/myapp latest 4f9a2b1c8e3d 5 seconds ago 112 MB`, }, { label: 'Share', @@ -38,10 +56,16 @@ const tabData = [ commands: ['podman push'], description: 'Podman lets you push your newly-built containers anywhere you want with a single podman push command.', - image: { - src: 'images/optimized/cli-screens/cli-share-image.webp', - alt: 'example of podman commands', - }, + terminal: `$ podman push myapp quay.io/myuser/myapp +Getting image source signatures +Copying blob 8a3f0c9b1e2d done +Copying blob 1c92e4a7b6f0 done +Writing manifest to image destination +Storing signatures + +$ podman search quay.io/myuser/myapp +INDEX NAME DESCRIPTION STARS OFFICIAL +quay.io quay.io/myuser/myapp 0`, }, ]; diff --git a/src/components/content/FeaturesCarousel/index.tsx b/src/components/content/FeaturesCarousel/index.tsx index 38096ed7d6..e41656caf2 100644 --- a/src/components/content/FeaturesCarousel/index.tsx +++ b/src/components/content/FeaturesCarousel/index.tsx @@ -1,60 +1,51 @@ import React, { useState } from 'react'; import { Icon } from '@iconify/react'; +import TerminalCard from '@site/src/components/utilities/TerminalCard'; import tabData from './data'; +const tabIcons: Record = { + Find: 'mdi:magnify', + Run: 'mdi:play-circle-outline', + Build: 'mdi:hammer-wrench', + Share: 'mdi:share-variant-outline', +}; + const Tab = (props): JSX.Element => { - const { label, commands, method, isActive } = props; + const { label, method, isActive } = props; return ( ); }; const TabContent = (props): JSX.Element => { - const { title, commands, image, description, isActive } = props; + const { title, commands, terminal, description, isActive } = props; return ( <> -
+
-

- {title} -

-
    +

    {title}

    +
      {commands.map((command, index) => ( -
    • +
    • + {command}
    • ))}
    -

    - {description} -

    +

    {description}

-
- +
+ {terminal}
@@ -67,7 +58,7 @@ function FeaturesCarousel() { // render content return (
-
+
{/* Loop through Tab data and generate tab buttons for each */} {tabData.map((tab, index) => { return ( @@ -75,36 +66,27 @@ function FeaturesCarousel() { ); })}
-
-
+
+
diff --git a/src/components/content/TestimonialSection/index.tsx b/src/components/content/TestimonialSection/index.tsx index 0fe59ce2d8..05041d1b6d 100644 --- a/src/components/content/TestimonialSection/index.tsx +++ b/src/components/content/TestimonialSection/index.tsx @@ -14,39 +14,40 @@ function TestimonialSection() { slider.scrollLeft = slider.scrollLeft + 500; }; return ( -
+
+
+
-
+
-
- {testimonials.map((testimonial, index) => { - return ; - })} +
+
+ {testimonials.map((testimonial, index) => { + return ; + })} +
+
+
diff --git a/src/components/content/ThankYouSection/data.ts b/src/components/content/ThankYouSection/data.ts index 053e2ff7aa..8b6cba4a21 100644 --- a/src/components/content/ThankYouSection/data.ts +++ b/src/components/content/ThankYouSection/data.ts @@ -2,43 +2,43 @@ const sponsorData = [ { label: 'Red Hat', href: 'https://www.redhat.com/', - src: 'logos/optimized/red-hat-120w-77h.webp', + src: '/logos/optimized/red-hat-120w-77h.webp', alt: 'Red Hat Logo', }, - { + { label: 'Amadeus', href: 'https://www.amadeus.com/', - src: 'logos/optimized/amadeus-171w-22h.webp', + src: '/logos/optimized/amadeus-171w-22h.webp', alt: 'Amadeus Logo', }, { label: 'Suse', href: 'https://www.suse.com', - src: 'logos/optimized/suse-167w-30h.webp', + src: '/logos/optimized/suse-167w-30h.webp', alt: 'Suse Logo', }, { label: 'Motorola', href: 'https://www.motorolasolutions.com/', - src: 'logos/optimized/motorola-solutions-128w-110h.webp', + src: '/logos/optimized/motorola-solutions-128w-110h.webp', alt: 'Motorola Solutions Logo', }, { label: 'NTT', href: 'https://www.global.ntt', - src: 'logos/optimized/ntt-145w-50h.webp', + src: '/logos/optimized/ntt-145w-50h.webp', alt: 'NTT Logo', }, { label: 'IBM', href: 'https://www.ibm.com', - src: 'logos/optimized/ibm-92w-37h.webp', + src: '/logos/optimized/ibm-92w-37h.webp', alt: 'IBM Logo', }, { label: 'Debian', href: 'https://www.debian.org/', - src: 'logos/optimized/debian-68w-90h.webp', + src: '/logos/optimized/debian-68w-90h.webp', alt: 'Debian Logo', }, ]; diff --git a/src/components/content/ThankYouSection/index.tsx b/src/components/content/ThankYouSection/index.tsx index 5df5dba6c1..a5bf8cc4dd 100644 --- a/src/components/content/ThankYouSection/index.tsx +++ b/src/components/content/ThankYouSection/index.tsx @@ -1,84 +1,27 @@ import React from 'react'; import sponsorData from './data'; -import { Icon } from '@iconify/react'; function ThankYouSection(): JSX.Element { - const [redHat, amadeus, suse, motorola, ntt, ibm, debian] = sponsorData; - const slideLeft = () => { - const slider = document.getElementById('slider'); - slider.scrollLeft = slider.scrollLeft - 500; - }; - const slideRight = () => { - const slider = document.getElementById('slider'); - slider.scrollLeft = slider.scrollLeft + 500; - }; return (

Special thanks to our contributors

-

+

The Podman community has contributors from many different organizations, including:

-
); diff --git a/src/components/content/WelcomeBanner/index.tsx b/src/components/content/WelcomeBanner/index.tsx index d2343e9a8b..da6c226e47 100644 --- a/src/components/content/WelcomeBanner/index.tsx +++ b/src/components/content/WelcomeBanner/index.tsx @@ -5,21 +5,21 @@ function WelcomeBanner() { return (
-
+
-
-

+
+

Welcome to the new Podman Website!

-
+

- Take a look around, don't forget to check out the new docs layout. Please report - any bugs to the issue tracker. + Take a look around, don't forget to check out the new docs layout. Please report any + bugs to the issue tracker.

diff --git a/src/components/layout/CommunityMeetingsCardGrid/index.tsx b/src/components/layout/CommunityMeetingsCardGrid/index.tsx index 31615cf0c2..4c4ebbed79 100644 --- a/src/components/layout/CommunityMeetingsCardGrid/index.tsx +++ b/src/components/layout/CommunityMeetingsCardGrid/index.tsx @@ -1,10 +1,11 @@ -import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import React, { ReactNode, useRef, useState } from 'react'; +import { Icon } from '@iconify/react'; +import { usePluginData } from '@docusaurus/useGlobalData'; import CustomCard from '@site/src/components/ui/CustomCard'; import SubcardGrid from '@site/src/components/layout/SubcardGrid'; -import SectionHeader from '@site/src/components/layout/SectionHeader'; import Dropdown from '@site/src/components/utilities/DropDown'; import CloseIcon from '@site/src/components/shapes/CloseIcon'; -import * as markDownFiles from '@site/static/data/meetings/notes/index'; // ToDo: Lazy load these files +import VideoEmbed from '@site/src/components/ui/VideoEmbed'; import './styles.css'; @@ -13,44 +14,73 @@ type CommunityMeetingsCardProps = { subtitle: string; date: string; timeZone: string; - buttons: [ - { - text: string; - path: string; - }, - ]; + buttons: { text: string; path: string }[]; +}; + +type MeetingNote = { + slug: string; + displayDate: string; + title: string; + recordingLink?: string; + recordingEmbedUrl?: string; +}; + +type MeetingsData = { community: MeetingNote[]; cabal: MeetingNote[] }; + +type ModalTriggerContent = { + text: string; + markDown?: ReactNode; + embedUrl?: string; + modalHeaderData?: string; + icon?: string; }; type DropdownOptionProps = { date: string; - meeting_recording: { - text: string; - link: string; - }; - meeting_minutes: { - text: string; - markDown: ReactNode; - modalHeaderData?: string; - }; + meeting_recording: ModalTriggerContent & { link?: string }; + meeting_minutes: ModalTriggerContent & { path: string }; }; type SubcardButtonProps = { text: string; path?: string; markDown?: ReactNode; - modalHeaderData?: String; + embedUrl?: string; + modalHeaderData?: string; + icon?: string; }; type SubcardGridProps = { buttons: SubcardButtonProps[]; icon: string; date: string; + embedUrl?: string; + embedTitle?: string; }; -function toggleModalOpen(ref, handler) { - useEffect(() => { - const listener = event => { - if (ref?.current?.contains(event.target)) { +function toModalOption(note: MeetingNote): DropdownOptionProps { + return { + date: note.displayDate, + meeting_minutes: { + path: `/community/meetings/${note.slug}`, + modalHeaderData: note.title, + text: 'Meeting Minutes', + icon: 'mdi:text-box-outline', + }, + meeting_recording: { + link: note.recordingLink, + embedUrl: note.recordingEmbedUrl, + modalHeaderData: note.title, + text: 'Watch Recording', + icon: 'mdi:play-circle-outline', + }, + }; +} + +function useOutsideClick(ref: React.RefObject, handler: (event: Event) => void) { + React.useEffect(() => { + const listener = (event: Event) => { + if (ref?.current?.contains(event.target as Node)) { return; } handler(event); @@ -64,73 +94,45 @@ function toggleModalOpen(ref, handler) { }, [ref, handler]); } -function CommunityMeetingsCardGrid({ cards }) { - let cabalDropdownOptions: DropdownOptionProps[] = []; - let MeetingDropdownOptions: DropdownOptionProps[] = []; - +function CommunityMeetingsCardGrid({ cards }: { cards: CommunityMeetingsCardProps[] }) { + const meetings = usePluginData('meeting-notes-plugin') as MeetingsData; const [isModalOpen, setIsModalOpen] = useState(false); const [modalHeader, setModalHeader] = useState(undefined); - const [meetinNotesMD, setMeetinNotesMD] = useState(undefined); - const meetingMinutesRef = [useRef(), useRef()]; - const modalRef = useRef(); + const [modalContent, setModalContent] = useState(undefined); + const meetingMinutesRef = [useRef(null), useRef(null)]; + const modalRef = useRef(null); + + useOutsideClick(modalRef, () => setIsModalOpen(false)); - toggleModalOpen(modalRef, () => setIsModalOpen(false)); + const communityOptions = meetings.community.map(toModalOption); + const cabalOptions = meetings.cabal.map(toModalOption); const prepareModalHeader = (text: string, date: string) => { - const modalHeader: ReactNode = ( -
-

{text}

-

{date}

-
setIsModalOpen(false)}> - + setModalHeader( +
+
+

{text}

+

{date}

-
+ +
, ); - setModalHeader(modalHeader); - }; - - const populateMeetings = (): void => { - Object.values(markDownFiles)?.forEach(mdFile => { - let mdReader = mdFile?.default(useRef()); - mdReader?.props?.children?.forEach(child => { - let field1: string = child?.props?.children?.[0]; - let field2: object = child?.props?.children?.[1]; - if (typeof field1 == 'string' && (field1.includes('BlueJeans') || field1.includes('Video'))) { - if (mdFile?.contentTitle?.includes('Cabal')) { - cabalDropdownOptions.unshift({ - date: (mdFile?.toc?.[0]?.value as string).split(/[0-9]{2}:[0-9]{2}/)[0], - meeting_minutes: { - markDown: mdReader, - modalHeaderData: mdFile['contentTitle'], - text: 'Meeting Minutes', - }, - meeting_recording: { - link: field2?.props?.href, - text: 'Watch Recording', - }, - }); - } else { - MeetingDropdownOptions.unshift({ - date: (mdFile?.toc?.[0]?.value as string).split(/[0-9]{2}:[0-9]{2}/)[0], - meeting_minutes: { - markDown: mdReader, - modalHeaderData: mdFile['contentTitle'], - text: 'Meeting Minutes', - }, - meeting_recording: { - link: field2?.props?.href, - text: 'Watch Recording', - }, - }); - } - } - }); - }); }; - const toggleIsModalOpen = (...modalData) => { - modalData && setMeetinNotesMD(modalData[0].markDown); - prepareModalHeader(modalData[0].modalHeaderData, modalData[1]); + const toggleIsModalOpen = (content: ModalTriggerContent, date: string) => { + const title = content?.modalHeaderData ?? ''; + if (content?.embedUrl) { + setModalContent(); + } else { + setModalContent(content?.markDown); + } + prepareModalHeader(title, date); setIsModalOpen(true); }; @@ -138,67 +140,71 @@ function CommunityMeetingsCardGrid({ cards }) { const { meeting_minutes, meeting_recording, date } = props; return ( -
-

{date}

- - {meeting_recording?.text} - - { - toggleIsModalOpen(meeting_minutes, date); - }} - className="cursor-pointer"> - {meeting_minutes?.text} - + ); } function getDropdownOption(options: DropdownOptionProps[]) { - return options.map(option => ); + return options.map((option, index) => ); } - populateMeetings(); - - let communityMeetingsData: SubcardGridProps[] = []; - let CabalMeetingsData: SubcardGridProps[] = []; - - // get top 2 CommunityMeetings & CabalMeetings for subcards - for (let i = 0; i < 2; i++) { - let meeting = MeetingDropdownOptions.shift(); - communityMeetingsData.push({ - date: meeting?.date, + const buildSubcardData = (options: DropdownOptionProps[]): SubcardGridProps[] => + options.slice(0, 2).map(meeting => ({ + date: meeting.date, icon: 'film-icon', + embedUrl: meeting.meeting_recording?.embedUrl, + embedTitle: meeting.meeting_recording?.modalHeaderData, buttons: [ - { - path: meeting?.meeting_recording?.link, - text: meeting?.meeting_recording?.text, - }, - { ...meeting?.meeting_minutes }, - ], - }); - meeting = cabalDropdownOptions.shift(); - CabalMeetingsData.push({ - date: meeting?.date, - icon: 'film-icon', - buttons: [ - { - path: meeting?.meeting_recording?.link, - text: meeting?.meeting_recording?.text, - }, - { ...meeting?.meeting_minutes }, + // If we can embed the recording directly, skip the "Watch Recording" + // button entirely instead of making it open the same video in a modal. + ...(meeting.meeting_recording?.embedUrl + ? [] + : [ + { + path: meeting.meeting_recording?.link, + text: meeting.meeting_recording?.text, + }, + ]), + { ...meeting.meeting_minutes }, ], - }); - } + })); + + const communityMeetingsData = buildSubcardData(communityOptions); + const cabalMeetingsData = buildSubcardData(cabalOptions); return ( -
- {cards.map((card: CommunityMeetingsCardProps, index: number) => { - let meetingsData = index == 1 ? CabalMeetingsData : communityMeetingsData; +
+ {cards.map((card, index) => { + const meetingsData = index == 1 ? cabalMeetingsData : communityMeetingsData; + const dropdownOptions = index == 1 ? cabalOptions : communityOptions; return (
+ className="card-container mb-4 flex w-full flex-col flex-wrap items-center justify-start gap-4 transition duration-150 ease-linear lg:mb-6 lg:flex-1"> - +
+ + Most Recent Meetings +
- -
- {modalHeader} -
- {meetinNotesMD} -
-
-
); })} + +
+ {modalHeader} +
+ {modalContent} +
+
+
); } diff --git a/src/components/layout/CommunityMeetingsCardGrid/styles.css b/src/components/layout/CommunityMeetingsCardGrid/styles.css index 6d99a88d40..5fa8f30b7a 100644 --- a/src/components/layout/CommunityMeetingsCardGrid/styles.css +++ b/src/components/layout/CommunityMeetingsCardGrid/styles.css @@ -1,40 +1,14 @@ -/* colors (current tailwind standard colors but not included with currently using tailwind installation) */ -.bg-stone-200 { - background-color: rgb(231 229 228); -} - -/* width custom classes */ -.w-90-screen { - width: 90vw; -} - -/* height custom classes */ -.h-80-screen { - height: 85vh; -} - .close-icon { cursor: pointer; } -.md-wrapper { - padding: 2rem; - height: 75vh; -} - -.md-wrapper h1, -h2, -h3 { - padding: 0.5rem; -} - -dialog { - padding: 0; +.modal-body h1, +.modal-body h2, +.modal-body h3 { + padding: 0.5rem 0; } -.modal-header { - background-color: white; - padding: 1rem; - display: inline-flex; - justify-content: space-between; +dialog::backdrop { + background-color: rgb(0 0 0 / 0.5); + backdrop-filter: blur(4px); } diff --git a/src/components/layout/HeroHeader/index.tsx b/src/components/layout/HeroHeader/index.tsx index fb50fa8a8a..679952e7c9 100644 --- a/src/components/layout/HeroHeader/index.tsx +++ b/src/components/layout/HeroHeader/index.tsx @@ -3,9 +3,9 @@ import BrowserOnly from '@docusaurus/BrowserOnly'; import { Icon } from '@iconify/react'; import Button from '@site/src/components/utilities/Button'; import DropdownButton from '@site/src/components/utilities/DropdownButton'; -import Link from '@site/src/components/utilities/Link'; import WaveBorder from '@site/src/components/shapes/WaveBorder'; import operatingSystemData from './installOptions'; +import { detectOperatingSystem } from '@site/src/lib/detectOperatingSystem'; type InstallOptionProps = Card & { icon: string; option?: React.ReactNode; @@ -14,123 +14,108 @@ type InstallOptionProps = Card & { other?: { path: string; text: string; subtext: string }; }; -const detectOperatingSystem = () => { - const userAgent = window.navigator.userAgent.toLowerCase().split(' '); - if (userAgent.find(item => item.includes('windows'))) { - return 'windows'; - } else if (userAgent.find(item => item.includes('macintosh'))) { - return 'mac'; - } - return 'linux'; -}; - function returnOperatingSystemData() { - const os = operatingSystemData.find(os => os.id === detectOperatingSystem() && os); - return os; + const os = operatingSystemData.find(os => os.id === detectOperatingSystem()); + return { os }; } +const InstallOptionRow = ({ + title, + subtitle, + icon, + path, +}: { + title: string; + subtitle: string; + icon: string; + path: string; +}): JSX.Element => ( + + + + +
+

{title}

+

{subtitle}

+
+
+); + const InstallOption = (props): JSX.Element => { if (!props) { props = operatingSystemData[0]; } return ( -
-
- -
-
-

{props.preferred.title}

-

{props.preferred.subtitle}

-
- -
-
+
+
+ + + {props.third && }
- - {props.third && ( -
); }; -function HeroHeader({ title, subtitle, podmanrelease, desktoprelease, image, platforms }) { +const VersionBadge = ({ icon, text, path }: { icon: string; text: string; path: string }): JSX.Element => ( + + + {text} + +); + +function HeroHeader({ title, subtitle, podmanrelease, desktoprelease, image, platforms, platformsLabel }) { return (
-
+

{title}

{subtitle}

-

- - Latest stable Podman - - - - - Latest stable Podman Desktop - - - - -

+
+ + + +
-

{platforms[0]}

+

{platformsLabel}

    - {platforms.slice(1).map((icon, index) => { + {platforms.map(platform => { return ( -
  • - +
  • + + +
  • ); })} diff --git a/src/components/layout/HeroHeader/installOptions.ts b/src/components/layout/HeroHeader/installOptions.ts index 7668a39f5f..8d47aab344 100644 --- a/src/components/layout/HeroHeader/installOptions.ts +++ b/src/components/layout/HeroHeader/installOptions.ts @@ -2,7 +2,7 @@ import { LATEST_VERSION, LATEST_DESKTOP_VERSION } from '@site/static/data/global const operatingSystemData = [ { id: 'windows', - preferred:{ + preferred: { title: 'Podman Desktop for Windows', subtitle: `Windows Installer v-${LATEST_DESKTOP_VERSION}`, icon: 'fa-brands:windows', @@ -24,7 +24,7 @@ const operatingSystemData = [ path: `https://github.com/podman-container-tools/podman/releases/download/v${LATEST_VERSION}/podman-installer-windows-arm64.msi`, }, other: { - path: 'docs/installation', + path: '/downloads', text: 'Other Install Options', }, }, @@ -44,7 +44,7 @@ const operatingSystemData = [ path: `https://github.com/podman-container-tools/podman/releases/download/v${LATEST_VERSION}/podman-installer-macos-arm64.pkg`, }, other: { - path: 'docs/installation', + path: '/downloads', text: 'Other Install Options', }, }, @@ -54,7 +54,7 @@ const operatingSystemData = [ title: 'Podman CLI for Linux', subtitle: `Podman Engine v${LATEST_VERSION}`, icon: 'material-symbols:terminal-rounded', - path: `docs/installation#installing-on-linux`, + path: `/docs/installation#installing-on-linux`, }, alt: { title: 'Podman Desktop for Linux', @@ -63,7 +63,7 @@ const operatingSystemData = [ path: `https://github.com/podman-desktop/podman-desktop/releases/download/v${LATEST_DESKTOP_VERSION}/podman-desktop-${LATEST_DESKTOP_VERSION}.flatpak`, }, other: { - path: 'docs/installation', + path: '/downloads', text: 'Other Install Options', }, }, diff --git a/src/components/layout/PageHeader/index.tsx b/src/components/layout/PageHeader/index.tsx index 0bb9afa786..a22fc922d9 100644 --- a/src/components/layout/PageHeader/index.tsx +++ b/src/components/layout/PageHeader/index.tsx @@ -1,6 +1,5 @@ import React, { PropsWithChildren } from 'react'; import { Icon } from '@iconify/react'; -import WaveBorder from '@site/src/components/shapes/WaveBorder'; import Markdown from '@site/src/components/utilities/Markdown'; import BasicResourcesBox from '@site/src/components/content/BasicResourcesBox'; type ImageSectionProps = LayoutProps & { @@ -14,36 +13,51 @@ type PageHeaderProps = LayoutProps & darkColor?: string; lightColor?: string; basicResources?: boolean; - instructions?: { [key:string]: any }; + instructions?: { [key: string]: any }; }; type PageHeaderSupplementalInfoProps = PageHeaderProps & { image?: Image; basicResources?: boolean; -} +}; type InstructionsProps = PageHeaderProps & { - instructions?: { [key:string]: any }; -} + instructions?: { [key: string]: any }; +}; const TextBox = ({ grid, display, layout, title, description }: PageHeaderProps): JSX.Element => { return (
    -

    {title}

    +

    {title}

    ); }; +const DecorativeBlob = () => ( +
    + + + + + + + +
    +); + const Image = ({ - grid, - display, - layout,// if array then {[key:string]: any}[]; - image = { path: 'images/optimized/podman-2-196w-172h.webp', alt: 'Podman Logo' }, + layout, // if array then {[key:string]: any}[]; + image = { path: '/images/optimized/podman-2-196w-172h.webp', alt: 'Podman Logo' }, }: ImageSectionProps) => { return ( -
    - {image.alt} +
    + + {image.alt}
    ); }; @@ -51,51 +65,56 @@ const Image = ({ function PageHeaderSupplementalInfo({ image, basicResources }: PageHeaderSupplementalInfoProps) { if (basicResources) { return ( - +
    + +
    ); - } - return ( - - ); + } + return ; } function Instructions({ instructions }: InstructionsProps) { if (instructions) { return (
    -

    {instructions.title}

    +

    {instructions.title}

    {instructions.subtitle}

    -
    - ) + ); } return null; } -function PageHeader({ title, description, image, lightColor = 'white', darkColor = 'gray-900', basicResources, instructions }: PageHeaderProps) { +function PageHeader({ + title, + description, + image, + lightColor = 'white', + darkColor = 'gray-900', + basicResources, + instructions, +}: PageHeaderProps) { return (
    -
    - -
    -
    -
    +
    +
    +
    -
    +
    -
    ); diff --git a/src/components/layout/SectionHeader/index.tsx b/src/components/layout/SectionHeader/index.tsx index 353d9404ec..0595d4fe72 100644 --- a/src/components/layout/SectionHeader/index.tsx +++ b/src/components/layout/SectionHeader/index.tsx @@ -22,11 +22,11 @@ function SectionHeader({ bgColor, }: SectionHeaderProps): JSX.Element { const applyTextColor = textGradient - ? `bg-gradient-radial bg-clip-text text-transparent dark:bg-gradient-radial dark:text-transparent ${textGradientStops}` + ? `bg-gradient-to-r bg-clip-text text-transparent dark:bg-gradient-to-r dark:text-transparent ${textGradientStops}` : `${textColor}`; return ( -
    -
    +
    +

    {title}

    diff --git a/src/components/layout/SubcardGrid/index.tsx b/src/components/layout/SubcardGrid/index.tsx index a431560cac..ad4d62189a 100644 --- a/src/components/layout/SubcardGrid/index.tsx +++ b/src/components/layout/SubcardGrid/index.tsx @@ -4,7 +4,7 @@ import { DayOfTheWeek } from '@site/src/components/utilities/DateUtils'; function SubcardGrid({ cards, toggleIsModalOpen }) { return ( -
    +
    {cards?.map((card, index) => { let date = new Date(card.date).getDay(); return ( @@ -16,6 +16,8 @@ function SubcardGrid({ cards, toggleIsModalOpen }) { text={card.subtitle} data={card.buttons} icon={card.icon} + embedUrl={card.embedUrl} + embedTitle={card.embedTitle} method={meeting_minutes => { toggleIsModalOpen(meeting_minutes, card.date); }} diff --git a/src/components/pages/MeetingNotePage/index.tsx b/src/components/pages/MeetingNotePage/index.tsx new file mode 100644 index 0000000000..b6e9071db6 --- /dev/null +++ b/src/components/pages/MeetingNotePage/index.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import Layout from '@theme/Layout'; +import Markdown from '@site/src/components/utilities/Markdown'; +import VideoEmbed from '@site/src/components/ui/VideoEmbed'; + +type MeetingNote = { + slug: string; + displayDate: string; + title: string; + recordingLink?: string; + recordingEmbedUrl?: string; + content: string; +}; + +// The page already renders the title in its own header, so drop the matching +// leading `# Title` line from the markdown to avoid showing it twice. +function stripLeadingTitle(content: string, title: string): string { + return content.replace(new RegExp(`^#\\s+${title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\n`), ''); +} + +function MeetingNotePage({ note }: { note: MeetingNote }): JSX.Element { + return ( + + + + ); +} + +export default MeetingNotePage; diff --git a/src/components/ui/ArticleCard/index.tsx b/src/components/ui/ArticleCard/index.tsx index d1ff58c057..c91bd22684 100644 --- a/src/components/ui/ArticleCard/index.tsx +++ b/src/components/ui/ArticleCard/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Icon } from '@iconify/react'; import parse from 'html-react-parser'; type ArticleCardProps = { title: string; @@ -13,89 +14,103 @@ type ArticleCardProps = { }; const FALLBACK_IMAGES = [ - 'images/raw/characters/seal-diving.png', - 'images/raw/characters/seals-swimming.png', - 'images/raw/characters/confused-seal.png', - 'images/raw/podman-selkie-385w-358h.png', + '/images/raw/characters/seal-diving.png', + '/images/raw/characters/seals-swimming.png', + '/images/raw/characters/confused-seal.png', + '/images/raw/podman-selkie-385w-358h.png', ]; const PublishDate = ({ date, styles }: { date: string; styles?: string }) => { return (
    -

    {date}

    + className={`${styles} flex h-fit max-w-fit items-center gap-1 rounded-full border border-white/60 bg-white/80 px-2.5 py-1 text-gray-700 shadow-sm backdrop-blur dark:border-white/10 dark:bg-gray-900/80 dark:text-gray-300`}> + +

    {date}

    ); }; -const Title = (title: string) => { - return

    {title}

    ; -}; function ArticleCard(props: ArticleCardProps) { // Select fallback image based on index, cycling through available images const fallbackImage = FALLBACK_IMAGES[(props.index || 0) % FALLBACK_IMAGES.length]; - + // Sanitizes HTML and converts it to plain text const sanitizeHtml = (html: string) => { if (!html) return html; - const div = document.createElement("div"); + const div = document.createElement('div'); div.innerHTML = html; - return div.textContent || div.innerText || ""; + return div.textContent || div.innerText || ''; }; const abbrSubtitle = sanitizeHtml(props.subtitle).trim().split(' ').slice(0, 32).join(' ').concat('...'); + + const TitleLink = ({ className }: { className?: string }) => ( +

    + + {sanitizeHtml(props.title)} + +

    + ); + + const Byline = () => ( + + ); + if (props.altLayout) { return ( -
    -
    - -
    + ); } // Normal Layout - else - return ( - + ); } export default ArticleCard; diff --git a/src/components/ui/Card/index.tsx b/src/components/ui/Card/index.tsx index fbeffed96c..9feeb8de1b 100644 --- a/src/components/ui/Card/index.tsx +++ b/src/components/ui/Card/index.tsx @@ -21,7 +21,7 @@ function CardBody(props) { } function CardButtons({ data = [{ text: 'button text' }] }) { return ( -
    +
    {data.map((button, index) => (
    {index == 0 ?
    + ); +} + +// Subcards (recent meeting recordings) get the date/day stamped directly on the video thumbnail. +function SubCard(props) { + const { title, subtitle, embedUrl, embedTitle, icon } = props; return ( -
    - - {props?.icon ? : } - +
    +
    + {embedUrl ? ( + + ) : icon ? ( +
    + +
    + ) : null} +
    + + {title} + {subtitle && · {subtitle}} +
    +
    +
    + +
    ); } +function CustomCard(props) { + return props.primary ? : ; +} + export default CustomCard; diff --git a/src/components/ui/InfoBanner/index.tsx b/src/components/ui/InfoBanner/index.tsx index 0d3bf350d6..e07085f499 100644 --- a/src/components/ui/InfoBanner/index.tsx +++ b/src/components/ui/InfoBanner/index.tsx @@ -16,18 +16,30 @@ function InfoBanner({ title, description, image, - styles, + styles = '', icon, - bgColor = 'from-blue-700 via-blue-700 to-blue-900 dark:from-blue-500 dark:to-blue-700', - titleColor = 'text-purple-700 dark:text-purple-500', + bgColor, + titleColor = 'text-white', marginHeight = 'mt-8 lg:mt-16', }: BannerProps): JSX.Element { + // A gradient's `background-image` always paints over a flat `background-color`, + // so a caller-supplied `bg-*` in `styles` would silently lose to this default — + // only fall back to the brand gradient when the caller hasn't set their own background. + // (Previously this default also omitted `bg-gradient-to-r`, so `from-*`/`via-*`/`to-*` + // never rendered as a gradient at all — the banner had no visible background.) + const hasCustomBackground = /(^|\s)bg-/.test(styles); + const resolvedBgColor = + bgColor ?? + (hasCustomBackground ? '' : 'bg-gradient-to-r from-blue-500 to-purple-700 dark:from-blue-700 dark:to-purple-900'); + return ( -
    -
    -
    +
    +
    +
    {icon ? ( - +
    + +
    ) : image ? ( {image.alt} ) : ( @@ -36,12 +48,12 @@ function InfoBanner({
    {title ? ( -
    -

    {title}

    - +
    +

    {title}

    +
    ) : ( - + )}
    diff --git a/src/components/ui/InfoBox/index.tsx b/src/components/ui/InfoBox/index.tsx index c2ad63047b..23aeca846d 100644 --- a/src/components/ui/InfoBox/index.tsx +++ b/src/components/ui/InfoBox/index.tsx @@ -3,15 +3,18 @@ import React from 'react'; type BoxProps = { title: string; text: string; - darkBg?: string; }; -function InfoBox({ title, text, darkBg = 'dark:bg-purple-900' }: BoxProps): JSX.Element { +function InfoBox({ title, text }: BoxProps): JSX.Element { return ( -