ep_headings2 0.2.101 → 0.2.104

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/index.js CHANGED
@@ -1,48 +1,22 @@
1
1
  'use strict';
2
2
 
3
- const eejs = require('ep_etherpad-lite/node/eejs/');
4
- const Changeset = require('ep_etherpad-lite/static/js/Changeset');
3
+ const {template} = require('ep_plugin_helpers');
4
+ const {lineAttributeExport} = require('ep_plugin_helpers/attributes-server');
5
5
 
6
- exports.eejsBlock_editbarMenuLeft = (hookName, args, cb) => {
7
- args.content += eejs.require('ep_headings2/templates/editbarButtons.ejs');
8
- return cb();
9
- };
6
+ const tags = ['h1', 'h2', 'h3', 'h4', 'code'];
10
7
 
11
- // Include CSS for HTML export
12
- exports.stylesForExport = () => (
13
- // These should be consistent with client CSS.
14
- 'h1{font-size: 2.5em;}\n' +
15
- 'h2{font-size: 1.8em;}\n' +
16
- 'h3{font-size: 1.5em;}\n' +
17
- 'h4{font-size: 1.2em;}\n' +
18
- 'code{font-family: RobotoMono;}\n');
8
+ const headingsExport = lineAttributeExport({
9
+ attr: 'heading',
10
+ tags,
11
+ normalize: (value) => (value === 'h5' || value === 'h6') ? 'h4' : value,
12
+ exportStyles:
13
+ 'h1{font-size: 2.5em;}\n' +
14
+ 'h2{font-size: 1.8em;}\n' +
15
+ 'h3{font-size: 1.5em;}\n' +
16
+ 'h4{font-size: 1.2em;}\n' +
17
+ 'code{font-family: RobotoMono;}\n',
18
+ });
19
19
 
20
- const _analyzeLine = (alineAttrs, apool) => {
21
- let header = null;
22
- if (alineAttrs) {
23
- const opIter = Changeset.opIterator(alineAttrs);
24
- if (opIter.hasNext()) {
25
- const op = opIter.next();
26
- header = Changeset.opAttributeValue(op, 'heading', apool);
27
- }
28
- }
29
- return header;
30
- };
31
-
32
- // line, apool,attribLine,text
33
- exports.getLineHTMLForExport = async (hookName, context) => {
34
- const header = _analyzeLine(context.attribLine, context.apool);
35
- if (header) {
36
- if (context.text.indexOf('*') === 0) {
37
- context.lineContent = context.lineContent.replace('*', '');
38
- }
39
- const paragraph = context.lineContent.match(/<p([^>]+)?>/);
40
- if (paragraph) {
41
- context.lineContent = context.lineContent.replace('<p', `<${header} `);
42
- context.lineContent = context.lineContent.replace('</p>', `</${header}>`);
43
- } else {
44
- context.lineContent = `<${header}>${context.lineContent}</${header}>`;
45
- }
46
- return context.lineContent;
47
- }
48
- };
20
+ exports.eejsBlock_editbarMenuLeft = template('ep_headings2/templates/editbarButtons.ejs');
21
+ exports.stylesForExport = headingsExport.stylesForExport;
22
+ exports.getLineHTMLForExport = headingsExport.getLineHTMLForExport;
package/locales/qqq.json CHANGED
@@ -4,5 +4,11 @@
4
4
  "BryanDavis"
5
5
  ]
6
6
  },
7
- "ep_headings.style": "{{Identical|Style}}"
7
+ "ep_headings.style": "Label for the heading style dropdown and its aria-label. {{Identical|Style}}",
8
+ "ep_headings.normal": "Option to remove heading formatting and return to normal text",
9
+ "ep_headings.h1": "Option for Heading level 1 (largest)",
10
+ "ep_headings.h2": "Option for Heading level 2",
11
+ "ep_headings.h3": "Option for Heading level 3",
12
+ "ep_headings.h4": "Option for Heading level 4 (smallest)",
13
+ "ep_headings.code": "Option for monospaced code formatting"
8
14
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "description": "Adds heading support to Etherpad Lite. Includes improved suppot for export, i18n etc.",
3
3
  "name": "ep_headings2",
