@squiz/formatted-text-editor 1.69.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,17 @@
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
+
9
+ ## 1.70.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 0513957: `enableTableTool` prop added to the Editor to control the display of the table insertion tool until we are ready to release it
14
+
3
15
  ## 1.69.0
4
16
 
5
17
  ### Minor Changes
package/demo/App.tsx CHANGED
@@ -22,6 +22,7 @@ function App() {
22
22
  const [reconvertedDoc, setReconvertedDoc] = useState('');
23
23
  const [error, setError] = useState<unknown>(null);
24
24
  const [editable, setEditable] = useState(true);
25
+ const [enableTableTool, setEnableTableTool] = useState(true);
25
26
  const [border, setBorder] = useState(true);
26
27
 
27
28
  const handleEditorChange: RemirrorEventListener<Extension> = (parameter) => {
@@ -45,8 +46,12 @@ function App() {
45
46
  <div className="form-group">
46
47
  <label htmlFor="editable">Editable</label>&nbsp;
47
48
  <input id="editable" type="checkbox" onChange={() => setEditable(!editable)} checked={editable} />
49
+ </div>
50
+ <div className="form-group">
48
51
  <label htmlFor="border">Border</label>&nbsp;
49
52
  <input id="border" type="checkbox" onChange={() => setBorder(!border)} checked={border} />
53
+ </div>
54
+ <div className="form-group">
50
55
  <label htmlFor="showChildren">Show children</label>&nbsp;
51
56
  <input
52
57
  id="showChildren"
@@ -55,6 +60,15 @@ function App() {
55
60
  checked={showChildren}
56
61
  />
57
62
  </div>
63
+ <div className="form-group">
64
+ <label htmlFor="tableTool">Enable table tool</label>&nbsp;
65
+ <input
66
+ id="tableTool"
67
+ type="checkbox"
68
+ onChange={() => setEnableTableTool(!enableTableTool)}
69
+ checked={enableTableTool}
70
+ />
71
+ </div>
58
72
  </div>
59
73
  <h1>Editor</h1>
60
74
  <div className="page-section">
@@ -64,6 +78,7 @@ function App() {
64
78
  border={border}
65
79
  content={`<p>Hello <a href="https://www.google.com"><strong>Mr Bean</strong></a>, nice to <a href="https://www.google.com">meet you</a>.<img src="https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif" height="150" width="200"/></p>`}
66
80
  onChange={handleEditorChange}
81
+ enableTableTool={enableTableTool}
67
82
  >
68
83
  {showChildren && <ComponentHandlers />}
69
84
  </Editor>
@@ -10,6 +10,7 @@ type EditorProps = {
10
10
  isFocused?: boolean;
11
11
  label?: string;
12
12
  attributes?: Record<string, string>;
13
+ enableTableTool?: boolean;
13
14
  };
14
- declare const Editor: ({ content, className, border, editable, onChange, children, isFocused, attributes, }: EditorProps) => React.JSX.Element;
15
+ declare const Editor: ({ content, className, border, editable, onChange, children, isFocused, attributes, enableTableTool, }: EditorProps) => React.JSX.Element;
15
16
  export default Editor;
@@ -34,6 +34,7 @@ const EditorContext_1 = require("./EditorContext");
34
34
  const Extensions_1 = require("../Extensions/Extensions");
35
35
  const useFocus_1 = __importDefault(require("../hooks/useFocus"));
36
36
  const resource_browser_1 = require("@squiz/resource-browser");
37
+ const extension_react_tables_1 = require("@remirror/extension-react-tables");
37
38
  const WrappedEditor = () => {
38
39
  const preventImagePaste = (0, react_1.useCallback)((event) => {
39
40
  const { clipboardData } = event;
@@ -50,7 +51,7 @@ const WrappedEditor = () => {
50
51
  (0, react_2.useEditorEvent)('paste', preventImagePaste);
51
52
  return react_1.default.createElement(react_2.EditorComponent, null);
52
53
  };
53
- const Editor = ({ content, className, border = true, editable = true, onChange, children, isFocused, attributes, }) => {
54
+ const Editor = ({ content, className, border = true, editable = true, onChange, children, isFocused, attributes, enableTableTool = false, }) => {
54
55
  const { manager, state, setState } = (0, react_2.useRemirror)({
55
56
  extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext), (0, react_1.useContext)(resource_browser_1.ResourceBrowserContext)),
56
57
  content,
@@ -67,12 +68,19 @@ const Editor = ({ content, className, border = true, editable = true, onChange,
67
68
  if (isFocused) {
68
69
  manager.view.dom.focus();
69
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
+ });
70
77
  }, []);
71
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) },
72
- react_1.default.createElement(react_2.Remirror, { manager: manager, state: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor", attributes: attributes },
73
- editable && react_1.default.createElement(EditorToolbar_1.Toolbar, { isVisible: isVisible }),
79
+ react_1.default.createElement(react_2.Remirror, { manager: manager, initialContent: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor", attributes: attributes },
80
+ editable && react_1.default.createElement(EditorToolbar_1.Toolbar, { isVisible: isVisible, enableTableTool: enableTableTool }),
74
81
  children && react_1.default.createElement("div", { className: "squiz-fte-scope__editor__children" }, children),
75
82
  react_1.default.createElement(WrappedEditor, null),
83
+ enableTableTool && react_1.default.createElement(extension_react_tables_1.TableComponents, { enableTableCellMenu: false }),
76
84
  editable && isVisible && react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null))));
77
85
  };
78
86
  exports.default = Editor;
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  type ToolbarProps = {
3
3
  isVisible: boolean;
4
+ enableTableTool: boolean;
4
5
  };
5
- export declare const Toolbar: ({ isVisible }: ToolbarProps) => React.JSX.Element;
6
+ export declare const Toolbar: ({ isVisible, enableTableTool }: ToolbarProps) => React.JSX.Element;
6
7
  export {};
@@ -20,8 +20,9 @@ const RemoveLinkButton_1 = __importDefault(require("./Tools/Link/RemoveLinkButto
20
20
  const ClearFormattingButton_1 = __importDefault(require("./Tools/ClearFormatting/ClearFormattingButton"));
21
21
  const ListButtons_1 = __importDefault(require("./Tools/Lists/ListButtons"));
22
22
  const HorizontalLineButton_1 = __importDefault(require("./Tools/HorizontalLine/HorizontalLineButton"));
23
+ const TableButton_1 = __importDefault(require("./Tools/Table/TableButton"));
23
24
  const hooks_1 = require("../hooks");
24
- const Toolbar = ({ isVisible }) => {
25
+ const Toolbar = ({ isVisible, enableTableTool }) => {
25
26
  const extensionNames = (0, hooks_1.useExtensionNames)();
26
27
  return (react_1.default.createElement(react_components_1.Toolbar, { className: (0, clsx_1.default)('editor-toolbar header-toolbar', isVisible && 'show-toolbar'), role: "toolbar", tabIndex: 0 },
27
28
  react_1.default.createElement("div", { className: "editor-toolbar__tools" },
@@ -40,6 +41,7 @@ const Toolbar = ({ isVisible }) => {
40
41
  react_1.default.createElement(LinkButton_1.default, null),
41
42
  react_1.default.createElement(RemoveLinkButton_1.default, null))),
42
43
  extensionNames.image && react_1.default.createElement(ImageButton_1.default, null),
43
- extensionNames.clearFormatting && react_1.default.createElement(ClearFormattingButton_1.default, null))));
44
+ extensionNames.clearFormatting && react_1.default.createElement(ClearFormattingButton_1.default, null),
45
+ enableTableTool && extensionNames.table && react_1.default.createElement(TableButton_1.default, null))));
44
46
  };
45
47
  exports.Toolbar = Toolbar;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const TableButton: () => React.JSX.Element;
3
+ export default TableButton;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const react_1 = __importDefault(require("react"));
7
+ const react_2 = require("@remirror/react");
8
+ const react_components_1 = require("@remirror/react-components");
9
+ const Button_1 = __importDefault(require("../../../ui/Button/Button"));
10
+ const TableViewRounded_1 = __importDefault(require("@mui/icons-material/TableViewRounded"));
11
+ const TableButton = () => {
12
+ const { createTable } = (0, react_2.useCommands)();
13
+ const active = (0, react_2.useActive)();
14
+ const enabled = createTable.enabled();
15
+ const handleSelect = () => {
16
+ createTable({ rowsCount: 4, columnsCount: 3, withHeaderRow: false });
17
+ };
18
+ return (react_1.default.createElement(react_1.default.Fragment, null,
19
+ react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: active.table(), icon: react_1.default.createElement(TableViewRounded_1.default, null), label: "Insert table" }),
20
+ react_1.default.createElement(react_components_1.VerticalDivider, null)));
21
+ };
22
+ exports.default = TableButton;
@@ -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
  }
@@ -12,12 +12,16 @@ const CodeBlockExtension_1 = require("./CodeBlockExtension/CodeBlockExtension");
12
12
  const ClearFormattingExtension_1 = require("./ClearFormattingExtension/ClearFormattingExtension");
13
13
  const UnsupportedNodeExtension_1 = require("./UnsuportedExtension/UnsupportedNodeExtension");
14
14
  const FetchUrlExtension_1 = require("./FetchUrlExtension/FetchUrlExtension");
15
+ const extension_react_tables_1 = require("@remirror/extension-react-tables");
16
+ const extension_react_component_1 = require("@remirror/extension-react-component");
15
17
  var NodeName;
16
18
  (function (NodeName) {
17
19
  NodeName["Image"] = "image";
18
20
  NodeName["CodeBlock"] = "codeBlock";
19
21
  NodeName["AssetImage"] = "assetImage";
20
22
  NodeName["Text"] = "text";
23
+ NodeName["TableControllerCell"] = "tableControllerCell";
24
+ NodeName["tableCell"] = "tableCell";
21
25
  NodeName["hardBreak"] = "hardBreak";
22
26
  NodeName["Unsupported"] = "unsupportedNode";
23
27
  })(NodeName = exports.NodeName || (exports.NodeName = {}));
@@ -61,6 +65,8 @@ const createExtensions = (context, browserContext) => {
61
65
  fetchUrl: browserContext.onRequestResource,
62
66
  }),
63
67
  new extensions_1.TextExtension(),
68
+ new extension_react_tables_1.TableExtension(),
69
+ new extension_react_component_1.ReactComponentExtension(),
64
70
  ];
