@manuscripts/body-editor 3.17.0 → 3.17.1

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.
@@ -22,7 +22,7 @@ class PopperManager {
22
22
  constructor() {
23
23
  this.isActive = () => !!this.activePopper;
24
24
  }
25
- show(target, contents, placement = 'bottom', showArrow = true, modifiers = [], autoFocus = true, onDestroy = undefined) {
25
+ show(target, contents, placement = 'bottom', showArrow = true, modifiers = [], autoFocus = true, autoCloseOnOutsideClick = true, onDestroy = undefined) {
26
26
  this.destroy();
27
27
  this.triggerElement = target;
28
28
  this.destructionListener = onDestroy;
@@ -85,9 +85,10 @@ class PopperManager {
85
85
  this.destroy();
86
86
  }
87
87
  };
88
- if (contents.classList.contains('context-menu') ||
88
+ if ((contents.classList.contains('context-menu') ||
89
89
  contents.classList.contains('language') ||
90
- contents.classList.contains('section-category')) {
90
+ contents.classList.contains('section-category')) &&
91
+ autoCloseOnOutsideClick) {
91
92
  window.addEventListener('click', this.handleDocumentClick);
92
93
  }
93
94
  });
@@ -177,7 +177,7 @@ class HorizontalPositionMenu {
177
177
  p.props.popper.replaceContent(content);
178
178
  }
179
179
  else {
180
- p.props.popper.show(this.positionMenuWrapper, content, 'left', false, [], true, () => {
180
+ p.props.popper.show(this.positionMenuWrapper, content, 'left', false, [], true, true, () => {
181
181
  this.menuOpen = false;
182
182
  });
183
183
  this.menuOpen = true;
@@ -48,7 +48,7 @@ var __importStar = (this && this.__importStar) || (function () {
48
48
  };
49
49
  })();
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.saveBibliographyItem = exports.insertBibliographyItems = exports.deleteNode = exports.updateNodeAttrs = exports.findChildrenAttrsByType = exports.findChildrenByType = exports.findChildByType = exports.findChildByID = void 0;
51
+ exports.isSelectionInsideNode = exports.saveBibliographyItem = exports.insertBibliographyItems = exports.deleteNode = exports.updateNodeAttrs = exports.findChildrenAttrsByType = exports.findChildrenByType = exports.findChildByType = exports.findChildByID = void 0;
52
52
  const transform_1 = require("@manuscripts/transform");
53
53
  const prosemirror_state_1 = require("prosemirror-state");
54
54
  const utils = __importStar(require("prosemirror-utils"));
@@ -144,3 +144,12 @@ const saveBibliographyItem = (view, attrs) => {
144
144
  (0, exports.insertBibliographyItems)(view, attrs);
145
145
  };
146
146
  exports.saveBibliographyItem = saveBibliographyItem;
147
+ const isSelectionInsideNode = (view, node, pos) => {
148
+ const { from, to, empty } = view.state.selection;
149
+ const end = pos + node.nodeSize;
150
+ if (empty) {
151
+ return from > pos && from < end;
152
+ }
153
+ return from >= pos && to <= end;
154
+ };
155
+ exports.isSelectionInsideNode = isSelectionInsideNode;
@@ -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.17.0';
4
+ exports.VERSION = '3.17.1';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -23,6 +23,7 @@ const style_guide_1 = require("@manuscripts/style-guide");
23
23
  const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
24
24
  const transform_1 = require("@manuscripts/transform");
25
25
  const isEqual_1 = __importDefault(require("lodash/isEqual"));
26
+ const xor_1 = __importDefault(require("lodash/xor"));
26
27
  const prosemirror_state_1 = require("prosemirror-state");
27
28
  const prosemirror_utils_1 = require("prosemirror-utils");
28
29
  const DeleteFootnoteDialog_1 = require("../components/views/DeleteFootnoteDialog");
@@ -32,25 +33,31 @@ const base_node_view_1 = require("./base_node_view");
32
33
  const creators_1 = require("./creators");
33
34
  const ReactSubView_1 = __importDefault(require("./ReactSubView"));
34
35
  const navigation_utils_1 = require("../lib/navigation-utils");
36
+ const view_1 = require("../lib/view");
35
37
  class FootnoteView extends base_node_view_1.BaseNodeView {
36
38
  constructor() {
37
39
  super(...arguments);
40
+ this.isMenuShown = false;
41
+ this.previousActionsLabels = [];
38
42
  this.initialise = () => {
39
43
  this.dom = document.createElement('div');
40
44
  this.dom.classList.add('footnote');
41
45
  this.dom.tabIndex = 0;
42
46
  this.contentDOM = document.createElement('div');
43
47
  this.contentDOM.classList.add('footnote-text');
44
- this.dom.addEventListener('mousedown', (e) => this.handleClick(e, false));
45
- this.dom.addEventListener('keydown', (0, navigation_utils_1.handleEnterKey)((e) => this.handleClick(e, true)));
46
- this.updateContents();
47
- };
48
- this.handleClick = (event, fromKeyboard = false) => {
49
- const element = event.target;
50
- const item = element.closest('.footnote');
51
- if (item) {
52
- this.showContextMenu(item, fromKeyboard);
48
+ if (!this.view.editable) {
49
+ const actions = this.getContextMenuActions();
50
+ if (actions.length > 0) {
51
+ this.dom.addEventListener('mousedown', () => this.showContextMenu(actions, true));
52
+ }
53
53
  }
54
+ this.dom.addEventListener('keydown', (0, navigation_utils_1.handleEnterKey)(() => {
55
+ const actions = this.getContextMenuActions();
56
+ if (actions.length > 0) {
57
+ this.showContextMenu(actions, true);
58
+ }
59
+ }));
60
+ this.updateContents();
54
61
  };
55
62
  this.handleMarkerClick = (e) => {
56
63
  e?.preventDefault();
@@ -134,35 +141,53 @@ class FootnoteView extends base_node_view_1.BaseNodeView {
134
141
  this.dom.innerHTML = '';
135
142
  this.dom.appendChild(marker);
136
143
  this.contentDOM && this.dom.appendChild(this.contentDOM);
144
+ const selectionInsideNode = (0, view_1.isSelectionInsideNode)(this.view, this.node, this.getPos());
145
+ const actions = this.getContextMenuActions();
146
+ const hasActions = actions.length > 0;
147
+ const currentLabels = actions.map(({ label }) => label);
148
+ const changedLabels = (0, xor_1.default)(currentLabels, this.previousActionsLabels);
149
+ if (selectionInsideNode &&
150
+ hasActions &&
151
+ (!this.isMenuShown || changedLabels.length > 0)) {
152
+ this.showContextMenu(actions, false);
153
+ this.previousActionsLabels = actions.map(({ label }) => label);
154
+ }
155
+ else if ((!hasActions || !selectionInsideNode) && this.isMenuShown) {
156
+ this.props.popper.destroy();
157
+ this.isMenuShown = false;
158
+ this.previousActionsLabels = [];
159
+ }
137
160
  }
138
161
  getFootnoteState() {
139
162
  const id = this.node.attrs.id;
140
163
  const fn = (0, footnotes_1.getFootnotesElementState)(this.view.state, id);
141
164
  return { id, fn };
142
165
  }
143
- showContextMenu(element, autoFocus) {
166
+ showContextMenu(actions, autoFocus) {
167
+ this.isMenuShown = true;
144
168
  this.props.popper.destroy();
169
+ this.contextMenu = (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, { actions }, this.node, this.getPos, this.view, ['context-menu', 'footnote-context-menu']);
170
+ this.props.popper.show(this.dom, this.contextMenu, 'right-start', true, [], autoFocus, false);
171
+ }
172
+ getContextMenuActions() {
145
173
  const can = this.props.getCapabilities();
146
174
  const { id, fn } = this.getFootnoteState();
147
- const componentProps = {
148
- actions: [],
149
- };
175
+ const actions = [];
150
176
  if (!fn?.unusedFootnoteIDs.has(id)) {
151
- componentProps.actions.push({
152
- label: 'Go to footnote Refernce',
177
+ actions.push({
178
+ label: 'Go to footnote Reference',
153
179
  action: () => this.handleMarkerClick(),
154
180
  icon: 'Scroll',
155
181
  });
156
182
  }
157
183
  if (can.editArticle && !(0, track_changes_plugin_1.isDeleted)(this.node)) {
158
- componentProps.actions.push({
184
+ actions.push({
159
185
  label: 'Delete',
160
186
  action: () => this.handleDeleteClick(),
161
187
  icon: 'Delete',
162
188
  });
163
189
  }
164
- this.contextMenu = (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'footnote-context-menu']);
165
- this.props.popper.show(element, this.contextMenu, 'right-start', true, [], autoFocus);
190
+ return actions;
166
191
  }
167
192
  }
168
193
  exports.FootnoteView = FootnoteView;
@@ -27,27 +27,27 @@ const base_node_view_1 = require("./base_node_view");
27
27
  const creators_1 = require("./creators");
28
28
  const ReactSubView_1 = __importDefault(require("./ReactSubView"));
29
29
  const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
30
+ const navigation_utils_1 = require("../lib/navigation-utils");
31
+ const view_1 = require("../lib/view");
30
32
  class GeneralTableFootnoteView extends base_node_view_1.BaseNodeView {
31
33
  constructor() {
32
34
  super(...arguments);
35
+ this.isMenuShown = false;
33
36
  this.initialise = () => {
34
37
  this.dom = document.createElement('div');
35
38
  this.dom.classList.add('footnote', 'general-table-footnote');
36
- this.dom.addEventListener('mousedown', this.handleClick);
39
+ this.dom.tabIndex = 0;
40
+ this.dom.addEventListener('keydown', (0, navigation_utils_1.handleEnterKey)(() => {
41
+ const can = this.props.getCapabilities();
42
+ const canShowMenu = can.editArticle && !(0, track_changes_plugin_1.isDeleted)(this.node);
43
+ if (canShowMenu) {
44
+ this.showContextMenu(true);
45
+ }
46
+ }));
37
47
  this.contentDOM = document.createElement('div');
38
48
  this.contentDOM.classList.add('footnote-text');
39
49
  this.updateContents();
40
50
  };
41
- this.handleClick = (e) => {
42
- const element = e.target;
43
- const can = this.props.getCapabilities();
44
- if (can.editArticle) {
45
- const item = element.closest('.general-table-footnote');
46
- if (item) {
47
- this.showContextMenu(item);
48
- }
49
- }
50
- };
51
51
  this.handleDeleteClick = () => {
52
52
  const componentProps = {
53
53
  header: 'Delete table general note',
@@ -80,21 +80,31 @@ class GeneralTableFootnoteView extends base_node_view_1.BaseNodeView {
80
80
  super.updateContents();
81
81
  this.dom.innerHTML = '';
82
82
  this.contentDOM && this.dom.appendChild(this.contentDOM);
83
+ const selectionInsideNode = (0, view_1.isSelectionInsideNode)(this.view, this.node, this.getPos());
84
+ const can = this.props.getCapabilities();
85
+ const canShowMenu = can.editArticle && !(0, track_changes_plugin_1.isDeleted)(this.node);
86
+ if (canShowMenu && selectionInsideNode && !this.isMenuShown) {
87
+ this.showContextMenu(false);
88
+ }
89
+ else if ((!canShowMenu || !selectionInsideNode) && this.isMenuShown) {
90
+ this.props.popper.destroy();
91
+ this.isMenuShown = false;
92
+ }
83
93
  }
84
- showContextMenu(element) {
94
+ showContextMenu(autoFocus = false) {
95
+ this.isMenuShown = true;
85
96
  this.props.popper.destroy();
86
97
  const componentProps = {
87
- actions: [],
98
+ actions: [
99
+ {
100
+ label: 'Delete',
101
+ action: () => this.handleDeleteClick(),
102
+ icon: 'Delete',
103
+ },
104
+ ],
88
105
  };
89
- if (!(0, track_changes_plugin_1.isDeleted)(this.node)) {
90
- componentProps.actions.push({
91
- label: 'Delete',
92
- action: () => this.handleDeleteClick(),
93
- icon: 'Delete',
94
- });
95
- }
96
106
  this.contextMenu = (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'footnote-context-menu']);
97
- this.props.popper.show(element, this.contextMenu, 'right-start');
107
+ this.props.popper.show(this.dom, this.contextMenu, 'right-start', false, [], autoFocus, false);
98
108
  }
99
109
  }
100
110
  exports.GeneralTableFootnoteView = GeneralTableFootnoteView;
@@ -19,7 +19,7 @@ export class PopperManager {
19
19
  constructor() {
20
20
  this.isActive = () => !!this.activePopper;
21
21
  }
22
- show(target, contents, placement = 'bottom', showArrow = true, modifiers = [], autoFocus = true, onDestroy = undefined) {
22
+ show(target, contents, placement = 'bottom', showArrow = true, modifiers = [], autoFocus = true, autoCloseOnOutsideClick = true, onDestroy = undefined) {
23
23
  this.destroy();
24
24
  this.triggerElement = target;
25
25
  this.destructionListener = onDestroy;
@@ -82,9 +82,10 @@ export class PopperManager {
82
82
  this.destroy();
83
83
  }
84
84
  };
85
- if (contents.classList.contains('context-menu') ||
85
+ if ((contents.classList.contains('context-menu') ||
86
86
  contents.classList.contains('language') ||
87
- contents.classList.contains('section-category')) {
87
+ contents.classList.contains('section-category')) &&
88
+ autoCloseOnOutsideClick) {
88
89
  window.addEventListener('click', this.handleDocumentClick);
89
90
  }
90
91
  });
@@ -174,7 +174,7 @@ export class HorizontalPositionMenu {
174
174
  p.props.popper.replaceContent(content);
175
175
  }
176
176
  else {
177
- p.props.popper.show(this.positionMenuWrapper, content, 'left', false, [], true, () => {
177
+ p.props.popper.show(this.positionMenuWrapper, content, 'left', false, [], true, true, () => {
178
178
  this.menuOpen = false;
179
179
  });
180
180
  this.menuOpen = true;
@@ -100,3 +100,11 @@ export const saveBibliographyItem = (view, attrs) => {
100
100
  }
101
101
  insertBibliographyItems(view, attrs);
102
102
  };
103
+ export const isSelectionInsideNode = (view, node, pos) => {
104
+ const { from, to, empty } = view.state.selection;
105
+ const end = pos + node.nodeSize;
106
+ if (empty) {
107
+ return from > pos && from < end;
108
+ }
109
+ return from >= pos && to <= end;
110
+ };
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.17.0';
1
+ export const VERSION = '3.17.1';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -17,6 +17,7 @@ import { ContextMenu } from '@manuscripts/style-guide';
17
17
  import { isDeleted, isPendingInsert } from '@manuscripts/track-changes-plugin';
18
18
  import { schema } from '@manuscripts/transform';
19
19
  import isEqual from 'lodash/isEqual';
20
+ import xor from 'lodash/xor';
20
21
  import { NodeSelection } from 'prosemirror-state';
21
22
  import { findParentNodeOfTypeClosestToPos } from 'prosemirror-utils';
22
23
  import { DeleteFootnoteDialog, } from '../components/views/DeleteFootnoteDialog';
@@ -26,25 +27,31 @@ import { BaseNodeView } from './base_node_view';
26
27
  import { createNodeView } from './creators';
27
28
  import ReactSubView from './ReactSubView';
28
29
  import { handleEnterKey } from '../lib/navigation-utils';
30
+ import { isSelectionInsideNode } from '../lib/view';
29
31
  export class FootnoteView extends BaseNodeView {
30
32
  constructor() {
31
33
  super(...arguments);
34
+ this.isMenuShown = false;
35
+ this.previousActionsLabels = [];
32
36
  this.initialise = () => {
33
37
  this.dom = document.createElement('div');
34
38
  this.dom.classList.add('footnote');
35
39
  this.dom.tabIndex = 0;
36
40
  this.contentDOM = document.createElement('div');
37
41
  this.contentDOM.classList.add('footnote-text');
38
- this.dom.addEventListener('mousedown', (e) => this.handleClick(e, false));
39
- this.dom.addEventListener('keydown', handleEnterKey((e) => this.handleClick(e, true)));
40
- this.updateContents();
41
- };
42
- this.handleClick = (event, fromKeyboard = false) => {
43
- const element = event.target;
44
- const item = element.closest('.footnote');
45
- if (item) {
46
- this.showContextMenu(item, fromKeyboard);
42
+ if (!this.view.editable) {
43
+ const actions = this.getContextMenuActions();
44
+ if (actions.length > 0) {
45
+ this.dom.addEventListener('mousedown', () => this.showContextMenu(actions, true));
46
+ }
47
47
  }
48
+ this.dom.addEventListener('keydown', handleEnterKey(() => {
49
+ const actions = this.getContextMenuActions();
50
+ if (actions.length > 0) {
51
+ this.showContextMenu(actions, true);
52
+ }
53
+ }));
54
+ this.updateContents();
48
55
  };
49
56
  this.handleMarkerClick = (e) => {
50
57
  e?.preventDefault();
@@ -128,35 +135,53 @@ export class FootnoteView extends BaseNodeView {
128
135
  this.dom.innerHTML = '';
129
136
  this.dom.appendChild(marker);
130
137
  this.contentDOM && this.dom.appendChild(this.contentDOM);
138
+ const selectionInsideNode = isSelectionInsideNode(this.view, this.node, this.getPos());
139
+ const actions = this.getContextMenuActions();
140
+ const hasActions = actions.length > 0;
141
+ const currentLabels = actions.map(({ label }) => label);
142
+ const changedLabels = xor(currentLabels, this.previousActionsLabels);
143
+ if (selectionInsideNode &&
144
+ hasActions &&
145
+ (!this.isMenuShown || changedLabels.length > 0)) {
146
+ this.showContextMenu(actions, false);
147
+ this.previousActionsLabels = actions.map(({ label }) => label);
148
+ }
149
+ else if ((!hasActions || !selectionInsideNode) && this.isMenuShown) {
150
+ this.props.popper.destroy();
151
+ this.isMenuShown = false;
152
+ this.previousActionsLabels = [];
153
+ }
131
154
  }
132
155
  getFootnoteState() {
133
156
  const id = this.node.attrs.id;
134
157
  const fn = getFootnotesElementState(this.view.state, id);
135
158
  return { id, fn };
136
159
  }
137
- showContextMenu(element, autoFocus) {
160
+ showContextMenu(actions, autoFocus) {
161
+ this.isMenuShown = true;
138
162
  this.props.popper.destroy();
163
+ this.contextMenu = ReactSubView(this.props, ContextMenu, { actions }, this.node, this.getPos, this.view, ['context-menu', 'footnote-context-menu']);
164
+ this.props.popper.show(this.dom, this.contextMenu, 'right-start', true, [], autoFocus, false);
165
+ }
166
+ getContextMenuActions() {
139
167
  const can = this.props.getCapabilities();
140
168
  const { id, fn } = this.getFootnoteState();
141
- const componentProps = {
142
- actions: [],
143
- };
169
+ const actions = [];
144
170
  if (!fn?.unusedFootnoteIDs.has(id)) {
145
- componentProps.actions.push({
146
- label: 'Go to footnote Refernce',
171
+ actions.push({
172
+ label: 'Go to footnote Reference',
147
173
  action: () => this.handleMarkerClick(),
148
174
  icon: 'Scroll',
149
175
  });
150
176
  }
151
177
  if (can.editArticle && !isDeleted(this.node)) {
152
- componentProps.actions.push({
178
+ actions.push({
153
179
  label: 'Delete',
154
180
  action: () => this.handleDeleteClick(),
155
181
  icon: 'Delete',
156
182
  });
157
183
  }
158
- this.contextMenu = ReactSubView(this.props, ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'footnote-context-menu']);
159
- this.props.popper.show(element, this.contextMenu, 'right-start', true, [], autoFocus);
184
+ return actions;
160
185
  }
161
186
  }
162
187
  const getEffectiveChildCount = (node) => {
@@ -21,27 +21,27 @@ import { BaseNodeView } from './base_node_view';
21
21
  import { createNodeView } from './creators';
22
22
  import ReactSubView from './ReactSubView';
23
23
  import { isDeleted, isPendingInsert } from '@manuscripts/track-changes-plugin';
24
+ import { handleEnterKey } from '../lib/navigation-utils';
25
+ import { isSelectionInsideNode } from '../lib/view';
24
26
  export class GeneralTableFootnoteView extends BaseNodeView {
25
27
  constructor() {
26
28
  super(...arguments);
29
+ this.isMenuShown = false;
27
30
  this.initialise = () => {
28
31
  this.dom = document.createElement('div');
29
32
  this.dom.classList.add('footnote', 'general-table-footnote');
30
- this.dom.addEventListener('mousedown', this.handleClick);
33
+ this.dom.tabIndex = 0;
34
+ this.dom.addEventListener('keydown', handleEnterKey(() => {
35
+ const can = this.props.getCapabilities();
36
+ const canShowMenu = can.editArticle && !isDeleted(this.node);
37
+ if (canShowMenu) {
38
+ this.showContextMenu(true);
39
+ }
40
+ }));
31
41
  this.contentDOM = document.createElement('div');
32
42
  this.contentDOM.classList.add('footnote-text');
33
43
  this.updateContents();
34
44
  };
35
- this.handleClick = (e) => {
36
- const element = e.target;
37
- const can = this.props.getCapabilities();
38
- if (can.editArticle) {
39
- const item = element.closest('.general-table-footnote');
40
- if (item) {
41
- this.showContextMenu(item);
42
- }
43
- }
44
- };
45
45
  this.handleDeleteClick = () => {
46
46
  const componentProps = {
47
47
  header: 'Delete table general note',
@@ -74,21 +74,31 @@ export class GeneralTableFootnoteView extends BaseNodeView {
74
74
  super.updateContents();
75
75
  this.dom.innerHTML = '';
76
76
  this.contentDOM && this.dom.appendChild(this.contentDOM);
77
+ const selectionInsideNode = isSelectionInsideNode(this.view, this.node, this.getPos());
78
+ const can = this.props.getCapabilities();
79
+ const canShowMenu = can.editArticle && !isDeleted(this.node);
80
+ if (canShowMenu && selectionInsideNode && !this.isMenuShown) {
81
+ this.showContextMenu(false);
82
+ }
83
+ else if ((!canShowMenu || !selectionInsideNode) && this.isMenuShown) {
84
+ this.props.popper.destroy();
85
+ this.isMenuShown = false;
86
+ }
77
87
  }
78
- showContextMenu(element) {
88
+ showContextMenu(autoFocus = false) {
89
+ this.isMenuShown = true;
79
90
  this.props.popper.destroy();
80
91
  const componentProps = {
81
- actions: [],
92
+ actions: [
93
+ {
94
+ label: 'Delete',
95
+ action: () => this.handleDeleteClick(),
96
+ icon: 'Delete',
97
+ },
98
+ ],
82
99
  };
83
- if (!isDeleted(this.node)) {
84
- componentProps.actions.push({
85
- label: 'Delete',
86
- action: () => this.handleDeleteClick(),
87
- icon: 'Delete',
88
- });
89
- }
90
100
  this.contextMenu = ReactSubView(this.props, ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'footnote-context-menu']);
91
- this.props.popper.show(element, this.contextMenu, 'right-start');
101
+ this.props.popper.show(this.dom, this.contextMenu, 'right-start', false, [], autoFocus, false);
92
102
  }
93
103
  }
94
104
  export default createNodeView(GeneralTableFootnoteView);
@@ -20,7 +20,7 @@ export declare class PopperManager {
20
20
  private triggerElement?;
21
21
  private container?;
22
22
  private destructionListener?;
23
- show(target: Element, contents: HTMLElement, placement?: Placement, showArrow?: boolean, modifiers?: Array<Partial<StrictModifiers>>, autoFocus?: boolean, onDestroy?: ((i: Instance) => void) | undefined): void;
23
+ show(target: Element, contents: HTMLElement, placement?: Placement, showArrow?: boolean, modifiers?: Array<Partial<StrictModifiers>>, autoFocus?: boolean, autoCloseOnOutsideClick?: boolean, onDestroy?: ((i: Instance) => void) | undefined): void;
24
24
  destroy(): void;
25
25
  getContainer(): HTMLElement | undefined;
26
26
  update(): void;
@@ -13,9 +13,10 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { BibliographyItemAttrs, ManuscriptEditorView, ManuscriptNodeType } from '@manuscripts/transform';
16
+ import { BibliographyItemAttrs, ManuscriptEditorView, ManuscriptNode, ManuscriptNodeType } from '@manuscripts/transform';
17
17
  import { Attrs } from 'prosemirror-model';
18
18
  import * as utils from 'prosemirror-utils';
19
+ import { EditorView } from 'prosemirror-view';
19
20
  export declare const findChildByID: (view: ManuscriptEditorView, id: string) => utils.NodeWithPos | undefined;
20
21
  export declare const findChildByType: (view: ManuscriptEditorView, type: ManuscriptNodeType) => utils.NodeWithPos | undefined;
21
22
  export declare const findChildrenByType: (view: ManuscriptEditorView, type: ManuscriptNodeType) => utils.NodeWithPos[];
@@ -24,3 +25,4 @@ export declare const updateNodeAttrs: (view: ManuscriptEditorView, type: Manuscr
24
25
  export declare const deleteNode: (view: ManuscriptEditorView, id: string) => void;
25
26
  export declare const insertBibliographyItems: (view: ManuscriptEditorView, items: BibliographyItemAttrs[]) => void;
26
27
  export declare const saveBibliographyItem: (view: ManuscriptEditorView, attrs: BibliographyItemAttrs[]) => void;
28
+ export declare const isSelectionInsideNode: (view: EditorView, node: ManuscriptNode, pos: number) => boolean;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.17.0";
1
+ export declare const VERSION = "3.17.1";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
@@ -13,6 +13,7 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { ContextMenuProps } from '@manuscripts/style-guide';
16
17
  import { FootnoteNode } from '@manuscripts/transform';
17
18
  import { Transaction } from 'prosemirror-state';
18
19
  import { Trackable } from '../types';
@@ -20,14 +21,16 @@ import { BaseNodeView } from './base_node_view';
20
21
  export declare class FootnoteView extends BaseNodeView<Trackable<FootnoteNode>> {
21
22
  dialog: HTMLElement;
22
23
  contextMenu: HTMLDivElement;
24
+ isMenuShown: boolean;
25
+ previousActionsLabels: string[];
23
26
  initialise: () => void;
24
27
  updateContents(): void;
25
28
  getFootnoteState(): {
26
29
  id: string;
27
30
  fn: import("../plugins/footnotes").FootnotesElementState | undefined;
28
31
  };
29
- showContextMenu(element: HTMLElement, autoFocus: boolean): void;
30
- handleClick: (event: Event, fromKeyboard?: boolean) => void;
32
+ showContextMenu(actions: ContextMenuProps['actions'], autoFocus: boolean): void;
33
+ getContextMenuActions(): import("@manuscripts/style-guide").Actions[];
31
34
  handleMarkerClick: (e?: Event) => void;
32
35
  handleDeleteClick: (e?: Event) => void;
33
36
  handleDelete: () => void;
@@ -19,10 +19,10 @@ import { BaseNodeView } from './base_node_view';
19
19
  export declare class GeneralTableFootnoteView extends BaseNodeView<Trackable<GeneralTableFootnoteNode>> {
20
20
  dialog: HTMLElement;
21
21
  contextMenu: HTMLDivElement;
22
+ isMenuShown: boolean;
22
23
  initialise: () => void;
23
24
  updateContents(): void;
24
- handleClick: (e: Event) => void;
25
- showContextMenu(element: HTMLElement): void;
25
+ showContextMenu(autoFocus?: boolean): void;
26
26
  handleDeleteClick: () => void;
27
27
  handleDelete: () => void;
28
28
  }
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.17.0",
4
+ "version": "3.17.1",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -43,7 +43,7 @@
43
43
  "@iarna/word-count": "1.1.2",
44
44
  "@manuscripts/style-guide": "3.7.1",
45
45
  "@manuscripts/track-changes-plugin": "2.4.0",
46
- "@manuscripts/transform": "4.5.1",
46
+ "@manuscripts/transform": "4.5.2",
47
47
  "@popperjs/core": "2.11.8",
48
48
  "citeproc": "2.4.63",
49
49
  "codemirror": "5.65.19",
package/src/lib/popper.ts CHANGED
@@ -36,6 +36,7 @@ export class PopperManager {
36
36
  showArrow = true,
37
37
  modifiers: Array<Partial<StrictModifiers>> = [],
38
38
  autoFocus = true,
39
+ autoCloseOnOutsideClick = true,
39
40
  onDestroy: ((i: Instance) => void) | undefined = undefined
40
41
  ) {
41
42
  // destroy any existing popper first
@@ -114,9 +115,10 @@ export class PopperManager {
114
115
  // add EventListener for checking if click was done outside of editor
115
116
  // only if popper has class 'context-menu'
116
117
  if (
117
- contents.classList.contains('context-menu') ||
118
- contents.classList.contains('language') ||
119
- contents.classList.contains('section-category')
118
+ (contents.classList.contains('context-menu') ||
119
+ contents.classList.contains('language') ||
120
+ contents.classList.contains('section-category')) &&
121
+ autoCloseOnOutsideClick
120
122
  ) {
121
123
  window.addEventListener('click', this.handleDocumentClick)
122
124
  }
@@ -261,6 +261,7 @@ export class HorizontalPositionMenu {
261
261
  false,
262
262
  [],
263
263
  true,
264
+ true,
264
265
  () => {
265
266
  this.menuOpen = false
266
267
  }
package/src/lib/view.ts CHANGED
@@ -27,6 +27,7 @@ import * as utils from 'prosemirror-utils'
27
27
 
28
28
  import { isHidden } from './track-changes-utils'
29
29
  import { sanitizeAttrsChange } from '@manuscripts/track-changes-plugin'
30
+ import { EditorView } from 'prosemirror-view'
30
31
 
31
32
  const metaNodeTypes = [
32
33
  schema.nodes.bibliography_item,
@@ -163,3 +164,19 @@ export const saveBibliographyItem = (
163
164
 
164
165
  insertBibliographyItems(view, attrs)
165
166
  }
167
+
168
+ export const isSelectionInsideNode = (
169
+ view: EditorView,
170
+ node: ManuscriptNode,
171
+ pos: number
172
+ ) => {
173
+ const { from, to, empty } = view.state.selection
174
+ const end = pos + node.nodeSize
175
+
176
+ // Treat a cursor as "inside" only when it is strictly within the node range.
177
+ if (empty) {
178
+ return from > pos && from < end
179
+ }
180
+
181
+ return from >= pos && to <= end
182
+ }
package/src/versions.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  //THIS FILE WAS GENERATED BY versions.mjs
2
- export const VERSION = '3.17.0';
2
+ export const VERSION = '3.17.1';
3
3
  export const MATHJAX_VERSION = '3.2.2';
@@ -19,6 +19,7 @@ import { isDeleted, isPendingInsert } from '@manuscripts/track-changes-plugin'
19
19
  import { FootnoteNode, ManuscriptNode, schema } from '@manuscripts/transform'
20
20
 
21
21
  import isEqual from 'lodash/isEqual'
22
+ import xor from 'lodash/xor'
22
23
  import { NodeSelection, Transaction } from 'prosemirror-state'
23
24
  import { findParentNodeOfTypeClosestToPos } from 'prosemirror-utils'
24
25
 
@@ -33,10 +34,13 @@ import { BaseNodeView } from './base_node_view'
33
34
  import { createNodeView } from './creators'
34
35
  import ReactSubView from './ReactSubView'
35
36
  import { handleEnterKey } from '../lib/navigation-utils'
37
+ import { isSelectionInsideNode } from '../lib/view'
36
38
 
37
39
  export class FootnoteView extends BaseNodeView<Trackable<FootnoteNode>> {
38
40
  dialog: HTMLElement
39
41
  contextMenu: HTMLDivElement
42
+ isMenuShown: boolean = false
43
+ previousActionsLabels: string[] = []
40
44
 
41
45
  public initialise = () => {
42
46
  this.dom = document.createElement('div')
@@ -44,10 +48,24 @@ export class FootnoteView extends BaseNodeView<Trackable<FootnoteNode>> {
44
48
  this.dom.tabIndex = 0
45
49
  this.contentDOM = document.createElement('div')
46
50
  this.contentDOM.classList.add('footnote-text')
47
- this.dom.addEventListener('mousedown', (e) => this.handleClick(e, false))
51
+ if (!this.view.editable) {
52
+ // when the editor is in the viewing mode we can't depend on the selection to show context-menu so we use mousedown
53
+ // as in that case will not have the same listener from prosemirror-view to interfere with it
54
+ const actions = this.getContextMenuActions()
55
+ if (actions.length > 0) {
56
+ this.dom.addEventListener('mousedown', () =>
57
+ this.showContextMenu(actions, true)
58
+ )
59
+ }
60
+ }
48
61
  this.dom.addEventListener(
49
62
  'keydown',
50
- handleEnterKey((e) => this.handleClick(e, true))
63
+ handleEnterKey(() => {
64
+ const actions = this.getContextMenuActions()
65
+ if (actions.length > 0) {
66
+ this.showContextMenu(actions, true)
67
+ }
68
+ })
51
69
  )
52
70
  this.updateContents()
53
71
  }
@@ -73,6 +91,29 @@ export class FootnoteView extends BaseNodeView<Trackable<FootnoteNode>> {
73
91
  this.dom.innerHTML = ''
74
92
  this.dom.appendChild(marker)
75
93
  this.contentDOM && this.dom.appendChild(this.contentDOM)
94
+
95
+ const selectionInsideNode = isSelectionInsideNode(
96
+ this.view,
97
+ this.node,
98
+ this.getPos()
99
+ )
100
+
101
+ const actions = this.getContextMenuActions()
102
+ const hasActions = actions.length > 0
103
+ const currentLabels = actions.map(({ label }) => label)
104
+ const changedLabels = xor(currentLabels, this.previousActionsLabels)
105
+ if (
106
+ selectionInsideNode &&
107
+ hasActions &&
108
+ (!this.isMenuShown || changedLabels.length > 0)
109
+ ) {
110
+ this.showContextMenu(actions, false)
111
+ this.previousActionsLabels = actions.map(({ label }) => label)
112
+ } else if ((!hasActions || !selectionInsideNode) && this.isMenuShown) {
113
+ this.props.popper.destroy()
114
+ this.isMenuShown = false
115
+ this.previousActionsLabels = []
116
+ }
76
117
  }
77
118
 
78
119
  getFootnoteState() {
@@ -81,55 +122,50 @@ export class FootnoteView extends BaseNodeView<Trackable<FootnoteNode>> {
81
122
  return { id, fn }
82
123
  }
83
124
 
84
- showContextMenu(element: HTMLElement, autoFocus: boolean) {
125
+ showContextMenu(actions: ContextMenuProps['actions'], autoFocus: boolean) {
126
+ this.isMenuShown = true
85
127
  this.props.popper.destroy()
86
-
87
- const can = this.props.getCapabilities()
88
- const { id, fn } = this.getFootnoteState()
89
-
90
- const componentProps: ContextMenuProps = {
91
- actions: [],
92
- }
93
- if (!fn?.unusedFootnoteIDs.has(id)) {
94
- componentProps.actions.push({
95
- label: 'Go to footnote Refernce',
96
- action: () => this.handleMarkerClick(),
97
- icon: 'Scroll',
98
- })
99
- }
100
- if (can.editArticle && !isDeleted(this.node)) {
101
- componentProps.actions.push({
102
- label: 'Delete',
103
- action: () => this.handleDeleteClick(),
104
- icon: 'Delete',
105
- })
106
- }
107
-
108
128
  this.contextMenu = ReactSubView(
109
129
  this.props,
110
130
  ContextMenu,
111
- componentProps,
131
+ { actions },
112
132
  this.node,
113
133
  this.getPos,
114
134
  this.view,
115
135
  ['context-menu', 'footnote-context-menu']
116
136
  )
117
137
  this.props.popper.show(
118
- element,
138
+ this.dom,
119
139
  this.contextMenu,
120
140
  'right-start',
121
141
  true,
122
142
  [],
123
- autoFocus
143
+ autoFocus,
144
+ false
124
145
  )
125
146
  }
126
147
 
127
- handleClick = (event: Event, fromKeyboard = false) => {
128
- const element = event.target as HTMLElement
129
- const item = element.closest('.footnote')
130
- if (item) {
131
- this.showContextMenu(item as HTMLElement, fromKeyboard)
148
+ getContextMenuActions() {
149
+ const can = this.props.getCapabilities()
150
+ const { id, fn } = this.getFootnoteState()
151
+ const actions: ContextMenuProps['actions'] = []
152
+
153
+ if (!fn?.unusedFootnoteIDs.has(id)) {
154
+ actions.push({
155
+ label: 'Go to footnote Reference',
156
+ action: () => this.handleMarkerClick(),
157
+ icon: 'Scroll',
158
+ })
159
+ }
160
+ if (can.editArticle && !isDeleted(this.node)) {
161
+ actions.push({
162
+ label: 'Delete',
163
+ action: () => this.handleDeleteClick(),
164
+ icon: 'Delete',
165
+ })
132
166
  }
167
+
168
+ return actions
133
169
  }
134
170
 
135
171
  handleMarkerClick = (e?: Event) => {
@@ -30,17 +30,30 @@ import { BaseNodeView } from './base_node_view'
30
30
  import { createNodeView } from './creators'
31
31
  import ReactSubView from './ReactSubView'
32
32
  import { isDeleted, isPendingInsert } from '@manuscripts/track-changes-plugin'
33
+ import { handleEnterKey } from '../lib/navigation-utils'
34
+ import { isSelectionInsideNode } from '../lib/view'
33
35
 
34
36
  export class GeneralTableFootnoteView extends BaseNodeView<
35
37
  Trackable<GeneralTableFootnoteNode>
36
38
  > {
37
39
  dialog: HTMLElement
38
40
  contextMenu: HTMLDivElement
41
+ isMenuShown: boolean = false
39
42
 
40
43
  public initialise = () => {
41
44
  this.dom = document.createElement('div')
42
45
  this.dom.classList.add('footnote', 'general-table-footnote')
43
- this.dom.addEventListener('mousedown', this.handleClick)
46
+ this.dom.tabIndex = 0
47
+ this.dom.addEventListener(
48
+ 'keydown',
49
+ handleEnterKey(() => {
50
+ const can = this.props.getCapabilities()
51
+ const canShowMenu = can.editArticle && !isDeleted(this.node)
52
+ if (canShowMenu) {
53
+ this.showContextMenu(true)
54
+ }
55
+ })
56
+ )
44
57
  this.contentDOM = document.createElement('div')
45
58
  this.contentDOM.classList.add('footnote-text')
46
59
  this.updateContents()
@@ -50,32 +63,34 @@ export class GeneralTableFootnoteView extends BaseNodeView<
50
63
  super.updateContents()
51
64
  this.dom.innerHTML = ''
52
65
  this.contentDOM && this.dom.appendChild(this.contentDOM)
53
- }
54
66
 
55
- handleClick = (e: Event) => {
56
- const element = e.target as HTMLElement
67
+ const selectionInsideNode = isSelectionInsideNode(
68
+ this.view,
69
+ this.node,
70
+ this.getPos()
71
+ )
57
72
  const can = this.props.getCapabilities()
58
-
59
- if (can.editArticle) {
60
- const item = element.closest('.general-table-footnote')
61
- if (item) {
62
- this.showContextMenu(item as HTMLElement)
63
- }
73
+ const canShowMenu = can.editArticle && !isDeleted(this.node)
74
+ if (canShowMenu && selectionInsideNode && !this.isMenuShown) {
75
+ this.showContextMenu(false)
76
+ } else if ((!canShowMenu || !selectionInsideNode) && this.isMenuShown) {
77
+ this.props.popper.destroy()
78
+ this.isMenuShown = false
64
79
  }
65
80
  }
66
81
 
67
- showContextMenu(element: HTMLElement) {
82
+ showContextMenu(autoFocus = false) {
83
+ this.isMenuShown = true
68
84
  this.props.popper.destroy()
69
85
 
70
86
  const componentProps: ContextMenuProps = {
71
- actions: [],
72
- }
73
- if (!isDeleted(this.node)) {
74
- componentProps.actions.push({
75
- label: 'Delete',
76
- action: () => this.handleDeleteClick(),
77
- icon: 'Delete',
78
- })
87
+ actions: [
88
+ {
89
+ label: 'Delete',
90
+ action: () => this.handleDeleteClick(),
91
+ icon: 'Delete',
92
+ },
93
+ ],
79
94
  }
80
95
 
81
96
  this.contextMenu = ReactSubView(
@@ -87,7 +102,15 @@ export class GeneralTableFootnoteView extends BaseNodeView<
87
102
  this.view,
88
103
  ['context-menu', 'footnote-context-menu']
89
104
  )
90
- this.props.popper.show(element, this.contextMenu, 'right-start')
105
+ this.props.popper.show(
106
+ this.dom,
107
+ this.contextMenu,
108
+ 'right-start',
109
+ false,
110
+ [],
111
+ autoFocus,
112
+ false
113
+ )
91
114
  }
92
115
 
93
116
  handleDeleteClick = () => {