@squiz/formatted-text-editor 1.21.1-alpha.19 → 1.21.1-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/demo/App.tsx +31 -10
  2. package/demo/index.scss +2 -7
  3. package/lib/Editor/Editor.js +26 -2
  4. package/lib/Editor/EditorContext.d.ts +10 -0
  5. package/lib/Editor/EditorContext.js +15 -0
  6. package/lib/EditorToolbar/FloatingToolbar.js +15 -16
  7. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +14 -5
  8. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +66 -14
  9. package/lib/EditorToolbar/Tools/Link/LinkButton.js +11 -15
  10. package/lib/EditorToolbar/Tools/Link/LinkModal.js +12 -5
  11. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +1 -8
  12. package/lib/Extensions/CommandsExtension/CommandsExtension.d.ts +20 -0
  13. package/lib/Extensions/CommandsExtension/CommandsExtension.js +52 -0
  14. package/lib/Extensions/Extensions.d.ts +6 -1
  15. package/lib/Extensions/Extensions.js +31 -20
  16. package/lib/Extensions/LinkExtension/AssetLinkExtension.d.ts +26 -0
  17. package/lib/Extensions/LinkExtension/AssetLinkExtension.js +102 -0
  18. package/lib/Extensions/LinkExtension/LinkExtension.d.ts +21 -12
  19. package/lib/Extensions/LinkExtension/LinkExtension.js +63 -65
  20. package/lib/Extensions/LinkExtension/common.d.ts +7 -0
  21. package/lib/Extensions/LinkExtension/common.js +14 -0
  22. package/lib/hooks/index.d.ts +1 -0
  23. package/lib/hooks/index.js +1 -0
  24. package/lib/hooks/useExpandedSelection.d.ts +23 -0
  25. package/lib/hooks/useExpandedSelection.js +37 -0
  26. package/lib/index.css +15 -3
  27. package/lib/index.d.ts +3 -2
  28. package/lib/index.js +5 -3
  29. package/lib/types.d.ts +3 -0
  30. package/lib/types.js +2 -0
  31. package/lib/ui/Button/Button.js +2 -3
  32. package/lib/ui/Fields/Input/Input.d.ts +1 -0
  33. package/lib/ui/Fields/Input/Input.js +8 -3
  34. package/lib/ui/Modal/Modal.js +2 -1
  35. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.d.ts +1 -2
  36. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +110 -105
  37. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +93 -69
  38. package/lib/utils/undefinedIfEmpty.d.ts +1 -0
  39. package/lib/utils/undefinedIfEmpty.js +7 -0
  40. package/package.json +3 -2
  41. package/src/Editor/Editor.spec.tsx +0 -26
  42. package/src/Editor/Editor.tsx +4 -3
  43. package/src/Editor/EditorContext.spec.tsx +26 -0
  44. package/src/Editor/EditorContext.ts +19 -0
  45. package/src/EditorToolbar/FloatingToolbar.tsx +19 -18
  46. package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +37 -9
  47. package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +96 -26
  48. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +103 -25
  49. package/src/EditorToolbar/Tools/Link/LinkButton.tsx +16 -19
  50. package/src/EditorToolbar/Tools/Link/LinkModal.tsx +13 -6
  51. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +26 -26
  52. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -10
  53. package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +22 -1
  54. package/src/Extensions/CommandsExtension/CommandsExtension.ts +54 -0
  55. package/src/Extensions/Extensions.ts +31 -19
  56. package/src/Extensions/LinkExtension/AssetLinkExtension.spec.ts +104 -0
  57. package/src/Extensions/LinkExtension/AssetLinkExtension.ts +128 -0
  58. package/src/Extensions/LinkExtension/LinkExtension.spec.ts +68 -0
  59. package/src/Extensions/LinkExtension/LinkExtension.ts +88 -82
  60. package/src/Extensions/LinkExtension/common.ts +10 -0
  61. package/src/hooks/index.ts +1 -0
  62. package/src/hooks/useExpandedSelection.ts +44 -0
  63. package/src/index.ts +3 -2
  64. package/src/types.ts +5 -0
  65. package/src/ui/Button/Button.tsx +2 -4
  66. package/src/ui/Fields/Input/Input.tsx +18 -4
  67. package/src/ui/Modal/Modal.tsx +2 -1
  68. package/src/ui/_forms.scss +14 -0
  69. package/src/utils/converters/mocks/squizNodeJson.mock.ts +177 -0
  70. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +41 -6
  71. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +124 -113
  72. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +56 -34
  73. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +107 -79
  74. package/src/utils/undefinedIfEmpty.spec.ts +12 -0
  75. package/src/utils/undefinedIfEmpty.ts +3 -0
  76. package/tailwind.config.cjs +3 -0
  77. package/tests/renderWithEditor.tsx +21 -12
  78. package/lib/FormattedTextEditor.d.ts +0 -2
  79. package/lib/FormattedTextEditor.js +0 -7
  80. package/lib/utils/converters/validNodeTypes.d.ts +0 -2
  81. package/lib/utils/converters/validNodeTypes.js +0 -21
  82. package/src/Editor/Editor.mock.tsx +0 -43
  83. package/src/FormattedTextEditor.spec.tsx +0 -10
  84. package/src/FormattedTextEditor.tsx +0 -3
  85. package/src/utils/converters/validNodeTypes.spec.ts +0 -33
  86. package/src/utils/converters/validNodeTypes.ts +0 -21
