ep_special_characters 0.0.59 → 0.0.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,68 @@
1
+ # Agent Guide — ep_special_characters
2
+
3
+ Insert special characters into a pad.
4
+
5
+ ## Tech stack
6
+
7
+ * Etherpad plugin framework (hooks declared in `ep.json`)
8
+ * EJS templates rendered server-side via `eejsBlock_*` hooks
9
+
10
+ ## Project structure
11
+
12
+ ```
13
+ ep_special_characters/
14
+ ├── AGENTS.md
15
+ ├── CONTRIBUTING.md
16
+ ├── ep.json
17
+ ├── hooks.js
18
+ ├── package.json
19
+ ├── static/
20
+ │ ├── css/
21
+ │ ├── html/
22
+ │ ├── js/
23
+ │ ├── tests/
24
+ ├── templates/
25
+ │ ├── editbarButtons.ejs
26
+ │ ├── modals.ejs
27
+ │ ├── scripts.ejs
28
+ │ ├── special_characters_menu.ejs
29
+ │ ├── styles.ejs
30
+ ```
31
+
32
+ ## Helpers used
33
+
34
+ _None — `ep_plugin_helpers` is not a dependency. Adoption is part of the helpers-adoption sweep (Phase 4)._
35
+
36
+
37
+ ## Helpers NOT used
38
+
39
+ _To be audited in the helpers-adoption sweep (Phase 4)._
40
+
41
+
42
+ ## Running tests locally
43
+
44
+ `ep_special_characters` runs inside Etherpad's test harness. From an etherpad checkout that has installed this plugin via `pnpm run plugins i --path ../ep_special_characters`:
45
+
46
+ ```bash
47
+ # Backend (Mocha) — harness boots its own server
48
+ pnpm --filter ep_etherpad-lite run test
49
+
50
+ # Playwright — needs `pnpm run dev` in a second terminal
51
+ pnpm --filter ep_etherpad-lite run test-ui
52
+ ```
53
+
54
+ ## Standing rules for agent edits
55
+
56
+ * PRs target `main`. Linear commits, no merge commits.
57
+ * Every bug fix includes a regression test in the same commit.
58
+ * All user-facing strings in `locales/`. No hardcoded English in templates.
59
+ * No hardcoded `aria-label` on icon-only controls — etherpad's html10n auto-populates `aria-label` from the localized string when (a) the element has a `data-l10n-id` and (b) no author-supplied `aria-label` is present. Adding a hardcoded English `aria-label` blocks that and leaves it untranslated. (See `etherpad-lite/src/static/js/vendors/html10n.ts:665-678`.)
60
+ * No nested interactive elements (no `<button>` inside `<a>`).
61
+ * LLM/Agent contributions are explicitly welcomed by maintainers.
62
+
63
+ ## Quick reference: hooks declared in `ep.json`
64
+
65
+ * Server: `eejsBlock_editbarMenuLeft`, `eejsBlock_scripts`, `eejsBlock_styles`, `eejsBlock_body`, `eejsBlock_dd_insert`
66
+ * Client: `aceInitInnerdocbodyHead`, `aceAttribsToClasses`, `aceCreateDomLine`
67
+
68
+ When adding a hook, register it in both `ep.json` *and* the matching `exports.<hook> = ...` in the JS file.
package/README.md CHANGED
@@ -1,10 +1,5 @@
1
1
  [![Backend Tests Status](https://github.com/ether/ep_special_characters/actions/workflows/test-and-release.yml/badge.svg)](https://github.com/ether/ep_special_characters/actions/workflows/test-and-release.yml)
2
2
 
3
- > ⚠️ **Known issue:** Clicking a character in the picker currently throws
4
- > `ReferenceError: require is not defined` because [`static/js/main.js:16`](static/js/main.js)
5
- > uses a browser-side `require()` that Etherpad's current esbuild bundler
6
- > doesn't expose. Tracking fix in [#87](https://github.com/ether/ep_special_characters/issues/87).
7
-
8
3
  # Special Character Picker for Etherpad
9
4
  TODO: Describe the plugin.
10
5
 
package/ep.json CHANGED
@@ -10,6 +10,7 @@
10
10
  "eejsBlock_dd_insert" : "ep_special_characters/hooks"
11
11
  },
12
12
  "client_hooks": {
13
+ "postAceInit": "ep_special_characters/static/js/hooks:postAceInit",
13
14
  "aceInitInnerdocbodyHead": "ep_special_characters/static/js/hooks:aceInitInnerdocbodyHead",
14
15
  "aceAttribsToClasses": "ep_special_characters/static/js/hooks:aceAttribsToClasses",
15
16
  "aceCreateDomLine": "ep_special_characters/static/js/hooks:aceCreateDomLine"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ep_special_characters",
3
3
  "description": "Insert special characters into a pad",
4
- "version": "0.0.59",
4
+ "version": "0.0.61",
5
5
  "author": {
6
6
  "name": "Johnyma22",
7
7
  "email": "john@mclear.co.uk",
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ exports.postAceInit = (hookName, context) => {
4
+ window.epSpecialCharactersAce = context.ace;
5
+ };
6
+
3
7
  exports.aceInitInnerdocbodyHead = (hookName, args, cb) => {
4
8
  const path = '../static/plugins/ep_special_characters/static/css/ace.css';
5
9
  args.iframeHTML.push(
package/static/js/main.js CHANGED
@@ -1,8 +1,24 @@
1
1
  'use strict';
2
2
 
3
3
  $(document).ready(() => {
4
+ const SPECIAL_CHARACTER_START_CODE_POINT = 0x20;
5
+ const SPECIAL_CHARACTER_END_CODE_POINT = 0x2FF;
4
6
  const module = $('#specialCharactersModal');
7
+ const specialChars = $('.specialChars');
8
+ let isSpecialCharactersPopulated = false;
9
+
10
+ const populateSpecialCharacters = () => {
11
+ if (isSpecialCharactersPopulated) return;
12
+ const chars = [];
13
+ for (let i = SPECIAL_CHARACTER_START_CODE_POINT; i <= SPECIAL_CHARACTER_END_CODE_POINT; i++) {
14
+ chars.push(`<li class='specialChar'>&#${i}</li>`);
15
+ }
16
+ specialChars.append(chars.join(''));
17
+ isSpecialCharactersPopulated = true;
18
+ };
19
+
5
20
  $('.insertSpecialCharacter').click(() => {
21
+ populateSpecialCharacters();
6
22
  module.toggleClass('popup-show');
7
23
  });
8
24
 
@@ -13,18 +29,13 @@ $(document).ready(() => {
13
29
  const char = ($(this).text());
14
30
  $('.usedSpecialCharacters').append(this);
15
31
  $('.usedSpecialCharactersLabel').show();
16
- const padeditor = require('ep_etherpad-lite/static/js/pad_editor').padeditor;
32
+ const ace = window.epSpecialCharactersAce;
17
33
  module.toggleClass('popup-show');
18
- return padeditor.ace.callWithAce((ace) => {
19
- const rep = ace.ace_getRep();
20
- ace.ace_replaceRange(rep.selStart, rep.selEnd, char);
21
- ace.ace_focus();
34
+ if (!ace) return;
35
+ return ace.callWithAce((editor) => {
36
+ const rep = editor.ace_getRep();
37
+ editor.ace_replaceRange(rep.selStart, rep.selEnd, char);
38
+ editor.ace_focus();
22
39
  }, 'specialCharacters');
23
40
  });
24
-
25
- let i = 0;
26
- while (i <= 5000) {
27
- $('.specialChars').append(`<li class='specialChar'>&#${i}</li>`);
28
- i++;
29
- }
30
41
  });
@@ -1,6 +1,8 @@
1
1
  import {expect, test} from '@playwright/test';
2
2
  import {getPadBody, goToNewPad} from 'ep_etherpad-lite/tests/frontend-new/helper/padHelper';
3
3
 
4
+ const expectedSpecialCharacterCount = (0x2FF - 0x20) + 1;
5
+
4
6
  test.beforeEach(async ({page}) => {
5
7
  await goToNewPad(page);
6
8
  });
@@ -10,4 +12,25 @@ test.describe('ep_special_characters', () => {
10
12
  const padBody = await getPadBody(page);
11
13
  await expect(padBody).toBeVisible();
12
14
  });
15
+
16
+ test('special characters are populated lazily on first modal open', async ({page}) => {
17
+ await expect(page.locator('.specialChars .specialChar')).toHaveCount(0);
18
+
19
+ await page.locator('.insertSpecialCharacter').first().click();
20
+ await expect(page.locator('#specialCharactersModal')).toHaveClass(/popup-show/);
21
+ await expect(page.locator('.specialChars .specialChar')).toHaveCount(expectedSpecialCharacterCount);
22
+ });
23
+
24
+ test('clicking a special character inserts it without page errors', async ({page}) => {
25
+ const pageErrors: Error[] = [];
26
+ page.on('pageerror', (error) => pageErrors.push(error));
27
+ const padBody = await getPadBody(page);
28
+ await padBody.click();
29
+
30
+ await page.locator('.insertSpecialCharacter').first().click();
31
+ await page.locator('.specialChars .specialChar', {hasText: 'Æ'}).first().click();
32
+
33
+ await expect(padBody).toContainText('Æ');
34
+ expect(pageErrors).toEqual([]);
35
+ });
13
36
  });