@squiz/formatted-text-editor 1.67.0 → 1.68.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/cypress/e2e/bold.spec.cy.ts +2 -2
  3. package/lib/EditorToolbar/Tools/Bold/BoldButton.js +2 -1
  4. package/lib/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.js +2 -1
  5. package/lib/EditorToolbar/Tools/Image/ImageButton.js +2 -1
  6. package/lib/EditorToolbar/Tools/Italic/ItalicButton.js +2 -1
  7. package/lib/EditorToolbar/Tools/Link/LinkButton.js +2 -1
  8. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +2 -1
  9. package/lib/EditorToolbar/Tools/Redo/RedoButton.js +2 -1
  10. package/lib/EditorToolbar/Tools/Underline/UnderlineButton.js +2 -1
  11. package/lib/EditorToolbar/Tools/Undo/UndoButton.js +2 -1
  12. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +4 -2
  13. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +1 -1
  14. package/lib/utils/getShortcutSymbol.d.ts +1 -0
  15. package/lib/utils/getShortcutSymbol.js +8 -0
  16. package/package.json +3 -3
  17. package/src/Editor/Editor.spec.tsx +8 -8
  18. package/src/EditorToolbar/FloatingToolbar.spec.tsx +5 -5
  19. package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +3 -3
  20. package/src/EditorToolbar/Tools/Bold/BoldButton.tsx +2 -1
  21. package/src/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.spec.tsx +2 -2
  22. package/src/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.tsx +4 -1
  23. package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +1 -1
  24. package/src/EditorToolbar/Tools/Image/ImageButton.tsx +2 -1
  25. package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +3 -3
  26. package/src/EditorToolbar/Tools/Italic/ItalicButton.tsx +2 -1
  27. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +1 -1
  28. package/src/EditorToolbar/Tools/Link/LinkButton.tsx +2 -1
  29. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +5 -5
  30. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -1
  31. package/src/EditorToolbar/Tools/Redo/RedoButton.spec.tsx +6 -6
  32. package/src/EditorToolbar/Tools/Redo/RedoButton.tsx +2 -1
  33. package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +3 -3
  34. package/src/EditorToolbar/Tools/Underline/UnderlineButton.tsx +2 -1
  35. package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +5 -5
  36. package/src/EditorToolbar/Tools/Undo/UndoButton.tsx +2 -1
  37. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +7 -3
  38. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +1 -1
  39. package/src/utils/getShortcutSymbol.spec.ts +27 -0
  40. package/src/utils/getShortcutSymbol.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.68.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4d3dbba: Updated FTE shortcuts to change tooltips depending on OS
8
+
9
+ ## 1.67.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 928cc88: Made children optional for formatted text nodes/tags
14
+ - Updated dependencies [928cc88]
15
+ - @squiz/dx-json-schema-lib@1.65.1
16
+
3
17
  ## 1.67.0
4
18
 
5
19
  ### Minor Changes
@@ -7,11 +7,11 @@ describe('The formatted text editor renders', () => {
7
7
  .type('It was the best of times it was the worst of times.')
8
8
  .setSelection('best of times');
9
9
 
10
- cy.findAllByRole('button', { name: 'Bold (cmd+B)' }).first().click();
10
+ cy.findAllByRole('button', { name: 'Bold (Ctrl+B)' }).first().click();
11
11
 
12
12
  cy.get('strong').should('include.text', 'best of times');
13
13
 
14
- cy.findAllByRole('button', { name: 'Bold (cmd+B)' }).first().click();
14
+ cy.findAllByRole('button', { name: 'Bold (Ctrl+B)' }).first().click();
15
15
 
16
16
  cy.get('strong').should('not.exist');
17
17
  });
@@ -7,6 +7,7 @@ const react_1 = __importDefault(require("react"));
7
7
  const react_2 = require("@remirror/react");
8
8
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
9
9
  const FormatBoldRounded_1 = __importDefault(require("@mui/icons-material/FormatBoldRounded"));
10
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
10
11
  const BoldButton = () => {
11
12
  const { toggleBold } = (0, react_2.useCommands)();
12
13
  const chain = (0, react_2.useChainedCommands)();
@@ -17,6 +18,6 @@ const BoldButton = () => {
17
18
  chain.toggleBold().focus().run();
18
19
  }
19
20
  };
20
- return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: active.bold(), icon: react_1.default.createElement(FormatBoldRounded_1.default, null), label: "Bold (cmd+B)" }));
21
+ return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: active.bold(), icon: react_1.default.createElement(FormatBoldRounded_1.default, null), label: `Bold (${(0, getShortcutSymbol_1.getShortcutSymbol)()}+B)` }));
21
22
  };
22
23
  exports.default = BoldButton;
@@ -31,6 +31,7 @@ const react_2 = require("@remirror/react");
31
31
  const react_components_1 = require("@remirror/react-components");
32
32
  const FormatClearRounded_1 = __importDefault(require("@mui/icons-material/FormatClearRounded"));
33
33
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
34
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
34
35
  const ClearFormattingButton = () => {
35
36
  const { clearFormatting } = (0, react_2.useCommands)();
36
37
  const { selection } = (0, react_2.useEditorState)();
@@ -51,6 +52,6 @@ const ClearFormattingButton = () => {
51
52
  (0, react_2.useKeymap)('Mod-\\', handleShortcut);
52
53
  return (react_1.default.createElement(react_1.default.Fragment, null,
53
54
  react_1.default.createElement(react_components_1.VerticalDivider, null),
54
- react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: false, isActive: false, icon: react_1.default.createElement(FormatClearRounded_1.default, null), label: `${contentSelected ? 'Clear formatting from selection' : 'Clear all formatting'} (cmd+\\)` })));
55
+ react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: false, isActive: false, icon: react_1.default.createElement(FormatClearRounded_1.default, null), label: `${contentSelected ? 'Clear formatting from selection' : 'Clear all formatting'} (${(0, getShortcutSymbol_1.getShortcutSymbol)()}+\\)` })));
55
56
  };
56
57
  exports.default = ClearFormattingButton;
