Change product landing page introLinks to a general map of link titles to links (#26360)
* Add cta button to frontmatter and context * Add cta to Page * Render cta button in product landing hero * Handle external links * Add simple unit test for cta link * Address feedback Co-authored-by: Peter Bengtsson <mail@peterbe.com> * Actually push condition update * Show it's an external link * Refactor FullLink so we use Link once Co-authored-by: Peter Bengtsson <mail@peterbe.com> * Custom link can also be null * Rename 'cta' to 'custom' and make it the last introLink * Update tests with 'cta' to 'custom' change * Filter once * Revert "Filter once" This reverts commit a3f9a8a06b505d77fed47ca96a66c187c86c3c91. * Update introLinks to a map of titles and URLs * No more custom introLink in LandingHero * Simplify introLinks processing * introLinks can also be null Co-authored-by: Peter Bengtsson <mail@peterbe.com> * more concise Co-authored-by: Peter Bengtsson <mail@peterbe.com> * No longer necessary with the a plain introLinks map Co-authored-by: Peter Bengtsson <mail@peterbe.com> * '.entries()` is simpler Co-authored-by: Peter Bengtsson <mail@peterbe.com> * 'link' could be false depending on what version you're on * Update test for new introLinks Co-authored-by: Peter Bengtsson <mail@peterbe.com>
This commit is contained in:
@@ -38,11 +38,7 @@ export type ProductLandingContextT = {
|
|||||||
intro: string
|
intro: string
|
||||||
beta_product: boolean
|
beta_product: boolean
|
||||||
product: Product
|
product: Product
|
||||||
introLinks: {
|
introLinks: Record<string, string> | null
|
||||||
quickstart?: string
|
|
||||||
reference?: string
|
|
||||||
overview?: string
|
|
||||||
} | null
|
|
||||||
product_video?: string
|
product_video?: string
|
||||||
featuredLinks: Record<string, Array<FeaturedLink>>
|
featuredLinks: Record<string, Array<FeaturedLink>>
|
||||||
productCodeExamples: Array<CodeExample>
|
productCodeExamples: Array<CodeExample>
|
||||||
@@ -128,13 +124,7 @@ export const getProductLandingContextFromRequest = (req: any): ProductLandingCon
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|
||||||
introLinks: page.introLinks
|
introLinks: page.introLinks || null,
|
||||||
? {
|
|
||||||
quickstart: page.introLinks.quickstart,
|
|
||||||
reference: page.introLinks.reference,
|
|
||||||
overview: page.introLinks.overview,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
|
|
||||||
featuredLinks: getFeaturedLinksFromReq(req),
|
featuredLinks: getFeaturedLinksFromReq(req),
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useMainContext } from 'components/context/MainContext'
|
import { LinkExternalIcon } from '@primer/octicons-react'
|
||||||
|
|
||||||
|
import { useMainContext } from 'components/context/MainContext'
|
||||||
import { Link } from 'components/Link'
|
import { Link } from 'components/Link'
|
||||||
import { useProductLandingContext } from 'components/context/ProductLandingContext'
|
import { useProductLandingContext } from 'components/context/ProductLandingContext'
|
||||||
import { useTranslation } from 'components/hooks/useTranslation'
|
import { useTranslation } from 'components/hooks/useTranslation'
|
||||||
@@ -46,7 +47,7 @@ export const LandingHero = () => {
|
|||||||
href={link}
|
href={link}
|
||||||
className={cx('btn btn-large f4 mt-3 mr-3 ', i === 0 && 'btn-primary')}
|
className={cx('btn btn-large f4 mt-3 mr-3 ', i === 0 && 'btn-primary')}
|
||||||
>
|
>
|
||||||
{t(key)}
|
{t(key) || key}
|
||||||
</FullLink>
|
</FullLink>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -72,7 +73,8 @@ export const LandingHero = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fully Qualified Link - it includes the version and locale in the path
|
// Fully Qualified Link - it includes the version and locale in the path if
|
||||||
|
// the href is not an external link.
|
||||||
type Props = {
|
type Props = {
|
||||||
href: string
|
href: string
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@@ -81,13 +83,24 @@ type Props = {
|
|||||||
export const FullLink = ({ href, children, className }: Props) => {
|
export const FullLink = ({ href, children, className }: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { currentVersion } = useVersion()
|
const { currentVersion } = useVersion()
|
||||||
const locale = router.locale || 'en'
|
|
||||||
const fullyQualifiedHref = `/${locale}${
|
const isExternal = href.startsWith('https')
|
||||||
currentVersion !== 'free-pro-team@latest' ? `/${currentVersion}` : ''
|
let linkHref = href
|
||||||
}${href}`
|
if (!isExternal) {
|
||||||
|
const locale = router.locale || 'en'
|
||||||
|
linkHref = `/${locale}${
|
||||||
|
currentVersion !== 'free-pro-team@latest' ? `/${currentVersion}` : ''
|
||||||
|
}${href}`
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={fullyQualifiedHref} className={className}>
|
<Link href={linkHref} className={className}>
|
||||||
{children}
|
{children}{' '}
|
||||||
|
{isExternal && (
|
||||||
|
<span className="ml-1">
|
||||||
|
<LinkExternalIcon size="small" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,11 +77,6 @@ export const schema = {
|
|||||||
},
|
},
|
||||||
introLinks: {
|
introLinks: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
|
||||||
quickstart: { type: 'string' },
|
|
||||||
reference: { type: 'string' },
|
|
||||||
overview: { type: 'string' },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
authors: {
|
authors: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|||||||
31
lib/page.js
31
lib/page.js
@@ -90,12 +90,7 @@ class Page {
|
|||||||
this.rawLearningTracks = this.learningTracks
|
this.rawLearningTracks = this.learningTracks
|
||||||
this.rawIncludeGuides = this.includeGuides
|
this.rawIncludeGuides = this.includeGuides
|
||||||
this.raw_product_video = this.product_video
|
this.raw_product_video = this.product_video
|
||||||
|
this.rawIntroLinks = this.introLinks
|
||||||
if (this.introLinks) {
|
|
||||||
this.introLinks.rawQuickstart = this.introLinks.quickstart
|
|
||||||
this.introLinks.rawReference = this.introLinks.reference
|
|
||||||
this.introLinks.rawOverview = this.introLinks.overview
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this the Homepage or a Product, Category, Topic, or Article?
|
// Is this the Homepage or a Product, Category, Topic, or Article?
|
||||||
this.documentType = getDocumentType(this.relativePath)
|
this.documentType = getDocumentType(this.relativePath)
|
||||||
@@ -209,18 +204,6 @@ class Page {
|
|||||||
|
|
||||||
this.product_video = await renderContent(this.raw_product_video, context, { textOnly: true })
|
this.product_video = await renderContent(this.raw_product_video, context, { textOnly: true })
|
||||||
|
|
||||||
if (this.introLinks) {
|
|
||||||
this.introLinks.quickstart = await renderContent(this.introLinks.rawQuickstart, context, {
|
|
||||||
textOnly: true,
|
|
||||||
})
|
|
||||||
this.introLinks.reference = await renderContent(this.introLinks.rawReference, context, {
|
|
||||||
textOnly: true,
|
|
||||||
})
|
|
||||||
this.introLinks.overview = await renderContent(this.introLinks.rawOverview, context, {
|
|
||||||
textOnly: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
context.relativePath = this.relativePath
|
context.relativePath = this.relativePath
|
||||||
const html = await renderContentCacheByContext('markdown')(this.markdown, context)
|
const html = await renderContentCacheByContext('markdown')(this.markdown, context)
|
||||||
|
|
||||||
@@ -260,6 +243,18 @@ class Page {
|
|||||||
this.learningTracks = learningTracks
|
this.learningTracks = learningTracks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// introLinks may contain Liquid and need to have versioning processed.
|
||||||
|
if (this.rawIntroLinks) {
|
||||||
|
const introLinks = {}
|
||||||
|
for (const [rawKey, value] of Object.entries(this.rawIntroLinks)) {
|
||||||
|
introLinks[rawKey] = await renderContent(value, context, {
|
||||||
|
textOnly: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.introLinks = introLinks
|
||||||
|
}
|
||||||
|
|
||||||
if (this.rawIncludeGuides) {
|
if (this.rawIncludeGuides) {
|
||||||
this.allTopics = []
|
this.allTopics = []
|
||||||
this.includeGuides = await getLinkData(this.rawIncludeGuides, context)
|
this.includeGuides = await getLinkData(this.rawIncludeGuides, context)
|
||||||
|
|||||||
9
tests/fixtures/article-with-introLinks.md
vendored
Normal file
9
tests/fixtures/article-with-introLinks.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Article with introLinks
|
||||||
|
versions:
|
||||||
|
ghec: '*'
|
||||||
|
ghes: '*'
|
||||||
|
introLinks:
|
||||||
|
overview: 'https://github.com'
|
||||||
|
custom link!: 'https://github.com/features'
|
||||||
|
---
|
||||||
@@ -609,6 +609,21 @@ describe('Page class', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('introLinks', () => {
|
||||||
|
it('includes the links specified in the introLinks frontmatter', async () => {
|
||||||
|
const page = await Page.init({
|
||||||
|
relativePath: 'article-with-introLinks.md',
|
||||||
|
basePath: path.join(__dirname, '../fixtures'),
|
||||||
|
languageCode: 'en',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(page.introLinks).toStrictEqual({
|
||||||
|
overview: 'https://github.com',
|
||||||
|
'custom link!': 'https://github.com/features',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Page.parseFrontmatter()', () => {
|
describe('Page.parseFrontmatter()', () => {
|
||||||
it('throws an error on bad input', () => {
|
it('throws an error on bad input', () => {
|
||||||
const markdown = null
|
const markdown = null
|
||||||
|
|||||||
Reference in New Issue
Block a user