@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
package/.eslintrc.json
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
{
|
2
|
+
"env": {
|
3
|
+
"jest/globals": true,
|
4
|
+
"cypress/globals": true
|
5
|
+
},
|
6
|
+
"plugins": ["cypress"],
|
7
|
+
"extends": [
|
8
|
+
"plugin:react/recommended",
|
9
|
+
"plugin:jsx-a11y/recommended",
|
10
|
+
"plugin:cypress/recommended",
|
11
|
+
"plugin:jest/style",
|
12
|
+
"plugin:jest/recommended"
|
13
|
+
],
|
14
|
+
"overrides": [
|
15
|
+
{
|
16
|
+
// Cypress use different assertion methods. Don't validate that tests have assertions.
|
17
|
+
"files": ["**/*.spec.cy.ts"],
|
18
|
+
"rules": { "jest/expect-expect": "off" }
|
19
|
+
}
|
20
|
+
],
|
21
|
+
"settings": {
|
22
|
+
"react": {
|
23
|
+
// Tells eslint-plugin-react to automatically detect the version of React to use.
|
24
|
+
"version": "detect"
|
25
|
+
},
|
26
|
+
// Tells eslint how to resolve imports
|
27
|
+
"import/resolver": {
|
28
|
+
"node": {
|
29
|
+
"paths": ["src"],
|
30
|
+
"extensions": [".js", ".jsx", ".ts", ".tsx"]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
package/CHANGELOG.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
5
|
+
|
6
|
+
## [1.6.1-alpha.5](https://gitlab.squiz.net/developer-experience/cmp/compare/v1.6.1-alpha.3...v1.6.1-alpha.5) (2023-02-02)
|
7
|
+
|
8
|
+
**Note:** Version bump only for package @squiz/formatted-text-editor
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
## [1.6.1-alpha.4](https://gitlab.squiz.net/developer-experience/cmp/compare/v1.6.1-alpha.3...v1.6.1-alpha.4) (2023-02-02)
|
15
|
+
|
16
|
+
**Note:** Version bump only for package @squiz/formatted-text-editor
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
## [1.6.1-alpha.3](https://gitlab.squiz.net/developer-experience/cmp/compare/v1.6.1-alpha.2...v1.6.1-alpha.3) (2023-02-02)
|
23
|
+
|
24
|
+
**Note:** Version bump only for package @squiz/formatted-text-editor
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
## [1.6.1-alpha.2](https://gitlab.squiz.net/developer-experience/cmp/compare/v1.6.0...v1.6.1-alpha.2) (2023-02-02)
|
31
|
+
|
32
|
+
**Note:** Version bump only for package @squiz/formatted-text-editor
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
## [1.6.1-alpha.1](https://gitlab.squiz.net/developer-experience/cmp/compare/v1.6.0...v1.6.1-alpha.1) (2023-02-02)
|
39
|
+
|
40
|
+
**Note:** Version bump only for package @squiz/formatted-text-editor
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
## [1.6.1-alpha.0](https://gitlab.squiz.net/developer-experience/cmp/compare/v1.6.0...v1.6.1-alpha.0) (2023-02-02)
|
47
|
+
|
48
|
+
**Note:** Version bump only for package @squiz/formatted-text-editor
|
package/README.md
CHANGED
@@ -51,6 +51,8 @@ Or if you'd like to "watch" for changes:
|
|
51
51
|
npm run test:watch
|
52
52
|
```
|
53
53
|
|
54
|
+
When testing text input in the text editor, it is recommended to use the `<MockEditor />` component, which is a simple wrapper around the Remirror component that provides additional functionality for testing (supplying text into the editor).
|
55
|
+
|
54
56
|
### End to end testing
|
55
57
|
|
56
58
|
This package uses [Cypress](https://docs.cypress.io/) for end to end testing.
|
@@ -60,6 +62,3 @@ To run tests locally you can run:
|
|
60
62
|
npm run test:e2e
|
61
63
|
```
|
62
64
|
Cypress is configured to look at a preview dev environment on `http://localhost:8080`.
|
63
|
-
|
64
|
-
|
65
|
-
## WIP: Publish package to NPM
|
package/build.js
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
const esbuild = require('esbuild');
|
2
|
+
const { sassPlugin } = require('esbuild-sass-plugin');
|
3
|
+
const postcss = require('postcss');
|
4
|
+
const postcssConfig = require('./postcss.config').plugins;
|
5
|
+
|
6
|
+
esbuild
|
7
|
+
.build({
|
8
|
+
entryPoints: ['src/index.scss'],
|
9
|
+
bundle: true,
|
10
|
+
outdir: 'lib',
|
11
|
+
plugins: [
|
12
|
+
sassPlugin({
|
13
|
+
type: 'css',
|
14
|
+
transform: async (source) => {
|
15
|
+
const { css } = postcss(postcssConfig).process(source);
|
16
|
+
return css;
|
17
|
+
},
|
18
|
+
}),
|
19
|
+
],
|
20
|
+
})
|
21
|
+
.catch(() => process.exit(1));
|
@@ -0,0 +1,18 @@
|
|
1
|
+
describe('The formatted text editor renders', () => {
|
2
|
+
it('Should toggle bold on highlighted text', () => {
|
3
|
+
cy.visit('/');
|
4
|
+
|
5
|
+
cy.get('.ProseMirror.remirror-editor')
|
6
|
+
.clear()
|
7
|
+
.type('It was the best of times it was the worst of times.')
|
8
|
+
.setSelection('best of times');
|
9
|
+
|
10
|
+
cy.findAllByRole('button', { name: 'Bold (cmd+B)' }).first().click();
|
11
|
+
|
12
|
+
cy.get('strong').should('include.text', 'best of times');
|
13
|
+
|
14
|
+
cy.findAllByRole('button', { name: 'Bold (cmd+B)' }).first().click();
|
15
|
+
|
16
|
+
cy.get('strong').should('not.exist');
|
17
|
+
});
|
18
|
+
});
|
@@ -0,0 +1,130 @@
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
2
|
+
/// <reference path="../global.d.ts" />
|
3
|
+
|
4
|
+
import '@testing-library/cypress/add-commands';
|
5
|
+
|
6
|
+
// ***********************************************
|
7
|
+
// This example commands.ts shows you how to
|
8
|
+
// create various custom commands and overwrite
|
9
|
+
// existing commands.
|
10
|
+
//
|
11
|
+
// For more comprehensive examples of custom
|
12
|
+
// commands please read more here:
|
13
|
+
// https://on.cypress.io/custom-commands
|
14
|
+
// ***********************************************
|
15
|
+
//
|
16
|
+
//
|
17
|
+
// -- This is a parent command --
|
18
|
+
// Cypress.Commands.add('login', (email, password) => { ... })
|
19
|
+
//
|
20
|
+
//
|
21
|
+
// -- This is a child command --
|
22
|
+
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
23
|
+
//
|
24
|
+
//
|
25
|
+
// -- This is a dual command --
|
26
|
+
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
27
|
+
//
|
28
|
+
//
|
29
|
+
// -- This will overwrite an existing command --
|
30
|
+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
31
|
+
//
|
32
|
+
// declare global {
|
33
|
+
// namespace Cypress {
|
34
|
+
// interface Chainable {
|
35
|
+
// login(email: string, password: string): Chainable<void>
|
36
|
+
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
37
|
+
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
38
|
+
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
39
|
+
// }
|
40
|
+
// }
|
41
|
+
// }
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Credits
|
45
|
+
* @Bkucera: https://github.com/cypress-io/cypress/issues/2839#issuecomment-447012818
|
46
|
+
* @Phrogz: https://stackoverflow.com/a/10730777/1556245
|
47
|
+
*
|
48
|
+
* Usage
|
49
|
+
* ```
|
50
|
+
* // Types "foo" and then selects "fo"
|
51
|
+
* cy.get('input')
|
52
|
+
* .type('foo')
|
53
|
+
* .setSelection('fo')
|
54
|
+
*
|
55
|
+
* // Types "foo", "bar", "baz", and "qux" on separate lines, then selects "foo", "bar", and "baz"
|
56
|
+
* cy.get('textarea')
|
57
|
+
* .type('foo{enter}bar{enter}baz{enter}qux{enter}')
|
58
|
+
* .setSelection('foo', 'baz')
|
59
|
+
*
|
60
|
+
* // Types "foo" and then sets the cursor before the last letter
|
61
|
+
* cy.get('input')
|
62
|
+
* .type('foo')
|
63
|
+
* .setCursorAfter('fo')
|
64
|
+
*
|
65
|
+
* // Types "foo" and then sets the cursor at the beginning of the word
|
66
|
+
* cy.get('input')
|
67
|
+
* .type('foo')
|
68
|
+
* .setCursorBefore('foo')
|
69
|
+
*
|
70
|
+
* // `setSelection` can alternatively target starting and ending nodes using query strings,
|
71
|
+
* // plus specific offsets. The queries are processed via `Element.querySelector`.
|
72
|
+
* cy.get('body')
|
73
|
+
* .setSelection({
|
74
|
+
* anchorQuery: 'ul > li > p', // required
|
75
|
+
* anchorOffset: 2 // default: 0
|
76
|
+
* focusQuery: 'ul > li > p:last-child', // default: anchorQuery
|
77
|
+
* focusOffset: 0 // default: 0
|
78
|
+
* })
|
79
|
+
*/
|
80
|
+
|
81
|
+
// Low level command reused by `setSelection` and low level command `setCursor`
|
82
|
+
Cypress.Commands.add('selection', { prevSubject: true }, (subject, fn) => {
|
83
|
+
cy.wrap(subject).trigger('mousedown').then(fn).trigger('mouseup');
|
84
|
+
|
85
|
+
cy.document().trigger('selectionchange');
|
86
|
+
return cy.wrap(subject);
|
87
|
+
});
|
88
|
+
|
89
|
+
Cypress.Commands.add('setSelection', { prevSubject: true }, (subject, query, endQuery) => {
|
90
|
+
return cy.wrap(subject).selection(($el: any[]) => {
|
91
|
+
if (typeof query === 'string') {
|
92
|
+
const anchorNode = getTextNode($el[0], query);
|
93
|
+
const focusNode = endQuery ? getTextNode($el[0], endQuery) : anchorNode;
|
94
|
+
const anchorOffset = anchorNode.wholeText.indexOf(query);
|
95
|
+
const focusOffset = endQuery
|
96
|
+
? focusNode.wholeText.indexOf(endQuery) + endQuery.length
|
97
|
+
: anchorOffset + query.length;
|
98
|
+
setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
|
99
|
+
} else if (typeof query === 'object') {
|
100
|
+
const el = $el[0];
|
101
|
+
const anchorNode = getTextNode(el.querySelector(query.anchorQuery));
|
102
|
+
const anchorOffset = query.anchorOffset || 0;
|
103
|
+
const focusNode = query.focusQuery ? getTextNode(el.querySelector(query.focusQuery)) : anchorNode;
|
104
|
+
const focusOffset = query.focusOffset || 0;
|
105
|
+
setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
|
106
|
+
}
|
107
|
+
});
|
108
|
+
});
|
109
|
+
|
110
|
+
// Helper functions
|
111
|
+
function getTextNode(el: HTMLElement, match?: any) {
|
112
|
+
const walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
|
113
|
+
if (!match) {
|
114
|
+
return walk.nextNode();
|
115
|
+
}
|
116
|
+
|
117
|
+
// const nodes = [];
|
118
|
+
let node;
|
119
|
+
while ((node = walk.nextNode())) {
|
120
|
+
if (node.wholeText.includes(match)) {
|
121
|
+
return node;
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
function setBaseAndExtent(...args) {
|
127
|
+
const document = args[0].ownerDocument;
|
128
|
+
document.getSelection().removeAllRanges();
|
129
|
+
document.getSelection().setBaseAndExtent(...args);
|
130
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
// ***********************************************************
|
2
|
+
// This example support/e2e.ts is processed and
|
3
|
+
// loaded automatically before your test files.
|
4
|
+
//
|
5
|
+
// This is a great place to put global configuration and
|
6
|
+
// behavior that modifies Cypress.
|
7
|
+
//
|
8
|
+
// You can change the location of this file or turn off
|
9
|
+
// automatically serving support files with the
|
10
|
+
// 'supportFile' configuration option.
|
11
|
+
//
|
12
|
+
// You can read more here:
|
13
|
+
// https://on.cypress.io/configuration
|
14
|
+
// ***********************************************************
|
15
|
+
|
16
|
+
// Import commands.js using ES2015 syntax:
|
17
|
+
import './commands';
|
18
|
+
|
19
|
+
// Alternatively you can use CommonJS syntax:
|
20
|
+
// require('./commands')
|
package/demo/App.tsx
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import FormattedTextEditor from '../src/FormattedTextEditor';
|
3
|
+
import { RemirrorEventListener, Extension } from '@remirror/core';
|
4
|
+
|
5
|
+
function App() {
|
6
|
+
const [doc, setDoc] = useState(null as any);
|
7
|
+
const [editable, setEditable] = useState(true);
|
8
|
+
const handleEditorChange: RemirrorEventListener<Extension> = (parameter) => {
|
9
|
+
if (doc !== parameter.state.doc) {
|
10
|
+
setDoc(parameter.state.doc);
|
11
|
+
}
|
12
|
+
};
|
13
|
+
|
14
|
+
return (
|
15
|
+
<div className="app">
|
16
|
+
<h1>Options</h1>
|
17
|
+
<div className="page-section">
|
18
|
+
<div className="form-group">
|
19
|
+
<label htmlFor="editable">Editable</label>
|
20
|
+
<input id="editable" type="checkbox" onChange={() => setEditable(!editable)} checked={editable} />
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
<h1>Editor</h1>
|
24
|
+
<div className="page-section">
|
25
|
+
<FormattedTextEditor
|
26
|
+
editable={editable}
|
27
|
+
content={`<p>Hello <a href="https://www.google.com"><strong>Mr Bean</strong></a>, nice to <a href="https://www.google.com">meet you</a>.</p>`}
|
28
|
+
onChange={handleEditorChange}
|
29
|
+
/>
|
30
|
+
</div>
|
31
|
+
<h1>Document</h1>
|
32
|
+
<div className="page-section">
|
33
|
+
<code>{JSON.stringify(doc, null, 2)}</code>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
);
|
37
|
+
}
|
38
|
+
|
39
|
+
export default App;
|
package/demo/index.html
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8" />
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon-dxp.svg" />
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7
|
+
<title>Formatted text editor - DXP package (SQUIZ)</title>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div id="root"></div>
|
11
|
+
<script type="module" src="./main.tsx"></script>
|
12
|
+
</body>
|
13
|
+
</html>
|
package/demo/index.scss
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
@import '../src/index.scss';
|
2
|
+
|
3
|
+
body {
|
4
|
+
font-family: 'Open Sans';
|
5
|
+
}
|
6
|
+
|
7
|
+
h1 {
|
8
|
+
font-weight: 600;
|
9
|
+
font-size: 1.5rem;
|
10
|
+
}
|
11
|
+
|
12
|
+
.app {
|
13
|
+
padding: 8px;
|
14
|
+
}
|
15
|
+
|
16
|
+
.page-section {
|
17
|
+
margin-bottom: 8px;
|
18
|
+
}
|
19
|
+
|
20
|
+
.form-group {
|
21
|
+
display: flex;
|
22
|
+
align-items: center;
|
23
|
+
}
|
24
|
+
|
25
|
+
code {
|
26
|
+
padding: 8px;
|
27
|
+
display: block;
|
28
|
+
white-space: pre;
|
29
|
+
background-color: #eee;
|
30
|
+
font-size: 0.8rem;
|
31
|
+
height: 40vh;
|
32
|
+
max-height: 40vh;
|
33
|
+
overflow: scroll;
|
34
|
+
}
|
35
|
+
|
36
|
+
.remirror-editor {
|
37
|
+
height: 40vh;
|
38
|
+
max-height: 40vh;
|
39
|
+
overflow: scroll;
|
40
|
+
}
|
package/demo/main.tsx
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import ReactDOM from 'react-dom/client';
|
3
|
+
import App from './App';
|
4
|
+
import './index.scss';
|
5
|
+
|
6
|
+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
7
|
+
<React.StrictMode>
|
8
|
+
<App />
|
9
|
+
</React.StrictMode>,
|
10
|
+
);
|
@@ -0,0 +1,3 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16">
|
2
|
+
<path fill="#1D9BE2" fill-rule="evenodd" d="M4 0a4 4 0 0 0-4 4v8a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4H4Zm-.883 5.766a4.01 4.01 0 0 0 1.66.909l5.334 1.49c.634.177 1.069-.628.574-1.062L5.532 2.59c-.829-.745-2.255-.827-3.001 0-1.078 1.342-.326 2.328.586 3.176Zm10.137-3.195c.848.761 1.05 2.188.203 2.95l-1.183.92a.903.903 0 0 1-1.134-.02l-1.091-.913a1.846 1.846 0 0 1 .137-2.937c.848-.761 2.221-.761 3.068 0Zm-2.03 6.754a4.01 4.01 0 0 1 1.66.909c.911.848 1.663 1.834.585 3.176-.746.827-2.172.745-3 0L5.314 8.897c-.495-.434-.06-1.239.574-1.061l5.334 1.49ZM2.745 13.43c-.848-.761-1.05-2.188-.203-2.95l1.183-.92a.903.903 0 0 1 1.134.02l1.091.913a1.846 1.846 0 0 1-.137 2.937c-.847.761-2.221.761-3.068 0Z" clip-rule="evenodd"/>
|
3
|
+
</svg>
|
@@ -0,0 +1 @@
|
|
1
|
+
/// <reference types="vite/client" />
|
@@ -0,0 +1 @@
|
|
1
|
+
module.exports = {};
|
package/jest.config.ts
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
export default {
|
2
|
+
preset: 'ts-jest',
|
3
|
+
maxWorkers: 1,
|
4
|
+
clearMocks: true,
|
5
|
+
collectCoverage: true,
|
6
|
+
collectCoverageFrom: ['src/**/*.{ts,tsx}'],
|
7
|
+
errorOnDeprecated: true,
|
8
|
+
fakeTimers: {
|
9
|
+
enableGlobally: true,
|
10
|
+
},
|
11
|
+
resetMocks: true,
|
12
|
+
resetModules: true,
|
13
|
+
testEnvironment: 'jsdom',
|
14
|
+
moduleNameMapper: {
|
15
|
+
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|css|scss)$':
|
16
|
+
'<rootDir>/file-transformer.js',
|
17
|
+
'^react($|/.+)': '<rootDir>/node_modules/react$1',
|
18
|
+
'^react-dom($|/.+)': '<rootDir>/node_modules/react-dom$1',
|
19
|
+
},
|
20
|
+
setupFilesAfterEnv: ['<rootDir>/jest.bootstrap.ts'],
|
21
|
+
// TODO: enable once directory structure is sorted and we have tests/more complete code being written.
|
22
|
+
// coverageThreshold: {
|
23
|
+
// global: {
|
24
|
+
// branches: 90,
|
25
|
+
// functions: 90,
|
26
|
+
// lines: 90,
|
27
|
+
// statements: 90,
|
28
|
+
// },
|
29
|
+
// },
|
30
|
+
};
|
package/lib/Editor/Editor.d.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
import { RemirrorContentType } from '@remirror/core';
|
1
|
+
import { RemirrorContentType, RemirrorEventListener, Extension } from '@remirror/core';
|
2
2
|
type EditorProps = {
|
3
3
|
content?: RemirrorContentType;
|
4
|
+
onChange?: RemirrorEventListener<Extension>;
|
5
|
+
editable?: boolean;
|
4
6
|
};
|
5
|
-
declare const Editor: ({ content }: EditorProps) => JSX.Element;
|
7
|
+
declare const Editor: ({ content, editable, onChange }: EditorProps) => JSX.Element;
|
6
8
|
export default Editor;
|
package/lib/Editor/Editor.js
CHANGED
@@ -1,26 +1,23 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import { BoldExtension, ItalicExtension, NodeFormattingExtension, UnderlineExtension, wysiwygPreset, } from 'remirror/extensions';
|
3
2
|
import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
|
4
|
-
import {
|
5
|
-
|
3
|
+
import { Toolbar, FloatingToolbar } from '../EditorToolbar';
|
4
|
+
import { Extensions } from '../Extensions/Extensions';
|
5
|
+
const Editor = ({ content, editable, onChange }) => {
|
6
6
|
const { manager, state, setState } = useRemirror({
|
7
|
-
extensions:
|
8
|
-
...wysiwygPreset(),
|
9
|
-
new BoldExtension(),
|
10
|
-
new ItalicExtension(),
|
11
|
-
new NodeFormattingExtension(),
|
12
|
-
new UnderlineExtension(),
|
13
|
-
],
|
7
|
+
extensions: Extensions,
|
14
8
|
content,
|
15
9
|
selection: 'start',
|
16
10
|
stringHandler: 'html',
|
17
11
|
});
|
18
12
|
const handleChange = (parameter) => {
|
19
13
|
setState(parameter.state);
|
14
|
+
onChange?.(parameter);
|
20
15
|
};
|
21
|
-
return (React.createElement(
|
22
|
-
React.createElement(
|
23
|
-
|
24
|
-
|
16
|
+
return (React.createElement("div", { className: "squiz-fte-scope" },
|
17
|
+
React.createElement("div", { className: "remirror-theme formatted-text-editor editor-wrapper" },
|
18
|
+
React.createElement(Remirror, { manager: manager, state: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor" },
|
19
|
+
React.createElement(Toolbar, null),
|
20
|
+
React.createElement(EditorComponent, null),
|
21
|
+
React.createElement(FloatingToolbar, null)))));
|
25
22
|
};
|
26
23
|
export default Editor;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const FloatingToolbar: () => JSX.Element;
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import React, { useMemo } from 'react';
|
2
|
+
import ItalicButton from './Tools/Italic/ItalicButton';
|
3
|
+
import UnderlineButton from './Tools/Underline/UnderlineButton';
|
4
|
+
import BoldButton from './Tools/Bold/BoldButton';
|
5
|
+
import { useExtensionNames } from '../hooks';
|
6
|
+
import RemoveLinkButton from './Tools/Link/RemoveLinkButton';
|
7
|
+
import LinkButton from './Tools/Link/LinkButton';
|
8
|
+
import { FloatingToolbar as RemirrorFloatingToolbar, usePositioner } from '@remirror/react';
|
9
|
+
import { VerticalDivider } from '@remirror/react-components';
|
10
|
+
import { createToolbarPositioner } from '../utils/createToolbarPositioner';
|
11
|
+
// The editor main toolbar
|
12
|
+
export const FloatingToolbar = () => {
|
13
|
+
const extensionNames = useExtensionNames();
|
14
|
+
const positioner = useMemo(() => createToolbarPositioner({ types: ['link'] }), []);
|
15
|
+
const { data } = usePositioner(positioner, []);
|
16
|
+
let buttons = [
|
17
|
+
extensionNames.bold && React.createElement(BoldButton, { key: "bold" }),
|
18
|
+
extensionNames.italic && React.createElement(ItalicButton, { key: "italic" }),
|
19
|
+
extensionNames.underline && React.createElement(UnderlineButton, { key: "underline" }),
|
20
|
+
];
|
21
|
+
if (data.marks?.link.isExclusivelyActive) {
|
22
|
+
// if all of the selected text is a link show the options to update/remove the link instead of the regular
|
23
|
+
// formatting options.
|
24
|
+
buttons = [React.createElement(LinkButton, { key: "update-link", inPopover: true }), React.createElement(RemoveLinkButton, { key: "remove-link" })];
|
25
|
+
}
|
26
|
+
else if (!data.marks?.link.isActive) {
|
27
|
+
// if none of the selected text is a link show the option to create a link.
|
28
|
+
buttons.push(React.createElement(VerticalDivider, { key: "link-divider", className: "link-divider" }), React.createElement(LinkButton, { key: "add-link", inPopover: true }));
|
29
|
+
}
|
30
|
+
return (React.createElement(RemirrorFloatingToolbar, { className: "squiz-fte-scope squiz-fte-scope__floating-popover", positioner: positioner }, buttons));
|
31
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const Toolbar: () => JSX.Element;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Toolbar as RemirrorToolbar, VerticalDivider } from '@remirror/react-components';
|
3
|
+
import ItalicButton from './Tools/Italic/ItalicButton';
|
4
|
+
import UnderlineButton from './Tools/Underline/UnderlineButton';
|
5
|
+
import BoldButton from './Tools/Bold/BoldButton';
|
6
|
+
import TextAlignButtons from './Tools/TextAlign/TextAlignButtons';
|
7
|
+
import UndoButton from './Tools/Undo/UndoButton';
|
8
|
+
import RedoButton from './Tools/Redo/RedoButton';
|
9
|
+
import TextTypeDropdown from './Tools/TextType/TextTypeDropdown';
|
10
|
+
import { useExtensionNames } from '../hooks';
|
11
|
+
import LinkButton from './Tools/Link/LinkButton';
|
12
|
+
export const Toolbar = () => {
|
13
|
+
const extensionNames = useExtensionNames();
|
14
|
+
return (React.createElement(RemirrorToolbar, { className: "remirror-toolbar editor-toolbar" },
|
15
|
+
extensionNames.history && (React.createElement(React.Fragment, null,
|
16
|
+
React.createElement(UndoButton, null),
|
17
|
+
React.createElement(RedoButton, null),
|
18
|
+
React.createElement(VerticalDivider, { className: "editor-divider" }))),
|
19
|
+
extensionNames.heading && extensionNames.paragraph && extensionNames.preformatted && React.createElement(TextTypeDropdown, null),
|
20
|
+
extensionNames.bold && React.createElement(BoldButton, null),
|
21
|
+
extensionNames.italic && React.createElement(ItalicButton, null),
|
22
|
+
extensionNames.underline && React.createElement(UnderlineButton, null),
|
23
|
+
extensionNames.nodeFormatting && React.createElement(TextAlignButtons, null),
|
24
|
+
extensionNames.link && React.createElement(LinkButton, null)));
|
25
|
+
};
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { ReactElement } from 'react';
|
2
|
+
import { SubmitHandler } from 'react-hook-form';
|
3
|
+
import { UpdateLinkOptions } from '../../../../Extensions/LinkExtension/LinkExtension';
|
4
|
+
export type LinkFormData = Pick<UpdateLinkOptions, 'href' | 'target' | 'title' | 'text'>;
|
5
|
+
export type FormProps = {
|
6
|
+
data: Partial<LinkFormData>;
|
7
|
+
onSubmit: SubmitHandler<LinkFormData>;
|
8
|
+
};
|
9
|
+
declare const LinkForm: ({ data, onSubmit }: FormProps) => ReactElement;
|
10
|
+
export default LinkForm;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { TextInput } from '../../../../ui/Inputs/Text/TextInput';
|
3
|
+
import { Select } from '../../../../ui/Inputs/Select/Select';
|
4
|
+
import { useForm } from 'react-hook-form';
|
5
|
+
const selectOptions = {
|
6
|
+
_self: { label: 'Current window' },
|
7
|
+
_blank: { label: 'New window' },
|
8
|
+
};
|
9
|
+
const LinkForm = ({ data, onSubmit }) => {
|
10
|
+
const { register, handleSubmit, setValue } = useForm({
|
11
|
+
defaultValues: data,
|
12
|
+
});
|
13
|
+
return (React.createElement("form", { className: "squiz-fte-form", onSubmit: handleSubmit(onSubmit) },
|
14
|
+
React.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
15
|
+
React.createElement(TextInput, { label: "URL", ...register('href') })),
|
16
|
+
React.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
17
|
+
React.createElement(TextInput, { label: "Text", ...register('text') })),
|
18
|
+
React.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
19
|
+
React.createElement(TextInput, { label: "Title", ...register('title') })),
|
20
|
+
React.createElement("div", { className: "squiz-fte-form-group mb-0" },
|
21
|
+
React.createElement(Select, { name: "target", label: "Target", value: data.target || '_self', options: selectOptions, onChange: (value) => setValue('target', value) }))));
|
22
|
+
};
|
23
|
+
export default LinkForm;
|