@@ -32,6 +32,7 @@ const ImageRounded_1 = __importDefault(require("@mui/icons-material/ImageRounded
32
32
  const ImageModal_1 = __importDefault(require("./ImageModal"));
33
33
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
34
34
  const Extensions_1 = require("../../../Extensions/Extensions");
35
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
35
36
  const ImageButton = ({ inPopover = false }) => {
36
37
  const [showModal, setShowModal] = (0, react_1.useState)(false);
37
38
  const { insertImage, insertAssetImage } = (0, react_2.useCommands)();
@@ -69,7 +70,7 @@ const ImageButton = ({ inPopover = false }) => {
69
70
  (0, react_2.useKeymap)('Mod-l', disabled ? () => false : handleShortcut);
70
71
  }
71
72
  return (react_1.default.createElement(react_1.default.Fragment, null,
72
- react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: active.image() || active.assetImage(), icon: react_1.default.createElement(ImageRounded_1.default, null), label: "Image (cmd+L)", isDisabled: disabled }),
73
+ react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: active.image() || active.assetImage(), icon: react_1.default.createElement(ImageRounded_1.default, null), label: `Image (${(0, getShortcutSymbol_1.getShortcutSymbol)()}+L)`, isDisabled: disabled }),
73
74
  showModal && react_1.default.createElement(ImageModal_1.default, { onCancel: () => setShowModal(false), onSubmit: handleSubmit })));
74
75
  };
75
76
  exports.default = ImageButton;
@@ -7,6 +7,7 @@ const react_1 = __importDefault(require("react"));
7
7
  const react_2 = require("@remirror/react");
8
8
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
9
9
  const FormatItalicRounded_1 = __importDefault(require("@mui/icons-material/FormatItalicRounded"));
10
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
10
11
  const ItalicButton = () => {
11
12
  const { toggleItalic } = (0, react_2.useCommands)();
12
13
  const chain = (0, react_2.useChainedCommands)();
@@ -17,6 +18,6 @@ const ItalicButton = () => {
17
18
  chain.toggleItalic().focus().run();
18
19
  }
19
20
  };
20
- return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: active.italic(), icon: react_1.default.createElement(FormatItalicRounded_1.default, null), label: "Italic (cmd+I)" }));
21
+ return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: active.italic(), icon: react_1.default.createElement(FormatItalicRounded_1.default, null), label: `Italic (${(0, getShortcutSymbol_1.getShortcutSymbol)()}+I)` }));
21
22
  };
22
23
  exports.default = ItalicButton;
@@ -32,6 +32,7 @@ const LinkModal_1 = __importDefault(require("./LinkModal"));
32
32
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
33
33
  const react_2 = require("@remirror/react");
34
34
  const Extensions_1 = require("../../../Extensions/Extensions");
35
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
35
36
  const LinkButton = ({ inPopover = false }) => {
36
37
  const [showModal, setShowModal] = (0, react_1.useState)(false);
37
38
  const { updateLink, updateAssetLink } = (0, react_2.useCommands)();
@@ -64,7 +65,7 @@ const LinkButton = ({ inPopover = false }) => {
64
65
  (0, react_2.useKeymap)('Mod-k', disabled ? () => true : handleShortcut);
65
66
  }
66
67
  return (react_1.default.createElement(react_1.default.Fragment, null,
67
- react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: active.link() || active.assetLink(), icon: react_1.default.createElement(InsertLinkRounded_1.default, null), label: "Link (cmd+K)", isDisabled: disabled }),
68
+ react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: active.link() || active.assetLink(), icon: react_1.default.createElement(InsertLinkRounded_1.default, null), label: `Link (${(0, getShortcutSymbol_1.getShortcutSymbol)()}+K)`, isDisabled: disabled }),
68
69
  showModal && react_1.default.createElement(LinkModal_1.default, { onCancel: () => setShowModal(false), onSubmit: handleSubmit })));
69
70
  };
70
71
  exports.default = LinkButton;
@@ -30,6 +30,7 @@ const react_1 = __importStar(require("react"));
30
30
  const react_2 = require("@remirror/react");
31
31
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
32
32
  const LinkOff_1 = __importDefault(require("@mui/icons-material/LinkOff"));
33
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
33
34
  const RemoveLinkButton = ({ inPopover = false }) => {
34
35
  const chain = (0, react_2.useChainedCommands)();
35
36
  const active = (0, react_2.useActive)();
@@ -48,6 +49,6 @@ const RemoveLinkButton = ({ inPopover = false }) => {
48
49
  // disable the shortcut if the button is disabled
49
50
  (0, react_2.useKeymap)('Shift-Mod-k', disabled ? () => true : handleShortcut);
50
51
  }
51
- return (react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: false, isDisabled: disabled, icon: react_1.default.createElement(LinkOff_1.default, null), label: "Remove link (shift+cmd+K)" }));
52
+ return (react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: false, isDisabled: disabled, icon: react_1.default.createElement(LinkOff_1.default, null), label: `Remove link (Shift+${(0, getShortcutSymbol_1.getShortcutSymbol)()}+K)` }));
52
53
  };
53
54
  exports.default = RemoveLinkButton;
@@ -7,6 +7,7 @@ const react_1 = __importDefault(require("react"));
7
7
  const react_2 = require("@remirror/react");
8
8
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
9
9
  const RedoRounded_1 = __importDefault(require("@mui/icons-material/RedoRounded"));
10
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
10
11
  const RedoButton = () => {
11
12
  const { redo } = (0, react_2.useCommands)();
12
13
  const { redoDepth } = (0, react_2.useHelpers)(true);
@@ -16,6 +17,6 @@ const RedoButton = () => {
16
17
  }
17
18
  };
18
19
  const enabled = redoDepth() > 0;
19
- return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: false, icon: react_1.default.createElement(RedoRounded_1.default, null), label: "Redo (shift+cmd+Z)" }));
20
+ return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: false, icon: react_1.default.createElement(RedoRounded_1.default, null), label: `Redo (Shift+${(0, getShortcutSymbol_1.getShortcutSymbol)()}+Z)` }));
20
21
  };
