diff --git a/README.md b/README.md index c96777c..b18d31e 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ const state = myToggle.init(); // reads cookie, binds checkbox Parallel checkboxes in **both** the User Settings panel and the Pad Wide Settings panel — matching how native settings (sticky chat, line numbers, etc.) work. The pad-wide value rides Etherpad's existing `padoptions` broadcast/persist rail, so changes propagate to every connected client and are remembered across reloads. The pad creator can `enforceSettings` to lock the user-side checkbox for everyone. -Requires Etherpad with the `ep_*` padOptions passthrough patch (>= 2.7.4) AND the runtime flag `settings.enablePluginPadOptions = true` in `settings.json` (default false). When either is missing the pad-wide column is hidden automatically and the user-side cookie toggle keeps working — plugins built on this helper run everywhere. +Requires Etherpad with the `ep_*` padOptions passthrough patch ([PR #7698](https://github.com/ether/etherpad/pull/7698), shipped in `>= 3.0.0`) AND the runtime flag `settings.enablePluginPadOptions = true` in `settings.json` (default false). When either is missing the pad-wide column is hidden automatically and the user-side cookie toggle keeps working — plugins built on this helper run everywhere. The console warning logged on degradation names the specific cause so an admin can tell whether to upgrade the core or to flip the runtime flag. ```json // settings.json diff --git a/index.js b/index.js index bcdf0e9..d4428c8 100644 --- a/index.js +++ b/index.js @@ -22,7 +22,7 @@ module.exports = { // PadToggle — parallel User Settings + Pad Wide Settings checkboxes, // matching native Etherpad behavior. Pad-wide value rides the existing // padoptions broadcast/persist rail; degrades gracefully on cores that - // lack the ep_* passthrough patch (Etherpad < 2.7.4). + // lack the ep_* passthrough patch (Etherpad < 3.0.0). // // Server side ONLY here. Client code must import the sub-path // 'ep_plugin_helpers/pad-toggle' directly to avoid pulling settings-toggle diff --git a/pad-select-server.js b/pad-select-server.js index e5faafd..810bbeb 100644 --- a/pad-select-server.js +++ b/pad-select-server.js @@ -5,7 +5,7 @@ // from a fixed list (e.g. indent size: 2 / 4, theme: light / dark, etc.). // Pad-wide values ride the existing padoptions COLLABROOM rail (stored at // pad.padOptions[pluginName] = {[settingId]: value}) when the core has the -// ep_* passthrough patch (Etherpad >= 2.7.4) AND the admin opted in via +// ep_* passthrough patch (Etherpad >= 3.0.0, PR #7698) AND the admin opted in via // settings.enablePluginPadOptions. Otherwise the pad-wide block silently // no-ops and the user-side cookie picker still works. // @@ -112,6 +112,12 @@ const padSelectServer = (rawConfig) => { [pluginName]: { [settingId]: { padWideSupported: isPadWideActive(), + // Granular flags so the client's degradation warning can + // name the specific cause — missing patch (Etherpad < + // 3.0.0) vs. missing runtime flag (default false). See + // pad-toggle-server.js for the same rationale. + patchPresent: padOptionsPluginPassthrough, + runtimeEnabled: runtimeFlagEnabled, options, defaultValue: cachedDefault, initialPadValue, diff --git a/pad-select.js b/pad-select.js index 8a63519..67759ac 100644 --- a/pad-select.js +++ b/pad-select.js @@ -160,10 +160,28 @@ const padSelectClient = (rawConfig) => { }); } else if (!isSupportedClient()) { if (typeof console !== 'undefined' && !init._warned) { + // See pad-toggle.js for the rationale: distinguish missing patch + // (Etherpad < 3.0.0) from missing runtime flag + // (settings.enablePluginPadOptions). Falls back to a generic line + // on servers that don't ship the granular capability fields. + const b = block() || {}; + let reason; + if (b.patchPresent != null || b.runtimeEnabled != null) { + if (!b.patchPresent) { + reason = 'server lacks ep_* passthrough patch (Etherpad < 3.0.0)'; + } else if (!b.runtimeEnabled) { + reason = 'settings.enablePluginPadOptions is false — set to true ' + + 'in settings.json to enable pad-wide options'; + } else { + reason = 'pad-wide block not rendered (eejsBlock_padSettings missing)'; + } + } else { + reason = 'server lacks ep_* passthrough patch (Etherpad < 3.0.0) ' + + 'or runtime flag settings.enablePluginPadOptions is false'; + } console.warn( `[ep_plugin_helpers.padSelect ${pluginName}] pad-wide settings ` + - 'unavailable — server lacks ep_* passthrough patch (Etherpad < 2.7.4). ' + - 'Per-user cookie picker still works.'); + `unavailable — ${reason}. Per-user cookie picker still works.`); init._warned = true; } } diff --git a/pad-toggle-server.js b/pad-toggle-server.js index 15cc92e..9fa16f5 100644 --- a/pad-toggle-server.js +++ b/pad-toggle-server.js @@ -4,9 +4,9 @@ // (mySettings) and Pad Wide Settings (padSettings) panels, mirroring native // Etherpad behavior. Pad-wide values ride the existing padoptions COLLABROOM // rail (stored at pad.padOptions[pluginName] = {enabled: bool}) when the core -// has the ep_* passthrough patch (Etherpad >= 2.7.4); on older cores the -// pad-wide block silently no-ops and the user-side cookie toggle alone still -// works. +// has the ep_* passthrough patch (Etherpad >= 3.0.0, PR #7698); on older +// cores the pad-wide block silently no-ops and the user-side cookie toggle +// alone still works. // // This module is intended for server-side import only. The companion // `pad-toggle.js` provides the client-side init/handleClientMessage hooks. @@ -18,7 +18,7 @@ const PLUGIN_NAME_RE = /^ep_[a-z0-9_]+$/; let padOptionsPluginPassthrough = false; try { - // The require lands on a leaf module on patched cores (Etherpad >= 2.7.4) + // The require lands on a leaf module on patched cores (Etherpad >= 3.0.0) // and throws on older cores. Server-only: this file is never bundled for // the browser, so esbuild's static analysis does not run here. // eslint-disable-next-line global-require @@ -64,7 +64,7 @@ const renderCheckbox = (settingId, l10nId, defaultLabel, idPrefix) => const padToggleServer = (rawConfig) => { const {pluginName, settingId, l10nId, defaultLabel, defaultEnabled} = validateConfig(rawConfig); let cachedDefaultEnabled = defaultEnabled; - // Etherpad >= 2.7.4 introduced settings.enablePluginPadOptions as a runtime + // Etherpad >= 3.0.0 introduced settings.enablePluginPadOptions as a runtime // gate on the ep_* passthrough (default false per AGENTS.MD §52). We grab // it from loadSettings so eejsBlock_padSettings + clientVars correctly // no-op when an admin hasn't opted in, even though PluginCapabilities @@ -98,6 +98,14 @@ const padToggleServer = (rawConfig) => { // init() reads this to decide whether to wire the pad-wide // checkbox, log the degradation warning, etc. padWideSupported: isPadWideActive(), + // Granular flags so the client's degradation warning can name + // the specific cause (missing patch vs. missing runtime flag) + // instead of guessing. Older client builds that don't read + // these still work — padWideSupported alone is sufficient + // for the gating logic; the warning just falls back to a + // generic line. + patchPresent: padOptionsPluginPassthrough, + runtimeEnabled: runtimeFlagEnabled, settingId, l10nId, defaultEnabled: cachedDefaultEnabled, diff --git a/pad-toggle.js b/pad-toggle.js index 58ece94..aaf6b08 100644 --- a/pad-toggle.js +++ b/pad-toggle.js @@ -67,10 +67,14 @@ const padToggleClient = (rawConfig) => { return window.clientVars || (window.top && window.top.clientVars) || null; }; - const isSupportedClient = () => { + const getCapabilityBlock = () => { const cv = getClientVars(); - const block = cv && cv.ep_plugin_helpers && cv.ep_plugin_helpers.padToggle && - cv.ep_plugin_helpers.padToggle[pluginName]; + return (cv && cv.ep_plugin_helpers && cv.ep_plugin_helpers.padToggle && + cv.ep_plugin_helpers.padToggle[pluginName]) || null; + }; + + const isSupportedClient = () => { + const block = getCapabilityBlock(); return !!(block && block.padWideSupported); }; @@ -155,10 +159,32 @@ const padToggleClient = (rawConfig) => { }); } else if (!isSupportedClient()) { if (typeof console !== 'undefined' && !init._warned) { + // The patch shipped in Etherpad 3.0.0 (PR #7698) and is enabled at + // runtime via `settings.enablePluginPadOptions` (default false per + // AGENTS.MD §52). Either condition can flip padWideSupported off + // — surface the specific cause so the admin knows whether to + // upgrade or to flip a settings flag. Falls back to a generic + // line on older servers that don't ship the capability fields. + const block = getCapabilityBlock(); + const patchPresent = block && block.patchPresent === true; + const runtimeEnabled = block && block.runtimeEnabled === true; + let reason; + if (block && (block.patchPresent != null || block.runtimeEnabled != null)) { + if (!patchPresent) { + reason = 'server lacks ep_* passthrough patch (Etherpad < 3.0.0)'; + } else if (!runtimeEnabled) { + reason = 'settings.enablePluginPadOptions is false — set to true ' + + 'in settings.json to enable pad-wide options'; + } else { + reason = 'pad-wide block not rendered (eejsBlock_padSettings missing)'; + } + } else { + reason = 'server lacks ep_* passthrough patch (Etherpad < 3.0.0) ' + + 'or runtime flag settings.enablePluginPadOptions is false'; + } console.warn( `[ep_plugin_helpers.padToggle ${pluginName}] pad-wide settings ` + - 'unavailable — server lacks ep_* passthrough patch (Etherpad < 2.7.4). ' + - 'Per-user cookie toggle still works.'); + `unavailable — ${reason}. Per-user cookie toggle still works.`); init._warned = true; } } diff --git a/test/pad-toggle.js b/test/pad-toggle.js index 51181e8..3f40a07 100644 --- a/test/pad-toggle.js +++ b/test/pad-toggle.js @@ -97,7 +97,7 @@ describe('padToggle', () => { it('is a no-op when settings.enablePluginPadOptions is missing/false', async () => { // Even on a patched core, the runtime flag is opt-in (default false in - // Etherpad >= 2.7.4). loadSettings without the flag set must leave + // Etherpad >= 3.0.0). loadSettings without the flag set must leave // pad-wide rendering off. const t = padToggle(baseConfig()); await t.loadSettings('h', {settings: {}}); // no enablePluginPadOptions @@ -114,6 +114,22 @@ describe('padToggle', () => { cv.ep_plugin_helpers.padToggle.ep_test.padWideSupported, false, 'capability flag in clientVars must reflect both core patch AND runtime flag'); }); + + it('clientVars exposes patchPresent + runtimeEnabled so the client can name the cause', async () => { + // PR shifts the client-side degradation warning from a generic + // "patch missing" line to a specific cause. Locking in that the + // server publishes the two flags. In this test env the patched + // core is not installed, so patchPresent is false; runtimeEnabled + // reflects the loadSettings call below. + const t = padToggle(baseConfig()); + await t.loadSettings('h', {settings: {enablePluginPadOptions: true}}); + const cv = await t.clientVars('h', {pad: null}); + const block = cv.ep_plugin_helpers.padToggle.ep_test; + assert.strictEqual(block.patchPresent, false, + 'patchPresent must reflect PluginCapabilities, not be conflated with the runtime flag'); + assert.strictEqual(block.runtimeEnabled, true, + 'runtimeEnabled must reflect settings.enablePluginPadOptions exactly'); + }); }); describe('loadSettings', () => {