diff --git a/components/context/ProductLandingContext.tsx b/components/context/ProductLandingContext.tsx index 08a02cfea5cf..a7f21a98abfe 100644 --- a/components/context/ProductLandingContext.tsx +++ b/components/context/ProductLandingContext.tsx @@ -87,9 +87,9 @@ export const getFeaturedLinksFromReq = (req: any): Record) || []).map((entry: any) => ({ href: entry.href, title: entry.title, - intro: entry.intro, - authors: entry.page.authors || [], - fullTitle: entry.fullTitle, + intro: entry.intro || null, + authors: entry.page?.authors || [], + fullTitle: entry.fullTitle || null, })), ] }) @@ -141,13 +141,13 @@ export const getProductLandingContextFromRequest = (req: any): ProductLandingCon featuredArticles: Object.entries(req.context.featuredLinks || []) .filter(([key]) => { - return key === 'guides' || key === 'popular' + return key === 'guides' || key === 'popular' || key === 'videos' }) .map(([key, links]: any) => { return { label: - key === 'popular' - ? req.context.page.featuredLinks.popularHeading || req.context.site.data.ui.toc[key] + key === 'popular' || key === 'videos' + ? req.context.page.featuredLinks[key + 'Heading'] || req.context.site.data.ui.toc[key] : req.context.site.data.ui.toc[key], viewAllHref: key === 'guides' && !req.context.currentCategory && hasGuidesPage @@ -158,9 +158,9 @@ export const getProductLandingContextFromRequest = (req: any): ProductLandingCon hideIntro: key === 'popular', href: link.href, title: link.title, - intro: link.intro, - authors: link.page.authors || [], - fullTitle: link.fullTitle, + intro: link.intro || null, + authors: link.page?.authors || [], + fullTitle: link.fullTitle || null, } }), } diff --git a/data/ui.yml b/data/ui.yml index e692ce51af6c..878c7e43224b 100644 --- a/data/ui.yml +++ b/data/ui.yml @@ -39,6 +39,7 @@ toc: popular: Popular guides: Guides whats_new: What's new + videos: Videos pages: article_version: 'Article version' miniToc: In this article diff --git a/lib/frontmatter.js b/lib/frontmatter.js index fa09fd10551b..9d99dd8c3af8 100644 --- a/lib/frontmatter.js +++ b/lib/frontmatter.js @@ -118,6 +118,20 @@ export const schema = { popularHeading: { type: 'string', }, + videos: { + type: 'array', + items: { + type: 'object', + properties: { + title: 'string', + href: 'string', + }, + }, + }, + // allows you to use an alternate heading for the videos column + videosHeading: { + type: 'string', + }, }, }, // Shown in `product-landing.html` "What's new" section diff --git a/middleware/featured-links.js b/middleware/featured-links.js index 4d8c8690fa72..2b34d66427aa 100644 --- a/middleware/featured-links.js +++ b/middleware/featured-links.js @@ -16,11 +16,17 @@ export default async function featuredLinks(req, res, next) { req.context.featuredLinks = {} for (const key in req.context.page.featuredLinks) { - req.context.featuredLinks[key] = await getLinkData( - req.context.page.featuredLinks[key], - req.context, - { title: true, intro: true, fullTitle: true } - ) + if (key === 'videos') { + // Videos are external URLs so don't run through getLinkData, they're + // objects with title and href properties. + req.context.featuredLinks[key] = req.context.page.featuredLinks[key] + } else { + req.context.featuredLinks[key] = await getLinkData( + req.context.page.featuredLinks[key], + req.context, + { title: true, intro: true, fullTitle: true } + ) + } } return next() diff --git a/tests/fixtures/article-with-videos.md b/tests/fixtures/article-with-videos.md new file mode 100644 index 000000000000..889d863c19de --- /dev/null +++ b/tests/fixtures/article-with-videos.md @@ -0,0 +1,15 @@ +--- +title: Article with featuredLinks +versions: + free-pro-team: '*' +featuredLinks: + videos: + - title: codespaces + href: 'https://www.youtube-nocookie.com/embed/cP0I9w2coGU' + - title: more codespaces + href: 'https://www.youtube-nocookie.com/embed/cP0I9w2coGU' + - title: even more codespaces + href: 'https://www.youtube-nocookie.com/embed/cP0I9w2coGU' + videosHeading: Custom Videos heading +layout: product-landing +--- diff --git a/tests/unit/page.js b/tests/unit/page.js index 127c1ca2c228..1329032205b5 100644 --- a/tests/unit/page.js +++ b/tests/unit/page.js @@ -532,6 +532,37 @@ describe('Page class', () => { }) }) + describe('videos', () => { + let page + + beforeEach(async () => { + page = await Page.init({ + relativePath: 'article-with-videos.md', + basePath: path.join(__dirname, '../fixtures'), + languageCode: 'en', + }) + }) + + it('includes videos specified in the featuredLinks frontmatter', async () => { + expect(page.featuredLinks.videos).toStrictEqual([ + { + title: 'codespaces', + href: 'https://www.youtube-nocookie.com/embed/cP0I9w2coGU', + }, + { + title: 'more codespaces', + href: 'https://www.youtube-nocookie.com/embed/cP0I9w2coGU', + }, + { + title: 'even more codespaces', + href: 'https://www.youtube-nocookie.com/embed/cP0I9w2coGU', + }, + ]) + + expect(page.featuredLinks.videosHeading).toBe('Custom Videos heading') + }) + }) + describe('Page.parseFrontmatter()', () => { it('throws an error on bad input', () => { const markdown = null