@squiz/formatted-text-editor 1.34.1-alpha.3 → 1.34.1-alpha.5

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.
@@ -39,10 +39,12 @@ const react_components_1 = require("@remirror/react-components");
39
39
  const createToolbarPositioner_1 = require("../utils/createToolbarPositioner");
40
40
  const ImageButton_1 = __importDefault(require("./Tools/Image/ImageButton"));
41
41
  const Extensions_1 = require("../Extensions/Extensions");
42
+ const ClearFormattingButton_1 = __importDefault(require("./Tools/ClearFormatting/ClearFormattingButton"));
42
43
  const FloatingToolbar = () => {
43
44
  const watchedMarks = [Extensions_1.MarkName.Link, Extensions_1.MarkName.AssetLink];
44
45
  const extensionNames = (0, hooks_1.useExtensionNames)();
45
46
  const positioner = (0, react_1.useMemo)(() => (0, createToolbarPositioner_1.createToolbarPositioner)({ types: watchedMarks }), []);
47
+ const { clearFormatting } = (0, react_2.useCommands)();
46
48
  const active = (0, react_2.useActive)();
47
49
  const { data: { marks }, } = (0, react_2.usePositioner)(positioner, []);
48
50
  let buttons = [
@@ -65,6 +67,10 @@ const FloatingToolbar = () => {
65
67
  // if none of the selected text is a link show the option to create a link.
66
68
  buttons.push(react_1.default.createElement(react_components_1.VerticalDivider, { key: "link-divider", className: "editor-divider" }), react_1.default.createElement(LinkButton_1.default, { key: "add-link", inPopover: true }));
67
69
  }
70
+ // Clear formatting will always be the last button in the toolbar
71
+ if (extensionNames.clearFormatting && clearFormatting.enabled()) {
72
+ buttons.push(react_1.default.createElement(ClearFormattingButton_1.default, { key: "clearFormatting" }));
73
+ }
68
74
  return (react_1.default.createElement(react_2.FloatingToolbar, { className: "squiz-fte-scope squiz-fte-scope__floating-popover", positioner: positioner }, buttons));
69
75
  };
70
76
  exports.FloatingToolbar = FloatingToolbar;
@@ -17,6 +17,7 @@ const hooks_1 = require("../hooks");
17
17
  const LinkButton_1 = __importDefault(require("./Tools/Link/LinkButton"));
18
18
  const ImageButton_1 = __importDefault(require("./Tools/Image/ImageButton"));
19
19
  const RemoveLinkButton_1 = __importDefault(require("./Tools/Link/RemoveLinkButton"));
20
+ const ClearFormattingButton_1 = __importDefault(require("./Tools/ClearFormatting/ClearFormattingButton"));
20
21
  const Toolbar = () => {
21
22
  const extensionNames = (0, hooks_1.useExtensionNames)();
22
23
  return (react_1.default.createElement(react_components_1.Toolbar, { className: "remirror-toolbar editor-toolbar" },
@@ -32,6 +33,7 @@ const Toolbar = () => {
32
33
  extensionNames.link && (react_1.default.createElement(react_1.default.Fragment, null,
33
34
  react_1.default.createElement(LinkButton_1.default, null),
34
35
  react_1.default.createElement(RemoveLinkButton_1.default, null))),
35
- extensionNames.image && react_1.default.createElement(ImageButton_1.default, null)));
36
+ extensionNames.image && react_1.default.createElement(ImageButton_1.default, null),
37
+ extensionNames.clearFormatting && react_1.default.createElement(ClearFormattingButton_1.default, null)));
36
38
  };
37
39
  exports.Toolbar = Toolbar;
