diff --git a/website/src/components/lesson.css.ts b/website/src/components/lesson.css.ts new file mode 100644 index 00000000..b1f6feac --- /dev/null +++ b/website/src/components/lesson.css.ts @@ -0,0 +1,117 @@ +import { globalStyle, style } from "@vanilla-extract/css" +import { important, padding } from "polished" +import { Styles } from "polished/lib/types/style" +import { + colors, + hsize, + hspace, + space, + thickness, + vspace, +} from "src/style/constants" +import { marginY, paddingX } from "src/style/utils" + +// Parent class defining a Wordpress page. +// All classes defined in this file begin with this generated className to control the scope of these styles. +export const lesson = style({}) + +const mobileQuery = "screen and (max-width: 52em)" + +//// INPUT STYLES +const mobileInputSize = `calc(1rem * 2)` + +// On mobile, increases width and height of input boxes for better mobile visibility. +globalStyle(`${lesson} input`, { + "@media": { + [mobileQuery]: important({ + width: hsize.full, + height: mobileInputSize, + }), + }, +}) + +// A global style that styles fill in the blank sentences where a word +// appears below the input box. +globalStyle(`${lesson} .fill-blank span`, { + display: "inline-flex", + flexDirection: "column", + alignItems: "center", + ...paddingX(hspace.halfEdge), +}) + +//// IMAGE AND CAPTION STYLES +const figure = `${lesson} figure` +// Gives flexbox items (image and caption) a column look only on mobile. +globalStyle(figure, { + display: "flex", + flexDirection: "row", + alignItems: "center", + textAlign: "left", + gap: `calc(${space.large} * 2)`, + + "@media": { + [mobileQuery]: important({ + flexDirection: "column", + gap: 0, + }), + }, +}) + +// Styles the caption text of figures. +globalStyle(`${figure} figcaption > *`, { + color: colors.primary, + display: "flex", + flexDirection: "column", + alignItems: "start", +}) + +/// TABLE STYLES +// Styles rows of a table. +// On mobile, since row width is limited, convert each row into a "column-like" element +// and space out their margins. +globalStyle(`${lesson} table tr`, { + "@media": { + [mobileQuery]: important({ + display: "flex", + flexDirection: "column", + border: `${thickness.thin} solid ${colors.text}`, + ...(marginY(vspace.large) as Styles), + }), + }, +}) + +// Styles columns of a table. +// On mobile, give columns full width with equally sized padding. +globalStyle(`${lesson} table td`, { + "@media": { + [mobileQuery]: important({ + width: hsize.full, + ...padding(hspace.medium), + }), + }, +}) + +/// HORIZONTAL LINE STYLES +globalStyle( + `${lesson} hr`, + important({ width: hsize.full, marginBottom: vspace["1.5"] }) +) + +/// BLOCKQUOTES STYLES +// By default, set this width to 500px, but on mobile make width auto to +// compensate for the smaller size screen. +globalStyle(`${lesson} blockquote`, { + width: `calc(${hsize.large} / 2)`, + "@media": { + [mobileQuery]: important({ + width: hsize.auto, + }), + }, +}) + +/// MODULE BUTTON STYLES +// Styles the position of the module buttons at the end of every lesson. +globalStyle(`${lesson} .module-buttons`, { + display: "flex", + justifyContent: "space-between", +}) diff --git a/website/src/components/wordpress.tsx b/website/src/components/wordpress.tsx index 56fb373d..d4a48e75 100644 --- a/website/src/components/wordpress.tsx +++ b/website/src/components/wordpress.tsx @@ -1,3 +1,5 @@ +import * as domhandler from "domhandler" +import { isText } from "domhandler" import parse, { DOMNode, HTMLReactParserOptions, @@ -5,30 +7,23 @@ import parse, { domToReact, htmlToDOM, } from "html-react-parser" -import * as domhandler from "domhandler" -import { isText } from "domhandler" import React from "react" +import { Tab, TabList, TabPanel } from "reakit" import { AudioPlayer, Button, Link } from "src/components" import * as Dailp from "src/graphql/dailp" import * as Wordpress from "src/graphql/wordpress" import { usePreferences } from "src/preferences-context" import { useRouteParams } from "src/renderer/PageShell" import { collectionWordPath, documentWordPath } from "src/routes" +import { useScrollableTabState } from "src/scrollable-tabs" import { AnnotatedForm } from "src/segment" import { annotationSection } from "src/segment.css" -import { wordpressUrl, devUrl, prodUrl } from "src/theme.css" +import { devUrl, prodUrl, wordpressUrl } from "src/theme.css" import { LevelOfDetail } from "src/types" -import * as printLessonCSS from "./print-lesson.css" -import { LexicalSearch } from "./lexical-search" -import { Glossary } from "./glossary" import * as css from "../pages/documents/document.css" -import { - useTabState, - Tab, - TabList, - TabPanel, -} from "reakit" -import { useScrollableTabState } from "src/scrollable-tabs" +import { Glossary } from "./glossary" +import * as lessonCSS from "./lesson.css" +import { LexicalSearch } from "./lexical-search" interface Props { slug: string @@ -68,14 +63,11 @@ export const WordpressPageContents = ({ // If the slug includes "lessons/", include a parent element that styles its children's elements for printed media. if (slug?.includes("lessons/")) { parsed = ( -
- {parse(content, parseOptions)} -
+
{parse(content, parseOptions)}
) } else { parsed = parse(content, parseOptions) } - return <>{parsed} } else { return null @@ -86,21 +78,23 @@ const parseOptions: HTMLReactParserOptions = { replace(node) { if ("data" in node) { const referenceEmbedStyle = /\[(\w*)\]/ // [search | glossary] - const wordEmbedStyle = /\[(\w*):([0-9]*)-?([0-9]*)?:?(audio)?(join)?(#)?(\w*)?\]/ // [DocName:Start(-OptionalEnd):?(audio?)(join?)#?OptionalChapterSlug?] + const wordEmbedStyle = + /\[(\w*):([0-9]*)-?([0-9]*)?:?(audio)?(join)?(#)?(\w*)?\]/ // [DocName:Start(-OptionalEnd):?(audio?)(join?)#?OptionalChapterSlug?] const wordSegments = node.data.match(wordEmbedStyle)?.filter((x) => !!x) - const referenceSegments = node.data.match(referenceEmbedStyle)?.filter((x) => !!x) + const referenceSegments = node.data + .match(referenceEmbedStyle) + ?.filter((x) => !!x) if (referenceSegments && referenceSegments[1] === "search") { - return() + return } else if (referenceSegments && referenceSegments[1] === "glossary") { - return() + return } if (wordSegments) { return parseWord(wordSegments) } - } - else if ("name" in node && "attribs" in node) { + } else if ("name" in node && "attribs" in node) { if (node.name === "a") { return urlToAbsolutePath(node.attribs, node.children) } else if (node.name === "button") { @@ -109,7 +103,10 @@ const parseOptions: HTMLReactParserOptions = { {domToReact(node.children, parseOptions)} ) - } else if (node.name === "div" && node.attribs["class"]?.includes("wpTabs")) { + } else if ( + node.name === "div" && + node.attribs["class"]?.includes("wpTabs") + ) { return nodesToTabs(node.children) } } @@ -254,17 +251,28 @@ const PullWords = (props: {
- {collectionSlug && props.chapterSlug ? - {documentCitation} : - {documentCitation}} - + {collectionSlug && props.chapterSlug ? ( + + {documentCitation} + + ) : ( + + {documentCitation} + + )}
) } -function parseWord(segments: string[] ): JSX.Element | undefined { +function parseWord(segments: string[]): JSX.Element | undefined { if ( segments.length > 2 && (segments[3] === "audio" || segments[4] === "audio") @@ -282,8 +290,12 @@ function parseWord(segments: string[] ): JSX.Element | undefined { = 4 ? (segments[5]!) : undefined} + last={ + segments.length < 4 || segments[4] === "#" + ? undefined + : parseInt(segments[3]!) + } + chapterSlug={segments.length >= 4 ? segments[5]! : undefined} /> ) } @@ -291,15 +303,18 @@ function parseWord(segments: string[] ): JSX.Element | undefined { } /** Replace DAILP links in an element with absolute local paths. -* @param attribs the attributes of the current element -* @param children the children of the current element -* @returns {JSX.Element} a Link with the href prop as a local path and containing the provided children -* -* "https://wp.dailp.northeastern.edu/" => "/" -* "https://dev.dailp.northeastern.edu/" => "/" -* "https://dailp.northeastern.edu/" => "/" -*/ -function urlToAbsolutePath(attribs: {[name: string]:string}, children: any): JSX.Element { + * @param attribs the attributes of the current element + * @param children the children of the current element + * @returns {JSX.Element} a Link with the href prop as a local path and containing the provided children + * + * "https://wp.dailp.northeastern.edu/" => "/" + * "https://dev.dailp.northeastern.edu/" => "/" + * "https://dailp.northeastern.edu/" => "/" + */ +function urlToAbsolutePath( + attribs: { [name: string]: string }, + children: any +): JSX.Element { const props = attributesToProps(attribs) if (props["href"]?.startsWith(wordpressUrl)) { @@ -313,13 +328,13 @@ function urlToAbsolutePath(attribs: {[name: string]:string}, children: any): JSX return {domToReact(children, parseOptions)} } -function nodesToTabs (node: domhandler.Node[]) { +function nodesToTabs(node: domhandler.Node[]) { const headingStyle = /\|(.*)\|/ const tabList = node.filter((value) => { if (!isText(value)) return false - let headings = value.data.match(headingStyle)?.filter(x => !!x) - return headings && headings.length > 1 + let headings = value.data.match(headingStyle)?.filter((x) => !!x) + return headings && headings.length > 1 }) const init: DOMNode[][] = [] const tabPanelList = node.reduce((prev, curr) => { @@ -332,27 +347,30 @@ function nodesToTabs (node: domhandler.Node[]) { }, init) const tabState = useScrollableTabState() return ( - <> - - {tabList.map((t, i) => { - return isText(t) && - - {t.data.match(headingStyle)?.filter(x => !!x)[1]} - + <> + + {tabList.map((t, i) => { + return ( + isText(t) && ( + + {t.data.match(headingStyle)?.filter((x) => !!x)[1]} + + ) + ) + })} + + {tabPanelList.map((p, i) => { + return ( + + {domToReact(p, parseOptions)} + + ) })} - - {tabPanelList.map((p, i) => { - return {domToReact(p, parseOptions)}})} - + ) }