@squiz/formatted-text-editor 1.12.0-alpha.8 → 1.12.1-alpha.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/.eslintrc.json +34 -0
- package/CHANGELOG.md +48 -0
- package/README.md +2 -3
- package/build.js +21 -0
- package/cypress/e2e/bold.spec.cy.ts +18 -0
- package/cypress/global.d.ts +9 -0
- package/cypress/support/commands.ts +130 -0
- package/cypress/support/e2e.ts +20 -0
- package/cypress/tsconfig.json +8 -0
- package/cypress.config.ts +7 -0
- package/demo/App.tsx +39 -0
- package/demo/index.html +13 -0
- package/demo/index.scss +40 -0
- package/demo/main.tsx +10 -0
- package/demo/public/favicon-dxp.svg +3 -0
- package/demo/vite-env.d.ts +1 -0
- package/file-transformer.js +1 -0
- package/jest.bootstrap.ts +3 -0
- package/jest.config.ts +30 -0
- package/lib/Editor/Editor.d.ts +4 -2
- package/lib/Editor/Editor.js +11 -14
- package/lib/EditorToolbar/FloatingToolbar.d.ts +1 -0
- package/lib/EditorToolbar/FloatingToolbar.js +31 -0
- package/lib/EditorToolbar/Toolbar.d.ts +1 -0
- package/lib/EditorToolbar/Toolbar.js +25 -0
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +10 -0
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +23 -0
- package/lib/EditorToolbar/Tools/Link/LinkButton.d.ts +5 -0
- package/lib/EditorToolbar/Tools/Link/LinkButton.js +34 -0
- package/lib/EditorToolbar/Tools/Link/LinkModal.d.ts +8 -0
- package/lib/EditorToolbar/Tools/Link/LinkModal.js +14 -0
- package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +16 -0
- package/lib/EditorToolbar/Tools/Redo/RedoButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/Redo/RedoButton.js +16 -0
- package/lib/EditorToolbar/Tools/TextAlign/TextAlignButtons.js +4 -1
- package/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.d.ts +5 -0
- package/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.js +32 -0
- package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.js +16 -0
- package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.js +16 -0
- package/lib/EditorToolbar/Tools/TextType/TextTypeDropdown.d.ts +2 -0
- package/lib/EditorToolbar/Tools/TextType/TextTypeDropdown.js +35 -0
- package/lib/EditorToolbar/Tools/Undo/UndoButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/Undo/UndoButton.js +16 -0
- package/lib/EditorToolbar/index.d.ts +2 -0
- package/lib/EditorToolbar/index.js +2 -0
- package/lib/Extensions/Extensions.d.ts +4 -0
- package/lib/Extensions/Extensions.js +20 -0
- package/lib/Extensions/LinkExtension/LinkExtension.d.ts +16 -0
- package/lib/Extensions/LinkExtension/LinkExtension.js +91 -0
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.d.ts +10 -0
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +46 -0
- package/lib/FormattedTextEditor.d.ts +2 -2
- package/lib/FormattedTextEditor.js +1 -6
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useExtensionNames.d.ts +1 -0
- package/lib/hooks/useExtensionNames.js +12 -0
- package/lib/index.css +787 -3686
- package/lib/ui/Inputs/Select/Select.d.ts +12 -0
- package/lib/ui/Inputs/Select/Select.js +23 -0
- package/lib/ui/Inputs/Text/TextInput.d.ts +4 -0
- package/lib/ui/Inputs/Text/TextInput.js +7 -0
- package/lib/ui/Modal/FormModal.d.ts +5 -0
- package/lib/ui/Modal/FormModal.js +11 -0
- package/lib/ui/Modal/Modal.d.ts +10 -0
- package/lib/ui/Modal/Modal.js +48 -0
- package/lib/ui/ToolbarButton/ToolbarButton.d.ts +1 -1
- package/lib/ui/ToolbarButton/ToolbarButton.js +1 -1
- package/lib/ui/ToolbarDropdown/ToolbarDropdown.d.ts +6 -0
- package/lib/ui/ToolbarDropdown/ToolbarDropdown.js +20 -0
- package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.d.ts +9 -0
- package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.js +8 -0
- package/lib/utils/createToolbarPositioner.d.ts +18 -0
- package/lib/utils/createToolbarPositioner.js +81 -0
- package/lib/utils/getCursorRect.d.ts +2 -0
- package/lib/utils/getCursorRect.js +3 -0
- package/package.json +22 -13
- package/postcss.config.js +12 -0
- package/src/Editor/Editor.mock.tsx +43 -0
- package/src/Editor/Editor.spec.tsx +254 -0
- package/src/Editor/Editor.tsx +46 -0
- package/src/Editor/_editor.scss +82 -0
- package/src/EditorToolbar/FloatingToolbar.spec.tsx +30 -0
- package/src/EditorToolbar/FloatingToolbar.tsx +40 -0
- package/src/EditorToolbar/Toolbar.tsx +33 -0
- package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +19 -0
- package/src/EditorToolbar/Tools/Bold/BoldButton.tsx +30 -0
- package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +19 -0
- package/src/EditorToolbar/Tools/Italic/ItalicButton.tsx +30 -0
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +30 -0
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +48 -0
- package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +277 -0
- package/src/EditorToolbar/Tools/Link/LinkButton.tsx +56 -0
- package/src/EditorToolbar/Tools/Link/LinkModal.tsx +29 -0
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +46 -0
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +27 -0
- package/src/EditorToolbar/Tools/Redo/RedoButton.spec.tsx +59 -0
- package/src/EditorToolbar/Tools/Redo/RedoButton.tsx +30 -0
- package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.tsx +31 -0
- package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.tsx +31 -0
- package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.tsx +31 -0
- package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.tsx +31 -0
- package/src/EditorToolbar/Tools/TextAlign/TextAlignButtons.tsx +21 -0
- package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.spec.tsx +56 -0
- package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.tsx +52 -0
- package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.spec.tsx +30 -0
- package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.tsx +25 -0
- package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.spec.tsx +47 -0
- package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.tsx +30 -0
- package/src/EditorToolbar/Tools/TextType/TextTypeDropdown.spec.tsx +51 -0
- package/src/EditorToolbar/Tools/TextType/TextTypeDropdown.tsx +44 -0
- package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +19 -0
- package/src/EditorToolbar/Tools/Underline/UnderlineButton.tsx +30 -0
- package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +49 -0
- package/src/EditorToolbar/Tools/Undo/UndoButton.tsx +30 -0
- package/src/EditorToolbar/_floating-toolbar.scss +4 -0
- package/src/EditorToolbar/_toolbar.scss +16 -0
- package/src/EditorToolbar/index.ts +2 -0
- package/src/Extensions/Extensions.ts +29 -0
- package/src/Extensions/LinkExtension/LinkExtension.ts +116 -0
- package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +50 -0
- package/src/FormattedTextEditor.spec.tsx +10 -0
- package/src/FormattedTextEditor.tsx +3 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useExtensionNames.ts +15 -0
- package/src/index.scss +19 -0
- package/src/index.ts +3 -0
- package/src/ui/Inputs/Select/Select.spec.tsx +30 -0
- package/src/ui/Inputs/Select/Select.tsx +66 -0
- package/src/ui/Inputs/Text/TextInput.spec.tsx +43 -0
- package/src/ui/Inputs/Text/TextInput.tsx +20 -0
- package/src/ui/Modal/FormModal.spec.tsx +20 -0
- package/src/ui/Modal/FormModal.tsx +17 -0
- package/src/ui/Modal/Modal.spec.tsx +113 -0
- package/src/ui/Modal/Modal.tsx +97 -0
- package/src/ui/Modal/_modal.scss +24 -0
- package/src/ui/ToolbarButton/ToolbarButton.tsx +26 -0
- package/src/ui/ToolbarButton/_toolbar-button.scss +17 -0
- package/src/ui/ToolbarDropdown/ToolbarDropdown.spec.tsx +78 -0
- package/src/ui/ToolbarDropdown/ToolbarDropdown.tsx +42 -0
- package/src/ui/ToolbarDropdown/_toolbar-dropdown.scss +32 -0
- package/src/ui/ToolbarDropdownButton/ToolbarDropdownButton.spec.tsx +48 -0
- package/src/ui/ToolbarDropdownButton/ToolbarDropdownButton.tsx +29 -0
- package/src/ui/ToolbarDropdownButton/_toolbar-dropdown-button.scss +14 -0
- package/src/ui/_buttons.scss +19 -0
- package/src/ui/_forms.scss +16 -0
- package/src/utils/createToolbarPositioner.ts +115 -0
- package/src/utils/getCursorRect.ts +5 -0
- package/tailwind.config.cjs +83 -0
- package/tests/index.ts +2 -0
- package/tests/renderWithEditor.tsx +110 -0
- package/tests/select.tsx +16 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +19 -0
- package/lib/EditorToolbar/EditorToolbar.d.ts +0 -7
- package/lib/EditorToolbar/EditorToolbar.js +0 -22
@@ -0,0 +1,39 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Left align button', () => {
|
7
|
+
it('Renders the left align button', () => {
|
8
|
+
const { baseElement, getByRole } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(getByRole('button', { name: 'Align left' })).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Applies active status after selecting left align button', () => {
|
14
|
+
const { baseElement, getByRole } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const leftAlignButton = getByRole('button', { name: 'Align left' });
|
18
|
+
expect(leftAlignButton).toBeTruthy();
|
19
|
+
expect(leftAlignButton.classList.contains('is-active')).toBeFalsy();
|
20
|
+
|
21
|
+
fireEvent.click(leftAlignButton);
|
22
|
+
|
23
|
+
setTimeout(() => {
|
24
|
+
expect(leftAlignButton.classList.contains('is-active')).toBeTruthy();
|
25
|
+
}, 50);
|
26
|
+
});
|
27
|
+
|
28
|
+
it('Should apply left alignment to editor after clicking button', () => {
|
29
|
+
const { baseElement, getByRole } = render(<Editor />);
|
30
|
+
expect(baseElement).toBeTruthy();
|
31
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeFalsy();
|
32
|
+
|
33
|
+
const leftAlignButton = getByRole('button', { name: 'Align left' });
|
34
|
+
expect(leftAlignButton).toBeTruthy();
|
35
|
+
fireEvent.click(leftAlignButton);
|
36
|
+
|
37
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeTruthy();
|
38
|
+
});
|
39
|
+
});
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useCommands, useChainedCommands } from '@remirror/react';
|
3
|
+
import { NodeFormattingExtension } from '@remirror/extension-node-formatting';
|
4
|
+
import ToolbarButton from '../../../../ui/ToolbarButton/ToolbarButton';
|
5
|
+
import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft';
|
6
|
+
|
7
|
+
const LeftAlignButton = () => {
|
8
|
+
const { leftAlign } = useCommands<NodeFormattingExtension>();
|
9
|
+
const chain = useChainedCommands();
|
10
|
+
|
11
|
+
const handleSelect = () => {
|
12
|
+
if (leftAlign.enabled()) {
|
13
|
+
chain.leftAlign().focus().run();
|
14
|
+
}
|
15
|
+
};
|
16
|
+
|
17
|
+
const active = leftAlign.active?.() || false;
|
18
|
+
const enabled = leftAlign.enabled();
|
19
|
+
|
20
|
+
return (
|
21
|
+
<ToolbarButton
|
22
|
+
handleOnClick={handleSelect}
|
23
|
+
isDisabled={!enabled}
|
24
|
+
isActive={active}
|
25
|
+
icon={<FormatAlignLeftIcon />}
|
26
|
+
label="Align left"
|
27
|
+
/>
|
28
|
+
);
|
29
|
+
};
|
30
|
+
|
31
|
+
export default LeftAlignButton;
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Right align button', () => {
|
7
|
+
it('Renders the right align button', () => {
|
8
|
+
const { baseElement, getByRole } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(getByRole('button', { name: 'Align right' })).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Applies active status after selecting right align button', () => {
|
14
|
+
const { baseElement, getByRole } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const rightAlignButton = getByRole('button', { name: 'Align right' });
|
18
|
+
expect(rightAlignButton).toBeTruthy();
|
19
|
+
expect(rightAlignButton.classList.contains('is-active')).toBeFalsy();
|
20
|
+
|
21
|
+
fireEvent.click(rightAlignButton);
|
22
|
+
|
23
|
+
setTimeout(() => {
|
24
|
+
expect(rightAlignButton.classList.contains('is-active')).toBeTruthy();
|
25
|
+
}, 50);
|
26
|
+
});
|
27
|
+
|
28
|
+
it('Should apply right alignment to editor after clicking button', () => {
|
29
|
+
const { baseElement, getByRole } = render(<Editor />);
|
30
|
+
expect(baseElement).toBeTruthy();
|
31
|
+
expect(baseElement.querySelector('p[data-node-text-align="right"]')).toBeFalsy();
|
32
|
+
|
33
|
+
const rightAlignButton = getByRole('button', { name: 'Align right' });
|
34
|
+
expect(rightAlignButton).toBeTruthy();
|
35
|
+
fireEvent.click(rightAlignButton);
|
36
|
+
|
37
|
+
expect(baseElement.querySelector('p[data-node-text-align="right"]')).toBeTruthy();
|
38
|
+
});
|
39
|
+
});
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useCommands, useChainedCommands } from '@remirror/react';
|
3
|
+
import { NodeFormattingExtension } from '@remirror/extension-node-formatting';
|
4
|
+
import ToolbarButton from '../../../../ui/ToolbarButton/ToolbarButton';
|
5
|
+
import FormatAlignRightIcon from '@mui/icons-material/FormatAlignRight';
|
6
|
+
|
7
|
+
const RightAlignButton = () => {
|
8
|
+
const { rightAlign } = useCommands<NodeFormattingExtension>();
|
9
|
+
const chain = useChainedCommands();
|
10
|
+
|
11
|
+
const handleSelect = () => {
|
12
|
+
if (rightAlign.enabled()) {
|
13
|
+
chain.rightAlign().focus().run();
|
14
|
+
}
|
15
|
+
};
|
16
|
+
|
17
|
+
const active = rightAlign.active?.() || false;
|
18
|
+
const enabled = rightAlign.enabled();
|
19
|
+
|
20
|
+
return (
|
21
|
+
<ToolbarButton
|
22
|
+
handleOnClick={handleSelect}
|
23
|
+
isDisabled={!enabled}
|
24
|
+
isActive={active}
|
25
|
+
icon={<FormatAlignRightIcon />}
|
26
|
+
label="Align right"
|
27
|
+
/>
|
28
|
+
);
|
29
|
+
};
|
30
|
+
|
31
|
+
export default RightAlignButton;
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import LeftAlignButton from './LeftAlign/LeftAlignButton';
|
3
|
+
import CenterAlignButton from './CenterAlign/CenterAlignButton';
|
4
|
+
import RightAlignButton from './RightAlign/RightAlignButton';
|
5
|
+
import JustifyAlignButton from './JustifyAlign/JustifyAlignButton';
|
6
|
+
import { VerticalDivider } from '@remirror/react-components';
|
7
|
+
|
8
|
+
const TextAlignButtons = () => {
|
9
|
+
return (
|
10
|
+
<>
|
11
|
+
<VerticalDivider className="editor-divider" />
|
12
|
+
<LeftAlignButton />
|
13
|
+
<CenterAlignButton />
|
14
|
+
<RightAlignButton />
|
15
|
+
<JustifyAlignButton />
|
16
|
+
<VerticalDivider className="editor-divider" />
|
17
|
+
</>
|
18
|
+
);
|
19
|
+
};
|
20
|
+
|
21
|
+
export default TextAlignButtons;
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
const headings = [
|
7
|
+
['Heading 1', 'h1'],
|
8
|
+
['Heading 2', 'h2'],
|
9
|
+
['Heading 3', 'h3'],
|
10
|
+
['Heading 4', 'h4'],
|
11
|
+
['Heading 5', 'h5'],
|
12
|
+
['Heading 6', 'h6'],
|
13
|
+
];
|
14
|
+
|
15
|
+
describe('Heading button', () => {
|
16
|
+
it.each(headings)('Renders the "%s" heading button', (label) => {
|
17
|
+
const { baseElement, getByRole } = render(<Editor />);
|
18
|
+
expect(baseElement).toBeTruthy();
|
19
|
+
expect(getByRole('button', { name: label })).toBeTruthy();
|
20
|
+
});
|
21
|
+
|
22
|
+
it.each(headings)('Applies active status after selecting "%s" heading button', (label) => {
|
23
|
+
const { baseElement, getByRole } = render(<Editor />);
|
24
|
+
expect(baseElement).toBeTruthy();
|
25
|
+
|
26
|
+
const headingButton = getByRole('button', { name: label });
|
27
|
+
expect(headingButton).toBeTruthy();
|
28
|
+
expect(headingButton.className).not.toContain('is-active');
|
29
|
+
|
30
|
+
fireEvent.click(headingButton);
|
31
|
+
expect(headingButton.className).toContain('is-active');
|
32
|
+
});
|
33
|
+
|
34
|
+
it.each(headings)('Should render a check icon if button is active', (label) => {
|
35
|
+
const { baseElement, getByRole } = render(<Editor />);
|
36
|
+
expect(baseElement).toBeTruthy();
|
37
|
+
|
38
|
+
const headingButton = getByRole('button', { name: label });
|
39
|
+
expect(headingButton.querySelector('svg[data-testid="CheckIcon"]')).toBeFalsy();
|
40
|
+
|
41
|
+
fireEvent.click(headingButton);
|
42
|
+
expect(headingButton.querySelector('svg[data-testid="CheckIcon"]')).toBeTruthy();
|
43
|
+
});
|
44
|
+
|
45
|
+
it.each(headings)('Should apply "%s" heading tag to editor after clicking button', (label, tag) => {
|
46
|
+
const { baseElement } = render(<Editor />);
|
47
|
+
expect(baseElement).toBeTruthy();
|
48
|
+
expect(baseElement.querySelector('div.remirror-editor h1')).toBeFalsy();
|
49
|
+
|
50
|
+
const headingButton = baseElement.querySelector(`button[title="${label}"]`) as HTMLButtonElement;
|
51
|
+
expect(headingButton).toBeTruthy();
|
52
|
+
fireEvent.click(headingButton);
|
53
|
+
|
54
|
+
expect(baseElement.querySelector(`div.remirror-editor ${tag}`)).toBeTruthy();
|
55
|
+
});
|
56
|
+
});
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useCommands, useChainedCommands, useActive } from '@remirror/react';
|
3
|
+
import { HeadingExtension } from '@remirror/extension-heading';
|
4
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
5
|
+
|
6
|
+
type HeadingButtonProps = {
|
7
|
+
level: number;
|
8
|
+
};
|
9
|
+
|
10
|
+
const HeadingButton = ({ level }: HeadingButtonProps) => {
|
11
|
+
const { toggleHeading } = useCommands<HeadingExtension>();
|
12
|
+
const chain = useChainedCommands();
|
13
|
+
|
14
|
+
const active = useActive<HeadingExtension>();
|
15
|
+
const enabled = toggleHeading.enabled({ level });
|
16
|
+
|
17
|
+
const handleSelect = () => {
|
18
|
+
if (toggleHeading.enabled({ level })) {
|
19
|
+
chain.toggleHeading({ level }).focus().run();
|
20
|
+
}
|
21
|
+
};
|
22
|
+
|
23
|
+
const headingContent = () => {
|
24
|
+
switch (level) {
|
25
|
+
case 1:
|
26
|
+
return <h1>Heading 1</h1>;
|
27
|
+
case 2:
|
28
|
+
return <h2>Heading 2</h2>;
|
29
|
+
case 3:
|
30
|
+
return <h3>Heading 3</h3>;
|
31
|
+
case 4:
|
32
|
+
return <h4>Heading 4</h4>;
|
33
|
+
case 5:
|
34
|
+
return <h5>Heading 5</h5>;
|
35
|
+
case 6:
|
36
|
+
return <h6>Heading 6</h6>;
|
37
|
+
}
|
38
|
+
};
|
39
|
+
|
40
|
+
return (
|
41
|
+
<DropdownButton
|
42
|
+
handleOnClick={handleSelect}
|
43
|
+
isDisabled={!enabled}
|
44
|
+
isActive={active.heading({ level })}
|
45
|
+
label={`Heading ${level}`}
|
46
|
+
>
|
47
|
+
{headingContent()}
|
48
|
+
</DropdownButton>
|
49
|
+
);
|
50
|
+
};
|
51
|
+
|
52
|
+
export default HeadingButton;
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Paragraph button', () => {
|
7
|
+
it('Renders paragraph button', () => {
|
8
|
+
const { baseElement } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(baseElement.querySelector('button[title="Paragraph"]')).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Paragraph button active status should be set on as default', () => {
|
14
|
+
const { baseElement } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const paragraphButton = baseElement.querySelector('button[title="Paragraph"]') as HTMLButtonElement;
|
18
|
+
expect(paragraphButton).toBeTruthy();
|
19
|
+
expect(paragraphButton.className).toContain('is-active');
|
20
|
+
});
|
21
|
+
|
22
|
+
it('Should render a check icon if button is active', () => {
|
23
|
+
const { baseElement } = render(<Editor />);
|
24
|
+
expect(baseElement).toBeTruthy();
|
25
|
+
|
26
|
+
const paragraphButton = baseElement.querySelector('button[title="Paragraph"]') as HTMLButtonElement;
|
27
|
+
expect(paragraphButton).toBeTruthy();
|
28
|
+
expect(paragraphButton.querySelector('svg[data-testid="CheckIcon"]')).toBeTruthy();
|
29
|
+
});
|
30
|
+
});
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useCommands, useChainedCommands, useActive } from '@remirror/react';
|
3
|
+
import { ParagraphExtension } from '@remirror/extension-paragraph';
|
4
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
5
|
+
|
6
|
+
const ParagraphButton = () => {
|
7
|
+
const { convertParagraph } = useCommands<ParagraphExtension>();
|
8
|
+
const chain = useChainedCommands();
|
9
|
+
|
10
|
+
const active = useActive<ParagraphExtension>();
|
11
|
+
|
12
|
+
const handleSelect = () => {
|
13
|
+
if (convertParagraph.enabled()) {
|
14
|
+
chain.convertParagraph().focus().run();
|
15
|
+
}
|
16
|
+
};
|
17
|
+
|
18
|
+
return (
|
19
|
+
<DropdownButton handleOnClick={handleSelect} isDisabled={false} isActive={active.paragraph()} label="Paragraph">
|
20
|
+
<p>Paragraph</p>
|
21
|
+
</DropdownButton>
|
22
|
+
);
|
23
|
+
};
|
24
|
+
|
25
|
+
export default ParagraphButton;
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Preformatted button', () => {
|
7
|
+
it('Renders the preformatted button', () => {
|
8
|
+
const { baseElement, getByRole } = render(<Editor />);
|
9
|
+
expect(baseElement).toBeTruthy();
|
10
|
+
expect(getByRole('button', { name: 'Preformatted' })).toBeTruthy();
|
11
|
+
});
|
12
|
+
|
13
|
+
it('Applies active status after selecting preformatted button', () => {
|
14
|
+
const { baseElement, getByRole } = render(<Editor />);
|
15
|
+
expect(baseElement).toBeTruthy();
|
16
|
+
|
17
|
+
const preformattedButton = getByRole('button', { name: 'Preformatted' });
|
18
|
+
expect(preformattedButton).toBeTruthy();
|
19
|
+
expect(preformattedButton.className).not.toContain('is-active');
|
20
|
+
|
21
|
+
fireEvent.click(preformattedButton);
|
22
|
+
expect(preformattedButton.className).toContain('is-active');
|
23
|
+
});
|
24
|
+
|
25
|
+
it('Should render a check icon if button is active', () => {
|
26
|
+
const { baseElement, getByRole } = render(<Editor />);
|
27
|
+
expect(baseElement).toBeTruthy();
|
28
|
+
|
29
|
+
const preformattedButton = getByRole('button', { name: 'Preformatted' });
|
30
|
+
expect(preformattedButton.querySelector('svg[data-testid="CheckIcon"]')).toBeFalsy();
|
31
|
+
|
32
|
+
fireEvent.click(preformattedButton);
|
33
|
+
expect(preformattedButton.querySelector('svg[data-testid="CheckIcon"]')).toBeTruthy();
|
34
|
+
});
|
35
|
+
|
36
|
+
it('Should apply preformatted tag to editor after clicking button', () => {
|
37
|
+
const { baseElement, getByRole } = render(<Editor />);
|
38
|
+
expect(baseElement).toBeTruthy();
|
39
|
+
expect(baseElement.querySelector('div.remirror-editor pre')).toBeFalsy();
|
40
|
+
|
41
|
+
const preformattedButton = getByRole('button', { name: 'Preformatted' });
|
42
|
+
expect(preformattedButton).toBeTruthy();
|
43
|
+
fireEvent.click(preformattedButton);
|
44
|
+
|
45
|
+
expect(baseElement.querySelector(`div.remirror-editor pre`)).toBeTruthy();
|
46
|
+
});
|
47
|
+
});
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useCommands, useActive } from '@remirror/react';
|
3
|
+
import { PreformattedExtension } from '../../../../Extensions/PreformattedExtension/PreformattedExtension';
|
4
|
+
import DropdownButton from '../../../../ui/ToolbarDropdownButton/ToolbarDropdownButton';
|
5
|
+
|
6
|
+
const PreformattedButton = () => {
|
7
|
+
const { togglePreformatted } = useCommands<PreformattedExtension>();
|
8
|
+
|
9
|
+
const active = useActive<PreformattedExtension>();
|
10
|
+
const enabled = togglePreformatted.enabled();
|
11
|
+
|
12
|
+
const handleSelect = () => {
|
13
|
+
if (togglePreformatted.enabled()) {
|
14
|
+
togglePreformatted();
|
15
|
+
}
|
16
|
+
};
|
17
|
+
|
18
|
+
return (
|
19
|
+
<DropdownButton
|
20
|
+
handleOnClick={handleSelect}
|
21
|
+
isDisabled={!enabled}
|
22
|
+
isActive={active.preformatted()}
|
23
|
+
label="Preformatted"
|
24
|
+
>
|
25
|
+
<pre>Preformatted</pre>
|
26
|
+
</DropdownButton>
|
27
|
+
);
|
28
|
+
};
|
29
|
+
|
30
|
+
export default PreformattedButton;
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
const dropdownButtons = [
|
7
|
+
'Paragraph',
|
8
|
+
'Heading 1',
|
9
|
+
'Heading 2',
|
10
|
+
'Heading 3',
|
11
|
+
'Heading 4',
|
12
|
+
'Heading 5',
|
13
|
+
'Heading 6',
|
14
|
+
'Preformatted',
|
15
|
+
];
|
16
|
+
|
17
|
+
const navigateKeys = ['Enter', 'Space'];
|
18
|
+
|
19
|
+
describe('Text type dropdown', () => {
|
20
|
+
it('should render the text type dropdown component', () => {
|
21
|
+
const { baseElement } = render(<Editor />);
|
22
|
+
expect(baseElement).toBeTruthy();
|
23
|
+
expect(baseElement.querySelector('button#dropdownHoverButton')).toBeTruthy();
|
24
|
+
});
|
25
|
+
|
26
|
+
it.each(dropdownButtons)('should render %s button in text type dropdown', (type) => {
|
27
|
+
const { baseElement } = render(<Editor />);
|
28
|
+
expect(baseElement).toBeTruthy();
|
29
|
+
|
30
|
+
const dropdownButton = baseElement.querySelector('button#dropdownHoverButton') as HTMLButtonElement;
|
31
|
+
expect(dropdownButton).toBeTruthy();
|
32
|
+
fireEvent.click(dropdownButton);
|
33
|
+
|
34
|
+
expect(baseElement.querySelector(`button[title="${type}"]`)).toBeTruthy();
|
35
|
+
});
|
36
|
+
|
37
|
+
it.each(navigateKeys)(`should display dropdown if '%s' is pressed while component is in focus`, async (key) => {
|
38
|
+
const { baseElement } = render(<Editor />);
|
39
|
+
expect(baseElement).toBeTruthy();
|
40
|
+
|
41
|
+
const dropdownMenu = baseElement.querySelector('div.toolbar-dropdown__menu') as HTMLDivElement;
|
42
|
+
expect(dropdownMenu.className).not.toContain('block'); // Hidden
|
43
|
+
|
44
|
+
const dropdownButton = baseElement.querySelector('button#dropdownHoverButton') as HTMLButtonElement;
|
45
|
+
fireEvent.keyDown(dropdownButton, { code: key });
|
46
|
+
|
47
|
+
setTimeout(() => {
|
48
|
+
expect(dropdownMenu.className).toContain('block'); // Visible
|
49
|
+
}, 50);
|
50
|
+
});
|
51
|
+
});
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import HeadingButton from './Heading/HeadingButton';
|
3
|
+
import ParagraphButton from './Paragraph/ParagraphButton';
|
4
|
+
import PreformattedButton from './Preformatted/PreformattedButton';
|
5
|
+
import ToolbarDropdown from '../../../ui/ToolbarDropdown/ToolbarDropdown';
|
6
|
+
import { useActive, VerticalDivider } from '@remirror/react';
|
7
|
+
import { PreformattedExtension } from '../../../Extensions/PreformattedExtension/PreformattedExtension';
|
8
|
+
|
9
|
+
const TextTypeDropdown = () => {
|
10
|
+
const active = useActive<PreformattedExtension>();
|
11
|
+
|
12
|
+
const activeLabel = () => {
|
13
|
+
// Determine if preformatted is active
|
14
|
+
if (active.preformatted()) {
|
15
|
+
return 'Preformatted';
|
16
|
+
}
|
17
|
+
// Determine if a heading is active
|
18
|
+
for (let i = 1; i <= 6; i++) {
|
19
|
+
if (active.heading({ level: i })) {
|
20
|
+
return `Heading ${i}`;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
// Default to paragraph
|
24
|
+
return 'Paragraph';
|
25
|
+
};
|
26
|
+
|
27
|
+
return (
|
28
|
+
<>
|
29
|
+
<ToolbarDropdown label={activeLabel()}>
|
30
|
+
<ParagraphButton />
|
31
|
+
<HeadingButton level={1} />
|
32
|
+
<HeadingButton level={2} />
|
33
|
+
<HeadingButton level={3} />
|
34
|
+
<HeadingButton level={4} />
|
35
|
+
<HeadingButton level={5} />
|
36
|
+
<HeadingButton level={6} />
|
37
|
+
<PreformattedButton />
|
38
|
+
</ToolbarDropdown>
|
39
|
+
<VerticalDivider className="editor-divider" />
|
40
|
+
</>
|
41
|
+
);
|
42
|
+
};
|
43
|
+
|
44
|
+
export default TextTypeDropdown;
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import '@testing-library/jest-dom';
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
4
|
+
import Editor from '../../../Editor/Editor';
|
5
|
+
|
6
|
+
describe('Underline button', () => {
|
7
|
+
it('Renders the underline button', () => {
|
8
|
+
render(<Editor />);
|
9
|
+
expect(screen.getByRole('button', { name: 'Underline (cmd+U)' })).toBeInTheDocument();
|
10
|
+
});
|
11
|
+
|
12
|
+
it('Activates the button if clicked', () => {
|
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)' });
|
16
|
+
fireEvent.click(underline);
|
17
|
+
expect(underline.classList.contains('is-active')).toBeTruthy();
|
18
|
+
});
|
19
|
+
});
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useCommands, useActive, useChainedCommands } from '@remirror/react';
|
3
|
+
import { UnderlineExtension } from '@remirror/extension-underline';
|
4
|
+
import ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
|
5
|
+
import FormatUnderlinedRoundedIcon from '@mui/icons-material/FormatUnderlinedRounded';
|
6
|
+
|
7
|
+
const UnderlineButton = () => {
|
8
|
+
const { toggleUnderline } = useCommands();
|
9
|
+
const chain = useChainedCommands();
|
10
|
+
|
11
|
+
const active = useActive<UnderlineExtension>();
|
12
|
+
const enabled = toggleUnderline.enabled();
|
13
|
+
const handleSelect = () => {
|
14
|
+
if (toggleUnderline.enabled()) {
|
15
|
+
chain.toggleUnderline().focus().run();
|
16
|
+
}
|
17
|
+
};
|
18
|
+
|
19
|
+
return (
|
20
|
+
<ToolbarButton
|
21
|
+
handleOnClick={handleSelect}
|
22
|
+
isDisabled={!enabled}
|
23
|
+
isActive={active.underline()}
|
24
|
+
icon={<FormatUnderlinedRoundedIcon />}
|
25
|
+
label="Underline (cmd+U)"
|
26
|
+
/>
|
27
|
+
);
|
28
|
+
};
|
29
|
+
|
30
|
+
export default UnderlineButton;
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
3
|
+
import Editor from '../../../Editor/Editor';
|
4
|
+
import React from 'react';
|
5
|
+
|
6
|
+
describe('Undo button', () => {
|
7
|
+
it('Renders the undo button', () => {
|
8
|
+
render(<Editor />);
|
9
|
+
expect(screen.getByRole('button', { name: 'Undo (cmd+Z)' })).toBeInTheDocument();
|
10
|
+
});
|
11
|
+
|
12
|
+
it('Renders a disabled button if you have not made any changes yet', () => {
|
13
|
+
render(<Editor />);
|
14
|
+
const undo = screen.getByRole('button', { name: 'Undo (cmd+Z)' });
|
15
|
+
expect(undo).toBeDisabled();
|
16
|
+
});
|
17
|
+
|
18
|
+
it('Enables the button when you perform an action', () => {
|
19
|
+
const { baseElement } = render(<Editor />);
|
20
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeFalsy();
|
21
|
+
|
22
|
+
const leftAlignButton = baseElement.querySelector('button[title="Align left"]') as HTMLButtonElement;
|
23
|
+
expect(leftAlignButton).toBeTruthy();
|
24
|
+
|
25
|
+
fireEvent.click(leftAlignButton);
|
26
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeTruthy();
|
27
|
+
const undo = screen.getByRole('button', { name: 'Undo (cmd+Z)' });
|
28
|
+
expect(undo).not.toBeDisabled();
|
29
|
+
});
|
30
|
+
|
31
|
+
it('Reverts this action when clicked', () => {
|
32
|
+
const { baseElement } = render(<Editor />);
|
33
|
+
|
34
|
+
// perform some action
|
35
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeFalsy();
|
36
|
+
const leftAlignButton = baseElement.querySelector('button[title="Align left"]') as HTMLButtonElement;
|
37
|
+
expect(leftAlignButton).toBeTruthy();
|
38
|
+
fireEvent.click(leftAlignButton);
|
39
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeTruthy();
|
40
|
+
|
41
|
+
// Check that this enables the undo button
|
42
|
+
const undo = screen.getByRole('button', { name: 'Undo (cmd+Z)' });
|
43
|
+
expect(undo).not.toBeDisabled();
|
44
|
+
|
45
|
+
// Click the undo button and check that this has reverted the previous action
|
46
|
+
fireEvent.click(undo);
|
47
|
+
expect(baseElement.querySelector('p[data-node-text-align="left"]')).toBeFalsy();
|
48
|
+
});
|
49
|
+
});
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useCommands, useHelpers } from '@remirror/react';
|
3
|
+
import { HistoryExtension } from 'remirror/extensions';
|
4
|
+
import ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
|
5
|
+
import UndoRoundedIcon from '@mui/icons-material/UndoRounded';
|
6
|
+
|
7
|
+
const UndoButton = () => {
|
8
|
+
const { undo } = useCommands<HistoryExtension>();
|
9
|
+
const { undoDepth } = useHelpers<HistoryExtension>(true);
|
10
|
+
|
11
|
+
const handleSelect = () => {
|
12
|
+
if (undo.enabled()) {
|
13
|
+
undo();
|
14
|
+
}
|
15
|
+
};
|
16
|
+
|
17
|
+
const enabled = undoDepth() > 0;
|
18
|
+
|
19
|
+
return (
|
20
|
+
<ToolbarButton
|
21
|
+
handleOnClick={handleSelect}
|
22
|
+
isDisabled={!enabled}
|
23
|
+
isActive={false}
|
24
|
+
icon={<UndoRoundedIcon />}
|
25
|
+
label="Undo (cmd+Z)"
|
26
|
+
/>
|
27
|
+
);
|
28
|
+
};
|
29
|
+
|
30
|
+
export default UndoButton;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
.formatted-text-editor .editor-toolbar {
|
2
|
+
@apply bg-white border-gray-200 border-b-2 border-solid p-1;
|
3
|
+
|
4
|
+
display: flex;
|
5
|
+
justify-items: center;
|
6
|
+
|
7
|
+
> *:not(:first-child) {
|
8
|
+
margin: 0 0 0 2px;
|
9
|
+
}
|
10
|
+
|
11
|
+
.editor-divider {
|
12
|
+
@apply -my-1 mx-1 border;
|
13
|
+
margin-right: 2px;
|
14
|
+
height: auto;
|
15
|
+
}
|
16
|
+
}
|