4
- "version": "0.2.101",
4
+ "version": "0.2.104",
5
5
  "author": {
6
6
  "name": "John McLear",
7
7
  "email": "john@mclear.co.uk"
@@ -33,6 +33,9 @@
33
33
  "bugs": {
34
34
  "url": "https://github.com/ether/ep_headings2/issues"
35
35
  },
36
+ "dependencies": {
37
+ "ep_plugin_helpers": "^0.2.0"
38
+ },
36
39
  "devDependencies": {
37
40
  "eslint": "^8.57.1",
38
41
  "eslint-config-etherpad": "^4.0.4",
@@ -1,10 +1,19 @@
1
1
  'use strict';
2
2
 
3
- const cssFiles = ['ep_headings2/static/css/editor.css'];
3
+ const {lineAttribute} = require('ep_plugin_helpers/attributes');
4
4
 
5
- // All our tags are block elements, so we just return them.
5
+ const cssFiles = ['ep_headings2/static/css/editor.css'];
6
6
  const tags = ['h1', 'h2', 'h3', 'h4', 'code'];
7
- exports.aceRegisterBlockElements = () => tags;
7
+
8
+ const headings = lineAttribute({
9
+ attr: 'heading',
10
+ tags,
11
+ normalize: (value) => (value === 'h5' || value === 'h6') ? 'h4' : value,
12
+ });
13
+
14
+ exports.aceRegisterBlockElements = headings.aceRegisterBlockElements;
15
+ exports.aceAttribsToClasses = headings.aceAttribsToClasses;
16
+ exports.aceDomLineProcessLineAttributes = headings.aceDomLineProcessLineAttributes;
8
17
 
9
18
  // Bind the event handler to the toolbar buttons
10
19
  exports.postAceInit = (hookName, context) => {
@@ -18,6 +27,8 @@ exports.postAceInit = (hookName, context) => {
18
27
  }, 'insertheading', true);
19
28
  hs.val('dummy');
20
29
  }
30
+ // Return focus to the editor after heading selection (fixes #130)
31
+ context.ace.focus();
21
32
  });
22
33
  };
23
34
 
