@squiz/formatted-text-editor 1.21.1-alpha.7 → 1.22.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/demo/App.tsx +52 -10
- package/demo/index.scss +11 -10
- package/jest.config.ts +0 -2
- package/lib/Editor/Editor.js +45 -7
- package/lib/Editor/EditorContext.d.ts +15 -0
- package/lib/Editor/EditorContext.js +15 -0
- package/lib/EditorToolbar/FloatingToolbar.js +11 -5
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +9 -8
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +91 -23
- package/lib/EditorToolbar/Tools/Image/ImageButton.d.ts +4 -1
- package/lib/EditorToolbar/Tools/Image/ImageButton.js +22 -14
- package/lib/EditorToolbar/Tools/Image/ImageModal.js +9 -5
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +14 -5
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +66 -14
- package/lib/EditorToolbar/Tools/Link/LinkButton.js +21 -13
- package/lib/EditorToolbar/Tools/Link/LinkModal.js +12 -5
- package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +1 -8
- package/lib/Extensions/CommandsExtension/CommandsExtension.d.ts +20 -0
- package/lib/Extensions/CommandsExtension/CommandsExtension.js +52 -0
- package/lib/Extensions/Extensions.d.ts +12 -5
- package/lib/Extensions/Extensions.js +42 -20
- package/lib/Extensions/ImageExtension/AssetImageExtension.d.ts +17 -0
- package/lib/Extensions/ImageExtension/AssetImageExtension.js +92 -0
- package/lib/Extensions/ImageExtension/ImageExtension.d.ts +4 -0
- package/lib/Extensions/ImageExtension/ImageExtension.js +11 -0
- package/lib/Extensions/LinkExtension/AssetLinkExtension.d.ts +26 -0
- package/lib/Extensions/LinkExtension/AssetLinkExtension.js +102 -0
- package/lib/Extensions/LinkExtension/LinkExtension.d.ts +19 -12
- package/lib/Extensions/LinkExtension/LinkExtension.js +56 -66
- package/lib/Extensions/LinkExtension/common.d.ts +7 -0
- package/lib/Extensions/LinkExtension/common.js +14 -0
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.d.ts +1 -1
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +6 -2
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useExpandedSelection.d.ts +23 -0
- package/lib/hooks/useExpandedSelection.js +37 -0
- package/lib/index.css +58 -23
- package/lib/index.d.ts +5 -2
- package/lib/index.js +9 -3
- package/lib/types.d.ts +3 -0
- package/lib/types.js +2 -0
- package/lib/ui/Button/Button.d.ts +2 -1
- package/lib/ui/Button/Button.js +4 -5
- package/lib/ui/Fields/Input/Input.d.ts +1 -0
- package/lib/ui/Fields/Input/Input.js +9 -3
- package/lib/ui/Modal/Modal.js +5 -3
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.d.ts +9 -0
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +174 -0
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.d.ts +9 -0
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +138 -0
- package/lib/utils/resolveMatrixAssetUrl.d.ts +1 -0
- package/lib/utils/resolveMatrixAssetUrl.js +10 -0
- package/lib/utils/undefinedIfEmpty.d.ts +1 -0
- package/lib/utils/undefinedIfEmpty.js +7 -0
- package/package.json +10 -4
- package/src/Editor/Editor.spec.tsx +78 -18
- package/src/Editor/Editor.tsx +28 -9
- package/src/Editor/EditorContext.spec.tsx +26 -0
- package/src/Editor/EditorContext.ts +26 -0
- package/src/Editor/_editor.scss +20 -4
- package/src/EditorToolbar/FloatingToolbar.spec.tsx +26 -7
- package/src/EditorToolbar/FloatingToolbar.tsx +15 -6
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +81 -6
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +167 -47
- package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +250 -2
- package/src/EditorToolbar/Tools/Image/ImageButton.tsx +29 -16
- package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +59 -20
- package/src/EditorToolbar/Tools/Image/ImageModal.tsx +12 -10
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +37 -9
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +96 -26
- package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +137 -26
- package/src/EditorToolbar/Tools/Link/LinkButton.tsx +28 -19
- package/src/EditorToolbar/Tools/Link/LinkModal.tsx +13 -6
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +27 -26
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -10
- package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +22 -1
- package/src/EditorToolbar/_floating-toolbar.scss +4 -5
- package/src/EditorToolbar/_toolbar.scss +1 -1
- package/src/Extensions/CommandsExtension/CommandsExtension.ts +54 -0
- package/src/Extensions/Extensions.ts +42 -18
- package/src/Extensions/ImageExtension/AssetImageExtension.spec.ts +76 -0
- package/src/Extensions/ImageExtension/AssetImageExtension.ts +111 -0
- package/src/Extensions/ImageExtension/ImageExtension.ts +17 -1
- package/src/Extensions/LinkExtension/AssetLinkExtension.spec.ts +104 -0
- package/src/Extensions/LinkExtension/AssetLinkExtension.ts +128 -0
- package/src/Extensions/LinkExtension/LinkExtension.spec.ts +68 -0
- package/src/Extensions/LinkExtension/LinkExtension.ts +71 -85
- package/src/Extensions/LinkExtension/common.ts +10 -0
- package/src/Extensions/PreformattedExtension/PreformattedExtension.spec.ts +41 -0
- package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +6 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useExpandedSelection.ts +44 -0
- package/src/index.ts +5 -2
- package/src/types.ts +5 -0
- package/src/ui/Button/Button.tsx +10 -6
- package/src/ui/Button/_button.scss +1 -1
- package/src/ui/Fields/Input/Input.spec.tsx +7 -1
- package/src/ui/Fields/Input/Input.tsx +23 -4
- package/src/ui/Modal/Modal.spec.tsx +15 -0
- package/src/ui/Modal/Modal.tsx +8 -4
- package/src/ui/ToolbarDropdown/_toolbar-dropdown.scss +1 -1
- package/src/ui/_forms.scss +14 -0
- package/src/utils/converters/mocks/squizNodeJson.mock.ts +271 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +480 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +212 -0
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +341 -0
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +159 -0
- package/src/utils/resolveMatrixAssetUrl.spec.ts +26 -0
- package/src/utils/resolveMatrixAssetUrl.ts +7 -0
- package/src/utils/undefinedIfEmpty.spec.ts +12 -0
- package/src/utils/undefinedIfEmpty.ts +3 -0
- package/tailwind.config.cjs +3 -0
- package/tests/renderWithEditor.tsx +28 -15
- package/tsconfig.json +1 -1
- package/lib/FormattedTextEditor.d.ts +0 -2
- package/lib/FormattedTextEditor.js +0 -7
- package/src/Editor/Editor.mock.tsx +0 -43
- package/src/FormattedTextEditor.spec.tsx +0 -10
- package/src/FormattedTextEditor.tsx +0 -3
- /package/tests/{select.tsx → select.ts} +0 -0
package/demo/App.tsx
CHANGED
@@ -1,13 +1,26 @@
|
|
1
1
|
import React, { useState } from 'react';
|
2
|
-
import
|
2
|
+
import { Editor, EditorContext, remirrorNodeToSquizNode, squizNodeToRemirrorNode } from '../src';
|
3
3
|
import { RemirrorEventListener, Extension } from '@remirror/core';
|
4
|
+
import ReactDiffViewer from 'react-diff-viewer-continued';
|
4
5
|
|
5
6
|
function App() {
|
6
|
-
const [doc, setDoc] = useState(
|
7
|
+
const [doc, setDoc] = useState('');
|
8
|
+
const [squizDoc, setSquizDoc] = useState('');
|
9
|
+
const [reconvertedDoc, setReconvertedDoc] = useState('');
|
10
|
+
const [error, setError] = useState<unknown>(null);
|
7
11
|
const [editable, setEditable] = useState(true);
|
12
|
+
|
8
13
|
const handleEditorChange: RemirrorEventListener<Extension> = (parameter) => {
|
9
|
-
|
10
|
-
setDoc(parameter.state.doc);
|
14
|
+
try {
|
15
|
+
setDoc(JSON.stringify(parameter.state.doc, null, 2));
|
16
|
+
|
17
|
+
const newSquizDoc = remirrorNodeToSquizNode(parameter.state.doc);
|
18
|
+
const newReconvertedDoc = parameter.state.schema.nodeFromJSON(squizNodeToRemirrorNode(newSquizDoc));
|
19
|
+
|
20
|
+
setSquizDoc(JSON.stringify(newSquizDoc, null, 2));
|
21
|
+
setReconvertedDoc(JSON.stringify(newReconvertedDoc, null, 2));
|
22
|
+
} catch (e) {
|
23
|
+
setError(e);
|
11
24
|
}
|
12
25
|
};
|
13
26
|
|
@@ -22,15 +35,44 @@ function App() {
|
|
22
35
|
</div>
|
23
36
|
<h1>Editor</h1>
|
24
37
|
<div className="page-section">
|
25
|
-
<
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
38
|
+
<EditorContext.Provider
|
39
|
+
value={{
|
40
|
+
matrix: {
|
41
|
+
matrixDomain: 'https://matrix-domain.squiz.net',
|
42
|
+
matrixIdentifier: 'matrix-api-identifier',
|
43
|
+
resolveMatrixAsset: (assetId: string) => {
|
44
|
+
return new Promise((resolve) => {
|
45
|
+
setTimeout(() => {
|
46
|
+
if (assetId.match(/invalid/i)) {
|
47
|
+
resolve(null);
|
48
|
+
} else {
|
49
|
+
resolve({
|
50
|
+
id: assetId,
|
51
|
+
type: assetId.match(/(image)/i)?.[0] || 'unknown',
|
52
|
+
});
|
53
|
+
}
|
54
|
+
}, 200);
|
55
|
+
});
|
56
|
+
},
|
57
|
+
},
|
58
|
+
}}
|
59
|
+
>
|
60
|
+
<Editor
|
61
|
+
editable={editable}
|
62
|
+
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>`}
|
63
|
+
onChange={handleEditorChange}
|
64
|
+
/>
|
65
|
+
</EditorContext.Provider>
|
30
66
|
</div>
|
31
67
|
<h1>Document</h1>
|
32
68
|
<div className="page-section">
|
33
|
-
|
69
|
+
{error instanceof Error && (
|
70
|
+
<div className="error">An error occurred when transforming the document: {error.message}</div>
|
71
|
+
)}
|
72
|
+
<div className="code-section">
|
73
|
+
<ReactDiffViewer oldValue={doc} newValue={reconvertedDoc} splitView={false} showDiffOnly={false} />
|
74
|
+
<ReactDiffViewer oldValue={squizDoc} newValue={squizDoc} splitView={false} showDiffOnly={false} />
|
75
|
+
</div>
|
34
76
|
</div>
|
35
77
|
</div>
|
36
78
|
);
|
package/demo/index.scss
CHANGED
@@ -22,19 +22,20 @@ h1 {
|
|
22
22
|
align-items: center;
|
23
23
|
}
|
24
24
|
|
25
|
-
code {
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
font-size: 0.8rem;
|
31
|
-
height: 40vh;
|
32
|
-
max-height: 40vh;
|
25
|
+
.code-section {
|
26
|
+
display: flex;
|
27
|
+
flex-direction: row;
|
28
|
+
gap: 8px;
|
29
|
+
height: 35vh;
|
33
30
|
overflow: scroll;
|
34
31
|
}
|
35
32
|
|
36
33
|
.remirror-editor {
|
37
|
-
height:
|
38
|
-
max-height:
|
34
|
+
height: 35vh;
|
35
|
+
max-height: 35vh;
|
39
36
|
overflow: scroll;
|
40
37
|
}
|
38
|
+
|
39
|
+
.error {
|
40
|
+
color: red;
|
41
|
+
}
|
package/jest.config.ts
CHANGED
@@ -22,8 +22,6 @@ export default {
|
|
22
22
|
moduleNameMapper: {
|
23
23
|
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|css|scss)$':
|
24
24
|
'<rootDir>/file-transformer.js',
|
25
|
-
'^react($|/.+)': '<rootDir>/node_modules/react$1',
|
26
|
-
'^react-dom($|/.+)': '<rootDir>/node_modules/react-dom$1',
|
27
25
|
},
|
28
26
|
setupFilesAfterEnv: ['<rootDir>/jest.bootstrap.ts'],
|
29
27
|
};
|
package/lib/Editor/Editor.js
CHANGED
@@ -1,15 +1,53 @@
|
|
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 =
|
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
|
-
const
|
34
|
+
const clsx_1 = __importDefault(require("clsx"));
|
35
|
+
const WrappedEditor = () => {
|
36
|
+
const preventImagePaste = (0, react_1.useCallback)((event) => {
|
37
|
+
const { clipboardData } = event;
|
38
|
+
const pastedData = clipboardData?.files[0];
|
39
|
+
if (pastedData?.type && pastedData?.type.startsWith('image/')) {
|
40
|
+
event.preventDefault();
|
41
|
+
}
|
42
|
+
// Allow other paste event handlers to be run.
|
43
|
+
return false;
|
44
|
+
}, []);
|
45
|
+
(0, react_2.useEditorEvent)('paste', preventImagePaste);
|
46
|
+
return react_1.default.createElement(react_2.EditorComponent, null);
|
47
|
+
};
|
48
|
+
const Editor = ({ content, editable = true, onChange }) => {
|
11
49
|
const { manager, state, setState } = (0, react_2.useRemirror)({
|
12
|
-
extensions: Extensions_1.
|
50
|
+
extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)),
|
13
51
|
content,
|
14
52
|
selection: 'start',
|
15
53
|
stringHandler: 'html',
|
@@ -19,10 +57,10 @@ const Editor = ({ content, editable, onChange }) => {
|
|
19
57
|
onChange?.(parameter);
|
20
58
|
};
|
21
59
|
return (react_1.default.createElement("div", { className: "squiz-fte-scope" },
|
22
|
-
react_1.default.createElement("div", { className:
|
60
|
+
react_1.default.createElement("div", { className: (0, clsx_1.default)('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled') },
|
23
61
|
react_1.default.createElement(react_2.Remirror, { manager: manager, state: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor" },
|
24
|
-
react_1.default.createElement(EditorToolbar_1.Toolbar, null),
|
25
|
-
react_1.default.createElement(
|
26
|
-
react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null)))));
|
62
|
+
editable && react_1.default.createElement(EditorToolbar_1.Toolbar, null),
|
63
|
+
react_1.default.createElement(WrappedEditor, null),
|
64
|
+
editable && react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null)))));
|
27
65
|
};
|
28
66
|
exports.default = Editor;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
export type MatrixAsset = {
|
3
|
+
id: string;
|
4
|
+
type: string | 'image';
|
5
|
+
};
|
6
|
+
export type MatrixAssetResolver = (assetId: string) => Promise<MatrixAsset | null>;
|
7
|
+
export type EditorContextOptions = {
|
8
|
+
matrix: {
|
9
|
+
matrixIdentifier: string;
|
10
|
+
matrixDomain: string;
|
11
|
+
resolveMatrixAsset: MatrixAssetResolver;
|
12
|
+
};
|
13
|
+
};
|
14
|
+
export declare const defaultEditorContext: EditorContextOptions;
|
15
|
+
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
|
+
resolveMatrixAsset: () => Promise.resolve(null),
|
13
|
+
},
|
14
|
+
};
|
15
|
+
exports.EditorContext = react_1.default.createContext(exports.defaultEditorContext);
|
@@ -37,22 +37,28 @@ const LinkButton_1 = __importDefault(require("./Tools/Link/LinkButton"));
|
|
37
37
|
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
|
+
const Extensions_1 = require("../Extensions/Extensions");
|
41
42
|
const FloatingToolbar = () => {
|
43
|
+
const watchedMarks = [Extensions_1.MarkName.Link, Extensions_1.MarkName.AssetLink];
|
42
44
|
const extensionNames = (0, hooks_1.useExtensionNames)();
|
43
|
-
const positioner = (0, react_1.useMemo)(() => (0, createToolbarPositioner_1.createToolbarPositioner)({ types:
|
44
|
-
const
|
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, []);
|
45
48
|
let buttons = [
|
46
49
|
extensionNames.bold && react_1.default.createElement(BoldButton_1.default, { key: "bold" }),
|
47
50
|
extensionNames.italic && react_1.default.createElement(ItalicButton_1.default, { key: "italic" }),
|
48
51
|
extensionNames.underline && react_1.default.createElement(UnderlineButton_1.default, { key: "underline" }),
|
49
52
|
];
|
50
|
-
if (
|
53
|
+
if (active.image() || active.assetImage()) {
|
54
|
+
buttons = [react_1.default.createElement(ImageButton_1.default, { key: "add-image", inPopover: true })];
|
55
|
+
}
|
56
|
+
else if (marks?.[Extensions_1.MarkName.Link].isExclusivelyActive || marks?.[Extensions_1.MarkName.AssetLink].isExclusivelyActive) {
|
51
57
|
// if all of the selected text is a link show the options to update/remove the link instead of the regular
|
52
58
|
// formatting options.
|
53
59
|
buttons = [react_1.default.createElement(LinkButton_1.default, { key: "update-link", inPopover: true }), react_1.default.createElement(RemoveLinkButton_1.default, { key: "remove-link" })];
|
54
60
|
}
|
55
|
-
else if (!
|
61
|
+
else if (!marks?.[Extensions_1.MarkName.Link].isActive && !marks?.[Extensions_1.MarkName.AssetLink].isActive) {
|
56
62
|
// if none of the selected text is a link show the option to create a link.
|
57
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 }));
|
58
64
|
}
|
@@ -1,17 +1,18 @@
|
|
1
1
|
import { ReactElement } from 'react';
|
2
2
|
import { SubmitHandler } from 'react-hook-form';
|
3
3
|
import { ImageAttributes } from '@remirror/extension-image/dist-types/image-extension';
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
import { NodeName } from '../../../../Extensions/Extensions';
|
5
|
+
import { AssetImageAttributes } from '../../../../Extensions/ImageExtension/AssetImageExtension';
|
6
|
+
import { DeepPartial } from '../../../../types';
|
7
|
+
export type ImageFormData = {
|
8
|
+
imageType: NodeName;
|
9
|
+
image: Pick<ImageAttributes, 'src' | 'alt' | 'width' | 'height'>;
|
10
|
+
assetImage: AssetImageAttributes;
|
9
11
|
};
|
10
|
-
export type ImageFormData = Pick<UpdateImageOptions, 'src' | 'alt' | 'width' | 'height'>;
|
11
12
|
export type FormProps = {
|
12
|
-
data:
|
13
|
+
data: DeepPartial<ImageFormData>;
|
13
14
|
onSubmit: SubmitHandler<ImageFormData>;
|
14
15
|
};
|
15
|
-
export type Dimensions = 'width' | 'height';
|
16
|
+
export type Dimensions = 'image.width' | 'image.height';
|
16
17
|
declare const ImageForm: ({ data, onSubmit }: FormProps) => ReactElement;
|
17
18
|
export default ImageForm;
|
@@ -33,34 +33,53 @@ const react_image_size_1 = require("react-image-size");
|
|
33
33
|
const Button_1 = __importDefault(require("../../../../ui/Button/Button"));
|
34
34
|
const LinkOff_1 = __importDefault(require("@mui/icons-material/LinkOff"));
|
35
35
|
const InsertLinkRounded_1 = __importDefault(require("@mui/icons-material/InsertLinkRounded"));
|
36
|
+
const clsx_1 = __importDefault(require("clsx"));
|
37
|
+
const Extensions_1 = require("../../../../Extensions/Extensions");
|
38
|
+
const Select_1 = require("../../../../ui/Fields/Select/Select");
|
39
|
+
const EditorContext_1 = require("../../../../Editor/EditorContext");
|
40
|
+
const imageTypeOptions = {
|
41
|
+
[Extensions_1.NodeName.Image]: { label: 'External image' },
|
42
|
+
[Extensions_1.NodeName.AssetImage]: { label: 'Asset image' },
|
43
|
+
};
|
44
|
+
const regexDataURI = /^data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*)$/i;
|
36
45
|
const ImageForm = ({ data, onSubmit }) => {
|
37
|
-
const { register, handleSubmit, setValue } = (0, react_hook_form_1.useForm)({
|
46
|
+
const { register, handleSubmit, setValue, watch, formState: { errors }, } = (0, react_hook_form_1.useForm)({
|
38
47
|
defaultValues: data,
|
39
48
|
});
|
49
|
+
const imageType = watch('imageType') || Extensions_1.NodeName.Image;
|
50
|
+
const context = (0, react_1.useContext)(EditorContext_1.EditorContext);
|
40
51
|
const [aspectRatioFromWidth, setAspectRatioFromWidth] = (0, react_1.useState)(9 / 16);
|
41
52
|
const [aspectRatioFromHeight, setAspectRatioFromHeight] = (0, react_1.useState)(16 / 9);
|
42
53
|
const [aspectRatioLocked, setAspectRatioLocked] = (0, react_1.useState)(true);
|
43
|
-
const setDimensionsFromURL = (e) => {
|
44
|
-
|
45
|
-
|
46
|
-
.
|
47
|
-
setValue('
|
48
|
-
setValue('height', height);
|
54
|
+
const setDimensionsFromURL = async (e) => {
|
55
|
+
try {
|
56
|
+
const { width, height } = await (0, react_image_size_1.getImageSize)(e.target.value);
|
57
|
+
setValue('image.width', width);
|
58
|
+
setValue('image.height', height);
|
49
59
|
setAspectRatioFromWidth(height / width);
|
50
60
|
setAspectRatioFromHeight(width / height);
|
51
|
-
}
|
52
|
-
|
53
|
-
//
|
54
|
-
|
55
|
-
}
|
61
|
+
}
|
62
|
+
catch (error) {
|
63
|
+
// swallow the error for fetching the image size, will occur if the URL does not point to an image.
|
64
|
+
// will be handled by validation when attempting to add the image.
|
65
|
+
}
|
66
|
+
};
|
67
|
+
const validateIsNotImage = async (src) => {
|
68
|
+
try {
|
69
|
+
await (0, react_image_size_1.getImageSize)(src);
|
70
|
+
return false;
|
71
|
+
}
|
72
|
+
catch (error) {
|
73
|
+
return true;
|
74
|
+
}
|
56
75
|
};
|
57
|
-
const calculateDimensions = () => {
|
76
|
+
const calculateDimensions = (event) => {
|
58
77
|
if (aspectRatioLocked) {
|
59
78
|
const currentTarget = event?.target;
|
60
79
|
const type = currentTarget.name;
|
61
80
|
const currentValue = currentTarget.value;
|
62
|
-
const otherValue = type === 'width' ? 'height' : 'width';
|
63
|
-
const aspectRatio = type === 'width' ? aspectRatioFromWidth : aspectRatioFromHeight;
|
81
|
+
const otherValue = type === 'image.width' ? 'image.height' : 'image.width';
|
82
|
+
const aspectRatio = type === 'image.width' ? aspectRatioFromWidth : aspectRatioFromHeight;
|
64
83
|
const newValue = Math.round(aspectRatio * Number(currentValue) * 100) / 100;
|
65
84
|
setValue(otherValue, newValue);
|
66
85
|
}
|
@@ -70,15 +89,64 @@ const ImageForm = ({ data, onSubmit }) => {
|
|
70
89
|
};
|
71
90
|
return (react_1.default.createElement("form", { className: "squiz-fte-form", onSubmit: handleSubmit(onSubmit) },
|
72
91
|
react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
73
|
-
react_1.default.createElement(
|
74
|
-
react_1.default.createElement(
|
75
|
-
react_1.default.createElement(
|
76
|
-
|
92
|
+
react_1.default.createElement(Select_1.Select, { name: "imageType", label: "Type", value: imageType, options: imageTypeOptions, onChange: (value) => setValue('imageType', value) })),
|
93
|
+
imageType === Extensions_1.NodeName.Image && (react_1.default.createElement(react_1.default.Fragment, null,
|
94
|
+
react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
95
|
+
react_1.default.createElement(Input_1.Input, { label: "Source", required: true, error: errors?.image?.src?.message, ...register('image.src', {
|
96
|
+
onChange: setDimensionsFromURL,
|
97
|
+
required: 'Source is required',
|
98
|
+
validate: {
|
99
|
+
isValidImage: async (value) => {
|
100
|
+
if (value && regexDataURI.test(value)) {
|
101
|
+
return 'Must not be a data URI';
|
102
|
+
}
|
103
|
+
if (value && (await validateIsNotImage(value))) {
|
104
|
+
return 'Must be a valid image URL';
|
105
|
+
}
|
106
|
+
},
|
107
|
+
},
|
108
|
+
}) })),
|
77
109
|
react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
78
|
-
react_1.default.createElement(Input_1.Input, { label: "
|
79
|
-
react_1.default.createElement("div", { className: "flex
|
80
|
-
react_1.default.createElement(
|
110
|
+
react_1.default.createElement(Input_1.Input, { label: "Alternative description", required: true, error: errors?.image?.alt?.message, ...register('image.alt', { required: 'Alternative description is required' }) })),
|
111
|
+
react_1.default.createElement("div", { className: "flex flex-row" },
|
112
|
+
react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
113
|
+
react_1.default.createElement(Input_1.Input, { label: "Width", type: "number", required: true, error: errors?.image?.width?.message, ...register('image.width', {
|
114
|
+
onChange: calculateDimensions,
|
115
|
+
required: 'Width is required',
|
116
|
+
validate: {
|
117
|
+
isValidWidth: (value) => {
|
118
|
+
if (value && !(value > 0)) {
|
119
|
+
return 'Must be higher than 0';
|
120
|
+
}
|
121
|
+
},
|
122
|
+
},
|
123
|
+
}) })),
|
124
|
+
react_1.default.createElement("div", { className: "flex mx-1 mb-2" },
|
125
|
+
react_1.default.createElement(Button_1.default, { handleOnClick: toggleAspectRatio, isActive: false, icon: aspectRatioLocked ? react_1.default.createElement(InsertLinkRounded_1.default, null) : react_1.default.createElement(LinkOff_1.default, null), label: "Constrain properties", isDisabled: false, className: (0, clsx_1.default)('my-auto', !errors?.image?.height && !errors?.image?.width && 'mb-0') })),
|
126
|
+
react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
127
|
+
react_1.default.createElement(Input_1.Input, { label: "Height", type: "number", required: true, error: errors?.image?.height?.message, ...register('image.height', {
|
128
|
+
onChange: calculateDimensions,
|
129
|
+
required: 'Height is required',
|
130
|
+
validate: {
|
131
|
+
isValidHeight: (value) => {
|
132
|
+
if (value && !(value > 0)) {
|
133
|
+
return 'Must be higher than 0';
|
134
|
+
}
|
135
|
+
},
|
136
|
+
},
|
137
|
+
}) }))))),
|
138
|
+
imageType === Extensions_1.NodeName.AssetImage && (react_1.default.createElement(react_1.default.Fragment, null,
|
81
139
|
react_1.default.createElement("div", { className: "squiz-fte-form-group mb-2" },
|
82
|
-
react_1.default.createElement(Input_1.Input, { label: "
|
140
|
+
react_1.default.createElement(Input_1.Input, { label: "Asset ID", required: true, error: errors?.assetImage?.matrixAssetId?.message, ...register('assetImage.matrixAssetId', {
|
141
|
+
required: 'Asset ID is required',
|
142
|
+
validate: {
|
143
|
+
isImage: async (assetId) => {
|
144
|
+
const asset = await context.matrix.resolveMatrixAsset(assetId);
|
145
|
+
if (asset?.type !== 'image') {
|
146
|
+
return 'Asset ID is invalid or not an image';
|
147
|
+
}
|
148
|
+
},
|
149
|
+
},
|
150
|
+
}) }))))));
|
83
151
|
};
|
84
152
|
exports.default = ImageForm;
|
@@ -27,27 +27,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
27
|
};
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
29
29
|
const react_1 = __importStar(require("react"));
|
30
|
+
const react_2 = require("@remirror/react");
|
30
31
|
const ImageRounded_1 = __importDefault(require("@mui/icons-material/ImageRounded"));
|
31
32
|
const ImageModal_1 = __importDefault(require("./ImageModal"));
|
32
33
|
const Button_1 = __importDefault(require("../../../ui/Button/Button"));
|
33
|
-
const
|
34
|
-
const ImageButton = () => {
|
34
|
+
const Extensions_1 = require("../../../Extensions/Extensions");
|
35
|
+
const ImageButton = ({ inPopover = false }) => {
|
35
36
|
const [showModal, setShowModal] = (0, react_1.useState)(false);
|
36
|
-
const { insertImage } = (0, react_2.useCommands)();
|
37
|
-
const active =
|
37
|
+
const { insertImage, insertAssetImage } = (0, react_2.useCommands)();
|
38
|
+
const active = (0, react_2.useActive)();
|
39
|
+
const selection = (0, react_2.useCurrentSelection)();
|
40
|
+
// if the active selection is not an image, disable the button as it means it will be text
|
41
|
+
const disabled = !selection.empty && !active.image() && !active.assetImage();
|
38
42
|
const handleClick = () => {
|
39
43
|
if (!showModal) {
|
40
|
-
|
41
|
-
// update the selected text in state before showing the modal.
|
42
|
-
requestAnimationFrame(() => {
|
43
|
-
setShowModal(true);
|
44
|
-
});
|
44
|
+
setShowModal(true);
|
45
45
|
}
|
46
46
|
};
|
47
47
|
const insertImageFromData = (data) => {
|
48
|
-
const {
|
49
|
-
if (
|
50
|
-
insertImage(
|
48
|
+
const { imageType, image, assetImage } = data;
|
49
|
+
if (imageType === Extensions_1.NodeName.Image) {
|
50
|
+
insertImage(image);
|
51
|
+
}
|
52
|
+
else {
|
53
|
+
insertAssetImage(assetImage);
|
51
54
|
}
|
52
55
|
};
|
53
56
|
const handleSubmit = (data) => {
|
@@ -59,9 +62,14 @@ const ImageButton = () => {
|
|
59
62
|
// Prevent other key handlers being run
|
60
63
|
return true;
|
61
64
|
}, []);
|
62
|
-
|
65
|
+
// when Ctrl+l is pressed show the modal, only registered in the toolbar button instance to avoid the key press
|
66
|
+
// being double handled.
|
67
|
+
if (!inPopover) {
|
68
|
+
// disable the shortcut if the button is disabled
|
69
|
+
(0, react_2.useKeymap)('Mod-l', disabled ? () => false : handleShortcut);
|
70
|
+
}
|
63
71
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
64
|
-
react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: active, icon: react_1.default.createElement(ImageRounded_1.default, null), label: "Image (cmd+L)", isDisabled:
|
72
|
+
react_1.default.createElement(Button_1.default, { handleOnClick: handleClick, isActive: active.image() || active.assetImage(), icon: react_1.default.createElement(ImageRounded_1.default, null), label: "Image (cmd+L)", isDisabled: disabled }),
|
65
73
|
showModal && react_1.default.createElement(ImageModal_1.default, { onCancel: () => setShowModal(false), onSubmit: handleSubmit })));
|
66
74
|
};
|
67
75
|
exports.default = ImageButton;
|
@@ -3,17 +3,21 @@ 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
6
|
const ImageForm_1 = __importDefault(require("./Form/ImageForm"));
|
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 Extensions_1 = require("../../../Extensions/Extensions");
|
11
11
|
const ImageModal = ({ onCancel, onSubmit }) => {
|
12
|
-
const { helpers, view: { state }, } = (0, react_2.useRemirrorContext)();
|
13
12
|
const selection = (0, react_2.useCurrentSelection)();
|
14
|
-
const currentImage =
|
15
|
-
const
|
13
|
+
const currentImage = selection?.node;
|
14
|
+
const currentImageAttrs = { ...currentImage?.attrs };
|
15
|
+
const formData = {
|
16
|
+
imageType: currentImage?.type.name === Extensions_1.NodeName.AssetImage ? Extensions_1.NodeName.AssetImage : Extensions_1.NodeName.Image,
|
17
|
+
image: currentImage?.type?.name === Extensions_1.NodeName.Image ? currentImageAttrs : {},
|
18
|
+
assetImage: currentImage?.type?.name === Extensions_1.NodeName.AssetImage ? currentImageAttrs : {},
|
19
|
+
};
|
16
20
|
return (react_1.default.createElement(FormModal_1.default, { title: "Image", onCancel: onCancel },
|
17
|
-
react_1.default.createElement(ImageForm_1.default, { data:
|
21
|
+
react_1.default.createElement(ImageForm_1.default, { data: formData, onSubmit: onSubmit })));
|
18
22
|
};
|
19
23
|
exports.default = ImageModal;
|
@@ -1,10 +1,19 @@
|
|
1
1
|
import { ReactElement } from 'react';
|
2
2
|
import { SubmitHandler } from 'react-hook-form';
|
3
|
-
import {
|
4
|
-
|
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
|
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;
|