@squiz/formatted-text-editor 1.70.0 → 1.71.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.
- package/CHANGELOG.md +6 -0
- package/lib/Editor/Editor.js +7 -1
- package/lib/Extensions/Extensions.d.ts +2 -0
- package/lib/Extensions/Extensions.js +2 -0
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +19 -4
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +23 -0
- package/package.json +1 -1
- package/src/Editor/Editor.tsx +8 -1
- package/src/Extensions/Extensions.ts +2 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +457 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +24 -5
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +210 -0
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +23 -0
package/CHANGELOG.md
CHANGED
package/lib/Editor/Editor.js
CHANGED
@@ -68,9 +68,15 @@ const Editor = ({ content, className, border = true, editable = true, onChange,
|
|
68
68
|
if (isFocused) {
|
69
69
|
manager.view.dom.focus();
|
70
70
|
}
|
71
|
+
// TODO: May want to come back to this and see if there's a better solution
|
72
|
+
// We have to add a type button attribute to the delete buttons so they don't cause a submit by accident.
|
73
|
+
const tableDeleteButtons = document.querySelectorAll('.remirror-table-delete-inner-button');
|
74
|
+
tableDeleteButtons.forEach((button) => {
|
75
|
+
button.setAttribute('type', 'button');
|
76
|
+
});
|
71
77
|
}, []);
|
72
78
|
return (react_1.default.createElement("div", { ref: wrapperRef, onBlur: handleBlur, onFocusCapture: handleFocus, className: (0, clsx_1.default)('squiz-fte-scope', 'squiz-fte-scope__editor', !editable && 'squiz-fte-scope__editor--is-disabled', border && 'squiz-fte-scope__editor--bordered', className) },
|
73
|
-
react_1.default.createElement(react_2.Remirror, { manager: manager,
|
79
|
+
react_1.default.createElement(react_2.Remirror, { manager: manager, initialContent: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor", attributes: attributes },
|
74
80
|
editable && react_1.default.createElement(EditorToolbar_1.Toolbar, { isVisible: isVisible, enableTableTool: enableTableTool }),
|
75
81
|
children && react_1.default.createElement("div", { className: "squiz-fte-scope__editor__children" }, children),
|
76
82
|
react_1.default.createElement(WrappedEditor, null),
|
@@ -20,6 +20,8 @@ var NodeName;
|
|
20
20
|
NodeName["CodeBlock"] = "codeBlock";
|
21
21
|
NodeName["AssetImage"] = "assetImage";
|
22
22
|
NodeName["Text"] = "text";
|
23
|
+
NodeName["TableControllerCell"] = "tableControllerCell";
|
24
|
+
NodeName["tableCell"] = "tableCell";
|
23
25
|
NodeName["hardBreak"] = "hardBreak";
|
24
26
|
NodeName["Unsupported"] = "unsupportedNode";
|
25
27
|
})(NodeName = exports.NodeName || (exports.NodeName = {}));
|
@@ -55,7 +55,7 @@ const resolveFontOptions = (node) => {
|
|
55
55
|
});
|
56
56
|
return fontOptions;
|
57
57
|
};
|
58
|
-
const transformAttributes = (attributes) => {
|
58
|
+
const transformAttributes = (attributes, nodeType) => {
|
59
59
|
const transformed = {};
|
60
60
|
Object.keys(attributes).forEach((key) => {
|
61
61
|
// Component service requires attributes to be a string, cast as needed.
|
@@ -63,6 +63,17 @@ const transformAttributes = (attributes) => {
|
|
63
63
|
transformed[key] = String(attributes[key]);
|
64
64
|
}
|
65
65
|
});
|
66
|
+
// We assign an attribute here for table controller cells as we need to differentiate
|
67
|
+
// between them and regular table headers (th)
|
68
|
+
if (nodeType === Extensions_1.NodeName.TableControllerCell) {
|
69
|
+
transformed[nodeType] = 'true';
|
70
|
+
}
|
71
|
+
// Another check here for more specific attributes for tables (column widths)
|
72
|
+
if (nodeType === Extensions_1.NodeName.TableControllerCell || nodeType === Extensions_1.NodeName.tableCell) {
|
73
|
+
if (Array.isArray(attributes.colwidth) && attributes.colwidth[0]) {
|
74
|
+
transformed.colwidth = String(attributes.colwidth[0]);
|
75
|
+
}
|
76
|
+
}
|
66
77
|
return transformed;
|
67
78
|
};
|
68
79
|
const transformFragment = (fragment) => {
|
@@ -75,9 +86,13 @@ const transformFragment = (fragment) => {
|
|
75
86
|
const transformNode = (node) => {
|
76
87
|
const formattingOptions = (0, undefinedIfEmpty_1.undefinedIfEmpty)(resolveFormattingOptions(node));
|
77
88
|
const font = (0, undefinedIfEmpty_1.undefinedIfEmpty)(resolveFontOptions(node));
|
78
|
-
|
79
|
-
|
80
|
-
|
89
|
+
let attributes;
|
90
|
+
if (node.type.name === Extensions_1.NodeName.Image ||
|
91
|
+
node.type.name === Extensions_1.NodeName.CodeBlock ||
|
92
|
+
node.type.name === Extensions_1.NodeName.TableControllerCell ||
|
93
|
+
node.type.name === Extensions_1.NodeName.tableCell) {
|
94
|
+
attributes = transformAttributes(node.attrs, node.type.name);
|
95
|
+
}
|
81
96
|
let transformedNode = { type: 'text', value: node.text || '' };
|
82
97
|
// Squiz "text" nodes can't have formatting/font options but Remirror "text" nodes can.
|
83
98
|
// If the node has formatting options wrap in a tag.
|
@@ -23,6 +23,10 @@ const getNodeType = (node) => {
|
|
23
23
|
li: 'listItem',
|
24
24
|
ul: 'bulletList',
|
25
25
|
hr: 'horizontalRule',
|
26
|
+
table: 'table',
|
27
|
+
tr: 'tableRow',
|
28
|
+
th: 'tableHeaderCell',
|
29
|
+
td: 'tableCell',
|
26
30
|
a: Extensions_1.NodeName.Text,
|
27
31
|
em: Extensions_1.NodeName.Text,
|
28
32
|
span: Extensions_1.NodeName.Text,
|
@@ -34,6 +38,12 @@ const getNodeType = (node) => {
|
|
34
38
|
return typeMap[node.type];
|
35
39
|
}
|
36
40
|
if (node.type === 'tag' && tagMap[node.tag]) {
|
41
|
+
// This is a specific check case for tables as there are some <th> tags which need to be returned
|
42
|
+
// as table controller cells.
|
43
|
+
if (node.attributes?.tableControllerCell) {
|
44
|
+
return 'tableControllerCell';
|
45
|
+
}
|
46
|
+
// Return regular tag for everything else
|
37
47
|
return tagMap[node.tag];
|
38
48
|
}
|
39
49
|
// Unsupported node type
|
@@ -42,6 +52,19 @@ const getNodeType = (node) => {
|
|
42
52
|
: `Unsupported node type provided: ${node.type}`);
|
43
53
|
};
|
44
54
|
const getNodeAttributes = (node) => {
|
55
|
+
if (node.type === 'tag' && node.tag === 'table') {
|
56
|
+
return {
|
57
|
+
isControllersInjected: true,
|
58
|
+
};
|
59
|
+
}
|
60
|
+
if (node.type === 'tag' && (node.tag === 'th' || node.tag === 'td')) {
|
61
|
+
return {
|
62
|
+
colspan: parseInt(node.attributes?.colspan ?? '1'),
|
63
|
+
rowspan: parseInt(node.attributes?.rowspan ?? '1'),
|
64
|
+
colwidth: node.attributes?.colwidth ? [parseInt(node.attributes.colwidth)] : null,
|
65
|
+
background: null,
|
66
|
+
};
|
67
|
+
}
|
45
68
|
if (node.type === 'tag' && node.tag === 'img') {
|
46
69
|
return {
|
47
70
|
alt: node.attributes?.alt,
|
package/package.json
CHANGED
package/src/Editor/Editor.tsx
CHANGED
@@ -74,6 +74,13 @@ const Editor = ({
|
|
74
74
|
if (isFocused) {
|
75
75
|
manager.view.dom.focus();
|
76
76
|
}
|
77
|
+
|
78
|
+
// TODO: May want to come back to this and see if there's a better solution
|
79
|
+
// We have to add a type button attribute to the delete buttons so they don't cause a submit by accident.
|
80
|
+
const tableDeleteButtons = document.querySelectorAll('.remirror-table-delete-inner-button');
|
81
|
+
tableDeleteButtons.forEach((button) => {
|
82
|
+
button.setAttribute('type', 'button');
|
83
|
+
});
|
77
84
|
}, []);
|
78
85
|
|
79
86
|
return (
|
@@ -91,7 +98,7 @@ const Editor = ({
|
|
91
98
|
>
|
92
99
|
<Remirror
|
93
100
|
manager={manager}
|
94
|
-
|
101
|
+
initialContent={state}
|
95
102
|
editable={editable}
|
96
103
|
onChange={handleChange}
|
97
104
|
placeholder="Write something"
|
@@ -225,6 +225,463 @@ describe('remirrorNodeToSquizNode', () => {
|
|
225
225
|
expect(result).toEqual(expected);
|
226
226
|
});
|
227
227
|
|
228
|
+
it('should handle tables', async () => {
|
229
|
+
const content: RemirrorJSON = {
|
230
|
+
type: 'doc',
|
231
|
+
content: [
|
232
|
+
{
|
233
|
+
type: 'table',
|
234
|
+
attrs: {
|
235
|
+
isControllersInjected: true,
|
236
|
+
insertButtonAttrs: null,
|
237
|
+
},
|
238
|
+
content: [
|
239
|
+
{
|
240
|
+
type: 'tableRow',
|
241
|
+
content: [
|
242
|
+
{
|
243
|
+
type: 'tableControllerCell',
|
244
|
+
attrs: {
|
245
|
+
colspan: 1,
|
246
|
+
rowspan: 1,
|
247
|
+
colwidth: null,
|
248
|
+
background: null,
|
249
|
+
},
|
250
|
+
},
|
251
|
+
{
|
252
|
+
type: 'tableControllerCell',
|
253
|
+
attrs: {
|
254
|
+
colspan: 1,
|
255
|
+
rowspan: 1,
|
256
|
+
colwidth: [100],
|
257
|
+
background: null,
|
258
|
+
},
|
259
|
+
},
|
260
|
+
{
|
261
|
+
type: 'tableControllerCell',
|
262
|
+
attrs: {
|
263
|
+
colspan: 1,
|
264
|
+
rowspan: 1,
|
265
|
+
colwidth: null,
|
266
|
+
background: null,
|
267
|
+
},
|
268
|
+
},
|
269
|
+
{
|
270
|
+
type: 'tableControllerCell',
|
271
|
+
attrs: {
|
272
|
+
colspan: 1,
|
273
|
+
rowspan: 1,
|
274
|
+
colwidth: null,
|
275
|
+
background: null,
|
276
|
+
},
|
277
|
+
},
|
278
|
+
],
|
279
|
+
},
|
280
|
+
{
|
281
|
+
type: 'tableRow',
|
282
|
+
content: [
|
283
|
+
{
|
284
|
+
type: 'tableControllerCell',
|
285
|
+
attrs: {
|
286
|
+
colspan: 1,
|
287
|
+
rowspan: 1,
|
288
|
+
colwidth: null,
|
289
|
+
background: null,
|
290
|
+
},
|
291
|
+
},
|
292
|
+
{
|
293
|
+
type: 'tableCell',
|
294
|
+
attrs: {
|
295
|
+
colspan: 1,
|
296
|
+
rowspan: 1,
|
297
|
+
colwidth: [100],
|
298
|
+
background: null,
|
299
|
+
},
|
300
|
+
content: [
|
301
|
+
{
|
302
|
+
type: 'paragraph',
|
303
|
+
attrs: {
|
304
|
+
nodeIndent: null,
|
305
|
+
nodeTextAlignment: '',
|
306
|
+
nodeLineHeight: null,
|
307
|
+
style: '',
|
308
|
+
},
|
309
|
+
content: [
|
310
|
+
{
|
311
|
+
type: 'text',
|
312
|
+
text: 'Col1Row1',
|
313
|
+
},
|
314
|
+
],
|
315
|
+
},
|
316
|
+
],
|
317
|
+
},
|
318
|
+
{
|
319
|
+
type: 'tableCell',
|
320
|
+
attrs: {
|
321
|
+
colspan: 1,
|
322
|
+
rowspan: 1,
|
323
|
+
colwidth: null,
|
324
|
+
background: null,
|
325
|
+
},
|
326
|
+
content: [
|
327
|
+
{
|
328
|
+
type: 'paragraph',
|
329
|
+
attrs: {
|
330
|
+
nodeIndent: null,
|
331
|
+
nodeTextAlignment: '',
|
332
|
+
nodeLineHeight: null,
|
333
|
+
style: '',
|
334
|
+
},
|
335
|
+
content: [
|
336
|
+
{
|
337
|
+
type: 'text',
|
338
|
+
text: 'Col2Row1',
|
339
|
+
},
|
340
|
+
],
|
341
|
+
},
|
342
|
+
],
|
343
|
+
},
|
344
|
+
{
|
345
|
+
type: 'tableCell',
|
346
|
+
attrs: {
|
347
|
+
colspan: 1,
|
348
|
+
rowspan: 1,
|
349
|
+
colwidth: null,
|
350
|
+
background: null,
|
351
|
+
},
|
352
|
+
content: [
|
353
|
+
{
|
354
|
+
type: 'paragraph',
|
355
|
+
attrs: {
|
356
|
+
nodeIndent: null,
|
357
|
+
nodeTextAlignment: '',
|
358
|
+
nodeLineHeight: null,
|
359
|
+
style: '',
|
360
|
+
},
|
361
|
+
content: [
|
362
|
+
{
|
363
|
+
type: 'text',
|
364
|
+
text: 'Col3Row1',
|
365
|
+
},
|
366
|
+
],
|
367
|
+
},
|
368
|
+
],
|
369
|
+
},
|
370
|
+
],
|
371
|
+
},
|
372
|
+
{
|
373
|
+
type: 'tableRow',
|
374
|
+
content: [
|
375
|
+
{
|
376
|
+
type: 'tableControllerCell',
|
377
|
+
attrs: {
|
378
|
+
colspan: 1,
|
379
|
+
rowspan: 1,
|
380
|
+
colwidth: null,
|
381
|
+
background: null,
|
382
|
+
},
|
383
|
+
},
|
384
|
+
{
|
385
|
+
type: 'tableCell',
|
386
|
+
attrs: {
|
387
|
+
colspan: 1,
|
388
|
+
rowspan: 1,
|
389
|
+
colwidth: [100],
|
390
|
+
background: null,
|
391
|
+
},
|
392
|
+
content: [
|
393
|
+
{
|
394
|
+
type: 'paragraph',
|
395
|
+
attrs: {
|
396
|
+
nodeIndent: null,
|
397
|
+
nodeTextAlignment: '',
|
398
|
+
nodeLineHeight: null,
|
399
|
+
style: '',
|
400
|
+
},
|
401
|
+
content: [
|
402
|
+
{
|
403
|
+
type: 'text',
|
404
|
+
text: 'Col1Row2',
|
405
|
+
},
|
406
|
+
],
|
407
|
+
},
|
408
|
+
],
|
409
|
+
},
|
410
|
+
{
|
411
|
+
type: 'tableCell',
|
412
|
+
attrs: {
|
413
|
+
colspan: 1,
|
414
|
+
rowspan: 1,
|
415
|
+
colwidth: null,
|
416
|
+
background: null,
|
417
|
+
},
|
418
|
+
content: [
|
419
|
+
{
|
420
|
+
type: 'paragraph',
|
421
|
+
attrs: {
|
422
|
+
nodeIndent: null,
|
423
|
+
nodeTextAlignment: '',
|
424
|
+
nodeLineHeight: null,
|
425
|
+
style: '',
|
426
|
+
},
|
427
|
+
content: [
|
428
|
+
{
|
429
|
+
type: 'text',
|
430
|
+
text: 'Col2Row2',
|
431
|
+
},
|
432
|
+
],
|
433
|
+
},
|
434
|
+
],
|
435
|
+
},
|
436
|
+
{
|
437
|
+
type: 'tableCell',
|
438
|
+
attrs: {
|
439
|
+
colspan: 1,
|
440
|
+
rowspan: 1,
|
441
|
+
colwidth: null,
|
442
|
+
background: null,
|
443
|
+
},
|
444
|
+
content: [
|
445
|
+
{
|
446
|
+
type: 'paragraph',
|
447
|
+
attrs: {
|
448
|
+
nodeIndent: null,
|
449
|
+
nodeTextAlignment: '',
|
450
|
+
nodeLineHeight: null,
|
451
|
+
style: '',
|
452
|
+
},
|
453
|
+
content: [
|
454
|
+
{
|
455
|
+
type: 'text',
|
456
|
+
text: 'Col3Row2',
|
457
|
+
},
|
458
|
+
],
|
459
|
+
},
|
460
|
+
],
|
461
|
+
},
|
462
|
+
],
|
463
|
+
},
|
464
|
+
],
|
465
|
+
},
|
466
|
+
],
|
467
|
+
};
|
468
|
+
|
469
|
+
const { editor } = await renderWithEditor(null, { content });
|
470
|
+
|
471
|
+
const expected: FormattedText = [
|
472
|
+
{
|
473
|
+
type: 'tag',
|
474
|
+
tag: 'table',
|
475
|
+
children: [
|
476
|
+
{
|
477
|
+
type: 'tag',
|
478
|
+
tag: 'tr',
|
479
|
+
children: [
|
480
|
+
{
|
481
|
+
type: 'tag',
|
482
|
+
tag: 'th',
|
483
|
+
children: [],
|
484
|
+
attributes: {
|
485
|
+
colspan: '1',
|
486
|
+
rowspan: '1',
|
487
|
+
tableControllerCell: 'true',
|
488
|
+
},
|
489
|
+
},
|
490
|
+
{
|
491
|
+
type: 'tag',
|
492
|
+
tag: 'th',
|
493
|
+
children: [],
|
494
|
+
attributes: {
|
495
|
+
colspan: '1',
|
496
|
+
rowspan: '1',
|
497
|
+
tableControllerCell: 'true',
|
498
|
+
colwidth: '100',
|
499
|
+
},
|
500
|
+
},
|
501
|
+
{
|
502
|
+
type: 'tag',
|
503
|
+
tag: 'th',
|
504
|
+
children: [],
|
505
|
+
attributes: {
|
506
|
+
colspan: '1',
|
507
|
+
rowspan: '1',
|
508
|
+
tableControllerCell: 'true',
|
509
|
+
},
|
510
|
+
},
|
511
|
+
{
|
512
|
+
type: 'tag',
|
513
|
+
tag: 'th',
|
514
|
+
children: [],
|
515
|
+
attributes: {
|
516
|
+
colspan: '1',
|
517
|
+
rowspan: '1',
|
518
|
+
tableControllerCell: 'true',
|
519
|
+
},
|
520
|
+
},
|
521
|
+
],
|
522
|
+
},
|
523
|
+
{
|
524
|
+
type: 'tag',
|
525
|
+
tag: 'tr',
|
526
|
+
children: [
|
527
|
+
{
|
528
|
+
type: 'tag',
|
529
|
+
tag: 'th',
|
530
|
+
children: [],
|
531
|
+
attributes: {
|
532
|
+
colspan: '1',
|
533
|
+
rowspan: '1',
|
534
|
+
tableControllerCell: 'true',
|
535
|
+
},
|
536
|
+
},
|
537
|
+
{
|
538
|
+
type: 'tag',
|
539
|
+
tag: 'td',
|
540
|
+
children: [
|
541
|
+
{
|
542
|
+
type: 'tag',
|
543
|
+
tag: 'p',
|
544
|
+
children: [
|
545
|
+
{
|
546
|
+
type: 'text',
|
547
|
+
value: 'Col1Row1',
|
548
|
+
},
|
549
|
+
],
|
550
|
+
},
|
551
|
+
],
|
552
|
+
attributes: {
|
553
|
+
colspan: '1',
|
554
|
+
rowspan: '1',
|
555
|
+
colwidth: '100',
|
556
|
+
},
|
557
|
+
},
|
558
|
+
{
|
559
|
+
type: 'tag',
|
560
|
+
tag: 'td',
|
561
|
+
children: [
|
562
|
+
{
|
563
|
+
type: 'tag',
|
564
|
+
tag: 'p',
|
565
|
+
children: [
|
566
|
+
{
|
567
|
+
type: 'text',
|
568
|
+
value: 'Col2Row1',
|
569
|
+
},
|
570
|
+
],
|
571
|
+
},
|
572
|
+
],
|
573
|
+
attributes: {
|
574
|
+
colspan: '1',
|
575
|
+
rowspan: '1',
|
576
|
+
},
|
577
|
+
},
|
578
|
+
{
|
579
|
+
type: 'tag',
|
580
|
+
tag: 'td',
|
581
|
+
children: [
|
582
|
+
{
|
583
|
+
type: 'tag',
|
584
|
+
tag: 'p',
|
585
|
+
children: [
|
586
|
+
{
|
587
|
+
type: 'text',
|
588
|
+
value: 'Col3Row1',
|
589
|
+
},
|
590
|
+
],
|
591
|
+
},
|
592
|
+
],
|
593
|
+
attributes: {
|
594
|
+
colspan: '1',
|
595
|
+
rowspan: '1',
|
596
|
+
},
|
597
|
+
},
|
598
|
+
],
|
599
|
+
},
|
600
|
+
{
|
601
|
+
type: 'tag',
|
602
|
+
tag: 'tr',
|
603
|
+
children: [
|
604
|
+
{
|
605
|
+
type: 'tag',
|
606
|
+
tag: 'th',
|
607
|
+
children: [],
|
608
|
+
attributes: {
|
609
|
+
colspan: '1',
|
610
|
+
rowspan: '1',
|
611
|
+
tableControllerCell: 'true',
|
612
|
+
},
|
613
|
+
},
|
614
|
+
{
|
615
|
+
type: 'tag',
|
616
|
+
tag: 'td',
|
617
|
+
children: [
|
618
|
+
{
|
619
|
+
type: 'tag',
|
620
|
+
tag: 'p',
|
621
|
+
children: [
|
622
|
+
{
|
623
|
+
type: 'text',
|
624
|
+
value: 'Col1Row2',
|
625
|
+
},
|
626
|
+
],
|
627
|
+
},
|
628
|
+
],
|
629
|
+
attributes: {
|
630
|
+
colspan: '1',
|
631
|
+
rowspan: '1',
|
632
|
+
colwidth: '100',
|
633
|
+
},
|
634
|
+
},
|
635
|
+
{
|
636
|
+
type: 'tag',
|
637
|
+
tag: 'td',
|
638
|
+
children: [
|
639
|
+
{
|
640
|
+
type: 'tag',
|
641
|
+
tag: 'p',
|
642
|
+
children: [
|
643
|
+
{
|
644
|
+
type: 'text',
|
645
|
+
value: 'Col2Row2',
|
646
|
+
},
|
647
|
+
],
|
648
|
+
},
|
649
|
+
],
|
650
|
+
attributes: {
|
651
|
+
colspan: '1',
|
652
|
+
rowspan: '1',
|
653
|
+
},
|
654
|
+
},
|
655
|
+
{
|
656
|
+
type: 'tag',
|
657
|
+
tag: 'td',
|
658
|
+
children: [
|
659
|
+
{
|
660
|
+
type: 'tag',
|
661
|
+
tag: 'p',
|
662
|
+
children: [
|
663
|
+
{
|
664
|
+
type: 'text',
|
665
|
+
value: 'Col3Row2',
|
666
|
+
},
|
667
|
+
],
|
668
|
+
},
|
669
|
+
],
|
670
|
+
attributes: {
|
671
|
+
colspan: '1',
|
672
|
+
rowspan: '1',
|
673
|
+
},
|
674
|
+
},
|
675
|
+
],
|
676
|
+
},
|
677
|
+
],
|
678
|
+
},
|
679
|
+
];
|
680
|
+
|
681
|
+
const result = remirrorNodeToSquizNode(editor.doc);
|
682
|
+
expect(result).toEqual(expected);
|
683
|
+
});
|
684
|
+
|
228
685
|
it.each([1, 2, 3, 4, 5, 6])('should handle heading %s formatting', async (level) => {
|
229
686
|
const content: RemirrorJSON = {
|
230
687
|
type: 'doc',
|
@@ -80,7 +80,7 @@ const resolveFontOptions = (node: ProsemirrorNode): FormattedNodeFontProperties
|
|
80
80
|
return fontOptions;
|
81
81
|
};
|
82
82
|
|
83
|
-
const transformAttributes = (attributes: Attrs): Record<string, string> => {
|
83
|
+
const transformAttributes = (attributes: Attrs, nodeType?: string): Record<string, string> => {
|
84
84
|
const transformed: Record<string, string> = {};
|
85
85
|
|
86
86
|
Object.keys(attributes).forEach((key) => {
|
@@ -90,6 +90,19 @@ const transformAttributes = (attributes: Attrs): Record<string, string> => {
|
|
90
90
|
}
|
91
91
|
});
|
92
92
|
|
93
|
+
// We assign an attribute here for table controller cells as we need to differentiate
|
94
|
+
// between them and regular table headers (th)
|
95
|
+
if (nodeType === NodeName.TableControllerCell) {
|
96
|
+
transformed[nodeType] = 'true';
|
97
|
+
}
|
98
|
+
|
99
|
+
// Another check here for more specific attributes for tables (column widths)
|
100
|
+
if (nodeType === NodeName.TableControllerCell || nodeType === NodeName.tableCell) {
|
101
|
+
if (Array.isArray(attributes.colwidth) && attributes.colwidth[0]) {
|
102
|
+
transformed.colwidth = String(attributes.colwidth[0]);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
93
106
|
return transformed;
|
94
107
|
};
|
95
108
|
|
@@ -106,10 +119,16 @@ const transformFragment = (fragment: Fragment): FormattedText => {
|
|
106
119
|
const transformNode = (node: ProsemirrorNode): FormattedNode => {
|
107
120
|
const formattingOptions = undefinedIfEmpty(resolveFormattingOptions(node));
|
108
121
|
const font = undefinedIfEmpty(resolveFontOptions(node));
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
122
|
+
let attributes;
|
123
|
+
|
124
|
+
if (
|
125
|
+
node.type.name === NodeName.Image ||
|
126
|
+
node.type.name === NodeName.CodeBlock ||
|
127
|
+
node.type.name === NodeName.TableControllerCell ||
|
128
|
+
node.type.name === NodeName.tableCell
|
129
|
+
) {
|
130
|
+
attributes = transformAttributes(node.attrs, node.type.name);
|
131
|
+
}
|
113
132
|
|
114
133
|
let transformedNode: FormattedNode = { type: 'text', value: node.text || '' };
|
115
134
|
|
@@ -665,4 +665,214 @@ describe('squizNodeToRemirrorNode', () => {
|
|
665
665
|
const result = squizNodeToRemirrorNode(squizComponentJSON);
|
666
666
|
expect(result).toEqual(expected);
|
667
667
|
});
|
668
|
+
|
669
|
+
it('should handle tables', () => {
|
670
|
+
const squizJSON: FormattedText = [
|
671
|
+
{
|
672
|
+
type: 'tag',
|
673
|
+
tag: 'table',
|
674
|
+
children: [
|
675
|
+
{
|
676
|
+
type: 'tag',
|
677
|
+
tag: 'tr',
|
678
|
+
children: [
|
679
|
+
{
|
680
|
+
type: 'tag',
|
681
|
+
tag: 'th',
|
682
|
+
children: [],
|
683
|
+
attributes: {
|
684
|
+
colspan: '1',
|
685
|
+
rowspan: '1',
|
686
|
+
tableControllerCell: 'true',
|
687
|
+
},
|
688
|
+
},
|
689
|
+
{
|
690
|
+
type: 'tag',
|
691
|
+
tag: 'th',
|
692
|
+
children: [],
|
693
|
+
attributes: {
|
694
|
+
colspan: '1',
|
695
|
+
rowspan: '1',
|
696
|
+
tableControllerCell: 'true',
|
697
|
+
colwidth: '366',
|
698
|
+
},
|
699
|
+
},
|
700
|
+
{
|
701
|
+
type: 'tag',
|
702
|
+
tag: 'th',
|
703
|
+
children: [],
|
704
|
+
attributes: {
|
705
|
+
colspan: '1',
|
706
|
+
rowspan: '1',
|
707
|
+
tableControllerCell: 'true',
|
708
|
+
},
|
709
|
+
},
|
710
|
+
],
|
711
|
+
},
|
712
|
+
{
|
713
|
+
type: 'tag',
|
714
|
+
tag: 'tr',
|
715
|
+
children: [
|
716
|
+
{
|
717
|
+
type: 'tag',
|
718
|
+
tag: 'th',
|
719
|
+
children: [],
|
720
|
+
attributes: {
|
721
|
+
colspan: '1',
|
722
|
+
rowspan: '1',
|
723
|
+
tableControllerCell: 'true',
|
724
|
+
},
|
725
|
+
},
|
726
|
+
{
|
727
|
+
type: 'tag',
|
728
|
+
tag: 'td',
|
729
|
+
children: [
|
730
|
+
{
|
731
|
+
type: 'tag',
|
732
|
+
tag: 'p',
|
733
|
+
children: [],
|
734
|
+
},
|
735
|
+
],
|
736
|
+
attributes: {
|
737
|
+
colspan: '1',
|
738
|
+
rowspan: '1',
|
739
|
+
colwidth: '366',
|
740
|
+
},
|
741
|
+
},
|
742
|
+
{
|
743
|
+
type: 'tag',
|
744
|
+
tag: 'td',
|
745
|
+
children: [
|
746
|
+
{
|
747
|
+
type: 'tag',
|
748
|
+
tag: 'p',
|
749
|
+
children: [],
|
750
|
+
},
|
751
|
+
],
|
752
|
+
attributes: {
|
753
|
+
colspan: '1',
|
754
|
+
rowspan: '1',
|
755
|
+
},
|
756
|
+
},
|
757
|
+
],
|
758
|
+
},
|
759
|
+
],
|
760
|
+
},
|
761
|
+
];
|
762
|
+
|
763
|
+
const expected: RemirrorJSON = {
|
764
|
+
type: 'doc',
|
765
|
+
content: [
|
766
|
+
{
|
767
|
+
type: 'table',
|
768
|
+
attrs: {
|
769
|
+
isControllersInjected: true,
|
770
|
+
},
|
771
|
+
content: [
|
772
|
+
{
|
773
|
+
type: 'tableRow',
|
774
|
+
attrs: {
|
775
|
+
nodeIndent: null,
|
776
|
+
nodeLineHeight: null,
|
777
|
+
nodeTextAlignment: null,
|
778
|
+
style: '',
|
779
|
+
},
|
780
|
+
content: [
|
781
|
+
{
|
782
|
+
type: 'tableControllerCell',
|
783
|
+
attrs: {
|
784
|
+
colspan: 1,
|
785
|
+
rowspan: 1,
|
786
|
+
colwidth: null,
|
787
|
+
background: null,
|
788
|
+
},
|
789
|
+
},
|
790
|
+
{
|
791
|
+
type: 'tableControllerCell',
|
792
|
+
attrs: {
|
793
|
+
colspan: 1,
|
794
|
+
rowspan: 1,
|
795
|
+
colwidth: [366],
|
796
|
+
background: null,
|
797
|
+
},
|
798
|
+
},
|
799
|
+
{
|
800
|
+
type: 'tableControllerCell',
|
801
|
+
attrs: {
|
802
|
+
colspan: 1,
|
803
|
+
rowspan: 1,
|
804
|
+
colwidth: null,
|
805
|
+
background: null,
|
806
|
+
},
|
807
|
+
},
|
808
|
+
],
|
809
|
+
},
|
810
|
+
{
|
811
|
+
type: 'tableRow',
|
812
|
+
attrs: {
|
813
|
+
nodeIndent: null,
|
814
|
+
nodeLineHeight: null,
|
815
|
+
nodeTextAlignment: null,
|
816
|
+
style: '',
|
817
|
+
},
|
818
|
+
content: [
|
819
|
+
{
|
820
|
+
type: 'tableControllerCell',
|
821
|
+
attrs: {
|
822
|
+
colspan: 1,
|
823
|
+
rowspan: 1,
|
824
|
+
colwidth: null,
|
825
|
+
background: null,
|
826
|
+
},
|
827
|
+
},
|
828
|
+
{
|
829
|
+
type: 'tableCell',
|
830
|
+
attrs: {
|
831
|
+
colspan: 1,
|
832
|
+
rowspan: 1,
|
833
|
+
colwidth: [366],
|
834
|
+
background: null,
|
835
|
+
},
|
836
|
+
content: [
|
837
|
+
{
|
838
|
+
type: 'paragraph',
|
839
|
+
attrs: {
|
840
|
+
nodeIndent: null,
|
841
|
+
nodeTextAlignment: null,
|
842
|
+
nodeLineHeight: null,
|
843
|
+
style: '',
|
844
|
+
},
|
845
|
+
},
|
846
|
+
],
|
847
|
+
},
|
848
|
+
{
|
849
|
+
type: 'tableCell',
|
850
|
+
attrs: {
|
851
|
+
colspan: 1,
|
852
|
+
rowspan: 1,
|
853
|
+
colwidth: null,
|
854
|
+
background: null,
|
855
|
+
},
|
856
|
+
content: [
|
857
|
+
{
|
858
|
+
type: 'paragraph',
|
859
|
+
attrs: {
|
860
|
+
nodeIndent: null,
|
861
|
+
nodeTextAlignment: null,
|
862
|
+
nodeLineHeight: null,
|
863
|
+
style: '',
|
864
|
+
},
|
865
|
+
},
|
866
|
+
],
|
867
|
+
},
|
868
|
+
],
|
869
|
+
},
|
870
|
+
],
|
871
|
+
},
|
872
|
+
],
|
873
|
+
};
|
874
|
+
|
875
|
+
const result = squizNodeToRemirrorNode(squizJSON);
|
876
|
+
expect(result).toEqual(expected);
|
877
|
+
});
|
668
878
|
});
|
@@ -28,6 +28,10 @@ const getNodeType = (node: FormattedNodes): string => {
|
|
28
28
|
li: 'listItem',
|
29
29
|
ul: 'bulletList',
|
30
30
|
hr: 'horizontalRule',
|
31
|
+
table: 'table',
|
32
|
+
tr: 'tableRow',
|
33
|
+
th: 'tableHeaderCell',
|
34
|
+
td: 'tableCell',
|
31
35
|
a: NodeName.Text,
|
32
36
|
em: NodeName.Text,
|
33
37
|
span: NodeName.Text,
|
@@ -41,6 +45,12 @@ const getNodeType = (node: FormattedNodes): string => {
|
|
41
45
|
}
|
42
46
|
|
43
47
|
if (node.type === 'tag' && tagMap[node.tag]) {
|
48
|
+
// This is a specific check case for tables as there are some <th> tags which need to be returned
|
49
|
+
// as table controller cells.
|
50
|
+
if (node.attributes?.tableControllerCell) {
|
51
|
+
return 'tableControllerCell';
|
52
|
+
}
|
53
|
+
// Return regular tag for everything else
|
44
54
|
return tagMap[node.tag];
|
45
55
|
}
|
46
56
|
|
@@ -53,6 +63,19 @@ const getNodeType = (node: FormattedNodes): string => {
|
|
53
63
|
};
|
54
64
|
|
55
65
|
const getNodeAttributes = (node: FormattedNodes): Attrs => {
|
66
|
+
if (node.type === 'tag' && node.tag === 'table') {
|
67
|
+
return {
|
68
|
+
isControllersInjected: true,
|
69
|
+
};
|
70
|
+
}
|
71
|
+
if (node.type === 'tag' && (node.tag === 'th' || node.tag === 'td')) {
|
72
|
+
return {
|
73
|
+
colspan: parseInt(node.attributes?.colspan ?? '1'),
|
74
|
+
rowspan: parseInt(node.attributes?.rowspan ?? '1'),
|
75
|
+
colwidth: node.attributes?.colwidth ? [parseInt(node.attributes.colwidth)] : null,
|
76
|
+
background: null,
|
77
|
+
};
|
78
|
+
}
|
56
79
|
if (node.type === 'tag' && node.tag === 'img') {
|
57
80
|
return {
|
58
81
|
alt: node.attributes?.alt,
|