@squiz/formatted-text-editor 1.68.1 → 1.70.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.70.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0513957: `enableTableTool` prop added to the Editor to control the display of the table insertion tool until we are ready to release it
8
+
9
+ ## 1.69.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 6b8b464: Added support for SHIFT + ENTER line breaks to FTE
14
+
3
15
  ## 1.68.1
4
16
 
5
17
  ### Patch 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,
@@ -70,9 +71,10 @@ const Editor = ({ content, className, border = true, editable = true, onChange,
70
71
  }, []);
71
72
  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
73
  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 }),
74
+ editable && react_1.default.createElement(EditorToolbar_1.Toolbar, { isVisible: isVisible, enableTableTool: enableTableTool }),
74
75
  children && react_1.default.createElement("div", { className: "squiz-fte-scope__editor__children" }, children),
75
76
  react_1.default.createElement(WrappedEditor, null),
77
+ enableTableTool && react_1.default.createElement(extension_react_tables_1.TableComponents, { enableTableCellMenu: false }),
76
78
  editable && isVisible && react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null))));
77
79
  };
78
80
  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,7 @@ export declare enum NodeName {
6
6
  CodeBlock = "codeBlock",
7
7
  AssetImage = "assetImage",
8
8
  Text = "text",
9
+ hardBreak = "hardBreak",
9
10
  Unsupported = "unsupportedNode"
10
11
  }
11
12
  export declare enum MarkName {
@@ -12,12 +12,15 @@ 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["hardBreak"] = "hardBreak";
21
24
  NodeName["Unsupported"] = "unsupportedNode";
22
25
  })(NodeName = exports.NodeName || (exports.NodeName = {}));
23
26
  var MarkName;
@@ -35,6 +38,7 @@ const createExtensions = (context, browserContext) => {
35
38
  new extensions_1.ItalicExtension(),
36
39
  new extensions_1.NodeFormattingExtension({ indents: [] }),
37
40
  new extensions_1.ParagraphExtension(),
41
+ new extensions_1.HardBreakExtension(),
38
42
  new PreformattedExtension_1.PreformattedExtension(),
39
43
  new CodeBlockExtension_1.ExtendedCodeBlockExtension({ defaultWrap: true }),
40
44
  new extensions_1.UnderlineExtension(),
@@ -59,6 +63,8 @@ const createExtensions = (context, browserContext) => {
59
63
  fetchUrl: browserContext.onRequestResource,
60
64
  }),
61
65
  new extensions_1.TextExtension(),
66
+ new extension_react_tables_1.TableExtension(),
67
+ new extension_react_component_1.ReactComponentExtension(),
62
68
  ];
63
69
  };
64
70
  };
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;
@@ -28,6 +28,7 @@ const getNodeType = (node) => {
28
28
  span: Extensions_1.NodeName.Text,
29
29
  strong: Extensions_1.NodeName.Text,
30
30
  code: Extensions_1.NodeName.CodeBlock,
31
+ br: Extensions_1.NodeName.hardBreak,
31
32
  };
