diff --git a/site/gatsby-site/playwright/e2e/navigation.spec.ts b/site/gatsby-site/playwright/e2e/navigation.spec.ts index eb5578903f..b27595873a 100644 --- a/site/gatsby-site/playwright/e2e/navigation.spec.ts +++ b/site/gatsby-site/playwright/e2e/navigation.spec.ts @@ -36,4 +36,48 @@ test.describe('Navigation', () => { await page.getByTestId('sidebar-desktop').locator('#sidebar > [data-testid="sidebar-tree"] > [data-testid="sidebar-welcome"] > [data-testid="sidebar-link"]').click(); await expect(page.locator('.rightSideTitle')).not.toBeVisible(); }); + + test('Check right sidebar "Contents" scroll-to section on click on docs', async ({ page }) => { + await page.setViewportSize({ width: 1280, height: 800 }); + + await page.goto('/'); + + const aboutLink = await page.locator('#main-footer .tw-footer-link').filter({ hasText: /^About$/ }); + if (await aboutLink.count() > 0) { + await aboutLink.click(); + + await page.locator('.rightSideTitle:has-text("CONTENTS")').waitFor({ state: 'visible' }); + const listItems = await page.locator('.rightSideBarUL li'); + expect(await listItems.count()).toBeGreaterThanOrEqual(1); + + await listItems.nth(1).click(); + await page.waitForTimeout(700); + + const subject = await page.locator('h2:has-text(\'Why "AI Incidents"?\')'); + const boundingBox = await subject.boundingBox(); + expect(boundingBox?.y).toBeCloseTo(0, 30); + } + }); + + test('Check right sidebar "Contents" scroll-to section on click on prismic blog post', async ({ page }) => { + await page.setViewportSize({ width: 1280, height: 800 }); + + await page.goto('/blog'); + + const postLink = await page.locator('h5:has-text("AI Incident Roundup – January ‘24")'); + if (await postLink.count() > 0) { + await postLink.click(); + + await page.locator('.rightSideTitle:has-text("CONTENTS")').waitFor({ state: 'visible' }); + const listItems = await page.locator('.rightSideBarUL li'); + expect(await listItems.count()).toBeGreaterThanOrEqual(1); + + await listItems.nth(1).click(); + await page.waitForTimeout(700); + + const subject = await page.locator('h2:has-text("🗄 Trending in the AIID")'); + const boundingBox = await subject.boundingBox(); + expect(boundingBox?.y).toBeCloseTo(0, 30); + } + }); }); diff --git a/site/gatsby-site/src/components/CustomHeaders.js b/site/gatsby-site/src/components/CustomHeaders.js new file mode 100644 index 0000000000..7d3f6d3ff9 --- /dev/null +++ b/site/gatsby-site/src/components/CustomHeaders.js @@ -0,0 +1,33 @@ +import React from 'react'; +import slugify from 'slugify'; + +const getText = (children) => { + // Recursively find all text nodes in the children + let text = ''; + + React.Children.forEach(children, (child) => { + if (typeof child === 'string') { + text += child; + } else if (child.props && child.props.children) { + text += getText(child.props.children); + } + }); + return text; +}; + +const Heading = ({ level, children }) => { + const text = getText(children); + + const id = slugify(text, { lower: true }); + + const HeadingComponent = `h${level}`; + + return {children}; +}; + +export const Heading1 = ({ children }) => {children}; +export const Heading2 = ({ children }) => {children}; +export const Heading3 = ({ children }) => {children}; +export const Heading4 = ({ children }) => {children}; +export const Heading5 = ({ children }) => {children}; +export const Heading6 = ({ children }) => {children}; diff --git a/site/gatsby-site/src/components/PrismicOutline.js b/site/gatsby-site/src/components/PrismicOutline.js new file mode 100644 index 0000000000..6625f3f1fe --- /dev/null +++ b/site/gatsby-site/src/components/PrismicOutline.js @@ -0,0 +1,35 @@ +import React from 'react'; + +const PrismicSidebarLayout = ({ tableOfContents }) => { + let navItems; + + if (tableOfContents.length > 0) { + navItems = tableOfContents.map((item) => { + return ( +
  • + + {item.title} + +
  • + ); + }); + } + + return ( + + ); +}; + +export default PrismicSidebarLayout; diff --git a/site/gatsby-site/src/components/blog/PrismicBlogPost.js b/site/gatsby-site/src/components/blog/PrismicBlogPost.js index 021585d462..6f50575f45 100644 --- a/site/gatsby-site/src/components/blog/PrismicBlogPost.js +++ b/site/gatsby-site/src/components/blog/PrismicBlogPost.js @@ -8,9 +8,23 @@ import React, { useEffect } from 'react'; import { Trans } from 'react-i18next'; import config from '../../../config'; import { useLayoutContext } from 'contexts/LayoutContext'; -import Outline from 'components/Outline'; +import PrismicOutline from 'components/PrismicOutline'; +import { extractHeaders } from 'utils/extractHeaders'; +import { Heading1, Heading2 } from 'components/CustomHeaders'; const PrismicBlogPost = ({ post, location }) => { + let headers = []; + + const extractedHeaders = extractHeaders(post.data.content); + + headers = extractedHeaders; + + // Define custom components for Prismic Rich Text + const components = { + heading1: ({ children }) => {children}, + heading2: ({ children }) => {children}, + }; + const metaTitle = post.data.metatitle; const canonicalUrl = config.gatsby.siteUrl + location.pathname; @@ -19,7 +33,7 @@ const PrismicBlogPost = ({ post, location }) => { const rightSidebar = ( <> - + ); @@ -61,7 +75,7 @@ const PrismicBlogPost = ({ post, location }) => {
    - +
    ); diff --git a/site/gatsby-site/src/components/doc/PrismicDocPost.js b/site/gatsby-site/src/components/doc/PrismicDocPost.js index f2475bf791..3387968f99 100644 --- a/site/gatsby-site/src/components/doc/PrismicDocPost.js +++ b/site/gatsby-site/src/components/doc/PrismicDocPost.js @@ -1,19 +1,27 @@ +import React, { useEffect } from 'react'; import { PrismicRichText } from '@prismicio/react'; -import TranslationBadge from 'components/i18n/TranslationBadge'; import { Link } from 'gatsby'; -import React, { useEffect } from 'react'; import { Trans } from 'react-i18next'; import config from '../../../config'; import { useLayoutContext } from 'contexts/LayoutContext'; -import Outline from 'components/Outline'; +import PrismicOutline from 'components/PrismicOutline'; import Leaderboards from 'components/landing/Leaderboards'; import Sponsors from 'components/landing/Sponsors'; -import { RichText } from 'prismic-reactjs'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; +import TranslationBadge from 'components/i18n/TranslationBadge'; +import { extractHeaders } from 'utils/extractHeaders'; +import { Heading1, Heading2 } from 'components/CustomHeaders'; const PrismicDocPost = ({ doc, location }) => { + let headers = []; + + const extractedHeaders = extractHeaders(doc.data.content); + + headers = extractedHeaders; + + // Define custom components for Prismic Rich Text const components = { + heading1: ({ children }) => {children}, + heading2: ({ children }) => {children}, Leaderboards: , Sponsors: , }; @@ -24,7 +32,7 @@ const PrismicDocPost = ({ doc, location }) => { const rightSidebar = ( <> - + ); @@ -36,7 +44,7 @@ const PrismicDocPost = ({ doc, location }) => { return ( <> -
    +

    {doc.data.title}

    @@ -49,27 +57,16 @@ const PrismicDocPost = ({ doc, location }) => { )}
    - {doc.data.content.map((content, index) => { - return ( - <> - {content.markdown && ( -
    - {(() => { - const rawMarkdown = RichText.asText(content.markdown.richText); - - return {rawMarkdown}; - })()} -
    - )} - {content.text && ( -
    - -
    - )} - {content.component &&
    {components[content.component]}
    } - - ); - })} + {doc.data.content.map((content, index) => ( + <> + {content.text && ( +
    + +
    + )} + {content.component &&
    {components[content.component]}
    } + + ))} ); }; diff --git a/site/gatsby-site/src/utils/extractHeaders.js b/site/gatsby-site/src/utils/extractHeaders.js new file mode 100644 index 0000000000..3d8fc4217a --- /dev/null +++ b/site/gatsby-site/src/utils/extractHeaders.js @@ -0,0 +1,36 @@ +import slugify from 'slugify'; + +// Extract headers from a content object +export const extractHeaders = (content) => { + const headers = []; + + let richText; + + if (content.length > 0) { + content.forEach((block) => { + richText = block.text.richText; + headers.push(...extractHeadersFromRichText(richText)); + }); + } else { + richText = content.richText; + headers.push(...extractHeadersFromRichText(richText)); + } + + return headers; +}; + +// Extract headers from a rich text object and return an array of headers with id and title +const extractHeadersFromRichText = (richText) => { + const headers = []; + + richText.forEach((block) => { + if (block.type === 'heading1' || block.type === 'heading2') { + headers.push({ + id: slugify(block.text, { lower: true }), + title: block.text, + }); + } + }); + + return headers; +};