21
22
  exports.default = RedoButton;
@@ -7,6 +7,7 @@ const react_1 = __importDefault(require("react"));
7
7
  const react_2 = require("@remirror/react");
8
8
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
9
9
  const FormatUnderlinedRounded_1 = __importDefault(require("@mui/icons-material/FormatUnderlinedRounded"));
10
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
10
11
  const UnderlineButton = () => {
11
12
  const { toggleUnderline } = (0, react_2.useCommands)();
12
13
  const chain = (0, react_2.useChainedCommands)();
@@ -17,6 +18,6 @@ const UnderlineButton = () => {
17
18
  chain.toggleUnderline().focus().run();
18
19
  }
19
20
  };
20
- return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: active.underline(), icon: react_1.default.createElement(FormatUnderlinedRounded_1.default, null), label: "Underline (cmd+U)" }));
21
+ return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: active.underline(), icon: react_1.default.createElement(FormatUnderlinedRounded_1.default, null), label: `Underline (${(0, getShortcutSymbol_1.getShortcutSymbol)()}+U)` }));
21
22
  };
22
23
  exports.default = UnderlineButton;
@@ -7,6 +7,7 @@ const react_1 = __importDefault(require("react"));
7
7
  const react_2 = require("@remirror/react");
8
8
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
9
9
  const UndoRounded_1 = __importDefault(require("@mui/icons-material/UndoRounded"));
10
+ const getShortcutSymbol_1 = require("../../../utils/getShortcutSymbol");
10
11
  const UndoButton = () => {
11
12
  const { undo } = (0, react_2.useCommands)();
12
13
  const { undoDepth } = (0, react_2.useHelpers)(true);
@@ -16,6 +17,6 @@ const UndoButton = () => {
16
17
  }
17
18
  };
18
19
  const enabled = undoDepth() > 0;
19
- return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: false, icon: react_1.default.createElement(UndoRounded_1.default, null), label: "Undo (cmd+Z)" }));
20
+ return (react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: !enabled, isActive: false, icon: react_1.default.createElement(UndoRounded_1.default, null), label: `Undo (${(0, getShortcutSymbol_1.getShortcutSymbol)()}+Z)` }));
20
21
  };
21
22
  exports.default = UndoButton;
@@ -121,7 +121,9 @@ const transformNode = (node) => {
121
121
  * @return {FormattedNode}
122
122
  */
123
123
  const wrapNodeIfNeeded = (node, wrappingNode, copyFont = true) => {
124
+ const wrappingNodeChildren = wrappingNode.children || [];
124
125
  if (node.type === 'tag' && wrappingNode.type === 'tag' && (node.tag === 'span' || node.tag === wrappingNode.tag)) {
126
+ const nodeChildren = node.children || [];
125
127
  // if the node we are wrapping with is a DOM node, and the node being wrapped is
126
128
  // a plain looking DOM node merge the 2 nodes.
127
129
  return {
@@ -141,13 +143,13 @@ const wrapNodeIfNeeded = (node, wrappingNode, copyFont = true) => {
141
143
  ...wrappingNode.font,
142
144
  }
143
145
  : {}),
144
- children: [...node.children, ...wrappingNode.children],
146
+ children: [...nodeChildren, ...wrappingNodeChildren],
145
147
  };
146
148
  }
147
149
  // if the node we are wrapping or the wrapping nodes are not compatible merge them.
148
150
  return {
149
151
  ...wrappingNode,
150
- children: [node, ...wrappingNode.children],
152
+ children: [node, ...wrappingNodeChildren],
151
153
  };
152
154
  };
