diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 9e529a692ff..e2bc4c89e68 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -139,16 +139,22 @@ input { .history-banner-date { opacity: 0.85; } .history-banner #history-banner-return { margin-left: auto; } -/* The history iframe takes the place of the live ACE editor. We use the */ -/* same positioning model (absolute, fill the editor area) so the page */ -/* layout is identical between modes. */ +/* The history iframe takes the place of the live ACE editor by occupying the + * same in-flow flex slot that #editorcontainer fills in live mode (flex: 1 1 + * auto inside the row-flex #editorcontainerbox). This keeps the layout + * identical between modes — crucially including any in-flow side panels in + * #editorcontainerbox, such as ep_webrtc's video column, which now sit beside + * the history iframe exactly as they sit beside the live editor. + * + * This used to be an absolute, inset:0 overlay: it filled the editor area but + * took the iframe out of flow, so an in-flow side panel ended up hidden + * beneath it (see ether/ep_webrtc rendering nothing in the timeslider). */ .history-frame-mount { display: none; - position: absolute; - inset: 0; + flex: 1 1 auto; + min-width: 0; /* allow the iframe to shrink to make room for a side panel */ border: 0; background: var(--bg-color, #f2f3f4); - z-index: 4; } .history-frame-mount[hidden] { display: none; } .history-frame-mount > iframe { @@ -164,8 +170,14 @@ input { /* editor, so we swap it for the history controls (slider + play/step */ /* buttons) that drive the iframe. The right-side menu (Settings / Share / */ /* Users / Chat / Home) stays fully interactive across modes. */ -body.history-mode #editorcontainer { display: none; } -body.history-mode #editorcontainerbox { position: relative; } +/* The two-id `#editorcontainerbox #editorcontainer { display: flex }` layout + * rule (specificity 0-2-0) outranks a plain `body.history-mode #editorcontainer` + * (0-1-1), so match the same container path here to actually win the cascade + * and remove the live editor from flow. This was latent before: the old + * absolute, inset:0 iframe overlay painted over the still-in-flow editor, so + * nobody noticed it wasn't hidden. Now that the iframe sits in the normal + * flex flow, the editor must genuinely be gone or the two would split the row. */ +body.history-mode #editorcontainerbox #editorcontainer { display: none; } body.history-mode .history-frame-mount { display: block; } body.history-mode #editbar .menu_left { display: none; } body.history-mode #editbar .show-more-icon-btn { display: none; } diff --git a/src/tests/frontend-new/specs/padmode.spec.ts b/src/tests/frontend-new/specs/padmode.spec.ts index b8e2451d8b8..d75a80e12a4 100644 --- a/src/tests/frontend-new/specs/padmode.spec.ts +++ b/src/tests/frontend-new/specs/padmode.spec.ts @@ -345,4 +345,55 @@ test.describe('in-pad history mode', () => { await expect(page.locator('body.history-mode')).toHaveCount(0); await expect(tbl.locator('.history-authors-row')).toHaveCount(0); }); + + test('history iframe occupies the editor flex slot so side panels sit beside it', async ({page}) => { + await goToNewPad(page); + await clearPadContent(page); + await writeToPad(page, 'one'); + await page.waitForTimeout(300); + await writeToPad(page, ' two'); + await page.waitForTimeout(500); + + // Simulate a plugin side panel mounted in #editorcontainerbox (e.g. + // ep_webrtc's #rtcbox video column): an in-flow flex item pinned left. + await page.evaluate(() => { + const box = document.getElementById('editorcontainerbox')!; + const panel = document.createElement('div'); + panel.id = 'test-side-panel'; + panel.style.cssText = 'display:flex;order:-1;flex:0 1 auto;width:120px'; + panel.innerHTML = '
'; + box.appendChild(panel); + }); + const editorRight = await page.locator('#editorcontainer').evaluate( + (el) => Math.round(el.getBoundingClientRect().right)); + + await page.locator('.buttonicon-history').click(); + await expect(page.locator('body.history-mode')).toBeVisible(); + await page.locator('#history-frame-mount iframe').waitFor({state: 'attached'}); + + // 1) The live editor is genuinely removed from flow — not merely painted + // over. The two-id `#editorcontainerbox #editorcontainer { display:flex }` + // layout rule outranks a plain `body.history-mode #editorcontainer`, so + // the hide rule must match the same container path to win the cascade. + expect(await page.locator('#editorcontainer').evaluate( + (el) => getComputedStyle(el).display)).toBe('none'); + + // 2) The history iframe is in normal flow (not an absolute overlay)... + expect(await page.locator('#history-frame-mount').evaluate( + (el) => getComputedStyle(el).position)).not.toBe('absolute'); + + // 3) ...so it lays out beside the side panel instead of on top of it, and + // still fills the rest of the editor's old width. + const r = (sel: string) => page.locator(sel).evaluate((el) => { + const b = el.getBoundingClientRect(); + return {left: b.left, right: b.right, top: b.top, bottom: b.bottom}; + }); + const panel = await r('#test-side-panel'); + const frame = await r('#history-frame-mount'); + const overlaps = !(frame.right <= panel.left || panel.right <= frame.left || + frame.bottom <= panel.top || panel.bottom <= frame.top); + expect(overlaps).toBe(false); + expect(Math.round(frame.left)).toBeGreaterThanOrEqual(Math.round(panel.right) - 2); + expect(Math.round(frame.right)).toBeGreaterThanOrEqual(editorRight - 3); + }); });