diff --git a/components/Events/common/utils.ts b/components/Events/common/utils.ts
index 1ab21d401..86aaa1a18 100644
--- a/components/Events/common/utils.ts
+++ b/components/Events/common/utils.ts
@@ -3,6 +3,7 @@ import {
buildMomentField,
convertDateToMoment,
getFrontmatterByPaths,
+ isFrontmatterEvent,
} from "../../../content/utils";
import {
getDocsDirectory,
@@ -141,10 +142,14 @@ function processFrontmatterURL(path?: string): string | null {
* @returns frontmatter for an event article.
*/
export function processEventFrontmatter(
- frontmatter: FrontmatterEvent
-): FrontmatterEvent {
+ frontmatter: Frontmatter | undefined
+): FrontmatterEvent | undefined {
+ if (!frontmatter) return;
+ if (!isFrontmatterEvent(frontmatter)) return;
return {
...processFrontmatter(["", frontmatter]),
+ enableNavigation: false,
+ enableOutline: false,
formattedSessions: formatSessions(
frontmatter.sessions,
frontmatter.timezone
diff --git a/components/Layout/components/Content/components/SupportForum/supportForum.styles.ts b/components/Layout/components/Content/components/SupportForum/supportForum.styles.ts
new file mode 100644
index 000000000..ebddf1bbc
--- /dev/null
+++ b/components/Layout/components/Content/components/SupportForum/supportForum.styles.ts
@@ -0,0 +1,10 @@
+import { RoundedPaper } from "@databiosphere/findable-ui/lib/components/common/Paper/paper.styles";
+import styled from "@emotion/styled";
+
+export const StyledPaper = styled(RoundedPaper)`
+ display: grid;
+ margin-top: 64px;
+ padding: 40px 32px;
+ place-items: center;
+ text-align: center;
+`;
diff --git a/components/Layout/components/Content/components/SupportForum/supportForum.tsx b/components/Layout/components/Content/components/SupportForum/supportForum.tsx
new file mode 100644
index 000000000..25a77c785
--- /dev/null
+++ b/components/Layout/components/Content/components/SupportForum/supportForum.tsx
@@ -0,0 +1,40 @@
+import {
+ ANCHOR_TARGET,
+ REL_ATTRIBUTE,
+} from "@databiosphere/findable-ui/lib/components/Links/common/entities";
+import {
+ TEXT_BODY_LARGE_400_2_LINES,
+ TEXT_HEADING,
+} from "@databiosphere/findable-ui/lib/theme/common/typography";
+import { Button, Typography } from "@mui/material";
+import { PATH_PARAMETERS } from "../../../../../../common/constants";
+import { StyledPaper } from "./supportForum.styles";
+
+export const SupportForum = (): JSX.Element => {
+ return (
+
+
+ AnVIL Support Forum
+
+
+ Be sure to check out the AnVIL Community for support, plus tips & tricks
+ from our users and much more.
+
+
+
+ );
+};
diff --git a/components/Layout/components/Header/components/Actions/actions.tsx b/components/Layout/components/Header/components/Actions/actions.tsx
new file mode 100644
index 000000000..2dc2ad403
--- /dev/null
+++ b/components/Layout/components/Header/components/Actions/actions.tsx
@@ -0,0 +1,11 @@
+import { Button } from "@mui/material";
+import Link from "next/link";
+import { BUTTON_PROPS } from "./constants";
+
+export const Actions = (): JSX.Element => {
+ return (
+
+ );
+};
diff --git a/components/Layout/components/Header/components/Actions/constants.ts b/components/Layout/components/Header/components/Actions/constants.ts
new file mode 100644
index 000000000..1331b01e4
--- /dev/null
+++ b/components/Layout/components/Header/components/Actions/constants.ts
@@ -0,0 +1,7 @@
+import { ButtonProps } from "@mui/material";
+
+export const BUTTON_PROPS: Partial = {
+ color: "primary",
+ size: "medium",
+ variant: "contained",
+};
diff --git a/components/Layout/components/Main/main.styles.ts b/components/Layout/components/Main/main.styles.ts
new file mode 100644
index 000000000..4b8a954a0
--- /dev/null
+++ b/components/Layout/components/Main/main.styles.ts
@@ -0,0 +1,6 @@
+import { Main as DXMain } from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/components/Main/main";
+import styled from "@emotion/styled";
+
+export const StyledMain = styled(DXMain)`
+ flex-direction: column;
+`;
diff --git a/components/Layout/components/Section/components/SectionContent/components/SectionOverview/sectionOverview.styles.ts b/components/Layout/components/Section/components/SectionContent/components/SectionOverview/sectionOverview.styles.ts
new file mode 100644
index 000000000..a578e432b
--- /dev/null
+++ b/components/Layout/components/Section/components/SectionContent/components/SectionOverview/sectionOverview.styles.ts
@@ -0,0 +1,52 @@
+import { primaryMain } from "@databiosphere/findable-ui/lib/styles/common/mixins/colors";
+import styled from "@emotion/styled";
+import { Grid2Props } from "@mui/material";
+
+type Props = Grid2Props & {
+ nth: number;
+};
+
+export const GroupOverview = styled.div`
+ .MuiDivider-root,
+ .MuiTypography-text-heading {
+ grid-column: 1 / -1;
+ }
+
+ .MuiDivider-root {
+ margin: 32px 0;
+ }
+
+ .MuiTypography-text-heading {
+ line-height: 34px;
+ }
+`;
+
+export const StyledList = styled("ul")`
+ display: grid;
+ gap: 0 64px;
+ grid-auto-flow: dense;
+ grid-column: 1 / -1;
+ grid-template-columns: 1fr 1fr;
+ list-style-position: inside;
+ margin-top: 8px;
+ padding-left: 0;
+
+ li {
+ grid-column: 1;
+ margin: 4px 0;
+ padding-left: 24px; // required for list-style-position: inside; allows for market to be positioned inside the list item.
+ text-indent: -15px; // required for list-style-position: inside; centering marker; half of the 24px width and half marker width @ 6px.
+
+ &:nth-of-type(n + ${(props) => props.nth}) {
+ grid-column: 2;
+ }
+
+ > * {
+ margin-left: -6px; // required for list-style-position: inside; assists with vertical alignment of list item; difference between indent and padding adjustments and half of the marker width.
+ }
+
+ &::marker {
+ color: ${primaryMain};
+ }
+ }
+`;
diff --git a/components/Layout/components/Section/components/SectionContent/components/SectionOverview/sectionOverview.tsx b/components/Layout/components/Section/components/SectionContent/components/SectionOverview/sectionOverview.tsx
new file mode 100644
index 000000000..2facc3bc3
--- /dev/null
+++ b/components/Layout/components/Section/components/SectionContent/components/SectionOverview/sectionOverview.tsx
@@ -0,0 +1,38 @@
+import { Link } from "@databiosphere/findable-ui/lib/components/Links/components/Link/link";
+import { TEXT_HEADING } from "@databiosphere/findable-ui/lib/theme/common/typography";
+import { Divider } from "@mui/material";
+import { Fragment } from "react";
+import { Heading } from "../../../../../../../common/Typography/components/Heading/heading";
+import { GroupOverview, StyledList } from "./sectionOverview.styles";
+import { Props } from "./types";
+
+const MAX_ROWS = 4;
+
+export const SectionOverview = ({ overview }: Props): JSX.Element | null => {
+ if (!overview) return null;
+ return (
+
+ {overview.map(({ label, links }, i) => {
+ return (
+ links.length > 0 && (
+
+ {i > 0 && }
+
+
+ {links.map((linkProps, i) => (
+
+
+
+ ))}
+
+
+ )
+ );
+ })}
+
+ );
+};
diff --git a/components/Layout/components/Section/components/SectionContent/components/SectionOverview/types.ts b/components/Layout/components/Section/components/SectionContent/components/SectionOverview/types.ts
new file mode 100644
index 000000000..2420d1229
--- /dev/null
+++ b/components/Layout/components/Section/components/SectionContent/components/SectionOverview/types.ts
@@ -0,0 +1,12 @@
+import { LinkProps } from "@databiosphere/findable-ui/lib/components/Links/components/Link/link";
+
+export interface Overview {
+ label: string;
+ links: OverviewLink[];
+}
+
+export type OverviewLink = string | LinkProps;
+
+export interface Props {
+ overview: (Omit & { links: LinkProps[] })[];
+}
diff --git a/components/Layout/components/Section/components/SectionContent/components/SectionOverview/utils.ts b/components/Layout/components/Section/components/SectionContent/components/SectionOverview/utils.ts
new file mode 100644
index 000000000..7c7d24888
--- /dev/null
+++ b/components/Layout/components/Section/components/SectionContent/components/SectionOverview/utils.ts
@@ -0,0 +1,153 @@
+import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/outline";
+import { isURLString } from "@databiosphere/findable-ui/lib/components/Links/common/utils";
+import { LinkProps } from "@databiosphere/findable-ui/lib/components/Links/components/Link/link";
+import {
+ Frontmatter,
+ FrontmatterOverview,
+} from "../../../../../../../../content/entities";
+import { isFrontmatterOverview } from "../../../../../../../../content/typeGuards";
+import { slugifyHeading } from "../../../../../../../../plugins/common/utils";
+import { OverviewLink } from "./types";
+
+const OVERVIEW_OUTLINE_DEPTH = 2;
+
+/**
+ * Maps an overview link to LinkProps.
+ * A string link is converted to a LinkProps with the title taken from the frontmatter.
+ * An undefined value is returned if the link is not found in the frontmatter, or if the title is not found.
+ * @param section - Section.
+ * @param link - Overview link.
+ * @param frontmatters - Paths with frontmatter.
+ * @returns link props.
+ */
+function getOverviewLink(
+ section: string,
+ link: OverviewLink,
+ frontmatters: [string, Frontmatter][]
+): LinkProps | undefined {
+ // Grab the configured URL from the link.
+ const url = getOverviewLinkUrl(link);
+ if (!url) return;
+ // Find the corresponding frontmatter for the link.
+ const pathFrontmatter = getPathFrontmatter(
+ section,
+ getOverviewLinkUrl(link),
+ frontmatters
+ );
+ if (!pathFrontmatter) return;
+ // Extract the title from the frontmatter.
+ const [, { title }] = pathFrontmatter;
+ if (!title) return;
+ // Return the link props.
+ return {
+ label: title,
+ url,
+ };
+}
+
+/**
+ * Gets the URL configured from an overview link.
+ * OverviewLink can be a string or LinkProps.
+ * @param link - Overview link.
+ * @returns overview link URL.
+ */
+function getOverviewLinkUrl(link: OverviewLink): string {
+ if (typeof link === "string") return link;
+ if (isURLString(link.url)) return link.url;
+ return "";
+}
+
+/**
+ * Finds the path with frontmatter tuple for a given path.
+ * Compares the given link with the path in the path with frontmatter tuples.
+ * @param section - Section.
+ * @param link - Link.
+ * @param frontmatters - Path frontmatter tuples.
+ * @returns path with frontmatter tuple.
+ */
+function getPathFrontmatter(
+ section: string,
+ link: string,
+ frontmatters: [string, Frontmatter][]
+): [string, Frontmatter] | undefined {
+ const regex = new RegExp(`^\\/${section}\\/`);
+ return frontmatters.find(([path]) => path === link.replace(regex, ""));
+}
+
+/**
+ * Maps the overview to an outline.
+ * @param overview - Overview.
+ * @returns outline.
+ */
+export function mapFrontmatterOutline(
+ overview: FrontmatterOverview["overview"]
+): FrontmatterOverview["outline"] {
+ return overview.reduce((acc, { label, links }) => {
+ if (links.length > 0) {
+ acc.push({
+ depth: OVERVIEW_OUTLINE_DEPTH,
+ hash: slugifyHeading(label),
+ value: label,
+ });
+ }
+ return acc;
+ }, [] as OutlineItem[]);
+}
+
+/**
+ * Maps the frontmatter overview to an overview comprising of LinkProps.
+ * @param section - Section.
+ * @param frontmatter - Frontmatter.
+ * @param frontmatters - Paths with frontmatter.
+ * @returns overview.
+ */
+export function mapFrontmatterOverview(
+ section: string,
+ frontmatter: FrontmatterOverview,
+ frontmatters: [string, Frontmatter][]
+): FrontmatterOverview["overview"] {
+ return frontmatter.overview.map(({ label, links }) => {
+ return { label, links: parseOverviewLinks(section, links, frontmatters) };
+ });
+}
+
+/**
+ * Maps overview links to LinkProps for display in the Link component.
+ * @param section - Section.
+ * @param links - Overview links.
+ * @param frontmatters - Paths with frontmatter.
+ * @returns link props.
+ */
+function parseOverviewLinks(
+ section: string,
+ links: OverviewLink[],
+ frontmatters: [string, Frontmatter][]
+): LinkProps[] {
+ return links.reduce((acc, link) => {
+ const overviewLink = getOverviewLink(section, link, frontmatters);
+ if (overviewLink) {
+ // Only add the link if the title exists; confirming the overview configured the link correctly.
+ acc.push(overviewLink);
+ }
+ return acc;
+ }, [] as LinkProps[]);
+}
+
+/**
+ * Returns parsed overview related frontmatter.
+ * @param section - Section.
+ * @param frontmatter - Frontmatter.
+ * @param frontmatters - Paths with frontmatter.
+ * @returns frontmatter.
+ */
+export function processOverviewFrontmatter(
+ section: string = "",
+ frontmatter: Frontmatter | undefined,
+ frontmatters: [string, Frontmatter][]
+): Frontmatter | undefined {
+ if (!frontmatter) return;
+ if (!isFrontmatterOverview(frontmatter)) return frontmatter;
+ const overview = mapFrontmatterOverview(section, frontmatter, frontmatters);
+ const outline = mapFrontmatterOutline(overview);
+ return { ...frontmatter, outline, overview };
+}
diff --git a/components/Layout/components/Section/components/SectionContent/sectionContent.styles.ts b/components/Layout/components/Section/components/SectionContent/sectionContent.styles.ts
new file mode 100644
index 000000000..b18eef051
--- /dev/null
+++ b/components/Layout/components/Section/components/SectionContent/sectionContent.styles.ts
@@ -0,0 +1,35 @@
+import {
+ Content,
+ ContentGrid,
+ OutlineGrid,
+ Positioner,
+} from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/contentLayout.styles";
+import { mediaTabletDown } from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints";
+import styled from "@emotion/styled";
+
+export const StyledSection = styled.section`
+ flex: 1;
+ width: 100%;
+`;
+
+export const StyledContentGrid = styled(ContentGrid)`
+ padding: 64px 0;
+`;
+
+export const StyledContent = styled(Content)`
+ padding: 0 40px;
+
+ ${mediaTabletDown} {
+ padding: 0 16px;
+ }
+`;
+
+export const StyledOutlineGrid = styled(OutlineGrid)`
+ padding: 64px 0;
+`;
+
+export const StyledPositioner = styled(Positioner)`
+ max-height: ${({ headerHeight }) => `calc(100vh - ${headerHeight}px)`};
+ padding-top: 0;
+ top: ${({ headerHeight }) => `${headerHeight}px`};
+`;
diff --git a/components/Layout/components/Section/components/SectionContent/sectionContent.tsx b/components/Layout/components/Section/components/SectionContent/sectionContent.tsx
new file mode 100644
index 000000000..49f7b4893
--- /dev/null
+++ b/components/Layout/components/Section/components/SectionContent/sectionContent.tsx
@@ -0,0 +1,52 @@
+import { PANEL_BACKGROUND_COLOR } from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/common/entities";
+import {
+ ContentLayout,
+ Outline,
+} from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/contentLayout.styles";
+import { useLayoutState } from "@databiosphere/findable-ui/lib/hooks/useLayoutState";
+import { ContentViewProps } from "@databiosphere/findable-ui/lib/views/ContentView/contentView";
+import { StaticProps } from "../../../../../../content/entities";
+import {
+ StyledContent,
+ StyledContentGrid,
+ StyledOutlineGrid,
+ StyledPositioner,
+ StyledSection,
+} from "./sectionContent.styles";
+
+export const SectionContent = ({
+ content,
+ outline,
+ slug,
+}: Omit &
+ Pick): JSX.Element => {
+ const {
+ layoutState: { headerHeight },
+ } = useLayoutState();
+ return (
+
+
+
+ {content}
+
+ {outline && (
+
+
+ {outline}
+
+
+ )}
+
+
+ );
+};
diff --git a/components/Layout/components/Section/components/SectionHero/sectionHero.styles.ts b/components/Layout/components/Section/components/SectionHero/sectionHero.styles.ts
new file mode 100644
index 000000000..95feafc31
--- /dev/null
+++ b/components/Layout/components/Section/components/SectionHero/sectionHero.styles.ts
@@ -0,0 +1,69 @@
+import {
+ Content,
+ ContentLayout,
+} from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/contentLayout.styles";
+import {
+ media1366Up,
+ mediaDesktopSmallUp,
+ mediaTabletDown,
+} from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints";
+import { smokeLightest } from "@databiosphere/findable-ui/lib/styles/common/mixins/colors";
+import { textHeadingXLarge } from "@databiosphere/findable-ui/lib/styles/common/mixins/fonts";
+import { css } from "@emotion/react";
+import styled from "@emotion/styled";
+
+interface Props {
+ headerHeight: number;
+}
+
+export const StyledSection = styled("section", {
+ shouldForwardProp: (props) => props !== "headerHeight",
+})`
+ background-color: ${smokeLightest};
+ padding-top: ${({ headerHeight }) => headerHeight}px;
+ width: 100%;
+`;
+
+export const SectionLayout = styled(ContentLayout)`
+ grid-template-areas: "hero";
+
+ ${mediaDesktopSmallUp} {
+ ${({ hasNavigation }) =>
+ hasNavigation
+ ? css`
+ grid-template-areas: ". hero";
+ `
+ : css`
+ grid-template-areas: "hero";
+ `};
+ }
+
+ ${media1366Up} {
+ grid-template-areas: ". hero .";
+ }
+
+ .MuiDivider-root {
+ grid-column: 1 / -1;
+ }
+`;
+
+export const Headline = styled(Content)`
+ grid-area: hero;
+ padding-bottom: 40px;
+ padding-top: 40px;
+ width: 100%;
+
+ ${mediaTabletDown} {
+ padding-bottom: 40px;
+ padding-top: 40px;
+ }
+
+ .MuiBreadcrumbs-root {
+ margin: 0;
+ }
+`;
+
+export const PageTitle = styled.h1`
+ ${textHeadingXLarge};
+ margin: 0;
+`;
diff --git a/components/Layout/components/Section/components/SectionHero/sectionHero.tsx b/components/Layout/components/Section/components/SectionHero/sectionHero.tsx
new file mode 100644
index 000000000..0c439dd1b
--- /dev/null
+++ b/components/Layout/components/Section/components/SectionHero/sectionHero.tsx
@@ -0,0 +1,33 @@
+import { Breadcrumbs } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs";
+import { PANEL_BACKGROUND_COLOR } from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/common/entities";
+import { useLayoutState } from "@databiosphere/findable-ui/lib/hooks/useLayoutState";
+import { StaticProps } from "../../../../../../content/entities";
+import { SectionDivider } from "../../../../../Home/components/Section/section.styles";
+import {
+ Headline,
+ PageTitle,
+ SectionLayout,
+ StyledSection,
+} from "./sectionHero.styles";
+
+export const SectionHero = ({
+ frontmatter: { breadcrumbs, title },
+}: StaticProps): JSX.Element => {
+ const {
+ layoutState: { headerHeight },
+ } = useLayoutState();
+ return (
+
+
+
+ {breadcrumbs && }
+ {title}
+
+
+
+
+ );
+};
diff --git a/components/News/common/utils.ts b/components/News/common/utils.ts
index 7517414d7..959a06104 100644
--- a/components/News/common/utils.ts
+++ b/components/News/common/utils.ts
@@ -62,6 +62,8 @@ export function processFrontmatter(
return {
...frontmatter,
date: processFrontmatterDate(frontmatter),
+ enableNavigation: false,
+ enableOutline: false,
url: processFrontmatterURL(path),
};
}
diff --git a/components/common/Typography/components/Heading/heading.tsx b/components/common/Typography/components/Heading/heading.tsx
index 2b09cc2c6..947885c6c 100644
--- a/components/common/Typography/components/Heading/heading.tsx
+++ b/components/common/Typography/components/Heading/heading.tsx
@@ -1,25 +1,34 @@
import { AnchorLink } from "@databiosphere/findable-ui/lib/components/common/AnchorLink/anchorLink";
import { TEXT_HEADING_LARGE } from "@databiosphere/findable-ui/lib/theme/common/typography";
-import { Typography } from "@mui/material";
+import { Typography, TypographyProps } from "@mui/material";
import { slugifyHeading } from "../../../../../plugins/common/utils";
export interface HeadingProps {
+ component?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
enableAnchor?: boolean;
headingValue: string;
+ headingValueSlug?: string;
+ sx?: TypographyProps["sx"];
+ variant?: TypographyProps["variant"];
}
export const Heading = ({
+ component = "h1",
enableAnchor = true,
headingValue,
+ headingValueSlug = slugifyHeading(headingValue),
+ sx = { mb: 2 },
+ variant = TEXT_HEADING_LARGE,
}: HeadingProps): JSX.Element => {
return (
{headingValue}
- {enableAnchor && }
+ {enableAnchor && }
);
};
diff --git a/components/index.tsx b/components/index.tsx
index d4e6dca9c..ff81bc5e7 100644
--- a/components/index.tsx
+++ b/components/index.tsx
@@ -43,7 +43,9 @@ export { EventsHero } from "./Events/components/EventsHero/eventsHero";
export { Events } from "./Events/events";
export { Card as WorkspaceCard } from "./Home/components/Section/components/SectionWorkspaces/components/Workspaces/components/Card/card";
export { ANVILBranding } from "./Layout/components/Footer/components/Branding/components/ANVILBranding/anvilBranding";
+export { Actions } from "./Layout/components/Header/components/Actions/actions";
export { LabelIconMenuItem } from "./Layout/components/Header/components/Content/components/Navigation/components/NavigationMenuItems/components/LabelIconMenuItem/labelIconMenuItem";
+export { SectionOverview } from "./Layout/components/Section/components/SectionContent/components/SectionOverview/sectionOverview";
export { NewsHero } from "./News/components/NewsHero/newsHero";
export { News } from "./News/news";
export { Grid as AnalysisPortals } from "./Overview/components/AnalysisPortals/analysisPortals.styles";
diff --git a/content/entities.ts b/content/entities.ts
index 8b0e3db11..9e05b344f 100644
--- a/content/entities.ts
+++ b/content/entities.ts
@@ -1,3 +1,11 @@
+import { Breadcrumb } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs";
+import { LayoutStyle } from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/common/entities";
+import { NavItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Nav/nav";
+import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/outline";
+import { MDXRemoteSerializeResult } from "next-mdx-remote";
+import { Overview } from "../components/Layout/components/Section/components/SectionContent/components/SectionOverview/types";
+import { NodeHero } from "../docs/common/entities";
+
export interface EventSession {
sessionEnd?: string;
sessionStart: string;
@@ -6,10 +14,17 @@ export interface EventSession {
export type Frontmatter =
| DefaultFrontmatter
| FrontmatterEvent
+ | FrontmatterOverview
| FrontmatterNews;
export interface DefaultFrontmatter {
+ breadcrumbs?: Breadcrumb[];
description: string;
+ enableContentEnd?: boolean;
+ enableNavigation?: boolean;
+ enableOutline?: boolean;
+ enableOverview?: boolean; // TODO: Remove when the updated "learn" UI is complete.
+ enableSupportForum?: boolean;
hidden?: boolean;
layoutStyle?: FrontmatterLayoutStyle;
title: string;
@@ -35,6 +50,11 @@ export interface FrontmatterNews extends DefaultFrontmatter {
url: string | null;
}
+export interface FrontmatterOverview extends DefaultFrontmatter {
+ outline?: OutlineItem[];
+ overview: Overview[];
+}
+
export type FrontmatterLayoutStyle =
| "LAYOUT_STYLE_CONTRAST_LIGHT"
| "LAYOUT_STYLE_CONTRAST_LIGHTEST"
@@ -43,3 +63,14 @@ export type FrontmatterLayoutStyle =
| "LAYOUT_STYLE_NO_CONTRAST_LIGHTEST";
export type Hashtag = `#${string}`;
+
+export interface StaticProps {
+ frontmatter: Frontmatter;
+ hero?: NodeHero | null;
+ layoutStyle: LayoutStyle | null;
+ mdxSource: MDXRemoteSerializeResult;
+ navigation?: NavItem[] | null;
+ outline?: OutlineItem[] | null;
+ pageTitle: string | null;
+ slug: string[];
+}
diff --git a/content/typeGuards.ts b/content/typeGuards.ts
new file mode 100644
index 000000000..a6de613a2
--- /dev/null
+++ b/content/typeGuards.ts
@@ -0,0 +1,12 @@
+import { Frontmatter, FrontmatterOverview } from "./entities";
+
+/**
+ * Type guard for "overview" related frontmatter.
+ * @param frontmatter - Frontmatter.
+ * @returns true if the frontmatter includes the "overview" property.
+ */
+export function isFrontmatterOverview(
+ frontmatter: Frontmatter
+): frontmatter is FrontmatterOverview {
+ return "overview" in frontmatter;
+}
diff --git a/content/utils.ts b/content/utils.ts
index bc87cab51..750e0b42c 100644
--- a/content/utils.ts
+++ b/content/utils.ts
@@ -4,6 +4,8 @@ import moment, { Moment, tz } from "moment-timezone";
import "moment-timezone/index";
import { default as path, default as pathTool } from "path";
import { SlugByFilePaths } from "../docs/common/entities";
+import { resolveDocPath } from "../docs/common/generateStaticPaths";
+import { mapSlugByFilePaths } from "../docs/common/utils";
import {
EventSession,
Frontmatter,
@@ -44,6 +46,18 @@ export function convertDateToMoment(
return tz(date, ["D MMM YYYY h:mm A", "D MMM YYYY"], timezone);
}
+/**
+ * Returns a tuple array of paths and frontmatter for the given section.
+ * @param section - Section.
+ * @returns tuple array of paths and frontmatter.
+ */
+export function generateSectionPathWithFrontmatter(
+ section: string
+): [string, Frontmatter][] {
+ const slugByFilePaths = mapSlugByFilePaths(resolveDocPath(section));
+ return [...getFrontmatterByPaths(slugByFilePaths)];
+}
+
/**
* Returns the path to the given content directory e.g. "...docs/events" or "...docs/news".
* @param dirName - Directory name.
diff --git a/docs/anvil-champions.mdx b/docs/anvil-champions.mdx
index 96e98165f..aeb7319a5 100644
--- a/docs/anvil-champions.mdx
+++ b/docs/anvil-champions.mdx
@@ -1,5 +1,6 @@
---
description: "AnVIL Champions serve as a local resource within their community (institution, consortium, research group, geographic region, etc.) who can help members of their community that are interested in using AnVIL."
+enableOutline: false
title: "AnVIL Champions"
---
diff --git a/docs/common/constants.ts b/docs/common/constants.ts
index a026f7406..61c96e581 100644
--- a/docs/common/constants.ts
+++ b/docs/common/constants.ts
@@ -41,6 +41,7 @@ export const MDX_COMPONENTS = {
Publications: C.Publications,
ResearchMaterials: C.ResearchMaterials,
Resources: C.Resources,
+ SectionOverview: C.SectionOverview,
Subheader: C.Subheader,
TextBodyLarge500: C.TextBodyLarge500,
Video: C.Video,
diff --git a/docs/common/entities.ts b/docs/common/entities.ts
index 691ad1a48..0448e0411 100644
--- a/docs/common/entities.ts
+++ b/docs/common/entities.ts
@@ -27,7 +27,6 @@ export enum NavigationKey {
export interface NavigationNode
extends Pick {
- enableOutline?: boolean;
hero?: NodeHero;
key?: string;
label?: string;
diff --git a/docs/common/generateStaticPaths.ts b/docs/common/generateStaticPaths.ts
new file mode 100644
index 000000000..cb153e25e
--- /dev/null
+++ b/docs/common/generateStaticPaths.ts
@@ -0,0 +1,30 @@
+import { GetStaticPathsResult } from "next/types";
+import pathTool from "path";
+import { DOC_SITE_FOLDER_NAME } from "./constants";
+import { mapSlugByFilePaths } from "./utils";
+
+/**
+ * Generates static paths for the documentation site, for the specified relative path.
+ * @param relativePath - Relative path.
+ * @returns static paths.
+ */
+export function generateStaticPaths(
+ relativePath = ""
+): GetStaticPathsResult["paths"] {
+ const docPath = resolveDocPath(relativePath);
+ const slugByFilePaths = mapSlugByFilePaths(docPath);
+ return [...slugByFilePaths].map(([, slug]) => {
+ return {
+ params: { slug },
+ };
+ });
+}
+
+/**
+ * Resolves the absolute path to a specific subdirectory within the documentation folder.
+ * @param relativePath - Relative path.
+ * @returns The absolute path to the specified subdirectory within the documentation folder.
+ */
+export function resolveDocPath(relativePath: string): string {
+ return pathTool.join(process.cwd(), DOC_SITE_FOLDER_NAME, relativePath);
+}
diff --git a/docs/common/generateStaticProps.ts b/docs/common/generateStaticProps.ts
new file mode 100644
index 000000000..a0cea1d31
--- /dev/null
+++ b/docs/common/generateStaticProps.ts
@@ -0,0 +1,64 @@
+import { LAYOUT_STYLE_NO_CONTRAST_DEFAULT } from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/common/constants";
+import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/outline";
+import { GetStaticPropsContext } from "next";
+import { SerializeOptions } from "next-mdx-remote/dist/types";
+import { serialize } from "next-mdx-remote/serialize";
+import { GetStaticPropsResult } from "next/types";
+import remarkGfm from "remark-gfm";
+import { Frontmatter, StaticProps } from "../../content/entities";
+import { rehypeSlug } from "../../plugins/rehypeSlug";
+import { remarkHeadings } from "../../plugins/remarkHeadings";
+import {
+ buildPageSlug,
+ extractMDXFrontmatter,
+ getNavigationConfig,
+ getStaticPropLayoutStyle,
+ getStaticPropNavigation,
+ getStaticPropOutline,
+ parseFrontmatter,
+} from "./utils";
+
+export async function generateStaticProps(
+ props: GetStaticPropsContext,
+ section = "",
+ frontmatterFn = (
+ frontmatter: Frontmatter | undefined
+ ): Frontmatter | undefined => frontmatter,
+ serializeOptions: SerializeOptions = {}
+): Promise | undefined> {
+ const slug = buildPageSlug(props, section);
+ if (!slug) return;
+ // Extract frontmatter and content from the MDX file.
+ const { content, data } = extractMDXFrontmatter(slug);
+ const frontmatter = frontmatterFn(parseFrontmatter(data));
+ if (!frontmatter || frontmatter.hidden) return;
+ // Serialize the MDX content.
+ const outline: OutlineItem[] = [];
+ const mdxSource = await serialize(content, {
+ ...serializeOptions,
+ mdxOptions: {
+ rehypePlugins: [rehypeSlug],
+ remarkPlugins: [[remarkHeadings, outline], remarkGfm],
+ },
+ scope: { ...serializeOptions.scope, frontmatter },
+ });
+ // Get the navigation configuration.
+ const {
+ hero = null,
+ layoutStyle = LAYOUT_STYLE_NO_CONTRAST_DEFAULT,
+ navigation = null,
+ } = getNavigationConfig(slug) || {};
+ const { title: pageTitle = null } = frontmatter;
+ return {
+ props: {
+ frontmatter,
+ hero,
+ layoutStyle: getStaticPropLayoutStyle(layoutStyle, frontmatter),
+ mdxSource,
+ navigation: getStaticPropNavigation(navigation, frontmatter),
+ outline: getStaticPropOutline(outline, frontmatter),
+ pageTitle,
+ slug,
+ },
+ };
+}
diff --git a/docs/common/utils.ts b/docs/common/utils.ts
index c9edc5fe2..238dc4f82 100644
--- a/docs/common/utils.ts
+++ b/docs/common/utils.ts
@@ -10,12 +10,49 @@ import { NavItem } from "@databiosphere/findable-ui/lib/components/Layout/compon
import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/outline";
import fs from "fs";
import matter from "gray-matter";
+import { GetStaticPropsContext } from "next";
import { GetStaticPathsResult } from "next/types";
import pathTool, * as path from "path";
import { Frontmatter } from "../../content/entities";
import { navigation as navigationConfig } from "../../site-config/anvil-portal/dev/navigation/navigation";
import { DOC_SITE_FOLDER_NAME } from "./constants";
-import { NavigationKey, NavigationNode, SlugByFilePaths } from "./entities";
+import {
+ Navigation,
+ NavigationKey,
+ NavigationNode,
+ SlugByFilePaths,
+} from "./entities";
+
+/**
+ * Returns the page slug for the given static props context and section.
+ * @param props - Static props context.
+ * @param section - Document section e.g. "learn".
+ * @returns page slug.
+ */
+export function buildPageSlug(
+ props: GetStaticPropsContext,
+ section?: string
+): string[] | undefined {
+ const slug = props.params?.slug;
+ if (!slug || typeof slug === "string") return;
+ if (section) return [section, ...slug];
+ return slug;
+}
+
+/**
+ * Returns MDX content and frontmatter from the given slug.
+ * @param slug - Slug.
+ * @returns MDX content and frontmatter.
+ */
+export function extractMDXFrontmatter(
+ slug: string[]
+): matter.GrayMatterFile {
+ const markdownWithMeta = fs.readFileSync(
+ getMDXFilePath(slug, getDocsDirectory()),
+ "utf-8"
+ );
+ return matter(markdownWithMeta);
+}
/**
* Filters out headings (H1, and H3-H6) from the outline.
@@ -27,11 +64,17 @@ export function filterOutline(outline: OutlineItem): boolean {
}
/**
- * Returns the path to the "docs" directory.
- * @returns path to the "docs" directory.
+ * Returns the static paths for each mdx content in the "docs" directory.
+ * @returns the static paths for the mdx content.
*/
-export function getDocsDirectory(): string {
- return pathTool.join(process.cwd(), DOC_SITE_FOLDER_NAME);
+export function generatePaths(): GetStaticPathsResult["paths"] {
+ const docsDirectory = getDocsDirectory();
+ const slugByFilePaths = mapSlugByFilePaths(docsDirectory);
+ return [...slugByFilePaths].map(([, slug]) => {
+ return {
+ params: { slug },
+ };
+ });
}
/**
@@ -59,6 +102,14 @@ function getActiveURL(pagePath: string, navigation?: NavItem[]): string {
return activeURLs.find((url) => pagePath === url) || activeURLs.pop() || "";
}
+/**
+ * Returns the path to the "docs" directory.
+ * @returns path to the "docs" directory.
+ */
+export function getDocsDirectory(): string {
+ return pathTool.join(process.cwd(), DOC_SITE_FOLDER_NAME);
+}
+
/**
* Returns MDX file path for the given slug.
* @param slug - Slug.
@@ -72,24 +123,6 @@ function getMDXFilePath(
return pathTool.join(docsDirectory, ...slug) + ".mdx";
}
-/**
- * Returns the navigation items with the active flag set to true if the URL matches the active URL.
- * @param activeURL - Active URL.
- * @param navigation - Navigation.
- * @returns the navigation items with the active flag set to true if the URL matches the active URL.
- */
-function getNavItems(
- activeURL: string,
- navigation?: NavItem[]
-): NavItem[] | undefined {
- return navigation?.map((navItem) => {
- if (activeURL === navItem.url) {
- return { ...navItem, active: true };
- }
- return navItem;
- });
-}
-
/**
* Returns the navigation config for the given slug.
* @param slug - Slug.
@@ -106,13 +139,7 @@ export function getNavigationConfig(
// Loop through the slug and find the node where slug matches the node's slug.
for (let i = 0; i < slug.length; i++) {
const key = slug[i];
- for (const {
- enableOutline = true,
- hero,
- layoutStyle,
- navigation,
- slugs,
- } of sectionMap.nodes) {
+ for (const { hero, layoutStyle, navigation, slugs } of sectionMap.nodes) {
if (slugs.includes(key)) {
if (slug.length !== 1 && i === 0) {
// Although the first slug's key is a match, continue if the slug has more than one element.
@@ -120,19 +147,107 @@ export function getNavigationConfig(
}
// Return the layout styles; navigation (and therefore hero) are undefined.
if (!navigation) {
- return { enableOutline, layoutStyle };
+ return { layoutStyle };
}
const pagePath = `/${slug.join("/")}`;
const activeURL = getActiveURL(pagePath, navigation);
if (activeURL) {
const navItems = getNavItems(activeURL, navigation);
- return { enableOutline, hero, layoutStyle, navigation: navItems };
+ return { hero, layoutStyle, navigation: navItems };
}
}
}
}
}
+/**
+ * Returns the navigation items with the active flag set to true if the URL matches the active URL.
+ * @param activeURL - Active URL.
+ * @param navigation - Navigation.
+ * @returns the navigation items with the active flag set to true if the URL matches the active URL.
+ */
+function getNavItems(
+ activeURL: string,
+ navigation?: NavItem[]
+): NavItem[] | undefined {
+ return navigation?.map((navItem) => {
+ if (activeURL === navItem.url) {
+ return { ...navItem, active: true };
+ }
+ return navItem;
+ });
+}
+
+/**
+ * Returns the static prop "layoutStyle", if specified from the frontmatter; otherwise defaults to configured layoutStyle.
+ * @param layoutStyle - Layout style.
+ * @param frontmatter - Frontmatter.
+ * @returns layout style.
+ */
+export function getStaticPropLayoutStyle(
+ layoutStyle: LayoutStyle | null,
+ frontmatter: Frontmatter | undefined
+): LayoutStyle | null {
+ switch (frontmatter?.layoutStyle) {
+ case "LAYOUT_STYLE_CONTRAST_LIGHT":
+ return LAYOUT_STYLE_CONTRAST_LIGHT;
+ case "LAYOUT_STYLE_CONTRAST_LIGHTEST":
+ return LAYOUT_STYLE_CONTRAST_LIGHTEST;
+ case "LAYOUT_STYLE_NO_CONTRAST_DEFAULT":
+ return LAYOUT_STYLE_NO_CONTRAST_DEFAULT;
+ case "LAYOUT_STYLE_NO_CONTRAST_LIGHT":
+ return LAYOUT_STYLE_NO_CONTRAST_LIGHT;
+ case "LAYOUT_STYLE_NO_CONTRAST_LIGHTEST":
+ return LAYOUT_STYLE_NO_CONTRAST_LIGHTEST;
+ default:
+ return layoutStyle;
+ }
+}
+
+/**
+ * Returns the static prop "navigation" from navigation configuration.
+ * If the frontmatter enableNavigation is false, the returned value is null.
+ * @param navigation - Navigation configuration.
+ * @param frontmatter - Frontmatter.
+ * @returns navigation.
+ */
+export function getStaticPropNavigation(
+ navigation: Navigation[] | null,
+ frontmatter: Frontmatter
+): Navigation[] | null {
+ if (frontmatter.enableNavigation) return navigation;
+ return null;
+}
+
+/**
+ * Returns the static prop "outline" from plugin-generated outline items, or the frontmatter outline, where the
+ * frontmatter outline takes precedence over plugin-generated outline.
+ * If the frontmatter enableOutline is false, the outline is returned as an empty array.
+ * @param outline - Plugin-generated outline items.
+ * @param frontmatter - Frontmatter.
+ * @returns outline.
+ */
+export function getStaticPropOutline(
+ outline: OutlineItem[],
+ frontmatter: Frontmatter
+): OutlineItem[] {
+ if (frontmatter.enableOutline) {
+ if ("outline" in frontmatter) return frontmatter.outline ?? [];
+ if (outline.length > 0) return outline.filter(filterOutline);
+ }
+ return [];
+}
+
+/**
+ * Frontmatter type guard.
+ * @param data - Grey matter file data.
+ * @returns true if the data is frontmatter.
+ */
+function isFrontmatter(data: unknown): data is Frontmatter {
+ if (!data) return false;
+ return typeof data === "object" && "title" in data;
+}
+
/**
* Returns true if the file is an MDX file.
* @param fileName - File name.
@@ -182,59 +297,28 @@ export function mapSlugByFilePaths(
}
/**
- * Returns the static paths for each mdx content in the "docs" directory.
- * @returns the static paths for the mdx content.
+ * Returns the frontmatter from the given grey matter file data.
+ * @param data - Grey matter file data.
+ * @returns frontmatter.
*/
-export function generatePaths(): GetStaticPathsResult["paths"] {
- const docsDirectory = getDocsDirectory();
- const slugByFilePaths = mapSlugByFilePaths(docsDirectory);
- return [...slugByFilePaths].map(([, slug]) => {
+export function parseFrontmatter(
+ data: matter.GrayMatterFile["data"]
+): Frontmatter | undefined {
+ if (isFrontmatter(data)) {
+ const {
+ enableContentEnd = true,
+ enableNavigation = true, // Remove default when "learn" UI is updated.
+ enableOutline = true,
+ enableOverview = false,
+ enableSupportForum = false,
+ } = data;
return {
- params: { slug },
+ enableContentEnd,
+ enableNavigation,
+ enableOutline,
+ enableOverview,
+ enableSupportForum,
+ ...data,
};
- });
-}
-
-/**
- * Returns the content layout style, specified by the navigation config or the frontmatter.
- * @param navigationLayoutStyle - Layout style, specified by the navigation config.
- * @param frontmatterLayoutStyle - Layout style, specified by the frontmatter.
- * @returns layout style.
- */
-export function getContentLayoutStyle(
- navigationLayoutStyle: LayoutStyle | undefined,
- frontmatterLayoutStyle: Frontmatter["layoutStyle"]
-): LayoutStyle | null {
- if (frontmatterLayoutStyle) {
- switch (frontmatterLayoutStyle) {
- case "LAYOUT_STYLE_CONTRAST_LIGHT":
- return LAYOUT_STYLE_CONTRAST_LIGHT;
- case "LAYOUT_STYLE_CONTRAST_LIGHTEST":
- return LAYOUT_STYLE_CONTRAST_LIGHTEST;
- case "LAYOUT_STYLE_NO_CONTRAST_DEFAULT":
- return LAYOUT_STYLE_NO_CONTRAST_DEFAULT;
- case "LAYOUT_STYLE_NO_CONTRAST_LIGHT":
- return LAYOUT_STYLE_NO_CONTRAST_LIGHT;
- case "LAYOUT_STYLE_NO_CONTRAST_LIGHTEST":
- return LAYOUT_STYLE_NO_CONTRAST_LIGHTEST;
- default:
- return null;
- }
}
- return navigationLayoutStyle || null;
-}
-
-/**
- * Returns MDX content and frontmatter from the given slug.
- * @param slug - Slug.
- * @returns MDX content and frontmatter.
- */
-export function parseMDXFrontmatter(
- slug: string[]
-): matter.GrayMatterFile {
- const markdownWithMeta = fs.readFileSync(
- getMDXFilePath(slug, getDocsDirectory()),
- "utf-8"
- );
- return matter(markdownWithMeta);
}
diff --git a/docs/learn/account-setup/creating-a-terra-account.mdx b/docs/learn/account-setup/creating-a-terra-account.mdx
index 8af032393..2c13e1f7c 100644
--- a/docs/learn/account-setup/creating-a-terra-account.mdx
+++ b/docs/learn/account-setup/creating-a-terra-account.mdx
@@ -1,6 +1,6 @@
---
description: ""
-title: "Account Setup"
+title: "Creating a Terra Account"
---
# Creating a Terra Account
diff --git a/docs/learn/account-setup/obtaining-a-google-id.mdx b/docs/learn/account-setup/obtaining-a-google-id.mdx
index fe5bde478..d6ac68fbf 100644
--- a/docs/learn/account-setup/obtaining-a-google-id.mdx
+++ b/docs/learn/account-setup/obtaining-a-google-id.mdx
@@ -1,6 +1,6 @@
---
description: "Obtaining a Google ID"
-title: "Account Setup"
+title: "Obtaining a Google ID"
---
# Obtaining a Google ID
diff --git a/docs/learn/account-setup/overview-of-account-setup.mdx b/docs/learn/account-setup/overview-of-account-setup.mdx
index 5b7e67ad4..becdde1ad 100644
--- a/docs/learn/account-setup/overview-of-account-setup.mdx
+++ b/docs/learn/account-setup/overview-of-account-setup.mdx
@@ -1,6 +1,6 @@
---
description: "Overview of Account Setup"
-title: "Account Setup"
+title: "Overview of Account Setup"
---
# Overview of Account Setup
diff --git a/docs/learn/getting-started.mdx b/docs/learn/getting-started.mdx
new file mode 100644
index 000000000..a18b33c2c
--- /dev/null
+++ b/docs/learn/getting-started.mdx
@@ -0,0 +1,37 @@
+---
+breadcrumbs:
+ - path: "/learn"
+ text: "Learn"
+ - path: ""
+ text: "Getting Started"
+description: "Getting started with the AnVIL Portal."
+enableContentEnd: false
+enableNavigation: false
+enableOverview: true
+enableSupportForum: true
+hidden: false
+overview:
+ - label: "Account Setup"
+ links:
+ - "/learn/account-setup/creating-a-terra-account"
+ - "/learn/account-setup/obtaining-a-google-id"
+ - "/learn/account-setup/overview-of-account-setup"
+ - label: "Billing Setup"
+ links:
+ - "/learn/billing-setup/billing-concepts"
+ - "/learn/billing-setup/creating-a-google-cloud-billing-account"
+ - label: "Accessing Data"
+ links:
+ - "/learn/accessing-data/bringing-your-own-data"
+ - "/learn/accessing-data/data-access-controls"
+ - "/learn/accessing-data/discovering-data"
+ - "/learn/accessing-data/requesting-data-access"
+ - label: "Running Analysis Workflows"
+ links:
+ - "/learn/analysis-workflows/running-galaxy-workflows-from-dockstore"
+ - "/learn/analysis-workflows/running-gatk"
+ - "/learn/analysis-workflows/using-example-workspaces"
+title: "Getting Started"
+---
+
+
diff --git a/package-lock.json b/package-lock.json
index b7041f4f3..0069d9988 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
"name": "the-anvil",
"version": "2.5.0",
"dependencies": {
- "@databiosphere/findable-ui": "15.0.2",
+ "@databiosphere/findable-ui": "16.0.0",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mdx-js/loader": "^3.0.1",
@@ -1080,9 +1080,9 @@
"license": "MIT"
},
"node_modules/@databiosphere/findable-ui": {
- "version": "15.0.2",
- "resolved": "https://registry.npmjs.org/@databiosphere/findable-ui/-/findable-ui-15.0.2.tgz",
- "integrity": "sha512-q6ANGEVeYmF8XjCAAiXadIva6tYZ4sO29grgQYAdcewgw1B7BxbiP7OXlQ9jAhSVGILZelWH8jqgFIR09lCrBw==",
+ "version": "16.0.0",
+ "resolved": "https://registry.npmjs.org/@databiosphere/findable-ui/-/findable-ui-16.0.0.tgz",
+ "integrity": "sha512-jWgWet5wrITcYOjRDNjm3GoijZCs4cKy5Tr/W5LDCjpTNN2PqvMfruTqB/qHDtpuIXYbKSjwFFfJ8jLd2QFDkg==",
"engines": {
"node": "20.10.0"
},
diff --git a/package.json b/package.json
index df4cc3dae..69e56ccc4 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"add-cser-materials": "esrun scripts/add-cser-materials.ts"
},
"dependencies": {
- "@databiosphere/findable-ui": "15.0.2",
+ "@databiosphere/findable-ui": "16.0.0",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mdx-js/loader": "^3.0.1",
diff --git a/pages/[...slug].tsx b/pages/[...slug].tsx
index c5fc75e32..7bc7810c8 100644
--- a/pages/[...slug].tsx
+++ b/pages/[...slug].tsx
@@ -14,15 +14,15 @@ import remarkGfm from "remark-gfm";
import { ContentView, Nav, NavBarHero } from "../components";
import { ContentEnd } from "../components/Layout/components/Content/components/ContentEnd/contentEnd";
import { Content } from "../components/Layout/components/Content/content";
-import { Frontmatter } from "../content/entities";
import { MDX_COMPONENTS, MDX_SCOPE } from "../docs/common/constants";
import { NodeHero } from "../docs/common/entities";
import {
+ extractMDXFrontmatter,
filterOutline,
generatePaths,
- getContentLayoutStyle,
getNavigationConfig,
- parseMDXFrontmatter,
+ getStaticPropLayoutStyle,
+ parseFrontmatter,
} from "../docs/common/utils";
import { rehypeSlug } from "../plugins/rehypeSlug";
import { remarkHeadings } from "../plugins/remarkHeadings";
@@ -68,15 +68,14 @@ export const getStaticProps: GetStaticProps = async (
) => {
const slug = props.params?.slug as string[];
const navigationConfig = getNavigationConfig(slug);
- const { enableOutline, hero, layoutStyle, navigation } =
- navigationConfig || {};
- const { content, data } = parseMDXFrontmatter(slug);
- const frontmatter = data as Frontmatter;
- if (frontmatter.hidden) {
- return {
- notFound: true,
- };
- }
+ const {
+ hero = null,
+ layoutStyle = null,
+ navigation = null,
+ } = navigationConfig || {};
+ const { content, data } = extractMDXFrontmatter(slug);
+ const frontmatter = parseFrontmatter(data);
+ if (!frontmatter || frontmatter.hidden) return { notFound: true };
const outline: OutlineItem[] = [];
const mdxSource = await serialize(content, {
mdxOptions: {
@@ -90,11 +89,11 @@ export const getStaticProps: GetStaticProps = async (
});
return {
props: {
- hero: hero ?? null,
- layoutStyle: getContentLayoutStyle(layoutStyle, frontmatter.layoutStyle),
+ hero,
+ layoutStyle: getStaticPropLayoutStyle(layoutStyle, frontmatter),
mdxSource,
- navigation: navigation ?? null,
- outline: enableOutline ? outline.filter(filterOutline) : [],
+ navigation,
+ outline: frontmatter.enableOutline ? outline.filter(filterOutline) : [],
pageTitle: frontmatter.title ?? null,
slug,
},
diff --git a/pages/events/[slug].tsx b/pages/events/[slug].tsx
index 3fade190e..8eab0c289 100644
--- a/pages/events/[slug].tsx
+++ b/pages/events/[slug].tsx
@@ -10,10 +10,13 @@ import { ContentView } from "../../components";
import { processEventFrontmatter } from "../../components/Events/common/utils";
import { ContentEnd } from "../../components/Layout/components/Content/components/ContentEnd/contentEnd";
import { Content } from "../../components/Layout/components/Content/content";
-import { Frontmatter } from "../../content/entities";
import { isFrontmatterEvent } from "../../content/utils";
import { MDX_COMPONENTS } from "../../docs/common/constants";
-import { generatePaths, parseMDXFrontmatter } from "../../docs/common/utils";
+import {
+ extractMDXFrontmatter,
+ generatePaths,
+ parseFrontmatter,
+} from "../../docs/common/utils";
import { rehypeSlug } from "../../plugins/rehypeSlug";
interface EventArticlePageUrlParams extends ParsedUrlQuery {
@@ -47,18 +50,10 @@ export const getStaticProps: GetStaticProps = async (
props: GetStaticPropsContext
) => {
const slug = getEventSlug(props.params as EventArticlePageUrlParams);
- const { content, data } = parseMDXFrontmatter(slug);
- const frontmatter = data as Frontmatter;
- if (frontmatter.hidden) {
- return {
- notFound: true,
- };
- }
- if (!isFrontmatterEvent(frontmatter)) {
- return {
- notFound: true,
- };
- }
+ const { content, data } = extractMDXFrontmatter(slug);
+ const frontmatter = parseFrontmatter(data);
+ if (!frontmatter || frontmatter.hidden) return { notFound: true };
+ if (!isFrontmatterEvent(frontmatter)) return { notFound: true };
const mdxSource = await serialize(content, {
mdxOptions: {
rehypePlugins: [rehypeSlug],
diff --git a/pages/events/index.tsx b/pages/events/index.tsx
index 019d8ee43..42ce3ac0e 100644
--- a/pages/events/index.tsx
+++ b/pages/events/index.tsx
@@ -6,9 +6,11 @@ import { serialize } from "next-mdx-remote/serialize";
import { ContentView } from "../../components";
import { processEventsFrontmatter } from "../../components/Events/common/utils";
import { Content } from "../../components/Layout/components/Content/content";
-import { Frontmatter } from "../../content/entities";
import { MDX_COMPONENTS } from "../../docs/common/constants";
-import { parseMDXFrontmatter } from "../../docs/common/utils";
+import {
+ extractMDXFrontmatter,
+ parseFrontmatter,
+} from "../../docs/common/utils";
const SLUG = ["events"];
@@ -31,13 +33,9 @@ const EventsPage = ({ mdxSource }: EventsPageProps): JSX.Element => {
};
export const getStaticProps: GetStaticProps = async () => {
- const { content, data } = parseMDXFrontmatter(SLUG);
- const frontmatter = data as Frontmatter;
- if (frontmatter.hidden) {
- return {
- notFound: true,
- };
- }
+ const { content, data } = extractMDXFrontmatter(SLUG);
+ const frontmatter = parseFrontmatter(data);
+ if (!frontmatter || frontmatter.hidden) return { notFound: true };
const mdxSource = await serialize(content, {
scope: {
events: processEventsFrontmatter(),
diff --git a/pages/learn/[...slug].tsx b/pages/learn/[...slug].tsx
new file mode 100644
index 000000000..2383001bb
--- /dev/null
+++ b/pages/learn/[...slug].tsx
@@ -0,0 +1,42 @@
+import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from "next";
+import { StyledMain } from "../../components/Layout/components/Main/main.styles";
+import { processOverviewFrontmatter } from "../../components/Layout/components/Section/components/SectionContent/components/SectionOverview/utils";
+import { StaticProps } from "../../content/entities";
+import { generateSectionPathWithFrontmatter } from "../../content/utils";
+import { generateStaticPaths } from "../../docs/common/generateStaticPaths";
+import { generateStaticProps } from "../../docs/common/generateStaticProps";
+import { ContentView } from "../../views/ContentView/contentView";
+import { LearnView } from "../../views/LearnView/learnView";
+
+const SECTION = "learn";
+
+const Page = (props: StaticProps): JSX.Element => {
+ if (!props.frontmatter.enableOverview) return ;
+ return ;
+};
+
+export const getStaticProps: GetStaticProps = async (
+ props: GetStaticPropsContext
+) => {
+ const frontmatters = generateSectionPathWithFrontmatter(SECTION);
+ const staticProps = await generateStaticProps(
+ props,
+ SECTION,
+ (frontmatter) =>
+ processOverviewFrontmatter(SECTION, frontmatter, frontmatters),
+ undefined
+ );
+ if (!staticProps) return { notFound: true };
+ return staticProps;
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ return {
+ fallback: false,
+ paths: generateStaticPaths(SECTION),
+ };
+};
+
+export default Page;
+
+Page.Main = StyledMain;
diff --git a/pages/learn/index.tsx b/pages/learn/index.tsx
new file mode 100644
index 000000000..a64800711
--- /dev/null
+++ b/pages/learn/index.tsx
@@ -0,0 +1,21 @@
+import { Main } from "@databiosphere/findable-ui/lib/components/Layout/components/ContentLayout/components/Main/main";
+import { GetStaticProps } from "next";
+import { StaticProps } from "../../content/entities";
+import { generateStaticProps } from "../../docs/common/generateStaticProps";
+import { ContentView } from "../../views/ContentView/contentView";
+
+const SLUG = ["learn"];
+
+const Page = (props: StaticProps): JSX.Element => {
+ return ;
+};
+
+export const getStaticProps: GetStaticProps = async () => {
+ const staticProps = await generateStaticProps({ params: { slug: SLUG } });
+ if (!staticProps) return { notFound: true };
+ return staticProps;
+};
+
+Page.Main = Main;
+
+export default Page;
diff --git a/pages/news/[year]/[month]/[date]/[slug].tsx b/pages/news/[year]/[month]/[date]/[slug].tsx
index a783d750d..c8f8531a8 100644
--- a/pages/news/[year]/[month]/[date]/[slug].tsx
+++ b/pages/news/[year]/[month]/[date]/[slug].tsx
@@ -10,11 +10,11 @@ import { ContentView } from "../../../../../components";
import { ContentEnd } from "../../../../../components/Layout/components/Content/components/ContentEnd/contentEnd";
import { Content } from "../../../../../components/Layout/components/Content/content";
import { processFrontmatter } from "../../../../../components/News/common/utils";
-import { Frontmatter } from "../../../../../content/entities";
import { MDX_COMPONENTS } from "../../../../../docs/common/constants";
import {
+ extractMDXFrontmatter,
generatePaths,
- parseMDXFrontmatter,
+ parseFrontmatter,
} from "../../../../../docs/common/utils";
import { rehypeSlug } from "../../../../../plugins/rehypeSlug";
@@ -52,13 +52,9 @@ export const getStaticProps: GetStaticProps = async (
props: GetStaticPropsContext
) => {
const slug = getNewsSlug(props.params as NewsArticlePageUrlParams);
- const { content, data } = parseMDXFrontmatter(slug);
- const frontmatter = data as Frontmatter;
- if (frontmatter.hidden) {
- return {
- notFound: true,
- };
- }
+ const { content, data } = extractMDXFrontmatter(slug);
+ const frontmatter = parseFrontmatter(data);
+ if (!frontmatter || frontmatter.hidden) return { notFound: true };
const mdxSource = await serialize(content, {
mdxOptions: {
rehypePlugins: [rehypeSlug],
diff --git a/pages/news/index.tsx b/pages/news/index.tsx
index eab36c37c..8279f15c1 100644
--- a/pages/news/index.tsx
+++ b/pages/news/index.tsx
@@ -6,9 +6,11 @@ import { serialize } from "next-mdx-remote/serialize";
import { ContentView } from "../../components";
import { Content } from "../../components/Layout/components/Content/content";
import { processNewsFrontmatter } from "../../components/News/common/utils";
-import { Frontmatter } from "../../content/entities";
import { MDX_COMPONENTS } from "../../docs/common/constants";
-import { parseMDXFrontmatter } from "../../docs/common/utils";
+import {
+ extractMDXFrontmatter,
+ parseFrontmatter,
+} from "../../docs/common/utils";
const SLUG = ["news"];
@@ -31,13 +33,9 @@ const NewsPage = ({ mdxSource }: NewsPageProps): JSX.Element => {
};
export const getStaticProps: GetStaticProps = async () => {
- const { content, data } = parseMDXFrontmatter(SLUG);
- const frontmatter = data as Frontmatter;
- if (frontmatter.hidden) {
- return {
- notFound: true,
- };
- }
+ const { content, data } = extractMDXFrontmatter(SLUG);
+ const frontmatter = parseFrontmatter(data);
+ if (!frontmatter || frontmatter.hidden) return { notFound: true };
const mdxSource = await serialize(content, {
scope: {
news: processNewsFrontmatter(),
diff --git a/site-config/anvil-portal/dev/config.ts b/site-config/anvil-portal/dev/config.ts
index 8a96f1f1f..cd0661af7 100644
--- a/site-config/anvil-portal/dev/config.ts
+++ b/site-config/anvil-portal/dev/config.ts
@@ -55,6 +55,7 @@ export function makeConfig(
versionInfo: true,
},
header: {
+ actions: C.Actions(),
announcements,
authenticationEnabled: false,
logo: C.Logo({
diff --git a/site-config/anvil-portal/dev/navigation/champions.ts b/site-config/anvil-portal/dev/navigation/champions.ts
index 17900ea3b..a4edda387 100644
--- a/site-config/anvil-portal/dev/navigation/champions.ts
+++ b/site-config/anvil-portal/dev/navigation/champions.ts
@@ -14,7 +14,6 @@ const PATH_SEGMENTS = {
export const ANVIL_CHAMPIONS: NavigationEntry = {
nodes: [
{
- enableOutline: false,
key: NODE_KEYS.ANVIL_CHAMPIONS,
layoutStyle: LAYOUT_STYLE_NO_CONTRAST_DEFAULT,
slugs: [PATH_SEGMENTS.ANVIL_CHAMPIONS],
diff --git a/views/ContentView/contentView.styles.ts b/views/ContentView/contentView.styles.ts
new file mode 100644
index 000000000..66039a258
--- /dev/null
+++ b/views/ContentView/contentView.styles.ts
@@ -0,0 +1,6 @@
+import { ContentView } from "@databiosphere/findable-ui/lib/views/ContentView/contentView";
+import styled from "@emotion/styled";
+
+export const StyledContentView = styled(ContentView)`
+ width: 100%;
+`;
diff --git a/views/ContentView/contentView.tsx b/views/ContentView/contentView.tsx
new file mode 100644
index 000000000..4a698624b
--- /dev/null
+++ b/views/ContentView/contentView.tsx
@@ -0,0 +1,70 @@
+import { NavItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Nav/nav";
+import { ContentsTab } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/components/ContentsTab/contentsTab";
+import {
+ Outline,
+ OutlineItem,
+} from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/outline";
+import { MDXRemote } from "next-mdx-remote";
+import { Nav, NavBarHero } from "../../components";
+import { ContentEnd } from "../../components/Layout/components/Content/components/ContentEnd/contentEnd";
+import { Content } from "../../components/Layout/components/Content/content";
+import { StaticProps } from "../../content/entities";
+import { MDX_COMPONENTS } from "../../docs/common/constants";
+import { NodeHero } from "../../docs/common/entities";
+import { StyledContentView } from "./contentView.styles";
+
+export const ContentView = ({
+ hero,
+ layoutStyle,
+ mdxSource,
+ navigation,
+ outline,
+ slug,
+}: StaticProps): JSX.Element | null => {
+ if (!mdxSource) return null;
+ return (
+
+
+
+
+ }
+ layoutStyle={layoutStyle ?? undefined}
+ outline={renderOutline(outline)}
+ navigation={renderNavigation(navigation, hero)}
+ />
+ );
+};
+
+/**
+ * Renders page navigation.
+ * @param navigation - Navigation items.
+ * @param hero - Navigation hero.
+ * @returns navigation.
+ */
+function renderNavigation(
+ navigation?: NavItem[] | null,
+ hero?: NodeHero | null
+): JSX.Element | undefined {
+ if (!navigation) return;
+ return (
+ : undefined}
+ navigation={navigation}
+ />
+ );
+}
+
+/**
+ * Renders page outline.
+ * @param outline - Outline items.
+ * @returns outline.
+ */
+function renderOutline(
+ outline?: OutlineItem[] | null
+): JSX.Element | undefined {
+ if (!outline) return;
+ if (outline.length === 0) return;
+ return ;
+}
diff --git a/views/LearnView/learnView.tsx b/views/LearnView/learnView.tsx
new file mode 100644
index 000000000..9d9901e18
--- /dev/null
+++ b/views/LearnView/learnView.tsx
@@ -0,0 +1,62 @@
+import { ContentsTab } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/components/ContentsTab/contentsTab";
+import {
+ Outline,
+ OutlineItem,
+} from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/outline";
+import { MDXRemote } from "next-mdx-remote";
+import { Fragment } from "react";
+import { ContentEnd } from "../../components/Layout/components/Content/components/ContentEnd/contentEnd";
+import { SupportForum } from "../../components/Layout/components/Content/components/SupportForum/supportForum";
+import { Content } from "../../components/Layout/components/Content/content";
+import { SectionContent } from "../../components/Layout/components/Section/components/SectionContent/sectionContent";
+import { SectionHero } from "../../components/Layout/components/Section/components/SectionHero/sectionHero";
+import { StaticProps } from "../../content/entities";
+import { MDX_COMPONENTS } from "../../docs/common/constants";
+
+export const LearnView = (props: StaticProps): JSX.Element => {
+ const { mdxSource, outline, ...contentProps } = props;
+ return (
+
+
+
+
+ {renderSupportForum(props)}
+ {renderContentEnd(props)}
+
+ }
+ outline={renderOutline(outline)}
+ {...contentProps}
+ />
+
+ );
+};
+
+/**
+ * Renders content end component "contribute" section.
+ * @param props - Static Props.
+ * @returns content end component.
+ */
+function renderContentEnd(props: StaticProps): JSX.Element | null {
+ if (!props.frontmatter.enableContentEnd) return null;
+ return ;
+}
+
+/**
+ * Renders page outline.
+ * @param outline - Outline items.
+ * @returns outline.
+ */
+function renderOutline(
+ outline?: OutlineItem[] | null
+): JSX.Element | undefined {
+ if (!outline) return;
+ if (outline.length === 0) return;
+ return ;
+}
+
+function renderSupportForum(props: StaticProps): JSX.Element | null {
+ if (!props.frontmatter.enableSupportForum) return null;
+ return ;
+}