@@ -28,16 +39,13 @@ const range = (start, end) => Array.from(
28
39
 
29
40
  // On caret position change show the current heading
30
41
  exports.aceEditEvent = (hookName, call) => {
31
- // If it's not a click or a key event and the text hasn't changed then do nothing
32
42
  const cs = call.callstack;
33
43
  if (!(cs.type === 'handleClick') && !(cs.type === 'handleKeyEvent') && !(cs.docTextChanged)) {
34
44
  return false;
35
45
  }
36
- // If it's an initial setup event then do nothing..
37
46
  if (cs.type === 'setBaseText' || cs.type === 'setup') return false;
38
47
 
39
- // It looks like we should check to see if this section has this attribute
40
- setTimeout(() => { // avoid race condition..
48
+ setTimeout(() => {
41
49
  const attributeManager = call.documentAttributeManager;
42
50
  const rep = call.rep;
43
51
  const activeAttributes = {};
@@ -60,7 +68,6 @@ exports.aceEditEvent = (hookName, call) => {
60
68
 
61
69
  $.each(activeAttributes, (k, attr) => {
62
70
  if (attr.count === totalNumberOfLines) {
63
- // show as active class
64
71
  const ind = tags.indexOf(k);
65
72
  $('#heading-selection').val(ind).niceSelect('update');
66
73
  }
@@ -68,39 +75,9 @@ exports.aceEditEvent = (hookName, call) => {
68
75
  }, 250);
69
76
  };
70
77
 
71
- // Our heading attribute will result in a heaading:h1... :h6 class
72
- exports.aceAttribsToClasses = (hookName, context) => {
73
- if (context.key === 'heading') {
74
- return [`heading:${context.value}`];
75
- }
76
- };
77
-
78
- // Here we convert the class heading:h1 into a tag
79
- exports.aceDomLineProcessLineAttributes = (hookName, context) => {
80
- const cls = context.cls;
81
- const headingType = /(?:^| )heading:([A-Za-z0-9]*)/.exec(cls);
82
- if (headingType) {
83
- let tag = headingType[1];
84
-
85
- // backward compatibility, we used propose h5 and h6, but not anymore
86
- if (tag === 'h5' || tag === 'h6') tag = 'h4';
87
-
88
- if (tags.indexOf(tag) >= 0) {
89
- const modifier = {
90
- preHtml: `<${tag}>`,
91
- postHtml: `</${tag}>`,
92
- processedMarker: true,
93
- };
94
- return [modifier];
95
- }
96
- }
97
- return [];
98
- };
99
-
100
78
  // Once ace is initialized, we set ace_doInsertHeading and bind it to the context
101
79
  exports.aceInitialized = (hookName, context) => {
102
80
  const editorInfo = context.editorInfo;
103
- // Passing a level >= 0 will set a heading on the selected lines, level < 0 will remove it.
104
81
  editorInfo.ace_doInsertHeading = (level) => {
105
82
  const {documentAttributeManager, rep} = context;
106
83
  if (!(rep.selStart && rep.selEnd)) return;
@@ -1,29 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ const {lineAttribute} = require('ep_plugin_helpers/attributes');
4
+
3
5
  const tags = ['h1', 'h2', 'h3', 'h4', 'code'];
4
6
 
5
- exports.collectContentPre = (hookName, context, cb) => {
6
- const tname = context.tname;
7
- const state = context.state;
8
- const lineAttributes = state.lineAttributes;
9
- const tagIndex = tags.indexOf(tname);
10
- if (tname === 'div' || tname === 'p') {
11
- delete lineAttributes.heading;
12
- }
13
- if (tagIndex >= 0) {
14
- lineAttributes.heading = tags[tagIndex];
15
- }
16
- return cb();
17
- };
7
+ const headings = lineAttribute({attr: 'heading', tags});
18
8
 
19
- // I don't even know when this is run..
20
- exports.collectContentPost = (hookName, context, cb) => {
21
- const tname = context.tname;
22
- const state = context.state;
23
- const lineAttributes = state.lineAttributes;
24
- const tagIndex = tags.indexOf(tname);
25
- if (tagIndex >= 0) {
26
- delete lineAttributes.heading;
27
- }
28
- return cb();
29
- };
9
+ exports.collectContentPre = headings.collectContentPre;
10
+ exports.collectContentPost = headings.collectContentPost;
@@ -12,6 +12,38 @@ describe('ep_headings2 - Set Heading and ensure its removed properly', function
12
12
  // Set Line 1 heading and check it's set
13
13
  // Set Line 2 to null heading value and check it's set
14
14
 
15
+ it('Heading select has aria-label for accessibility', async function () {
16
+ this.timeout(60000);
17
+ const chrome$ = helper.padChrome$;
18
+ const $select = chrome$('#heading-selection');
19
+ expect($select.attr('aria-label')).to.not.be(undefined);
20
+ expect($select.attr('aria-label').length).to.be.greaterThan(0);
21
+ });
22
+
23
+ it('Focus returns to editor after selecting a heading', async function () {
24
+ this.timeout(60000);
25
+ const chrome$ = helper.padChrome$;
26
+ const inner$ = helper.padInner$;
27
+
28
+ // Type some text
29
+ const $firstTextElement = inner$('div').first();
30
+ $firstTextElement.sendkeys('{selectall}');
31
+ $firstTextElement.sendkeys('Test focus');
32
+
33
+ // Select heading 1
34
+ chrome$('#heading-selection').val('0');
35
+ chrome$('#heading-selection').change();
36
+
37
+ // Wait for heading to be applied, then check focus is back in the editor
38
+ await helper.waitForPromise(() => inner$('div').first().find('h1').length === 1);
39
+
40
+ // The editor iframe should have focus, not the toolbar
41
+ const editorHasFocus = inner$('div').first().is(':focus') ||
42
+ inner$.document.hasFocus() ||
43
+ $(helper.padOuter$.document).find('iframe[name="ace_inner"]').is(':focus');
44
+ expect(editorHasFocus).to.be(true);
45
+ });
46
+
15
47
  it('Option select is changed when heading is changed', async function () {
16
48
  this.timeout(60000);
17
49
  const chrome$ = helper.padChrome$;
@@ -1,6 +1,9 @@
1
1
  <li class="separator acl-write"></li>
2
2
  <li id="headings" class="acl-write">
3
- <select id="heading-selection">
3
+ <select id="heading-selection"
4
+ aria-label="Text style"
5
+ data-l10n-id="ep_headings.style"
6
+ data-l10n-attr="aria-label">
4
7
  <option value="dummy" selected data-l10n-id="ep_headings.style">Style</option>
5
8
  <option value="-1" data-l10n-id="ep_headings.normal">Normal</option>
6
9
  <option value="0" data-l10n-id="ep_headings.h1">Heading 1</option>