From b26a230335ab9685251dc2783dd42d0b7f099886 Mon Sep 17 00:00:00 2001 From: Frank Ledo Date: Mon, 20 Jul 2026 11:58:25 -0700 Subject: [PATCH] fix: exclude toolbar and status bar from Select All Cmd+A uses Electron's native `selectAll` role, which dispatches Blink's SelectAll command across the entire document. The toolbar and status bar live in the same document as the rendered markdown, so their text was selected and copied along with the document content. Extends the `user-select: none` treatment already applied to `.file-name` to the `.toolbar` and `.status-bar` containers. Keeping this in CSS rather than replacing the menu role preserves native Cmd+A behaviour inside text inputs such as the find bar. Adds e2e coverage driving the same Blink command the menu role dispatches. Fixes #29 --- src/index.css | 6 ++++ tests/e2e/select-all.spec.ts | 63 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tests/e2e/select-all.spec.ts diff --git a/src/index.css b/src/index.css index c166d80..843c394 100644 --- a/src/index.css +++ b/src/index.css @@ -107,6 +107,9 @@ body { -webkit-app-region: drag; flex-shrink: 0; transition: padding-left 0.2s ease; + /* Chrome is not part of the document: keep it out of Select All / copy */ + user-select: none; + -webkit-user-select: none; } /* Toolbar in fullscreen mode (no traffic lights) */ @@ -457,6 +460,9 @@ body { font-size: 12px; color: var(--text-muted); flex-shrink: 0; + /* Chrome is not part of the document: keep it out of Select All / copy */ + user-select: none; + -webkit-user-select: none; } .status-left, diff --git a/tests/e2e/select-all.spec.ts b/tests/e2e/select-all.spec.ts new file mode 100644 index 0000000..5232186 --- /dev/null +++ b/tests/e2e/select-all.spec.ts @@ -0,0 +1,63 @@ +/** + * E2E Tests: Select All scoping + * + * Cmd+A uses Electron's native `selectAll` role, which dispatches Blink's + * SelectAll command across the whole document. The app chrome (toolbar and + * status bar) lives in the same document as the rendered markdown, so without + * `user-select: none` on the chrome its text is selected and copied too. + * + * These tests drive the same Blink command the menu role dispatches and assert + * on the serialized selection. + */ +import { test, expect } from './electron-app'; + +const SAMPLE_MARKDOWN_HTML = '

Sample Heading

Body paragraph text.

'; + +/** + * Render known content into the viewer, then run Blink's SelectAll and return + * the serialized selection. + */ +async function selectAllText(mainWindow: import('@playwright/test').Page): Promise { + return mainWindow.evaluate((html) => { + const viewer = document.getElementById('markdown-viewer'); + const content = document.getElementById('markdown-content'); + if (!viewer || !content) throw new Error('viewer elements not found'); + + viewer.classList.remove('hidden'); + document.getElementById('drop-zone')?.classList.add('hidden'); + content.innerHTML = html; + + window.getSelection()?.removeAllRanges(); + document.execCommand('selectAll'); + + return window.getSelection()?.toString() ?? ''; + }, SAMPLE_MARKDOWN_HTML); +} + +test.describe('Select All', () => { + test('selects the markdown document content', async ({ mainWindow }) => { + await mainWindow.waitForSelector('.toolbar', { timeout: 5000 }); + const selection = await selectAllText(mainWindow); + + expect(selection).toContain('Sample Heading'); + expect(selection).toContain('Body paragraph text.'); + }); + + test('does not select status bar text', async ({ mainWindow }) => { + await mainWindow.waitForSelector('.status-bar', { timeout: 5000 }); + const selection = await selectAllText(mainWindow); + + expect(selection).not.toContain('Not watching'); + expect(selection).not.toContain('No file'); + expect(selection).not.toContain('100%'); + }); + + test('does not select toolbar text', async ({ mainWindow }) => { + await mainWindow.waitForSelector('.toolbar', { timeout: 5000 }); + const selection = await selectAllText(mainWindow); + + // "Open" and "Edit" are toolbar button labels + expect(selection).not.toContain('Open'); + expect(selection).not.toContain('Edit'); + }); +});