@@ -0,0 +1,2 @@
1
+ declare const ClearFormattingButton: () => JSX.Element;
2
+ export default ClearFormattingButton;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const react_1 = __importStar(require("react"));
30
+ const react_2 = require("@remirror/react");
31
+ const react_components_1 = require("@remirror/react-components");
32
+ const FormatClearRounded_1 = __importDefault(require("@mui/icons-material/FormatClearRounded"));
33
+ const Button_1 = __importDefault(require("../../../ui/Button/Button"));
34
+ const ClearFormattingButton = () => {
35
+ const { clearFormatting } = (0, react_2.useCommands)();
36
+ const { selection } = (0, react_2.useEditorState)();
37
+ // Checks wether we have specific content selected or not
38
+ const contentSelected = !selection.empty;
39
+ const handleSelect = () => {
40
+ if (clearFormatting.enabled()) {
41
+ clearFormatting();
42
+ }
43
+ };
44
+ const handleShortcut = (0, react_1.useCallback)(() => {
45
+ handleSelect();
46
+ // Prevent other key handlers being run
47
+ return true;
48
+ }, []);
49
+ // When Ctrl+\ is pressed clear formatting, only registered in the toolbar button instance to avoid the key press
50
+ // being double handled.
51
+ (0, react_2.useKeymap)('Mod-\\', handleShortcut);
52
+ return (react_1.default.createElement(react_1.default.Fragment, null,
53
+ react_1.default.createElement(react_components_1.VerticalDivider, null),
54
+ react_1.default.createElement(Button_1.default, { handleOnClick: handleSelect, isDisabled: false, isActive: false, icon: react_1.default.createElement(FormatClearRounded_1.default, null), label: `${contentSelected ? 'Clear formatting from selection' : 'Clear all formatting'} (cmd+\\)` })));
55
+ };
56
+ exports.default = ClearFormattingButton;
@@ -0,0 +1,5 @@
1
+ import { PlainExtension, CommandFunction } from '@remirror/core';
2
+ export declare class ClearFormattingExtension extends PlainExtension {
3
+ get name(): "clearFormatting";
4
+ clearFormatting(): CommandFunction;
5
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ClearFormattingExtension = void 0;
10
+ const core_1 = require("@remirror/core");
11
+ const getNodeNamesByGroup_1 = require("../../utils/getNodeNamesByGroup");
12
+ const getMarkNamesByGroup_1 = require("../../utils/getMarkNamesByGroup");
13
+ let ClearFormattingExtension = class ClearFormattingExtension extends core_1.PlainExtension {
14
+ get name() {
15
+ return 'clearFormatting';
16
+ }
17
+ clearFormatting() {
18
+ return ({ dispatch, tr, state, view }) => {
19
+ const { empty, ranges } = state.selection;
20
+ const schema = state.schema;
21
+ const formattingNodes = (0, getNodeNamesByGroup_1.getNodeNamesByGroup)(schema, core_1.ExtensionTag.FormattingNode);
22
+ const formattingMarks = (0, getMarkNamesByGroup_1.getMarkNamesByGroup)(schema, core_1.ExtensionTag.FormattingMark);
23
+ let isChanged = false;
24
+ ranges.forEach(({ $from, $to }) => {
25
+ // Check if there is a selection or not, if no selection use the doc content size as the range
26
+ state.doc.nodesBetween(empty ? 0 : $from.pos, empty ? state.doc.content.size : $to.pos, (node, pos) => {
27
+ // Clear marks (bold, italic, etc)
28
+ node.marks.forEach((mark) => {
29
+ if (formattingMarks.includes(mark.type.name)) {
30
+ tr.removeMark(pos, pos + node.nodeSize, mark);
31
+ isChanged = true;
32
+ }
33
+ });
34
+ // Leave non-foramtting nodes as-is
35
+ if (!formattingNodes.includes(node.type.name)) {
36
+ return;
37
+ }
38
+ // Clear node attributes & set to paragraph by default
39
+ if (node.type.name === schema.nodes.paragraph.name) {
40
+ const { nodeTextAlignment } = node.attrs;
41
+ if (nodeTextAlignment && nodeTextAlignment !== 'left') {
42
+ tr.setNodeAttribute(pos, 'nodeTextAlignment', null);
43
+ isChanged = true;
44
+ }
45
+ }
46
+ else {
47
+ tr.setNodeMarkup(pos, schema.nodes.paragraph, null, node.marks);
48
+ isChanged = true;
49
+ }
50
+ });
51
+ });
52
+ if (!empty) {
53
+ // Move the cursor to the end of the selection
54
+ tr.setSelection((0, core_1.getTextSelection)({ from: state.selection.to, to: state.selection.to }, tr.doc));
55
+ }
56
+ dispatch?.(tr);
57
+ view?.focus();
58
+ return isChanged;
59
+ };
60
+ }
61
+ };
62
+ __decorate([
63
+ (0, core_1.command)()
64
+ ], ClearFormattingExtension.prototype, "clearFormatting", null);
65
+ ClearFormattingExtension = __decorate([
66
+ (0, core_1.extension)({})
67
+ ], ClearFormattingExtension);
68
+ exports.ClearFormattingExtension = ClearFormattingExtension;
@@ -8,6 +8,7 @@ const LinkExtension_1 = require("./LinkExtension/LinkExtension");
8
8
  const ImageExtension_1 = require("./ImageExtension/ImageExtension");
9
9
  const CommandsExtension_1 = require("./CommandsExtension/CommandsExtension");
10
10
  const AssetImageExtension_1 = require("./ImageExtension/AssetImageExtension");
11
+ const ClearFormattingExtension_1 = require("./ClearFormattingExtension/ClearFormattingExtension");
11
12
  var NodeName;
12
13
  (function (NodeName) {
13
14
  NodeName["Image"] = "image";
@@ -40,6 +41,7 @@ const createExtensions = (context) => {
40
41
  new AssetLinkExtension_1.AssetLinkExtension({
41
42
  matrixDomain: context.matrix.matrixDomain,
42
43
  }),
44
+ new ClearFormattingExtension_1.ClearFormattingExtension(),
43
45
  ];
44
46
  };
45
47
  };
@@ -0,0 +1,2 @@
1
+ import { EditorSchema, ExtensionTagType } from '@remirror/core';
2
+ export declare const getMarkNamesByGroup: (schema: EditorSchema, group: ExtensionTagType) => string[];
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getMarkNamesByGroup = void 0;
4
+ const getMarkNamesByGroup = (schema, group) => {
5
+ return Object.values(schema.marks)
6
+ .filter((mark) => mark.spec.group?.includes(group))
7
+ .map((mark) => mark.name);
8
+ };
9
+ exports.getMarkNamesByGroup = getMarkNamesByGroup;
@@ -0,0 +1,2 @@
1
+ import { EditorSchema, ExtensionTagType } from '@remirror/core';
2
+ export declare const getNodeNamesByGroup: (schema: EditorSchema, group: ExtensionTagType) => string[];
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNodeNamesByGroup = void 0;
4
+ const getNodeNamesByGroup = (schema, group) => {
5
+ return Object.values(schema.nodes)
6
+ .filter((node) => node.spec.group?.includes(group))
7
+ .map((node) => node.name);
8
+ };
9
+ exports.getNodeNamesByGroup = getNodeNamesByGroup;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.34.1-alpha.3",
3
+ "version": "1.34.1-alpha.5",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "scripts": {
@@ -20,8 +20,8 @@
20
20
  "@headlessui/react": "1.7.11",
21
21
  "@mui/icons-material": "5.11.16",
22
22
  "@remirror/react": "2.0.25",
23
- "@squiz/dx-json-schema-lib": "1.34.1-alpha.3",
24
- "@squiz/resource-browser": "1.34.1-alpha.3",
23
+ "@squiz/dx-json-schema-lib": "1.34.1-alpha.5",
24
+ "@squiz/resource-browser": "1.34.1-alpha.5",
25
25
  "clsx": "1.2.1",
26
26
  "react-hook-form": "7.43.2",
27
27
  "react-image-size": "2.0.0",
@@ -75,5 +75,5 @@
75
75
  "volta": {
76
76
  "node": "18.15.0"
77
77
  },
78
- "gitHead": "c7360adacee0d77cdef12576f51a23fd228bc501"
78
+ "gitHead": "96b2fec093ea7e6746c0718074221b9e88b989ba"
79
79
  }
@@ -5,17 +5,20 @@ import BoldButton from './Tools/Bold/BoldButton';
5
5
  import { useExtensionNames } from '../hooks';
6
6
  import RemoveLinkButton from './Tools/Link/RemoveLinkButton';
7
7
  import LinkButton from './Tools/Link/LinkButton';
8
- import { FloatingToolbar as RemirrorFloatingToolbar, useActive, usePositioner } from '@remirror/react';
8
+ import { FloatingToolbar as RemirrorFloatingToolbar, useActive, usePositioner, useCommands } from '@remirror/react';
9
9
  import { VerticalDivider } from '@remirror/react-components';
10
10
  import { createToolbarPositioner, ToolbarPositionerRange } from '../utils/createToolbarPositioner';
11
11
  import ImageButton from './Tools/Image/ImageButton';
12
12
  import { MarkName } from '../Extensions/Extensions';
13
13
  import { ImageExtension } from '../Extensions/ImageExtension/ImageExtension';
14
+ import ClearFormattingButton from './Tools/ClearFormatting/ClearFormattingButton';
15
+ import { ClearFormattingExtension } from '../Extensions/ClearFormattingExtension/ClearFormattingExtension';
14
16
 
15
17
  export const FloatingToolbar = () => {
16
18
  const watchedMarks = [MarkName.Link, MarkName.AssetLink];
17
19
  const extensionNames = useExtensionNames();
18
20
  const positioner = useMemo(() => createToolbarPositioner({ types: watchedMarks }), []);
21
+ const { clearFormatting } = useCommands<ClearFormattingExtension>();
19
22
  const active = useActive<ImageExtension>();
20
23
  const {
21
24
  data: { marks },
@@ -44,6 +47,11 @@ export const FloatingToolbar = () => {
44
47
  );
45
48
  }
46
49
 
50
+ // Clear formatting will always be the last button in the toolbar
51
+ if (extensionNames.clearFormatting && clearFormatting.enabled()) {
52
+ buttons.push(<ClearFormattingButton key="clearFormatting" />);
53
+ }
54
+
47
55
  return (
48
56
  <RemirrorFloatingToolbar className="squiz-fte-scope squiz-fte-scope__floating-popover" positioner={positioner}>
49
57
  {buttons}
@@ -11,6 +11,7 @@ import { useExtensionNames } from '../hooks';
11
11
  import LinkButton from './Tools/Link/LinkButton';
12
12
  import ImageButton from './Tools/Image/ImageButton';
13
13
  import RemoveLinkButton from './Tools/Link/RemoveLinkButton';
14
+ import ClearFormattingButton from './Tools/ClearFormatting/ClearFormattingButton';
14
15
 
15
16
  export const Toolbar = () => {
16
17
  const extensionNames = useExtensionNames();
@@ -36,6 +37,7 @@ export const Toolbar = () => {
36
37
  </>
37
38
  )}
38
39
  {extensionNames.image && <ImageButton />}
40
+ {extensionNames.clearFormatting && <ClearFormattingButton />}
39
41
  </RemirrorToolbar>
40
42
  );
41
43
  };
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import '@testing-library/jest-dom';
3
+ import { screen, fireEvent } from '@testing-library/react';
4
+ import { renderWithEditor } from '../../../../tests';
5
+ import ClearFormattingButton from './ClearFormattingButton';
6
+
7
+ describe('Clear formatting button', () => {
8
+ it('Renders the clear formatting button', async () => {
9
+ await renderWithEditor(<ClearFormattingButton />, { content: 'Some nonsense content here' });
10
+ expect(screen.getByRole('button', { name: 'Clear all formatting (cmd+\\)' })).toBeInTheDocument();
11
+ });
12
+
13
+ it('Clears the formatting from editor content after clicking button', async () => {
14
+ const { getHtmlContent } = await renderWithEditor(<ClearFormattingButton />, {
15
+ content: '<p>Hello <strong>Mr Bean</strong></p>',
16
+ });
17
+
18
+ const clearFormatting = screen.getByRole('button', { name: 'Clear all formatting (cmd+\\)' });
19
+ fireEvent.click(clearFormatting);
20
+
21
+ expect(getHtmlContent()).toBe('<p style="">Hello Mr Bean</p>');
22
+ });
23
+
24
+ it('Clears the formatting from editor content when shortcut is pressed', async () => {
25
+ const { getHtmlContent } = await renderWithEditor(<ClearFormattingButton />, {
26
+ content: '<p>Hello <strong>Mr Bean</strong></p>',
27
+ });
28
+
29
+ const editor = screen.getByRole('textbox'); // Assuming the editor is an input field or textarea
30
+ fireEvent.keyDown(editor, { key: '\\', code: 'Backslash', ctrlKey: true });
31
+
32
+ expect(getHtmlContent()).toBe('<p style="">Hello Mr Bean</p>');
33
+ });
34
+ });
@@ -0,0 +1,45 @@
1
+ import React, { useCallback } from 'react';
2
+ import { useCommands, useEditorState, useKeymap } from '@remirror/react';
3
+ import { VerticalDivider } from '@remirror/react-components';
4
+ import FormatClearRoundedIcon from '@mui/icons-material/FormatClearRounded';
5
+ import { ClearFormattingExtension } from '../../../Extensions/ClearFormattingExtension/ClearFormattingExtension';
6
+ import Button from '../../../ui/Button/Button';
7
+
8
+ const ClearFormattingButton = () => {
9
+ const { clearFormatting } = useCommands<ClearFormattingExtension>();
10
+ const { selection } = useEditorState();
11
+
12
+ // Checks wether we have specific content selected or not
13
+ const contentSelected = !selection.empty;
14
+
15
+ const handleSelect = () => {
16
+ if (clearFormatting.enabled()) {
17
+ clearFormatting();
18
+ }
19
+ };
20
+
21
+ const handleShortcut = useCallback(() => {
22
+ handleSelect();
23
+ // Prevent other key handlers being run
24
+ return true;
25
+ }, []);
26
+
27
+ // When Ctrl+\ is pressed clear formatting, only registered in the toolbar button instance to avoid the key press
28
+ // being double handled.
29
+ useKeymap('Mod-\\', handleShortcut);
30
+
31
+ return (
32
+ <>
33
+ <VerticalDivider />
34
+ <Button
35
+ handleOnClick={handleSelect}
36
+ isDisabled={false}
37
+ isActive={false}
38
+ icon={<FormatClearRoundedIcon />}
39
+ label={`${contentSelected ? 'Clear formatting from selection' : 'Clear all formatting'} (cmd+\\)`}
40
+ />
41
+ </>
42
+ );
43
+ };
44
+
45
+ export default ClearFormattingButton;
@@ -0,0 +1,71 @@
1
+ import {
2
+ command,
3
+ extension,
4
+ PlainExtension,
5
+ CommandFunction,
6
+ Mark,
7
+ ExtensionTag,
8
+ getTextSelection,
9
+ } from '@remirror/core';
10
+ import { getNodeNamesByGroup } from '../../utils/getNodeNamesByGroup';
11
+ import { getMarkNamesByGroup } from '../../utils/getMarkNamesByGroup';
12
+
13
+ @extension({})
14
+ export class ClearFormattingExtension extends PlainExtension {
15
+ get name() {
16
+ return 'clearFormatting' as const;
17
+ }
18
+
19
+ @command()
20
+ clearFormatting(): CommandFunction {
21
+ return ({ dispatch, tr, state, view }) => {
22
+ const { empty, ranges } = state.selection;
23
+ const schema = state.schema;
24
+
25
+ const formattingNodes = getNodeNamesByGroup(schema, ExtensionTag.FormattingNode);
26
+ const formattingMarks = getMarkNamesByGroup(schema, ExtensionTag.FormattingMark);
27
+ let isChanged = false;
28
+
29
+ ranges.forEach(({ $from, $to }) => {
30
+ // Check if there is a selection or not, if no selection use the doc content size as the range
31
+ state.doc.nodesBetween(empty ? 0 : $from.pos, empty ? state.doc.content.size : $to.pos, (node, pos) => {
32
+ // Clear marks (bold, italic, etc)
33
+ node.marks.forEach((mark: Mark) => {
34
+ if (formattingMarks.includes(mark.type.name)) {
35
+ tr.removeMark(pos, pos + node.nodeSize, mark);
36
+ isChanged = true;
37
+ }
38
+ });
39
+
40
+ // Leave non-foramtting nodes as-is
41
+ if (!formattingNodes.includes(node.type.name)) {
42
+ return;
43
+ }
44
+
45
+ // Clear node attributes & set to paragraph by default
46
+ if (node.type.name === schema.nodes.paragraph.name) {
47
+ const { nodeTextAlignment } = node.attrs;
48
+
49
+ if (nodeTextAlignment && nodeTextAlignment !== 'left') {
50
+ tr.setNodeAttribute(pos, 'nodeTextAlignment', null);
51
+ isChanged = true;
52
+ }
53
+ } else {
54
+ tr.setNodeMarkup(pos, schema.nodes.paragraph, null, node.marks);
55
+ isChanged = true;
56
+ }
57
+ });
58
+ });
59
+
60
+ if (!empty) {
61
+ // Move the cursor to the end of the selection
62
+ tr.setSelection(getTextSelection({ from: state.selection.to, to: state.selection.to }, tr.doc));
63
+ }
64
+
65
+ dispatch?.(tr);
66
+ view?.focus();
67
+
68
+ return isChanged;
69
+ };
70
+ }
71
+ }
@@ -15,6 +15,7 @@ import { ImageExtension } from './ImageExtension/ImageExtension';
15
15
  import { CommandsExtension } from './CommandsExtension/CommandsExtension';
16
16
  import { EditorContextOptions } from '../Editor/EditorContext';
17
17
  import { AssetImageExtension } from './ImageExtension/AssetImageExtension';
18
+ import { ClearFormattingExtension } from './ClearFormattingExtension/ClearFormattingExtension';
18
19
 
19
20
  export enum NodeName {
20
21
  Image = 'image',
@@ -48,6 +49,7 @@ export const createExtensions = (context: EditorContextOptions) => {
48
49
  new AssetLinkExtension({
49
50
  matrixDomain: context.matrix.matrixDomain,
50
51
  }),
52
+ new ClearFormattingExtension(),
51
53
  ];
52
54
  };
53
55
  };