package/demo/App.tsx CHANGED
@@ -1,24 +1,26 @@
1
1
  import React, { useState } from 'react';
2
- import FormattedTextEditor from '../src/FormattedTextEditor';
2
+ import { Editor, EditorContext, remirrorNodeToSquizNode, squizNodeToRemirrorNode } from '../src';
3
3
  import { RemirrorEventListener, Extension } from '@remirror/core';
4
- import { remirrorNodeToSquizNode } from '../src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode';
5
- import { squizNodeToRemirrorNode } from '../src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode';
6
4
  import ReactDiffViewer from 'react-diff-viewer-continued';
7
5
 
8
6
  function App() {
9
7
  const [doc, setDoc] = useState('');
10
8
  const [squizDoc, setSquizDoc] = useState('');
11
9
  const [reconvertedDoc, setReconvertedDoc] = useState('');
10
+ const [error, setError] = useState<unknown>(null);
12
11
  const [editable, setEditable] = useState(true);
13
12
 
14
13
  const handleEditorChange: RemirrorEventListener<Extension> = (parameter) => {
15
- if (doc !== parameter.state.doc) {
14
+ try {
15
+ setDoc(JSON.stringify(parameter.state.doc, null, 2));
16
+
16
17
  const newSquizDoc = remirrorNodeToSquizNode(parameter.state.doc);
17
18
  const newReconvertedDoc = parameter.state.schema.nodeFromJSON(squizNodeToRemirrorNode(newSquizDoc));
18
19
 
19
- setDoc(JSON.stringify(parameter.state.doc, null, 2));
20
20
  setSquizDoc(JSON.stringify(newSquizDoc, null, 2));
21
21
  setReconvertedDoc(JSON.stringify(newReconvertedDoc, null, 2));
22
+ } catch (e) {
23
+ setError(e);
22
24
  }
23
25
  };
24
26
 
@@ -33,14 +35,33 @@ function App() {
33
35
  </div>
34
36
  <h1>Editor</h1>
35
37
  <div className="page-section">
36
- <FormattedTextEditor
37
- editable={editable}
38
- 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>.<img src="https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif" height="150" width="200"/></p>`}
39
- onChange={handleEditorChange}
40
- />
38
+ <EditorContext.Provider
39
+ value={{
40
+ matrix: {
41
+ matrixDomain: 'https://matrix-domain.squiz.net',
42
+ matrixIdentifier: 'matrix-api-identifier',
43
+ isValidMatrixAssetId: (assetId: string) => {
44
+ return new Promise((resolve) => {
45
+ setTimeout(() => {
46
+ resolve(assetId.indexOf('invalid') < 0);
47
+ }, 200);
48
+ });
49
+ },
50
+ },
51
+ }}
52
+ >
53
+ <Editor
54
+ editable={editable}
55
+ 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>.<img src="https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif" height="150" width="200"/></p>`}
56
+ onChange={handleEditorChange}
57
+ />
58
+ </EditorContext.Provider>
41
59
  </div>
42
60
  <h1>Document</h1>
43
61
  <div className="page-section">
62
+ {error instanceof Error && (
63
+ <div className="error">An error occurred when transforming the document: {error.message}</div>
64
+ )}
44
65
  <div className="code-section">
45
66
  <ReactDiffViewer oldValue={doc} newValue={reconvertedDoc} splitView={false} showDiffOnly={false} />
46
67
  <ReactDiffViewer oldValue={squizDoc} newValue={squizDoc} splitView={false} showDiffOnly={false} />
package/demo/index.scss CHANGED
@@ -36,11 +36,6 @@ h1 {
36
36
  overflow: scroll;
37
37
  }
38
38
 
39
- .squiz-fte-modal-footer__button {
40
- margin-top: 1rem;
41
- }
42
-
43
- .mock-buttons {
44
- display: flex;
45
- gap: 0.5rem;
39
+ .error {
40
+ color: red;
46
41
  }
@@ -1,16 +1,40 @@
1
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
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_1 = __importDefault(require("react"));
29
+ const react_1 = __importStar(require("react"));
7
30
  const react_2 = require("@remirror/react");
8
31
  const EditorToolbar_1 = require("../EditorToolbar");
32
+ const EditorContext_1 = require("./EditorContext");
9
33
  const Extensions_1 = require("../Extensions/Extensions");
10
34
  const clsx_1 = __importDefault(require("clsx"));
11
35
  const Editor = ({ content, editable = true, onChange }) => {
12
36
  const { manager, state, setState } = (0, react_2.useRemirror)({
13
- extensions: Extensions_1.Extensions,
37
+ extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)),
14
38
  content,
15
39
  selection: 'start',
16
40
  stringHandler: 'html',
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ export type EditorContextOptions = {
3
+ matrix: {
4
+ matrixIdentifier: string;
5
+ matrixDomain: string;
6
+ isValidMatrixAssetId: (assetId: string) => boolean | Promise<boolean>;
7
+ };
8
+ };
9
+ export declare const defaultEditorContext: EditorContextOptions;
10
+ export declare const EditorContext: React.Context<EditorContextOptions>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EditorContext = exports.defaultEditorContext = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ exports.defaultEditorContext = {
9
+ matrix: {
10
+ matrixIdentifier: '',
11
+ matrixDomain: '',
12
+ isValidMatrixAssetId: () => true,
13
+ },
14
+ };
15
+ exports.EditorContext = react_1.default.createContext(exports.defaultEditorContext);
@@ -38,31 +38,30 @@ const react_2 = require("@remirror/react");
38
38
  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
- // The editor main toolbar
41
+ const Extensions_1 = require("../Extensions/Extensions");
42
42
  const FloatingToolbar = () => {
43
+ const watchedMarks = [Extensions_1.MarkName.Link, Extensions_1.MarkName.AssetLink];
43
44
  const extensionNames = (0, hooks_1.useExtensionNames)();
44
- const positioner = (0, react_1.useMemo)(() => (0, createToolbarPositioner_1.createToolbarPositioner)({ types: ['link'] }), []);
45
- const { data } = (0, react_2.usePositioner)(positioner, []);
46
- const activeImage = (0, react_2.useActive)();
45
+ const positioner = (0, react_1.useMemo)(() => (0, createToolbarPositioner_1.createToolbarPositioner)({ types: watchedMarks }), []);
46
+ const active = (0, react_2.useActive)();
47
+ const { data: { marks }, } = (0, react_2.usePositioner)(positioner, []);
47
48
  let buttons = [
48
49
  extensionNames.bold && react_1.default.createElement(BoldButton_1.default, { key: "bold" }),
49
50
  extensionNames.italic && react_1.default.createElement(ItalicButton_1.default, { key: "italic" }),
50
51
  extensionNames.underline && react_1.default.createElement(UnderlineButton_1.default, { key: "underline" }),
51
52
  ];
52
- if (!activeImage.image()) {
53
- if (data.marks?.link.isExclusivelyActive) {
54
- // if all of the selected text is a link show the options to update/remove the link instead of the regular
55
- // formatting options.
56
- buttons = [react_1.default.createElement(LinkButton_1.default, { key: "update-link", inPopover: true }), react_1.default.createElement(RemoveLinkButton_1.default, { key: "remove-link" })];
57
- }
58
- else if (!data.marks?.link.isActive) {
59
- // if none of the selected text is a link show the option to create a link.
60
- 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 }));
61
- }
62
- }
63
- else {
53
+ if (active.image()) {
64
54
  buttons.push(react_1.default.createElement(react_components_1.VerticalDivider, { key: "image-divider", className: "editor-divider" }), react_1.default.createElement(ImageButton_1.default, { key: "add-image", inPopover: true }));
65
55
  }
56
+ else if (marks?.[Extensions_1.MarkName.Link].isExclusivelyActive || marks?.[Extensions_1.MarkName.AssetLink].isExclusivelyActive) {
57
+ // if all of the selected text is a link show the options to update/remove the link instead of the regular
58
+ // formatting options.
59
+ buttons = [react_1.default.createElement(LinkButton_1.default, { key: "update-link", inPopover: true }), react_1.default.createElement(RemoveLinkButton_1.default, { key: "remove-link" })];
60
+ }
61
+ else if (!marks?.[Extensions_1.MarkName.Link].isActive && !marks?.[Extensions_1.MarkName.AssetLink].isActive) {
62
+ // if none of the selected text is a link show the option to create a link.
63
+ 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 }));
64
+ }
66
65
  return (react_1.default.createElement(react_2.FloatingToolbar, { className: "squiz-fte-scope squiz-fte-scope__floating-popover", positioner: positioner }, buttons));
