diff --git a/frontend/src/components/ArticleLink.jsx b/frontend/src/components/ArticleLink.jsx index 2a07ded..57b726b 100644 --- a/frontend/src/components/ArticleLink.jsx +++ b/frontend/src/components/ArticleLink.jsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' -import { Link } from 'react-router-dom' +import { Link, useLocation } from 'react-router-dom' import api from '../api/client' import { useSplitView } from '../context/SplitViewContext' import './ArticleLink.css' @@ -60,7 +60,12 @@ export default function ArticleLink({ slug, sectionId = null, children, classNam // A citation can point at one section rather than the whole article; the // reader opens on it. Nothing in prose writes one, so it is normally absent. const where = sectionId ? `?section=${encodeURIComponent(sectionId)}` : '' - const href = (/^\d+$/.test(slug) ? `/articles/${slug}` : `/articles/s/${slug}`) + where + // A cross-reference followed from Editorial stays in Editorial. Reading and + // Editorial are two modes of the same page, and one link out of the second + // into the first put an educator into the learner's view of the next + // article with no way back to the queue. + const root = useLocation().pathname.startsWith('/editorial') ? '/editorial/articles' : '/articles' + const href = (/^\d+$/.test(slug) ? `${root}/${slug}` : `${root}/s/${slug}`) + where useEffect(() => () => clearTimeout(timer.current), []) diff --git a/frontend/src/components/ArticleReader.jsx b/frontend/src/components/ArticleReader.jsx index b38d54e..a1c012e 100644 --- a/frontend/src/components/ArticleReader.jsx +++ b/frontend/src/components/ArticleReader.jsx @@ -7,6 +7,7 @@ import '../pages/ArticlesPage.css' import useMediaQuery from '../hooks/useMediaQuery' import useHeaderOffset from '../hooks/useHeaderOffset' import { useSessionDrawer } from '../context/SessionDrawer' +import SiteMenuLinks from './SiteMenuLinks' import RichText from './RichText' /** * Article prose goes through the same renderer as everything else. @@ -94,6 +95,11 @@ export default function ArticleReader({ // you consult, and a wall of prose hides the one heading you came for. const [openIds, setOpenIds] = useState({}) const [drawerOpen, setDrawerOpen] = useState(false) + //: Which half of the drawer is showing. The same two the session player + //: offers, because it is the same button: the thing you are in, and the way + //: out of it. Reading had only the contents, so the burger on an article was + //: a menu button that could not reach the menu. + const [drawerTab, setDrawerTab] = useState('contents') // On a phone the contents live behind the same button as the site menu, the // way a session's questions do: one control at the top left whose contents // change with where you are, rather than a second menu to hunt for. @@ -105,13 +111,15 @@ export default function ArticleReader({ // was still sitting there in the layout: the contents did not open, and the // site menu did not either. The button was simply dead. const narrow = useMediaQuery('(max-width: 820px)') - const { register: registerDrawer } = useSessionDrawer() + const { register: registerDrawer, setOpen: setDrawerShown } = useSessionDrawer() useEffect(() => { if (!narrow || bare) return undefined // A toggle, not an opener: the button that opened the contents is the // one a thumb goes back to, and pressing it again did nothing at all. return registerDrawer(() => setDrawerOpen(open => !open)) }, [narrow, bare, registerDrawer]) + // So the header's button can show a cross while this is open. + useEffect(() => { setDrawerShown(drawerOpen) }, [drawerOpen, setDrawerShown]) // Collapsing the contents rail hands its width to the prose. const [railOpen, setRailOpen] = useState(() => stored(RAIL_KEY, 'open') !== 'closed') const [size, setSize] = useState(() => (SIZES.includes(stored(SIZE_KEY, 'm')) ? stored(SIZE_KEY, 'm') : 'm')) @@ -381,6 +389,24 @@ export default function ArticleReader({ onClick={() => setDrawerOpen(false)} /> )}