From fad4a64d541045c0f51403acc9d0aa391f9c570d Mon Sep 17 00:00:00 2001 From: Evan Bonsignori Date: Thu, 7 Aug 2025 11:24:01 -0700 Subject: [PATCH 1/4] remove Discussion feedback link from Search Overlay (#56926) --- src/search/components/input/SearchOverlay.tsx | 40 ++----------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/src/search/components/input/SearchOverlay.tsx b/src/search/components/input/SearchOverlay.tsx index 177a6f7a8205..f8977830d726 100644 --- a/src/search/components/input/SearchOverlay.tsx +++ b/src/search/components/input/SearchOverlay.tsx @@ -5,13 +5,11 @@ import { ActionList, Box, IconButton, - Link, Overlay, Spinner, Stack, Text, TextInput, - Token, } from '@primer/react' import { SearchIcon, @@ -31,18 +29,18 @@ import { executeGeneralSearch, GENERAL_SEARCH_CONTEXT, } from '../helpers/execute-search-actions' - -import styles from './SearchOverlay.module.scss' import { Banner } from '@primer/react/drafts' import { useCombinedSearchResults } from '@/search/components/hooks/useAISearchAutocomplete' import { AskAIResults } from './AskAIResults' import { sendEvent, uuidv4 } from '@/events/components/events' -import { getIsStaff } from '@/events/components/dotcom-cookies' import { EventType } from '@/events/types' import { ASK_AI_EVENT_GROUP, SEARCH_OVERLAY_EVENT_GROUP } from '@/events/components/event-groups' +import { useSharedUIContext } from '@/frame/components/context/SharedUIContext' + import type { AIReference } from '../types' import type { AutocompleteSearchHit, GeneralSearchHit } from '@/search/types' -import { useSharedUIContext } from '@/frame/components/context/SharedUIContext' + +import styles from './SearchOverlay.module.scss' type Props = { searchOverlayOpen: boolean @@ -783,36 +781,6 @@ export function SearchOverlay({ }} />
- - - { - if (await getIsStaff()) { - // Hubbers users use an internal discussion for feedback - window.open('https://github.com/github/docs-team/discussions/5172', '_blank') - } else { - // public discussion for feedback - window.open('https://github.com/orgs/community/discussions/164214', '_blank') - } - }} - as="button" - > - {t('search.overlay.give_feedback')} - - Date: Thu, 7 Aug 2025 12:33:09 -0700 Subject: [PATCH 2/4] add development placeholders and toggles for landing page redesign (#56981) --- data/ui.yml | 11 + src/fixtures/fixtures/data/ui.yml | 11 + .../ui/ScrollButton/ScrollButton.tsx | 10 +- src/frame/lib/frontmatter.js | 8 + src/frame/middleware/context/generic-toc.ts | 26 ++- .../components/bespoke/BespokeLanding.tsx | 29 +++ .../components/discovery/DiscoveryLanding.tsx | 28 +++ .../components/journey/JourneyLanding.tsx | 17 ++ .../LandingArticleGridWithFilter.module.scss | 36 +++ .../shared/LandingArticleGridWithFilter.tsx | 210 ++++++++++++++++++ .../components/shared/LandingCarousel.tsx | 50 +++++ .../components/shared/LandingHero.tsx | 18 ++ src/landings/context/BespokeContext.tsx | 49 ++++ src/landings/context/DiscoveryContext.tsx | 61 +++++ src/landings/context/JourneyContext.tsx | 49 ++++ src/landings/pages/product.tsx | 61 ++++- 16 files changed, 668 insertions(+), 6 deletions(-) create mode 100644 src/landings/components/bespoke/BespokeLanding.tsx create mode 100644 src/landings/components/discovery/DiscoveryLanding.tsx create mode 100644 src/landings/components/journey/JourneyLanding.tsx create mode 100644 src/landings/components/shared/LandingArticleGridWithFilter.module.scss create mode 100644 src/landings/components/shared/LandingArticleGridWithFilter.tsx create mode 100644 src/landings/components/shared/LandingCarousel.tsx create mode 100644 src/landings/components/shared/LandingHero.tsx create mode 100644 src/landings/context/BespokeContext.tsx create mode 100644 src/landings/context/DiscoveryContext.tsx create mode 100644 src/landings/context/JourneyContext.tsx diff --git a/data/ui.yml b/data/ui.yml index 319655cefa7e..d749d965157a 100644 --- a/data/ui.yml +++ b/data/ui.yml @@ -260,6 +260,17 @@ footer: expert_services: Expert services blog: Blog machine: Some of this content may be machine- or AI-translated. +bespoke_landing: + articles: Articles + all_categories: All categories + search_articles: Search articles +discovery_landing: + recommended: Recommended + articles: Articles + all_categories: All categories + search_articles: Search articles +journey_landing: + articles: '{{ number }} Articles' product_landing: quickstart: Quickstart reference: Reference diff --git a/src/fixtures/fixtures/data/ui.yml b/src/fixtures/fixtures/data/ui.yml index 319655cefa7e..d749d965157a 100644 --- a/src/fixtures/fixtures/data/ui.yml +++ b/src/fixtures/fixtures/data/ui.yml @@ -260,6 +260,17 @@ footer: expert_services: Expert services blog: Blog machine: Some of this content may be machine- or AI-translated. +bespoke_landing: + articles: Articles + all_categories: All categories + search_articles: Search articles +discovery_landing: + recommended: Recommended + articles: Articles + all_categories: All categories + search_articles: Search articles +journey_landing: + articles: '{{ number }} Articles' product_landing: quickstart: Quickstart reference: Reference diff --git a/src/frame/components/ui/ScrollButton/ScrollButton.tsx b/src/frame/components/ui/ScrollButton/ScrollButton.tsx index 59149ebde57e..6cce1a4e62a1 100644 --- a/src/frame/components/ui/ScrollButton/ScrollButton.tsx +++ b/src/frame/components/ui/ScrollButton/ScrollButton.tsx @@ -18,6 +18,14 @@ export const ScrollButton = ({ className, ariaLabel }: ScrollButtonPropsT) => { // We cannot determine document.documentElement.scrollTop height because we set the height: 100vh and set overflow to auto to keep the header sticky // That means window.scrollTop height is always 0 // Using IntersectionObserver we can determine if the h1 header is in view or not. If not, we show the scroll to top button, if so, we hide it + const h1Element = document.getElementsByTagName('h1')[0] + if (!h1Element) { + if (process.env.NODE_ENV !== 'production') { + throw new Error('No h1 element found in the document.') + } + return + } + const observer = new IntersectionObserver( function (entries) { if (entries[0].isIntersecting === false) { @@ -28,7 +36,7 @@ export const ScrollButton = ({ className, ariaLabel }: ScrollButtonPropsT) => { }, { threshold: [0] }, ) - observer.observe(document.getElementsByTagName('h1')[0]) + observer.observe(h1Element) return () => { observer.disconnect() } diff --git a/src/frame/lib/frontmatter.js b/src/frame/lib/frontmatter.js index 1483278ec3f7..013fe4482c32 100644 --- a/src/frame/lib/frontmatter.js +++ b/src/frame/lib/frontmatter.js @@ -14,6 +14,9 @@ const layoutNames = [ 'release-notes', 'inline', 'category-landing', + 'bespoke-landing', + 'discovery-landing', + 'journey-landing', false, ] @@ -325,6 +328,11 @@ export const schema = { }, description: 'Array of articles to feature in the spotlight section', }, + // Recommended configuration for category landing pages + recommended: { + type: 'array', + description: 'Array of articles to feature in the carousel section', + }, }, } diff --git a/src/frame/middleware/context/generic-toc.ts b/src/frame/middleware/context/generic-toc.ts index c15a74eaa146..948a2d712f51 100644 --- a/src/frame/middleware/context/generic-toc.ts +++ b/src/frame/middleware/context/generic-toc.ts @@ -3,6 +3,24 @@ import type { Response, NextFunction } from 'express' import type { ExtendedRequest, Context, Tree, ToC } from '@/types' import findPageInSiteTree from '@/frame/lib/find-page-in-site-tree' +function isNewLandingPage(currentLayoutName: string): boolean { + return ( + currentLayoutName === 'category-landing' || + currentLayoutName === 'bespoke_landing' || + currentLayoutName === 'discovery_landing' || + currentLayoutName === 'journey_landing' + ) +} + +// TODO: TEMP: This is a temporary solution to turn off/on new landing pages while we develop them. +function isNewLandingPageFeature(req: ExtendedRequest): boolean { + return ( + req.query?.feature === 'bespoke-landing' || + req.query?.feature === 'journey-landing' || + req.query?.feature === 'discovery-landing' + ) +} + // This module adds either flatTocItems or nestedTocItems to the context object for // product, category, and subcategory TOCs that don't have other layouts specified. // They are rendered by includes/generic-toc-flat.html or includes/generic-toc-nested.html. @@ -10,8 +28,9 @@ export default async function genericToc(req: ExtendedRequest, res: Response, ne if (!req.context) throw new Error('request not contextualized') if (!req.context.page) return next() if ( + !isNewLandingPageFeature(req) && req.context.currentLayoutName !== 'default' && - req.context.currentLayoutName !== 'category-landing' + !isNewLandingPage(req.context.currentLayoutName || '') ) return next() // This middleware can only run on product, category, and subcategories. @@ -96,7 +115,10 @@ export default async function genericToc(req: ExtendedRequest, res: Response, ne renderIntros = false req.context.genericTocNested = await getTocItems(treePage, req.context, { recurse: isRecursive, - renderIntros: req.context.currentLayoutName === 'category-landing' ? true : false, + renderIntros: + isNewLandingPageFeature(req) || isNewLandingPage(req.context.currentLayoutName || '') + ? true + : false, includeHidden, }) } diff --git a/src/landings/components/bespoke/BespokeLanding.tsx b/src/landings/components/bespoke/BespokeLanding.tsx new file mode 100644 index 000000000000..29746737c2dc --- /dev/null +++ b/src/landings/components/bespoke/BespokeLanding.tsx @@ -0,0 +1,29 @@ +import { useMemo } from 'react' + +import { DefaultLayout } from '@/frame/components/DefaultLayout' +import { useBespokeContext } from '@/landings/context/BespokeContext' +import { LandingHero } from '@/landings/components/shared/LandingHero' +import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter' + +import type { ArticleCardItems } from '@/landings/types' + +export const BespokeLanding = () => { + const { title, intro, tocItems } = useBespokeContext() + + const flatArticles: ArticleCardItems = useMemo( + () => tocItems.flatMap((item) => item.childTocItems || []), + [tocItems], + ) + + return ( + +
+ + +
+ +
+
+
+ ) +} diff --git a/src/landings/components/discovery/DiscoveryLanding.tsx b/src/landings/components/discovery/DiscoveryLanding.tsx new file mode 100644 index 000000000000..4bd247378cf5 --- /dev/null +++ b/src/landings/components/discovery/DiscoveryLanding.tsx @@ -0,0 +1,28 @@ +import { useMemo } from 'react' + +import { DefaultLayout } from '@/frame/components/DefaultLayout' +import { useDiscoveryContext } from '@/landings/context/DiscoveryContext' +import { LandingHero } from '@/landings/components/shared/LandingHero' +import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter' +import { LandingCarousel } from '@/landings/components/shared/LandingCarousel' + +import type { ArticleCardItems } from '@/landings/types' + +export const DiscoveryLanding = () => { + const { title, intro, tocItems, recommended } = useDiscoveryContext() + + const flatArticles: ArticleCardItems = useMemo( + () => tocItems.flatMap((item) => item.childTocItems || []), + [tocItems], + ) + + return ( + +
+ + + +
+
+ ) +} diff --git a/src/landings/components/journey/JourneyLanding.tsx b/src/landings/components/journey/JourneyLanding.tsx new file mode 100644 index 000000000000..9ce831ef7610 --- /dev/null +++ b/src/landings/components/journey/JourneyLanding.tsx @@ -0,0 +1,17 @@ +import { DefaultLayout } from '@/frame/components/DefaultLayout' +import { useJourneyContext } from '@/landings/context/JourneyContext' +import { LandingHero } from '@/landings/components/shared/LandingHero' + +export const JourneyLanding = () => { + const { title, intro } = useJourneyContext() + + return ( + +
+ + +
TODO
+
+
+ ) +} diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.module.scss b/src/landings/components/shared/LandingArticleGridWithFilter.module.scss new file mode 100644 index 000000000000..3729fb1b82f3 --- /dev/null +++ b/src/landings/components/shared/LandingArticleGridWithFilter.module.scss @@ -0,0 +1,36 @@ +@import "@primer/css/support/variables/layout.scss"; +@import "@primer/css/support/mixins/layout.scss"; + +.articleGrid { + display: grid; + gap: 1.5rem; + + // Mobile: 1 column + grid-template-columns: 1fr; + + // Tablet: 2 columns + @include breakpoint(md) { + grid-template-columns: repeat(2, 1fr); + } + + // Desktop: 3 columns + @include breakpoint(lg) { + grid-template-columns: repeat(3, 1fr); + } +} + +.articleCard { + display: flex; + flex-direction: column; + height: 100%; +} + +.cardContent { + display: flex; + flex-direction: column; + height: 100%; +} + +.cardFooter { + margin-top: auto; +} diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.tsx b/src/landings/components/shared/LandingArticleGridWithFilter.tsx new file mode 100644 index 000000000000..df6f5798812d --- /dev/null +++ b/src/landings/components/shared/LandingArticleGridWithFilter.tsx @@ -0,0 +1,210 @@ +import { useState, useRef } from 'react' +import { TextInput, ActionMenu, ActionList, Button, Box } from '@primer/react' +import { SearchIcon } from '@primer/octicons-react' +import cx from 'classnames' + +import { Link } from '@/frame/components/Link' +import { ArticleCardItems, ChildTocItem } from '@/landings/types' +import { getOcticonComponent } from '@/landings/lib/octicons' + +import styles from './LandingArticleGridWithFilter.module.scss' + +type ArticleGridProps = { + flatArticles: ArticleCardItems +} + +export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => { + const [searchQuery, setSearchQuery] = useState('') + const [selectedCategory, setSelectedCategory] = useState('All') + const [selectedCategoryIndex, setSelectedCategoryIndex] = useState(0) + + const inputRef = useRef(null) + + // Extract unique categories from the articles + const categories: string[] = [ + 'All', + ...new Set(flatArticles.flatMap((item) => item.category || [])), + ] + + const applyFilters = () => { + let results = flatArticles + + if (searchQuery) { + results = results.filter((token) => { + return Object.values(token).some((value) => { + if (typeof value === 'string') { + return value.toLowerCase().includes(searchQuery.toLowerCase()) + } else if (Array.isArray(value)) { + return value.some((item) => { + if (typeof item === 'string') { + return item.toLowerCase().includes(searchQuery.toLowerCase()) + } + }) + } + return false + }) + }) + } + + if (selectedCategory !== 'All') { + results = results.filter((item) => item.category?.includes(selectedCategory)) + } + + return results + } + + const filteredResults = applyFilters() + + const handleSearch = (query: string) => { + setSearchQuery(query) + } + + const handleFilter = (option: string, index: number) => { + setSelectedCategory(option) + setSelectedCategoryIndex(index) + } + + const handleResetFilter = () => { + setSearchQuery('') + setSelectedCategory('All') + setSelectedCategoryIndex(0) + if (inputRef.current) { + inputRef.current.value = '' + } + } + + return ( +
+

+ TODO: Article grid placeholder +

+ {/* Filter and Search Controls */} +
+
+
e.preventDefault()}> + { + const query = e.target.value || '' + handleSearch(query) + }} + /> + +
+
+ + + + Category: + {' '} + {categories[selectedCategoryIndex]} + + + + {categories.map((category, index) => ( + handleFilter(category, index)} + > + {category} + + ))} + + + + + +
+
+ + {/* Results Grid */} +
+ {filteredResults.map((article, index) => ( + + ))} + {filteredResults.length === 0 && ( +
+

No articles found matching your criteria.

+
+ )} +
+
+ ) +} + +type ArticleCardProps = { + article: ChildTocItem +} + +const ArticleCard = ({ article }: ArticleCardProps) => { + const IconComponent = getOcticonComponent(article.octicon || undefined) + + return ( + +
+
+ {IconComponent && ( + + )} +
+

+ + {article.title} + +

+ {article.intro &&

{article.intro}

} +
+
+ + {/* Categories */} + {article.category && article.category.length > 0 && ( +
+ {article.category.map((cat, index) => ( + + {cat} + + ))} +
+ )} + + {/* Complexity */} + {article.complexity && article.complexity.length > 0 && ( +
+ {article.complexity.map((comp, index) => ( + + {comp} + + ))} +
+ )} + +
+ + Read article → + +
+
+
+ ) +} diff --git a/src/landings/components/shared/LandingCarousel.tsx b/src/landings/components/shared/LandingCarousel.tsx new file mode 100644 index 000000000000..46082777abfe --- /dev/null +++ b/src/landings/components/shared/LandingCarousel.tsx @@ -0,0 +1,50 @@ +import type { TocItem } from '@/landings/types' + +type LandingCarouselProps = { + recommended?: string[] // Array of article paths + flatArticles: TocItem[] +} + +export const LandingCarousel = ({ flatArticles, recommended }: LandingCarouselProps) => { + // Helper function to find article data from tocItems + const findArticleData = (articlePath: string) => { + const cleanPath = articlePath.startsWith('/') ? articlePath.slice(1) : articlePath + return flatArticles.find( + (item) => item.fullPath && cleanPath.split('/').pop() === item.fullPath.split('/').pop(), + ) + } + + // Process recommended items to get article data + const processedRecommendedItems = + recommended?.map((recommendedArticlePath) => { + const articleData = findArticleData(recommendedArticlePath) + return { + article: recommendedArticlePath, + title: articleData?.title || 'Unknown Article', + description: articleData?.intro || '', + url: articleData?.fullPath || recommendedArticlePath, + } + }) || [] + + return ( +
+

+ TODO: Carousel placeholder +

+
    + {processedRecommendedItems.map((article) => ( +
  • + +

    {article.title}

    +
    +

    {article.description}

    +
  • + ))} +
+
+ ) +} diff --git a/src/landings/components/shared/LandingHero.tsx b/src/landings/components/shared/LandingHero.tsx new file mode 100644 index 000000000000..fc717b2799a7 --- /dev/null +++ b/src/landings/components/shared/LandingHero.tsx @@ -0,0 +1,18 @@ +import { Lead } from '@/frame/components/ui/Lead/Lead' + +type LandingHeroProps = { + title: string + intro?: string +} + +export const LandingHero = ({ title, intro }: LandingHeroProps) => { + return ( +
+
+

TODO: Landing hero placeholder

+

{title}

+ {intro && {intro}} +
+
+ ) +} diff --git a/src/landings/context/BespokeContext.tsx b/src/landings/context/BespokeContext.tsx new file mode 100644 index 000000000000..f7bd5b9ca940 --- /dev/null +++ b/src/landings/context/BespokeContext.tsx @@ -0,0 +1,49 @@ +import { createContext, useContext } from 'react' +import { FeaturedLink, getFeaturedLinksFromReq } from '@/landings/components/ProductLandingContext' +import { mapRawTocItemToTocItem } from '@/landings/types' +import type { TocItem } from '@/landings/types' +import type { LearningTrack } from '@/types' + +export type BespokeContextT = { + title: string + intro: string + productCallout: string + permissions: string + tocItems: Array + variant?: 'compact' | 'expanded' + featuredLinks: Record> + renderedPage: string + currentLearningTrack?: LearningTrack + currentLayout: string +} + +export const BespokeContext = createContext(null) + +export const useBespokeContext = (): BespokeContextT => { + const context = useContext(BespokeContext) + + if (!context) { + throw new Error('"useBespokeContext" may only be used inside "BespokeContext.Provider"') + } + + return context +} + +export const getBespokeContextFromRequest = async (req: any): Promise => { + const page = req.context.page + + return { + title: page.title, + productCallout: page.product || '', + permissions: page.permissions || '', + intro: page.intro, + tocItems: (req.context.genericTocFlat || req.context.genericTocNested || []).map( + mapRawTocItemToTocItem, + ), + variant: req.context.genericTocFlat ? 'expanded' : 'compact', + featuredLinks: getFeaturedLinksFromReq(req), + renderedPage: req.context.renderedPage, + currentLearningTrack: req.context.currentLearningTrack, + currentLayout: req.context.currentLayoutName, + } +} diff --git a/src/landings/context/DiscoveryContext.tsx b/src/landings/context/DiscoveryContext.tsx new file mode 100644 index 000000000000..c4b13f2702d6 --- /dev/null +++ b/src/landings/context/DiscoveryContext.tsx @@ -0,0 +1,61 @@ +import { createContext, useContext } from 'react' +import { FeaturedLink, getFeaturedLinksFromReq } from '@/landings/components/ProductLandingContext' +import { mapRawTocItemToTocItem } from '@/landings/types' +import type { TocItem } from '@/landings/types' +import type { LearningTrack } from '@/types' + +export type DiscoveryContextT = { + title: string + intro: string + productCallout: string + permissions: string + tocItems: Array + variant?: 'compact' | 'expanded' + featuredLinks: Record> + renderedPage: string + currentLearningTrack?: LearningTrack + currentLayout: string + recommended?: string[] // Array of article paths +} + +export const DiscoveryContext = createContext(null) + +export const useDiscoveryContext = (): DiscoveryContextT => { + const context = useContext(DiscoveryContext) + + if (!context) { + throw new Error('"useDiscoveryContext" may only be used inside "DiscoveryContext.Provider"') + } + + return context +} + +export const getDiscoveryContextFromRequest = async (req: any): Promise => { + const page = req.context.page + + // Support legacy `spotlight` property as `recommended` for pages like Copilot Cookbook + // However, `spotlight` will have lower priority than the `recommended` property + let recommended: string[] = [] + if (page.recommended && page.recommended.length > 0) { + recommended = page.recommended + } else if (page.spotlight && page.spotlight.length > 0) { + // Remove the `image` property from spotlight items, since we don't use those for the carousel + recommended = page.spotlight.map((item: any) => item.article) + } + + return { + title: page.title, + productCallout: page.product || '', + permissions: page.permissions || '', + intro: page.intro, + tocItems: (req.context.genericTocFlat || req.context.genericTocNested || []).map( + mapRawTocItemToTocItem, + ), + variant: req.context.genericTocFlat ? 'expanded' : 'compact', + featuredLinks: getFeaturedLinksFromReq(req), + renderedPage: req.context.renderedPage, + currentLearningTrack: req.context.currentLearningTrack, + currentLayout: req.context.currentLayoutName, + recommended, + } +} diff --git a/src/landings/context/JourneyContext.tsx b/src/landings/context/JourneyContext.tsx new file mode 100644 index 000000000000..47c0c8340172 --- /dev/null +++ b/src/landings/context/JourneyContext.tsx @@ -0,0 +1,49 @@ +import { createContext, useContext } from 'react' +import { FeaturedLink, getFeaturedLinksFromReq } from '@/landings/components/ProductLandingContext' +import { mapRawTocItemToTocItem } from '@/landings/types' +import type { TocItem } from '@/landings/types' +import type { LearningTrack } from '@/types' + +export type JourneyContextT = { + title: string + intro: string + productCallout: string + permissions: string + tocItems: Array + variant?: 'compact' | 'expanded' + featuredLinks: Record> + renderedPage: string + currentLearningTrack?: LearningTrack + currentLayout: string +} + +export const JourneyContext = createContext(null) + +export const useJourneyContext = (): JourneyContextT => { + const context = useContext(JourneyContext) + + if (!context) { + throw new Error('"useJourneyContext" may only be used inside "JourneyContext.Provider"') + } + + return context +} + +export const getJourneyContextFromRequest = async (req: any): Promise => { + const page = req.context.page + + return { + title: page.title, + productCallout: page.product || '', + permissions: page.permissions || '', + intro: page.intro, + tocItems: (req.context.genericTocFlat || req.context.genericTocNested || []).map( + mapRawTocItemToTocItem, + ), + variant: req.context.genericTocFlat ? 'expanded' : 'compact', + featuredLinks: getFeaturedLinksFromReq(req), + renderedPage: req.context.renderedPage, + currentLearningTrack: req.context.currentLearningTrack, + currentLayout: req.context.currentLayoutName, + } +} diff --git a/src/landings/pages/product.tsx b/src/landings/pages/product.tsx index 44b41bbddf7f..eeed5a9c96d6 100644 --- a/src/landings/pages/product.tsx +++ b/src/landings/pages/product.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react' import { GetServerSideProps } from 'next' import { useRouter } from 'next/router' @@ -46,7 +47,24 @@ import { CategoryLandingContext, CategoryLandingContextT, } from '@/frame/components/context/CategoryLandingContext' -import { useEffect } from 'react' +import { BespokeLanding } from '@/landings/components/bespoke/BespokeLanding' +import { + BespokeContext, + getBespokeContextFromRequest, + BespokeContextT, +} from '@/landings/context/BespokeContext' +import { DiscoveryLanding } from '@/landings/components/discovery/DiscoveryLanding' +import { + DiscoveryContext, + DiscoveryContextT, + getDiscoveryContextFromRequest, +} from '@/landings/context/DiscoveryContext' +import { JourneyLanding } from '@/landings/components/journey/JourneyLanding' +import { + getJourneyContextFromRequest, + JourneyContext, + JourneyContextT, +} from '@/landings/context/JourneyContext' function initiateArticleScripts() { copyCode() @@ -61,6 +79,9 @@ type Props = { tocLandingContext?: TocLandingContextT articleContext?: ArticleContextT categoryLandingContext?: CategoryLandingContextT + bespokeContext?: BespokeContextT + discoveryContext?: DiscoveryContextT + journeyContext?: JourneyContextT } const GlobalPage = ({ mainContext, @@ -69,6 +90,9 @@ const GlobalPage = ({ tocLandingContext, articleContext, categoryLandingContext, + bespokeContext, + journeyContext, + discoveryContext, }: Props) => { const router = useRouter() @@ -82,7 +106,25 @@ const GlobalPage = ({ }, [router.events]) let content - if (productLandingContext) { + if (bespokeContext) { + content = ( + + + + ) + } else if (discoveryContext) { + content = ( + + + + ) + } else if (journeyContext) { + content = ( + + + + ) + } else if (productLandingContext) { content = ( @@ -140,7 +182,20 @@ export const getServerSideProps: GetServerSideProps = async (context) => const additionalUINamespaces: string[] = [] // This looks a little funky, but it's so we only send one context's data to the client - if (currentLayoutName === 'product-landing') { + // TODO: TEMP: This is a temporary solution to turn off/on new landing pages while we develop them + if (currentLayoutName === 'bespoke-landing' || req.query?.feature === 'bespoke-landing') { + props.bespokeContext = await getBespokeContextFromRequest(req) + additionalUINamespaces.push('bespoke_landing') + } else if (currentLayoutName === 'journey-landing' || req.query?.feature === 'journey-landing') { + props.journeyContext = await getJourneyContextFromRequest(req) + additionalUINamespaces.push('journey_landing') + } else if ( + currentLayoutName === 'discovery-landing' || + req?.query?.feature === 'discovery-landing' + ) { + props.discoveryContext = await getDiscoveryContextFromRequest(req) + additionalUINamespaces.push('discovery_landing') + } else if (currentLayoutName === 'product-landing') { props.productLandingContext = await getProductLandingContextFromRequest(req) additionalUINamespaces.push('product_landing') } else if (currentLayoutName === 'product-guides') { From 5665a1eda047eb4681eaa757b9f1458478cbf549 Mon Sep 17 00:00:00 2001 From: Sophie <29382425+sophietheking@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:56:56 +0200 Subject: [PATCH 3/4] Contributing Guidelines now visible in repository tab and sidebar (#56962) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Felicity Chapman --- .../setting-guidelines-for-repository-contributors.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/content/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors.md b/content/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors.md index 67409e16d30a..396864b3f4bf 100644 --- a/content/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors.md +++ b/content/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors.md @@ -17,6 +17,11 @@ shortTitle: Contributor guidelines To help your project contributors do good work, you can add a file with contribution guidelines to your project repository's root, `docs`, or `.github` folder. When someone opens a pull request or creates an issue, they will see a link to that file. {% ifversion fpt or ghec %}The link to the contributing guidelines also appears on your repository's `contribute` page. For an example of a `contribute` page, see [github/docs/contribute](https://github.com/github/docs/contribute).{% endif %} +{% ifversion fpt or ghec or ghes > 3.18 %}If your repository includes a `CONTRIBUTING.md` file, {% data variables.product.github %} also surfaces it in two other places to make it easier for contributors to discover: + +* A "{% octicon "people" aria-hidden="true" aria-label="people" %} Contributing" tab in the repository overview (next to the "{% octicon "book" aria-hidden="true" aria-label="book" %} README" and "{% octicon "code-of-conduct" aria-hidden="true" aria-label="code-of-conduct" %} Code of conduct") +* A "Contributing" link in the repository sidebar{% endif %} + For the repository owner, contribution guidelines are a way to communicate how people should contribute. For contributors, the guidelines help them verify that they're submitting well-formed pull requests and opening useful issues. @@ -28,7 +33,7 @@ You can create default contribution guidelines for your organization or personal > [!TIP] > Repository maintainers can set specific guidelines for issues by creating an issue or pull request template for the repository. For more information, see [AUTOTITLE](/communities/using-templates-to-encourage-useful-issues-and-pull-requests/about-issue-and-pull-request-templates). -## Adding a _CONTRIBUTING_ file +## Adding a `CONTRIBUTING.md` file {% data reusables.repositories.navigate-to-repo %} {% data reusables.files.add-file %} From d13b1f0fcc5d1ddc64ef2c2995caeefdcd90892c Mon Sep 17 00:00:00 2001 From: Sarita Iyer <66540150+saritai@users.noreply.github.com> Date: Thu, 7 Aug 2025 16:07:18 -0400 Subject: [PATCH 4/4] Create docs-internal PR for Early Access preview (#57031)