67
66
  };
68
67
  exports.FloatingToolbar = FloatingToolbar;
@@ -1,10 +1,19 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { SubmitHandler } from 'react-hook-form';
3
- import { UpdateLinkOptions } from '../../../../Extensions/LinkExtension/LinkExtension';
4
- export type LinkFormData = Pick<UpdateLinkOptions, 'href' | 'target' | 'title' | 'text'>;
3
+ import { FromToProps } from 'remirror';
4
+ import { UpdateLinkProps } from '../../../../Extensions/LinkExtension/LinkExtension';
5
+ import { UpdateAssetLinkProps } from '../../../../Extensions/LinkExtension/AssetLinkExtension';
6
+ import { MarkName } from '../../../../Extensions/Extensions';
7
+ import { DeepPartial } from '../../../../types';
8
+ export type LinkFormData = {
9
+ linkType: MarkName;
10
+ text: string;
11
+ link: UpdateLinkProps['attrs'];
12
+ assetLink: UpdateAssetLinkProps['attrs'];
13
+ range: FromToProps;
14
+ };
5
15
  export type FormProps = {
6
- data: Partial<LinkFormData>;
16
+ data?: DeepPartial<LinkFormData>;
7
17
  onSubmit: SubmitHandler<LinkFormData>;
8
18
  };
