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 +17 -43
- package/locales/qqq.json +7 -1
- package/package.json +4 -1
- package/static/js/index.js +15 -38
- package/static/js/shared.js +5 -24
- package/static/tests/frontend/specs/test.js +32 -0
- package/templates/editbarButtons.ejs +4 -1
package/index.js
CHANGED
|
@@ -1,48 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const {template} = require('ep_plugin_helpers');
|
|
4
|
+
const {lineAttributeExport} = require('ep_plugin_helpers/attributes-server');
|
|
5
5
|
|
|
6
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
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",
|
package/static/js/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {lineAttribute} = require('ep_plugin_helpers/attributes');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const cssFiles = ['ep_headings2/static/css/editor.css'];
|
|
6
6
|
const tags = ['h1', 'h2', 'h3', 'h4', 'code'];
|
|
7
|
-
|
|
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
|
-
|
|
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;
|
package/static/js/shared.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
20
|
-
exports.collectContentPost =
|
|
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>
|