@oat-sa/tao-core-ui 3.16.2 → 3.17.0

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.
@@ -1,535 +1,536 @@
1
1
  define(['jquery', 'lodash', 'ui/ckeditor/dtdHandler', 'ckeditor', 'context', 'module', 'services/features'], function ($, _, dtdHandler, ckeditor, context, module, featuresService) { 'use strict';
2
2
 
3
- $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $;
4
- _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
5
- dtdHandler = dtdHandler && Object.prototype.hasOwnProperty.call(dtdHandler, 'default') ? dtdHandler['default'] : dtdHandler;
6
- context = context && Object.prototype.hasOwnProperty.call(context, 'default') ? context['default'] : context;
7
- module = module && Object.prototype.hasOwnProperty.call(module, 'default') ? module['default'] : module;
8
- featuresService = featuresService && Object.prototype.hasOwnProperty.call(featuresService, 'default') ? featuresService['default'] : featuresService;
3
+ $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $;
4
+ _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
5
+ dtdHandler = dtdHandler && Object.prototype.hasOwnProperty.call(dtdHandler, 'default') ? dtdHandler['default'] : dtdHandler;
6
+ context = context && Object.prototype.hasOwnProperty.call(context, 'default') ? context['default'] : context;
7
+ module = module && Object.prototype.hasOwnProperty.call(module, 'default') ? module['default'] : module;
8
+ featuresService = featuresService && Object.prototype.hasOwnProperty.call(featuresService, 'default') ? featuresService['default'] : featuresService;
9
9
 
10
+ /**
11
+ * This program is free software; you can redistribute it and/or
12
+ * modify it under the terms of the GNU General Public License
13
+ * as published by the Free Software Foundation; under version 2
14
+ * of the License (non-upgradable).
15
+ *
16
+ * This program is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24
+ *
25
+ * Copyright (c) 2015-2022 (original work) Open Assessment Technologies SA ;
26
+ */
27
+
28
+ /**
29
+ * Cache original config
30
+ */
31
+ const originalConfig = _.cloneDeep(window.CKEDITOR.config);
32
+ const moduleConfig = module.config();
33
+ const furiganaPluginVisibilityKey = 'ckeditor/TaoFurigana';
34
+ // Check if decorations feature flag is explicitly set to false
35
+ const decorationsOn = context.featureFlags && context.featureFlags.FEATURE_FLAG_CKEDITOR_DECORATIONS !== false;
36
+ function getUserLanguage() {
37
+ const documentLang = window.document.documentElement.getAttribute('lang');
38
+ const documentLocale = documentLang && documentLang.split('-')[0];
39
+ return documentLocale;
40
+ }
41
+ const lang = getUserLanguage();
42
+ const ckConfigurator = function () {
10
43
  /**
11
- * This program is free software; you can redistribute it and/or
12
- * modify it under the terms of the GNU General Public License
13
- * as published by the Free Software Foundation; under version 2
14
- * of the License (non-upgradable).
15
- *
16
- * This program is distributed in the hope that it will be useful,
17
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
- * GNU General Public License for more details.
20
- *
21
- * You should have received a copy of the GNU General Public License
22
- * along with this program; if not, write to the Free Software
23
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24
- *
25
- * Copyright (c) 2015-2022 (original work) Open Assessment Technologies SA ;
44
+ * Toolbar presets that you normally never would need to change, they can however be overridden with options.toolbar.
45
+ * The argument 'toolbarType' determines which toolbar to use
26
46
  */
47
+ const toolbarPresets = {
48
+ inline: [{
49
+ name: 'basicstyles',
50
+ items: ['Bold', 'Italic', 'Subscript', 'Superscript']
51
+ }, {
52
+ name: 'insert',
53
+ items: ['SpecialChar', 'TaoQtiTable', 'TaoTooltip']
54
+ }, {
55
+ name: 'links',
56
+ items: ['Link']
57
+ }, {
58
+ name: 'language',
59
+ items: ['Language']
60
+ }, {
61
+ name: 'styles',
62
+ items: ['Format']
63
+ }, {
64
+ name: 'paragraph',
65
+ items: ['NumberedList', 'BulletedList', '-', 'Blockquote', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
66
+ }, {
67
+ name: 'interactionsource',
68
+ items: ['InteractionSource']
69
+ }],
70
+ flow: [{
71
+ name: 'basicstyles',
72
+ items: ['Bold', 'Italic', 'Subscript', 'Superscript']
73
+ }, {
74
+ name: 'insert',
75
+ items: ['SpecialChar', 'TaoQtiTable', 'TaoTooltip']
76
+ }, {
77
+ name: 'links',
78
+ items: ['Link']
79
+ }, {
80
+ name: 'language',
81
+ items: ['Language']
82
+ }, {
83
+ name: 'interactionsource',
84
+ items: ['InteractionSource']
85
+ }],
86
+ block: [{
87
+ name: 'basicstyles',
88
+ items: ['Bold', 'Italic', 'Subscript', 'Superscript']
89
+ }, {
90
+ name: 'insert',
91
+ items: ['Image', 'SpecialChar', 'TaoQtiTable', 'TaoTooltip']
92
+ }, {
93
+ name: 'links',
94
+ items: ['Link']
95
+ }, {
96
+ name: 'language',
97
+ items: ['Language']
98
+ }, {
99
+ name: 'styles',
100
+ items: ['Format']
101
+ }, {
102
+ name: 'paragraph',
103
+ items: ['NumberedList', 'BulletedList', '-', 'Blockquote', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
104
+ }, {
105
+ name: 'interactionsource',
106
+ items: ['InteractionSource']
107
+ }],
108
+ extendedText: [{
109
+ name: 'basicstyles',
110
+ items: ['Bold', 'Italic', 'Underline', 'Subscript', 'Superscript']
111
+ }, {
112
+ name: 'insert',
113
+ items: ['SpecialChar', 'TaoTab', 'TaoUnTab']
114
+ }, {
115
+ name: 'paragraph',
116
+ items: ['NumberedList', 'BulletedList']
117
+ }, {
118
+ name: 'clipboard',
119
+ items: ['Cut', 'Copy', 'Paste']
120
+ }, {
121
+ name: 'history',
122
+ items: ['Undo', 'Redo']
123
+ }, {
124
+ name: 'textcolor',
125
+ items: ['TextColor']
126
+ }, {
127
+ name: 'font',
128
+ items: ['Font']
129
+ }, {
130
+ name: 'fontsize',
131
+ items: ['FontSize']
132
+ }],
133
+ htmlField: [{
134
+ name: 'basicstyles',
135
+ items: ['Bold', 'Italic', 'Underline']
136
+ }, {
137
+ name: 'exponent',
138
+ items: ['Subscript', 'Superscript']
139
+ }, {
140
+ name: 'fontstyles',
141
+ items: ['TextColor', 'Font', 'FontSize']
142
+ }, {
143
+ name: 'paragraph',
144
+ items: ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
145
+ }, {
146
+ name: 'indent',
147
+ items: ['TaoTab', 'TaoUnTab']
148
+ }, {
149
+ name: 'history',
150
+ items: ['Undo', 'Redo']
151
+ }, {
152
+ name: 'list',
153
+ items: ['NumberedList', 'BulletedList']
154
+ }, {
155
+ name: 'insert',
156
+ items: ['Link', 'SpecialChar']
157
+ }, {
158
+ name: 'language',
159
+ items: ['Language']
160
+ }, {
161
+ name: 'interactionsource',
162
+ items: ['InteractionSource']
163
+ }],
164
+ table: [{
165
+ name: 'basicstyles',
166
+ items: ['Bold', 'Italic', 'Subscript', 'Superscript']
167
+ }, {
168
+ name: 'insert',
169
+ items: ['SpecialChar', 'TaoQtiTable', 'TaoTooltip']
170
+ }, {
171
+ name: 'links',
172
+ items: ['Link']
173
+ }, {
174
+ name: 'paragraph',
175
+ items: ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
176
+ }, {
177
+ name: 'language',
178
+ items: ['Language']
179
+ }, {
180
+ name: 'interactionsource',
181
+ items: ['InteractionSource']
182
+ }]
183
+ };
27
184
 
28
185
  /**
29
- * Cache original config
186
+ * defaults for editor configuration
30
187
  */
31
- const originalConfig = _.cloneDeep(window.CKEDITOR.config);
32
- const moduleConfig = module.config();
33
- const furiganaPluginVisibilityKey = 'ckeditor/TaoFurigana';
34
- const decorationsOn = !!(context.featureFlags && context.featureFlags.FEATURE_FLAG_CKEDITOR_DECORATIONS);
35
- function getUserLanguage() {
36
- const documentLang = window.document.documentElement.getAttribute('lang');
37
- const documentLocale = documentLang && documentLang.split('-')[0];
38
- return documentLocale;
188
+ const ckConfigDefault = {
189
+ disableAutoInline: true,
190
+ entities: false,
191
+ entities_processNumerical: true,
192
+ autoParagraph: false,
193
+ extraPlugins: 'confighelper, taolanguage, interactionsource',
194
+ floatSpaceDockedOffsetY: 0,
195
+ forcePasteAsPlainText: true,
196
+ skin: 'tao',
197
+ language: lang,
198
+ removePlugins: '',
199
+ linkShowAdvancedTab: false,
200
+ justifyClasses: ['txt-lft', 'txt-ctr', 'txt-rgt', 'txt-jty'],
201
+ linkShowTargetTab: false,
202
+ coreStyles_underline: {
203
+ element: 'span',
204
+ attributes: {
205
+ class: 'txt-underline'
206
+ }
207
+ },
208
+ coreStyles_strike: {
209
+ element: 'span',
210
+ attributes: {
211
+ class: 'txt-strike'
212
+ }
213
+ },
214
+ coreStyles_subscript: {
215
+ element: 'sub',
216
+ attributes: {
217
+ class: decorationsOn ? 'txt-subscript' : ''
218
+ }
219
+ },
220
+ coreStyles_superscript: {
221
+ element: 'sup',
222
+ attributes: {
223
+ class: decorationsOn ? 'txt-superscript' : ''
224
+ }
225
+ },
226
+ coreStyles_highlight: {
227
+ element: 'span',
228
+ attributes: {
229
+ class: 'txt-highlight'
230
+ }
231
+ },
232
+ specialChars: ['!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', ['<', 'Less than'], ['≤', 'Less than or equal to'], '≈', '=', '≠', ['≥', 'Greater than or equal to'], ['>', 'Greater than'], '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '€', '‘', '’', '“', '”', '–', '—', '¡', '¢', '£', '¤', '¥', '¦', '§', '¨', '©', 'ª', '«', '¬', '®', '¯', '°', '²', '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', 'Œ', 'œ', 'Ŵ', '&#374', '&#373', 'ŷ', '‚', '‛', '„', '…', '™', '►', '•', '→', '⇒', '⇔', '♦', '≈'],
233
+ disableNativeTableHandles: true
234
+ };
235
+ if (moduleConfig && moduleConfig.specialChars) {
236
+ ckConfigDefault.specialChars = moduleConfig.specialChars;
39
237
  }
40
- const lang = getUserLanguage();
41
- const ckConfigurator = function () {
42
- /**
43
- * Toolbar presets that you normally never would need to change, they can however be overridden with options.toolbar.
44
- * The argument 'toolbarType' determines which toolbar to use
45
- */
46
- const toolbarPresets = {
47
- inline: [{
48
- name: 'basicstyles',
49
- items: ['Bold', 'Italic', 'Subscript', 'Superscript']
50
- }, {
51
- name: 'insert',
52
- items: ['SpecialChar', 'TaoQtiTable', 'TaoTooltip']
53
- }, {
54
- name: 'links',
55
- items: ['Link']
56
- }, {
57
- name: 'language',
58
- items: ['Language']
59
- }, {
60
- name: 'styles',
61
- items: ['Format']
62
- }, {
63
- name: 'paragraph',
64
- items: ['NumberedList', 'BulletedList', '-', 'Blockquote', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
65
- }, {
66
- name: 'interactionsource',
67
- items: ['InteractionSource']
68
- }],
69
- flow: [{
70
- name: 'basicstyles',
71
- items: ['Bold', 'Italic', 'Subscript', 'Superscript']
72
- }, {
73
- name: 'insert',
74
- items: ['SpecialChar', 'TaoQtiTable', 'TaoTooltip']
75
- }, {
76
- name: 'links',
77
- items: ['Link']
78
- }, {
79
- name: 'language',
80
- items: ['Language']
81
- }, {
82
- name: 'interactionsource',
83
- items: ['InteractionSource']
84
- }],
85
- block: [{
86
- name: 'basicstyles',
87
- items: ['Bold', 'Italic', 'Subscript', 'Superscript']
88
- }, {
89
- name: 'insert',
90
- items: ['Image', 'SpecialChar', 'TaoQtiTable', 'TaoTooltip']
91
- }, {
92
- name: 'links',
93
- items: ['Link']
94
- }, {
95
- name: 'language',
96
- items: ['Language']
97
- }, {
98
- name: 'styles',
99
- items: ['Format']
100
- }, {
101
- name: 'paragraph',
102
- items: ['NumberedList', 'BulletedList', '-', 'Blockquote', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
103
- }, {
104
- name: 'interactionsource',
105
- items: ['InteractionSource']
106
- }],
107
- extendedText: [{
108
- name: 'basicstyles',
109
- items: ['Bold', 'Italic', 'Underline', 'Subscript', 'Superscript']
110
- }, {
111
- name: 'insert',
112
- items: ['SpecialChar', 'TaoTab', 'TaoUnTab']
113
- }, {
114
- name: 'paragraph',
115
- items: ['NumberedList', 'BulletedList']
116
- }, {
117
- name: 'clipboard',
118
- items: ['Cut', 'Copy', 'Paste']
119
- }, {
120
- name: 'history',
121
- items: ['Undo', 'Redo']
122
- }, {
123
- name: 'textcolor',
124
- items: ['TextColor']
125
- }, {
126
- name: 'font',
127
- items: ['Font']
128
- }, {
129
- name: 'fontsize',
130
- items: ['FontSize']
131
- }],
132
- htmlField: [{
133
- name: 'basicstyles',
134
- items: ['Bold', 'Italic', 'Underline']
135
- }, {
136
- name: 'exponent',
137
- items: ['Subscript', 'Superscript']
138
- }, {
139
- name: 'fontstyles',
140
- items: ['TextColor', 'Font', 'FontSize']
141
- }, {
142
- name: 'paragraph',
143
- items: ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
144
- }, {
145
- name: 'indent',
146
- items: ['TaoTab', 'TaoUnTab']
147
- }, {
148
- name: 'history',
149
- items: ['Undo', 'Redo']
150
- }, {
151
- name: 'list',
152
- items: ['NumberedList', 'BulletedList']
153
- }, {
154
- name: 'insert',
155
- items: ['Link', 'SpecialChar']
156
- }, {
157
- name: 'language',
158
- items: ['Language']
159
- }, {
160
- name: 'interactionsource',
161
- items: ['InteractionSource']
162
- }],
163
- table: [{
164
- name: 'basicstyles',
165
- items: ['Bold', 'Italic', 'Subscript', 'Superscript']
166
- }, {
167
- name: 'insert',
168
- items: ['SpecialChar', 'TaoQtiTable', 'TaoTooltip']
169
- }, {
170
- name: 'links',
171
- items: ['Link']
172
- }, {
173
- name: 'paragraph',
174
- items: ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
175
- }, {
176
- name: 'language',
177
- items: ['Language']
178
- }, {
179
- name: 'interactionsource',
180
- items: ['InteractionSource']
181
- }]
182
- };
183
-
184
- /**
185
- * defaults for editor configuration
186
- */
187
- const ckConfigDefault = {
188
- disableAutoInline: true,
189
- entities: false,
190
- entities_processNumerical: true,
191
- autoParagraph: false,
192
- extraPlugins: 'confighelper, taolanguage, interactionsource',
193
- floatSpaceDockedOffsetY: 0,
194
- forcePasteAsPlainText: true,
195
- skin: 'tao',
196
- language: lang,
197
- removePlugins: '',
198
- linkShowAdvancedTab: false,
199
- justifyClasses: ['txt-lft', 'txt-ctr', 'txt-rgt', 'txt-jty'],
200
- linkShowTargetTab: false,
201
- coreStyles_underline: {
202
- element: 'span',
203
- attributes: {
204
- class: 'txt-underline'
205
- }
206
- },
207
- coreStyles_strike: {
208
- element: 'span',
209
- attributes: {
210
- class: 'txt-strike'
211
- }
212
- },
213
- coreStyles_subscript: {
214
- element: 'sub',
215
- attributes: {
216
- class: decorationsOn ? 'txt-subscript' : ''
217
- }
218
- },
219
- coreStyles_superscript: {
220
- element: 'sup',
221
- attributes: {
222
- class: decorationsOn ? 'txt-superscript' : ''
223
- }
224
- },
225
- coreStyles_highlight: {
226
- element: 'span',
227
- attributes: {
228
- class: 'txt-highlight'
229
- }
230
- },
231
- specialChars: ['!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', ['<', 'Less than'], ['≤', 'Less than or equal to'], '≈', '=', '≠', ['≥', 'Greater than or equal to'], ['>', 'Greater than'], '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '€', '‘', '’', '“', '”', '–', '—', '¡', '¢', '£', '¤', '¥', '¦', '§', '¨', '©', 'ª', '«', '¬', '®', '¯', '°', '²', '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', 'Œ', 'œ', 'Ŵ', '&#374', '&#373', 'ŷ', '‚', '‛', '„', '…', '™', '►', '•', '→', '⇒', '⇔', '♦', '≈'],
232
- disableNativeTableHandles: true
233
- };
234
- if (moduleConfig && moduleConfig.specialChars) {
235
- ckConfigDefault.specialChars = moduleConfig.specialChars;
236
- }
237
238
 
238
- /**
239
- * Insert positioned plugins at position specified in options.positionedPlugins
240
- *
241
- * @param {Object} ckConfig
242
- * @param {Object} positionedPlugins
243
- */
244
- const _updatePlugins = function (ckConfig, positionedPlugins) {
245
- let itCnt,
246
- tbCnt = ckConfig.toolbar.length,
247
- itLen,
248
- method,
249
- plugin,
250
- index,
251
- separator,
252
- idxItem,
253
- numToReplace,
254
- stringVal,
255
- stringVals = {},
256
- i;
257
- positionedPlugins = positionedPlugins || {};
239
+ /**
240
+ * Insert positioned plugins at position specified in options.positionedPlugins
241
+ *
242
+ * @param {Object} ckConfig
243
+ * @param {Object} positionedPlugins
244
+ */
245
+ const _updatePlugins = function (ckConfig, positionedPlugins) {
246
+ let itCnt,
247
+ tbCnt = ckConfig.toolbar.length,
248
+ itLen,
249
+ method,
250
+ plugin,
251
+ index,
252
+ separator,
253
+ idxItem,
254
+ numToReplace,
255
+ stringVal,
256
+ stringVals = {},
257
+ i;
258
+ positionedPlugins = positionedPlugins || {};
258
259
 
259
- // add positioned plugins to extraPlugins and let CKEDITOR take care of their registration
260
- ckConfig.extraPlugins = function (positionedPluginArr, extraPlugins) {
261
- let pluginIndex = positionedPluginArr.length;
262
- let extraPluginArr = extraPlugins.split(',');
263
- const NATIVE_BUTTONS = new Set(['bold', 'italic', 'underline', 'strike', 'subscript', 'superscript', 'numberedlist', 'bulletedlist', 'blockquote', 'justifyleft', 'justifycenter', 'justifyright', 'justifyblock', 'link', 'unlink', 'image', 'specialchar', 'format', 'font', 'fontsize', 'textcolor', 'language']);
264
- while (pluginIndex--) {
265
- positionedPluginArr[pluginIndex] = positionedPluginArr[pluginIndex].toLowerCase();
266
- }
267
- positionedPluginArr = positionedPluginArr.filter(p => !NATIVE_BUTTONS.has(p));
268
- extraPluginArr = _.compact(_.union(extraPluginArr, positionedPluginArr));
269
- return extraPluginArr.join(',');
270
- }(_.keys(positionedPlugins), ckConfig.extraPlugins);
260
+ // add positioned plugins to extraPlugins and let CKEDITOR take care of their registration
261
+ ckConfig.extraPlugins = function (positionedPluginArr, extraPlugins) {
262
+ let pluginIndex = positionedPluginArr.length;
263
+ let extraPluginArr = extraPlugins.split(',');
264
+ const NATIVE_BUTTONS = new Set(['bold', 'italic', 'underline', 'strike', 'subscript', 'superscript', 'numberedlist', 'bulletedlist', 'blockquote', 'justifyleft', 'justifycenter', 'justifyright', 'justifyblock', 'link', 'unlink', 'image', 'specialchar', 'format', 'font', 'fontsize', 'textcolor', 'language']);
265
+ while (pluginIndex--) {
266
+ positionedPluginArr[pluginIndex] = positionedPluginArr[pluginIndex].toLowerCase();
267
+ }
268
+ positionedPluginArr = positionedPluginArr.filter(p => !NATIVE_BUTTONS.has(p));
269
+ extraPluginArr = _.compact(_.union(extraPluginArr, positionedPluginArr));
270
+ return extraPluginArr.join(',');
271
+ }(_.keys(positionedPlugins), ckConfig.extraPlugins);
271
272
 
272
- // capture line breaks (/) and such
273
- // and turn them into a objects temporarily
274
- for (i = 0; i < tbCnt; i++) {
275
- if (_.isString(ckConfig.toolbar[i])) {
276
- stringVals[i] = ckConfig.toolbar[i];
277
- ckConfig.toolbar[i] = {
278
- items: []
279
- };
280
- }
273
+ // capture line breaks (/) and such
274
+ // and turn them into a objects temporarily
275
+ for (i = 0; i < tbCnt; i++) {
276
+ if (_.isString(ckConfig.toolbar[i])) {
277
+ stringVals[i] = ckConfig.toolbar[i];
278
+ ckConfig.toolbar[i] = {
279
+ items: []
280
+ };
281
281
  }
282
+ }
282
283
 
283
- // add positioned plugins to toolbar
284
- for (plugin in positionedPlugins) {
285
- method = function (pluginProps) {
286
- let propIndex = pluginProps.length;
287
- while (propIndex--) {
288
- if (pluginProps[propIndex].indexOf('insert') === 0 || pluginProps[propIndex] === 'replace') {
289
- return pluginProps[propIndex];
290
- }
284
+ // add positioned plugins to toolbar
285
+ for (plugin in positionedPlugins) {
286
+ method = function (pluginProps) {
287
+ let propIndex = pluginProps.length;
288
+ while (propIndex--) {
289
+ if (pluginProps[propIndex].indexOf('insert') === 0 || pluginProps[propIndex] === 'replace') {
290
+ return pluginProps[propIndex];
291
291
  }
292
- throw new Error('Missing key insertBefore | insertAfter | replace in positionedPlugins');
293
- }(_.keys(positionedPlugins[plugin]));
292
+ }
293
+ throw new Error('Missing key insertBefore | insertAfter | replace in positionedPlugins');
294
+ }(_.keys(positionedPlugins[plugin]));
294
295
 
295
- // the item to insert before | after
296
- idxItem = positionedPlugins[plugin][method].toLowerCase();
297
- separator = positionedPlugins[plugin].separator || false;
298
- index = -1;
296
+ // the item to insert before | after
297
+ idxItem = positionedPlugins[plugin][method].toLowerCase();
298
+ separator = positionedPlugins[plugin].separator || false;
299
+ index = -1;
299
300
 
300
- // each button row
301
- while (tbCnt--) {
302
- itLen = ckConfig.toolbar[tbCnt].items.length;
301
+ // each button row
302
+ while (tbCnt--) {
303
+ itLen = ckConfig.toolbar[tbCnt].items.length;
303
304
 
304
- // each item in row
305
- for (itCnt = 0; itCnt < itLen; itCnt++) {
306
- if (ckConfig.toolbar[tbCnt].items[itCnt].toLowerCase() === idxItem) {
307
- index = itCnt;
308
- break;
309
- }
310
- }
311
- //continue
312
- if (index > -1) {
313
- numToReplace = method === 'replace' ? 1 : 0;
314
- if (method === 'insertAfter') {
315
- index++;
316
- }
317
- if (separator) {
318
- ckConfig.toolbar[tbCnt].items.splice(index, numToReplace, '-');
319
- index++;
320
- }
321
- ckConfig.toolbar[tbCnt].items.splice(index, numToReplace, plugin);
305
+ // each item in row
306
+ for (itCnt = 0; itCnt < itLen; itCnt++) {
307
+ if (ckConfig.toolbar[tbCnt].items[itCnt].toLowerCase() === idxItem) {
308
+ index = itCnt;
322
309
  break;
323
310
  }
324
311
  }
325
- // reset tbCnt
326
- tbCnt = ckConfig.toolbar.length;
312
+ //continue
313
+ if (index > -1) {
314
+ numToReplace = method === 'replace' ? 1 : 0;
315
+ if (method === 'insertAfter') {
316
+ index++;
317
+ }
318
+ if (separator) {
319
+ ckConfig.toolbar[tbCnt].items.splice(index, numToReplace, '-');
320
+ index++;
321
+ }
322
+ ckConfig.toolbar[tbCnt].items.splice(index, numToReplace, plugin);
323
+ break;
324
+ }
327
325
  }
326
+ // reset tbCnt
327
+ tbCnt = ckConfig.toolbar.length;
328
+ }
328
329
 
329
- // re-add toolbar line breaks
330
- for (stringVal in stringVals) {
331
- ckConfig.toolbar[stringVal] = stringVals[stringVal];
332
- }
333
- };
334
- const _switchDtd = function _switchDtd(dtdMode) {
335
- dtdHandler.setMode(dtdMode);
336
- window.CKEDITOR.dtd = dtdHandler.getDtd();
337
- };
330
+ // re-add toolbar line breaks
331
+ for (stringVal in stringVals) {
332
+ ckConfig.toolbar[stringVal] = stringVals[stringVal];
333
+ }
334
+ };
335
+ const _switchDtd = function _switchDtd(dtdMode) {
336
+ dtdHandler.setMode(dtdMode);
337
+ window.CKEDITOR.dtd = dtdHandler.getDtd();
338
+ };
338
339
 
339
- /**
340
- * Generate a configuration object for CKEDITOR
341
- *
342
- * @param editor instance of ckeditor
343
- * @param toolbarType block | inline | flow | qtiBlock | qtiInline | qtiFlow | htmlField | reset to get back to normal
344
- * @param {Object} [options] - is based on the CKEDITOR config object with some additional sugar
345
- * Note that it's here you need to add parameters for the resource manager.
346
- * Some options are not covered in http://docs.ckeditor.com/#!/api/CKEDITOR.config
347
- * @param [options.dtdOverrides] - @see dtdOverrides which pre-defines them
348
- * @param {Object} [options.positionedPlugins] - @see ckConfig.positionedPlugins
349
- * @param {Boolean} [options.qtiImage] - enables the qtiImage plugin
350
- * @param {Boolean} [options.qtiMedia] - enables the qtiMedia plugin
351
- * @param {Boolean} [options.qtiInclude] - enables the qtiInclude plugin
352
- * @param {Boolean} [options.underline] - enables the underline plugin
353
- * @param {Boolean} [options.highlight] - enables the highlight plugin
354
- * @param {Boolean} [options.mathJax] - enables the mathJax plugin
355
- * @param {Boolean} [options.horizontalRule] - enables the horizontalRule plugin
356
- * @param {Boolean} [options.furiganaPlugin] - enables the furiganaPlugin plugin if feature flag is set
357
- * @param {String} [options.removePlugins] - a coma-separated list of plugins that should not be loaded: 'plugin1,plugin2,plugin3'
358
- *
359
- * @see http://docs.ckeditor.com/#!/api/CKEDITOR.config
360
- */
361
- const getConfig = function (editor, toolbarType, options) {
362
- let toolbar, toolbars, config, dtdMode;
340
+ /**
341
+ * Generate a configuration object for CKEDITOR
342
+ *
343
+ * @param editor instance of ckeditor
344
+ * @param toolbarType block | inline | flow | qtiBlock | qtiInline | qtiFlow | htmlField | reset to get back to normal
345
+ * @param {Object} [options] - is based on the CKEDITOR config object with some additional sugar
346
+ * Note that it's here you need to add parameters for the resource manager.
347
+ * Some options are not covered in http://docs.ckeditor.com/#!/api/CKEDITOR.config
348
+ * @param [options.dtdOverrides] - @see dtdOverrides which pre-defines them
349
+ * @param {Object} [options.positionedPlugins] - @see ckConfig.positionedPlugins
350
+ * @param {Boolean} [options.qtiImage] - enables the qtiImage plugin
351
+ * @param {Boolean} [options.qtiMedia] - enables the qtiMedia plugin
352
+ * @param {Boolean} [options.qtiInclude] - enables the qtiInclude plugin
353
+ * @param {Boolean} [options.underline] - enables the underline plugin
354
+ * @param {Boolean} [options.highlight] - enables the highlight plugin
355
+ * @param {Boolean} [options.mathJax] - enables the mathJax plugin
356
+ * @param {Boolean} [options.horizontalRule] - enables the horizontalRule plugin
357
+ * @param {Boolean} [options.furiganaPlugin] - enables the furiganaPlugin plugin if feature flag is set
358
+ * @param {String} [options.removePlugins] - a coma-separated list of plugins that should not be loaded: 'plugin1,plugin2,plugin3'
359
+ *
360
+ * @see http://docs.ckeditor.com/#!/api/CKEDITOR.config
361
+ */
362
+ const getConfig = function (editor, toolbarType, options) {
363
+ let toolbar, toolbars, config, dtdMode;
363
364
 
364
- // This is different from CKEDITOR.config.extraPlugins since it also allows to position the button
365
- // Valid positioning keys are insertAfter | insertBefore | replace followed by the button name, e.g. 'Anchor'
366
- // separator bool, defaults to false
367
- let positionedPlugins = {};
368
- if (toolbarType === 'reset') {
369
- return originalConfig;
370
- }
371
- options = options || {};
372
- options.resourcemgr = options.resourcemgr || {};
373
- toolbars = _.cloneDeep(toolbarPresets);
374
- dtdMode = options.dtdMode || 'html';
375
- const ckConfig = _.cloneDeep(ckConfigDefault);
365
+ // This is different from CKEDITOR.config.extraPlugins since it also allows to position the button
366
+ // Valid positioning keys are insertAfter | insertBefore | replace followed by the button name, e.g. 'Anchor'
367
+ // separator bool, defaults to false
368
+ let positionedPlugins = {};
369
+ if (toolbarType === 'reset') {
370
+ return originalConfig;
371
+ }
372
+ options = options || {};
373
+ options.resourcemgr = options.resourcemgr || {};
374
+ toolbars = _.cloneDeep(toolbarPresets);
375
+ dtdMode = options.dtdMode || 'html';
376
+ const ckConfig = _.cloneDeep(ckConfigDefault);
376
377
 
377
- // modify DTD to either comply with QTI or XHTML
378
- if (dtdMode === 'qti' || toolbarType.indexOf('qti') === 0) {
379
- toolbarType = toolbarType.slice(3).toLowerCase();
380
- ckConfig.allowedContent = true;
381
- ckConfig.autoParagraph = false;
382
- dtdMode = 'qti';
383
- }
378
+ // modify DTD to either comply with QTI or XHTML
379
+ if (dtdMode === 'qti' || toolbarType.indexOf('qti') === 0) {
380
+ toolbarType = toolbarType.slice(3).toLowerCase();
381
+ ckConfig.allowedContent = true;
382
+ ckConfig.autoParagraph = false;
383
+ dtdMode = 'qti';
384
+ }
384
385
 
385
- // modify plugins - this will change the toolbar too
386
- // this would add the qti plugins in positionedPlugins
387
- if (dtdMode === 'qti') {
388
- if (options.qtiMedia) {
389
- positionedPlugins.TaoQtiMedia = {
390
- insertAfter: 'SpecialChar'
391
- };
392
- }
393
- if (options.qtiImage) {
394
- positionedPlugins.TaoQtiImage = {
395
- insertAfter: 'SpecialChar'
396
- };
397
- }
398
- if (options.qtiInclude) {
399
- positionedPlugins.TaoQtiInclude = {
400
- insertAfter: 'SpecialChar'
386
+ // modify plugins - this will change the toolbar too
387
+ // this would add the qti plugins in positionedPlugins
388
+ if (dtdMode === 'qti') {
389
+ if (options.qtiMedia) {
390
+ positionedPlugins.TaoQtiMedia = {
391
+ insertAfter: 'SpecialChar'
392
+ };
393
+ }
394
+ if (options.qtiImage) {
395
+ positionedPlugins.TaoQtiImage = {
396
+ insertAfter: 'SpecialChar'
397
+ };
398
+ }
399
+ if (options.qtiInclude) {
400
+ positionedPlugins.TaoQtiInclude = {
401
+ insertAfter: 'SpecialChar'
402
+ };
403
+ }
404
+ if (decorationsOn) {
405
+ positionedPlugins.Strike = {
406
+ insertAfter: 'Italic'
407
+ };
408
+ if (options.underline) {
409
+ positionedPlugins.TaoUnderline = {
410
+ insertAfter: 'Strike'
401
411
  };
402
412
  }
403
- if (decorationsOn) {
404
- positionedPlugins.Strike = {
413
+ } else {
414
+ if (options.underline) {
415
+ positionedPlugins.Underline = {
405
416
  insertAfter: 'Italic'
406
417
  };
407
- if (options.underline) {
408
- positionedPlugins.TaoUnderline = {
409
- insertAfter: 'Strike'
410
- };
411
- }
412
- } else {
413
- if (options.underline) {
414
- positionedPlugins.Underline = {
415
- insertAfter: 'Italic'
416
- };
417
- }
418
- }
419
- if (options.highlight) {
420
- positionedPlugins.TaoHighlight = {
421
- insertAfter: decorationsOn && options.underline ? 'TaoUnderline' : options.underline ? 'Underline' : 'Italic'
422
- };
423
418
  }
424
- if (options.mathJax) {
425
- positionedPlugins.TaoQtiMaths = {
426
- insertAfter: 'SpecialChar'
419
+ }
420
+ if (options.highlight) {
421
+ positionedPlugins.TaoHighlight = {
422
+ insertAfter: decorationsOn && options.underline ? 'TaoUnderline' : options.underline ? 'Underline' : 'Italic'
423
+ };
424
+ }
425
+ if (options.mathJax) {
426
+ positionedPlugins.TaoQtiMaths = {
427
+ insertAfter: 'SpecialChar'
428
+ };
429
+ }
430
+ if (options.horizontalRule && ['block', 'inline'].includes(toolbarType)) {
431
+ positionedPlugins.HorizontalRule = {
432
+ insertAfter: 'TaoTooltip'
433
+ };
434
+ }
435
+ if (options.furiganaPlugin && featuresService.isVisible(furiganaPluginVisibilityKey, false)) {
436
+ if (!options.toolbar || options.toolbar.find(el => el.items.includes('Superscript'))) {
437
+ positionedPlugins.TaoFurigana = {
438
+ insertAfter: 'Superscript'
427
439
  };
428
- }
429
- if (options.horizontalRule && ['block', 'inline'].includes(toolbarType)) {
430
- positionedPlugins.HorizontalRule = {
431
- insertAfter: 'TaoTooltip'
440
+ } else {
441
+ const lastGroup = options.toolbar[options.toolbar.length - 1];
442
+ const firstPlugin = lastGroup.items[0];
443
+ positionedPlugins.TaoFurigana = {
444
+ insertBefore: firstPlugin
432
445
  };
433
446
  }
434
- if (options.furiganaPlugin && featuresService.isVisible(furiganaPluginVisibilityKey, false)) {
435
- if (!options.toolbar || options.toolbar.find(el => el.items.includes('Superscript'))) {
436
- positionedPlugins.TaoFurigana = {
437
- insertAfter: 'Superscript'
438
- };
439
- } else {
440
- const lastGroup = options.toolbar[options.toolbar.length - 1];
441
- const firstPlugin = lastGroup.items[0];
442
- positionedPlugins.TaoFurigana = {
443
- insertBefore: firstPlugin
444
- };
445
- }
446
- }
447
447
  }
448
+ }
448
449
 
449
- // if there is a toolbar in the options add it to the set
450
- if (options.toolbar) {
451
- toolbars[toolbarType] = _.clone(options.toolbar);
452
- }
450
+ // if there is a toolbar in the options add it to the set
451
+ if (options.toolbar) {
452
+ toolbars[toolbarType] = _.clone(options.toolbar);
453
+ }
453
454
 
454
- // add toolbars to config
455
- for (toolbar in toolbars) {
456
- if (Object.prototype.hasOwnProperty.call(toolbars, toolbar)) {
457
- ckConfig['toolbar_' + toolbar] = toolbars[toolbar];
458
- }
455
+ // add toolbars to config
456
+ for (toolbar in toolbars) {
457
+ if (Object.prototype.hasOwnProperty.call(toolbars, toolbar)) {
458
+ ckConfig['toolbar_' + toolbar] = toolbars[toolbar];
459
459
  }
460
+ }
460
461
 
461
- // add the toolbar
462
- if (typeof toolbars[toolbarType] !== 'undefined') {
463
- ckConfig.toolbar = toolbars[toolbarType];
462
+ // add the toolbar
463
+ if (typeof toolbars[toolbarType] !== 'undefined') {
464
+ ckConfig.toolbar = toolbars[toolbarType];
464
465
 
465
- //enable sourcedialog plugin upon featureflag (false by default)
466
- if (context.featureFlags && context.featureFlags.FEATURE_FLAG_CKEDITOR_SOURCEDIALOG) {
467
- ckConfig.toolbar.push({
468
- name: 'sourcedialog',
469
- items: ['Sourcedialog']
470
- });
471
- }
466
+ //enable sourcedialog plugin upon featureflag (false by default)
467
+ if (context.featureFlags && context.featureFlags.FEATURE_FLAG_CKEDITOR_SOURCEDIALOG) {
468
+ ckConfig.toolbar.push({
469
+ name: 'sourcedialog',
470
+ items: ['Sourcedialog']
471
+ });
472
472
  }
473
+ }
473
474
 
474
- // ensures positionedPlugins has the right format
475
- if (typeof options.positionedPlugins !== 'undefined') {
476
- options.positionedPlugins = {};
477
- }
475
+ // ensures positionedPlugins has the right format
476
+ if (typeof options.positionedPlugins !== 'undefined') {
477
+ options.positionedPlugins = {};
478
+ }
478
479
 
479
- // set options.positionedPlugins to false to prevent the class from using them at all
480
- if (false !== options.positionedPlugins) {
481
- // this would add positionedPlugins (e.g. the media manager)
482
- positionedPlugins = _.assign(positionedPlugins, _.clone(options.positionedPlugins));
483
- _updatePlugins(ckConfig, positionedPlugins);
484
- }
480
+ // set options.positionedPlugins to false to prevent the class from using them at all
481
+ if (false !== options.positionedPlugins) {
482
+ // this would add positionedPlugins (e.g. the media manager)
483
+ positionedPlugins = _.assign(positionedPlugins, _.clone(options.positionedPlugins));
484
+ _updatePlugins(ckConfig, positionedPlugins);
485
+ }
485
486
 
486
- // forward the options to ckConfig, exclude local options
487
- config = _.assign({}, _.cloneDeep(originalConfig), ckConfig, _.omit(options, ['qtiImage', 'qtiInclude', 'underline', 'strike', 'highlight', 'mathJax', 'toolbar', 'positionedPlugins']));
487
+ // forward the options to ckConfig, exclude local options
488
+ config = _.assign({}, _.cloneDeep(originalConfig), ckConfig, _.omit(options, ['qtiImage', 'qtiInclude', 'underline', 'strike', 'highlight', 'mathJax', 'toolbar', 'positionedPlugins']));
488
489
 
489
- // debugger: has this config been used?
490
- //config.aaaConfigurationHasBeenLoadedFromConfigurator = true;
490
+ // debugger: has this config been used?
491
+ //config.aaaConfigurationHasBeenLoadedFromConfigurator = true;
491
492
 
492
- // toggle global DTD depending on the CK instance which is receiving the focus
493
- // I know that this is rather ugly <= don't worry, we'll keep this a secret ;)
494
- editor.on('focus', function () {
495
- _switchDtd(dtdMode);
496
- // should be 1 on html, undefined on qti
497
- // console.log(CKEDITOR.dtd.pre.img)
498
- });
493
+ // toggle global DTD depending on the CK instance which is receiving the focus
494
+ // I know that this is rather ugly <= don't worry, we'll keep this a secret ;)
495
+ editor.on('focus', function () {
496
+ _switchDtd(dtdMode);
497
+ // should be 1 on html, undefined on qti
498
+ // console.log(CKEDITOR.dtd.pre.img)
499
+ });
499
500
 
500
- // remove title 'Rich Text Editor, instance n' that CKE sets by default
501
- // ref: http://tinyurl.com/keedruc
502
- editor.on('instanceReady', function (e) {
503
- $(e.editor.element.$).removeAttr('title');
504
- });
501
+ // remove title 'Rich Text Editor, instance n' that CKE sets by default
502
+ // ref: http://tinyurl.com/keedruc
503
+ editor.on('instanceReady', function (e) {
504
+ $(e.editor.element.$).removeAttr('title');
505
+ });
505
506
 
506
- // This fixes bug #2855. Unfortunately this can be done on the global object only, not on the instance
507
- window.CKEDITOR.on('dialogDefinition', function (e) {
508
- let linkTypes, wanted, linkIndex;
509
- if (e.data.name !== 'link') {
510
- return;
511
- }
512
- linkTypes = e.data.definition.getContents('info').get('linkType').items;
513
- linkIndex = linkTypes.length;
514
- while (linkIndex--) {
515
- if (linkTypes[linkIndex][1] !== 'anchor') {
516
- wanted = linkIndex;
517
- continue;
518
- }
519
- }
520
- linkTypes.splice(wanted + 1, 1);
507
+ // This fixes bug #2855. Unfortunately this can be done on the global object only, not on the instance
508
+ window.CKEDITOR.on('dialogDefinition', function (e) {
509
+ let linkTypes, wanted, linkIndex;
510
+ if (e.data.name !== 'link') {
521
511
  return;
522
- });
523
- return config;
524
- };
512
+ }
513
+ linkTypes = e.data.definition.getContents('info').get('linkType').items;
514
+ linkIndex = linkTypes.length;
515
+ while (linkIndex--) {
516
+ if (linkTypes[linkIndex][1] !== 'anchor') {
517
+ wanted = linkIndex;
518
+ continue;
519
+ }
520
+ }
521
+ linkTypes.splice(wanted + 1, 1);
522
+ return;
523
+ });
524
+ return config;
525
+ };
525
526
 
526
- // Set TAO custom DTD the first time CKEditor is initialized
527
- _switchDtd('qti');
528
- return {
529
- getConfig: getConfig
530
- };
531
- }();
527
+ // Set TAO custom DTD the first time CKEditor is initialized
528
+ _switchDtd('qti');
529
+ return {
530
+ getConfig: getConfig
531
+ };
532
+ }();
532
533
 
533
- return ckConfigurator;
534
+ return ckConfigurator;
534
535
 
535
536
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oat-sa/tao-core-ui",
3
- "version": "3.16.2",
3
+ "version": "3.17.0",
4
4
  "displayName": "TAO Core UI",
5
5
  "description": "UI libraries of TAO",
6
6
  "scripts": {
@@ -29,7 +29,10 @@ import featuresService from 'services/features';
29
29
  const originalConfig = _.cloneDeep(window.CKEDITOR.config);
30
30
  const moduleConfig = module.config();
31
31
  const furiganaPluginVisibilityKey = 'ckeditor/TaoFurigana';
32
- const decorationsOn = !!(context.featureFlags && context.featureFlags.FEATURE_FLAG_CKEDITOR_DECORATIONS);
32
+ // Check if decorations feature flag is explicitly set to false
33
+ const decorationsOn =
34
+ context.featureFlags &&
35
+ context.featureFlags.FEATURE_FLAG_CKEDITOR_DECORATIONS !== false;
33
36
 
34
37
  function getUserLanguage() {
35
38
  const documentLang = window.document.documentElement.getAttribute('lang');