9
- declare const LinkForm: ({ data, onSubmit }: FormProps) => ReactElement;
10
- export default LinkForm;
19
+ export declare const LinkForm: ({ data, onSubmit }: FormProps) => ReactElement;
@@ -1,28 +1,80 @@
1
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
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_1 = __importDefault(require("react"));
29
+ exports.LinkForm = void 0;
30
+ const react_1 = __importStar(require("react"));
31
+ const clsx_1 = __importDefault(require("clsx"));
32
+ const react_hook_form_1 = require("react-hook-form");
7
33
  const Input_1 = require("../../../../ui/Fields/Input/Input");
8
34
  const Select_1 = require("../../../../ui/Fields/Select/Select");
9
- const react_hook_form_1 = require("react-hook-form");
10
- const selectOptions = {
11
- _self: { label: 'Current window' },
12
- _blank: { label: 'New window' },
35
+ const common_1 = require("../../../../Extensions/LinkExtension/common");
36
+ const EditorContext_1 = require("../../../../Editor/EditorContext");
37
+ const Extensions_1 = require("../../../../Extensions/Extensions");
38
+ const linkTypeOptions = {
39
+ [Extensions_1.MarkName.Link]: { label: 'Link to URL' },
40
+ [Extensions_1.MarkName.AssetLink]: { label: 'Link to asset' },
41
+ };
42
+ const targetOptions = {
43
+ [common_1.LinkTarget.Self]: { label: 'Current window' },
44
+ [common_1.LinkTarget.Blank]: { label: 'New window' },
13
45
  };
14
46
  const LinkForm = ({ data, onSubmit }) => {
15
- const { register, handleSubmit, setValue } = (0, react_hook_form_1.useForm)({
47
+ const context = (0, react_1.useContext)(EditorContext_1.EditorContext);
48
+ const { register, handleSubmit, setValue, watch, formState: { errors }, } = (0, react_hook_form_1.useForm)({
16
49
  defaultValues: data,
17
50
  });
51
+ const linkType = watch('linkType') || Extensions_1.MarkName.Link;
18
52
  return (react_1.default.createElement("form", { className: "squiz-fte-form", onSubmit: handleSubmit(onSubmit) },
19
53
  react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
20
- react_1.default.createElement(Input_1.Input, { label: "URL", ...register('href') })),
21
- react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
22
- react_1.default.createElement(Input_1.Input, { label: "Text", ...register('text') })),
23
- react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
24
- react_1.default.createElement(Input_1.Input, { label: "Title", ...register('title') })),
25
- react_1.default.createElement("div", { className: "squiz-fte-form-group mb-0" },
26
- react_1.default.createElement(Select_1.Select, { name: "target", label: "Target", value: data.target || '_self', options: selectOptions, onChange: (value) => setValue('target', value) }))));
54
+ react_1.default.createElement(Select_1.Select, { name: "linkType", label: "Type", value: linkType, options: linkTypeOptions, onChange: (value) => setValue('linkType', value) })),
55
+ linkType === Extensions_1.MarkName.Link && (react_1.default.createElement(react_1.default.Fragment, null,
56
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
57
+ react_1.default.createElement(Input_1.Input, { label: "URL", ...register('link.href') })),
58
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
59
+ react_1.default.createElement(Input_1.Input, { label: "Text", ...register('text') })),
60
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
61
+ react_1.default.createElement(Input_1.Input, { label: "Title", ...register('link.title') })),
62
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-0') },
63
+ react_1.default.createElement(Select_1.Select, { name: "link.target", label: "Target", value: data?.link?.target || '_self', options: targetOptions, onChange: (value) => setValue('link.target', value) })))),
64
+ linkType === Extensions_1.MarkName.AssetLink && (react_1.default.createElement(react_1.default.Fragment, null,
65
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
66
+ react_1.default.createElement(Input_1.Input, { label: "Asset ID", error: errors?.assetLink?.matrixAssetId?.message, ...register('assetLink.matrixAssetId', {
67
+ validate: {
68
+ isValidAsset: async (assetId) => {
69
+ if (assetId && !(await context.matrix.isValidMatrixAssetId(assetId))) {
70
+ return 'Invalid asset ID';
71
+ }
72
+ },
73
+ },
74
+ }) })),
75
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-2') },
76
+ react_1.default.createElement(Input_1.Input, { label: "Text", ...register('text') })),
77
+ react_1.default.createElement("div", { className: (0, clsx_1.default)('squiz-fte-form-group mb-0') },
78
+ react_1.default.createElement(Select_1.Select, { name: "assetLink.target", label: "Target", value: data?.link?.target || '_self', options: targetOptions, onChange: (value) => setValue('assetLink.target', value) }))))));
27
79
  };
