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 ( +