Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ If your database needs additional settings, you will have to use a personalized
| `PAD_OPTIONS_ALWAYS_SHOW_CHAT` | | `false` |
| `PAD_OPTIONS_CHAT_AND_USERS` | | `false` |
| `PAD_OPTIONS_LANG` | | `null` |
| `PAD_OPTIONS_FADE_INACTIVE_AUTHOR_COLORS` | Fade each author's caret/background toward white as they go inactive. Set to `false` on busy pads (every faded author counts as a second on-screen color, so 30 contributors visually become 60), when users pick light colors that fade into the background, or whenever inactivity tracking is undesirable. | `true` |


### Shortcuts
Expand Down
3 changes: 2 additions & 1 deletion settings.json.docker
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,8 @@
"rtl": "${PAD_OPTIONS_RTL:false}",
"alwaysShowChat": "${PAD_OPTIONS_ALWAYS_SHOW_CHAT:false}",
"chatAndUsers": "${PAD_OPTIONS_CHAT_AND_USERS:false}",
"lang": "${PAD_OPTIONS_LANG:null}"
"lang": "${PAD_OPTIONS_LANG:null}",
"fadeInactiveAuthorColors": "${PAD_OPTIONS_FADE_INACTIVE_AUTHOR_COLORS:true}"
},

/*
Expand Down
8 changes: 7 additions & 1 deletion settings.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,13 @@
"rtl": false,
"alwaysShowChat": false,
"chatAndUsers": false,
"lang": null
"lang": null,
/*
* When true (default), each author's caret/background color fades toward white
* as the author goes inactive. Set to false if users pick light colors and the
* faded variants become visually indistinguishable.
*/
"fadeInactiveAuthorColors": true
},