28
- exports.default = LinkForm;
80
+ exports.LinkForm = LinkForm;
@@ -32,29 +32,25 @@ const LinkModal_1 = __importDefault(require("./LinkModal"));
32
32
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
33
33
  const react_2 = require("@remirror/react");
34
34
  const LinkExtension_1 = require("../../../Extensions/LinkExtension/LinkExtension");
35
- const createToolbarPositioner_1 = require("../../../utils/createToolbarPositioner");
35
+ const Extensions_1 = require("../../../Extensions/Extensions");
36
36
  const LinkButton = ({ inPopover = false }) => {
37
37
  const [showModal, setShowModal] = (0, react_1.useState)(false);
38
- const { selectLink, updateLink } = (0, react_2.useCommands)();
38
+ const { updateLink, updateAssetLink } = (0, react_2.useCommands)();
39
39
  const active = (0, react_2.useActive)();
40
40
  // If the image tool is active, disable the link tool as they shouldn't work at the same time
41
- const disabled = (0, react_2.useActive)().image();
42
- const positioner = (0, react_1.useMemo)(() => (0, createToolbarPositioner_1.createToolbarPositioner)({ types: ['link'] }), []);
43
- const { data } = (0, react_2.usePositioner)(positioner, []);
41
+ const disabled = active.image();
44
42
  const handleClick = () => {
45
43
  if (!showModal) {
46
- if (data.isSelectionInView) {
47
- selectLink();
48
- }
49
- // form element are uncontrolled, let the event loop run to
50
- // update the selected text in state before showing the modal.
51
- requestAnimationFrame(() => {
52
- setShowModal(true);
53
- });
44
+ setShowModal(true);
54
45
  }
55
46
  };
56
47
  const handleSubmit = (data) => {
57
- updateLink(data);
48
+ if (data.linkType === Extensions_1.MarkName.AssetLink) {
49
+ updateAssetLink({ text: data.text, attrs: data.assetLink, range: data.range });
50
+ }
51
+ else {
52
+ updateLink({ text: data.text, attrs: data.link, range: data.range });
53
+ }
58
54
  setShowModal(false);
59
55
  };
60
56
  if (!inPopover) {
@@ -63,7 +59,7 @@ const LinkButton = ({ inPopover = false }) => {
63
59
  (0, react_2.useExtensionEvent)(LinkExtension_1.LinkExtension, 'onShortcut', (0, react_1.useCallback)(() => handleClick(), []));
64
60
  }
65
61
  return (react_1.default.createElement(react_1.default.Fragment, null,
66
- react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: active.link(), icon: react_1.default.createElement(InsertLinkRounded_1.default, null), label: "Link (cmd+K)", isDisabled: disabled }),
62
+ react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: active.link() || active.assetLink(), icon: react_1.default.createElement(InsertLinkRounded_1.default, null), label: "Link (cmd+K)", isDisabled: disabled }),
67
63
  showModal && react_1.default.createElement(LinkModal_1.default, { onCancel: () => setShowModal(false), onSubmit: handleSubmit })));