153
155
  const transformMark = (mark, node) => {
@@ -133,7 +133,7 @@ const unwrapNodeIfNeeded = (node) => {
133
133
  const formatNode = (node) => {
134
134
  const children = [];
135
135
  if ('children' in node) {
136
- node.children.forEach((child) => {
136
+ node.children?.forEach((child) => {
137
137
  children.push(...formatNode(child));
138
138
  });
139
139
  }
@@ -0,0 +1 @@
1
+ export declare const getShortcutSymbol: () => string;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getShortcutSymbol = void 0;
4
+ const getShortcutSymbol = () => {
5
+ // If we can detect Mac then we return "⌘" otherwise default to "Ctrl"
6
+ return window.navigator.userAgent.toLowerCase().indexOf('mac') > -1 ? '⌘' : 'Ctrl';
7
+ };
8
+ exports.getShortcutSymbol = getShortcutSymbol;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.67.0",
3
+ "version": "1.68.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "private": false,
@@ -23,7 +23,7 @@
23
23
  "@headlessui/react": "1.7.11",
24
24
  "@mui/icons-material": "5.11.16",
25
25
  "@remirror/react": "2.0.25",
26
- "@squiz/dx-json-schema-lib": "^1.65.0",
26
+ "@squiz/dx-json-schema-lib": "^1.65.1",
27
27
  "@squiz/resource-browser": "^1.66.3",
28
28
  "clsx": "1.2.1",
29
29
  "react-hook-form": "7.43.2",
@@ -35,7 +35,7 @@
35
35
  "@testing-library/jest-dom": "5.16.5",
36
36
  "@testing-library/react": "14.0.0",
37
37
  "@testing-library/user-event": "14.4.3",
38
- "@types/node": "20.11.24",
38
+ "@types/node": "20.12.4",
39
39
  "@types/react": "^18.2.45",
40
40
  "@types/react-dom": "^18.2.18",
41
41
  "@vitejs/plugin-react": "3.0.0",
@@ -23,17 +23,17 @@ describe('Formatted text editor', () => {
23
23
 
24
24
  it('Renders the bold button', () => {
25
25
  render(<Editor />);
26
- expect(screen.getByRole('button', { name: 'Bold (cmd+B)' })).toBeInTheDocument();
26
+ expect(screen.getByRole('button', { name: 'Bold (Ctrl+B)' })).toBeInTheDocument();
27
27
  });
28
28
 
29
29
  it('Renders the italic button', () => {
30
30
  render(<Editor />);
31
- expect(screen.getByRole('button', { name: 'Italic (cmd+I)' })).toBeInTheDocument();
31
+ expect(screen.getByRole('button', { name: 'Italic (Ctrl+I)' })).toBeInTheDocument();
32
32
  });
33
33
 
34
34
  it('Renders the underline button', () => {
35
35
  render(<Editor />);
36
- expect(screen.getByRole('button', { name: 'Underline (cmd+U)' })).toBeInTheDocument();
36
+ expect(screen.getByRole('button', { name: 'Underline (Ctrl+U)' })).toBeInTheDocument();
37
37
  });
38
38
 
39
39
  it('Renders the align left button', () => {
@@ -296,9 +296,9 @@ describe('Formatted text editor', () => {
296
296
 
297
297
  it('Should not display the toolbar if is not editable', () => {
298
298
  render(<Editor editable={false} />);
299
- expect(screen.queryByRole('button', { name: 'Bold (cmd+B)' })).not.toBeInTheDocument();
300
- expect(screen.queryByRole('button', { name: 'Italic (cmd+I)' })).not.toBeInTheDocument();
301
- expect(screen.queryByRole('button', { name: 'Underline (cmd+U)' })).not.toBeInTheDocument();
299
+ expect(screen.queryByRole('button', { name: 'Bold (Ctrl+B)' })).not.toBeInTheDocument();
300
+ expect(screen.queryByRole('button', { name: 'Italic (Ctrl+I)' })).not.toBeInTheDocument();
301
+ expect(screen.queryByRole('button', { name: 'Underline (Ctrl+U)' })).not.toBeInTheDocument();
302
302
  });
303
303
 
304
304
  it('Should not display the floating toolbar if is not editable', async () => {
@@ -415,7 +415,7 @@ describe('Formatted text editor', () => {
415
415
  </ResourceBrowserContext.Provider>,
416
416
  );
417
417
 
418
- await act(() => fireEvent.click(screen.getByRole('button', { name: 'Link (cmd+K)' })));
418
+ await act(() => fireEvent.click(screen.getByRole('button', { name: 'Link (Ctrl+K)' })));
419
419
  await act(() => fireEvent.click(screen.getByText('Choose asset')));
420
420
  await act(() => fireEvent.click(screen.getByRole('button', { name: 'Drill down to My Matrix instance children' })));
421
421
  await act(() => fireEvent.click(screen.getByRole('button', { name: 'Drill down to Folder 1 children' })));
@@ -426,7 +426,7 @@ describe('Formatted text editor', () => {
426
426
  // Toolbar (Link button) should be mounted.
427
427
  // Toolbar should be shown (.show-toolbar class).
428
428
  expect(screen.getByRole('button', { name: /My image/ })).toBeInTheDocument();
429
- expect(within(document.body).getByRole('button', { name: 'Link (cmd+K)', hidden: true })).toBeInTheDocument();
429
+ expect(within(document.body).getByRole('button', { name: 'Link (Ctrl+K)', hidden: true })).toBeInTheDocument();
430
430
  expect(document.querySelector('.show-toolbar')).toBeInTheDocument();
431
431
  });
432
432
  });
@@ -9,11 +9,11 @@ describe('FloatingToolbar', () => {
9
9
 
10
10
  it.each([
11
11
  ['Nothing selected', 3, 3, []],
12
- ['Regular text selected', 3, 4, ['Bold (cmd+B)', 'Italic (cmd+I)', 'Underline (cmd+U)', 'Link (cmd+K)']],
13
- ['Regular text + link selected', 3, 17, ['Bold (cmd+B)', 'Italic (cmd+I)', 'Underline (cmd+U)']],
12
+ ['Regular text selected', 3, 4, ['Bold (Ctrl+B)', 'Italic (Ctrl+I)', 'Underline (Ctrl+U)', 'Link (Ctrl+K)']],
13
+ ['Regular text + link selected', 3, 17, ['Bold (Ctrl+B)', 'Italic (Ctrl+I)', 'Underline (Ctrl+U)']],
14
14
  ['Nothing selected, positioned directly on the left of a link', 12, 12, []],
15
15
  ['Nothing selected, positioned directly on the right of a link', 19, 19, []],
16
- ['Nothing selected, positioned within a link', 13, 13, ['Link (cmd+K)', 'Remove link (shift+cmd+K)']],
16
+ ['Nothing selected, positioned within a link', 13, 13, ['Link (Ctrl+K)', 'Remove link (Shift+Ctrl+K)']],
17
17
  ])(
18
18
  'Renders formatting buttons when text is selected - %s',
19
19
  async (description: string, from: number, to: number, expectedButtons: string[]) => {
@@ -29,8 +29,8 @@ describe('FloatingToolbar', () => {
29
29
  );
30
30
 
31
31
  it.each([
32
- ['Image selected', 1, ['Image (cmd+L)']],
33
- ['Asset image selected', 2, ['Image (cmd+L)']],
32
+ ['Image selected', 1, ['Image (Ctrl+L)']],
33
+ ['Asset image selected', 2, ['Image (Ctrl+L)']],
34
34
  ])(
35
35
  'Renders formatting buttons when node is selected - %s',
36
36
  async (description: string, pos: number, expectedButtons: string[]) => {
@@ -6,13 +6,13 @@ import Editor from '../../../Editor/Editor';
6
6
  describe('Bold button', () => {
7
7
  it('Renders the bold button', () => {
8
8
  render(<Editor />);
9
- expect(screen.getByRole('button', { name: 'Bold (cmd+B)' })).toBeInTheDocument();
9
+ expect(screen.getByRole('button', { name: 'Bold (Ctrl+B)' })).toBeInTheDocument();
10
10
  });
11
11
 
12
12
  it('Activates the button if clicked', () => {
13
13
  render(<Editor />);
14
- expect(screen.getByRole('button', { name: 'Bold (cmd+B)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
15
- const bold = screen.getByRole('button', { name: 'Bold (cmd+B)' });
14
+ expect(screen.getByRole('button', { name: 'Bold (Ctrl+B)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
15
+ const bold = screen.getByRole('button', { name: 'Bold (Ctrl+B)' });
16
16
  fireEvent.click(bold);
17
17
  expect(bold.classList.contains('squiz-fte-btn--is-active')).toBeTruthy();
18
18
  });
@@ -3,6 +3,7 @@ import { useCommands, useActive, useChainedCommands } from '@remirror/react';
3
3
  import { BoldExtension } from '@remirror/extension-bold';
4
4
  import Button from '../../../ui/Button/Button';
5
5
  import FormatBoldRoundedIcon from '@mui/icons-material/FormatBoldRounded';
6
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
6
7
 
7
8
  const BoldButton = () => {
8
9
  const { toggleBold } = useCommands();
@@ -22,7 +23,7 @@ const BoldButton = () => {
22
23
  isDisabled={!enabled}
23
24
  isActive={active.bold()}
24
25
  icon={<FormatBoldRoundedIcon />}
25
- label="Bold (cmd+B)"
26
+ label={`Bold (${getShortcutSymbol()}+B)`}
26
27
  />
27
28
  );
28
29
  };
@@ -7,7 +7,7 @@ import ClearFormattingButton from './ClearFormattingButton';
7
7
  describe('Clear formatting button', () => {
8
8
  it('Renders the clear formatting button', async () => {
9
9
  await renderWithEditor(<ClearFormattingButton />, { content: 'Some nonsense content here' });
10
- expect(screen.getByRole('button', { name: 'Clear all formatting (cmd+\\)' })).toBeInTheDocument();
10
+ expect(screen.getByRole('button', { name: 'Clear all formatting (Ctrl+\\)' })).toBeInTheDocument();
11
11
  });
12
12
 
13
13
  it('Clears the formatting from editor content after clicking button', async () => {
@@ -15,7 +15,7 @@ describe('Clear formatting button', () => {
15
15
  content: '<p>Hello <strong>Mr Bean</strong></p>',
16
16
  });
17
17
 
18
- const clearFormatting = screen.getByRole('button', { name: 'Clear all formatting (cmd+\\)' });
18
+ const clearFormatting = screen.getByRole('button', { name: 'Clear all formatting (Ctrl+\\)' });
19
19
  fireEvent.click(clearFormatting);
20
20
 
21
21
  expect(getHtmlContent()).toBe('<p style="">Hello Mr Bean</p>');
@@ -4,6 +4,7 @@ import { VerticalDivider } from '@remirror/react-components';
4
4
  import FormatClearRoundedIcon from '@mui/icons-material/FormatClearRounded';
5
5
  import { ClearFormattingExtension } from '../../../Extensions/ClearFormattingExtension/ClearFormattingExtension';
6
6
  import Button from '../../../ui/Button/Button';
7
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
7
8
 
8
9
  const ClearFormattingButton = () => {
9
10
  const { clearFormatting } = useCommands<ClearFormattingExtension>();
@@ -36,7 +37,9 @@ const ClearFormattingButton = () => {
36
37
  isDisabled={false}
37
38
  isActive={false}
38
39
  icon={<FormatClearRoundedIcon />}
39
- label={`${contentSelected ? 'Clear formatting from selection' : 'Clear all formatting'} (cmd+\\)`}
40
+ label={`${
41
+ contentSelected ? 'Clear formatting from selection' : 'Clear all formatting'
42
+ } (${getShortcutSymbol()}+\\)`}
40
43
  />
41
44
  </>
42
45
  );
@@ -10,7 +10,7 @@ jest.mock('react-image-size');
10
10
 
11
11
  describe('ImageButton', () => {
12
12
  const openModal = async () => {
13
- fireEvent.click(screen.getByRole('button', { name: 'Image (cmd+L)' }));
13
+ fireEvent.click(screen.getByRole('button', { name: 'Image (Ctrl+L)' }));
14
14
  await screen.findByRole('button', { name: 'Apply' });
15
15
  fireEvent.click(screen.getByRole('button', { name: 'From URL' }));
16
16
  };
@@ -8,6 +8,7 @@ import { ImageExtension } from '../../../Extensions/ImageExtension/ImageExtensio
8
8
  import { NodeName } from '../../../Extensions/Extensions';
9
9
  import { AssetImageExtension } from '../../../Extensions/ImageExtension/AssetImageExtension';
10
10
  import { CodeBlockExtension } from 'remirror/dist-types/extensions';
11
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
11
12
 
12
13
  type ImageButtonProps = {
13
14
  inPopover?: boolean;
@@ -60,7 +61,7 @@ const ImageButton = ({ inPopover = false }: ImageButtonProps) => {
60
61
  handleOnClick={handleClick}
61
62
  isActive={active.image() || active.assetImage()}
62
63
  icon={<ImageRoundedIcon />}
63
- label="Image (cmd+L)"
64
+ label={`Image (${getShortcutSymbol()}+L)`}
64
65
  isDisabled={disabled}
65
66
  />
66
67
  {showModal && <ImageModal onCancel={() => setShowModal(false)} onSubmit={handleSubmit} />}
@@ -6,13 +6,13 @@ import Editor from '../../../Editor/Editor';
6
6
  describe('Italic button', () => {
7
7
  it('Renders the italic button', () => {
8
8
  render(<Editor />);
9
- expect(screen.getByRole('button', { name: 'Italic (cmd+I)' })).toBeInTheDocument();
9
+ expect(screen.getByRole('button', { name: 'Italic (Ctrl+I)' })).toBeInTheDocument();
10
10
  });
11
11
 
12
12
  it('Activates the button if clicked', () => {
13
13
  render(<Editor />);
14
- expect(screen.getByRole('button', { name: 'Italic (cmd+I)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
15
- const italic = screen.getByRole('button', { name: 'Italic (cmd+I)' });
14
+ expect(screen.getByRole('button', { name: 'Italic (Ctrl+I)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
15
+ const italic = screen.getByRole('button', { name: 'Italic (Ctrl+I)' });
16
16
  fireEvent.click(italic);
17
17
  expect(italic.classList.contains('squiz-fte-btn--is-active')).toBeTruthy();
18
18
  });
@@ -3,6 +3,7 @@ import { useCommands, useActive, useChainedCommands } from '@remirror/react';
3
3
  import { ItalicExtension } from '@remirror/extension-italic';
4
4
  import Button from '../../../ui/Button/Button';
5
5
  import FormatItalicRoundedIcon from '@mui/icons-material/FormatItalicRounded';
6
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
6
7
 
7
8
  const ItalicButton = () => {
8
9
  const { toggleItalic } = useCommands();
@@ -22,7 +23,7 @@ const ItalicButton = () => {
22
23
  isDisabled={!enabled}
23
24
  isActive={active.italic()}
24
25
  icon={<FormatItalicRoundedIcon />}
25
- label="Italic (cmd+I)"
26
+ label={`Italic (${getShortcutSymbol()}+I)`}
26
27
  />
27
28
  );
28
29
  };
@@ -6,7 +6,7 @@ import LinkButton from './LinkButton';
6
6
 
7
7
  describe('LinkButton', () => {
8
8
  const openModal = async () => {
9
- fireEvent.click(screen.getByRole('button', { name: 'Link (cmd+K)' }));
9
+ fireEvent.click(screen.getByRole('button', { name: 'Link (Ctrl+K)' }));
10
10
  await screen.findByRole('button', { name: 'Apply' });
11
11
  fireEvent.click(screen.getByRole('button', { name: 'From URL' }));
12
12
  };
@@ -10,6 +10,7 @@ import { AssetLinkExtension } from '../../../Extensions/LinkExtension/AssetLinkE
10
10
  import { MarkName } from '../../../Extensions/Extensions';
11
11
  import { ImageExtension } from '../../../Extensions/ImageExtension/ImageExtension';
12
12
  import { CodeBlockExtension } from 'remirror/dist-types/extensions';
13
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
13
14
 
14
15
  export type LinkButtonProps = {
15
16
  inPopover?: boolean;
@@ -55,7 +56,7 @@ const LinkButton = ({ inPopover = false }: LinkButtonProps) => {
55
56
  handleOnClick={handleClick}
56
57
  isActive={active.link() || active.assetLink()}
57
58
  icon={<InsertLinkRoundedIcon />}
58
- label="Link (cmd+K)"
59
+ label={`Link (${getShortcutSymbol()}+K)`}
59
60
  isDisabled={disabled}
60
61
  />
61
62
  {showModal && <LinkModal onCancel={() => setShowModal(false)} onSubmit={handleSubmit} />}
@@ -40,7 +40,7 @@ describe('RemoveLinkButton', () => {
40
40
  await act(() => editor.selectText('all'));
41
41
 
42
42
  // remove the links.
43
- fireEvent.click(screen.getByRole('button', { name: 'Remove link (shift+cmd+K)' }));
43
+ fireEvent.click(screen.getByRole('button', { name: 'Remove link (Shift+Ctrl+K)' }));
44
44
 
45
45
  // make sure both types of link have been removed.
46
46
  expect(getJsonContent()).toEqual({
@@ -101,11 +101,11 @@ describe('RemoveLinkButton', () => {
101
101
  });
102
102
 
103
103
  // expect remove button to be disabled
104
- expect(screen.getByRole('button', { name: 'Remove link (shift+cmd+K)' })).toBeDisabled();
104
+ expect(screen.getByRole('button', { name: 'Remove link (Shift+Ctrl+K)' })).toBeDisabled();
105
105
  // jump to the middle of the link.
106
106
  await act(() => editor.selectText(3));
107
107
  // expect remove button to be enabled
108
- expect(screen.getByRole('button', { name: 'Remove link (shift+cmd+K)' })).not.toBeDisabled();
108
+ expect(screen.getByRole('button', { name: 'Remove link (Shift+Ctrl+K)' })).not.toBeDisabled();
109
109
  });
110
110
 
111
111
  it('Enables the Remove link button when asset link text is selected', async () => {
@@ -134,10 +134,10 @@ describe('RemoveLinkButton', () => {
134
134
  });
135
135
 
136
136
  // expect remove button to be disabled
137
- expect(screen.getByRole('button', { name: 'Remove link (shift+cmd+K)' })).toBeDisabled();
137
+ expect(screen.getByRole('button', { name: 'Remove link (Shift+Ctrl+K)' })).toBeDisabled();
138
138
  // jump to the middle of the link.
139
139
  await act(() => editor.selectText(3));
140
140
  // expect remove button to be enabled
141
- expect(screen.getByRole('button', { name: 'Remove link (shift+cmd+K)' })).not.toBeDisabled();
141
+ expect(screen.getByRole('button', { name: 'Remove link (Shift+Ctrl+K)' })).not.toBeDisabled();
142
142
  });
143
143
  });
@@ -5,6 +5,7 @@ import LinkOffIcon from '@mui/icons-material/LinkOff';
5
5
  import { AssetLinkExtension } from '../../../Extensions/LinkExtension/AssetLinkExtension';
6
6
  import { LinkExtension } from '../../../Extensions/LinkExtension/LinkExtension';
7
7
  import { LinkButtonProps } from './LinkButton';
8
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
8
9
 
9
10
  const RemoveLinkButton = ({ inPopover = false }: LinkButtonProps) => {
10
11
  const chain = useChainedCommands();
@@ -33,7 +34,7 @@ const RemoveLinkButton = ({ inPopover = false }: LinkButtonProps) => {
33
34
  isActive={false}
34
35
  isDisabled={disabled}
35
36
  icon={<LinkOffIcon />}
36
- label="Remove link (shift+cmd+K)"
37
+ label={`Remove link (Shift+${getShortcutSymbol()}+K)`}
37
38
  />
38
39
  );
39
40
  };
@@ -6,12 +6,12 @@ import React from 'react';
6
6
  describe('Redo button', () => {
7
7
  it('Renders the redo button', () => {
8
8
  render(<Editor />);
9
- expect(screen.getByRole('button', { name: 'Redo (shift+cmd+Z)' })).toBeInTheDocument();
9
+ expect(screen.getByRole('button', { name: 'Redo (Shift+Ctrl+Z)' })).toBeInTheDocument();
10
10
  });
11
11
 
12
12
  it('Renders a disabled button if you have not made any changes yet', () => {
13
13
  render(<Editor />);
14
- const redo = screen.getByRole('button', { name: 'Redo (shift+cmd+Z)' });
14
+ const redo = screen.getByRole('button', { name: 'Redo (Shift+Ctrl+Z)' });
15
15
  expect(redo).toBeDisabled();
16
16
  });
17
17
 
@@ -25,12 +25,12 @@ describe('Redo button', () => {
25
25
  expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeTruthy();
26
26
 
27
27
  // Revert this action
28
- const undo = screen.getByRole('button', { name: 'Undo (cmd+Z)' });
28
+ const undo = screen.getByRole('button', { name: 'Undo (Ctrl+Z)' });
29
29
  fireEvent.click(undo);
30
30
  expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeFalsy();
31
31
 
32
32
  // Check that this enables the redo button
33
- const redo = screen.getByRole('button', { name: 'Redo (shift+cmd+Z)' });
33
+ const redo = screen.getByRole('button', { name: 'Redo (Shift+Ctrl+Z)' });
34
34
  expect(redo).not.toBeDisabled();
35
35
  });
36
36
 
@@ -44,12 +44,12 @@ describe('Redo button', () => {
44
44
  expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeTruthy();
45
45
 
46
46
  // Revert this action
47
- const undo = screen.getByRole('button', { name: 'Undo (cmd+Z)' });
47
+ const undo = screen.getByRole('button', { name: 'Undo (Ctrl+Z)' });
48
48
  fireEvent.click(undo);
49
49
  expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeFalsy();
50
50
 
51
51
  // Check that this enables the redo button
52
- const redo = screen.getByRole('button', { name: 'Redo (shift+cmd+Z)' });
52
+ const redo = screen.getByRole('button', { name: 'Redo (Shift+Ctrl+Z)' });
53
53
  expect(redo).not.toBeDisabled();
54
54
 
55
55
  // Click the redo button and check that this has reverted the previous action
@@ -3,6 +3,7 @@ import { useCommands, useHelpers } from '@remirror/react';
3
3
  import { HistoryExtension } from 'remirror/extensions';
4
4
  import Button from '../../../ui/Button/Button';
5
5
  import RedoRoundedIcon from '@mui/icons-material/RedoRounded';
6
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
6
7
 
7
8
  const RedoButton = () => {
8
9
  const { redo } = useCommands<HistoryExtension>();
@@ -22,7 +23,7 @@ const RedoButton = () => {
22
23
  isDisabled={!enabled}
23
24
  isActive={false}
24
25
  icon={<RedoRoundedIcon />}
25
- label="Redo (shift+cmd+Z)"
26
+ label={`Redo (Shift+${getShortcutSymbol()}+Z)`}
26
27
  />
27
28
  );
28
29
  };
@@ -6,13 +6,13 @@ import Editor from '../../../Editor/Editor';
6
6
  describe('Underline button', () => {
7
7
  it('Renders the underline button', () => {
8
8
  render(<Editor />);
9
- expect(screen.getByRole('button', { name: 'Underline (cmd+U)' })).toBeInTheDocument();
9
+ expect(screen.getByRole('button', { name: 'Underline (Ctrl+U)' })).toBeInTheDocument();
10
10
  });
11
11
 
12
12
  it('Activates the button if clicked', () => {
13
13
  render(<Editor />);
14
- expect(screen.getByRole('button', { name: 'Underline (cmd+U)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
15
- const underline = screen.getByRole('button', { name: 'Underline (cmd+U)' });
14
+ expect(screen.getByRole('button', { name: 'Underline (Ctrl+U)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
15
+ const underline = screen.getByRole('button', { name: 'Underline (Ctrl+U)' });
16
16
  fireEvent.click(underline);
17
17
  expect(underline.classList.contains('squiz-fte-btn--is-active')).toBeTruthy();
18
18
  });
@@ -3,6 +3,7 @@ import { useCommands, useActive, useChainedCommands } from '@remirror/react';
3
3
  import { UnderlineExtension } from '@remirror/extension-underline';
4
4
  import Button from '../../../ui/Button/Button';
5
5
  import FormatUnderlinedRoundedIcon from '@mui/icons-material/FormatUnderlinedRounded';
6
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
6
7
 
7
8
  const UnderlineButton = () => {
8
9
  const { toggleUnderline } = useCommands();
@@ -22,7 +23,7 @@ const UnderlineButton = () => {
22
23
  isDisabled={!enabled}
23
24
  isActive={active.underline()}
24
25
  icon={<FormatUnderlinedRoundedIcon />}
25
- label="Underline (cmd+U)"
26
+ label={`Underline (${getShortcutSymbol()}+U)`}
26
27
  />
27
28
  );
28
29
  };
@@ -8,12 +8,12 @@ import UndoButton from './UndoButton';
8
8
  describe('Undo button', () => {
9
9
  it('Renders the undo button', () => {
10
10
  render(<Editor />);
11
- expect(screen.getByRole('button', { name: 'Undo (cmd+Z)' })).toBeInTheDocument();
11
+ expect(screen.getByRole('button', { name: 'Undo (Ctrl+Z)' })).toBeInTheDocument();
12
12
  });
13
13
 
14
14
  it('Renders a disabled button if you have not made any changes yet', () => {
15
15
  render(<Editor />);
16
- const undo = screen.getByRole('button', { name: 'Undo (cmd+Z)' });
16
+ const undo = screen.getByRole('button', { name: 'Undo (Ctrl+Z)' });
17
17
  expect(undo).toBeDisabled();
18
18
  });
19
19
 
@@ -26,7 +26,7 @@ describe('Undo button', () => {
26
26
 
27
27
  fireEvent.click(leftAlignButton);
28
28
  expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeTruthy();
29
- const undo = screen.getByRole('button', { name: 'Undo (cmd+Z)' });
29
+ const undo = screen.getByRole('button', { name: 'Undo (Ctrl+Z)' });
30
30
  expect(undo).not.toBeDisabled();
31
31
  });
32
32
 
@@ -41,7 +41,7 @@ describe('Undo button', () => {
41
41
  expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeTruthy();
42
42
 
43
43
  // Check that this enables the undo button
44
- const undo = screen.getByRole('button', { name: 'Undo (cmd+Z)' });
44
+ const undo = screen.getByRole('button', { name: 'Undo (Ctrl+Z)' });
45
45
  expect(undo).not.toBeDisabled();
46
46
 
47
47
  // Click the undo button and check that this has reverted the previous action
@@ -60,7 +60,7 @@ describe('Undo button', () => {
60
60
  content: [{ type: 'text', text: 'Initial content... with some updated content.' }],
61
61
  });
62
62
 
63
- fireEvent.click(screen.getByRole('button', { name: 'Undo (cmd+Z)' }));
63
+ fireEvent.click(screen.getByRole('button', { name: 'Undo (Ctrl+Z)' }));
64
64
  expect(getJsonContent()).toEqual({
65
65
  type: 'paragraph',
66
66
  attrs: expect.any(Object),
@@ -3,6 +3,7 @@ import { useCommands, useHelpers } from '@remirror/react';
3
3
  import { HistoryExtension } from 'remirror/extensions';
4
4
  import Button from '../../../ui/Button/Button';
5
5
  import UndoRoundedIcon from '@mui/icons-material/UndoRounded';
6
+ import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
6
7
 
7
8
  const UndoButton = () => {
8
9
  const { undo } = useCommands<HistoryExtension>();
@@ -22,7 +23,7 @@ const UndoButton = () => {
22
23
  isDisabled={!enabled}
23
24
  isActive={false}
24
25
  icon={<UndoRoundedIcon />}
25
- label="Undo (cmd+Z)"
26
+ label={`Undo (${getShortcutSymbol()}+Z)`}
26
27
  />
27
28
  );
28
29
  };
@@ -13,7 +13,7 @@ type FontOptions = FormattedTextModels.v1.FormattedNodeFontProperties;
13
13
  type FormattedText = FormattedTextModels.v1.FormattedText;
14
14
  type FormattedNode = FormattedTextModels.v1.FormattedNodes;
15
15
  type FormattedNodeFontProperties = FormattedTextModels.v1.FormattedNodeFontProperties;
16
- type FormattedNodeWithChildren = Extract<FormattedNode, { children: FormattedNode[] }>;
16
+ type FormattedNodeWithChildren = Extract<FormattedNode, { children?: FormattedNode[] }>;
17
17
  type RemirrorTextAlignment = Exclude<Remirror.Attributes['nodeTextAlignment'], undefined>;
18
18
  type FormattedTextAlignment = FormattingOptions['alignment'];
19
19
 
@@ -164,7 +164,11 @@ const wrapNodeIfNeeded = (
164
164
  wrappingNode: FormattedNodeWithChildren,
165
165
  copyFont: boolean = true,
166
166
  ): FormattedNode => {
167
+ const wrappingNodeChildren = wrappingNode.children || [];
168
+
167
169
  if (node.type === 'tag' && wrappingNode.type === 'tag' && (node.tag === 'span' || node.tag === wrappingNode.tag)) {
170
+ const nodeChildren = node.children || [];
171
+
168
172
  // if the node we are wrapping with is a DOM node, and the node being wrapped is
169
173
  // a plain looking DOM node merge the 2 nodes.
170
174
  return {
@@ -186,14 +190,14 @@ const wrapNodeIfNeeded = (
186
190
  }
187
191
  : {},
188
192
  ),
189
- children: [...node.children, ...wrappingNode.children],
193
+ children: [...nodeChildren, ...wrappingNodeChildren],
190
194
  };
191
195
  }
192
196
 
193
197
  // if the node we are wrapping or the wrapping nodes are not compatible merge them.
194
198
  return {
195
199
  ...wrappingNode,
196
- children: [node, ...wrappingNode.children],
200
+ children: [node, ...wrappingNodeChildren],
197
201
  };
198
202
  };
199
203
 
@@ -146,7 +146,7 @@ const formatNode = (node: FormattedNodes): RemirrorJSON[] => {
146
146
  const children: RemirrorJSON[] = [];
147
147
 
148
148
  if ('children' in node) {
149
- node.children.forEach((child: FormattedNodes) => {
149
+ node.children?.forEach((child: FormattedNodes) => {
150
150
  children.push(...formatNode(child));
151
151
  });
152
152
  }
@@ -0,0 +1,27 @@
1
+ import { getShortcutSymbol } from './getShortcutSymbol';
2
+
3
+ describe('getShortcutSymbol', () => {
4
+ it('Returns Mac command icon if user-agent is a Mac based user-agent', () => {
5
+ Object.defineProperty(window, `navigator`, {
6
+ value: {
7
+ userAgent:
8
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9',
9
+ },
10
+ writable: true,
11
+ });
12
+
13
+ expect(getShortcutSymbol()).toBe('⌘');
14
+ });
15
+
16
+ it('Returns Ctrl shortcut if user-agent is not a Mac based user-agent', () => {
17
+ Object.defineProperty(window, `navigator`, {
18
+ value: {
19
+ userAgent:
20
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246',
21
+ },
22
+ writable: true,
23
+ });
24
+
25
+ expect(getShortcutSymbol()).toBe('Ctrl');
26
+ });
27
+ });
@@ -0,0 +1,4 @@
1
+ export const getShortcutSymbol = (): string => {
2
+ // If we can detect Mac then we return "⌘" otherwise default to "Ctrl"
3
+ return window.navigator.userAgent.toLowerCase().indexOf('mac') > -1 ? '⌘' : 'Ctrl';
4
+ };