diff --git a/src/static/js/pad.ts b/src/static/js/pad.ts index d9698f5e776..76fc2616bf4 100644 --- a/src/static/js/pad.ts +++ b/src/static/js/pad.ts @@ -76,6 +76,22 @@ const getParameters = [ $('#editbar').css('display', 'flex'); }, }, + { + // 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. + name: 'showMenuRight', + checkVal: null, + callback: (val) => { + if (val === 'false') { + $('#editbar .menu_right').hide(); + } else if (val === 'true') { + $('#editbar .menu_right').show(); + } + }, + }, { name: 'showChat', checkVal: null, @@ -678,6 +694,14 @@ 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(); + } } 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 new file mode 100644 index 00000000000..5d09749a27c --- /dev/null +++ b/src/tests/frontend-new/specs/hide_menu_right.spec.ts @@ -0,0 +1,60 @@ +import {expect, Page, test} from "@playwright/test"; +import {appendQueryParams, goToNewPad} from "../helper/padHelper"; + +test.beforeEach(async ({page}) => { + // clearCookies on the page's own context — creating a separate + // BrowserContext and clearing cookies on it is a no-op for the page + // fixture (Qodo review feedback on #7553). + await page.context().clearCookies(); + await goToNewPad(page); +}); + +test.describe('showMenuRight URL parameter', function () { + test('without the parameter, .menu_right is visible', async function ({page}) { + await expect(page.locator('#editbar .menu_right')).toBeVisible(); + }); + + test('showMenuRight=false hides .menu_right', async function ({page}) { + await appendQueryParams(page, {showMenuRight: 'false'}); + await expect(page.locator('#editbar .menu_right')).toBeHidden(); + // The left menu stays visible so the pad remains navigable. + await expect(page.locator('#editbar .menu_left')).toBeVisible(); + }); + + test('showMenuRight=true keeps .menu_right visible', async function ({page}) { + await appendQueryParams(page, {showMenuRight: 'true'}); + await expect(page.locator('#editbar .menu_right')).toBeVisible(); + }); + + // Helper: open the Share popup, flip it to read-only, read the r.* URL + // back out of #linkinput. The readonly toggle is a checkbox + // (`#readonlyinput`) that rewrites #linkinput's value live. The popup + // animates open, so the checkbox is briefly "not stable" — wait for + // the popup's show class before interacting, and use force:true so a + // trailing transform doesn't trip Playwright's stability check. + const getReadonlyUrl = async (page: Page) => { + await page.locator('.buttonicon-embed').click(); + await page.locator('#embed.popup-show').waitFor({state: 'visible'}); + await page.locator('#readonlyinput').check({force: true}); + await page.waitForFunction( + () => (document.querySelector('#linkinput') as HTMLInputElement | null) + ?.value.includes('/p/r.')); + const url = await page.locator('#linkinput').inputValue(); + expect(url).toMatch(/\/p\/r\./); + return url; + }; + + test('readonly pad hides .menu_right by default', async function ({page}) { + const readonlyUrl = await getReadonlyUrl(page); + await page.goto(readonlyUrl); + await page.waitForSelector('#editorcontainer.initialized'); + await expect(page.locator('#editbar .menu_right')).toBeHidden(); + }); + + test('readonly pad with showMenuRight=true keeps the menu visible', async function ({page}) { + const readonlyUrl = await getReadonlyUrl(page); + await page.goto(`${readonlyUrl}?showMenuRight=true`); + await page.waitForSelector('#editorcontainer.initialized'); + await expect(page.locator('#editbar .menu_right')).toBeVisible(); + }); +});