/*
Expand Down
1 change: 1 addition & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"pad.settings.stickychat": "Chat always on screen",
"pad.settings.chatandusers": "Show Chat and Users",
"pad.settings.colorcheck": "Authorship colors",
"pad.settings.fadeInactiveAuthorColors": "Fade inactive author colors",
"pad.settings.linenocheck": "Line numbers",
"pad.settings.rtlcheck": "Read content from right to left?",
"pad.settings.enforceSettings": "Enforce settings for other users",
Expand Down
4 changes: 4 additions & 0 deletions src/node/db/Pad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type PadViewSettings = {
showLineNumbers: boolean;
rtlIsTrue: boolean;
padFontFamily: string;
fadeInactiveAuthorColors: boolean;
};

type PadSettings = {
Expand Down Expand Up @@ -103,6 +104,9 @@ class Pad {
settings.padOptions.showLineNumbers !== false : !!rawView.showLineNumbers,
rtlIsTrue: !!rawView.rtlIsTrue,
padFontFamily: typeof rawView.padFontFamily === 'string' ? rawView.padFontFamily : '',
fadeInactiveAuthorColors: rawView.fadeInactiveAuthorColors == null ?
settings.padOptions.fadeInactiveAuthorColors !== false :
!!rawView.fadeInactiveAuthorColors,
},
};
}
Expand Down
2 changes: 2 additions & 0 deletions src/node/utils/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export type SettingsType = {
alwaysShowChat: boolean,
chatAndUsers: boolean,
lang: string | null,
fadeInactiveAuthorColors: boolean,
},
enableMetrics: boolean,
padShortcutEnabled: {
Expand Down Expand Up @@ -439,6 +440,7 @@ const settings: SettingsType = {
alwaysShowChat: false,
chatAndUsers: false,
lang: null,
fadeInactiveAuthorColors: true,
},
/**
* Wether to enable the /stats endpoint. The functionality in the admin menu is untouched for this.
Expand Down
19 changes: 18 additions & 1 deletion src/static/js/ace2_inner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ function Ace2Inner(editorInfo, cssManagers) {
let doesWrap = true;
let hasLineNumbers = true;
let isStyled = true;
let fadeInactiveAuthorColors =
window.clientVars?.padOptions?.fadeInactiveAuthorColors !== false;

let console = (DEBUG && window.console);

Expand Down Expand Up @@ -236,7 +238,13 @@ function Ace2Inner(editorInfo, cssManagers) {
cssManagers.parent.removeSelectorStyle(authorSelector);
} else if (info.bgcolor) {
let bgcolor = info.bgcolor;
if ((typeof info.fade) === 'number') {
// The fade is controlled at runtime by the fadeInactiveAuthorColors flag (default
// true), which tracks padOptions.view.fadeInactiveAuthorColors and is updated by
// ace_setProperty when the user/pad-settings checkbox flips. Disabling it keeps
// each author's background at their chosen value — useful on busy pads where each
// faded author would otherwise count as a second on-screen color, or when
// inactivity tracking is undesirable for whatever reason.
if (fadeInactiveAuthorColors && (typeof info.fade) === 'number') {
bgcolor = fadeColor(bgcolor, info.fade);
}
const textColor =
Expand Down Expand Up @@ -667,6 +675,15 @@ function Ace2Inner(editorInfo, cssManagers) {
targetBody.classList.toggle('ltr', !value);
document.documentElement.dir = value ? 'rtl' : 'ltr';
},
fadeinactiveauthorcolors: (value) => {
fadeInactiveAuthorColors = `${value}` !== 'false';
// Re-apply styles for every known author so that pre-faded backgrounds
// refresh immediately when the toggle flips, instead of waiting for the
// next fade tick (which only fires on join/leave).
for (const [author, info] of Object.entries(authorInfos)) {
if (info) setAuthorStyle(author, info);
}
},
};

const setter = setters[key.toLowerCase()];
Expand Down
14 changes: 14 additions & 0 deletions src/static/js/pad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ const getParameters = [
$('#clearAuthorship').hide();
},
},
{
name: 'fadeInactiveAuthorColors',
checkVal: 'false',
callback: (val) => {
if (!clientVars.initialOptions) return;
if (!clientVars.initialOptions.view) clientVars.initialOptions.view = {};
clientVars.initialOptions.view.fadeInactiveAuthorColors = false;
},
},
{
name: 'showControls',
checkVal: 'true',
Expand Down Expand Up @@ -214,6 +223,7 @@ const getMyViewOverrides = () => {
showLineNumbers: padcookie.getPref('showLineNumbers'),
rtlIsTrue: padcookie.getPref('rtlIsTrue'),
padFontFamily: padcookie.getPref('padFontFamily'),
fadeInactiveAuthorColors: padcookie.getPref('fadeInactiveAuthorColors'),
},
};
if (language == null) delete overrides.lang;
Expand Down Expand Up @@ -549,6 +559,8 @@ const pad = {
$('#padsettings-options-stickychat').prop('checked', !!padOptions.alwaysShowChat);
$('#padsettings-options-chatandusers').prop('checked', !!padOptions.chatAndUsers);
$('#padsettings-options-colorscheck').prop('checked', view.showAuthorColors !== false);
$('#padsettings-options-fadeauthorcheck')
.prop('checked', view.fadeInactiveAuthorColors !== false);
$('#padsettings-options-linenoscheck').prop('checked', view.showLineNumbers !== false);
$('#padsettings-options-rtlcheck').prop('checked', !!view.rtlIsTrue);
$('#padsettings-viewfontmenu').val(view.padFontFamily || '');
Expand All @@ -565,6 +577,8 @@ const pad = {
$('#options-stickychat').prop('checked', !!effectiveOptions.alwaysShowChat);
$('#options-chatandusers').prop('checked', !!effectiveOptions.chatAndUsers);
$('#options-colorscheck').prop('checked', effectiveOptions.view?.showAuthorColors !== false);
$('#options-fadeauthorcheck')
.prop('checked', effectiveOptions.view?.fadeInactiveAuthorColors !== false);
$('#options-linenoscheck').prop('checked', effectiveOptions.view?.showLineNumbers !== false);
$('#options-rtlcheck').prop('checked', !!effectiveOptions.view?.rtlIsTrue);
$('#viewfontmenu').val(effectiveOptions.view?.padFontFamily || '');
Expand Down
16 changes: 16 additions & 0 deletions src/static/js/pad_editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ const padeditor = (() => {
padutils.bindCheckboxChange($('#options-colorscheck'), () => {
pad.setMyViewOption('showAuthorColors', padutils.getCheckbox($('#options-colorscheck')));
});
padutils.bindCheckboxChange($('#options-fadeauthorcheck'), () => {
pad.setMyViewOption(
'fadeInactiveAuthorColors',
padutils.getCheckbox($('#options-fadeauthorcheck')));
});
padutils.bindCheckboxChange($('#options-linenoscheck'), () => {
pad.setMyViewOption('showLineNumbers', padutils.getCheckbox($('#options-linenoscheck')));
});
Expand Down Expand Up @@ -108,6 +113,13 @@ const padeditor = (() => {
'showAuthorColors', padutils.getCheckbox('#padsettings-options-colorscheck'));
});

// Fade inactive author colors
padutils.bindCheckboxChange($('#padsettings-options-fadeauthorcheck'), () => {
pad.changePadViewOption(
'fadeInactiveAuthorColors',
padutils.getCheckbox('#padsettings-options-fadeauthorcheck'));
});

// Right to left
padutils.bindCheckboxChange($('#padsettings-options-rtlcheck'), () => {
pad.changePadViewOption(
Expand Down Expand Up @@ -248,6 +260,10 @@ const padeditor = (() => {
$('iframe[name="ace_outer"]').contents().find('#sidedivinner').toggleClass('authorColors', v);
padutils.setCheckbox($('#options-colorscheck'), v);

v = getOption('fadeInactiveAuthorColors', true);
self.ace.setProperty('fadeInactiveAuthorColors', v);
padutils.setCheckbox($('#options-fadeauthorcheck'), v);

// Override from parameters if true
if (settings.noColors !== false) {
self.ace.setProperty('showsauthorcolors', !settings.noColors);
Expand Down
8 changes: 8 additions & 0 deletions src/templates/pad.html
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ <h2 data-l10n-id="pad.settings.myView"></h2>
<input type="checkbox" id="options-colorscheck">
<label for="options-colorscheck" data-l10n-id="pad.settings.colorcheck"></label>
</p>
<p>
<input type="checkbox" id="options-fadeauthorcheck" checked>
<label for="options-fadeauthorcheck" data-l10n-id="pad.settings.fadeInactiveAuthorColors"></label>
</p>
<p>
<input type="checkbox" id="options-linenoscheck" checked>
<label for="options-linenoscheck" data-l10n-id="pad.settings.linenocheck"></label>
Expand Down Expand Up @@ -217,6 +221,10 @@ <h2 data-l10n-id="pad.settings.padSettings"></h2>
<input type="checkbox" id="padsettings-options-colorscheck">
<label for="padsettings-options-colorscheck" data-l10n-id="pad.settings.colorcheck"></label>
</p>
<p>
<input type="checkbox" id="padsettings-options-fadeauthorcheck" checked>
<label for="padsettings-options-fadeauthorcheck" data-l10n-id="pad.settings.fadeInactiveAuthorColors"></label>
</p>
<p>
<input type="checkbox" id="padsettings-options-linenoscheck" checked>
<label for="padsettings-options-linenoscheck" data-l10n-id="pad.settings.linenocheck"></label>
Expand Down
11 changes: 11 additions & 0 deletions src/tests/backend/specs/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,15 @@ describe(__filename, function () {
}
});
});

// Regression test for ether/etherpad#7138.
// padOptions.fadeInactiveAuthorColors must default to true so existing
// installations keep the legacy fade-on-inactive behavior, and must be
// overridable via PAD_OPTIONS_FADE_INACTIVE_AUTHOR_COLORS in docker.
describe('padOptions.fadeInactiveAuthorColors (issue #7138)', function () {
it('defaults to true so existing deployments are unchanged', function () {
const settings = require('../../../node/utils/Settings');
assert.strictEqual(settings.padOptions.fadeInactiveAuthorColors, true);
});
});
});
53 changes: 53 additions & 0 deletions src/tests/frontend-new/specs/inactive_color_fade.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {expect, test} from "@playwright/test";
import {appendQueryParams, goToNewPad} from "../helper/padHelper";
import {showSettings} from "../helper/settingsHelper";

test.beforeEach(async ({page}) => {
// clearCookies on the page's own context — `browser.newContext()`
// creates a separate context that the `page` fixture doesn't use,
// so clearing cookies on it is a no-op (Qodo review feedback).
await page.context().clearCookies();
await goToNewPad(page);
});

test.describe('fadeInactiveAuthorColors (issue #7138)', function () {
test('server-side default is true (legacy fade behavior preserved)', async function ({page}) {
const fade = await page.evaluate(
() => (window as any).clientVars?.padOptions?.fadeInactiveAuthorColors);
expect(fade).toBe(true);
});

test('per-pad view default propagates from server settings', async function ({page}) {
const fade = await page.evaluate(
() => (window as any).clientVars?.initialOptions?.view?.fadeInactiveAuthorColors);
expect(fade).toBe(true);
});

test('?fadeInactiveAuthorColors=false flips the per-pad view value', async function ({page}) {
await appendQueryParams(page, {fadeInactiveAuthorColors: 'false'});
const fade = await page.evaluate(
() => (window as any).clientVars?.initialOptions?.view?.fadeInactiveAuthorColors);
expect(fade).toBe(false);
});

test('My View checkbox toggles the per-user cookie', async function ({page}) {
// Open the settings popup, untick the box, confirm the cookie pref now
// overrides the server-default.
await showSettings(page);
const checkbox = page.locator('#options-fadeauthorcheck');
await expect(checkbox).toBeChecked();
// The label is i18n'd, not hardcoded — assert the localized string actually
// rendered (catches missing keys, not just a present DOM node).
await expect(page.locator('label[for="options-fadeauthorcheck"]'))
.toHaveText('Fade inactive author colors');
await page.locator('label[for="options-fadeauthorcheck"]').click();
await expect(checkbox).not.toBeChecked();
// padcookie stores prefs as a single JSON cookie. The name is `prefs` over
// HTTPS and `prefsHttp` over HTTP, optionally with a `cookiePrefix`.
const cookies = await page.context().cookies();
const prefsCookie = cookies.find((c) => /(prefs|prefsHttp)$/.test(c.name));
expect(prefsCookie, 'expected a prefs cookie after toggling').toBeDefined();
const decoded = JSON.parse(decodeURIComponent(prefsCookie!.value));
expect(decoded.fadeInactiveAuthorColors).toBe(false);
});
});
Loading