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;
+};