Skip to content

feat(padToggle): native-style User + Pad Wide settings helper#8

Merged
JohnMcLear merged 4 commits intomainfrom
feat/pad-toggle
May 7, 2026
Merged

feat(padToggle): native-style User + Pad Wide settings helper#8
JohnMcLear merged 4 commits intomainfrom
feat/pad-toggle

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

Summary

  • New padToggle helper that emits parallel checkboxes in both the User Settings and Pad Wide Settings panels, matching how native Etherpad toggles (sticky chat, line numbers, dark mode, …) work.
  • Pad-wide value rides the existing padoptions COLLABROOM rail — broadcast to every connected client, persisted with the pad, honored by enforceSettings — so the helper carries no parallel transport.
  • Capability-detects via ep_etherpad-lite/node/utils/PluginCapabilities (added by the etherpad-lite passthrough patch). On older cores the pad-wide block silently no-ops and the user-side cookie toggle keeps working — plugins built on this helper are backward-compatible.
  • Server side: loadSettings, clientVars, eejsBlock_mySettings, eejsBlock_padSettings. Client side: init({onChange}), handleClientMessage_CLIENT_MESSAGE. Split across pad-toggle-server.js and pad-toggle.js following the existing attributes-server.js/attributes.js convention so the client bundle stays clean.

i18n + a11y

  • l10nId is required (i18n is mandatory) — emits data-l10n-id on every <label>.
  • defaultLabel is required (a11y fallback) — rendered inside the <label> so screen readers announce something before html10n loads. html10n overwrites at runtime in the user's locale.
  • Both label and id are HTML-escaped against injection.

Companion patches

  • etherpad-lite: ep_* passthrough in applyPadSettings (separate PR — capability flag lives there).
  • ep_table_of_contents: first migrant from hand-rolled eejsBlock_mySettings to padToggle (separate PR).

Test plan

  • 14 new unit tests cover config validation (ep_*, settingId, l10nId, defaultLabel), HTML output (ids, data-l10n-id, label fallback, escaping), capability fallback (no-op when patch absent), clientVars seeding, instance-default override, sub-path imports.
  • Full helper suite: 65 passing.
  • Live two-checkbox render verified in patched Etherpad with ep_table_of_contents migrated; both checkboxes carry the i18n key + screen-reader fallback.
  • Browser broadcast/enforce verified in CI Playwright once the core patch lands.

🤖 Generated with Claude Code

JohnMcLear and others added 4 commits May 7, 2026 15:45
Plugins built on toggle() get only a User Settings checkbox stored in a
per-user cookie — they never appear in the Pad Wide Settings panel and
can't ride enforceSettings. That's the wrong half of Etherpad's native
model: every core toggle (sticky chat, line numbers, etc.) renders in
both panels and broadcasts pad-wide changes to every connected client.

padToggle emits parallel checkboxes in both panels with one config
object, stashes the pad-wide value at pad.padOptions[pluginName] so it
rides the existing padoptions COLLABROOM rail, and honors enforce by
locking the user-side checkbox when the pad creator turns it on.

Capability-detects the ep_* passthrough patch via PluginCapabilities;
on older cores the pad-wide block silently no-ops and the user-side
cookie toggle keeps working, so plugins built on padToggle are
backward-compatible.

i18n is mandatory (no hardcoded English labels) — the helper requires
an l10nId and emits only data-l10n-id, leaving translations to the
plugin's own locales/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-level requires of ep_etherpad-lite/node/* in pad-toggle.js were getting
crawled by esbuild when plugin authors imported the helper from client code,
even though the requires were inside try/catch and the chain only reached
leaf modules. The bundler resolves the package and pulls in everything
along the way. The existing convention (attributes.js client / attributes-
server.js server) handles this cleanly — adopt the same split:

- pad-toggle-server.js — server hooks (loadSettings, clientVars,
  eejsBlock_mySettings, eejsBlock_padSettings) + capability detection via
  PluginCapabilities.
- pad-toggle.js — client hooks (init, handleClientMessage_CLIENT_MESSAGE)
  with no top-level node-only requires.

Plugin authors:
  // server (index.js)
  const {padToggle} = require('ep_plugin_helpers/pad-toggle-server');
  // client (static/js/postAceInit.js)
  const {padToggle} = require('ep_plugin_helpers/pad-toggle');

The top-level `padToggle` getter on ep_plugin_helpers' index.js still
returns the server factory for callers who already use the index entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The empty <label data-l10n-id="..."></label> meant screen readers had
nothing to announce while html10n was still loading or if a translation
failed to fetch. Native Etherpad's templates always render English text
inside the label tag for exactly this reason; the helper was missing
that safety net.

Make defaultLabel a required config field. Helper now renders
<label data-l10n-id="..." for="...">Default text</label> with both the
defaultLabel and l10nId HTML-escaped to prevent injection. html10n still
overwrites the text the moment it loads in the user's locale.

Validate on both server and client factories so a misconfigured plugin
fails loudly on import rather than silently shipping a nameless
checkbox.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

@JohnMcLear JohnMcLear merged commit 55ca19e into main May 7, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant