Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add right sidebar content for prismic content #2795

Merged
44 changes: 44 additions & 0 deletions site/gatsby-site/playwright/e2e/navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
});
33 changes: 33 additions & 0 deletions site/gatsby-site/src/components/CustomHeaders.js
Original file line number Diff line number Diff line change
@@ -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 <HeadingComponent id={id}>{children}</HeadingComponent>;
};

export const Heading1 = ({ children }) => <Heading level={1}>{children}</Heading>;
export const Heading2 = ({ children }) => <Heading level={2}>{children}</Heading>;
export const Heading3 = ({ children }) => <Heading level={3}>{children}</Heading>;
export const Heading4 = ({ children }) => <Heading level={4}>{children}</Heading>;
export const Heading5 = ({ children }) => <Heading level={5}>{children}</Heading>;
export const Heading6 = ({ children }) => <Heading level={6}>{children}</Heading>;
35 changes: 35 additions & 0 deletions site/gatsby-site/src/components/PrismicOutline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';

const PrismicSidebarLayout = ({ tableOfContents }) => {
let navItems;

if (tableOfContents.length > 0) {
navItems = tableOfContents.map((item) => {
return (
<li key={item.id} className="list-none">
<a
href={`#${item.id}`}
className="text-[#5c6975] no-underline font-normal py-2 pr-0 block relative hover:text-blue-500"
>
{item.title}
</a>
</li>
);
});
}

return (
<aside className="sidebar right">
<ul data-cy="outline" className="rightSideBarUL list-revert pl-8">
{navItems?.length > 0 && (
<>
<li className="rightSideTitle">CONTENTS</li>
{navItems}
</>
)}
</ul>
</aside>
);
};

export default PrismicSidebarLayout;
20 changes: 17 additions & 3 deletions site/gatsby-site/src/components/blog/PrismicBlogPost.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => <Heading1>{children}</Heading1>,
heading2: ({ children }) => <Heading2>{children}</Heading2>,
};

const metaTitle = post.data.metatitle;

const canonicalUrl = config.gatsby.siteUrl + location.pathname;
Expand All @@ -19,7 +33,7 @@ const PrismicBlogPost = ({ post, location }) => {

const rightSidebar = (
<>
<Outline location={loc} />
<PrismicOutline location={loc} tableOfContents={headers} />
</>
);

Expand Down Expand Up @@ -61,7 +75,7 @@ const PrismicBlogPost = ({ post, location }) => {
</span>
</div>
<div className="prose">
<PrismicRichText field={post.data.content.richText} />
<PrismicRichText field={post.data.content.richText} components={components} />
</div>
</>
);
Expand Down
55 changes: 26 additions & 29 deletions site/gatsby-site/src/components/doc/PrismicDocPost.js
Original file line number Diff line number Diff line change
@@ -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 }) => <Heading1>{children}</Heading1>,
heading2: ({ children }) => <Heading2>{children}</Heading2>,
Leaderboards: <Leaderboards />,
Sponsors: <Sponsors />,
};
Expand All @@ -24,7 +32,7 @@ const PrismicDocPost = ({ doc, location }) => {

const rightSidebar = (
<>
<Outline location={loc} />
<PrismicOutline location={loc} tableOfContents={headers} />
</>
);

Expand All @@ -36,7 +44,7 @@ const PrismicDocPost = ({ doc, location }) => {

return (
<>
<div className={'titleWrapper'}>
<div className="titleWrapper">
<h1>{doc.data.title}</h1>
</div>
<div className="flex items-center mb-6 -mt-1 flex-wrap">
Expand All @@ -49,27 +57,16 @@ const PrismicDocPost = ({ doc, location }) => {
</>
)}
</div>
{doc.data.content.map((content, index) => {
return (
<>
{content.markdown && (
<div className="prose">
{(() => {
const rawMarkdown = RichText.asText(content.markdown.richText);

return <ReactMarkdown remarkPlugins={[remarkGfm]}>{rawMarkdown}</ReactMarkdown>;
})()}
</div>
)}
{content.text && (
<div className="prose">
<PrismicRichText key={index} field={content.text.richText} />
</div>
)}
{content.component && <div className=" mt-4">{components[content.component]}</div>}
</>
);
})}
{doc.data.content.map((content, index) => (
<>
{content.text && (
<div className="prose">
<PrismicRichText key={index} field={content.text.richText} components={components} />
</div>
)}
{content.component && <div className="mt-4">{components[content.component]}</div>}
</>
))}
</>
);
};
Expand Down
36 changes: 36 additions & 0 deletions site/gatsby-site/src/utils/extractHeaders.js
Original file line number Diff line number Diff line change
@@ -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;
};