@manuscripts/body-editor 3.2.20 → 3.2.23

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/dist/cjs/icons.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addFigureBtnIcon = exports.plusIcon = exports.lockIcon = exports.scrollIcon = exports.sectionCategoryIcon = exports.editIcon = exports.deleteIcon = exports.alertIcon = exports.arrowUp = exports.arrowDown = void 0;
3
+ exports.fileCorruptedIcon = exports.imageDefaultIcon = exports.imageLeftIcon = exports.imageRightIcon = exports.addFigureBtnIcon = exports.plusIcon = exports.lockIcon = exports.scrollIcon = exports.sectionCategoryIcon = exports.editIcon = exports.deleteIcon = exports.alertIcon = exports.arrowUp = exports.arrowDown = void 0;
4
4
  const style_guide_1 = require("@manuscripts/style-guide");
5
5
  const react_1 = require("react");
6
6
  const server_1 = require("react-dom/server");
@@ -15,3 +15,7 @@ exports.scrollIcon = (0, server_1.renderToStaticMarkup)((0, react_1.createElemen
15
15
  exports.lockIcon = (0, server_1.renderToStaticMarkup)((0, react_1.createElement)(style_guide_1.LockIcon));
16
16
  exports.plusIcon = renderIcon(style_guide_1.PlusIcon);
17
17
  exports.addFigureBtnIcon = (0, server_1.renderToStaticMarkup)((0, react_1.createElement)(style_guide_1.PlusIcon));
18
+ exports.imageRightIcon = (0, server_1.renderToStaticMarkup)((0, react_1.createElement)(style_guide_1.ImageRightIcon));
19
+ exports.imageLeftIcon = (0, server_1.renderToStaticMarkup)((0, react_1.createElement)(style_guide_1.ImageLeftIcon));
20
+ exports.imageDefaultIcon = (0, server_1.renderToStaticMarkup)((0, react_1.createElement)(style_guide_1.ImageDefaultIcon));
21
+ exports.fileCorruptedIcon = (0, server_1.renderToStaticMarkup)((0, react_1.createElement)(style_guide_1.FileCorruptedIcon));
@@ -22,7 +22,7 @@ const prosemirror_utils_1 = require("prosemirror-utils");
22
22
  const react_1 = require("react");
23
23
  const server_1 = require("react-dom/server");
24
24
  const commands_1 = require("../commands");
25
- const figure_editable_1 = require("../views/figure_editable");
25
+ const image_element_1 = require("../views/image_element");
26
26
  const popper_1 = require("./popper");
27
27
  const utils_1 = require("./utils");
28
28
  const view_1 = require("./view");
@@ -164,16 +164,16 @@ class ContextMenu {
164
164
  title: 'Left',
165
165
  action: () => (0, view_1.updateNodeAttrs)(this.view, transform_1.schema.nodes.figure, {
166
166
  ...figure.attrs,
167
- type: figure_editable_1.figurePositions.left,
167
+ type: image_element_1.figurePositions.left,
168
168
  }),
169
169
  Icon: style_guide_1.ImageLeftIcon,
170
- selected: attrType === figure_editable_1.figurePositions.left,
170
+ selected: attrType === image_element_1.figurePositions.left,
171
171
  },
172
172
  {
173
173
  title: 'Default',
174
174
  action: () => (0, view_1.updateNodeAttrs)(this.view, transform_1.schema.nodes.figure, {
175
175
  ...figure.attrs,
176
- type: figure_editable_1.figurePositions.default,
176
+ type: image_element_1.figurePositions.default,
177
177
  }),
178
178
  Icon: style_guide_1.ImageDefaultIcon,
179
179
  selected: !attrType,
@@ -182,10 +182,10 @@ class ContextMenu {
182
182
  title: 'Right',
183
183
  action: () => (0, view_1.updateNodeAttrs)(this.view, transform_1.schema.nodes.figure, {
184
184
  ...figure.attrs,
185
- type: figure_editable_1.figurePositions.right,
185
+ type: image_element_1.figurePositions.right,
186
186
  }),
187
187
  Icon: style_guide_1.ImageRightIcon,
188
- selected: attrType === figure_editable_1.figurePositions.right,
188
+ selected: attrType === image_element_1.figurePositions.right,
189
189
  },
190
190
  ];
191
191
  const submenuLabel = 'Position';
@@ -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.20';
4
+ exports.VERSION = '3.2.23';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -18,26 +18,17 @@ 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.FigureEditableView = exports.figurePositions = void 0;
22
- const style_guide_1 = require("@manuscripts/style-guide");
21
+ exports.FigureEditableView = void 0;
23
22
  const transform_1 = require("@manuscripts/transform");
24
23
  const prosemirror_state_1 = require("prosemirror-state");
25
24
  const prosemirror_utils_1 = require("prosemirror-utils");
26
- const react_1 = require("react");
27
- const server_1 = require("react-dom/server");
28
25
  const FigureDropdown_1 = require("../components/views/FigureDropdown");
26
+ const icons_1 = require("../icons");
29
27
  const track_changes_utils_1 = require("../lib/track-changes-utils");
30
- const view_1 = require("../lib/view");
31
28
  const creators_1 = require("./creators");
32
29
  const figure_1 = require("./figure");
33
30
  const figure_uploader_1 = require("./figure_uploader");
34
31
  const ReactSubView_1 = __importDefault(require("./ReactSubView"));
35
- var figurePositions;
36
- (function (figurePositions) {
37
- figurePositions["left"] = "half-left";
38
- figurePositions["right"] = "half-right";
39
- figurePositions["default"] = "";
40
- })(figurePositions = exports.figurePositions || (exports.figurePositions = {}));
41
32
  class FigureEditableView extends figure_1.FigureView {
42
33
  constructor() {
43
34
  super(...arguments);
@@ -60,7 +51,7 @@ class FigureEditableView extends figure_1.FigureView {
60
51
  element.classList.add('figure', 'placeholder');
61
52
  const instructions = document.createElement('div');
62
53
  instructions.classList.add('instructions');
63
- const iconHtml = (0, server_1.renderToStaticMarkup)((0, react_1.createElement)(style_guide_1.FileCorruptedIcon, { className: 'icon' }));
54
+ const iconHtml = icons_1.fileCorruptedIcon;
64
55
  instructions.innerHTML = `
65
56
  <div>
66
57
  <div class="unsupported-icon-wrapper">${iconHtml}</div>
@@ -98,78 +89,6 @@ class FigureEditableView extends figure_1.FigureView {
98
89
  element.appendChild(instructions);
99
90
  return element;
100
91
  };
101
- this.createPositionMenuWrapper = () => {
102
- const can = this.props.getCapabilities();
103
- this.positionMenuWrapper = document.createElement('div');
104
- this.positionMenuWrapper.classList.add('position-menu');
105
- const positionMenuButton = document.createElement('div');
106
- positionMenuButton.classList.add('position-menu-button');
107
- let icon;
108
- switch (this.figurePosition) {
109
- case figurePositions.left:
110
- icon = style_guide_1.ImageLeftIcon;
111
- break;
112
- case figurePositions.right:
113
- icon = style_guide_1.ImageRightIcon;
114
- break;
115
- default:
116
- icon = style_guide_1.ImageDefaultIcon;
117
- break;
118
- }
119
- if (icon) {
120
- positionMenuButton.innerHTML = (0, server_1.renderToStaticMarkup)((0, react_1.createElement)(icon));
121
- }
122
- if (can.editArticle) {
123
- positionMenuButton.addEventListener('click', this.showPositionMenu);
124
- }
125
- this.positionMenuWrapper.appendChild(positionMenuButton);
126
- return this.positionMenuWrapper;
127
- };
128
- this.showPositionMenu = () => {
129
- this.props.popper.destroy();
130
- const figure = this.node;
131
- const componentProps = {
132
- actions: [
133
- {
134
- label: 'Left',
135
- action: () => {
136
- this.props.popper.destroy();
137
- (0, view_1.updateNodeAttrs)(this.view, transform_1.schema.nodes.figure, {
138
- ...figure.attrs,
139
- type: figurePositions.left,
140
- });
141
- },
142
- icon: 'ImageLeft',
143
- selected: this.figurePosition === figurePositions.left,
144
- },
145
- {
146
- label: 'Default',
147
- action: () => {
148
- this.props.popper.destroy();
149
- (0, view_1.updateNodeAttrs)(this.view, transform_1.schema.nodes.figure, {
150
- ...figure.attrs,
151
- type: figurePositions.default,
152
- });
153
- },
154
- icon: 'ImageDefault',
155
- selected: !this.figurePosition,
156
- },
157
- {
158
- label: 'Right',
159
- action: () => {
160
- this.props.popper.destroy();
161
- (0, view_1.updateNodeAttrs)(this.view, transform_1.schema.nodes.figure, {
162
- ...figure.attrs,
163
- type: figurePositions.right,
164
- });
165
- },
166
- icon: 'ImageRight',
167
- selected: this.figurePosition === figurePositions.right,
168
- },
169
- ],
170
- };
171
- this.props.popper.show(this.positionMenuWrapper, (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'position-menu']), 'left', false);
172
- };
173
92
  }
174
93
  initialise() {
175
94
  this.upload = this.upload.bind(this);
@@ -181,7 +100,6 @@ class FigureEditableView extends figure_1.FigureView {
181
100
  const src = this.node.attrs.src;
182
101
  const files = this.props.getFiles();
183
102
  const file = src && files.filter((f) => f.id === src)[0];
184
- this.figurePosition = this.node.attrs.type;
185
103
  this.container.innerHTML = '';
186
104
  const can = this.props.getCapabilities();
187
105
  const link = file && this.props.fileManagement.previewLink(file);
@@ -236,7 +154,6 @@ class FigureEditableView extends figure_1.FigureView {
236
154
  }
237
155
  addTools() {
238
156
  this.manageReactTools();
239
- this.container.appendChild(this.createPositionMenuWrapper());
240
157
  }
241
158
  manageReactTools() {
242
159
  let handleDownload;
@@ -24,6 +24,7 @@ class FigureElementView extends image_element_1.ImageElementView {
24
24
  constructor() {
25
25
  super(...arguments);
26
26
  this.ignoreMutation = () => true;
27
+ this.resizeObserver = null;
27
28
  this.createElement = () => {
28
29
  super.createElement();
29
30
  this.addFigureElementButtons();
@@ -48,16 +49,65 @@ class FigureElementView extends image_element_1.ImageElementView {
48
49
  this.view.dispatch(tr);
49
50
  };
50
51
  }
52
+ initialise() {
53
+ super.initialise();
54
+ requestAnimationFrame(() => this.updateButtonPosition());
55
+ this.setupResizeObserver();
56
+ }
57
+ setupResizeObserver() {
58
+ if (this.resizeObserver) {
59
+ this.resizeObserver.disconnect();
60
+ }
61
+ this.resizeObserver = new ResizeObserver(() => {
62
+ requestAnimationFrame(() => this.updateButtonPosition());
63
+ });
64
+ this.container.querySelectorAll('figure').forEach((figure) => {
65
+ this.resizeObserver?.observe(figure);
66
+ });
67
+ }
51
68
  addFigureElementButtons() {
52
69
  if (this.props.getCapabilities()?.editArticle) {
53
- const addFigureBtn = Object.assign(document.createElement('button'), {
70
+ this.addFigureBtn = Object.assign(document.createElement('button'), {
54
71
  className: 'add-figure-button',
55
72
  innerHTML: icons_1.addFigureBtnIcon,
56
73
  title: 'Add figure',
57
74
  });
58
- addFigureBtn.addEventListener('click', () => this.addFigure());
59
- this.container.prepend(addFigureBtn);
75
+ this.addFigureBtn.addEventListener('click', () => this.addFigure());
76
+ this.container.prepend(this.addFigureBtn);
77
+ }
78
+ }
79
+ updateButtonPosition() {
80
+ if (!this.addFigureBtn) {
81
+ return;
82
+ }
83
+ const figures = this.container.querySelectorAll('figure');
84
+ const lastFigure = figures[figures.length - 1];
85
+ if (!lastFigure) {
86
+ return;
87
+ }
88
+ const lastFigureRect = lastFigure.getBoundingClientRect();
89
+ const containerRect = this.container.getBoundingClientRect();
90
+ const relativeTop = lastFigureRect.bottom - containerRect.top + 20;
91
+ this.addFigureBtn.style.top = `${relativeTop}px`;
92
+ }
93
+ update(node) {
94
+ const handledBySuper = super.update(node);
95
+ if (handledBySuper) {
96
+ this.setupResizeObserver();
97
+ requestAnimationFrame(() => this.updateButtonPosition());
98
+ }
99
+ return handledBySuper;
100
+ }
101
+ updateContents() {
102
+ super.updateContents();
103
+ requestAnimationFrame(() => this.updateButtonPosition());
104
+ }
105
+ destroy() {
106
+ if (this.resizeObserver) {
107
+ this.resizeObserver.disconnect();
108
+ this.resizeObserver = null;
60
109
  }
110
+ super.destroy();
61
111
  }
62
112
  }
63
113
  exports.FigureElementView = FigureElementView;
@@ -18,13 +18,87 @@ 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.ImageElementView = void 0;
21
+ exports.ImageElementView = exports.figurePositions = void 0;
22
+ const style_guide_1 = require("@manuscripts/style-guide");
23
+ const transform_1 = require("@manuscripts/transform");
24
+ const icons_1 = require("../icons");
22
25
  const block_view_1 = __importDefault(require("./block_view"));
23
26
  const creators_1 = require("./creators");
27
+ const ReactSubView_1 = __importDefault(require("./ReactSubView"));
28
+ var figurePositions;
29
+ (function (figurePositions) {
30
+ figurePositions["left"] = "half-left";
31
+ figurePositions["right"] = "half-right";
32
+ figurePositions["default"] = "";
33
+ })(figurePositions = exports.figurePositions || (exports.figurePositions = {}));
24
34
  class ImageElementView extends block_view_1.default {
25
35
  constructor() {
26
36
  super(...arguments);
27
37
  this.ignoreMutation = () => true;
38
+ this.createPositionMenuWrapper = () => {
39
+ const can = this.props.getCapabilities();
40
+ this.positionMenuWrapper = document.createElement('div');
41
+ this.positionMenuWrapper.classList.add('position-menu');
42
+ const positionMenuButton = document.createElement('div');
43
+ positionMenuButton.classList.add('position-menu-button');
44
+ const firstFigure = this.getFirstFigure();
45
+ this.figurePosition = firstFigure?.attrs.type || figurePositions.default;
46
+ let icon;
47
+ switch (this.figurePosition) {
48
+ case figurePositions.left:
49
+ icon = icons_1.imageLeftIcon;
50
+ break;
51
+ case figurePositions.right:
52
+ icon = icons_1.imageRightIcon;
53
+ break;
54
+ default:
55
+ icon = icons_1.imageDefaultIcon;
56
+ break;
57
+ }
58
+ if (icon) {
59
+ positionMenuButton.innerHTML = icon;
60
+ }
61
+ if (can.editArticle) {
62
+ positionMenuButton.addEventListener('click', this.showPositionMenu);
63
+ }
64
+ this.positionMenuWrapper.appendChild(positionMenuButton);
65
+ return this.positionMenuWrapper;
66
+ };
67
+ this.showPositionMenu = () => {
68
+ this.props.popper.destroy();
69
+ const componentProps = {
70
+ actions: [
71
+ {
72
+ label: 'Left',
73
+ action: () => {
74
+ this.props.popper.destroy();
75
+ this.updateAllFiguresPosition(figurePositions.left);
76
+ },
77
+ icon: 'ImageLeft',
78
+ selected: this.figurePosition === figurePositions.left,
79
+ },
80
+ {
81
+ label: 'Default',
82
+ action: () => {
83
+ this.props.popper.destroy();
84
+ this.updateAllFiguresPosition(figurePositions.default);
85
+ },
86
+ icon: 'ImageDefault',
87
+ selected: !this.figurePosition,
88
+ },
89
+ {
90
+ label: 'Right',
91
+ action: () => {
92
+ this.props.popper.destroy();
93
+ this.updateAllFiguresPosition(figurePositions.right);
94
+ },
95
+ icon: 'ImageRight',
96
+ selected: this.figurePosition === figurePositions.right,
97
+ },
98
+ ],
99
+ };
100
+ this.props.popper.show(this.positionMenuWrapper, (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'position-menu']), 'left', false);
101
+ };
28
102
  }
29
103
  createDOM() {
30
104
  super.createDOM();
@@ -39,6 +113,72 @@ class ImageElementView extends block_view_1.default {
39
113
  this.contentDOM.classList.add('figure-block');
40
114
  this.contentDOM.setAttribute('id', this.node.attrs.id);
41
115
  this.container.appendChild(this.contentDOM);
116
+ this.addTools();
117
+ }
118
+ updateContents() {
119
+ super.updateContents();
120
+ this.addTools();
121
+ }
122
+ addTools() {
123
+ this.addPositionMenu();
124
+ }
125
+ addPositionMenu() {
126
+ if (this.props.getCapabilities()?.editArticle) {
127
+ const existingMenu = this.container.querySelector('.position-menu');
128
+ if (existingMenu) {
129
+ existingMenu.remove();
130
+ }
131
+ this.container.prepend(this.createPositionMenuWrapper());
132
+ }
133
+ }
134
+ getFirstFigure() {
135
+ if (this.node.type === transform_1.schema.nodes.image_element) {
136
+ const figureNode = this.node.firstChild;
137
+ if (figureNode && figureNode.type === transform_1.schema.nodes.figure) {
138
+ return figureNode;
139
+ }
140
+ return null;
141
+ }
142
+ else {
143
+ let firstFigure = null;
144
+ this.node.forEach((node) => {
145
+ if (node.type === transform_1.schema.nodes.figure && !firstFigure) {
146
+ firstFigure = node;
147
+ }
148
+ });
149
+ return firstFigure;
150
+ }
151
+ }
152
+ getAllFigures() {
153
+ const figures = [];
154
+ let pos = this.getPos() + 1;
155
+ if (this.node.type === transform_1.schema.nodes.image_element) {
156
+ const figureNode = this.node.firstChild;
157
+ if (figureNode && figureNode.type === transform_1.schema.nodes.figure) {
158
+ figures.push({ node: figureNode, pos });
159
+ }
160
+ }
161
+ else {
162
+ this.node.forEach((node) => {
163
+ if (node.type === transform_1.schema.nodes.figure) {
164
+ figures.push({ node: node, pos });
165
+ }
166
+ pos += node.nodeSize;
167
+ });
168
+ }
169
+ return figures;
170
+ }
171
+ updateAllFiguresPosition(position) {
172
+ const figures = this.getAllFigures();
173
+ const { tr } = this.view.state;
174
+ figures.forEach(({ node, pos }) => {
175
+ tr.setNodeMarkup(pos, undefined, {
176
+ ...node.attrs,
177
+ type: position,
178
+ });
179
+ });
180
+ this.view.dispatch(tr);
181
+ this.figurePosition = position;
42
182
  }
43
183
  }
44
184
  exports.ImageElementView = ImageElementView;
package/dist/es/icons.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AlertIcon, ArrowDownCircleIcon, ArrowUpIcon, DeleteIcon, EditIcon, LockIcon, PlusIcon, ScrollIcon, SectionCategoryIcon, } from '@manuscripts/style-guide';
1
+ import { AlertIcon, ArrowDownCircleIcon, ArrowUpIcon, DeleteIcon, EditIcon, FileCorruptedIcon, ImageDefaultIcon, ImageLeftIcon, ImageRightIcon, LockIcon, PlusIcon, ScrollIcon, SectionCategoryIcon, } from '@manuscripts/style-guide';
2
2
  import { createElement } from 'react';
3
3
  import { renderToStaticMarkup } from 'react-dom/server';
4
4
  const renderIcon = (c) => renderToStaticMarkup(createElement(c));
@@ -12,3 +12,7 @@ export const scrollIcon = renderToStaticMarkup(createElement(ScrollIcon));
12
12
  export const lockIcon = renderToStaticMarkup(createElement(LockIcon));
13
13
  export const plusIcon = renderIcon(PlusIcon);
14
14
  export const addFigureBtnIcon = renderToStaticMarkup(createElement(PlusIcon));
15
+ export const imageRightIcon = renderToStaticMarkup(createElement(ImageRightIcon));
16
+ export const imageLeftIcon = renderToStaticMarkup(createElement(ImageLeftIcon));
17
+ export const imageDefaultIcon = renderToStaticMarkup(createElement(ImageDefaultIcon));
18
+ export const fileCorruptedIcon = renderToStaticMarkup(createElement(FileCorruptedIcon));
@@ -19,7 +19,7 @@ import { findChildrenByType } from 'prosemirror-utils';
19
19
  import { createElement } from 'react';
20
20
  import { renderToStaticMarkup } from 'react-dom/server';
21
21
  import { addNodeComment, createBlock, findPosBeforeFirstSubsection, insertGeneralTableFootnote, insertInlineTableFootnote, isCommentingAllowed, } from '../commands';
22
- import { figurePositions } from '../views/figure_editable';
22
+ import { figurePositions } from '../views/image_element';
23
23
  import { PopperManager } from './popper';
24
24
  import { getMatchingChild, isChildOfNodeTypes, isSelectionInNode, } from './utils';
25
25
  import { updateNodeAttrs } from './view';
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.2.20';
1
+ export const VERSION = '3.2.23';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -13,25 +13,16 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { ContextMenu, FileCorruptedIcon, ImageDefaultIcon, ImageLeftIcon, ImageRightIcon, } from '@manuscripts/style-guide';
17
16
  import { schema } from '@manuscripts/transform';
18
17
  import { NodeSelection } from 'prosemirror-state';
19
18
  import { findParentNodeOfTypeClosestToPos } from 'prosemirror-utils';
20
- import { createElement } from 'react';
21
- import { renderToStaticMarkup } from 'react-dom/server';
22
19
  import { FigureOptions, } from '../components/views/FigureDropdown';
20
+ import { fileCorruptedIcon } from '../icons';
23
21
  import { isDeleted } from '../lib/track-changes-utils';
24
- import { updateNodeAttrs } from '../lib/view';
25
22
  import { createEditableNodeView } from './creators';
26
23
  import { FigureView } from './figure';
27
24
  import { figureUploader } from './figure_uploader';
28
25
  import ReactSubView from './ReactSubView';
29
- export var figurePositions;
30
- (function (figurePositions) {
31
- figurePositions["left"] = "half-left";
32
- figurePositions["right"] = "half-right";
33
- figurePositions["default"] = "";
34
- })(figurePositions || (figurePositions = {}));
35
26
  export class FigureEditableView extends FigureView {
36
27
  constructor() {
37
28
  super(...arguments);
@@ -54,7 +45,7 @@ export class FigureEditableView extends FigureView {
54
45
  element.classList.add('figure', 'placeholder');
55
46
  const instructions = document.createElement('div');
56
47
  instructions.classList.add('instructions');
57
- const iconHtml = renderToStaticMarkup(createElement(FileCorruptedIcon, { className: 'icon' }));
48
+ const iconHtml = fileCorruptedIcon;
58
49
  instructions.innerHTML = `
59
50
  <div>
60
51
  <div class="unsupported-icon-wrapper">${iconHtml}</div>
@@ -92,78 +83,6 @@ export class FigureEditableView extends FigureView {
92
83
  element.appendChild(instructions);
93
84
  return element;
94
85
  };
95
- this.createPositionMenuWrapper = () => {
96
- const can = this.props.getCapabilities();
97
- this.positionMenuWrapper = document.createElement('div');
98
- this.positionMenuWrapper.classList.add('position-menu');
99
- const positionMenuButton = document.createElement('div');
100
- positionMenuButton.classList.add('position-menu-button');
101
- let icon;
102
- switch (this.figurePosition) {
103
- case figurePositions.left:
104
- icon = ImageLeftIcon;
105
- break;
106
- case figurePositions.right:
107
- icon = ImageRightIcon;
108
- break;
109
- default:
110
- icon = ImageDefaultIcon;
111
- break;
112
- }
113
- if (icon) {
114
- positionMenuButton.innerHTML = renderToStaticMarkup(createElement(icon));
115
- }
116
- if (can.editArticle) {
117
- positionMenuButton.addEventListener('click', this.showPositionMenu);
118
- }
119
- this.positionMenuWrapper.appendChild(positionMenuButton);
120
- return this.positionMenuWrapper;
121
- };
122
- this.showPositionMenu = () => {
123
- this.props.popper.destroy();
124
- const figure = this.node;
125
- const componentProps = {
126
- actions: [
127
- {
128
- label: 'Left',
129
- action: () => {
130
- this.props.popper.destroy();
131
- updateNodeAttrs(this.view, schema.nodes.figure, {
132
- ...figure.attrs,
133
- type: figurePositions.left,
134
- });
135
- },
136
- icon: 'ImageLeft',
137
- selected: this.figurePosition === figurePositions.left,
138
- },
139
- {
140
- label: 'Default',
141
- action: () => {
142
- this.props.popper.destroy();
143
- updateNodeAttrs(this.view, schema.nodes.figure, {
144
- ...figure.attrs,
145
- type: figurePositions.default,
146
- });
147
- },
148
- icon: 'ImageDefault',
149
- selected: !this.figurePosition,
150
- },
151
- {
152
- label: 'Right',
153
- action: () => {
154
- this.props.popper.destroy();
155
- updateNodeAttrs(this.view, schema.nodes.figure, {
156
- ...figure.attrs,
157
- type: figurePositions.right,
158
- });
159
- },
160
- icon: 'ImageRight',
161
- selected: this.figurePosition === figurePositions.right,
162
- },
163
- ],
164
- };
165
- this.props.popper.show(this.positionMenuWrapper, ReactSubView(this.props, ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'position-menu']), 'left', false);
166
- };
167
86
  }
168
87
  initialise() {
169
88
  this.upload = this.upload.bind(this);
@@ -175,7 +94,6 @@ export class FigureEditableView extends FigureView {
175
94
  const src = this.node.attrs.src;
176
95
  const files = this.props.getFiles();
177
96
  const file = src && files.filter((f) => f.id === src)[0];
178
- this.figurePosition = this.node.attrs.type;
179
97
  this.container.innerHTML = '';
180
98
  const can = this.props.getCapabilities();
181
99
  const link = file && this.props.fileManagement.previewLink(file);
@@ -230,7 +148,6 @@ export class FigureEditableView extends FigureView {
230
148
  }
231
149
  addTools() {
232
150
  this.manageReactTools();
233
- this.container.appendChild(this.createPositionMenuWrapper());
234
151
  }
235
152
  manageReactTools() {
236
153
  let handleDownload;
@@ -21,6 +21,7 @@ export class FigureElementView extends ImageElementView {
21
21
  constructor() {
22
22
  super(...arguments);
23
23
  this.ignoreMutation = () => true;
24
+ this.resizeObserver = null;
24
25
  this.createElement = () => {
25
26
  super.createElement();
26
27
  this.addFigureElementButtons();
@@ -45,16 +46,65 @@ export class FigureElementView extends ImageElementView {
45
46
  this.view.dispatch(tr);
46
47
  };
47
48
  }
49
+ initialise() {
50
+ super.initialise();
51
+ requestAnimationFrame(() => this.updateButtonPosition());
52
+ this.setupResizeObserver();
53
+ }
54
+ setupResizeObserver() {
55
+ if (this.resizeObserver) {
56
+ this.resizeObserver.disconnect();
57
+ }
58
+ this.resizeObserver = new ResizeObserver(() => {
59
+ requestAnimationFrame(() => this.updateButtonPosition());
60
+ });
61
+ this.container.querySelectorAll('figure').forEach((figure) => {
62
+ this.resizeObserver?.observe(figure);
63
+ });
64
+ }
48
65
  addFigureElementButtons() {
49
66
  if (this.props.getCapabilities()?.editArticle) {
50
- const addFigureBtn = Object.assign(document.createElement('button'), {
67
+ this.addFigureBtn = Object.assign(document.createElement('button'), {
51
68
  className: 'add-figure-button',
52
69
  innerHTML: addFigureBtnIcon,
53
70
  title: 'Add figure',
54
71
  });
55
- addFigureBtn.addEventListener('click', () => this.addFigure());
56
- this.container.prepend(addFigureBtn);
72
+ this.addFigureBtn.addEventListener('click', () => this.addFigure());
73
+ this.container.prepend(this.addFigureBtn);
74
+ }
75
+ }
76
+ updateButtonPosition() {
77
+ if (!this.addFigureBtn) {
78
+ return;
79
+ }
80
+ const figures = this.container.querySelectorAll('figure');
81
+ const lastFigure = figures[figures.length - 1];
82
+ if (!lastFigure) {
83
+ return;
84
+ }
85
+ const lastFigureRect = lastFigure.getBoundingClientRect();
86
+ const containerRect = this.container.getBoundingClientRect();
87
+ const relativeTop = lastFigureRect.bottom - containerRect.top + 20;
88
+ this.addFigureBtn.style.top = `${relativeTop}px`;
89
+ }
90
+ update(node) {
91
+ const handledBySuper = super.update(node);
92
+ if (handledBySuper) {
93
+ this.setupResizeObserver();
94
+ requestAnimationFrame(() => this.updateButtonPosition());
95
+ }
96
+ return handledBySuper;
97
+ }
98
+ updateContents() {
99
+ super.updateContents();
100
+ requestAnimationFrame(() => this.updateButtonPosition());
101
+ }
102
+ destroy() {
103
+ if (this.resizeObserver) {
104
+ this.resizeObserver.disconnect();
105
+ this.resizeObserver = null;
57
106
  }
107
+ super.destroy();
58
108
  }
59
109
  }
60
110
  export default createNodeView(FigureElementView);
@@ -13,12 +13,86 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { ContextMenu } from '@manuscripts/style-guide';
17
+ import { schema } from '@manuscripts/transform';
18
+ import { imageDefaultIcon, imageLeftIcon, imageRightIcon } from '../icons';
16
19
  import BlockView from './block_view';
17
20
  import { createNodeView } from './creators';
21
+ import ReactSubView from './ReactSubView';
22
+ export var figurePositions;
23
+ (function (figurePositions) {
24
+ figurePositions["left"] = "half-left";
25
+ figurePositions["right"] = "half-right";
26
+ figurePositions["default"] = "";
27
+ })(figurePositions || (figurePositions = {}));
18
28
  export class ImageElementView extends BlockView {
19
29
  constructor() {
20
30
  super(...arguments);
21
31
  this.ignoreMutation = () => true;
32
+ this.createPositionMenuWrapper = () => {
33
+ const can = this.props.getCapabilities();
34
+ this.positionMenuWrapper = document.createElement('div');
35
+ this.positionMenuWrapper.classList.add('position-menu');
36
+ const positionMenuButton = document.createElement('div');
37
+ positionMenuButton.classList.add('position-menu-button');
38
+ const firstFigure = this.getFirstFigure();
39
+ this.figurePosition = firstFigure?.attrs.type || figurePositions.default;
40
+ let icon;
41
+ switch (this.figurePosition) {
42
+ case figurePositions.left:
43
+ icon = imageLeftIcon;
44
+ break;
45
+ case figurePositions.right:
46
+ icon = imageRightIcon;
47
+ break;
48
+ default:
49
+ icon = imageDefaultIcon;
50
+ break;
51
+ }
52
+ if (icon) {
53
+ positionMenuButton.innerHTML = icon;
54
+ }
55
+ if (can.editArticle) {
56
+ positionMenuButton.addEventListener('click', this.showPositionMenu);
57
+ }
58
+ this.positionMenuWrapper.appendChild(positionMenuButton);
59
+ return this.positionMenuWrapper;
60
+ };
61
+ this.showPositionMenu = () => {
62
+ this.props.popper.destroy();
63
+ const componentProps = {
64
+ actions: [
65
+ {
66
+ label: 'Left',
67
+ action: () => {
68
+ this.props.popper.destroy();
69
+ this.updateAllFiguresPosition(figurePositions.left);
70
+ },
71
+ icon: 'ImageLeft',
72
+ selected: this.figurePosition === figurePositions.left,
73
+ },
74
+ {
75
+ label: 'Default',
76
+ action: () => {
77
+ this.props.popper.destroy();
78
+ this.updateAllFiguresPosition(figurePositions.default);
79
+ },
80
+ icon: 'ImageDefault',
81
+ selected: !this.figurePosition,
82
+ },
83
+ {
84
+ label: 'Right',
85
+ action: () => {
86
+ this.props.popper.destroy();
87
+ this.updateAllFiguresPosition(figurePositions.right);
88
+ },
89
+ icon: 'ImageRight',
90
+ selected: this.figurePosition === figurePositions.right,
91
+ },
92
+ ],
93
+ };
94
+ this.props.popper.show(this.positionMenuWrapper, ReactSubView(this.props, ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'position-menu']), 'left', false);
95
+ };
22
96
  }
23
97
  createDOM() {
24
98
  super.createDOM();
@@ -33,6 +107,72 @@ export class ImageElementView extends BlockView {
33
107
  this.contentDOM.classList.add('figure-block');
34
108
  this.contentDOM.setAttribute('id', this.node.attrs.id);
35
109
  this.container.appendChild(this.contentDOM);
110
+ this.addTools();
111
+ }
112
+ updateContents() {
113
+ super.updateContents();
114
+ this.addTools();
115
+ }
116
+ addTools() {
117
+ this.addPositionMenu();
118
+ }
119
+ addPositionMenu() {
120
+ if (this.props.getCapabilities()?.editArticle) {
121
+ const existingMenu = this.container.querySelector('.position-menu');
122
+ if (existingMenu) {
123
+ existingMenu.remove();
124
+ }
125
+ this.container.prepend(this.createPositionMenuWrapper());
126
+ }
127
+ }
128
+ getFirstFigure() {
129
+ if (this.node.type === schema.nodes.image_element) {
130
+ const figureNode = this.node.firstChild;
131
+ if (figureNode && figureNode.type === schema.nodes.figure) {
132
+ return figureNode;
133
+ }
134
+ return null;
135
+ }
136
+ else {
137
+ let firstFigure = null;
138
+ this.node.forEach((node) => {
139
+ if (node.type === schema.nodes.figure && !firstFigure) {
140
+ firstFigure = node;
141
+ }
142
+ });
143
+ return firstFigure;
144
+ }
145
+ }
146
+ getAllFigures() {
147
+ const figures = [];
148
+ let pos = this.getPos() + 1;
149
+ if (this.node.type === schema.nodes.image_element) {
150
+ const figureNode = this.node.firstChild;
151
+ if (figureNode && figureNode.type === schema.nodes.figure) {
152
+ figures.push({ node: figureNode, pos });
153
+ }
154
+ }
155
+ else {
156
+ this.node.forEach((node) => {
157
+ if (node.type === schema.nodes.figure) {
158
+ figures.push({ node: node, pos });
159
+ }
160
+ pos += node.nodeSize;
161
+ });
162
+ }
163
+ return figures;
164
+ }
165
+ updateAllFiguresPosition(position) {
166
+ const figures = this.getAllFigures();
167
+ const { tr } = this.view.state;
168
+ figures.forEach(({ node, pos }) => {
169
+ tr.setNodeMarkup(pos, undefined, {
170
+ ...node.attrs,
171
+ type: position,
172
+ });
173
+ });
174
+ this.view.dispatch(tr);
175
+ this.figurePosition = position;
36
176
  }
37
177
  }
38
178
  export default createNodeView(ImageElementView);
@@ -8,3 +8,7 @@ export declare const scrollIcon: string;
8
8
  export declare const lockIcon: string;
9
9
  export declare const plusIcon: string;
10
10
  export declare const addFigureBtnIcon: string;
11
+ export declare const imageRightIcon: string;
12
+ export declare const imageLeftIcon: string;
13
+ export declare const imageDefaultIcon: string;
14
+ export declare const fileCorruptedIcon: string;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.2.20";
1
+ export declare const VERSION = "3.2.23";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
@@ -14,15 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { FigureView } from './figure';
17
- export declare enum figurePositions {
18
- left = "half-left",
19
- right = "half-right",
20
- default = ""
21
- }
22
17
  export declare class FigureEditableView extends FigureView {
23
18
  reactTools: HTMLDivElement;
24
- positionMenuWrapper: HTMLDivElement;
25
- figurePosition: string;
26
19
  initialise(): void;
27
20
  upload: (file: File) => Promise<void>;
28
21
  updateContents(): void;
@@ -32,8 +25,6 @@ export declare class FigureEditableView extends FigureView {
32
25
  private createUnsupportedFormat;
33
26
  protected createImg: (src: string) => HTMLImageElement;
34
27
  protected createPlaceholder: () => HTMLDivElement;
35
- createPositionMenuWrapper: () => HTMLDivElement;
36
- showPositionMenu: () => void;
37
28
  }
38
29
  declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch | undefined) => import("../types").NodeViewCreator<FigureEditableView>;
39
30
  export default _default;
@@ -13,12 +13,21 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { Node } from 'prosemirror-model';
16
17
  import { ImageElementView } from './image_element';
17
18
  export declare class FigureElementView extends ImageElementView {
18
19
  ignoreMutation: () => boolean;
20
+ private addFigureBtn;
21
+ private resizeObserver;
19
22
  createElement: () => void;
23
+ initialise(): void;
24
+ private setupResizeObserver;
20
25
  private addFigureElementButtons;
26
+ private updateButtonPosition;
27
+ update(node: Node): boolean;
28
+ updateContents(): void;
21
29
  private addFigure;
30
+ destroy(): void;
22
31
  }
23
32
  declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch | undefined) => import("../types").NodeViewCreator<FigureElementView>;
24
33
  export default _default;
@@ -16,11 +16,26 @@
16
16
  import { FigureElementNode } from '@manuscripts/transform';
17
17
  import { Trackable } from '../types';
18
18
  import BlockView from './block_view';
19
+ export declare enum figurePositions {
20
+ left = "half-left",
21
+ right = "half-right",
22
+ default = ""
23
+ }
19
24
  export declare class ImageElementView extends BlockView<Trackable<FigureElementNode>> {
20
25
  container: HTMLElement;
26
+ private positionMenuWrapper;
27
+ private figurePosition;
21
28
  ignoreMutation: () => boolean;
22
29
  createDOM(): void;
23
30
  createElement(): void;
31
+ updateContents(): void;
32
+ protected addTools(): void;
33
+ protected addPositionMenu(): void;
34
+ private createPositionMenuWrapper;
35
+ private getFirstFigure;
36
+ private getAllFigures;
37
+ private showPositionMenu;
38
+ private updateAllFiguresPosition;
24
39
  }
25
40
  declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch | undefined) => import("../types").NodeViewCreator<ImageElementView>;
26
41
  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.20",
4
+ "version": "3.2.23",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -40,7 +40,7 @@
40
40
  "@manuscripts/json-schema": "2.2.12",
41
41
  "@manuscripts/style-guide": "3.1.5",
42
42
  "@manuscripts/track-changes-plugin": "2.0.3",
43
- "@manuscripts/transform": "4.2.4",
43
+ "@manuscripts/transform": "4.2.6",
44
44
  "@popperjs/core": "2.11.8",
45
45
  "citeproc": "2.4.63",
46
46
  "codemirror": "5.65.19",
@@ -169,7 +169,6 @@
169
169
  width: 25px;
170
170
  z-index: 1;
171
171
  height: 25px;
172
- bottom: 220px;
173
172
  cursor: pointer;
174
173
  position: absolute;
175
174
  border-radius: 50%;
@@ -324,10 +323,10 @@
324
323
  text-align: center !important;
325
324
  }
326
325
 
327
- .ProseMirror .figure-block .position-menu {
326
+ .ProseMirror .block-figure_element .position-menu, .ProseMirror .block-image_element .position-menu {
328
327
  position: absolute;
329
- top: 4px;
330
- left: calc(100% - 96px);
328
+ top: 15px;
329
+ right: 35px;
331
330
  z-index: 2;
332
331
  cursor: pointer;
333
332
  padding-left: 100px;