diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 67079ee..1a3e44e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -235,12 +235,6 @@ function AppRoutes() { {/* Cross-references in article prose address a topic by slug, which outlives a numeric id and is what an educator actually writes. */} } /> - {/* The same page at Editorial's own address. An article reached - from the queue is being worked on, not read, and the trail out - of it should go back to the queue — which it cannot do if the - two are the same URL. */} - } /> - } /> } /> {/* One front door. The dashboard's contents are sections of Settings now; the old address still works for anyone who @@ -265,6 +259,15 @@ function AppRoutes() { } /> } /> } /> + {/* The same page as Reading, at Editorial's own address: an + article reached from the queue is being worked on, not read, + and the trail out of it goes back to the queue. Behind the + same door as the queue itself — it started out in the + authenticated block, where any signed-in learner could open + the editorial view of an article and find a crumb to a page + they are not allowed on. */} + } /> + } /> } /> } /> } /> diff --git a/frontend/src/pages/ArticlesPage.jsx b/frontend/src/pages/ArticlesPage.jsx index 7032934..201c081 100644 --- a/frontend/src/pages/ArticlesPage.jsx +++ b/frontend/src/pages/ArticlesPage.jsx @@ -314,10 +314,13 @@ export function ArticlePage() { // taken to the editorial one rather than ignored: the ask is legitimate, the // address is the wrong one. useEffect(() => { - if (!inEditorial && searchParams.get('edit') === '1' && id) { - navigate(`/editorial/articles/${id}?edit=1`, { replace: true }) - } - }, [inEditorial, searchParams, id, navigate]) + if (inEditorial || searchParams.get('edit') !== '1' || !id) return + // Only for somebody who may go there. For anybody else the parameter is + // just dropped: sending a learner to a door they cannot open is worse + // than ignoring a query string they did not type. + if (user?.is_moderator) navigate(`/editorial/articles/${id}?edit=1`, { replace: true }) + else navigate(`/articles/${id}`, { replace: true }) + }, [inEditorial, searchParams, id, navigate, user?.is_moderator]) // Reading claims the window; editing hands it back. Width only — the // navbar's own section strip is left alone, because every link on it is @@ -477,7 +480,11 @@ export function ArticlePage() { // is how somebody ended up in edit mode on Bronchiolitis in the middle of a // session. The same page at /editorial/articles/:id has all of it. const editorActions = !inEditorial ? ( - canEdit ? ( + // Moderators and admins, and nobody else. `canEdit` also covers whoever + // wrote the article, which is not the same thing: Editorial is behind the + // moderator door, so offering that door to an author who is a plain user + // is offering them a Forbidden page. + user?.is_moderator ? ( Open in Editorial diff --git a/frontend/src/pages/ArticlesPage.test.jsx b/frontend/src/pages/ArticlesPage.test.jsx index 936354f..302e019 100644 --- a/frontend/src/pages/ArticlesPage.test.jsx +++ b/frontend/src/pages/ArticlesPage.test.jsx @@ -307,8 +307,9 @@ describe('topic reading', () => { expect(screen.queryByRole('button', { name: 'Edit' })).toBeNull() expect(screen.queryByRole('button', { name: /Unpublish|Publish/ })).toBeNull() expect(screen.queryByRole('button', { name: /Generate cards/ })).toBeNull() - // And a learner is not offered the other mode either — the door belongs to - // whoever may edit. + // And a learner is not offered the other mode either. Editorial is behind + // the moderator door, so the way through belongs to whoever may go there — + // not to whoever happens to have written the article. expect(screen.queryByRole('link', { name: 'Open in Editorial' })).toBeNull() })