Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[DP-180] Fixed mobile display of lesson pages #229

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions website/src/components/lesson.css.ts
Original file line number Diff line number Diff line change
@@ -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)"
chanmioh marked this conversation as resolved.
Show resolved Hide resolved

//// INPUT STYLES
const mobileInputSize = `calc(1rem * 2)`
chanmioh marked this conversation as resolved.
Show resolved Hide resolved

// 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",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does inline-flex do? Why not normal 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)`,
chanmioh marked this conversation as resolved.
Show resolved Hide resolved

"@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),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised this as clause is necessary, it wasn't passing typecheck? Later on, padding seems to work fine so maybe revise the type of the marginY function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean revise the type that marginY returns? Since it looks like the padding functions that work return a Styles instead of StyleRules which allows them to work

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try this out chanmi, and deploy changes after @GracefulLemming reviews.

}),
},
})

// 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)`,
chanmioh marked this conversation as resolved.
Show resolved Hide resolved
"@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",
})
146 changes: 82 additions & 64 deletions website/src/components/wordpress.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
import * as domhandler from "domhandler"
import { isText } from "domhandler"
import parse, {
DOMNode,
HTMLReactParserOptions,
attributesToProps,
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
Expand Down Expand Up @@ -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 = (
<div className={printLessonCSS.lesson}>
{parse(content, parseOptions)}
</div>
<div className={lessonCSS.lesson}>{parse(content, parseOptions)}</div>
)
} else {
parsed = parse(content, parseOptions)
}

return <>{parsed}</>
} else {
return null
Expand All @@ -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(<LexicalSearch/>)
return <LexicalSearch />
} else if (referenceSegments && referenceSegments[1] === "glossary") {
return(<Glossary/>)
return <Glossary />
}
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") {
Expand All @@ -109,7 +103,10 @@ const parseOptions: HTMLReactParserOptions = {
{domToReact(node.children, parseOptions)}
</Button>
)
} else if (node.name === "div" && node.attribs["class"]?.includes("wpTabs")) {
} else if (
node.name === "div" &&
node.attribs["class"]?.includes("wpTabs")
) {
return nodesToTabs(node.children)
}
}
Expand Down Expand Up @@ -254,17 +251,28 @@ const PullWords = (props: {
</div>
<div>
<i>
{collectionSlug && props.chapterSlug ?
<Link href={collectionWordPath(collectionSlug, props.chapterSlug, props.first)}>{documentCitation}</Link> :
<Link href={documentWordPath(props.slug, props.first)}>{documentCitation}</Link>}

{collectionSlug && props.chapterSlug ? (
<Link
href={collectionWordPath(
collectionSlug,
props.chapterSlug,
props.first
)}
>
{documentCitation}
</Link>
) : (
<Link href={documentWordPath(props.slug, props.first)}>
{documentCitation}
</Link>
)}
</i>
</div>
</>
)
}

function parseWord(segments: string[] ): JSX.Element | undefined {
function parseWord(segments: string[]): JSX.Element | undefined {
if (
segments.length > 2 &&
(segments[3] === "audio" || segments[4] === "audio")
Expand All @@ -282,24 +290,31 @@ function parseWord(segments: string[] ): JSX.Element | undefined {
<PullWords
slug={segments[1]!}
first={parseInt(segments[2]!)}
last={segments.length < 4 || segments[4] === "#" ? undefined : parseInt(segments[3]!)}
chapterSlug={segments.length >= 4 ? (segments[5]!) : undefined}
last={
segments.length < 4 || segments[4] === "#"
? undefined
: parseInt(segments[3]!)
}
chapterSlug={segments.length >= 4 ? segments[5]! : undefined}
/>
)
}
return 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)) {
Expand All @@ -313,13 +328,13 @@ function urlToAbsolutePath(attribs: {[name: string]:string}, children: any): JSX
return <Link {...props}>{domToReact(children, parseOptions)}</Link>
}

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) => {
Expand All @@ -332,27 +347,30 @@ function nodesToTabs (node: domhandler.Node[]) {
}, init)
const tabState = useScrollableTabState()
return (
<>
<TabList
{...tabState}
className={css.docTabs}
aria-label="My tabs">
{tabList.map((t, i) => {
return isText(t) &&
<Tab
id={"tab-"+i}
className={css.docTab}
{...tabState}>
{t.data.match(headingStyle)?.filter(x => !!x)[1]}
</Tab>
<>
<TabList {...tabState} className={css.docTabs} aria-label="My tabs">
{tabList.map((t, i) => {
return (
isText(t) && (
<Tab id={"tab-" + i} className={css.docTab} {...tabState}>
{t.data.match(headingStyle)?.filter((x) => !!x)[1]}
</Tab>
)
)
})}
</TabList>
{tabPanelList.map((p, i) => {
return (
<TabPanel
id={"tabPanel" + i}
tabId={"tab-" + i}
className={css.docTabPanel}
{...tabState}
>
{domToReact(p, parseOptions)}
</TabPanel>
)
})}
</TabList>
{tabPanelList.map((p, i) => {
return <TabPanel
id={"tabPanel"+i}
tabId={"tab-"+i}
className={css.docTabPanel}
{...tabState}>{domToReact(p, parseOptions)}</TabPanel>})}
</>
</>
)
}