68
64
  };
69
65
  exports.default = LinkButton;
@@ -3,17 +3,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const remirror_1 = require("remirror");
7
- const LinkForm_1 = __importDefault(require("./Form/LinkForm"));
6
+ const LinkForm_1 = require("./Form/LinkForm");
8
7
  const react_1 = __importDefault(require("react"));
9
8
  const react_2 = require("@remirror/react");
10
9
  const FormModal_1 = __importDefault(require("../../../ui/Modal/FormModal"));
10
+ const hooks_1 = require("../../../hooks");
11
+ const Extensions_1 = require("../../../Extensions/Extensions");
11
12
  const LinkModal = ({ onCancel, onSubmit }) => {
12
13
  const { helpers, view: { state }, } = (0, react_2.useRemirrorContext)();
13
- const selection = (0, react_2.useCurrentSelection)();
14
- const currentLink = (0, remirror_1.getMarkRanges)(selection, 'link')[0];
14
+ const { selection, marks } = (0, hooks_1.useExpandedSelection)([Extensions_1.MarkName.Link, Extensions_1.MarkName.AssetLink]);
15
15
  const selectedText = helpers.getTextBetween(selection.from, selection.to, state.doc);
16
+ const data = {
17
+ linkType: marks[0]?.type?.name === Extensions_1.MarkName.AssetLink ? Extensions_1.MarkName.AssetLink : Extensions_1.MarkName.Link,
18
+ text: selectedText,
19
+ link: marks.find((mark) => mark.type.name === 'link')?.attrs || {},
20
+ assetLink: marks.find((mark) => mark.type.name === Extensions_1.MarkName.AssetLink)?.attrs || {},
21
+ range: { from: selection.from, to: selection.to },
22
+ };
16
23
  return (react_1.default.createElement(FormModal_1.default, { title: "Link", onCancel: onCancel },
17
- react_1.default.createElement(LinkForm_1.default, { data: { ...currentLink?.mark.attrs, text: selectedText }, onSubmit: onSubmit })));
24
+ react_1.default.createElement(LinkForm_1.LinkForm, { data: data, onSubmit: onSubmit })));
18
25
  };
19
26
  exports.default = LinkModal;
