@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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.71.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 882fb82: Added table transormation support between Squiz & Remirror models (allowing saving of tables)
8
+
3
9
  ## 1.70.0
4
10
 
5
11
  ### Minor Changes
@@ -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, state: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor", attributes: attributes },
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),
@@ -6,6 +6,8 @@ export declare enum NodeName {
6
6
  CodeBlock = "codeBlock",
7
7
  AssetImage = "assetImage",
8
8
  Text = "text",
9
+ TableControllerCell = "tableControllerCell",
10
+ tableCell = "tableCell",
9
11
  hardBreak = "hardBreak",
10
12
  Unsupported = "unsupportedNode"
11
13
  }
@@ -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
- const attributes = node.type.name === Extensions_1.NodeName.Image || node.type.name === Extensions_1.NodeName.CodeBlock
79
- ? transformAttributes(node.attrs)
80
- : undefined;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.70.0",
3
+ "version": "1.71.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "private": false,
@@ -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
- state={state}
101
+ initialContent={state}
95
102
  editable={editable}
96
103
  onChange={handleChange}
97
104
  placeholder="Write something"
@@ -36,6 +36,8 @@ export enum NodeName {
36
36
  CodeBlock = 'codeBlock',
37
37
  AssetImage = 'assetImage',
38
38
  Text = 'text',
39
+ TableControllerCell = 'tableControllerCell',
40
+ tableCell = 'tableCell',
39
41
  hardBreak = 'hardBreak',
40
42
  Unsupported = 'unsupportedNode',
41
43
  }
@@ -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
- const attributes =
110
- node.type.name === NodeName.Image || node.type.name === NodeName.CodeBlock
111
- ? transformAttributes(node.attrs)
112
- : undefined;
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,