diff --git a/gatsby-config.js b/gatsby-config.js index 5e830b80..1d80f424 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -20,6 +20,13 @@ module.exports = { path: `${__dirname}/src`, }, }, + { + resolve: `gatsby-source-filesystem`, + options: { + name: `images`, + path: `${__dirname}/static`, + }, + }, { resolve: 'gatsby-plugin-manifest', options: { diff --git a/gatsby-node.js b/gatsby-node.js index f32412a0..5d1a55e0 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -68,6 +68,9 @@ exports.createPages = ({ graphql, actions }) => { component: slash(articleTemplate), context: { id: edge.node.id, + image: (edge.node.pageAttributes.image || '') + .replace(/^\//, '') + .replace(/^static\//, ''), }, }); }); diff --git a/src/Components/Featured-contributor/FeaturedContributor.jsx b/src/Components/Featured-contributor/FeaturedContributor.jsx index 7af5d2a7..9e2718d9 100644 --- a/src/Components/Featured-contributor/FeaturedContributor.jsx +++ b/src/Components/Featured-contributor/FeaturedContributor.jsx @@ -2,14 +2,41 @@ import React from 'react'; import { motion } from 'framer-motion'; import { Calendar, GitCommitHorizontal, MapPin } from 'lucide-react'; import './featured-contributor.css'; -import { Link } from 'gatsby'; +import { Link, graphql, useStaticQuery } from 'gatsby'; +import { GatsbyImage, getImage } from 'gatsby-plugin-image'; const FeaturedContributor = ({ contributor, darkmode }) => { + const data = useStaticQuery(graphql` + query FeaturedContributorImagesQuery { + allFile( + filter: { + sourceInstanceName: { eq: "images" } + extension: { in: ["jpg", "jpeg", "png", "webp", "avif"] } + } + ) { + nodes { + relativePath + childImageSharp { + gatsbyImageData(width: 500, placeholder: BLURRED) + } + } + } + } + `); + if (!contributor) return null; const pageAttributes = contributor?.node?.pageAttributes; const { name, image, location, datepublished, intro, firstcommit } = pageAttributes || {}; + const normalizeImagePath = (value) => + (value || '').replace(/^\//, '').replace(/^static\//, ''); + const normalizedPath = normalizeImagePath(image); + const matchedImageNode = data.allFile.nodes.find( + (node) => node.relativePath === normalizedPath + ); + const optimizedImageData = getImage(matchedImageNode); + const fallbackImageSrc = image; const slug = contributor?.node?.fields?.slug; return ( { transition={{ delay: 0.2, duration: 0.4 }} whileHover={{ scale: 1.02 }} > - {name} + {optimizedImageData ? ( + + ) : ( + {name} + )}
diff --git a/src/Components/Search/SearchResults.jsx b/src/Components/Search/SearchResults.jsx index 592d1deb..01d11190 100644 --- a/src/Components/Search/SearchResults.jsx +++ b/src/Components/Search/SearchResults.jsx @@ -1,11 +1,37 @@ import React from 'react'; import './search-result.css'; -import { Link } from 'gatsby'; +import { Link, graphql, useStaticQuery } from 'gatsby'; import { Github, Linkedin } from 'lucide-react'; import { motion } from 'framer-motion'; import XIcon from '../XIcon.jsx'; +import { GatsbyImage, getImage } from 'gatsby-plugin-image'; function SearchResults({ results, darkmode }) { + const data = useStaticQuery(graphql` + query SearchResultsImagesQuery { + allFile( + filter: { + sourceInstanceName: { eq: "images" } + extension: { in: ["jpg", "jpeg", "png", "webp", "avif"] } + } + ) { + nodes { + relativePath + childImageSharp { + gatsbyImageData( + width: 50 + height: 50 + placeholder: BLURRED + ) + } + } + } + } + `); + + const normalizeImagePath = (value) => + (value || '').replace(/^\//, '').replace(/^static\//, ''); + if (!results || results.length === 0) { return (
{sortedResults.map(({ item, score }) => { if (!item) return null; + const normalizedPath = normalizeImagePath(item?.image); + const matchedImageNode = data.allFile.nodes.find( + (node) => node.relativePath === normalizedPath + ); + const optimizedImageData = getImage(matchedImageNode); return (
- {item?.name} + {optimizedImageData ? ( + + ) : ( + {item?.name} + )}

diff --git a/src/pages/index.js b/src/pages/index.js index f70aeb77..9c1a4bc2 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -7,6 +7,7 @@ import { Helmet } from 'react-helmet'; import dayjs from 'dayjs'; import axios from 'axios'; import Papa from 'papaparse'; +import { GatsbyImage, getImage } from 'gatsby-plugin-image'; import ContributorsList from '../Components/Contributor/ContributorsList.jsx'; import FeaturedContributor from '../Components/Featured-contributor/FeaturedContributor.jsx'; import Search from '../Components/Search/Search.jsx'; @@ -15,10 +16,29 @@ const IndexPage = (props) => { const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const { data } = props; + const normalizeImagePath = (value) => + (value || '').replace(/^\//, '').replace(/^static\//, ''); + const imageNodes = data?.allFile?.nodes || []; + const getOptimizedImageData = (value) => { + const normalizedPath = normalizeImagePath(value); + if (!normalizedPath) return null; + + const matchedNode = imageNodes.find( + (node) => node.relativePath === normalizedPath + ); + return getImage(matchedNode); + }; + const contributors = data.allAsciidoc.edges; const [thankYou, setThankYou] = React.useState([]); const [darkmode, setDarkmode] = React.useState(null); + const jenkinsLogoImageData = getOptimizedImageData('/jenkins.png'); + const randomContributorImagePath = thankYou[6]?.replace(/['"]+/g, ''); + const randomContributorImageData = getOptimizedImageData( + randomContributorImagePath + ); + useEffect(() => { if (typeof window !== 'undefined') { const mediaquery = @@ -124,7 +144,14 @@ const IndexPage = (props) => { continuous integration and delivery - Jenkins logo + {jenkinsLogoImageData ? ( + + ) : ( + Jenkins logo + )} @@ -183,18 +210,40 @@ const IndexPage = (props) => { alignItems: 'center', }} > - Random contributor + {randomContributorImageData ? ( + + ) : ( + Random contributor + )} { export default IndexPage; export const pageQuery = graphql` - query { + query IndexPageQuery { allAsciidoc(limit: 1000, sort: { fields: { publicationDate: DESC } }) { edges { node { @@ -312,5 +361,18 @@ export const pageQuery = graphql` } } } + allFile( + filter: { + sourceInstanceName: { eq: "images" } + extension: { in: ["jpg", "jpeg", "png", "webp", "avif"] } + } + ) { + nodes { + relativePath + childImageSharp { + gatsbyImageData(width: 120, placeholder: BLURRED) + } + } + } } `; diff --git a/src/templates/contributor-details.jsx b/src/templates/contributor-details.jsx index b648f46a..f2068844 100644 --- a/src/templates/contributor-details.jsx +++ b/src/templates/contributor-details.jsx @@ -10,6 +10,7 @@ import { Github, Linkedin, Mail } from 'lucide-react'; import XIcon from '../Components/XIcon.jsx'; import { motion } from 'framer-motion'; import './contributor-details.css'; +import { GatsbyImage, getImage } from 'gatsby-plugin-image'; function ContributorDetails(props) { const theme = useTheme(); const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); @@ -18,9 +19,12 @@ function ContributorDetails(props) { const title = props.data.asciidoc.pageAttributes.name + ' - Jenkins Contributor Spotlight'; + const imageData = getImage(props.data.imageFile); // State for sanitized HTML - const [sanitizedHTML, setSanitizedHTML] = useState(props.data.asciidoc.html); + const [sanitizedHTML, setSanitizedHTML] = useState( + props.data.asciidoc.html + ); // Sanitize HTML on client side only useEffect(() => { @@ -99,16 +103,38 @@ function ContributorDetails(props) { }} > - Contributor avatar + + {imageData ? ( + + ) : ( + Contributor avatar + )} +