32
33
  if (typeMap[node.type]) {
33
34
  return typeMap[node.type];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.68.1",
3
+ "version": "1.70.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",
@@ -233,6 +233,29 @@ describe('Formatted text editor', () => {
233
233
  expect(baseElement.querySelectorAll('div.remirror-editor p')).toHaveLength(1);
234
234
  });
235
235
 
236
+ it('Applies line break within text content when SHIFT+ENTER is used', async () => {
237
+ const { editor, getJsonContent } = await renderWithEditor(null, {
238
+ content: 'Some nonsense content here',
239
+ });
240
+
241
+ await act(() => editor.jumpTo('end'));
242
+ await act(() => editor.press('Shift-Enter'));
243
+
244
+ expect(getJsonContent()).toEqual({
245
+ type: 'paragraph',
246
+ attrs: expect.any(Object),
247
+ content: [
248
+ {
249
+ type: 'text',
250
+ text: 'Some nonsense content here',
251
+ },
252
+ {
253
+ type: 'hardBreak',
254
+ },
255
+ ],
256
+ });
257
+ });
258
+
236
259
  it('should allow text to be pasted into the editor', async () => {
237
260
  const { editor, getJsonContent } = await renderWithEditor(<ImageButton />, {
238
261
  content: 'Some nonsense content here',
@@ -8,6 +8,7 @@ import { EditorContext } from './EditorContext';
8
8
  import { createExtensions } from '../Extensions/Extensions';
9
9
  import useFocus from '../hooks/useFocus';
10
10
  import { ResourceBrowserContext } from '@squiz/resource-browser';
11
+ import { TableComponents } from '@remirror/extension-react-tables';
11
12
 
12
13
  type EditorProps = {
13
14
  className?: string;
@@ -19,6 +20,7 @@ type EditorProps = {
19
20
  isFocused?: boolean;
20
21
  label?: string;
21
22
  attributes?: Record<string, string>;
23
+ enableTableTool?: boolean;
22
24
  };
23
25
 
24
26
  const WrappedEditor = () => {
@@ -51,6 +53,7 @@ const Editor = ({
51
53
  children,
52
54
  isFocused,
53
55
  attributes,
56
+ enableTableTool = false,
54
57
  }: EditorProps) => {
55
58
  const { manager, state, setState } = useRemirror({
56
59
  extensions: createExtensions(useContext(EditorContext), useContext(ResourceBrowserContext)),
@@ -95,9 +98,10 @@ const Editor = ({
95
98
  label="Text editor"
96
99
  attributes={attributes}
97
100
  >
98
- {editable && <Toolbar isVisible={isVisible} />}
101
+ {editable && <Toolbar isVisible={isVisible} enableTableTool={enableTableTool} />}
99
102
  {children && <div className="squiz-fte-scope__editor__children">{children}</div>}
100
103
  <WrappedEditor />
104
+ {enableTableTool && <TableComponents enableTableCellMenu={false} />}
101
105
  {editable && isVisible && <FloatingToolbar />}
102
106
  </Remirror>
103
107
  </div>
@@ -3,6 +3,10 @@
3
3
  font-family: 'Open Sans' !important;
4
4
  @apply bg-white rounded border-gray-300;
5
5
 
6
+ .squiz-fte-scope__floating-popover {
7
+ z-index: 999; // ensure table styles don't hide this menu
8
+ }
9
+
6
10
  &:has(&__children) {
7
11
  /* The children replace the space taken up by top padding of the editor when present. */
8
12
  @apply w-full min-h-[2rem];
@@ -27,6 +31,10 @@
27
31
  /* Make sure content aligned with "text-align: justify" is justified */
28
32
  @apply block;
29
33
  }
34
+
35
+ &-wrapper {
36
+ position: relative;
37
+ }
30
38
  }
31
39
 
32
40
  &--bordered {
@@ -62,4 +70,361 @@
62
70
  &:has(.show-toolbar) {
63
71
  @apply shadow-md border-0;
64
72
  }
73
+
74
+ // Tables
75
+ .remirror-table {
76
+ &-container {
77
+ @apply m-8;
78
+ }
79
+
80
+ width: 100%;
81
+
82
+ tbody {
83
+ th,
84
+ td {
85
+ @apply p-2;
86
+ }
87
+
88
+ p {
89
+ margin: 0; // everything is by default a paragraph, inside the table that does not make sense so remove the margin
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ // TODO: work through this nonsense and see which bits are necessary for functionality
96
+ :root {
97
+ --rmr-color-table-default-border: #e0e0e0;
98
+ --rmr-color-table-selected-cell: transparent;
99
+ --rmr-color-table-selected-border: #0774d2;
100
+ --rmr-color-table-selected-controller: #0774d2;
101
+ --rmr-hue-blue-7: #0774d2;
102
+ --rmr-color-table-mark: #f5f5f5;
103
+ --rmr-color-table-default-controller: #f7f7f7;
104
+ --rmr-color-table-predelete-cell: transparent;
105
+ --rmr-color-table-predelete-border: #d72321;
106
+ --rmr-color-table-predelete-controller: #d72321;
107
+ }
108
+
109
+ //remirror styles
110
+ .remirror-positioner {
111
+ cursor: none;
112
+ min-height: 1px;
113
+ min-width: 1px;
114
+ pointer-events: none;
115
+ position: absolute;
116
+ -webkit-user-select: none;
117
+ -moz-user-select: none;
118
+ -ms-user-select: none;
119
+ user-select: none;
120
+ z-index: -1;
121
+ }
122
+
123
+ .remirror-positioner-widget {
124
+ height: 0;
125
+ position: absolute;
126
+ width: 0;
127
+ }
128
+
129
+ .remirror-editor.ProseMirror .tableWrapper {
130
+ overflow-x: auto;
131
+ }
132
+
133
+ .remirror-editor.ProseMirror table {
134
+ border-collapse: collapse;
135
+ overflow: hidden;
136
+ table-layout: fixed;
137
+ width: 100%;
138
+ }
139
+
140
+ .remirror-editor.ProseMirror td,
141
+ .remirror-editor.ProseMirror th {
142
+ border-color: var(--rmr-color-table-default-border);
143
+ border-style: solid;
144
+ border-width: 1px;
145
+ box-sizing: border-box;
146
+ position: relative;
147
+ vertical-align: top;
148
+ }
149
+
150
+ .remirror-editor.ProseMirror .column-resize-handle {
151
+ background-color: var(--rmr-hue-blue-7);
152
+ bottom: 0;
153
+ pointer-events: none;
154
+ position: absolute;
155
+ right: -2px;
156
+ top: 0;
157
+ width: 4px;
158
+ z-index: 40;
159
+ }
160
+
161
+ .remirror-editor.ProseMirror.resize-cursor {
162
+ cursor: ew-resize;
163
+ cursor: col-resize;
164
+ }
165
+
166
+ .remirror-editor.ProseMirror td.selectedCell,
167
+ .remirror-editor.ProseMirror th.selectedCell {
168
+ background-color: var(--rmr-color-table-selected-cell);
169
+ border-color: var(--rmr-color-table-selected-border);
170
+ border-style: double;
171
+ }
172
+
173
+ .remirror-table-colgroup > col:first-of-type {
174
+ overflow: visible;
175
+ width: 13px;
176
+ }
177
+
178
+ .remirror-controllers-toggle {
179
+ visibility: hidden;
180
+ }
181
+
182
+ .remirror-table-show-controllers .remirror-controllers-toggle {
183
+ visibility: visible;
184
+ }
185
+
186
+ .remirror-table-insert-button {
187
+ background-color: #dcdcdc;
188
+ border-radius: 4px;
189
+ cursor: pointer;
190
+ height: 18px;
191
+ position: absolute;
192
+ transition: background-color 0.15s ease;
193
+ width: 18px;
194
+ z-index: 25;
195
+ }
196
+
197
+ .remirror-table-insert-button svg {
198
+ fill: #fff;
199
+ }
200
+
201
+ .remirror-table-insert-button:hover {
202
+ background-color: #136bda;
203
+ }
204
+
205
+ .remirror-table-insert-button:hover svg {
206
+ fill: #fff;
207
+ }
208
+
209
+ .remirror-table-delete-inner-button {
210
+ background-color: #cecece;
211
+ border: none;
212
+ border-radius: 4px;
213
+ cursor: pointer;
214
+ height: 18px;
215
+ padding: 0;
216
+ position: absolute;
217
+ transition: background-color 0.15s ease;
218
+ width: 18px;
219
+ z-index: 30;
220
+ }
221
+
222
+ .remirror-table-delete-inner-button:hover {
223
+ background-color: #ff7884;
224
+ }
225
+
226
+ .remirror-table-delete-table-inner-button {
227
+ left: calc(var(--remirror-table-delete-button-x) - 9px);
228
+ top: calc(var(--remirror-table-delete-button-y) - 9px);
229
+ }
230
+
231
+ .remirror-table-delete-row-column-inner-button {
232
+ left: calc(var(--remirror-table-delete-row-column-button-x) - 9px);
233
+ top: calc(var(--remirror-table-delete-row-column-button-y) - 9px);
234
+ }
235
+
236
+ .remirror-table-with-controllers {
237
+ height: 1px;
238
+ margin-bottom: 40px;
239
+ margin-top: 40px;
240
+ }
241
+
242
+ .ProseMirror table.remirror-table-with-controllers {
243
+ overflow: visible;
244
+ }
245
+
246
+ .remirror-table-waitting-controllers {
247
+ display: none;
248
+ }
249
+
250
+ .remirror-table-tbody-with-controllers > tr:first-of-type {
251
+ height: 12px;
252
+ overflow: visible;
253
+ }
254
+
255
+ .remirror-table-tbody-with-controllers > tr:first-of-type th:first-of-type {
256
+ cursor: pointer;
257
+ height: 12px;
258
+ overflow: visible;
259
+ padding: 0;
260
+ position: relative;
261
+ width: 12px;
262
+ z-index: 15;
263
+ }
264
+
265
+ .remirror-table-tbody-with-controllers > tr:first-of-type th:first-of-type div.remirror-table-controller-wrapper {
266
+ align-items: flex-end;
267
+ display: flex;
268
+ height: 12px;
269
+ justify-content: flex-end;
270
+ overflow: visible;
271
+ width: 12px;
272
+ }
273
+
274
+ .remirror-table-tbody-with-controllers > tr:first-of-type th:first-of-type div.remirror-table-controller-trigger-area {
275
+ display: none;
276
+ flex: 1;
277
+ position: relative;
278
+ z-index: 10;
279
+ }
280
+
281
+ .remirror-table-tbody-with-controllers
282
+ > tr:first-of-type
283
+ th:first-of-type
284
+ div.remirror-table-controller-mark-row-corner {
285
+ border-color: var(--rmr-color-table-mark);
286
+ border-radius: 50%;
287
+ border-style: solid;
288
+ border-width: 2px;
289
+ bottom: -2px;
290
+ height: 0;
291
+ left: -12px;
292
+ position: absolute;
293
+ width: 0;
294
+ }
295
+
296
+ .remirror-table-tbody-with-controllers
297
+ > tr:first-of-type
298
+ th:first-of-type
299
+ div.remirror-table-controller-mark-column-corner {
300
+ border-color: var(--rmr-color-table-mark);
301
+ border-radius: 50%;
302
+ border-style: solid;
303
+ border-width: 2px;
304
+ height: 0;
305
+ position: absolute;
306
+ right: -2px;
307
+ top: -12px;
308
+ width: 0;
309
+ }
310
+
311
+ .remirror-table-tbody-with-controllers > tr:first-of-type th:nth-of-type(n + 2) {
312
+ cursor: pointer;
313
+ height: 12px;
314
+ overflow: visible;
315
+ padding: 0;
316
+ position: relative;
317
+ z-index: 15;
318
+ }
319
+
320
+ .remirror-table-tbody-with-controllers > tr:first-of-type th:nth-of-type(n + 2) div.remirror-table-controller-wrapper {
321
+ align-items: flex-end;
322
+ display: flex;
323
+ flex-direction: row;
324
+ height: 12px;
325
+ justify-content: flex-end;
326
+ overflow: visible;
327
+ width: 100%;
328
+ }
329
+
330
+ .remirror-table-tbody-with-controllers
331
+ > tr:first-of-type
332
+ th:nth-of-type(n + 2)
333
+ div.remirror-table-controller-trigger-area {
334
+ flex: 1;
335
+ height: 36px;
336
+ position: relative;
337
+ z-index: 10;
338
+ }
339
+
340
+ .remirror-table-tbody-with-controllers
341
+ > tr:first-of-type
342
+ th:nth-of-type(n + 2)
343
+ div.remirror-table-controller-mark-row-corner {
344
+ display: none;
345
+ }
346
+
347
+ .remirror-table-tbody-with-controllers
348
+ > tr:first-of-type
349
+ th:nth-of-type(n + 2)
350
+ div.remirror-table-controller-mark-column-corner {
351
+ border-color: var(--rmr-color-table-mark);
352
+ border-radius: 50%;
353
+ border-style: solid;
354
+ border-width: 2px;
355
+ height: 0;
356
+ position: absolute;
357
+ right: -2px;
358
+ top: -12px;
359
+ width: 0;
360
+ }
361
+
362
+ .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th {
363
+ cursor: pointer;
364
+ overflow: visible;
365
+ padding: 0;
366
+ position: relative;
367
+ width: 12px;
368
+ z-index: 15;
369
+ }
370
+
371
+ .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th div.remirror-table-controller-wrapper {
372
+ align-items: flex-end;
373
+ display: flex;
374
+ flex-direction: column;
375
+ height: 100%;
376
+ justify-content: flex-end;
377
+ overflow: visible;
378
+ width: 12px;
379
+ }
380
+
381
+ .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th div.remirror-table-controller-trigger-area {
382
+ flex: 1;
383
+ position: relative;
384
+ width: 36px;
385
+ z-index: 10;
386
+ }
387
+
388
+ .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th div.remirror-table-controller-mark-row-corner {
389
+ border-color: var(--rmr-color-table-mark);
390
+ border-radius: 50%;
391
+ border-style: solid;
392
+ border-width: 2px;
393
+ bottom: -2px;
394
+ height: 0;
395
+ left: -12px;
396
+ position: absolute;
397
+ width: 0;
398
+ }
399
+
400
+ .remirror-table-tbody-with-controllers > tr:nth-of-type(n + 2) th div.remirror-table-controller-mark-column-corner {
401
+ display: none;
402
+ }
403
+
404
+ .remirror-table-tbody-with-controllers th.remirror-table-controller {
405
+ background-color: var(--rmr-color-table-default-controller);
406
+ }
407
+
408
+ .remirror-table-tbody-with-controllers th.selectedCell.remirror-table-controller {
409
+ background-color: var(--rmr-color-table-selected-controller);
410
+ }
411
+
412
+ .remirror-table-show-predelete td.selectedCell,
413
+ .remirror-table-show-predelete th.selectedCell.remirror-table-controller {
414
+ background-color: var(--rmr-color-table-predelete-cell) !important;
415
+ border-color: var(--rmr-color-table-predelete-border) !important;
416
+ }
417
+
418
+ .remirror-table-show-predelete th.selectedCell.remirror-table-controller {
419
+ background-color: var(--rmr-color-table-predelete-controller) !important;
420
+ }
421
+
422
+ .remirror-table-show-predelete.remirror-table-preselect-all td,
423
+ .remirror-table-show-predelete.remirror-table-preselect-all th.remirror-table-controller {
424
+ background-color: var(--rmr-color-table-predelete-cell) !important;
425
+ border-color: var(--rmr-color-table-predelete-border) !important;
426
+ }
427
+
428
+ .remirror-table-show-predelete.remirror-table-preselect-all th.remirror-table-controller {
429
+ background-color: var(--rmr-color-table-predelete-controller) !important;
65
430
  }
@@ -14,12 +14,14 @@ import RemoveLinkButton from './Tools/Link/RemoveLinkButton';
14
14
  import ClearFormattingButton from './Tools/ClearFormatting/ClearFormattingButton';
15
15
  import ListButtons from './Tools/Lists/ListButtons';
16
16
  import HorizontalLineButton from './Tools/HorizontalLine/HorizontalLineButton';
17
+ import TableButton from './Tools/Table/TableButton';
17
18
  import { useExtensionNames } from '../hooks';
18
19
 
19
20
  type ToolbarProps = {
20
21
  isVisible: boolean;
22
+ enableTableTool: boolean;
21
23
  };
22
- export const Toolbar = ({ isVisible }: ToolbarProps) => {
24
+ export const Toolbar = ({ isVisible, enableTableTool }: ToolbarProps) => {
23
25
  const extensionNames = useExtensionNames();
24
26
 
25
27
  return (
@@ -51,6 +53,7 @@ export const Toolbar = ({ isVisible }: ToolbarProps) => {
51
53
  )}
52
54
  {extensionNames.image && <ImageButton />}
53
55
  {extensionNames.clearFormatting && <ClearFormattingButton />}
56
+ {enableTableTool && extensionNames.table && <TableButton />}
54
57
  </div>
55
58
  </RemirrorToolbar>
56
59
  );
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import '@testing-library/jest-dom';
3
+ import { screen, fireEvent } from '@testing-library/react';
4
+ import { renderWithEditor } from '../../../../tests';
5
+ import TableButton from './TableButton';
6
+
7
+ const defaultTableMarkup = `<div class="remirror-table-show-controllers"><table class="remirror-table remirror-table-with-controllers" data-controllers-injected="" style="min-width: 40px;"><colgroup class="remirror-table-colgroup"><col><col><col><col></colgroup><tbody class="remirror-table-tbody-with-controllers"><tr><th contenteditable="false" class="remirror-table-controller remirror-controllers-toggle selectedCell" data-controller-cell=""><div contenteditable="false" class="remirror-table-controller-wrapper"><div contenteditable="false"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-mark-row-corner"></div><div class="remirror-table-controller-mark-column-corner"></div></div></th><th contenteditable="false" class="remirror-table-controller remirror-controllers-toggle" data-controller-cell=""><div contenteditable="false" class="remirror-table-controller-wrapper"><div contenteditable="false"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-mark-row-corner"></div><div class="remirror-table-controller-mark-column-corner"></div></div></th><th contenteditable="false" class="remirror-table-controller remirror-controllers-toggle" data-controller-cell=""><div contenteditable="false" class="remirror-table-controller-wrapper"><div contenteditable="false"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-mark-row-corner"></div><div class="remirror-table-controller-mark-column-corner"></div></div></th><th contenteditable="false" class="remirror-table-controller remirror-controllers-toggle" data-controller-cell=""><div contenteditable="false" class="remirror-table-controller-wrapper"><div contenteditable="false"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-mark-row-corner"></div><div class="remirror-table-controller-mark-column-corner"></div></div></th></tr><tr><th contenteditable="false" class="remirror-table-controller remirror-controllers-toggle" data-controller-cell=""><div contenteditable="false" class="remirror-table-controller-wrapper"><div contenteditable="false"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-mark-row-corner"></div><div class="remirror-table-controller-mark-column-corner"></div></div></th><td><p style=""><br class="ProseMirror-trailingBreak"></p></td><td><p style=""><br class="ProseMirror-trailingBreak"></p></td><td><p style=""><br class="ProseMirror-trailingBreak"></p></td></tr><tr><th contenteditable="false" class="remirror-table-controller remirror-controllers-toggle" data-controller-cell=""><div contenteditable="false" class="remirror-table-controller-wrapper"><div contenteditable="false"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-mark-row-corner"></div><div class="remirror-table-controller-mark-column-corner"></div></div></th><td><p style=""><br class="ProseMirror-trailingBreak"></p></td><td><p style=""><br class="ProseMirror-trailingBreak"></p></td><td><p style=""><br class="ProseMirror-trailingBreak"></p></td></tr><tr><th contenteditable="false" class="remirror-table-controller remirror-controllers-toggle" data-controller-cell=""><div contenteditable="false" class="remirror-table-controller-wrapper"><div contenteditable="false"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-mark-row-corner"></div><div class="remirror-table-controller-mark-column-corner"></div></div></th><td><p style=""><br class="ProseMirror-trailingBreak"></p></td><td><p style=""><br class="ProseMirror-trailingBreak"></p></td><td><p style=""><br class="ProseMirror-trailingBreak"></p></td></tr><tr><th contenteditable="false" class="remirror-table-controller remirror-controllers-toggle" data-controller-cell=""><div contenteditable="false" class="remirror-table-controller-wrapper"><div contenteditable="false"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-trigger-area"></div><div class="remirror-table-controller-mark-row-corner"></div><div class="remirror-table-controller-mark-column-corner"></div></div></th><td><p style=""><br class="ProseMirror-trailingBreak"></p></td><td><p style=""><br class="ProseMirror-trailingBreak"></p></td><td><p style=""><br class="ProseMirror-trailingBreak"></p></td></tr></tbody></table><div></div></div>`;
8
+
9
+ describe('Table button', () => {
10
+ it('Renders the table button', async () => {
11
+ await renderWithEditor(<TableButton />, { content: 'Some nonsense content here' });
12
+ expect(screen.getByRole('button', { name: 'Insert table' })).toBeInTheDocument();
13
+ });
14
+
15
+ it('Inserts a table into the editor content after clicking button', async () => {
16
+ const { getHtmlContent } = await renderWithEditor(<TableButton />, {
17
+ content: '<p>Hello Mr Goat</p>',
18
+ });
19
+
20
+ const horizontalLine = screen.getByRole('button', { name: 'Insert table' });
21
+ fireEvent.click(horizontalLine);
22
+
23
+ expect(getHtmlContent()).toBe(defaultTableMarkup + '<p style="">Hello Mr Goat</p>');
24
+ });
25
+ });
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { useCommands, useActive } from '@remirror/react';
3
+ import { VerticalDivider } from '@remirror/react-components';
4
+ import { TableExtension } from '@remirror/extension-react-tables';
5
+ import Button from '../../../ui/Button/Button';
6
+ import TableViewRoundedIcon from '@mui/icons-material/TableViewRounded';
7
+
8
+ const TableButton = () => {
9
+ const { createTable } = useCommands();
10
+
11
+ const active = useActive<TableExtension>();
12
+ const enabled = createTable.enabled();
13
+
14
+ const handleSelect = () => {
15
+ createTable({ rowsCount: 4, columnsCount: 3, withHeaderRow: false });
16
+ };
17
+
18
+ return (
19
+ <>
20
+ <Button
21
+ handleOnClick={handleSelect}
22
+ isDisabled={!enabled}
23
+ isActive={active.table()}
24
+ icon={<TableViewRoundedIcon />}
25
+ label="Insert table"
26
+ />
27
+ <VerticalDivider />
28
+ </>
29
+ );
30
+ };
31
+
32
+ export default TableButton;
@@ -13,6 +13,7 @@ import {
13
13
  PlaceholderExtension,
14
14
  HorizontalRuleExtension,
15
15
  TextExtension,
16
+ HardBreakExtension,
16
17
  } from 'remirror/extensions';
17
18
  import { Extension } from '@remirror/core';
18
19
  import { PreformattedExtension } from './PreformattedExtension/PreformattedExtension';
@@ -27,11 +28,15 @@ import { ClearFormattingExtension } from './ClearFormattingExtension/ClearFormat
27
28
  import { UnsupportedNodeExtension } from './UnsuportedExtension/UnsupportedNodeExtension';
28
29
  import { ResourceBrowserContextProps } from '@squiz/resource-browser';
29
30
  import { FetchUrlExtension } from './FetchUrlExtension/FetchUrlExtension';
31
+ import { TableExtension } from '@remirror/extension-react-tables';
32
+ import { ReactComponentExtension } from '@remirror/extension-react-component';
33
+
30
34
  export enum NodeName {
31
35
  Image = 'image',
32
36
  CodeBlock = 'codeBlock',
33
37
  AssetImage = 'assetImage',
34
38
  Text = 'text',
39
+ hardBreak = 'hardBreak',
35
40
  Unsupported = 'unsupportedNode',
36
41
  }
37
42
 
@@ -50,6 +55,7 @@ export const createExtensions = (context: EditorContextOptions, browserContext:
50
55
  new ItalicExtension(),
51
56
  new NodeFormattingExtension({ indents: [] }),
52
57
  new ParagraphExtension(),
58
+ new HardBreakExtension(),
53
59
  new PreformattedExtension(),
54
60
  new ExtendedCodeBlockExtension({ defaultWrap: true }),
55
61
  new UnderlineExtension(),
@@ -74,6 +80,8 @@ export const createExtensions = (context: EditorContextOptions, browserContext:
74
80
  fetchUrl: browserContext.onRequestResource,
75
81
  }),
76
82
  new TextExtension(),
83
+ new TableExtension(),
84
+ new ReactComponentExtension(),
77
85
  ];
78
86
  };
79
87
  };
@@ -131,8 +131,7 @@ describe('htmlToSquizNode', () => {
131
131
  [
132
132
  'nested block level tags are un-nested/normalised to a supported structure',
133
133
  "<div><p>Div tags are not support, <p>Nested block element also aren't</p></p>" +
134
- '<span>Span tags are not supported</span><br />' +
135
- 'Break lines also, you got it, not supported.',
134
+ '<span>Span tags are not supported</span>',
136
135
  [
137
136
  {
138
137
  children: [
@@ -163,7 +162,7 @@ describe('htmlToSquizNode', () => {
163
162
  children: [
164
163
  {
165
164
  type: 'text',
166
- value: 'Span tags are not supported Break lines also, you got it, not supported.',
165
+ value: 'Span tags are not supported',
167
166
  },
168
167
  ],
169
168
  tag: 'p',
@@ -112,6 +112,30 @@ describe('remirrorNodeToSquizNode', () => {
112
112
  expect(result).toEqual(expected);
113
113
  });
114
114
 
115
+ it('should handle line break formatting', async () => {
116
+ const content: RemirrorJSON = {
117
+ type: 'doc',
118
+ content: [
119
+ {
120
+ type: 'hardBreak',
121
+ },
122
+ ],
123
+ };
124
+
125
+ const { editor } = await renderWithEditor(null, { content });
126
+
127
+ const expected: FormattedText = [
128
+ {
129
+ type: 'tag',
130
+ tag: 'br',
131
+ children: [],
132
+ },
133
+ ];
134
+
135
+ const result = remirrorNodeToSquizNode(editor.doc);
136
+ expect(result).toEqual(expected);
137
+ });
138
+
115
139
  it('should handle images', async () => {
116
140
  const content: RemirrorJSON = {
117
141
  type: 'doc',
@@ -213,6 +213,29 @@ describe('squizNodeToRemirrorNode', () => {
213
213
  expect(result).toEqual(expected);
214
214
  });
215
215
 
216
+ it('should handle line breaks', () => {
217
+ const squizComponentJSON: FormattedText = [
218
+ {
219
+ children: [],
220
+ type: 'tag',
221
+ tag: 'br',
222
+ },
223
+ ];
224
+
225
+ const expected: RemirrorJSON = {
226
+ content: [
227
+ {
228
+ attrs: expect.any(Object),
229
+ type: 'hardBreak',
230
+ },
231
+ ],
232
+ type: 'doc',
233
+ };
234
+
235
+ const result = squizNodeToRemirrorNode(squizComponentJSON);
236
+ expect(result).toEqual(expected);
237
+ });
238
+
216
239
  it('should handle pre formatted text', () => {
217
240
  const squizComponentJSON: FormattedText = [
218
241
  {
@@ -33,6 +33,7 @@ const getNodeType = (node: FormattedNodes): string => {
33
33
  span: NodeName.Text,
34
34
  strong: NodeName.Text,
35
35
  code: NodeName.CodeBlock,
36
+ br: NodeName.hardBreak,
36
37
  };
37
38
 
38
39
  if (typeMap[node.type]) {
@@ -19,6 +19,7 @@ describe('getNodeNamesByGroup', () => {
19
19
  'assetImage',
20
20
  'doc',
21
21
  'text',
22
+ 'tableControllerCell',
22
23
  'codeBlock',
23
24
  'image',
24
25
  'unsupportedNode',
@@ -26,6 +27,11 @@ describe('getNodeNamesByGroup', () => {
26
27
  'listItem',
27
28
  'orderedList',
28
29
  'horizontalRule',
30
+ 'table',
31
+ 'hardBreak',
32
+ 'tableRow',
33
+ 'tableCell',
34
+ 'tableHeaderCell',
29
35
  ]);
30
36
  });
31
37
  });