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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,14 @@ 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). On older cores 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 (>= 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.

```json
// settings.json
{
"enablePluginPadOptions": true
}
```

```js
const {padToggle} = require('ep_plugin_helpers');
Expand Down
20 changes: 17 additions & 3 deletions pad-toggle-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,20 @@ 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
// 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
// reports the patch is present in the core.
let runtimeFlagEnabled = false;

const isPadWideActive = () => padOptionsPluginPassthrough && runtimeFlagEnabled;

const loadSettings = async (hookName, args) => {
const ps = (args && args.settings && args.settings[pluginName]) || {};
const root = (args && args.settings) || {};
const ps = root[pluginName] || {};
if (typeof ps.defaultEnabled === 'boolean') cachedDefaultEnabled = ps.defaultEnabled;
runtimeFlagEnabled = root.enablePluginPadOptions === true;
};

const clientVars = async (hookName, ctx) => {
Expand All @@ -83,7 +93,11 @@ const padToggleServer = (rawConfig) => {
ep_plugin_helpers: {
padToggle: {
[pluginName]: {
padWideSupported: padOptionsPluginPassthrough,
// True iff the running core has the patch AND the admin has
// opted in via settings.enablePluginPadOptions. Client-side
// init() reads this to decide whether to wire the pad-wide
// checkbox, log the degradation warning, etc.
padWideSupported: isPadWideActive(),
settingId,
l10nId,
defaultEnabled: cachedDefaultEnabled,
Expand All @@ -100,7 +114,7 @@ const padToggleServer = (rawConfig) => {
};

const eejsBlock_padSettings = (hookName, args, cb) => {
if (!padOptionsPluginPassthrough) return cb();
if (!isPadWideActive()) return cb();
args.content += renderCheckbox(settingId, l10nId, defaultLabel, 'padsettings-');
return cb();
};
Expand Down
20 changes: 20 additions & 0 deletions test/pad-toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,26 @@ describe('padToggle', () => {
done();
});
});

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
// pad-wide rendering off.
const t = padToggle(baseConfig());
await t.loadSettings('h', {settings: {}}); // no enablePluginPadOptions
const args = {content: ''};
await new Promise((res) => t.eejsBlock_padSettings('h', args, res));
assert.strictEqual(args.content, '');
});

it('clientVars reports padWideSupported=false when the runtime flag is off', async () => {
const t = padToggle(baseConfig());
await t.loadSettings('h', {settings: {enablePluginPadOptions: false}});
const cv = await t.clientVars('h', {pad: null});
assert.strictEqual(
cv.ep_plugin_helpers.padToggle.ep_test.padWideSupported, false,
'capability flag in clientVars must reflect both core patch AND runtime flag');
});
});

describe('loadSettings', () => {
Expand Down