diff --git a/README.md b/README.md index 3dcba7f..19c1ae9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Click a forum, topic, post or FAQ heading to copy a link to it, ready-made as BB ## Features - Click a forum or topic title, a post subject, or a question in the FAQ or BBCode guide, and a link to it is copied as BBCode, for example `[url=https://example.com/forum/viewtopic.php?t=42]Topic title[/url]`. Paste it into a post and it shows as a readable link. -- Code blocks have a separate **Copy code** button beside **Select all**. It copies plain text, preserving indentation and line breaks, and provides visible and screen reader feedback. If copying is blocked, the code is selected for manual copying. The ACP link-format setting does not affect code copying. +- Code blocks use one **Copy code** button in place of **Select all** when JavaScript runs. With JavaScript off, the original **Select all** link remains. It copies plain text, preserving indentation and line breaks, and provides visible and screen reader feedback. If copying is blocked, the code is selected for manual copying. The ACP link-format setting does not affect code copying. - Also works on the title above the posting form and on the posts in the topic review below it. - Links are full addresses built from the forum, topic or post ID, so they work anywhere they're pasted and never carry a session ID. They always point at the forum, topic or post itself, not a page of it. - A small icon (Font Awesome `fa-clone` by default) marks each copyable heading and turns into a tick for a moment after copying. Screen readers announce "The link has been copied." diff --git a/docs/images/screenshots/README.md b/docs/images/screenshots/README.md index 4210761..7741fd6 100644 --- a/docs/images/screenshots/README.md +++ b/docs/images/screenshots/README.md @@ -22,11 +22,11 @@ Clicking a question in the FAQ copies a link to that question. ## Code block -The separate **Copy code** button copies plain code without BBCode. **Select all** -remains available. This Playwright screenshot shows the success label on phpBB -3.3 after an actual clipboard write and read. +**Copy code** replaces **Select all** when JavaScript runs. With JavaScript off, +the original link remains. The screenshot shows the success label on phpBB 3.3, +captured with the shared screenshot tool using a controlled clipboard outcome. -Copied feedback beside Select all in a code block +Single code-copy control showing Copied feedback ## ACP settings diff --git a/docs/images/screenshots/copylinks-code.png b/docs/images/screenshots/copylinks-code.png index 9931cae..2ec2e93 100644 Binary files a/docs/images/screenshots/copylinks-code.png and b/docs/images/screenshots/copylinks-code.png differ diff --git a/docs/update/2026-10-25/01-copy-code.png b/docs/update/2026-10-25/01-copy-code.png index e4500f3..cbbcffb 100644 Binary files a/docs/update/2026-10-25/01-copy-code.png and b/docs/update/2026-10-25/01-copy-code.png differ diff --git a/docs/update/2026-10-25/02-code-copied.png b/docs/update/2026-10-25/02-code-copied.png index 9794f37..2ec2e93 100644 Binary files a/docs/update/2026-10-25/02-code-copied.png and b/docs/update/2026-10-25/02-code-copied.png differ diff --git a/docs/update/2026-10-25/03-copy-manually.png b/docs/update/2026-10-25/03-copy-manually.png index f6afc7e..72394b9 100644 Binary files a/docs/update/2026-10-25/03-copy-manually.png and b/docs/update/2026-10-25/03-copy-manually.png differ diff --git a/docs/update/2026-10-25/README.md b/docs/update/2026-10-25/README.md index 1dd4d4b..a5f07dc 100644 --- a/docs/update/2026-10-25/README.md +++ b/docs/update/2026-10-25/README.md @@ -10,15 +10,15 @@ states. The PNGs retain their original resolution. ## Copy code control -A separate Copy code button appears beside Select all. Keyboard focus remains visible. +Copy code replaces Select all when JavaScript runs, leaving one control. With JavaScript off, the original Select all link remains. Keyboard focus stays visible. -Copy code button with a visible keyboard focus outline beside Select all +Single Copy code button with a visible keyboard focus outline ## Successful copy The button shows Copied! after plain code is copied. Indentation and line breaks are preserved. -Copied success feedback beside Select all +Single code-copy control showing Copied success feedback ## Manual-copy fallback diff --git a/styles/all/template/copylinks.js b/styles/all/template/copylinks.js index 1f1d33c..3f0a661 100644 --- a/styles/all/template/copylinks.js +++ b/styles/all/template/copylinks.js @@ -239,7 +239,7 @@ mark(anchor, 'faq'); }); - // Keep Select all intact and add a native button beside it. Reading the + // Replace Select all only after the copy button is ready. Reading the // rendered code preserves indentation, line breaks and highlighted text. document.querySelectorAll('.codebox').forEach(function (box) { var header = box.querySelector('p'); @@ -267,7 +267,12 @@ announce(codeFailedMessage); }); }); - header.appendChild(button); + var selectAll = header.querySelector('a[onclick*="selectCode("]'); + if (selectAll) { + header.replaceChild(button, selectAll); + } else { + header.appendChild(button); + } }); // One listener for every control. Clicks with a modifier key or a diff --git a/styles/all/theme/copylinks.css b/styles/all/theme/copylinks.css index 23e7c33..1bae663 100644 --- a/styles/all/theme/copylinks.css +++ b/styles/all/theme/copylinks.css @@ -55,9 +55,8 @@ } } -/* Text button beside Select all, using the codebox header's typography. */ +/* Copy control using the codebox header's typography. */ .codebox .copylinks-code { - margin-inline-start: 0.75em; padding: 0; border: 0; background: transparent; diff --git a/tests/browser/README.md b/tests/browser/README.md index bef7744..c137a68 100644 --- a/tests/browser/README.md +++ b/tests/browser/README.md @@ -5,8 +5,8 @@ extension enabled. Set `COPYLINKS_TEST_URL` to a topic containing at least two code blocks, including multiline code with indentation, blank lines and Unicode. The script checks exact clipboard contents, multiple blocks, repeated copying, Enter and Space, visible feedback, the status region, manual-copy selection after denied, missing or insecure clipboard access and recovery, focus, -a 320-pixel viewport, existing heading copying and Select all, and content -availability with JavaScript disabled. Clipboard writes are stubbed. +a 320-pixel viewport, existing heading copying, one code control with JavaScript, +and the original Select all link and content with JavaScript disabled. Clipboard writes are stubbed. Run with Node.js and Playwright available in the environment: diff --git a/tests/browser/code-copy.js b/tests/browser/code-copy.js index c12c9d2..cf293b5 100644 --- a/tests/browser/code-copy.js +++ b/tests/browser/code-copy.js @@ -28,6 +28,8 @@ const { chromium } = require('playwright'); const buttons = page.locator('.copylinks-code'); await buttons.first().waitFor(); assert.ok(await buttons.count() >= 2, 'fixture needs two code blocks'); + assert.equal(await page.locator('.codebox p a[onclick*="selectCode("]').count(), 0, 'Select all is replaced when JavaScript runs'); + assert.equal(await buttons.count(), await page.locator('.codebox pre > code').count()); const expected = await page.locator('.codebox pre > code').allInnerTexts(); const first = buttons.first(); await first.click(); @@ -43,10 +45,6 @@ const { chromium } = require('playwright'); await page.waitForFunction(() => window.copiedTexts.length === 4); assert.equal(await page.evaluate(() => window.copiedTexts[3]), expected[1]); await page.waitForFunction(() => Array.from(document.querySelectorAll('.copylinks-code')).every(button => button.textContent === 'Copy code')); - await page.locator('.codebox p a').first().click(); - // Chromium's native selection omits the final block newline. - assert.equal(await page.evaluate(() => window.getSelection().toString()), expected[0].replace(/\n$/, '')); - assert.equal(await page.evaluate(() => window.copiedTexts.length), 4); // Existing heading copying still uses the configured output format. await page.locator('h2.topic-title > a').click(); @@ -100,10 +98,11 @@ const { chromium } = require('playwright'); const plainPage = await noJS.newPage(); await plainPage.goto(process.env.COPYLINKS_TEST_URL); assert.equal(await plainPage.locator('.copylinks-code').count(), 0); - assert.ok(await plainPage.locator('.codebox p a').count() >= 2); + assert.ok(await plainPage.locator('.codebox p a[onclick*="selectCode("]').count() >= 2); + assert.equal(await plainPage.getByRole('link', {name: 'Select all', exact: true}).count(), expected.length); assert.deepEqual(await plainPage.locator('.codebox pre > code').allTextContents(), expected); await noJS.close(); - console.log('PASS: exact code, multiple blocks, Enter/Space, repeat feedback, Select all, heading format, denied/missing/insecure clipboard selection fallback and recovery, focus, 320px layout, accessible name, no-JS content.'); + console.log('PASS: exact code, multiple blocks, Enter/Space, repeat feedback, one code control, heading format, denied/missing/insecure clipboard selection fallback and recovery, focus, 320px layout, accessible name, no-JS Select all and content.'); } finally { await browser.close(); }