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
11 changes: 7 additions & 4 deletions ep.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
"parts": [
{
"name": "ep_spellcheck",
"client_hooks": {
"postAceInit": "ep_spellcheck/static/js/spellcheck:postAceInit"
},
"hooks": {
"loadSettings": "ep_spellcheck/spellcheck",
"clientVars": "ep_spellcheck/spellcheck",
"eejsBlock_mySettings": "ep_spellcheck/spellcheck",
"eejsBlock_dd_view" : "ep_spellcheck/spellcheck"
"eejsBlock_padSettings": "ep_spellcheck/spellcheck"
},
"client_hooks": {
"postAceInit": "ep_spellcheck/static/js/spellcheck:postAceInit",
"handleClientMessage_CLIENT_MESSAGE": "ep_spellcheck/static/js/spellcheck:handleClientMessage_CLIENT_MESSAGE"
}
}
]
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"type": "individual",
"url": "https://etherpad.org/"
},
"dependencies": {
"ep_plugin_helpers": "^0.5.2"
},
"devDependencies": {
"eslint": "^8.57.1",
"eslint-config-etherpad": "^4.0.5",
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 23 additions & 15 deletions spellcheck.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
'use strict';

const eejs = require('ep_etherpad-lite/node/eejs/');
const settings = require('ep_etherpad-lite/node/utils/Settings');
const {padToggle} = require('ep_plugin_helpers/pad-toggle-server');

exports.eejsBlock_mySettings = (hookName, args, cb) => {
let checkedState = 'checked';
if (settings.ep_spellcheck) {
if (settings.ep_spellcheck.disabledByDefault === true) {
checkedState = '';
}
// Parallel User Settings + Pad Wide Settings checkboxes for "Spell Check".
// Helper owns markup, storage, broadcast, enforce, and i18n wiring.
const spellcheckToggle = padToggle({
pluginName: 'ep_spellcheck',
settingId: 'spellcheck',
l10nId: 'ep_spellcheck.spellcheck',
defaultLabel: 'Spell Check',
defaultEnabled: true,
});

// Older settings.json used `ep_spellcheck.disabledByDefault: true` to flip
// the checkbox off. Translate to the helper's `defaultEnabled` so existing
// installs keep their current behavior after the conversion.
exports.loadSettings = async (hookName, args) => {
const ps = args && args.settings && args.settings.ep_spellcheck;
if (ps && typeof ps.defaultEnabled !== 'boolean' &&
typeof ps.disabledByDefault === 'boolean') {
ps.defaultEnabled = !ps.disabledByDefault;
}
const ejsPath = 'ep_spellcheck/templates/spellcheck_entry.ejs';
args.content += eejs.require(ejsPath, {checked: checkedState});
return cb();
return spellcheckToggle.loadSettings(hookName, args);
};

exports.eejsBlock_dd_view = (hookName, args, cb) => {
const li = "<li><a href='#' onClick='$(\"#options-spellcheck\").click();'>Spell Check</a></li>";
args.content += li;
};
exports.clientVars = spellcheckToggle.clientVars;
exports.eejsBlock_mySettings = spellcheckToggle.eejsBlock_mySettings;
exports.eejsBlock_padSettings = spellcheckToggle.eejsBlock_padSettings;
72 changes: 28 additions & 44 deletions static/js/spellcheck.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,35 @@
'use strict';

const padcookie = require('ep_etherpad-lite/static/js/pad_cookie').padcookie;
// Sub-path import keeps the client bundle clean — the top-level
// `ep_plugin_helpers` index pulls in server-only modules (eejs, Settings).
const {padToggle} = require('ep_plugin_helpers/pad-toggle');

const postAceInit = (hook, context) => {
const $outer = $('iframe[name="ace_outer"]').contents().find('iframe');
const $inner = $outer.contents().find('#innerdocbody');
// The `spellcheck` attribute is inherited from the nearest ancestor that
// sets it, so we only need to toggle it on #innerdocbody. The previous
// implementation walked every <div>/<span> descendant on every toggle,
// which is O(n) in line count and multiplied the browser's already-slow
// per-keystroke spellchecking work — observable as typing lag on pads
// with more than a few hundred lines (#26). Setting the attribute once
// on the contenteditable root is equivalent for the browser's checker.
const spellcheck = {
enable: () => { $inner.attr('spellcheck', 'true'); },
disable: () => { $inner.attr('spellcheck', 'false'); },
};
/* init */
if (padcookie.getPref('spellcheck') === false) {
$('#options-spellcheck').val();
$('#options-spellcheck').attr('checked', 'unchecked');
$('#options-spellcheck').attr('checked', false);
} else {
$('#options-spellcheck').attr('checked', 'checked');
}
// Same config as the server-side instance — must agree on pluginName,
// settingId, l10nId, and defaultLabel so checkbox ids and clientVars line up.
const spellcheckToggle = padToggle({
pluginName: 'ep_spellcheck',
settingId: 'spellcheck',
l10nId: 'ep_spellcheck.spellcheck',
defaultLabel: 'Spell Check',
defaultEnabled: true,
});

// Re-export so the helper sees pad-wide broadcasts and refreshes our state
// when another user toggles the pad-wide checkbox.
exports.handleClientMessage_CLIENT_MESSAGE = spellcheckToggle.handleClientMessage_CLIENT_MESSAGE;

if ($('#options-spellcheck').is(':checked')) {
spellcheck.enable();
} else {
spellcheck.disable();
}
exports.postAceInit = () => {
// The `spellcheck` attribute is inherited from the nearest ancestor that
// sets it, so we only need to toggle it on #innerdocbody. Walking every
// <div>/<span> descendant (the previous behavior) is O(n) in line count
// and multiplied the browser's already-slow per-keystroke spellchecking
// work — observable as typing lag on pads with > a few hundred lines (#26).
const $inner = $('iframe[name="ace_outer"]').contents().find('iframe')
.contents().find('#innerdocbody');

/* on click */
$('#options-spellcheck').on('click', () => {
if ($('#options-spellcheck').is(':checked')) {
padcookie.setPref('spellcheck', true);
spellcheck.enable();
} else {
padcookie.setPref('spellcheck', false);
spellcheck.disable();
}
// The previous code force-reloaded the page on Chrome via
// `window.browser.chrome`. That check was always a no-op on Chrome
// (Chrome has no `window.browser` object — it's the Firefox
// WebExtension API) and would have thrown a TypeError on Firefox.
// Toggling the spellcheck attribute takes effect in every current
// browser without a reload, so just drop the reload.
spellcheckToggle.init({
onChange: (enabled) => {
$inner.attr('spellcheck', enabled ? 'true' : 'false');
},
});
};
exports.postAceInit = postAceInit;
4 changes: 0 additions & 4 deletions templates/spellcheck_entry.ejs

This file was deleted.

Loading