@@ -8,14 +8,7 @@ const react_2 = require("@remirror/react");
8
8
  const Button_1 = __importDefault(require("../../../ui/Button/Button"));
9
9
  const LinkOff_1 = __importDefault(require("@mui/icons-material/LinkOff"));
10
10
  const RemoveLinkButton = () => {
11
- const { commands } = (0, react_2.useRemirrorContext)({ autoUpdate: true });
12
11
  const chain = (0, react_2.useChainedCommands)();
13
- const enabled = commands.removeLink.enabled();
14
- const handleClick = () => {
15
- if (enabled) {
16
- chain.removeLink().focus().run();
17
- }
18
- };
19
- return (react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: false, isDisabled: !enabled, icon: react_1.default.createElement(LinkOff_1.default, null), label: "Remove link" }));
12
+ return (react_1.default.createElement(Button_1.default, { handleOnClick: () => chain.removeLink().removeAssetLink().focus().run(), isActive: false, icon: react_1.default.createElement(LinkOff_1.default, null), label: "Remove link" }));
20
13
  };
21
14
  exports.default = RemoveLinkButton;
@@ -0,0 +1,20 @@
1
+ import { PlainExtension } from '@remirror/core';
2
+ import { CommandFunction, FromToProps, MarkType } from 'remirror';
3
+ import { Attrs } from 'prosemirror-model';
4
+ export declare class CommandsExtension extends PlainExtension {
5
+ get name(): string;
6
+ /**
7
+ * Updates the attributes of a specific mark for the current selection.
8
+ * Optionally, if text is provided it will replace the current selection.
9
+ * The cursor will be place at the end of the selection after changes.
10
+ *
11
+ * @param {object} options
12
+ */
13
+ updateMark(options: {
14
+ mark: MarkType;
15
+ attrs?: Attrs;
16
+ text?: string;
17
+ removeMark?: boolean;
18
+ range: FromToProps;
19
+ }): CommandFunction;
20
+ }
@@ -0,0 +1,52 @@
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.CommandsExtension = void 0;
10
+ const core_1 = require("@remirror/core");
11
+ const remirror_1 = require("remirror");
12
+ class CommandsExtension extends core_1.PlainExtension {
13
+ get name() {
14
+ return 'squizCommands';
15
+ }
16
+ /**
17
+ * Updates the attributes of a specific mark for the current selection.
18
+ * Optionally, if text is provided it will replace the current selection.
19
+ * The cursor will be place at the end of the selection after changes.
20
+ *
21
+ * @param {object} options
22
+ */
23
+ updateMark(options) {
24
+ return (props) => {
25
+ const { tr, dispatch, view } = props;
26
+ const { mark, attrs, text, removeMark, range } = options;
27
+ const selectedText = tr.doc.textBetween(range.from, range.to);
28
+ if (text !== undefined && text !== selectedText) {
29
+ // update the text in the editor if it was updated, update the range to cover the length of the new text.
30
+ tr.insertText(text, range.from, range.to);
31
+ range.to = range.from + text.length;
32
+ }
33
+ // apply the link, or remove it if no URL was provided.
34
+ if (removeMark) {
35
+ tr.removeMark(range.from, range.to, mark);
36
+ }
37
+ else {
38
+ tr.addMark(range.from, range.to, mark.create(attrs));
39
+ }
40
+ // move the cursor to the end of the link and re-focus the editor.
41
+ tr.setSelection((0, remirror_1.getTextSelection)({ from: range.to, to: range.to }, tr.doc));
42
+ // apply the transaction.
43
+ dispatch?.(tr);
44
+ view?.focus();
45
+ return true;
46
+ };
47
+ }
48
+ }
49
+ __decorate([
50
+ (0, remirror_1.command)()
51
+ ], CommandsExtension.prototype, "updateMark", null);
52
+ exports.CommandsExtension = CommandsExtension;
@@ -1,2 +1,7 @@
1
1
  import { Extension } from '@remirror/core';
2
- export declare const Extensions: () => Extension[];
2
+ import { EditorContextOptions } from '../Editor/EditorContext';
3
+ export declare enum MarkName {
4
+ Link = "link",
5
+ AssetLink = "assetLink"
6
+ }
7
+ export declare const createExtensions: (context: EditorContextOptions) => () => Extension[];
@@ -1,26 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Extensions = void 0;
3
+ exports.createExtensions = exports.MarkName = void 0;
4
4
  const extensions_1 = require("remirror/extensions");
