diff --git a/src/static/js/pad.ts b/src/static/js/pad.ts index 8a8360d55f1..f176bddd9e6 100644 --- a/src/static/js/pad.ts +++ b/src/static/js/pad.ts @@ -91,10 +91,12 @@ const getParameters = [ }, { // showMenuRight accepts 'true' or 'false'. Explicit 'false' hides the - // right-side toolbar (import/export/timeslider/settings/share/users); - // explicit 'true' forces it visible, overriding the readonly - // auto-hide applied further down (issue #5182). Any other value is - // a no-op — the menu stays in its default state. + // right-side toolbar (import/export/timeslider/settings/embed/home/ + // users) — useful for iframe-embedded readonly pads where the menu + // is just chrome (issue #5182). Explicit 'true' forces the menu + // visible (a no-op against the default, kept for symmetry and for + // overriding any future caller-applied hide). Any other value is a + // no-op — the menu stays in its default state. name: 'showMenuRight', checkVal: null, callback: (val) => { @@ -787,14 +789,18 @@ const pad = { $('#chaticon').hide(); $('#options-chatandusers').parent().hide(); $('#options-stickychat').parent().hide(); - // Hide the right-side toolbar on readonly pads — import/export, - // timeslider, settings, share, users are all noise for viewers - // who can't interact with the pad. Callers who need those - // controls visible on a readonly pad can force them back via - // `?showMenuRight=true`, which runs in getParameters() above. - if (getUrlVars().get('showMenuRight') !== 'true') { - $('#editbar .menu_right').hide(); - } + // The right-side toolbar stays visible on readonly pads. The + // server-side `toolbar.menu(buttons, isReadOnly)` (see + // src/node/utils/toolbar.ts) already strips `savedrevision`, and + // `.readonly .acl-write { display: none }` hides the Import column + // inside the import/export popup, so the remaining controls + // (export, timeslider, settings, embed, home, showusers) are all + // safe for readonly viewers — and the userlist is the surface that + // plugins like ep_guest hang their "Log In" button off, so hiding + // it traps guests in readonly with no way out. Iframe-embed use + // cases that want a clean look (issue #5182) opt in to the hide + // via `?showMenuRight=false`, or hide the whole editbar via + // `?showControls=false`. } else if (!settings.hideChat) { $('#chaticon').show(); } $('body').addClass(window.clientVars.readonly ? 'readonly' : 'readwrite'); diff --git a/src/tests/frontend-new/specs/hide_menu_right.spec.ts b/src/tests/frontend-new/specs/hide_menu_right.spec.ts index 5d09749a27c..8498b668b42 100644 --- a/src/tests/frontend-new/specs/hide_menu_right.spec.ts +++ b/src/tests/frontend-new/specs/hide_menu_right.spec.ts @@ -44,10 +44,25 @@ test.describe('showMenuRight URL parameter', function () { return url; }; - test('readonly pad hides .menu_right by default', async function ({page}) { + test('readonly pad shows .menu_right by default', async function ({page}) { + // The server-side toolbar.menu(..., isReadOnly) strips `savedrevision` + // and `.readonly .acl-write { display: none }` hides the Import + // column of the import/export popup, so the remaining controls + // (export, timeslider, settings, embed, home, showusers) are safe + // for readonly viewers — and ep_guest / other auth plugins depend + // on the userlist being reachable to surface their Log In button. const readonlyUrl = await getReadonlyUrl(page); await page.goto(readonlyUrl); await page.waitForSelector('#editorcontainer.initialized'); + await expect(page.locator('#editbar .menu_right')).toBeVisible(); + }); + + test('readonly pad with showMenuRight=false hides the menu', async function ({page}) { + // Iframe-embed use case (issue #5182): the embedder opts in to the + // hide via the URL parameter when they want a clean look. + const readonlyUrl = await getReadonlyUrl(page); + await page.goto(`${readonlyUrl}?showMenuRight=false`); + await page.waitForSelector('#editorcontainer.initialized'); await expect(page.locator('#editbar .menu_right')).toBeHidden(); });