65
71
  };
66
72
  };
package/lib/index.css CHANGED
@@ -445,6 +445,9 @@
445
445
  .squiz-fte-scope .flex {
446
446
  display: flex !important;
447
447
  }
448
+ .squiz-fte-scope .table {
449
+ display: table !important;
450
+ }
448
451
  .squiz-fte-scope .grid {
449
452
  display: grid !important;
450
453
  }
@@ -799,6 +802,9 @@
799
802
  --tw-bg-opacity: 1;
800
803
  background-color: rgb(255 255 255 / var(--tw-bg-opacity));
801
804
  }
805
+ .squiz-fte-scope.squiz-fte-scope__editor .squiz-fte-scope__floating-popover {
806
+ z-index: 999;
807
+ }
802
808
  .squiz-fte-scope.squiz-fte-scope__editor:has(.squiz-fte-scope.squiz-fte-scope__editor__children) {
803
809
  min-height: 2rem;
804
810
  width: 100%;
@@ -825,6 +831,9 @@
825
831
  position: relative;
826
832
  display: block;
827
833
  }
834
+ .squiz-fte-scope.squiz-fte-scope__editor .remirror-editor-wrapper {
835
+ position: relative;
836
+ }
828
837
  .squiz-fte-scope.squiz-fte-scope__editor--bordered {
829
838
  border-width: 2px;
830
839
  border-style: solid;
@@ -865,6 +874,293 @@
865
874
  var(--tw-ring-shadow, 0 0 #0000),
866
875
  var(--tw-shadow);
867
876
  }
877
+ .squiz-fte-scope.squiz-fte-scope__editor .remirror-table {
878
+ width: 100%;
879
+ }
880
+ .squiz-fte-scope.squiz-fte-scope__editor .remirror-table-container {
881
+ margin: 2rem;
882
+ }
883
+ .squiz-fte-scope.squiz-fte-scope__editor .remirror-table tbody th,
884
+ .squiz-fte-scope.squiz-fte-scope__editor .remirror-table tbody td {
885
+ padding: 0.5rem;
886
+ }
887
+ .squiz-fte-scope.squiz-fte-scope__editor .remirror-table tbody p {
888
+ margin: 0;
889
+ }
890
+ .squiz-fte-scope :root {
891
+ --rmr-color-table-default-border: #e0e0e0;
892
+ --rmr-color-table-selected-cell: transparent;
893
+ --rmr-color-table-selected-border: #0774d2;
894
+ --rmr-color-table-selected-controller: #0774d2;
895
+ --rmr-hue-blue-7: #0774d2;
896
+ --rmr-color-table-mark: #f5f5f5;
897
+ --rmr-color-table-default-controller: #f7f7f7;
898
+ --rmr-color-table-predelete-cell: transparent;
899
+ --rmr-color-table-predelete-border: #d72321;
900
+ --rmr-color-table-predelete-controller: #d72321;
901
+ }
902
+ .squiz-fte-scope .remirror-positioner {
903
+ cursor: none;
904
+ min-height: 1px;
905
+ min-width: 1px;
906
+ pointer-events: none;
907
+ position: absolute;
908
+ -webkit-user-select: none;
909
+ -moz-user-select: none;
910
+ user-select: none;
911
+ z-index: -1;
912
+ }
913
+ .squiz-fte-scope .remirror-positioner-widget {
914
+ height: 0;
915
+ position: absolute;
916
+ width: 0;
917
+ }
918
+ .squiz-fte-scope .remirror-editor.ProseMirror .tableWrapper {
919
+ overflow-x: auto;
920
+ }
921
+ .squiz-fte-scope .remirror-editor.ProseMirror table {
922
+ border-collapse: collapse;
923
+ overflow: hidden;
924
+ table-layout: fixed;
925
+ width: 100%;
926
+ }
927
+ .squiz-fte-scope .remirror-editor.ProseMirror td,
928
+ .squiz-fte-scope .remirror-editor.ProseMirror th {
929
+ border-color: var(--rmr-color-table-default-border);
930
+ border-style: solid;
931
+ border-width: 1px;
932
+ box-sizing: border-box;
933
+ position: relative;
934
+ vertical-align: top;
935
+ }
936
+ .squiz-fte-scope .remirror-editor.ProseMirror .column-resize-handle {
937
+ background-color: var(--rmr-hue-blue-7);
938
+ bottom: 0;
939
+ pointer-events: none;
940
+ position: absolute;
941
+ right: -2px;
942
+ top: 0;
943
+ width: 4px;
944
+ z-index: 40;
945
+ }
946
+ .squiz-fte-scope .remirror-editor.ProseMirror.resize-cursor {
947
+ cursor: ew-resize;
948
+ cursor: col-resize;
949
+ }
950
+ .squiz-fte-scope .remirror-editor.ProseMirror td.selectedCell,
951
+ .squiz-fte-scope .remirror-editor.ProseMirror th.selectedCell {
952
+ background-color: var(--rmr-color-table-selected-cell);
953
+ border-color: var(--rmr-color-table-selected-border);
954
+ border-style: double;
955
+ }
956
+ .squiz-fte-scope .remirror-table-colgroup > col:first-of-type {
957
+ overflow: visible;
958
+ width: 13px;
959
+ }
960
+ .squiz-fte-scope .remirror-controllers-toggle {
961
+ visibility: hidden;
962
+ }
963
+ .squiz-fte-scope .remirror-table-show-controllers .remirror-controllers-toggle {
964
+ visibility: visible;
965
+ }
966
+ .squiz-fte-scope .remirror-table-insert-button {
967
+ background-color: #dcdcdc;
968
+ border-radius: 4px;
969
+ cursor: pointer;
970
+ height: 18px;
971
+ position: absolute;
972
+ transition: background-color 0.15s ease;
973
+ width: 18px;
974
+ z-index: 25;
975
+ }
976
+ .squiz-fte-scope .remirror-table-insert-button svg {
977
+ fill: #fff;
978
+ }
979
+ .squiz-fte-scope .remirror-table-insert-button:hover {
980
+ background-color: #136bda;
981
+ }
982
+ .squiz-fte-scope .remirror-table-insert-button:hover svg {
983
+ fill: #fff;
984
+ }
985
+ .squiz-fte-scope .remirror-table-delete-inner-button {
986
+ background-color: #cecece;
987
+ border: none;
988
+ border-radius: 4px;
989
+ cursor: pointer;
990
+ height: 18px;
991
+ padding: 0;
992
+ position: absolute;
993
+ transition: background-color 0.15s ease;
994
+ width: 18px;
995
+ z-index: 30;
996
+ }
997
+ .squiz-fte-scope .remirror-table-delete-inner-button:hover {
998
+ background-color: #ff7884;
999
+ }
1000
+ .squiz-fte-scope .remirror-table-delete-table-inner-button {
1001
+ left: calc(var(--remirror-table-delete-button-x) - 9px);
1002
+ top: calc(var(--remirror-table-delete-button-y) - 9px);
1003
+ }
1004
+ .squiz-fte-scope .remirror-table-delete-row-column-inner-button {
1005
+ left: calc(var(--remirror-table-delete-row-column-button-x) - 9px);
1006
+ top: calc(var(--remirror-table-delete-row-column-button-y) - 9px);
1007
+ }
1008
+ .squiz-fte-scope .remirror-table-with-controllers {
1009
+ height: 1px;
1010
+ margin-bottom: 40px;
1011
+ margin-top: 40px;
1012
+ }
1013
+ .squiz-fte-scope .ProseMirror table.remirror-table-with-controllers {
1014
+ overflow: visible;
1015
+ }
1016
+ .squiz-fte-scope .remirror-table-waitting-controllers {
1017
+ display: none;
1018
+ }
1019
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type {
1020
+ height: 12px;
1021
+ overflow: visible;
1022
+ }
1023
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:first-of-type {
1024
+ cursor: pointer;
1025
+ height: 12px;
1026
+ overflow: visible;
1027
+ padding: 0;
1028
+ position: relative;
1029
+ width: 12px;
1030
+ z-index: 15;
1031
+ }
1032
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:first-of-type div.remirror-table-controller-wrapper {
1033
+ align-items: flex-end;
1034
+ display: flex;
1035
+ height: 12px;
1036
+ justify-content: flex-end;
1037
+ overflow: visible;
1038
+ width: 12px;
1039
+ }
1040
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:first-of-type div.remirror-table-controller-trigger-area {
1041
+ display: none;
1042
+ flex: 1;
1043
+ position: relative;
1044
+ z-index: 10;
1045
+ }
1046
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:first-of-type div.remirror-table-controller-mark-row-corner {
1047
+ border-color: var(--rmr-color-table-mark);
1048
+ border-radius: 50%;
1049
+ border-style: solid;
1050
+ border-width: 2px;
1051
+ bottom: -2px;
1052
+ height: 0;
1053
+ left: -12px;
1054
+ position: absolute;
1055
+ width: 0;
1056
+ }
1057
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:first-of-type div.remirror-table-controller-mark-column-corner {
1058
+ border-color: var(--rmr-color-table-mark);
1059
+ border-radius: 50%;
1060
+ border-style: solid;
1061
+ border-width: 2px;
1062
+ height: 0;
1063
+ position: absolute;
1064
+ right: -2px;
1065
+ top: -12px;
1066
+ width: 0;
1067
+ }
1068
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:nth-of-type(n + 2) {
1069
+ cursor: pointer;
1070
+ height: 12px;
1071
+ overflow: visible;
1072
+ padding: 0;
1073
+ position: relative;
1074
+ z-index: 15;
1075
+ }
1076
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:nth-of-type(n + 2) div.remirror-table-controller-wrapper {
1077
+ align-items: flex-end;
1078
+ display: flex;
1079
+ flex-direction: row;
1080
+ height: 12px;
1081
+ justify-content: flex-end;
1082
+ overflow: visible;
1083
+ width: 100%;
1084
+ }
1085
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:nth-of-type(n + 2) div.remirror-table-controller-trigger-area {
1086
+ flex: 1;
1087
+ height: 36px;
1088
+ position: relative;
1089
+ z-index: 10;
1090
+ }
1091
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:nth-of-type(n + 2) div.remirror-table-controller-mark-row-corner {
1092
+ display: none;
1093
+ }
1094
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:first-of-type th:nth-of-type(n + 2) div.remirror-table-controller-mark-column-corner {
1095
+ border-color: var(--rmr-color-table-mark);
1096
+ border-radius: 50%;
1097
+ border-style: solid;
1098
+ border-width: 2px;
1099
+ height: 0;
1100
+ position: absolute;
1101
+ right: -2px;
1102
+ top: -12px;
1103
+ width: 0;
1104
+ }
1105
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th {
1106
+ cursor: pointer;
1107
+ overflow: visible;
1108
+ padding: 0;
1109
+ position: relative;
1110
+ width: 12px;
1111
+ z-index: 15;
1112
+ }
1113
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th div.remirror-table-controller-wrapper {
1114
+ align-items: flex-end;
1115
+ display: flex;
1116
+ flex-direction: column;
1117
+ height: 100%;
1118
+ justify-content: flex-end;
1119
+ overflow: visible;
1120
+ width: 12px;
1121
+ }
1122
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th div.remirror-table-controller-trigger-area {
1123
+ flex: 1;
1124
+ position: relative;
1125
+ width: 36px;
1126
+ z-index: 10;
1127
+ }
1128
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th div.remirror-table-controller-mark-row-corner {
1129
+ border-color: var(--rmr-color-table-mark);
1130
+ border-radius: 50%;
1131
+ border-style: solid;
1132
+ border-width: 2px;
1133
+ bottom: -2px;
1134
+ height: 0;
1135
+ left: -12px;
1136
+ position: absolute;
1137
+ width: 0;
1138
+ }
1139
+ .squiz-fte-scope .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th div.remirror-table-controller-mark-column-corner {
1140
+ display: none;
1141
+ }
1142
+ .squiz-fte-scope .remirror-table-tbody-with-controllers th.remirror-table-controller {
1143
+ background-color: var(--rmr-color-table-default-controller);
1144
+ }
1145
+ .squiz-fte-scope .remirror-table-tbody-with-controllers th.selectedCell.remirror-table-controller {
1146
+ background-color: var(--rmr-color-table-selected-controller);
1147
+ }
1148
+ .squiz-fte-scope .remirror-table-show-predelete td.selectedCell,
1149
+ .squiz-fte-scope .remirror-table-show-predelete th.selectedCell.remirror-table-controller {
1150
+ background-color: var(--rmr-color-table-predelete-cell) !important;
1151
+ border-color: var(--rmr-color-table-predelete-border) !important;
1152
+ }
1153
+ .squiz-fte-scope .remirror-table-show-predelete th.selectedCell.remirror-table-controller {
1154
+ background-color: var(--rmr-color-table-predelete-controller) !important;
1155
+ }
1156
+ .squiz-fte-scope .remirror-table-show-predelete.remirror-table-preselect-all td,
1157
+ .squiz-fte-scope .remirror-table-show-predelete.remirror-table-preselect-all th.remirror-table-controller {
1158
+ background-color: var(--rmr-color-table-predelete-cell) !important;
1159
+ border-color: var(--rmr-color-table-predelete-border) !important;
1160
+ }
1161
+ .squiz-fte-scope .remirror-table-show-predelete.remirror-table-preselect-all th.remirror-table-controller {
1162
+ background-color: var(--rmr-color-table-predelete-controller) !important;
1163
+ }
868
1164
  .squiz-fte-scope .editor-toolbar,
869
1165
  .squiz-fte-scope__floating-popover {
870
1166
  border-top-left-radius: 8px;
@@ -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.69.0",
3
+ "version": "1.71.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "private": false,
@@ -22,6 +22,7 @@
22
22
  "dependencies": {
23
23
  "@headlessui/react": "1.7.11",
24
24
  "@mui/icons-material": "5.11.16",
25
+ "@remirror/extension-react-tables": "^2.2.19",
25
26
  "@remirror/react": "2.0.25",
26
27
  "@squiz/dx-json-schema-lib": "^1.65.1",
27
28
  "@squiz/resource-browser": "^1.66.3",