5
5
  const PreformattedExtension_1 = require("./PreformattedExtension/PreformattedExtension");
6
+ const AssetLinkExtension_1 = require("./LinkExtension/AssetLinkExtension");
6
7
  const LinkExtension_1 = require("./LinkExtension/LinkExtension");
7
8
  const ImageExtension_1 = require("./ImageExtension/ImageExtension");
8
- const Extensions = () => [
9
- new extensions_1.BoldExtension(),
10
- new extensions_1.HeadingExtension(),
11
- new extensions_1.ItalicExtension(),
12
- new extensions_1.NodeFormattingExtension({ indents: [] }),
13
- new extensions_1.ParagraphExtension(),
14
- new PreformattedExtension_1.PreformattedExtension(),
15
- new extensions_1.UnderlineExtension(),
16
- new extensions_1.HistoryExtension(),
17
- new ImageExtension_1.ImageExtension({ preferPastedTextContent: false }),
18
- new LinkExtension_1.LinkExtension({
19
- supportedTargets: [
20
- // '_self' is the browser default and will be used when encountering a link with a
21
- // different target is encountered.
22
- '_blank',
23
- ],
24
- }),
25
- ];
26
- exports.Extensions = Extensions;
9
+ const CommandsExtension_1 = require("./CommandsExtension/CommandsExtension");
10
+ var MarkName;
11
+ (function (MarkName) {
12
+ MarkName["Link"] = "link";
13
+ MarkName["AssetLink"] = "assetLink";
14
+ })(MarkName = exports.MarkName || (exports.MarkName = {}));
15
+ const createExtensions = (context) => {
16
+ return () => {
17
+ return [
18
+ new CommandsExtension_1.CommandsExtension(),
19
+ new extensions_1.BoldExtension(),
20
+ new extensions_1.HeadingExtension(),
21
+ new extensions_1.ItalicExtension(),
22
+ new extensions_1.NodeFormattingExtension({ indents: [] }),
23
+ new extensions_1.ParagraphExtension(),
24
+ new PreformattedExtension_1.PreformattedExtension(),
25
+ new extensions_1.UnderlineExtension(),
26
+ new extensions_1.HistoryExtension(),
27
+ new ImageExtension_1.ImageExtension(),
28
+ new ImageExtension_1.ImageExtension({ preferPastedTextContent: false }),
29
+ new LinkExtension_1.LinkExtension(),
30
+ new AssetLinkExtension_1.AssetLinkExtension({
31
+ matrixIdentifier: context.matrix.matrixIdentifier,
32
+ matrixDomain: context.matrix.matrixDomain,
33
+ }),
34
+ ];
35
+ };
36
+ };
37
+ exports.createExtensions = createExtensions;
@@ -0,0 +1,26 @@
1
+ import { ApplySchemaAttributes, FromToProps, MarkExtensionSpec, MarkSpecOverride } from 'remirror';
2
+ import { CommandFunction, MarkExtension } from '@remirror/core';
3
+ import { LinkTarget } from './common';
4
+ export type AssetLinkAttributes = {
5
+ matrixAssetId: string;
6
+ matrixIdentifier: string;
7
+ matrixDomain: string;
8
+ target: LinkTarget;
9
+ };
10
+ export type AssetLinkOptions = {
11
+ matrixIdentifier?: string;
12
+ matrixDomain?: string;
13
+ defaultTarget?: LinkTarget;
14
+ supportedTargets?: LinkTarget[];
15
+ };
16
+ export type UpdateAssetLinkProps = {
17
+ text: string;
18
+ attrs: Partial<AssetLinkAttributes>;
19
+ range: FromToProps;
20
+ };
21
+ export declare class AssetLinkExtension extends MarkExtension<AssetLinkOptions> {
22
+ get name(): string;
23
+ createMarkSpec(extra: ApplySchemaAttributes, override: MarkSpecOverride): MarkExtensionSpec;
24
+ updateAssetLink({ text, attrs, range }: UpdateAssetLinkProps): CommandFunction;
25
+ removeAssetLink(): CommandFunction;
26
+ }