@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.
- package/CHANGELOG.md +14 -0
- package/cypress/e2e/bold.spec.cy.ts +2 -2
- package/lib/EditorToolbar/Tools/Bold/BoldButton.js +2 -1
- package/lib/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.js +2 -1
- package/lib/EditorToolbar/Tools/Image/ImageButton.js +2 -1
- package/lib/EditorToolbar/Tools/Italic/ItalicButton.js +2 -1
- package/lib/EditorToolbar/Tools/Link/LinkButton.js +2 -1
- package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +2 -1
- package/lib/EditorToolbar/Tools/Redo/RedoButton.js +2 -1
- package/lib/EditorToolbar/Tools/Underline/UnderlineButton.js +2 -1
- package/lib/EditorToolbar/Tools/Undo/UndoButton.js +2 -1
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +4 -2
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +1 -1
- package/lib/utils/getShortcutSymbol.d.ts +1 -0
- package/lib/utils/getShortcutSymbol.js +8 -0
- package/package.json +3 -3
- package/src/Editor/Editor.spec.tsx +8 -8
- package/src/EditorToolbar/FloatingToolbar.spec.tsx +5 -5
- package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +3 -3
- package/src/EditorToolbar/Tools/Bold/BoldButton.tsx +2 -1
- package/src/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.spec.tsx +2 -2
- package/src/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.tsx +4 -1
- package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/Image/ImageButton.tsx +2 -1
- package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +3 -3
- package/src/EditorToolbar/Tools/Italic/ItalicButton.tsx +2 -1
- package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/Link/LinkButton.tsx +2 -1
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +5 -5
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -1
- package/src/EditorToolbar/Tools/Redo/RedoButton.spec.tsx +6 -6
- package/src/EditorToolbar/Tools/Redo/RedoButton.tsx +2 -1
- package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +3 -3
- package/src/EditorToolbar/Tools/Underline/UnderlineButton.tsx +2 -1
- package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +5 -5
- package/src/EditorToolbar/Tools/Undo/UndoButton.tsx +2 -1
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +7 -3
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +1 -1
- package/src/utils/getShortcutSymbol.spec.ts +27 -0
- 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 ( | 
| 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 ( | 
| 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:  | 
| 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'} ( | 
| 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:  | 
| 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:  | 
| 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:  | 
| 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:  | 
| 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:  | 
| 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:  | 
| 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:  | 
| 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: [... | 
| 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, ... | 
| 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 | 
| 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. | 
| 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. | 
| 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. | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 300 | 
            -
                expect(screen.queryByRole('button', { name: 'Italic ( | 
| 301 | 
            -
                expect(screen.queryByRole('button', { name: 'Underline ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 13 | 
            -
                ['Regular text + link selected', 3, 17, ['Bold ( | 
| 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 ( | 
| 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 ( | 
| 33 | 
            -
                ['Asset image selected', 2, ['Image ( | 
| 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 ( | 
| 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 ( | 
| 15 | 
            -
                const bold = screen.getByRole('button', { name: 'Bold ( | 
| 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= | 
| 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 ( | 
| 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 ( | 
| 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={`${ | 
| 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 ( | 
| 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= | 
| 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 ( | 
| 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 ( | 
| 15 | 
            -
                const italic = screen.getByRole('button', { name: 'Italic ( | 
| 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= | 
| 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 ( | 
| 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= | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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= | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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= | 
| 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 ( | 
| 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 ( | 
| 15 | 
            -
                const underline = screen.getByRole('button', { name: 'Underline ( | 
| 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= | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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 ( | 
| 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= | 
| 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 | 
| 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: [... | 
| 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, ... | 
| 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 | 
| 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 | 
            +
            });
         |