@@ -1,6 +1,6 @@
1
1
  import { renderWithEditor } from '../../../tests';
2
2
 
3
- describe('AssetLinkExtension', () => {
3
+ describe('PreformattedExtension', () => {
4
4
  it('Parses HTML content with preformatted text', async () => {
5
5
  const { getJsonContent } = await renderWithEditor(null, {
6
6
  content: `<pre>This is some preformatted text</pre>`,
@@ -0,0 +1,20 @@
1
+ import { EditorSchema, ExtensionTag } from '@remirror/core';
2
+ import { renderWithEditor } from '../../tests';
3
+ import { getMarkNamesByGroup } from './getMarkNamesByGroup';
4
+
5
+ describe('getMarkNamesGroup', () => {
6
+ it('Should return expected extensions with "FormattingMark" tag', async () => {
7
+ const { editor } = await renderWithEditor(null);
8
+ const schema: EditorSchema = editor.schema;
9
+ const formattingMarkNames = getMarkNamesByGroup(schema, ExtensionTag.FormattingMark);
10
+ const otherMarkNames = Object.values(schema.marks)
11
+ .map((mark) => mark.name)
12
+ .filter((mark) => !formattingMarkNames.includes(mark));
13
+
14
+ // This tag is used by the clear formatting extension.
15
+ // Marks in the first array will be transformed to a paragraph when formatting is cleared.
16
+ // Mark in the second array will be left as-is.
17
+ expect(formattingMarkNames).toEqual(['bold', 'italic', 'underline']);
18
+ expect(otherMarkNames).toEqual(['assetLink', 'link']);
19
+ });
20
+ });
@@ -0,0 +1,7 @@
1
+ import { EditorSchema, ExtensionTagType } from '@remirror/core';
2
+
3
+ export const getMarkNamesByGroup = (schema: EditorSchema, group: ExtensionTagType): string[] => {
4
+ return Object.values(schema.marks)
5
+ .filter((mark) => mark.spec.group?.includes(group))
6
+ .map((mark) => mark.name);
7
+ };
@@ -0,0 +1,20 @@
1
+ import { EditorSchema, ExtensionTag } from '@remirror/core';
2
+ import { renderWithEditor } from '../../tests';
3
+ import { getNodeNamesByGroup } from './getNodeNamesByGroup';
4
+
5
+ describe('getNodeNamesByGroup', () => {
6
+ it('Should return expected extensions with "FormattingNode" tag', async () => {
7
+ const { editor } = await renderWithEditor(null);
8
+ const schema: EditorSchema = editor.schema;
9
+ const formattingNodeNames = getNodeNamesByGroup(schema, ExtensionTag.FormattingNode);
10
+ const otherNodeNames = Object.values(schema.nodes)
11
+ .map((node) => node.name)
12
+ .filter((node) => !formattingNodeNames.includes(node));
13
+
14
+ // This tag is used by the clear formatting extension.
15
+ // Nodes in the first array will be transformed to a paragraph when formatting is cleared.
16
+ // Nodes in the second array will be left as-is.
17
+ expect(formattingNodeNames).toEqual(['paragraph', 'heading', 'preformatted']);
18
+ expect(otherNodeNames).toEqual(['assetImage', 'doc', 'text', 'image']);
19
+ });
20
+ });
@@ -0,0 +1,7 @@
1
+ import { EditorSchema, ExtensionTagType } from '@remirror/core';
2
+
3
+ export const getNodeNamesByGroup = (schema: EditorSchema, group: ExtensionTagType): string[] => {
4
+ return Object.values(schema.nodes)
5
+ .filter((node) => node.spec.group?.includes(group))
6
+ .map((node) => node.name);
7
+ };