@manuscripts/body-editor 3.2.36 → 3.3.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.
@@ -0,0 +1,138 @@
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
+ exports.DragAndDropUploader = void 0;
30
+ const react_1 = __importStar(require("react"));
31
+ const styled_components_1 = __importDefault(require("styled-components"));
32
+ const DragAndDropUploader = ({ upload, onUploadError, allowedFileTypes, maxFileSizeMB, }) => {
33
+ const [isUploading, setIsUploading] = (0, react_1.useState)(false);
34
+ const fileInputRef = (0, react_1.useRef)(null);
35
+ const validateFile = (file) => {
36
+ if (!file) {
37
+ onUploadError('No file selected.');
38
+ return false;
39
+ }
40
+ if (allowedFileTypes && allowedFileTypes.length > 0) {
41
+ if (!allowedFileTypes.includes(file.type)) {
42
+ onUploadError(`Invalid file type. Allowed types: ${allowedFileTypes.join(', ')}`);
43
+ return false;
44
+ }
45
+ }
46
+ if (maxFileSizeMB && file.size > maxFileSizeMB * 1024 * 1024) {
47
+ onUploadError(`File size exceeds limit of ${maxFileSizeMB} MB.`);
48
+ return false;
49
+ }
50
+ return true;
51
+ };
52
+ const processFiles = async (files) => {
53
+ if (files && files.length > 0) {
54
+ const file = files[0];
55
+ if (validateFile(file)) {
56
+ try {
57
+ setIsUploading(true);
58
+ await upload(file);
59
+ }
60
+ catch (error) {
61
+ onUploadError('Upload failed. Please try again.');
62
+ console.error('Upload error:', error);
63
+ }
64
+ finally {
65
+ setIsUploading(false);
66
+ }
67
+ }
68
+ }
69
+ else {
70
+ onUploadError('No file selected or dropped.');
71
+ }
72
+ };
73
+ const handleDragOver = (e) => {
74
+ if (e.dataTransfer && e.dataTransfer.items) {
75
+ for (const item of e.dataTransfer.items) {
76
+ if (item.kind === 'file' && item.type.startsWith('image/')) {
77
+ e.preventDefault();
78
+ e.dataTransfer.dropEffect = 'copy';
79
+ }
80
+ }
81
+ }
82
+ };
83
+ const handleDrop = async (e) => {
84
+ e.preventDefault();
85
+ await processFiles(e.dataTransfer.files);
86
+ };
87
+ const handleFileChange = async (e) => {
88
+ await processFiles(e.target.files);
89
+ };
90
+ const handleAreaClick = (event) => {
91
+ const target = event.target;
92
+ if (target.dataset && target.dataset.action) {
93
+ return;
94
+ }
95
+ fileInputRef.current?.click();
96
+ };
97
+ return (react_1.default.createElement(DragAndDropContainer, { onDragOver: handleDragOver, onDrop: !isUploading ? handleDrop : undefined, onClick: !isUploading ? handleAreaClick : undefined, tabIndex: isUploading ? -1 : 0, "aria-label": "Drag and drop area for file upload, or click to open file dialog.", role: "button", onKeyDown: (e) => {
98
+ if (e.key === 'Enter' || e.key === ' ') {
99
+ e.preventDefault();
100
+ fileInputRef.current?.click();
101
+ }
102
+ } },
103
+ react_1.default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileChange, "aria-hidden": "true" }),
104
+ react_1.default.createElement("p", null,
105
+ "Drag or click here to upload a file to link",
106
+ react_1.default.createElement("br", null),
107
+ "or drag items here from the file",
108
+ ' ',
109
+ react_1.default.createElement("span", { "data-action": "open-other-files" }, "'Other files'"),
110
+ " in the inspector.")));
111
+ };
112
+ exports.DragAndDropUploader = DragAndDropUploader;
113
+ const DragAndDropContainer = styled_components_1.default.div `
114
+ display: flex;
115
+ flex-direction: column;
116
+ align-items: center;
117
+ justify-content: center;
118
+ padding: 16px;
119
+ border: 1px dashed ${(props) => props.theme.colors.border.secondary};
120
+ border-radius: 8px;
121
+ background-color: ${(props) => props.theme.colors.background.secondary};
122
+ color: ${(props) => props.theme.colors.text.primary};
123
+ font-size: ${(props) => props.theme.font.size.normal};
124
+
125
+ input[type='file'] {
126
+ display: none;
127
+ }
128
+
129
+ p {
130
+ margin: 0;
131
+ font-size: 14px;
132
+ text-align: center;
133
+ }
134
+ span {
135
+ text-decoration: underline;
136
+ cursor: pointer;
137
+ }
138
+ `;
@@ -0,0 +1,127 @@
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
+ exports.ExtLinkEditor = void 0;
30
+ const style_guide_1 = require("@manuscripts/style-guide");
31
+ const react_1 = __importStar(require("react"));
32
+ const styled_components_1 = __importDefault(require("styled-components"));
33
+ const DragAndDropUploader_1 = require("../DragAndDropUploader");
34
+ const ExtLinkEditor = ({ node, nodePos, view, editorProps, isEditing, setIsEditing, }) => {
35
+ const [uploadError, setUploadError] = (0, react_1.useState)(null);
36
+ const fileManagement = editorProps.fileManagement;
37
+ const extLink = node.attrs.extLink;
38
+ const files = editorProps.getFiles();
39
+ const file = extLink ? files.find((f) => f.id === extLink) : undefined;
40
+ const onUpdate = (newAttrs) => {
41
+ const tr = view.state.tr.setNodeMarkup(nodePos, undefined, {
42
+ ...node.attrs,
43
+ ...newAttrs,
44
+ });
45
+ view.dispatch(tr);
46
+ };
47
+ const handleFileUpload = async (file) => {
48
+ setUploadError(null);
49
+ const result = await fileManagement.upload(file);
50
+ if (!result) {
51
+ handleUploadError('File upload failed');
52
+ return;
53
+ }
54
+ setIsEditing(false);
55
+ onUpdate({ extLink: result.id });
56
+ };
57
+ const handleUploadError = (message) => {
58
+ setUploadError(message);
59
+ };
60
+ const handleRemove = () => {
61
+ setUploadError(null);
62
+ onUpdate({ extLink: '' });
63
+ setIsEditing(false);
64
+ };
65
+ return (react_1.default.createElement("div", null,
66
+ !isEditing && !extLink && (react_1.default.createElement(style_guide_1.IconTextButton, { onClick: () => setIsEditing(true), "aria-label": "Add linked file" },
67
+ react_1.default.createElement(style_guide_1.LinkIcon, null),
68
+ "Add link")),
69
+ isEditing && !extLink && (react_1.default.createElement("div", null,
70
+ react_1.default.createElement(ExtLinkEditorLabel, null, "Link"),
71
+ react_1.default.createElement(ExtLinkEditorContainer, null,
72
+ react_1.default.createElement(DragAndDropUploader_1.DragAndDropUploader, { upload: handleFileUpload, onUploadError: handleUploadError }),
73
+ react_1.default.createElement(CloseUploaderButton, { onClick: () => setIsEditing(false) })))),
74
+ extLink && (react_1.default.createElement(LinkedFileInfoBox, null,
75
+ react_1.default.createElement("p", null,
76
+ react_1.default.createElement(style_guide_1.LinkIcon, null),
77
+ file ? file.name : 'File does not exist.'),
78
+ react_1.default.createElement(style_guide_1.IconButton, { onClick: handleRemove, "aria-label": "Remove linked file" },
79
+ react_1.default.createElement(style_guide_1.DeleteIcon, null)))),
80
+ uploadError && (react_1.default.createElement("div", null,
81
+ react_1.default.createElement("p", { className: "font-semibold" }, "Error:"),
82
+ react_1.default.createElement("p", null, uploadError)))));
83
+ };
84
+ exports.ExtLinkEditor = ExtLinkEditor;
85
+ const ExtLinkEditorLabel = styled_components_1.default.div `
86
+ color: var(--label-color);
87
+ font-size: 18px;
88
+ font-style: normal;
89
+ font-weight: 400;
90
+ line-height: 24px;
91
+ letter-spacing: -0.369px;
92
+ margin-top: 12px;
93
+ cursor: pointer;
94
+ `;
95
+ const ExtLinkEditorContainer = styled_components_1.default.div `
96
+ position: relative;
97
+ `;
98
+ const LinkedFileInfoBox = styled_components_1.default.div `
99
+ display: flex;
100
+ flex-direction: row;
101
+ align-items: center;
102
+ justify-content: space-between;
103
+ padding: 4px 8px;
104
+ background-color: ${(props) => props.theme.colors.background.secondary};
105
+ color: ${(props) => props.theme.colors.text.primary};
106
+ border: 1px solid ${(props) => props.theme.colors.border.secondary};
107
+ border-radius: 8px;
108
+
109
+ p {
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: flex-start;
113
+ gap: 8px;
114
+
115
+ font-family: ${(props) => props.theme.font.family.sans};
116
+ font-weight: ${(props) => props.theme.font.weight.normal};
117
+ font-size: ${(props) => props.theme.font.size.medium};
118
+ }
119
+ `;
120
+ const CloseUploaderButton = (0, styled_components_1.default)(style_guide_1.CloseButton) `
121
+ border: 1px solid ${(props) => props.theme.colors.border.tertiary};
122
+ box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.12);
123
+
124
+ position: absolute;
125
+ top: -8px;
126
+ right: -8px;
127
+ `;
@@ -31,6 +31,7 @@ const groupFiles = (doc, files) => {
31
31
  const fileMap = new Map(files.map((f) => [f.id, f]));
32
32
  const figures = [];
33
33
  const supplements = [];
34
+ const linkedFiles = [];
34
35
  const attachments = [];
35
36
  const getFile = (href) => {
36
37
  const file = fileMap.get(href);
@@ -67,6 +68,15 @@ const groupFiles = (doc, files) => {
67
68
  if (figureTypes.includes(node.type)) {
68
69
  figures.push(getFigureElementFiles(node, pos));
69
70
  }
71
+ if (node.type === transform_1.schema.nodes.image_element) {
72
+ if (node.attrs.extLink) {
73
+ linkedFiles.push({
74
+ node,
75
+ pos,
76
+ file: getFile(node.attrs.extLink),
77
+ });
78
+ }
79
+ }
70
80
  if (node.type === transform_1.schema.nodes.supplement) {
71
81
  supplements.push({
72
82
  node,
@@ -86,6 +96,7 @@ const groupFiles = (doc, files) => {
86
96
  figures,
87
97
  supplements,
88
98
  attachments,
99
+ linkedFiles,
89
100
  others: [...fileMap.values()],
90
101
  };
91
102
  };
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MATHJAX_VERSION = exports.VERSION = void 0;
4
- exports.VERSION = '3.2.36';
4
+ exports.VERSION = '3.3.0';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -18,11 +18,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.createSubViewAsync = void 0;
21
22
  const react_1 = __importDefault(require("react"));
23
+ const react_dom_1 = require("react-dom");
22
24
  const client_1 = require("react-dom/client");
23
25
  const styled_components_1 = require("styled-components");
24
26
  function createSubView(props, Component, componentProps, node, getPos, view, classNames = []) {
25
27
  const container = document.createElement('div');
28
+ const Wrapped = createView(props, Component, componentProps, node, getPos, view, classNames, container);
29
+ const root = (0, client_1.createRoot)(container);
30
+ root.render(react_1.default.createElement(Wrapped, null));
31
+ return container;
32
+ }
33
+ function createView(props, Component, componentProps, node, getPos, view, classNames = [], container) {
26
34
  container.classList.add('tools-panel');
27
35
  if (classNames.length) {
28
36
  container.classList.add(...classNames);
@@ -41,8 +49,21 @@ function createSubView(props, Component, componentProps, node, getPos, view, cla
41
49
  return (react_1.default.createElement(styled_components_1.ThemeProvider, { theme: props.theme },
42
50
  react_1.default.createElement(Component, { nodeAttrs: node.attrs, setNodeAttrs: setNodeAttrs, viewProps: { node, view, getPos }, container: container, ...props, ...componentProps })));
43
51
  };
52
+ return Wrapped;
53
+ }
54
+ function createSubViewAsync(props, Component, componentProps, node, getPos, view, classNames = []) {
55
+ const container = document.createElement('div');
56
+ const Wrapped = createView(props, Component, componentProps, node, getPos, view, classNames, container);
44
57
  const root = (0, client_1.createRoot)(container);
45
- root.render(react_1.default.createElement(Wrapped, null));
46
- return container;
58
+ const res = new Promise((resolve) => {
59
+ queueMicrotask(() => {
60
+ (0, react_dom_1.flushSync)(() => {
61
+ root.render(react_1.default.createElement(Wrapped, null));
62
+ });
63
+ resolve(container);
64
+ });
65
+ });
66
+ return res;
47
67
  }
68
+ exports.createSubViewAsync = createSubViewAsync;
48
69
  exports.default = createSubView;
@@ -27,6 +27,7 @@ class AccessibilityElementView extends block_view_1.default {
27
27
  createElement() {
28
28
  super.createElement();
29
29
  this.contentDOM.className = 'accessibility_element_input';
30
+ this.contentDOM.setAttribute('contenteditable', 'true');
30
31
  }
31
32
  }
32
33
  exports.AccessibilityElementView = AccessibilityElementView;
@@ -14,6 +14,29 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
17
40
  var __importDefault = (this && this.__importDefault) || function (mod) {
18
41
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
42
  };
@@ -21,10 +44,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
21
44
  exports.ImageElementView = exports.figurePositions = void 0;
22
45
  const style_guide_1 = require("@manuscripts/style-guide");
23
46
  const transform_1 = require("@manuscripts/transform");
47
+ const ExtLinkEditor_1 = require("../components/views/ExtLinkEditor");
24
48
  const position_menu_1 = require("../lib/position-menu");
25
49
  const block_view_1 = __importDefault(require("./block_view"));
26
50
  const creators_1 = require("./creators");
27
- const ReactSubView_1 = __importDefault(require("./ReactSubView"));
51
+ const ReactSubView_1 = __importStar(require("./ReactSubView"));
28
52
  var figurePositions;
29
53
  (function (figurePositions) {
30
54
  figurePositions["left"] = "half-left";
@@ -34,6 +58,7 @@ var figurePositions;
34
58
  class ImageElementView extends block_view_1.default {
35
59
  constructor() {
36
60
  super(...arguments);
61
+ this.isEditingExtLink = false;
37
62
  this.ignoreMutation = () => true;
38
63
  this.showPositionMenu = () => {
39
64
  const firstFigure = this.getFirstFigure();
@@ -83,17 +108,24 @@ class ImageElementView extends block_view_1.default {
83
108
  createElement() {
84
109
  this.container = document.createElement('div');
85
110
  this.container.classList.add('block');
86
- this.container.setAttribute('contenteditable', 'true');
111
+ this.container.setAttribute('contenteditable', 'false');
87
112
  this.dom.appendChild(this.container);
88
- this.contentDOM = document.createElement('figure');
89
- this.contentDOM.classList.add('figure-block');
113
+ this.subcontainer = document.createElement('div');
114
+ this.subcontainer.classList.add('figure-block-group');
115
+ this.contentDOM = document.createElement('div');
116
+ this.contentDOM.setAttribute('contenteditable', 'false');
90
117
  this.contentDOM.setAttribute('id', this.node.attrs.id);
91
- this.container.appendChild(this.contentDOM);
118
+ this.contentDOM.classList.add('figure-block');
119
+ this.subcontainer.appendChild(this.contentDOM);
120
+ this.container.appendChild(this.subcontainer);
92
121
  this.addTools();
93
122
  }
94
123
  updateContents() {
95
124
  super.updateContents();
96
125
  this.addTools();
126
+ if (this.node.type === transform_1.schema.nodes.image_element) {
127
+ this.addExternalLinkedFileEditor();
128
+ }
97
129
  }
98
130
  addTools() {
99
131
  this.addPositionMenu();
@@ -159,6 +191,27 @@ class ImageElementView extends block_view_1.default {
159
191
  this.view.dispatch(tr);
160
192
  this.figurePosition = position;
161
193
  }
194
+ addExternalLinkedFileEditor() {
195
+ if (this.props.dispatch && this.props.theme) {
196
+ const componentProps = {
197
+ node: this.node,
198
+ nodePos: this.getPos(),
199
+ view: this.view,
200
+ editorProps: this.props,
201
+ isEditing: this.isEditingExtLink,
202
+ setIsEditing: (val) => {
203
+ this.isEditingExtLink = val;
204
+ this.updateContents();
205
+ },
206
+ };
207
+ return (0, ReactSubView_1.createSubViewAsync)(this.props, ExtLinkEditor_1.ExtLinkEditor, componentProps, this.node, this.getPos, this.view, ['ext-link-editor-container']).then((elem) => {
208
+ this.extLinkEditorContainer?.remove();
209
+ this.extLinkEditorContainer = elem;
210
+ this.subcontainer?.appendChild(this.extLinkEditorContainer);
211
+ return;
212
+ });
213
+ }
214
+ }
162
215
  }
163
216
  exports.ImageElementView = ImageElementView;
164
217
  exports.default = (0, creators_1.createNodeView)(ImageElementView);
@@ -0,0 +1,108 @@
1
+ import React, { useRef, useState, } from 'react';
2
+ import styled from 'styled-components';
3
+ export const DragAndDropUploader = ({ upload, onUploadError, allowedFileTypes, maxFileSizeMB, }) => {
4
+ const [isUploading, setIsUploading] = useState(false);
5
+ const fileInputRef = useRef(null);
6
+ const validateFile = (file) => {
7
+ if (!file) {
8
+ onUploadError('No file selected.');
9
+ return false;
10
+ }
11
+ if (allowedFileTypes && allowedFileTypes.length > 0) {
12
+ if (!allowedFileTypes.includes(file.type)) {
13
+ onUploadError(`Invalid file type. Allowed types: ${allowedFileTypes.join(', ')}`);
14
+ return false;
15
+ }
16
+ }
17
+ if (maxFileSizeMB && file.size > maxFileSizeMB * 1024 * 1024) {
18
+ onUploadError(`File size exceeds limit of ${maxFileSizeMB} MB.`);
19
+ return false;
20
+ }
21
+ return true;
22
+ };
23
+ const processFiles = async (files) => {
24
+ if (files && files.length > 0) {
25
+ const file = files[0];
26
+ if (validateFile(file)) {
27
+ try {
28
+ setIsUploading(true);
29
+ await upload(file);
30
+ }
31
+ catch (error) {
32
+ onUploadError('Upload failed. Please try again.');
33
+ console.error('Upload error:', error);
34
+ }
35
+ finally {
36
+ setIsUploading(false);
37
+ }
38
+ }
39
+ }
40
+ else {
41
+ onUploadError('No file selected or dropped.');
42
+ }
43
+ };
44
+ const handleDragOver = (e) => {
45
+ if (e.dataTransfer && e.dataTransfer.items) {
46
+ for (const item of e.dataTransfer.items) {
47
+ if (item.kind === 'file' && item.type.startsWith('image/')) {
48
+ e.preventDefault();
49
+ e.dataTransfer.dropEffect = 'copy';
50
+ }
51
+ }
52
+ }
53
+ };
54
+ const handleDrop = async (e) => {
55
+ e.preventDefault();
56
+ await processFiles(e.dataTransfer.files);
57
+ };
58
+ const handleFileChange = async (e) => {
59
+ await processFiles(e.target.files);
60
+ };
61
+ const handleAreaClick = (event) => {
62
+ const target = event.target;
63
+ if (target.dataset && target.dataset.action) {
64
+ return;
65
+ }
66
+ fileInputRef.current?.click();
67
+ };
68
+ return (React.createElement(DragAndDropContainer, { onDragOver: handleDragOver, onDrop: !isUploading ? handleDrop : undefined, onClick: !isUploading ? handleAreaClick : undefined, tabIndex: isUploading ? -1 : 0, "aria-label": "Drag and drop area for file upload, or click to open file dialog.", role: "button", onKeyDown: (e) => {
69
+ if (e.key === 'Enter' || e.key === ' ') {
70
+ e.preventDefault();
71
+ fileInputRef.current?.click();
72
+ }
73
+ } },
74
+ React.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileChange, "aria-hidden": "true" }),
75
+ React.createElement("p", null,
76
+ "Drag or click here to upload a file to link",
77
+ React.createElement("br", null),
78
+ "or drag items here from the file",
79
+ ' ',
80
+ React.createElement("span", { "data-action": "open-other-files" }, "'Other files'"),
81
+ " in the inspector.")));
82
+ };
83
+ const DragAndDropContainer = styled.div `
84
+ display: flex;
85
+ flex-direction: column;
86
+ align-items: center;
87
+ justify-content: center;
88
+ padding: 16px;
89
+ border: 1px dashed ${(props) => props.theme.colors.border.secondary};
90
+ border-radius: 8px;
91
+ background-color: ${(props) => props.theme.colors.background.secondary};
92
+ color: ${(props) => props.theme.colors.text.primary};
93
+ font-size: ${(props) => props.theme.font.size.normal};
94
+
95
+ input[type='file'] {
96
+ display: none;
97
+ }
98
+
99
+ p {
100
+ margin: 0;
101
+ font-size: 14px;
102
+ text-align: center;
103
+ }
104
+ span {
105
+ text-decoration: underline;
106
+ cursor: pointer;
107
+ }
108
+ `;
@@ -0,0 +1,97 @@
1
+ import { CloseButton, DeleteIcon, IconButton, IconTextButton, LinkIcon, } from '@manuscripts/style-guide';
2
+ import React, { useState } from 'react';
3
+ import styled from 'styled-components';
4
+ import { DragAndDropUploader } from '../DragAndDropUploader';
5
+ export const ExtLinkEditor = ({ node, nodePos, view, editorProps, isEditing, setIsEditing, }) => {
6
+ const [uploadError, setUploadError] = useState(null);
7
+ const fileManagement = editorProps.fileManagement;
8
+ const extLink = node.attrs.extLink;
9
+ const files = editorProps.getFiles();
10
+ const file = extLink ? files.find((f) => f.id === extLink) : undefined;
11
+ const onUpdate = (newAttrs) => {
12
+ const tr = view.state.tr.setNodeMarkup(nodePos, undefined, {
13
+ ...node.attrs,
14
+ ...newAttrs,
15
+ });
16
+ view.dispatch(tr);
17
+ };
18
+ const handleFileUpload = async (file) => {
19
+ setUploadError(null);
20
+ const result = await fileManagement.upload(file);
21
+ if (!result) {
22
+ handleUploadError('File upload failed');
23
+ return;
24
+ }
25
+ setIsEditing(false);
26
+ onUpdate({ extLink: result.id });
27
+ };
28
+ const handleUploadError = (message) => {
29
+ setUploadError(message);
30
+ };
31
+ const handleRemove = () => {
32
+ setUploadError(null);
33
+ onUpdate({ extLink: '' });
34
+ setIsEditing(false);
35
+ };
36
+ return (React.createElement("div", null,
37
+ !isEditing && !extLink && (React.createElement(IconTextButton, { onClick: () => setIsEditing(true), "aria-label": "Add linked file" },
38
+ React.createElement(LinkIcon, null),
39
+ "Add link")),
40
+ isEditing && !extLink && (React.createElement("div", null,
41
+ React.createElement(ExtLinkEditorLabel, null, "Link"),
42
+ React.createElement(ExtLinkEditorContainer, null,
43
+ React.createElement(DragAndDropUploader, { upload: handleFileUpload, onUploadError: handleUploadError }),
44
+ React.createElement(CloseUploaderButton, { onClick: () => setIsEditing(false) })))),
45
+ extLink && (React.createElement(LinkedFileInfoBox, null,
46
+ React.createElement("p", null,
47
+ React.createElement(LinkIcon, null),
48
+ file ? file.name : 'File does not exist.'),
49
+ React.createElement(IconButton, { onClick: handleRemove, "aria-label": "Remove linked file" },
50
+ React.createElement(DeleteIcon, null)))),
51
+ uploadError && (React.createElement("div", null,
52
+ React.createElement("p", { className: "font-semibold" }, "Error:"),
53
+ React.createElement("p", null, uploadError)))));
54
+ };
55
+ const ExtLinkEditorLabel = styled.div `
56
+ color: var(--label-color);
57
+ font-size: 18px;
58
+ font-style: normal;
59
+ font-weight: 400;
60
+ line-height: 24px;
61
+ letter-spacing: -0.369px;
62
+ margin-top: 12px;
63
+ cursor: pointer;
64
+ `;
65
+ const ExtLinkEditorContainer = styled.div `
66
+ position: relative;
67
+ `;
68
+ const LinkedFileInfoBox = styled.div `
69
+ display: flex;
70
+ flex-direction: row;
71
+ align-items: center;
72
+ justify-content: space-between;
73
+ padding: 4px 8px;
74
+ background-color: ${(props) => props.theme.colors.background.secondary};
75
+ color: ${(props) => props.theme.colors.text.primary};
76
+ border: 1px solid ${(props) => props.theme.colors.border.secondary};
77
+ border-radius: 8px;
78
+
79
+ p {
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: flex-start;
83
+ gap: 8px;
84
+
85
+ font-family: ${(props) => props.theme.font.family.sans};
86
+ font-weight: ${(props) => props.theme.font.weight.normal};
87
+ font-size: ${(props) => props.theme.font.size.medium};
88
+ }
89
+ `;
90
+ const CloseUploaderButton = styled(CloseButton) `
91
+ border: 1px solid ${(props) => props.theme.colors.border.tertiary};
92
+ box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.12);
93
+
94
+ position: absolute;
95
+ top: -8px;
96
+ right: -8px;
97
+ `;
@@ -27,6 +27,7 @@ export const groupFiles = (doc, files) => {
27
27
  const fileMap = new Map(files.map((f) => [f.id, f]));
28
28
  const figures = [];
29
29
  const supplements = [];
30
+ const linkedFiles = [];
30
31
  const attachments = [];
31
32
  const getFile = (href) => {
32
33
  const file = fileMap.get(href);
@@ -63,6 +64,15 @@ export const groupFiles = (doc, files) => {
63
64
  if (figureTypes.includes(node.type)) {
64
65
  figures.push(getFigureElementFiles(node, pos));
65
66
  }
67
+ if (node.type === schema.nodes.image_element) {
68
+ if (node.attrs.extLink) {
69
+ linkedFiles.push({
70
+ node,
71
+ pos,
72
+ file: getFile(node.attrs.extLink),
73
+ });
74
+ }
75
+ }
66
76
  if (node.type === schema.nodes.supplement) {
67
77
  supplements.push({
68
78
  node,
@@ -82,6 +92,7 @@ export const groupFiles = (doc, files) => {
82
92
  figures,
83
93
  supplements,
84
94
  attachments,
95
+ linkedFiles,
85
96
  others: [...fileMap.values()],
86
97
  };
87
98
  };
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.2.36';
1
+ export const VERSION = '3.3.0';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -14,10 +14,17 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import React from 'react';
17
+ import { flushSync } from 'react-dom';
17
18
  import { createRoot } from 'react-dom/client';
18
19
  import { ThemeProvider } from 'styled-components';
19
20
  function createSubView(props, Component, componentProps, node, getPos, view, classNames = []) {
20
21
  const container = document.createElement('div');
22
+ const Wrapped = createView(props, Component, componentProps, node, getPos, view, classNames, container);
23
+ const root = createRoot(container);
24
+ root.render(React.createElement(Wrapped, null));
25
+ return container;
26
+ }
27
+ function createView(props, Component, componentProps, node, getPos, view, classNames = [], container) {
21
28
  container.classList.add('tools-panel');
22
29
  if (classNames.length) {
23
30
  container.classList.add(...classNames);
@@ -36,8 +43,20 @@ function createSubView(props, Component, componentProps, node, getPos, view, cla
36
43
  return (React.createElement(ThemeProvider, { theme: props.theme },
37
44
  React.createElement(Component, { nodeAttrs: node.attrs, setNodeAttrs: setNodeAttrs, viewProps: { node, view, getPos }, container: container, ...props, ...componentProps })));
38
45
  };
46
+ return Wrapped;
47
+ }
48
+ export function createSubViewAsync(props, Component, componentProps, node, getPos, view, classNames = []) {
49
+ const container = document.createElement('div');
50
+ const Wrapped = createView(props, Component, componentProps, node, getPos, view, classNames, container);
39
51
  const root = createRoot(container);
40
- root.render(React.createElement(Wrapped, null));
41
- return container;
52
+ const res = new Promise((resolve) => {
53
+ queueMicrotask(() => {
54
+ flushSync(() => {
55
+ root.render(React.createElement(Wrapped, null));
56
+ });
57
+ resolve(container);
58
+ });
59
+ });
60
+ return res;
42
61
  }
43
62
  export default createSubView;
@@ -21,6 +21,7 @@ export class AccessibilityElementView extends BlockView {
21
21
  createElement() {
22
22
  super.createElement();
23
23
  this.contentDOM.className = 'accessibility_element_input';
24
+ this.contentDOM.setAttribute('contenteditable', 'true');
24
25
  }
25
26
  }
26
27
  export default createNodeView(AccessibilityElementView);
@@ -15,10 +15,11 @@
15
15
  */
16
16
  import { ContextMenu } from '@manuscripts/style-guide';
17
17
  import { schema } from '@manuscripts/transform';
18
+ import { ExtLinkEditor, } from '../components/views/ExtLinkEditor';
18
19
  import { createPositionMenuWrapper } from '../lib/position-menu';
19
20
  import BlockView from './block_view';
20
21
  import { createNodeView } from './creators';
21
- import ReactSubView from './ReactSubView';
22
+ import ReactSubView, { createSubViewAsync } from './ReactSubView';
22
23
  export var figurePositions;
23
24
  (function (figurePositions) {
24
25
  figurePositions["left"] = "half-left";
@@ -28,6 +29,7 @@ export var figurePositions;
28
29
  export class ImageElementView extends BlockView {
29
30
  constructor() {
30
31
  super(...arguments);
32
+ this.isEditingExtLink = false;
31
33
  this.ignoreMutation = () => true;
32
34
  this.showPositionMenu = () => {
33
35
  const firstFigure = this.getFirstFigure();
@@ -77,17 +79,24 @@ export class ImageElementView extends BlockView {
77
79
  createElement() {
78
80
  this.container = document.createElement('div');
79
81
  this.container.classList.add('block');
80
- this.container.setAttribute('contenteditable', 'true');
82
+ this.container.setAttribute('contenteditable', 'false');
81
83
  this.dom.appendChild(this.container);
82
- this.contentDOM = document.createElement('figure');
83
- this.contentDOM.classList.add('figure-block');
84
+ this.subcontainer = document.createElement('div');
85
+ this.subcontainer.classList.add('figure-block-group');
86
+ this.contentDOM = document.createElement('div');
87
+ this.contentDOM.setAttribute('contenteditable', 'false');
84
88
  this.contentDOM.setAttribute('id', this.node.attrs.id);
85
- this.container.appendChild(this.contentDOM);
89
+ this.contentDOM.classList.add('figure-block');
90
+ this.subcontainer.appendChild(this.contentDOM);
91
+ this.container.appendChild(this.subcontainer);
86
92
  this.addTools();
87
93
  }
88
94
  updateContents() {
89
95
  super.updateContents();
90
96
  this.addTools();
97
+ if (this.node.type === schema.nodes.image_element) {
98
+ this.addExternalLinkedFileEditor();
99
+ }
91
100
  }
92
101
  addTools() {
93
102
  this.addPositionMenu();
@@ -153,5 +162,26 @@ export class ImageElementView extends BlockView {
153
162
  this.view.dispatch(tr);
154
163
  this.figurePosition = position;
155
164
  }
165
+ addExternalLinkedFileEditor() {
166
+ if (this.props.dispatch && this.props.theme) {
167
+ const componentProps = {
168
+ node: this.node,
169
+ nodePos: this.getPos(),
170
+ view: this.view,
171
+ editorProps: this.props,
172
+ isEditing: this.isEditingExtLink,
173
+ setIsEditing: (val) => {
174
+ this.isEditingExtLink = val;
175
+ this.updateContents();
176
+ },
177
+ };
178
+ return createSubViewAsync(this.props, ExtLinkEditor, componentProps, this.node, this.getPos, this.view, ['ext-link-editor-container']).then((elem) => {
179
+ this.extLinkEditorContainer?.remove();
180
+ this.extLinkEditorContainer = elem;
181
+ this.subcontainer?.appendChild(this.extLinkEditorContainer);
182
+ return;
183
+ });
184
+ }
185
+ }
156
186
  }
157
187
  export default createNodeView(ImageElementView);
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ interface DragAndDropUploaderProps {
3
+ upload: (file: File) => Promise<void>;
4
+ onUploadError: (message: string) => void;
5
+ allowedFileTypes?: string[];
6
+ maxFileSizeMB?: number;
7
+ }
8
+ export declare const DragAndDropUploader: React.FC<DragAndDropUploaderProps>;
9
+ export {};
@@ -0,0 +1,13 @@
1
+ import { ManuscriptNode } from '@manuscripts/transform';
2
+ import { EditorView } from 'prosemirror-view';
3
+ import React from 'react';
4
+ import { EditorProps } from '../../configs/ManuscriptsEditor';
5
+ export interface ExtLinkEditorProps {
6
+ node: ManuscriptNode;
7
+ nodePos: number;
8
+ view: EditorView;
9
+ editorProps: EditorProps;
10
+ isEditing: boolean;
11
+ setIsEditing: (val: boolean) => void;
12
+ }
13
+ export declare const ExtLinkEditor: React.FC<ExtLinkEditorProps>;
@@ -19,6 +19,7 @@ export type ManuscriptFiles = {
19
19
  figures: ElementFiles[];
20
20
  supplements: NodeFile[];
21
21
  attachments: NodeFile[];
22
+ linkedFiles: NodeFile[];
22
23
  others: FileAttachment[];
23
24
  };
24
25
  export type Upload = (file: File) => Promise<FileAttachment>;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.2.36";
1
+ export declare const VERSION = "3.3.0";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
@@ -28,4 +28,5 @@ export interface ReactViewComponentProps<NodeT extends ManuscriptNode> {
28
28
  container: HTMLDivElement;
29
29
  }
30
30
  declare function createSubView<T extends Trackable<ManuscriptNode>>(props: EditorProps, Component: React.FC<any>, componentProps: object, node: T, getPos: () => number, view: ManuscriptEditorView, classNames?: string[]): HTMLDivElement;
31
+ export declare function createSubViewAsync<T extends Trackable<ManuscriptNode>>(props: EditorProps, Component: React.FC<any>, componentProps: object, node: T, getPos: () => number, view: ManuscriptEditorView, classNames?: string[]): Promise<HTMLDivElement>;
31
32
  export default createSubView;
@@ -13,7 +13,7 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { FigureElementNode } from '@manuscripts/transform';
16
+ import { ImageElementNode } from '@manuscripts/transform';
17
17
  import { Trackable } from '../types';
18
18
  import BlockView from './block_view';
19
19
  export declare enum figurePositions {
@@ -21,10 +21,13 @@ export declare enum figurePositions {
21
21
  right = "half-right",
22
22
  default = ""
23
23
  }
24
- export declare class ImageElementView extends BlockView<Trackable<FigureElementNode>> {
24
+ export declare class ImageElementView extends BlockView<Trackable<ImageElementNode>> {
25
25
  container: HTMLElement;
26
+ subcontainer: HTMLElement;
27
+ extLinkEditorContainer: HTMLDivElement;
26
28
  private positionMenuWrapper;
27
29
  private figurePosition;
30
+ private isEditingExtLink;
28
31
  ignoreMutation: () => boolean;
29
32
  createDOM(): void;
30
33
  createElement(): void;
@@ -35,6 +38,7 @@ export declare class ImageElementView extends BlockView<Trackable<FigureElementN
35
38
  private getAllFigures;
36
39
  private showPositionMenu;
37
40
  private updateAllFiguresPosition;
41
+ private addExternalLinkedFileEditor;
38
42
  }
39
43
  declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch | undefined) => import("../types").NodeViewCreator<ImageElementView>;
40
44
  export default _default;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@manuscripts/body-editor",
3
3
  "description": "Prosemirror components for editing and viewing manuscripts",
4
- "version": "3.2.36",
4
+ "version": "3.3.0",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -38,9 +38,9 @@
38
38
  "@citation-js/plugin-pubmed": "0.3.0",
39
39
  "@citation-js/plugin-ris": "0.7.18",
40
40
  "@manuscripts/json-schema": "2.2.12",
41
- "@manuscripts/style-guide": "3.1.7",
41
+ "@manuscripts/style-guide": "3.2.0",
42
42
  "@manuscripts/track-changes-plugin": "2.0.9",
43
- "@manuscripts/transform": "4.2.10",
43
+ "@manuscripts/transform": "4.2.11",
44
44
  "@popperjs/core": "2.11.8",
45
45
  "citeproc": "2.4.63",
46
46
  "codemirror": "5.65.19",
@@ -129,8 +129,8 @@
129
129
  border: 1px solid #f2f2f2;
130
130
  border-radius: 4px;
131
131
 
132
- padding: 4pt !important;
133
- gap: 4pt !important;
132
+ padding: 8px;
133
+ gap: 8px !important;
134
134
 
135
135
  grid-template-columns: repeat(3, auto) !important;
136
136
  grid-template-rows: repeat(1, minmax(min-content, max-content)) [caption listing] auto !important;
@@ -140,6 +140,27 @@
140
140
  justify-self: center;
141
141
  position: relative;
142
142
  }
143
+ .ProseMirror .figure-block-group {
144
+ width: 100%;
145
+ border: 1px solid #f2f2f2;
146
+ border-radius: 4px;
147
+ position: relative;
148
+ padding: 8px;
149
+ gap: 8px !important;
150
+
151
+ grid-template-columns: repeat(3, auto) !important;
152
+ grid-template-rows: repeat(1, minmax(min-content, max-content)) [caption listing] auto !important;
153
+ }
154
+
155
+ .ProseMirror .figure-block-group .figure-block {
156
+ border: none;
157
+ border-radius: 0;
158
+ position: static;
159
+ padding: 0;
160
+ margin: 0 !important;
161
+ grid-template-columns: auto;
162
+ grid-template-rows: auto;
163
+ }
143
164
 
144
165
  .ProseMirror .figure-group {
145
166
  border: 1px solid #f2f2f2;
@@ -267,8 +288,12 @@
267
288
  padding: 16px 32px;
268
289
  }
269
290
 
270
- .ProseMirror .figure.placeholder a {
291
+ .ProseMirror a[data-action] {
271
292
  text-decoration: underline;
293
+ cursor: pointer;
294
+ }
295
+ .ProseMirror a[data-action]:hover {
296
+ text-decoration: none;
272
297
  }
273
298
 
274
299
  .ProseMirror .figure.placeholder.over {
@@ -345,7 +370,9 @@
345
370
  text-align: center !important;
346
371
  }
347
372
 
348
- .ProseMirror .block-figure_element .position-menu, .ProseMirror .block-image_element .position-menu, .ProseMirror .block-embed .position-menu {
373
+ .ProseMirror .block-figure_element .position-menu,
374
+ .ProseMirror .block-image_element .position-menu,
375
+ ProseMirror .block-embed .position-menu {
349
376
  position: absolute;
350
377
  top: 15px;
351
378
  right: 35px;
@@ -371,9 +398,8 @@
371
398
  align-items: center;
372
399
  pointer-events: auto;
373
400
  transition: opacity 0.2s;
374
- background-color: #FAFAFA;
375
- border: 1px solid #F2F2F2;
376
-
401
+ background-color: #fafafa;
402
+ border: 1px solid #f2f2f2;
377
403
  }
378
404
  .ProseMirror .figure-block .figure:hover .drag-handler {
379
405
  opacity: 1;
@@ -382,13 +408,17 @@
382
408
  /* Drop area blue dotted outline */
383
409
  .ProseMirror .figure-block .figure.drop-target-above,
384
410
  .ProseMirror .figure-block .figure.drop-target-below {
385
- border: 2px dotted #20AEDF !important;
411
+ border: 2px dotted #20aedf !important;
386
412
  transition: border 0.2s, box-shadow 0.2s;
387
413
  position: relative;
388
414
  }
389
415
 
390
- .ProseMirror .figure-block .figure.drop-target-above::before { top: 0; }
391
- .ProseMirror .figure-block .figure.drop-target-below::after { bottom: 0; }
416
+ .ProseMirror .figure-block .figure.drop-target-above::before {
417
+ top: 0;
418
+ }
419
+ .ProseMirror .figure-block .figure.drop-target-below::after {
420
+ bottom: 0;
421
+ }
392
422
 
393
423
  .ProseMirror .figure.dragging {
394
424
  opacity: 0.6;
@@ -1052,8 +1082,12 @@ figure.block:has(.equation.selected-suggestion) {
1052
1082
  .block,
1053
1083
  .tracking-visible
1054
1084
  .selected-suggestion[data-track-op='move'][data-track-status='pending']
1055
- .block , .block:has(figure.selected-suggestion, [data-track-op='move'][data-track-status='pending']){
1056
- box-shadow: inset 3px 0 0 var(--updated-border-color) ;
1085
+ .block,
1086
+ .block:has(
1087
+ figure.selected-suggestion,
1088
+ [data-track-op='move'][data-track-status='pending']
1089
+ ) {
1090
+ box-shadow: inset 3px 0 0 var(--updated-border-color);
1057
1091
  }
1058
1092
 
1059
1093
  .tracking-visible .selected-suggestion[data-track-op='set_attrs'] .block,
@@ -1527,7 +1561,8 @@ th:hover > .table-context-menu-button,
1527
1561
  justify-content: center;
1528
1562
  }
1529
1563
 
1530
- .ProseMirror .accessibility_element {
1564
+ .ProseMirror .accessibility_element,
1565
+ .ProseMirror .ext-link-editor-container {
1531
1566
  margin-top: 12px;
1532
1567
  grid-column-start: 1;
1533
1568
  grid-column-end: -1;
@@ -1535,10 +1570,15 @@ th:hover > .table-context-menu-button,
1535
1570
  display: none;
1536
1571
  }
1537
1572
 
1538
- .ProseMirror .show_accessibility_element .accessibility_element {
1573
+ .ProseMirror .show_accessibility_element .accessibility_element,
1574
+ .ProseMirror .show_accessibility_element .ext-link-editor-container {
1539
1575
  display: block;
1540
1576
  }
1541
1577
 
1578
+ .ProseMirror .ext-link-editor-container {
1579
+ position: static;
1580
+ }
1581
+
1542
1582
  .ProseMirror .accessibility_element_label {
1543
1583
  color: var(--label-color);
1544
1584
  font-size: 18px;
@@ -1550,6 +1590,11 @@ th:hover > .table-context-menu-button,
1550
1590
  cursor: pointer;
1551
1591
  }
1552
1592
 
1593
+ .ProseMirror .accessibility_element_input:focus,
1594
+ .ProseMirror .accessibility_element_input:focus-visible {
1595
+ outline: none;
1596
+ }
1597
+
1553
1598
  .ProseMirror .accessibility_element_input {
1554
1599
  border-radius: 4px;
1555
1600
  border: 1px solid var(--accepted-bg-color);
package/styles/Editor.css CHANGED
@@ -329,6 +329,7 @@
329
329
  }
330
330
 
331
331
  .block-image_element .figure-label {
332
+ display: block;
332
333
  text-align: center;
333
334
  grid-column-start: 1;
334
335
  grid-column-end: -1;