From 1bb709b35c8998429fcc14cf89102955fd5a114b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 10:18:54 +0000 Subject: [PATCH 1/3] Initial plan From aa7166f60f3456f4a1f265862a6560d609d48ecb Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 May 2026 11:21:59 +0100 Subject: [PATCH 2/3] fix: explain masked font colors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 4 ++- static/js/index.js | 26 +++++++++++++++++++ .../frontend-new/specs/font_color.spec.ts | 26 +++++++++++++++++-- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 427352c..3c11552 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ![Demo](demo.gif) Adds a font color picker to the Etherpad toolbar, with HTML export support. +If authorship colors are enabled, the chosen font color is still saved but +the editor will warn that author colors are masking the preview. ## Install @@ -26,4 +28,4 @@ To reposition the color picker in the toolbar, add `fontColor` to your `settings ## License -Apache-2.0 \ No newline at end of file +Apache-2.0 diff --git a/static/js/index.js b/static/js/index.js index dabf32a..5c33c3c 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -3,12 +3,35 @@ const {inlineAttribute} = require('ep_plugin_helpers/attributes'); const colors = ['black', 'red', 'green', 'blue', 'yellow', 'orange']; +const authorColorsMessageTitle = 'Font colors are hidden by authorship colors'; +const authorColorsMessageText = + 'Your font color was saved. Turn off "Colors" in Settings to preview it.'; +let authorColorsNoticeShown = false; const fontColor = inlineAttribute({attr: 'color', values: colors}); exports.aceAttribsToClasses = fontColor.aceAttribsToClasses; exports.aceCreateDomLine = fontColor.aceCreateDomLine; +const hasAuthorColorsEnabled = () => $('iframe[name="ace_outer"]').contents() + .find('iframe[name="ace_inner"]').contents().find('#innerdocbody').hasClass('authorColors'); + +const updateToolbarHint = () => { + const authorColorsEnabled = hasAuthorColorsEnabled(); + const hint = authorColorsEnabled ? authorColorsMessageText : ''; + $('.font-color-icon a, .color-selection, #color-selection').attr('title', hint); + if (!authorColorsEnabled) authorColorsNoticeShown = false; +}; + +const maybeShowAuthorColorsNotice = () => { + if (!hasAuthorColorsEnabled() || authorColorsNoticeShown || !$.gritter) return; + authorColorsNoticeShown = true; + $.gritter.add({ + title: authorColorsMessageTitle, + text: authorColorsMessageText, + }); +}; + // Bind the event handler to the toolbar buttons exports.postAceInit = (hook, context) => { const hs = $('.color-selection, #color-selection'); @@ -21,6 +44,7 @@ exports.postAceInit = (hook, context) => { }, 'insertColor', true); hs.val('dummy'); context.ace.focus(); + maybeShowAuthorColorsNotice(); } }); $('.font_color').hover(() => { @@ -41,6 +65,7 @@ exports.postAceInit = (hook, context) => { hs.niceSelect('update'); }); } + updateToolbarHint(); }; const doInsertColors = function (level) { @@ -83,6 +108,7 @@ exports.aceEditEvent = (hook, call) => { if (cs.type === 'setBaseText' || cs.type === 'setup') return; setTimeout(() => { + updateToolbarHint(); const colorSelect = $('.color-selection, #color-selection'); colorSelect.val('dummy'); colorSelect.niceSelect('update'); diff --git a/static/tests/frontend-new/specs/font_color.spec.ts b/static/tests/frontend-new/specs/font_color.spec.ts index 6978ec6..c63535c 100644 --- a/static/tests/frontend-new/specs/font_color.spec.ts +++ b/static/tests/frontend-new/specs/font_color.spec.ts @@ -1,14 +1,17 @@ import {expect, test} from '@playwright/test'; import {clearPadContent, getPadBody, goToNewPad, writeToPad} from 'ep_etherpad-lite/tests/frontend-new/helper/padHelper'; +import {hideSettings, showSettings} from 'ep_etherpad-lite/tests/frontend-new/helper/settingsHelper'; test.beforeEach(async ({page}) => { await goToNewPad(page); }); const setColor = async (page: any, value: string) => { - await page.evaluate((v: string) => { - const sel = document.querySelector('.color-selection')!; + await page.locator('.font-color-icon').click(); + const select = page.locator('.color-selection').first(); + await expect(select).toBeAttached(); + await select.evaluate((sel: HTMLSelectElement, v: string) => { sel.value = v; sel.dispatchEvent(new Event('change', {bubbles: true})); }, value); @@ -50,6 +53,25 @@ test.describe('ep_font_color', () => { () => document.querySelector('.color-selection')!.value), {timeout: 5_000}).toBe('1'); }); + + test('explains that authorship colors hide the chosen font color', async ({page}) => { + const padBody = await getPadBody(page); + await padBody.click(); + await clearPadContent(page); + await writeToPad(page, 'foo'); + await page.keyboard.press('ControlOrMeta+A'); + + await setColor(page, '1'); + await expect(page.locator('.gritter-item').first()) + .toContainText('Turn off "Colors" in Settings to preview it.'); + + await showSettings(page); + await page.locator('label[for="options-colorscheck"]').click(); + await hideSettings(page); + await expect(page.frameLocator('iframe[name="ace_outer"]').frameLocator('iframe[name="ace_inner"]') + .locator('#innerdocbody')).not.toHaveClass(/authorColors/); + await expect(page.locator('.color-selection')).toHaveAttribute('title', ''); + }); }); test.describe('ep_font_color l10n', () => { From eabc6e177f2c2c0225c65dd3974779066f30ca11 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 May 2026 21:19:22 +0100 Subject: [PATCH 3/3] Fix font color frontend coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- static/js/index.js | 17 +++++++++++++++++ .../tests/frontend-new/specs/font_color.spec.ts | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 5c33c3c..8052707 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -16,6 +16,9 @@ exports.aceCreateDomLine = fontColor.aceCreateDomLine; const hasAuthorColorsEnabled = () => $('iframe[name="ace_outer"]').contents() .find('iframe[name="ace_inner"]').contents().find('#innerdocbody').hasClass('authorColors'); +const getInnerDocBody = () => $('iframe[name="ace_outer"]').contents() + .find('iframe[name="ace_inner"]').contents().find('#innerdocbody').get(0); + const updateToolbarHint = () => { const authorColorsEnabled = hasAuthorColorsEnabled(); const hint = authorColorsEnabled ? authorColorsMessageText : ''; @@ -35,6 +38,11 @@ const maybeShowAuthorColorsNotice = () => { // Bind the event handler to the toolbar buttons exports.postAceInit = (hook, context) => { const hs = $('.color-selection, #color-selection'); + const scheduleHintRefresh = () => { + [0, 50, 150, 400, 800].forEach((delay) => setTimeout(() => { + updateToolbarHint(); + }, delay)); + }; hs.on('change', function () { const value = $(this).val(); const intValue = parseInt(value, 10); @@ -55,6 +63,15 @@ exports.postAceInit = (hook, context) => { $('#font-color').toggle(); context.ace.focus(); }); + const innerDocBody = getInnerDocBody(); + if (innerDocBody && typeof MutationObserver !== 'undefined') { + new MutationObserver((mutations) => { + if (mutations.some((mutation) => mutation.attributeName === 'class')) updateToolbarHint(); + }).observe(innerDocBody, {attributes: true, attributeFilter: ['class']}); + } + $('#options-colorscheck, #padsettings-options-colorscheck, ' + + 'label[for="options-colorscheck"], label[for="padsettings-options-colorscheck"]') + .on('click change', scheduleHintRefresh); // Re-render the niceSelect dropdown whenever the active UI language // changes. html10n rewrites the underlying