| 1441 |
ariadna |
1 |
/* global ns CKEDITOR */
|
|
|
2 |
/**
|
|
|
3 |
* Adds a html text field to the form.
|
|
|
4 |
*
|
|
|
5 |
* @param {type} parent
|
|
|
6 |
* @param {type} field
|
|
|
7 |
* @param {type} params
|
|
|
8 |
* @param {type} setValue
|
|
|
9 |
* @returns {undefined}
|
|
|
10 |
*/
|
|
|
11 |
ns.Html = function (parent, field, params, setValue) {
|
|
|
12 |
this.parent = parent;
|
|
|
13 |
this.field = field;
|
|
|
14 |
this.value = params;
|
|
|
15 |
this.setValue = setValue;
|
|
|
16 |
this.tags = ns.$.merge(['br'], (this.field.tags || this.defaultTags));
|
|
|
17 |
};
|
|
|
18 |
ns.Html.first = true;
|
|
|
19 |
|
|
|
20 |
ns.Html.prototype.defaultTags = ['strong', 'em', 'del', 'h2', 'h3', 'a', 'ul', 'ol', 'table', 'hr'];
|
|
|
21 |
|
|
|
22 |
// This should probably be named "hasTag()" instead...
|
|
|
23 |
// And might be more efficient if this.tags.contains() were used?
|
|
|
24 |
ns.Html.prototype.inTags = function (value) {
|
|
|
25 |
return (ns.$.inArray(value.toLowerCase(), this.tags) >= 0);
|
|
|
26 |
};
|
|
|
27 |
|
|
|
28 |
/**
|
|
|
29 |
* Check if the provided button is enabled by config.
|
|
|
30 |
*
|
|
|
31 |
* @param {string} value
|
|
|
32 |
* @return {boolean}
|
|
|
33 |
*/
|
|
|
34 |
ns.Html.prototype.inButtons = function (button) {
|
|
|
35 |
return (H5PIntegration.editor !== undefined && H5PIntegration.editor.wysiwygButtons !== undefined && H5PIntegration.editor.wysiwygButtons.indexOf(button) !== -1);
|
|
|
36 |
};
|
|
|
37 |
|
|
|
38 |
ns.Html.prototype.getCKEditorConfig = function () {
|
|
|
39 |
const config = {
|
|
|
40 |
updateSourceElementOnDestroy: true,
|
|
|
41 |
plugins: ['Essentials', 'Paragraph'],
|
|
|
42 |
alignment: { options: ["left", "center", "right"] },
|
|
|
43 |
toolbar: [],
|
|
|
44 |
};
|
|
|
45 |
|
|
|
46 |
// Basic styles
|
|
|
47 |
const basicstyles = [];
|
|
|
48 |
const basicstylesPlugins = [];
|
|
|
49 |
if (this.inTags("strong") || this.inTags("b")) {
|
|
|
50 |
basicstyles.push('bold');
|
|
|
51 |
basicstylesPlugins.push('Bold');
|
|
|
52 |
// Might make "strong" duplicated in the tag lists. Which doesn't really
|
|
|
53 |
// matter. Note: CKeditor will only make strongs.
|
|
|
54 |
this.tags.push("strong");
|
|
|
55 |
}
|
|
|
56 |
if (this.inTags("em") || this.inTags("i")) {
|
|
|
57 |
// Use <em> elements for italic text instead of CKE's default <i>
|
|
|
58 |
// Has to be a plugin to work
|
|
|
59 |
const ItalicAsEmPlugin = function (editor) {
|
|
|
60 |
editor.conversion.for('downcast').attributeToElement({
|
|
|
61 |
model: 'italic',
|
|
|
62 |
view: 'em',
|
|
|
63 |
converterPriority: 'high',
|
|
|
64 |
});
|
|
|
65 |
}
|
|
|
66 |
|
|
|
67 |
basicstyles.push('italic');
|
|
|
68 |
basicstylesPlugins.push('Italic', ItalicAsEmPlugin);
|
|
|
69 |
this.tags.push("i");
|
|
|
70 |
}
|
|
|
71 |
if (this.inTags("u")) {
|
|
|
72 |
basicstyles.push('underline');
|
|
|
73 |
basicstylesPlugins.push('Underline');
|
|
|
74 |
this.tags.push("u");
|
|
|
75 |
}
|
|
|
76 |
if (this.inTags("strike") || this.inTags("del") || this.inTags("s")) {
|
|
|
77 |
basicstyles.push('strikethrough');
|
|
|
78 |
basicstylesPlugins.push('Strikethrough');
|
|
|
79 |
// Might make "strike" or "del" or both duplicated in the tag lists. Which
|
|
|
80 |
// again doesn't really matter.
|
|
|
81 |
this.tags.push("strike");
|
|
|
82 |
this.tags.push("del");
|
|
|
83 |
this.tags.push("s");
|
|
|
84 |
}
|
|
|
85 |
if (this.inTags("sub")) {
|
|
|
86 |
basicstyles.push("subscript");
|
|
|
87 |
basicstylesPlugins.push('Subscript');
|
|
|
88 |
}
|
|
|
89 |
if (this.inTags("sup")) {
|
|
|
90 |
basicstyles.push("superscript");
|
|
|
91 |
basicstylesPlugins.push('Superscript');
|
|
|
92 |
}
|
|
|
93 |
if (basicstyles.length > 0) {
|
|
|
94 |
basicstyles.push('|', 'removeFormat');
|
|
|
95 |
basicstylesPlugins.push('RemoveFormat');
|
|
|
96 |
config.plugins.push(...basicstylesPlugins);
|
|
|
97 |
config.toolbar.push(...basicstyles);
|
|
|
98 |
}
|
|
|
99 |
|
|
|
100 |
// Alignment is added to all wysiwygs
|
|
|
101 |
config.plugins.push('Alignment');
|
|
|
102 |
config.toolbar.push('|', 'alignment');
|
|
|
103 |
|
|
|
104 |
// Paragraph styles
|
|
|
105 |
const paragraph = [];
|
|
|
106 |
const paragraphPlugins = [];
|
|
|
107 |
if (this.inTags("ul") || this.inTags("ol")) {
|
|
|
108 |
paragraphPlugins.push('List');
|
|
|
109 |
}
|
|
|
110 |
if (this.inTags("ul")) {
|
|
|
111 |
paragraph.push("bulletedList");
|
|
|
112 |
this.tags.push("li");
|
|
|
113 |
}
|
|
|
114 |
if (this.inTags("ol")) {
|
|
|
115 |
paragraph.push("numberedList");
|
|
|
116 |
this.tags.push("li");
|
|
|
117 |
}
|
|
|
118 |
if (this.inTags("blockquote")) {
|
|
|
119 |
paragraph.push("blockquote");
|
|
|
120 |
paragraphPlugins.push('BlockQuote');
|
|
|
121 |
}
|
|
|
122 |
if (this.inButtons('language')) {
|
|
|
123 |
this.tags.push('span');
|
|
|
124 |
paragraph.push('textPartLanguage');
|
|
|
125 |
paragraphPlugins.push('TextPartLanguage');
|
|
|
126 |
}
|
|
|
127 |
if (paragraph.length > 0) {
|
|
|
128 |
config.plugins.push(...paragraphPlugins);
|
|
|
129 |
config.toolbar.push(...paragraph);
|
|
|
130 |
}
|
|
|
131 |
|
|
|
132 |
// Links.
|
|
|
133 |
if (this.inTags("a")) {
|
|
|
134 |
const items = ["link"];
|
|
|
135 |
config.plugins.push('Link', 'AutoLink');
|
|
|
136 |
config.toolbar.push("|", ...items);
|
|
|
137 |
config.link = {
|
|
|
138 |
// Automatically add target="_blank" and rel="noopener noreferrer" to all external links.
|
|
|
139 |
addTargetToExternalLinks: true,
|
|
|
140 |
// Automatically add protocol if not present
|
|
|
141 |
defaultProtocol: 'http://',
|
|
|
142 |
}
|
|
|
143 |
}
|
|
|
144 |
|
|
|
145 |
// Inserts
|
|
|
146 |
const inserts = [];
|
|
|
147 |
const insertsPlugins = [];
|
|
|
148 |
if (this.inTags('img')) {
|
|
|
149 |
// TODO: Include toolbar functionality to insert and edit images
|
|
|
150 |
// For now, we just include the plugin to prevent data loss
|
|
|
151 |
insertsPlugins.push('Image');
|
|
|
152 |
}
|
|
|
153 |
if (this.inTags("hr")) {
|
|
|
154 |
inserts.push("horizontalLine");
|
|
|
155 |
insertsPlugins.push('HorizontalLine');
|
|
|
156 |
}
|
|
|
157 |
if (this.inTags('code')) {
|
|
|
158 |
if (this.inButtons('inlineCode')) {
|
|
|
159 |
inserts.push('code');
|
|
|
160 |
insertsPlugins.push('Code');
|
|
|
161 |
}
|
|
|
162 |
if (this.inTags('pre') && this.inButtons('codeSnippet')) {
|
|
|
163 |
inserts.push('codeBlock');
|
|
|
164 |
insertsPlugins.push('CodeBlock');
|
|
|
165 |
}
|
|
|
166 |
}
|
|
|
167 |
if (inserts.length > 0) {
|
|
|
168 |
config.toolbar.push("|", ...inserts);
|
|
|
169 |
}
|
|
|
170 |
if (insertsPlugins.length > 0) {
|
|
|
171 |
config.plugins.push(...insertsPlugins);
|
|
|
172 |
}
|
|
|
173 |
|
|
|
174 |
if (this.inTags("table")) {
|
|
|
175 |
config.toolbar.push("insertTable");
|
|
|
176 |
config.plugins.push(
|
|
|
177 |
'Table',
|
|
|
178 |
'TableToolbar',
|
|
|
179 |
'TableProperties',
|
|
|
180 |
'TableCellProperties',
|
|
|
181 |
'TableColumnResize',
|
|
|
182 |
'TableCaption'
|
|
|
183 |
);
|
|
|
184 |
config.table = {
|
|
|
185 |
contentToolbar: [
|
|
|
186 |
'toggleTableCaption',
|
|
|
187 |
'tableColumn',
|
|
|
188 |
'tableRow',
|
|
|
189 |
'mergeTableCells',
|
|
|
190 |
'tableProperties',
|
|
|
191 |
'tableCellProperties'
|
|
|
192 |
],
|
|
|
193 |
tableProperties: {
|
|
|
194 |
defaultProperties: {
|
|
|
195 |
borderStyle: 'underline',
|
|
|
196 |
borderWidth: '0.083em',
|
|
|
197 |
borderColor: '#494949',
|
|
|
198 |
padding: '0',
|
|
|
199 |
alignment: 'left'
|
|
|
200 |
}
|
|
|
201 |
},
|
|
|
202 |
tableCellProperties: {
|
|
|
203 |
defaultProperties: {
|
|
|
204 |
borderStyle: 'underline',
|
|
|
205 |
borderWidth: '0.083em',
|
|
|
206 |
borderColor: '#494949',
|
|
|
207 |
padding: '1px'
|
|
|
208 |
}
|
|
|
209 |
}
|
|
|
210 |
}
|
|
|
211 |
ns.$.merge(this.tags, ["tr", "td", "th", "colgroup", "col", "thead", "tbody", "tfoot", "figure", "figcaption"]);
|
|
|
212 |
}
|
|
|
213 |
|
|
|
214 |
// Add dropdown to toolbar if formatters in tags (h1, h2, etc).
|
|
|
215 |
const formats = [];
|
|
|
216 |
for (let index = 1; index < 7; index++) {
|
|
|
217 |
if (this.inTags('h' + index)) {
|
|
|
218 |
formats.push({ model: 'heading' + index, view: 'h' + index, title: 'Heading ' + index, class: 'ck-heading_heading' + index });
|
|
|
219 |
}
|
|
|
220 |
}
|
|
|
221 |
|
|
|
222 |
if (this.inTags('pre')) {
|
|
|
223 |
formats.push({ model: 'formatted', view: 'pre', title: 'Formatted', class: 'ck-heading_formatted' });
|
|
|
224 |
}
|
|
|
225 |
|
|
|
226 |
// if (this.inTags("address")) formats.push("address"); // TODO: potential data loss
|
|
|
227 |
if (formats.length > 0 || this.inTags('p') || this.inTags('div')) {
|
|
|
228 |
// If the formats are shown, always have a paragraph
|
|
|
229 |
formats.push({ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' });
|
|
|
230 |
this.tags.push("p");
|
|
|
231 |
this.field.enterMode = "p";
|
|
|
232 |
config.heading = {options: formats};
|
|
|
233 |
config.plugins.push('Heading');
|
|
|
234 |
config.toolbar.push('heading');
|
|
|
235 |
}
|
|
|
236 |
|
|
|
237 |
if (this.field.font !== undefined) {
|
|
|
238 |
// Create wrapper for text styling options
|
|
|
239 |
var styles = [];
|
|
|
240 |
var colors = [];
|
|
|
241 |
this.tags.push('span');
|
|
|
242 |
|
|
|
243 |
/**
|
|
|
244 |
* Help set specified values for property.
|
|
|
245 |
*
|
|
|
246 |
* @private
|
|
|
247 |
* @param {Array} values list
|
|
|
248 |
* @param {string} prop Property name
|
|
|
249 |
*/
|
|
|
250 |
const setValues = function (values, prop) {
|
|
|
251 |
options = [];
|
|
|
252 |
for (let i = 0; i < values.length; i++) {
|
|
|
253 |
options.push(values[i]);
|
|
|
254 |
}
|
|
|
255 |
config[prop] = { options: options };
|
|
|
256 |
};
|
|
|
257 |
|
|
|
258 |
// Font family chooser
|
|
|
259 |
if (this.field.font.family) {
|
|
|
260 |
styles.push('fontFamily');
|
|
|
261 |
config.plugins.push('FontFamily');
|
|
|
262 |
|
|
|
263 |
let fontFamilies = [
|
|
|
264 |
'default',
|
|
|
265 |
'Arial, Helvetica, sans-serif',
|
|
|
266 |
'Comic Sans MS, Cursive, sans-serif',
|
|
|
267 |
'Courier New, Courier, monospace',
|
|
|
268 |
'Georgia, serif',
|
|
|
269 |
'Lucida Sans Unicode, Lucida Grande, sans-serif',
|
|
|
270 |
'Tahoma, Geneva, sans-serif',
|
|
|
271 |
'Times New Roman, Times, serif',
|
|
|
272 |
'Trebuchet MS, Helvetica, sans-serif',
|
|
|
273 |
'Verdana, Geneva, sans-serif'
|
|
|
274 |
]
|
|
|
275 |
|
|
|
276 |
// If custom fonts are set, use those
|
|
|
277 |
if (this.field.font.family instanceof Array) {
|
|
|
278 |
fontFamilies = ['default', ...this.field.font.family.map(font => (
|
|
|
279 |
font.label + ', ' + font.css
|
|
|
280 |
))];
|
|
|
281 |
}
|
|
|
282 |
|
|
|
283 |
setValues(fontFamilies, 'fontFamily');
|
|
|
284 |
config.fontFamily.supportAllValues = true;
|
|
|
285 |
}
|
|
|
286 |
|
|
|
287 |
// Font size chooser
|
|
|
288 |
if (this.field.font.size) {
|
|
|
289 |
styles.push('fontSize');
|
|
|
290 |
config.plugins.push('FontSize');
|
|
|
291 |
|
|
|
292 |
let fontSizes = [];
|
|
|
293 |
const convertToEm = (percent) => parseFloat(percent) / 100 + 'em';
|
|
|
294 |
|
|
|
295 |
if (this.field.font.size instanceof Array) {
|
|
|
296 |
// Use specified sizes
|
|
|
297 |
fontSizes = this.field.font.size.map(size => ({
|
|
|
298 |
title: size.label,
|
|
|
299 |
model: convertToEm(size.css)
|
|
|
300 |
}));
|
|
|
301 |
} else {
|
|
|
302 |
// Standard font sizes that are available
|
|
|
303 |
fontSizes = [
|
|
|
304 |
'Default', '50%', '56.25%', '62.5%', '68.75%', '75%', '87.5%',
|
|
|
305 |
'100%', '112.5%', '125%', '137.5%', '150%', '162.5%', '175%',
|
|
|
306 |
'225%', '300%', '450%'
|
|
|
307 |
].map(percent => ({
|
|
|
308 |
title: percent,
|
|
|
309 |
model: percent === 'Default' ? '1em' : convertToEm(percent)
|
|
|
310 |
}));
|
|
|
311 |
}
|
|
|
312 |
|
|
|
313 |
setValues(fontSizes, 'fontSize');
|
|
|
314 |
}
|
|
|
315 |
|
|
|
316 |
/**
|
|
|
317 |
* Format an array of color objects for ckeditor
|
|
|
318 |
* @param {Array} values
|
|
|
319 |
* @returns {string}
|
|
|
320 |
*/
|
|
|
321 |
const getColors = function (values) {
|
|
|
322 |
const colors = [];
|
|
|
323 |
for (let i = 0; i < values.length; i++) {
|
|
|
324 |
const val = values[i];
|
|
|
325 |
if (val.label && val.css) {
|
|
|
326 |
// Check if valid color format
|
|
|
327 |
const css = val.css.match(/^(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)|hsla?\([0-9,.% ]+\)) *;?$/i);
|
|
|
328 |
|
|
|
329 |
// If invalid, skip
|
|
|
330 |
if (!css) {
|
|
|
331 |
continue;
|
|
|
332 |
}
|
|
|
333 |
|
|
|
334 |
colors.push({color: css[0], label: val.label});
|
|
|
335 |
}
|
|
|
336 |
}
|
|
|
337 |
return colors;
|
|
|
338 |
};
|
|
|
339 |
|
|
|
340 |
// Text color chooser
|
|
|
341 |
if (this.field.font.color) {
|
|
|
342 |
colors.push('fontColor');
|
|
|
343 |
config.plugins.push('FontColor');
|
|
|
344 |
|
|
|
345 |
if (this.field.font.color instanceof Array) {
|
|
|
346 |
config.fontColor = { colors: getColors(this.field.font.color) };
|
|
|
347 |
}
|
|
|
348 |
}
|
|
|
349 |
|
|
|
350 |
// Text background color chooser
|
|
|
351 |
if (this.field.font.background) {
|
|
|
352 |
colors.push('fontBackgroundColor');
|
|
|
353 |
config.plugins.push('FontBackgroundColor');
|
|
|
354 |
|
|
|
355 |
if (this.field.font.background instanceof Array) {
|
|
|
356 |
config.fontBackgroundColor = { colors: getColors(this.field.font.color) };
|
|
|
357 |
}
|
|
|
358 |
}
|
|
|
359 |
|
|
|
360 |
// Add the text styling options
|
|
|
361 |
if (styles.length) {
|
|
|
362 |
config.toolbar.push(...styles);
|
|
|
363 |
}
|
|
|
364 |
if (colors.length) {
|
|
|
365 |
config.toolbar.push(...colors);
|
|
|
366 |
}
|
|
|
367 |
}
|
|
|
368 |
|
|
|
369 |
if (this.field.enterMode === 'p') {
|
|
|
370 |
this.tags.push('p');
|
|
|
371 |
}
|
|
|
372 |
else {
|
|
|
373 |
this.tags.push('div');
|
|
|
374 |
|
|
|
375 |
// Without this, empty divs get deleted on init of cke
|
|
|
376 |
config.plugins.push('GeneralHtmlSupport');
|
|
|
377 |
config.htmlSupport = {
|
|
|
378 |
allow: [{
|
|
|
379 |
name: 'div',
|
|
|
380 |
attributes: true,
|
|
|
381 |
classes: true,
|
|
|
382 |
styles: true
|
|
|
383 |
}]
|
|
|
384 |
};
|
|
|
385 |
}
|
|
|
386 |
|
|
|
387 |
return config;
|
|
|
388 |
};
|
|
|
389 |
|
|
|
390 |
/**
|
|
|
391 |
* Append field to wrapper.
|
|
|
392 |
*
|
|
|
393 |
* @param {type} $wrapper
|
|
|
394 |
* @returns {undefined}
|
|
|
395 |
*/
|
|
|
396 |
ns.Html.prototype.appendTo = function ($wrapper) {
|
|
|
397 |
var that = this;
|
|
|
398 |
|
|
|
399 |
this.$item = ns.$(this.createHtml()).appendTo($wrapper);
|
|
|
400 |
this.$input = this.$item.children('.ckeditor');
|
|
|
401 |
this.$errors = this.$item.children('.h5p-errors');
|
|
|
402 |
|
|
|
403 |
ns.bindImportantDescriptionEvents(this, this.field.name, this.parent);
|
|
|
404 |
|
|
|
405 |
this.ckEditorConfig = this.getCKEditorConfig();
|
|
|
406 |
|
|
|
407 |
this.$input.focus(function () {
|
|
|
408 |
// Blur is not fired on destroy. Therefore we need to keep track of it!
|
|
|
409 |
var blurFired = false;
|
|
|
410 |
|
|
|
411 |
// Remove placeholder
|
|
|
412 |
that.$placeholder = that.$item.find('.h5peditor-ckeditor-placeholder').detach();
|
|
|
413 |
|
|
|
414 |
if (ns.Html.first) {
|
|
|
415 |
ClassicEditor.basePath = ns.basePath + '/ckeditor/';
|
|
|
416 |
ns.Html.first = false;
|
|
|
417 |
}
|
|
|
418 |
|
|
|
419 |
if (ns.Html.current === that) {
|
|
|
420 |
return;
|
|
|
421 |
}
|
|
|
422 |
// Remove existing CK instance.
|
|
|
423 |
ns.Html.removeWysiwyg();
|
|
|
424 |
|
|
|
425 |
that.inputWidth = that.$input[0].getBoundingClientRect().width;
|
|
|
426 |
ns.Html.current = that;
|
|
|
427 |
|
|
|
428 |
ClassicEditor
|
|
|
429 |
.create(this, that.ckEditorConfig)
|
|
|
430 |
.then(editor => {
|
|
|
431 |
const getEditorHeight = () => {
|
|
|
432 |
// Use the dimensions of the H5P iframe, not the editor iframe
|
|
|
433 |
const { innerHeight, innerWidth } = window.parent;
|
|
|
434 |
|
|
|
435 |
let ratio = 0.5;
|
|
|
436 |
|
|
|
437 |
switch (true) {
|
|
|
438 |
case innerHeight < 560:
|
|
|
439 |
ratio = 0.2
|
|
|
440 |
break;
|
|
|
441 |
case innerHeight < 768 && innerWidth < 576:
|
|
|
442 |
ratio = 0.25
|
|
|
443 |
break;
|
|
|
444 |
case innerHeight < 768:
|
|
|
445 |
ratio = 0.3
|
|
|
446 |
break;
|
|
|
447 |
case innerHeight < 1024 && innerWidth < 576:
|
|
|
448 |
ratio = 0.3;
|
|
|
449 |
break;
|
|
|
450 |
case innerHeight < 1024:
|
|
|
451 |
ratio = 0.45;
|
|
|
452 |
break;
|
|
|
453 |
default:
|
|
|
454 |
break;
|
|
|
455 |
}
|
|
|
456 |
|
|
|
457 |
return (ratio * innerHeight * 0.85) + 'px';
|
|
|
458 |
}
|
|
|
459 |
|
|
|
460 |
that.ckeditor = editor;
|
|
|
461 |
const editable = editor.ui.view.editable;
|
|
|
462 |
editorElement = editable.element;
|
|
|
463 |
editor.ui.view.element.style.maxWidth = that.inputWidth + 'px';
|
|
|
464 |
editorElement.style.maxHeight = getEditorHeight();
|
|
|
465 |
|
|
|
466 |
// Readjust toolbar's grouped item dropdown panel,
|
|
|
467 |
// since it can overflow the parent iframe element by using default positioning
|
|
|
468 |
const dropdownPanel = editor.ui.view.toolbar._behavior.groupedItemsDropdown;
|
|
|
469 |
dropdownPanel.panelPosition = 'auto';
|
|
|
470 |
|
|
|
471 |
// Disable sticky toolbar, since it has problem within iframes
|
|
|
472 |
editor.ui.view.stickyPanel.unbind('isActive');
|
|
|
473 |
editor.ui.view.stickyPanel.isActive = false;
|
|
|
474 |
|
|
|
475 |
// Remove overflow protection on startup
|
|
|
476 |
let initialData = editor.getData();
|
|
|
477 |
if (initialData.includes('table-overflow-protection')) {
|
|
|
478 |
initialData = initialData.replace(/<div class=\"table-overflow-protection\">.*<\/div>/, '');
|
|
|
479 |
editor.setData(initialData);
|
|
|
480 |
}
|
|
|
481 |
|
|
|
482 |
// Mimic old enter_mode behaviour if not specifically set to 'p'
|
|
|
483 |
if (that.field.enterMode !== 'p') {
|
|
|
484 |
// Use <div> elements instead of <p>
|
|
|
485 |
editor.conversion.for('downcast').elementToElement({
|
|
|
486 |
model: 'paragraph',
|
|
|
487 |
view: 'div',
|
|
|
488 |
converterPriority: 'high'
|
|
|
489 |
});
|
|
|
490 |
}
|
|
|
491 |
|
|
|
492 |
editor.ui.on('update', () => {
|
|
|
493 |
editorElement.style.maxHeight = getEditorHeight();
|
|
|
494 |
});
|
|
|
495 |
|
|
|
496 |
editor.editing.view.focus();
|
|
|
497 |
|
|
|
498 |
editor.on('focus', function () {
|
|
|
499 |
blurFired = false;
|
|
|
500 |
editorElement.style.maxHeight = getEditorHeight();
|
|
|
501 |
});
|
|
|
502 |
|
|
|
503 |
editor.once('destroy', function () {
|
|
|
504 |
// In some cases, the blur event is not fired. Need to be sure it is, so that
|
|
|
505 |
// validation and saving is done
|
|
|
506 |
if (!blurFired) {
|
|
|
507 |
blur();
|
|
|
508 |
}
|
|
|
509 |
|
|
|
510 |
// Display placeholder if:
|
|
|
511 |
// -- The value held by the field is empty AND
|
|
|
512 |
// -- The value shown in the UI is empty AND
|
|
|
513 |
// -- A placeholder is defined
|
|
|
514 |
const value = editor.getData();
|
|
|
515 |
if (that.$placeholder.length !== 0 && (value === undefined || value.length === 0) && (that.value === undefined || that.value.length === 0)) {
|
|
|
516 |
that.$placeholder.appendTo(that.$item.find('.ckeditor'));
|
|
|
517 |
}
|
|
|
518 |
|
|
|
519 |
// Since validate() is not always run,
|
|
|
520 |
// make sure tabe overflow protection is added always when editor is destroyed
|
|
|
521 |
if (value.includes('table') && !value.includes('table-overflow-protection')) {
|
|
|
522 |
that.value = value + '<div class="table-overflow-protection"></div>';
|
|
|
523 |
that.setValue(that.field, that.value);
|
|
|
524 |
that.$input.html(that.value).change();
|
|
|
525 |
}
|
|
|
526 |
});
|
|
|
527 |
|
|
|
528 |
var blur = function () {
|
|
|
529 |
blurFired = true;
|
|
|
530 |
|
|
|
531 |
// Do not validate if the field has been hidden.
|
|
|
532 |
if (that.$item.is(':visible')) {
|
|
|
533 |
that.validate();
|
|
|
534 |
}
|
|
|
535 |
};
|
|
|
536 |
|
|
|
537 |
editor.on('blur', blur);
|
|
|
538 |
})
|
|
|
539 |
.catch(error => {
|
|
|
540 |
throw new Error('Error loading CKEditor: ' + error);
|
|
|
541 |
});
|
|
|
542 |
});
|
|
|
543 |
|
|
|
544 |
// Always preload the first CKEditor field to avoid focus problems when the
|
|
|
545 |
// editor is opened inside an iframe and focus has to be set by a human made
|
|
|
546 |
// event (Safari).
|
|
|
547 |
// if (this.$item.is(':visible') && !ns.Html.firstLoad) {
|
|
|
548 |
// handleFocus();
|
|
|
549 |
// ns.Html.firstLoad = true;
|
|
|
550 |
// }
|
|
|
551 |
};
|
|
|
552 |
|
|
|
553 |
/**
|
|
|
554 |
* Create HTML for the HTML field.
|
|
|
555 |
*/
|
|
|
556 |
ns.Html.prototype.createHtml = function () {
|
|
|
557 |
const id = ns.getNextFieldId(this.field);
|
|
|
558 |
var input = '<div id="' + id + '"';
|
|
|
559 |
if (this.field.description !== undefined) {
|
|
|
560 |
input += ' aria-describedby="' + ns.getDescriptionId(id) + '"';
|
|
|
561 |
}
|
|
|
562 |
input += ' class="ckeditor" tabindex="0" contenteditable="true">';
|
|
|
563 |
if (this.value !== undefined) {
|
|
|
564 |
input += this.value;
|
|
|
565 |
}
|
|
|
566 |
else if (this.field.placeholder !== undefined) {
|
|
|
567 |
input += '<span class="h5peditor-ckeditor-placeholder">' + this.field.placeholder + '</span>';
|
|
|
568 |
}
|
|
|
569 |
// Add overflow protection if table
|
|
|
570 |
if (this.field.tags.includes('table') && !input.includes('table-overflow-protection')) {
|
|
|
571 |
input += '<div class="table-overflow-protection"></div>';
|
|
|
572 |
}
|
|
|
573 |
input += '</div>';
|
|
|
574 |
|
|
|
575 |
return ns.createFieldMarkup(this.field, ns.createImportantDescription(this.field.important) + input, id);
|
|
|
576 |
};
|
|
|
577 |
|
|
|
578 |
/**
|
|
|
579 |
* Validate the current text field.
|
|
|
580 |
*/
|
|
|
581 |
ns.Html.prototype.validate = function () {
|
|
|
582 |
var that = this;
|
|
|
583 |
|
|
|
584 |
if (that.$errors.children().length) {
|
|
|
585 |
that.$errors.empty();
|
|
|
586 |
this.$input.addClass('error');
|
|
|
587 |
}
|
|
|
588 |
|
|
|
589 |
// Get contents from editor
|
|
|
590 |
// If there are more than one ckeditor, getData() might be undefined when ckeditor is not
|
|
|
591 |
let value = ((this.ckeditor !== undefined && this.ckeditor.getData() !== undefined)
|
|
|
592 |
? this.ckeditor.getData()
|
|
|
593 |
: this.$input.html());
|
|
|
594 |
|
|
|
595 |
value = value
|
|
|
596 |
// Remove placeholder text if any:
|
|
|
597 |
.replace(/<span class="h5peditor-ckeditor-placeholder">.*<\/span>/, '')
|
|
|
598 |
// Workaround for Microsoft browsers that otherwise can produce non-emtpy fields causing trouble
|
|
|
599 |
.replace(/^<br>$/, '');
|
|
|
600 |
|
|
|
601 |
var $value = ns.$('<div>' + value + '</div>');
|
|
|
602 |
var textValue = $value.text();
|
|
|
603 |
|
|
|
604 |
// Check if we have any text at all.
|
|
|
605 |
if (!this.field.optional && !textValue.length) {
|
|
|
606 |
// We can accept empty text, if there's an image instead.
|
|
|
607 |
if (!(this.inTags("img") && $value.find('img').length > 0)) {
|
|
|
608 |
this.$errors.append(ns.createError(ns.t('core', 'requiredProperty', { ':property': ns.t('core', 'textField') })));
|
|
|
609 |
}
|
|
|
610 |
}
|
|
|
611 |
|
|
|
612 |
// Verify HTML tags. Removes tags not in allowed tags. Will replace with
|
|
|
613 |
// the tag's content. So if we get an unallowed container, the contents
|
|
|
614 |
// will remain, without the container.
|
|
|
615 |
$value.find('*').each(function () {
|
|
|
616 |
if (!that.inTags(this.tagName)) {
|
|
|
617 |
ns.$(this).replaceWith(ns.$(this).contents());
|
|
|
618 |
}
|
|
|
619 |
});
|
|
|
620 |
|
|
|
621 |
// Add overflow protection if chance of aligned tables
|
|
|
622 |
if(that.inTags('table') && !value.includes('table-overflow-protection')) {
|
|
|
623 |
this.$input.append('<div class="table-overflow-protection"></div>');
|
|
|
624 |
$value.append('<div class="table-overflow-protection"></div>');
|
|
|
625 |
}
|
|
|
626 |
|
|
|
627 |
value = $value.html();
|
|
|
628 |
|
|
|
629 |
// Display errors and bail if set.
|
|
|
630 |
if (that.$errors.children().length) {
|
|
|
631 |
return false;
|
|
|
632 |
}
|
|
|
633 |
else {
|
|
|
634 |
this.$input.removeClass('error');
|
|
|
635 |
}
|
|
|
636 |
|
|
|
637 |
this.value = value;
|
|
|
638 |
this.setValue(this.field, value);
|
|
|
639 |
this.$input.change(); // Trigger change event.
|
|
|
640 |
|
|
|
641 |
return value;
|
|
|
642 |
};
|
|
|
643 |
|
|
|
644 |
/**
|
|
|
645 |
* Destroy H5PEditor existing CK instance. If it exists.
|
|
|
646 |
*/
|
|
|
647 |
ns.Html.removeWysiwyg = function () {
|
|
|
648 |
if (ns.Html.current !== undefined) {
|
|
|
649 |
try {
|
|
|
650 |
ns.Html.current.ckeditor.destroy();
|
|
|
651 |
}
|
|
|
652 |
catch (e) {
|
|
|
653 |
// No-op, just stop error from propagating. This usually occurs if
|
|
|
654 |
// the CKEditor DOM has been removed together with other DOM data.
|
|
|
655 |
}
|
|
|
656 |
ns.Html.current = undefined;
|
|
|
657 |
}
|
|
|
658 |
};
|
|
|
659 |
|
|
|
660 |
/**
|
|
|
661 |
* Remove this item.
|
|
|
662 |
*/
|
|
|
663 |
ns.Html.prototype.remove = function () {
|
|
|
664 |
this.$item.remove();
|
|
|
665 |
};
|
|
|
666 |
|
|
|
667 |
/**
|
|
|
668 |
* When someone from the outside wants to set a value.
|
|
|
669 |
*
|
|
|
670 |
* @param {string} value
|
|
|
671 |
*/
|
|
|
672 |
ns.Html.prototype.forceValue = function (value) {
|
|
|
673 |
if (this.ckeditor === undefined) {
|
|
|
674 |
this.$input.html(value);
|
|
|
675 |
}
|
|
|
676 |
else {
|
|
|
677 |
this.ckeditor.setData(value);
|
|
|
678 |
}
|
|
|
679 |
this.validate();
|
|
|
680 |
};
|
|
|
681 |
|
|
|
682 |
ns.widgets.html = ns.Html;
|