@manuscripts/body-editor 3.5.2 → 3.5.3

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.
@@ -8,6 +8,7 @@ const prosemirror_state_1 = require("prosemirror-state");
8
8
  const prosemirror_utils_1 = require("prosemirror-utils");
9
9
  const filtered_document_1 = require("../../lib/filtered-document");
10
10
  const track_changes_utils_1 = require("../../lib/track-changes-utils");
11
+ const utils_1 = require("../../lib/utils");
11
12
  const shouldSkipNode = (node) => {
12
13
  return (0, filtered_document_1.isMoved)(node) || (0, track_changes_utils_1.isDeleted)(node);
13
14
  };
@@ -31,20 +32,35 @@ const findSelectedOption = (options) => {
31
32
  }
32
33
  };
33
34
  exports.findSelectedOption = findSelectedOption;
34
- const getDeepestSubsection = (subsection) => {
35
+ const getDeepestSubsectionPosition = (subsection, position) => {
35
36
  while (subsection.lastChild && (0, transform_1.isSectionNodeType)(subsection.lastChild.type)) {
37
+ if ((0, track_changes_utils_1.isDeleted)(subsection.lastChild)) {
38
+ return (position +
39
+ jumpToPreviousValidNode(subsection.nodeSize - subsection.lastChild.nodeSize - 2, subsection) +
40
+ 1);
41
+ }
42
+ position += subsection.nodeSize - subsection.lastChild.nodeSize;
36
43
  subsection = subsection.lastChild;
37
44
  }
38
- return subsection;
45
+ return position + subsection.content.size;
46
+ };
47
+ const getInsertDataTracked = (node) => {
48
+ const dataTracked = node.attrs.dataTracked;
49
+ const change = dataTracked?.find((c) => c.operation === 'insert');
50
+ return change ? [change] : null;
39
51
  };
40
- const replaceDeepestSubsection = (parentNode, newSubsection) => {
41
- if (!parentNode.lastChild || !(0, transform_1.isSectionNodeType)(parentNode.lastChild.type)) {
42
- return newSubsection;
52
+ const jumpToPreviousValidNode = (beforeSection, doc) => {
53
+ const $beforeSection = doc.resolve(beforeSection);
54
+ let pos = beforeSection;
55
+ for (let i = $beforeSection.index() - 1; i >= 0; i--) {
56
+ pos = $beforeSection.posAtIndex(i);
57
+ const node = doc.resolve(pos + 1).node();
58
+ if (!((0, track_changes_utils_1.isDeleted)(node) && node.type === transform_1.schema.nodes.section) && i >= 0) {
59
+ pos = $beforeSection.posAtIndex(i + 1);
60
+ break;
61
+ }
43
62
  }
44
- const lastSubsectionIndex = parentNode.content.childCount - 1;
45
- const lastSubsection = parentNode.content.child(lastSubsectionIndex);
46
- const updatedSubsection = replaceDeepestSubsection(lastSubsection, newSubsection);
47
- return parentNode.copy(parentNode.content.replaceChild(lastSubsectionIndex, updatedSubsection));
63
+ return pos;
48
64
  };
49
65
  const demoteSectionToParagraph = (state, dispatch, view) => {
50
66
  const { doc, selection: { $from }, schema, tr, } = state;
@@ -55,40 +71,35 @@ const demoteSectionToParagraph = (state, dispatch, view) => {
55
71
  const afterSectionTitleOffset = $afterSectionTitle.parentOffset;
56
72
  const sectionDepth = $from.depth - 1;
57
73
  const section = $from.node(sectionDepth);
58
- const beforeSection = $from.before(sectionDepth);
59
- const afterSection = $from.after(sectionDepth);
74
+ let beforeSection = $from.before(sectionDepth);
75
+ beforeSection = jumpToPreviousValidNode(beforeSection, doc);
60
76
  const $beforeSection = doc.resolve(beforeSection);
61
77
  const previousNode = $beforeSection.nodeBefore;
62
78
  const paragraph = nodes.paragraph.create({
63
79
  id: (0, transform_1.generateNodeID)(nodes.paragraph),
80
+ dataTracked: getInsertDataTracked(section),
64
81
  }, sectionTitle.content);
65
82
  let anchor;
66
- let fragment;
67
83
  const sectionContent = section.content.cut(afterSectionTitleOffset);
68
84
  if (previousNode && (0, transform_1.isSectionNodeType)(previousNode.type)) {
69
85
  const hasSubsections = previousNode.lastChild && (0, transform_1.isSectionNodeType)(previousNode.lastChild.type);
70
86
  if (hasSubsections) {
71
- const deepestSubsection = getDeepestSubsection(previousNode.lastChild);
72
- const updatedDeepestSubsection = deepestSubsection.copy(deepestSubsection.content
73
- .append(prosemirror_model_1.Fragment.from(paragraph))
74
- .append(sectionContent));
75
- const updatedPreviousNode = replaceDeepestSubsection(previousNode, updatedDeepestSubsection);
76
- fragment = updatedPreviousNode.content;
87
+ beforeSection = getDeepestSubsectionPosition(previousNode, beforeSection - previousNode.nodeSize);
77
88
  }
78
89
  else {
79
- fragment = prosemirror_model_1.Fragment.from(previousNode.content)
80
- .append(prosemirror_model_1.Fragment.from(paragraph))
81
- .append(sectionContent);
90
+ beforeSection = beforeSection - 1;
82
91
  }
83
- tr.replaceWith(beforeSection - previousNode.nodeSize, afterSection, previousNode.copy(fragment));
84
- anchor = beforeSection;
92
+ anchor = beforeSection + 1;
85
93
  }
86
94
  else {
87
- tr.replaceWith(beforeSection, afterSection, prosemirror_model_1.Fragment.from(paragraph).append(sectionContent));
88
95
  anchor = beforeSection + 1;
89
96
  }
90
- tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, anchor, anchor + paragraph.content.size));
91
- dispatch((0, track_changes_plugin_1.skipTracking)(tr));
97
+ const content = (0, utils_1.filterBlockNodes)(prosemirror_model_1.Fragment.from(paragraph).append(sectionContent), (node) => !(0, track_changes_utils_1.isShadowDelete)(node));
98
+ tr.insert(beforeSection, content);
99
+ const afterSection = tr.mapping.map($from.after(sectionDepth));
100
+ tr.delete(afterSection - section.nodeSize, afterSection);
101
+ tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, anchor));
102
+ dispatch((0, track_changes_plugin_1.setAction)(tr, track_changes_plugin_1.TrackChangesAction.structuralChangeAction, 'convert-to-paragraph'));
92
103
  view && view.focus();
93
104
  };
94
105
  exports.demoteSectionToParagraph = demoteSectionToParagraph;
@@ -104,18 +115,15 @@ const promoteParagraphToSection = (state, dispatch, view) => {
104
115
  const parentSection = $from.node(sectionDepth);
105
116
  const startIndex = $from.index(sectionDepth);
106
117
  const endIndex = $from.indexAfter(sectionDepth);
107
- const beforeParentSection = $from.before(sectionDepth);
108
- const afterParentSection = $from.after(sectionDepth);
118
+ let afterParentSection = $from.after(sectionDepth);
109
119
  const items = [];
110
- let offset = 0;
111
120
  if (startIndex > 0) {
112
121
  const precedingSection = parentSection.cut(0, beforeParagraphOffset);
113
122
  items.push(precedingSection);
114
- offset += precedingSection.nodeSize;
115
123
  }
116
- const textContent = paragraph.textContent;
124
+ const textContent = (0, prosemirror_utils_1.findChildrenByType)(paragraph, schema.nodes.text).map(({ node }) => node);
117
125
  const sectionTitle = textContent
118
- ? nodes.section_title.create({}, schema.text(textContent))
126
+ ? nodes.section_title.create({}, prosemirror_model_1.Fragment.from(textContent))
119
127
  : nodes.section_title.create();
120
128
  let sectionContent = prosemirror_model_1.Fragment.from(sectionTitle);
121
129
  if (endIndex < parentSection.childCount) {
@@ -125,18 +133,27 @@ const promoteParagraphToSection = (state, dispatch, view) => {
125
133
  sectionContent = sectionContent.append(prosemirror_model_1.Fragment.from(paragraph.copy()));
126
134
  }
127
135
  if (parentSection.type.name === 'body') {
128
- const newSection = nodes.section.create({}, sectionContent);
129
- items[0] = nodes.body.create({}, items[0]
130
- ? items[0].content.append(prosemirror_model_1.Fragment.from(newSection))
131
- : prosemirror_model_1.Fragment.from(newSection));
136
+ sectionContent = prosemirror_model_1.Fragment.from(sectionTitle);
137
+ afterParentSection = beforeParagraph + paragraph.nodeSize;
138
+ parentSection.forEach((node, offset, index) => {
139
+ if (node.type !== nodes.section &&
140
+ index > tr.doc.resolve(beforeParagraph).index()) {
141
+ const pos = $from.start($from.depth - 1);
142
+ afterParentSection = pos + offset + node.nodeSize;
143
+ sectionContent = sectionContent.append(prosemirror_model_1.Fragment.from(node));
144
+ }
145
+ });
146
+ items[0] = nodes.section.create({ dataTracked: getInsertDataTracked(paragraph) }, sectionContent);
132
147
  }
133
148
  else {
134
- items.push(parentSection.copy(sectionContent));
149
+ items.push(parentSection.type.create({ dataTracked: getInsertDataTracked(paragraph) }, sectionContent));
135
150
  }
136
- tr.replaceWith(beforeParentSection, afterParentSection, items);
137
- const anchor = beforeParentSection + offset + 2;
138
- tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, anchor, anchor + sectionTitle.content.size));
139
- dispatch((0, track_changes_plugin_1.skipTracking)(tr));
151
+ const content = (0, utils_1.filterBlockNodes)(prosemirror_model_1.Fragment.from(items[items.length - 1]), (node) => !(0, track_changes_utils_1.isShadowDelete)(node));
152
+ tr.insert(afterParentSection, content);
153
+ tr.delete(beforeParagraph, afterParentSection - 1);
154
+ const anchor = tr.mapping.map(afterParentSection) - content.size + 2;
155
+ tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, anchor));
156
+ dispatch((0, track_changes_plugin_1.setAction)(tr, track_changes_plugin_1.TrackChangesAction.structuralChangeAction, 'convert-to-section'));
140
157
  view && view.focus();
141
158
  };
142
159
  exports.promoteParagraphToSection = promoteParagraphToSection;
@@ -8,6 +8,7 @@ const transform_1 = require("@manuscripts/transform");
8
8
  const prosemirror_utils_1 = require("prosemirror-utils");
9
9
  const react_1 = __importDefault(require("react"));
10
10
  const hierarchy_1 = require("../../../lib/hierarchy");
11
+ const track_changes_utils_1 = require("../../../lib/track-changes-utils");
11
12
  const utils_1 = require("../../../lib/utils");
12
13
  const helpers_1 = require("../helpers");
13
14
  const OptionComponent_1 = require("./OptionComponent");
@@ -30,13 +31,16 @@ const buildOptions = (state) => {
30
31
  const beforeSection = $from.before(sectionDepth);
31
32
  const $beforeSection = doc.resolve(beforeSection);
32
33
  const sectionOffset = $beforeSection.parentOffset;
34
+ const section = $from.node(sectionDepth);
35
+ !(0, track_changes_utils_1.isDeleted)(section) &&
36
+ options.push({
37
+ nodeType: nodes.paragraph,
38
+ label: 'Paragraph',
39
+ action: helpers_1.demoteSectionToParagraph,
40
+ isDisabled: (sectionDepth <= 1 && sectionOffset <= 1) || (0, track_changes_utils_1.isDeleted)(section),
41
+ isSelected: false,
42
+ });
33
43
  options.push({
34
- nodeType: nodes.paragraph,
35
- label: 'Paragraph',
36
- action: helpers_1.demoteSectionToParagraph,
37
- isDisabled: sectionDepth <= 1 && sectionOffset <= 1,
38
- isSelected: false,
39
- }, {
40
44
  nodeType: nodes.section,
41
45
  label: 'Heading',
42
46
  isDisabled: true,
@@ -45,18 +49,21 @@ const buildOptions = (state) => {
45
49
  return options;
46
50
  }
47
51
  case parentElementType.schema.nodes.paragraph: {
52
+ const paragraph = $from.node($from.depth);
48
53
  options.push({
49
54
  nodeType: nodes.paragraph,
50
55
  label: 'Paragraph',
51
56
  isDisabled: true,
52
57
  isSelected: true,
53
- }, {
54
- nodeType: nodes.section,
55
- label: 'Heading',
56
- action: helpers_1.promoteParagraphToSection,
57
- isDisabled: false,
58
- isSelected: false,
59
58
  });
59
+ !(0, track_changes_utils_1.isDeleted)(paragraph) &&
60
+ options.push({
61
+ nodeType: nodes.section,
62
+ label: 'Heading',
63
+ action: helpers_1.promoteParagraphToSection,
64
+ isDisabled: (0, track_changes_utils_1.isDeleted)(paragraph),
65
+ isSelected: false,
66
+ });
60
67
  return options;
61
68
  }
62
69
  default: {
@@ -17,6 +17,7 @@
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.addTrackChangesClassNames = exports.addTrackChangesAttributes = exports.getAttrsTrackingButton = void 0;
19
19
  exports.isDeleted = isDeleted;
20
+ exports.isShadowDelete = isShadowDelete;
20
21
  exports.isPendingInsert = isPendingInsert;
21
22
  exports.isPending = isPending;
22
23
  exports.isPendingSetAttrs = isPendingSetAttrs;
@@ -38,6 +39,13 @@ function isDeleted(node) {
38
39
  }
39
40
  return false;
40
41
  }
42
+ function isShadowDelete(node) {
43
+ if (node.attrs.dataTracked) {
44
+ const changes = node.attrs.dataTracked;
45
+ return changes.some(({ operation, moveNodeId }) => operation === 'delete' && moveNodeId);
46
+ }
47
+ return false;
48
+ }
41
49
  function isPendingInsert(node) {
42
50
  if (node.attrs.dataTracked) {
43
51
  const changes = node.attrs.dataTracked;
@@ -15,9 +15,10 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.getLastTitleNode = exports.getInsertPos = exports.createToggleButton = exports.isEditAllowed = exports.isBodyLocked = exports.cleanItemValues = exports.shouldRenderField = exports.hasParent = exports.isNotNull = exports.createHeader = exports.isSelectionInBody = exports.isSelectionInNode = exports.isChildOfNodeTypes = exports.findParentElement = exports.findParentSection = exports.findParentNodeWithIdValue = exports.findParentNodeWithId = exports.getChildOfType = exports.getMatchingDescendant = exports.getMatchingChild = void 0;
18
+ exports.getLastTitleNode = exports.filterBlockNodes = exports.getInsertPos = exports.createToggleButton = exports.isEditAllowed = exports.isBodyLocked = exports.cleanItemValues = exports.shouldRenderField = exports.hasParent = exports.isNotNull = exports.createHeader = exports.isSelectionInBody = exports.isSelectionInNode = exports.isChildOfNodeTypes = exports.findParentElement = exports.findParentSection = exports.findParentNodeWithIdValue = exports.findParentNodeWithId = exports.getChildOfType = exports.getMatchingDescendant = exports.getMatchingChild = void 0;
19
19
  exports.iterateChildren = iterateChildren;
20
20
  const transform_1 = require("@manuscripts/transform");
21
+ const prosemirror_model_1 = require("prosemirror-model");
21
22
  const prosemirror_utils_1 = require("prosemirror-utils");
22
23
  const config_1 = require("../components/references/ReferenceForm/config");
23
24
  const icons_1 = require("../icons");
@@ -170,6 +171,23 @@ const getInsertPos = (type, parent, pos) => {
170
171
  return insertPos;
171
172
  };
172
173
  exports.getInsertPos = getInsertPos;
174
+ const filterBlockNodes = (fragment, predicate) => {
175
+ const updatedNodes = [];
176
+ fragment.forEach((child) => {
177
+ if (!child.isBlock) {
178
+ updatedNodes.push(child);
179
+ return;
180
+ }
181
+ const newContent = child.content.size
182
+ ? (0, exports.filterBlockNodes)(child.content, predicate)
183
+ : child.content;
184
+ if (predicate(child)) {
185
+ updatedNodes.push(child.type.create(child.attrs, newContent, child.marks));
186
+ }
187
+ });
188
+ return prosemirror_model_1.Fragment.fromArray(updatedNodes);
189
+ };
190
+ exports.filterBlockNodes = filterBlockNodes;
173
191
  const getLastTitleNode = (state) => {
174
192
  const altTitleNode = (0, prosemirror_utils_1.findChildrenByType)(state.doc, state.schema.nodes.alt_titles)[0];
175
193
  if (altTitleNode) {
@@ -59,9 +59,6 @@ const getEffectiveSelection = ($pos) => {
59
59
  to: $pos.after(depth),
60
60
  };
61
61
  }
62
- else {
63
- break;
64
- }
65
62
  }
66
63
  if (current) {
67
64
  return current;
@@ -115,12 +112,18 @@ const buildTextDecoration = (doc, selection) => {
115
112
  };
116
113
  };
117
114
  const buildGroupOfChangesDecoration = (doc, changes) => {
118
- const from = changes[0].from, to = changes[changes.length - 1].to;
119
- const decoration = prosemirror_view_1.Decoration.inline(from, to, {
120
- class: 'selected-suggestion',
121
- });
115
+ const decorations = [];
116
+ if (changes[0].dataTracked.operation === track_changes_plugin_1.CHANGE_OPERATION.structure) {
117
+ changes.map((c) => decorations.push(prosemirror_view_1.Decoration.node(c.from, c.to, { class: 'selected-suggestion' })));
118
+ }
119
+ else {
120
+ const from = changes[0].from, to = changes[changes.length - 1].to;
121
+ decorations.push(prosemirror_view_1.Decoration.inline(from, to, {
122
+ class: 'selected-suggestion',
123
+ }));
124
+ }
122
125
  return {
123
- decorations: prosemirror_view_1.DecorationSet.create(doc, [decoration]),
126
+ decorations: prosemirror_view_1.DecorationSet.create(doc, decorations),
124
127
  suggestion: changes[0].dataTracked,
125
128
  };
126
129
  };
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSelectionChangeGroup = exports.NodesSelection = void 0;
3
+ exports.getSelectionChangeGroup = exports.NodesSelection = exports.InlineNodesSelection = void 0;
4
4
  const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
5
5
  const prosemirror_state_1 = require("prosemirror-state");
6
6
  const commands_1 = require("./commands");
7
- class NodesSelection extends prosemirror_state_1.Selection {
7
+ class InlineNodesSelection extends prosemirror_state_1.Selection {
8
8
  constructor($from, $to) {
9
9
  super($to, $to);
10
10
  this.$startNode = $from;
@@ -28,18 +28,37 @@ class NodesSelection extends prosemirror_state_1.Selection {
28
28
  };
29
29
  }
30
30
  }
31
+ exports.InlineNodesSelection = InlineNodesSelection;
32
+ class NodesSelection extends prosemirror_state_1.NodeSelection {
33
+ constructor($from, $to) {
34
+ super($from);
35
+ this.$startNode = $from;
36
+ this.$endNode = $to;
37
+ }
38
+ toJSON() {
39
+ return {
40
+ type: 'nodes',
41
+ startNode: this.$startNode,
42
+ endNode: this.$endNode,
43
+ };
44
+ }
45
+ }
31
46
  exports.NodesSelection = NodesSelection;
32
47
  const getSelectionChangeGroup = (state) => {
33
48
  const selection = state.selection;
34
49
  const $pos = (0, commands_1.isTextSelection)(selection)
35
50
  ? selection.$cursor
36
- : selection instanceof NodesSelection && selection.$from;
51
+ : (selection instanceof NodesSelection ||
52
+ selection instanceof InlineNodesSelection) &&
53
+ selection.$from;
37
54
  if ($pos) {
38
55
  return track_changes_plugin_1.trackChangesPluginKey
39
56
  .getState(state)
40
- ?.changeSet.groupChanges.find((changes) => changes.length > 1 &&
41
- $pos.pos >= changes[0].from &&
42
- $pos.pos <= changes[changes.length - 1].to);
57
+ ?.changeSet.groupChanges.find((c) => isPositionAtRange(c, $pos.pos));
43
58
  }
44
59
  };
45
60
  exports.getSelectionChangeGroup = getSelectionChangeGroup;
61
+ const isPositionAtRange = (changes, pos) => (changes.length > 1 ||
62
+ changes[0].dataTracked.operation === track_changes_plugin_1.CHANGE_OPERATION.structure) &&
63
+ pos >= changes[0].from &&
64
+ pos <= changes[changes.length - 1].to;
@@ -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.5.2';
4
+ exports.VERSION = '3.5.3';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -1,10 +1,11 @@
1
- import { setAction, skipTracking, TrackChangesAction, } from '@manuscripts/track-changes-plugin';
1
+ import { setAction, TrackChangesAction, } from '@manuscripts/track-changes-plugin';
2
2
  import { generateNodeID, isSectionNodeType, nodeNames, schema, } from '@manuscripts/transform';
3
3
  import { Fragment } from 'prosemirror-model';
4
4
  import { TextSelection } from 'prosemirror-state';
5
- import { hasParentNodeOfType } from 'prosemirror-utils';
5
+ import { findChildrenByType, hasParentNodeOfType } from 'prosemirror-utils';
6
6
  import { isMoved } from '../../lib/filtered-document';
7
- import { isDeleted } from '../../lib/track-changes-utils';
7
+ import { isDeleted, isShadowDelete } from '../../lib/track-changes-utils';
8
+ import { filterBlockNodes } from '../../lib/utils';
8
9
  export const shouldSkipNode = (node) => {
9
10
  return isMoved(node) || isDeleted(node);
10
11
  };
@@ -24,20 +25,35 @@ export const findSelectedOption = (options) => {
24
25
  }
25
26
  }
26
27
  };
27
- const getDeepestSubsection = (subsection) => {
28
+ const getDeepestSubsectionPosition = (subsection, position) => {
28
29
  while (subsection.lastChild && isSectionNodeType(subsection.lastChild.type)) {
30
+ if (isDeleted(subsection.lastChild)) {
31
+ return (position +
32
+ jumpToPreviousValidNode(subsection.nodeSize - subsection.lastChild.nodeSize - 2, subsection) +
33
+ 1);
34
+ }
35
+ position += subsection.nodeSize - subsection.lastChild.nodeSize;
29
36
  subsection = subsection.lastChild;
30
37
  }
31
- return subsection;
38
+ return position + subsection.content.size;
39
+ };
40
+ const getInsertDataTracked = (node) => {
41
+ const dataTracked = node.attrs.dataTracked;
42
+ const change = dataTracked?.find((c) => c.operation === 'insert');
43
+ return change ? [change] : null;
32
44
  };
33
- const replaceDeepestSubsection = (parentNode, newSubsection) => {
34
- if (!parentNode.lastChild || !isSectionNodeType(parentNode.lastChild.type)) {
35
- return newSubsection;
45
+ const jumpToPreviousValidNode = (beforeSection, doc) => {
46
+ const $beforeSection = doc.resolve(beforeSection);
47
+ let pos = beforeSection;
48
+ for (let i = $beforeSection.index() - 1; i >= 0; i--) {
49
+ pos = $beforeSection.posAtIndex(i);
50
+ const node = doc.resolve(pos + 1).node();
51
+ if (!(isDeleted(node) && node.type === schema.nodes.section) && i >= 0) {
52
+ pos = $beforeSection.posAtIndex(i + 1);
53
+ break;
54
+ }
36
55
  }
37
- const lastSubsectionIndex = parentNode.content.childCount - 1;
38
- const lastSubsection = parentNode.content.child(lastSubsectionIndex);
39
- const updatedSubsection = replaceDeepestSubsection(lastSubsection, newSubsection);
40
- return parentNode.copy(parentNode.content.replaceChild(lastSubsectionIndex, updatedSubsection));
56
+ return pos;
41
57
  };
42
58
  export const demoteSectionToParagraph = (state, dispatch, view) => {
43
59
  const { doc, selection: { $from }, schema, tr, } = state;
@@ -48,40 +64,35 @@ export const demoteSectionToParagraph = (state, dispatch, view) => {
48
64
  const afterSectionTitleOffset = $afterSectionTitle.parentOffset;
49
65
  const sectionDepth = $from.depth - 1;
50
66
  const section = $from.node(sectionDepth);
51
- const beforeSection = $from.before(sectionDepth);
52
- const afterSection = $from.after(sectionDepth);
67
+ let beforeSection = $from.before(sectionDepth);
68
+ beforeSection = jumpToPreviousValidNode(beforeSection, doc);
53
69
  const $beforeSection = doc.resolve(beforeSection);
54
70
  const previousNode = $beforeSection.nodeBefore;
55
71
  const paragraph = nodes.paragraph.create({
56
72
  id: generateNodeID(nodes.paragraph),
73
+ dataTracked: getInsertDataTracked(section),
57
74
  }, sectionTitle.content);
58
75
  let anchor;
59
- let fragment;
60
76
  const sectionContent = section.content.cut(afterSectionTitleOffset);
61
77
  if (previousNode && isSectionNodeType(previousNode.type)) {
62
78
  const hasSubsections = previousNode.lastChild && isSectionNodeType(previousNode.lastChild.type);
63
79
  if (hasSubsections) {
64
- const deepestSubsection = getDeepestSubsection(previousNode.lastChild);
65
- const updatedDeepestSubsection = deepestSubsection.copy(deepestSubsection.content
66
- .append(Fragment.from(paragraph))
67
- .append(sectionContent));
68
- const updatedPreviousNode = replaceDeepestSubsection(previousNode, updatedDeepestSubsection);
69
- fragment = updatedPreviousNode.content;
80
+ beforeSection = getDeepestSubsectionPosition(previousNode, beforeSection - previousNode.nodeSize);
70
81
  }
71
82
  else {
72
- fragment = Fragment.from(previousNode.content)
73
- .append(Fragment.from(paragraph))
74
- .append(sectionContent);
83
+ beforeSection = beforeSection - 1;
75
84
  }
76
- tr.replaceWith(beforeSection - previousNode.nodeSize, afterSection, previousNode.copy(fragment));
77
- anchor = beforeSection;
85
+ anchor = beforeSection + 1;
78
86
  }
79
87
  else {
80
- tr.replaceWith(beforeSection, afterSection, Fragment.from(paragraph).append(sectionContent));
81
88
  anchor = beforeSection + 1;
82
89
  }
83
- tr.setSelection(TextSelection.create(tr.doc, anchor, anchor + paragraph.content.size));
84
- dispatch(skipTracking(tr));
90
+ const content = filterBlockNodes(Fragment.from(paragraph).append(sectionContent), (node) => !isShadowDelete(node));
91
+ tr.insert(beforeSection, content);
92
+ const afterSection = tr.mapping.map($from.after(sectionDepth));
93
+ tr.delete(afterSection - section.nodeSize, afterSection);
94
+ tr.setSelection(TextSelection.create(tr.doc, anchor));
95
+ dispatch(setAction(tr, TrackChangesAction.structuralChangeAction, 'convert-to-paragraph'));
85
96
  view && view.focus();
86
97
  };
87
98
  export const promoteParagraphToSection = (state, dispatch, view) => {
@@ -96,18 +107,15 @@ export const promoteParagraphToSection = (state, dispatch, view) => {
96
107
  const parentSection = $from.node(sectionDepth);
97
108
  const startIndex = $from.index(sectionDepth);
98
109
  const endIndex = $from.indexAfter(sectionDepth);
99
- const beforeParentSection = $from.before(sectionDepth);
100
- const afterParentSection = $from.after(sectionDepth);
110
+ let afterParentSection = $from.after(sectionDepth);
101
111
  const items = [];
102
- let offset = 0;
103
112
  if (startIndex > 0) {
104
113
  const precedingSection = parentSection.cut(0, beforeParagraphOffset);
105
114
  items.push(precedingSection);
106
- offset += precedingSection.nodeSize;
107
115
  }
108
- const textContent = paragraph.textContent;
116
+ const textContent = findChildrenByType(paragraph, schema.nodes.text).map(({ node }) => node);
109
117
  const sectionTitle = textContent
110
- ? nodes.section_title.create({}, schema.text(textContent))
118
+ ? nodes.section_title.create({}, Fragment.from(textContent))
111
119
  : nodes.section_title.create();
112
120
  let sectionContent = Fragment.from(sectionTitle);
113
121
  if (endIndex < parentSection.childCount) {
@@ -117,18 +125,27 @@ export const promoteParagraphToSection = (state, dispatch, view) => {
117
125
  sectionContent = sectionContent.append(Fragment.from(paragraph.copy()));
118
126
  }
119
127
  if (parentSection.type.name === 'body') {
120
- const newSection = nodes.section.create({}, sectionContent);
121
- items[0] = nodes.body.create({}, items[0]
122
- ? items[0].content.append(Fragment.from(newSection))
123
- : Fragment.from(newSection));
128
+ sectionContent = Fragment.from(sectionTitle);
129
+ afterParentSection = beforeParagraph + paragraph.nodeSize;
130
+ parentSection.forEach((node, offset, index) => {
131
+ if (node.type !== nodes.section &&
132
+ index > tr.doc.resolve(beforeParagraph).index()) {
133
+ const pos = $from.start($from.depth - 1);
134
+ afterParentSection = pos + offset + node.nodeSize;
135
+ sectionContent = sectionContent.append(Fragment.from(node));
136
+ }
137
+ });
138
+ items[0] = nodes.section.create({ dataTracked: getInsertDataTracked(paragraph) }, sectionContent);
124
139
  }
125
140
  else {
126
- items.push(parentSection.copy(sectionContent));
141
+ items.push(parentSection.type.create({ dataTracked: getInsertDataTracked(paragraph) }, sectionContent));
127
142
  }
128
- tr.replaceWith(beforeParentSection, afterParentSection, items);
129
- const anchor = beforeParentSection + offset + 2;
130
- tr.setSelection(TextSelection.create(tr.doc, anchor, anchor + sectionTitle.content.size));
131
- dispatch(skipTracking(tr));
143
+ const content = filterBlockNodes(Fragment.from(items[items.length - 1]), (node) => !isShadowDelete(node));
144
+ tr.insert(afterParentSection, content);
145
+ tr.delete(beforeParagraph, afterParentSection - 1);
146
+ const anchor = tr.mapping.map(afterParentSection) - content.size + 2;
147
+ tr.setSelection(TextSelection.create(tr.doc, anchor));
148
+ dispatch(setAction(tr, TrackChangesAction.structuralChangeAction, 'convert-to-section'));
132
149
  view && view.focus();
133
150
  };
134
151
  export const isIndentationAllowed = (action) => (state) => {
@@ -2,6 +2,7 @@ import { schema, } from '@manuscripts/transform';
2
2
  import { hasParentNodeOfType } from 'prosemirror-utils';
3
3
  import React from 'react';
4
4
  import { findClosestParentElement } from '../../../lib/hierarchy';
5
+ import { isDeleted } from '../../../lib/track-changes-utils';
5
6
  import { isEditAllowed } from '../../../lib/utils';
6
7
  import { demoteSectionToParagraph, findSelectedOption, optionName, promoteParagraphToSection, titleCase, } from '../helpers';
7
8
  import { OptionComponent } from './OptionComponent';
@@ -24,13 +25,16 @@ const buildOptions = (state) => {
24
25
  const beforeSection = $from.before(sectionDepth);
25
26
  const $beforeSection = doc.resolve(beforeSection);
26
27
  const sectionOffset = $beforeSection.parentOffset;
28
+ const section = $from.node(sectionDepth);
29
+ !isDeleted(section) &&
30
+ options.push({
31
+ nodeType: nodes.paragraph,
32
+ label: 'Paragraph',
33
+ action: demoteSectionToParagraph,
34
+ isDisabled: (sectionDepth <= 1 && sectionOffset <= 1) || isDeleted(section),
35
+ isSelected: false,
36
+ });
27
37
  options.push({
28
- nodeType: nodes.paragraph,
29
- label: 'Paragraph',
30
- action: demoteSectionToParagraph,
31
- isDisabled: sectionDepth <= 1 && sectionOffset <= 1,
32
- isSelected: false,
33
- }, {
34
38
  nodeType: nodes.section,
35
39
  label: 'Heading',
36
40
  isDisabled: true,
@@ -39,18 +43,21 @@ const buildOptions = (state) => {
39
43
  return options;
40
44
  }
41
45
  case parentElementType.schema.nodes.paragraph: {
46
+ const paragraph = $from.node($from.depth);
42
47
  options.push({
43
48
  nodeType: nodes.paragraph,
44
49
  label: 'Paragraph',
45
50
  isDisabled: true,
46
51
  isSelected: true,
47
- }, {
48
- nodeType: nodes.section,
49
- label: 'Heading',
50
- action: promoteParagraphToSection,
51
- isDisabled: false,
52
- isSelected: false,
53
52
  });
53
+ !isDeleted(paragraph) &&
54
+ options.push({
55
+ nodeType: nodes.section,
56
+ label: 'Heading',
57
+ action: promoteParagraphToSection,
58
+ isDisabled: isDeleted(paragraph),
59
+ isSelected: false,
60
+ });
54
61
  return options;
55
62
  }
56
63
  default: {
@@ -25,6 +25,13 @@ export function isDeleted(node) {
25
25
  }
26
26
  return false;
27
27
  }
28
+ export function isShadowDelete(node) {
29
+ if (node.attrs.dataTracked) {
30
+ const changes = node.attrs.dataTracked;
31
+ return changes.some(({ operation, moveNodeId }) => operation === 'delete' && moveNodeId);
32
+ }
33
+ return false;
34
+ }
28
35
  export function isPendingInsert(node) {
29
36
  if (node.attrs.dataTracked) {
30
37
  const changes = node.attrs.dataTracked;
@@ -14,6 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { isElementNodeType, isSectionNodeType, schema, } from '@manuscripts/transform';
17
+ import { Fragment, } from 'prosemirror-model';
17
18
  import { findChildrenByType, findParentNode, findParentNodeOfTypeClosestToPos, } from 'prosemirror-utils';
18
19
  import { fieldConfigMap } from '../components/references/ReferenceForm/config';
19
20
  import { arrowDown } from '../icons';
@@ -150,6 +151,22 @@ export const getInsertPos = (type, parent, pos) => {
150
151
  });
151
152
  return insertPos;
152
153
  };
154
+ export const filterBlockNodes = (fragment, predicate) => {
155
+ const updatedNodes = [];
156
+ fragment.forEach((child) => {
157
+ if (!child.isBlock) {
158
+ updatedNodes.push(child);
159
+ return;
160
+ }
161
+ const newContent = child.content.size
162
+ ? filterBlockNodes(child.content, predicate)
163
+ : child.content;
164
+ if (predicate(child)) {
165
+ updatedNodes.push(child.type.create(child.attrs, newContent, child.marks));
166
+ }
167
+ });
168
+ return Fragment.fromArray(updatedNodes);
169
+ };
153
170
  export const getLastTitleNode = (state) => {
154
171
  const altTitleNode = findChildrenByType(state.doc, state.schema.nodes.alt_titles)[0];
155
172
  if (altTitleNode) {
@@ -1,4 +1,4 @@
1
- import { CHANGE_STATUS, } from '@manuscripts/track-changes-plugin';
1
+ import { CHANGE_OPERATION, CHANGE_STATUS, } from '@manuscripts/track-changes-plugin';
2
2
  import { schema, } from '@manuscripts/transform';
3
3
  import { Plugin, PluginKey } from 'prosemirror-state';
4
4
  import { Decoration, DecorationSet } from 'prosemirror-view';
@@ -56,9 +56,6 @@ const getEffectiveSelection = ($pos) => {
56
56
  to: $pos.after(depth),
57
57
  };
58
58
  }
59
- else {
60
- break;
61
- }
62
59
  }
63
60
  if (current) {
64
61
  return current;
@@ -112,12 +109,18 @@ const buildTextDecoration = (doc, selection) => {
112
109
  };
113
110
  };
114
111
  const buildGroupOfChangesDecoration = (doc, changes) => {
115
- const from = changes[0].from, to = changes[changes.length - 1].to;
116
- const decoration = Decoration.inline(from, to, {
117
- class: 'selected-suggestion',
118
- });
112
+ const decorations = [];
113
+ if (changes[0].dataTracked.operation === CHANGE_OPERATION.structure) {
114
+ changes.map((c) => decorations.push(Decoration.node(c.from, c.to, { class: 'selected-suggestion' })));
115
+ }
116
+ else {
117
+ const from = changes[0].from, to = changes[changes.length - 1].to;
118
+ decorations.push(Decoration.inline(from, to, {
119
+ class: 'selected-suggestion',
120
+ }));
121
+ }
119
122
  return {
120
- decorations: DecorationSet.create(doc, [decoration]),
123
+ decorations: DecorationSet.create(doc, decorations),
121
124
  suggestion: changes[0].dataTracked,
122
125
  };
123
126
  };
@@ -1,7 +1,7 @@
1
- import { trackChangesPluginKey } from '@manuscripts/track-changes-plugin';
2
- import { Selection } from 'prosemirror-state';
1
+ import { CHANGE_OPERATION, trackChangesPluginKey, } from '@manuscripts/track-changes-plugin';
2
+ import { NodeSelection, Selection } from 'prosemirror-state';
3
3
  import { isTextSelection } from './commands';
4
- export class NodesSelection extends Selection {
4
+ export class InlineNodesSelection extends Selection {
5
5
  constructor($from, $to) {
6
6
  super($to, $to);
7
7
  this.$startNode = $from;
@@ -25,16 +25,34 @@ export class NodesSelection extends Selection {
25
25
  };
26
26
  }
27
27
  }
28
+ export class NodesSelection extends NodeSelection {
29
+ constructor($from, $to) {
30
+ super($from);
31
+ this.$startNode = $from;
32
+ this.$endNode = $to;
33
+ }
34
+ toJSON() {
35
+ return {
36
+ type: 'nodes',
37
+ startNode: this.$startNode,
38
+ endNode: this.$endNode,
39
+ };
40
+ }
41
+ }
28
42
  export const getSelectionChangeGroup = (state) => {
29
43
  const selection = state.selection;
30
44
  const $pos = isTextSelection(selection)
31
45
  ? selection.$cursor
32
- : selection instanceof NodesSelection && selection.$from;
46
+ : (selection instanceof NodesSelection ||
47
+ selection instanceof InlineNodesSelection) &&
48
+ selection.$from;
33
49
  if ($pos) {
34
50
  return trackChangesPluginKey
35
51
  .getState(state)
36
- ?.changeSet.groupChanges.find((changes) => changes.length > 1 &&
37
- $pos.pos >= changes[0].from &&
38
- $pos.pos <= changes[changes.length - 1].to);
52
+ ?.changeSet.groupChanges.find((c) => isPositionAtRange(c, $pos.pos));
39
53
  }
40
54
  };
55
+ const isPositionAtRange = (changes, pos) => (changes.length > 1 ||
56
+ changes[0].dataTracked.operation === CHANGE_OPERATION.structure) &&
57
+ pos >= changes[0].from &&
58
+ pos <= changes[changes.length - 1].to;
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.5.2';
1
+ export const VERSION = '3.5.3';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -16,6 +16,7 @@
16
16
  import { TrackedAttrs } from '@manuscripts/track-changes-plugin';
17
17
  import { Attrs, Fragment, Node as ProsemirrorNode } from 'prosemirror-model';
18
18
  export declare function isDeleted(node: ProsemirrorNode): boolean;
19
+ export declare function isShadowDelete(node: ProsemirrorNode): boolean;
19
20
  export declare function isPendingInsert(node: ProsemirrorNode): boolean;
20
21
  export declare function isPending(node: ProsemirrorNode): boolean;
21
22
  export declare function isPendingSetAttrs(node: ProsemirrorNode): boolean;
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { BibliographyItemAttrs, BibliographyItemType, ManuscriptEditorState, ManuscriptNode, ManuscriptNodeType } from '@manuscripts/transform';
17
- import { Node as ProseMirrorNode, NodeType, ResolvedPos } from 'prosemirror-model';
17
+ import { Fragment, Node as ProseMirrorNode, NodeType, ResolvedPos } from 'prosemirror-model';
18
18
  import { EditorState, Selection } from 'prosemirror-state';
19
19
  export declare function iterateChildren(node: ManuscriptNode, recurse?: boolean): Iterable<ManuscriptNode>;
20
20
  export declare const getMatchingChild: (parent: ManuscriptNode, matcher: (node: ManuscriptNode) => boolean, deep?: boolean) => ManuscriptNode | undefined;
@@ -36,6 +36,7 @@ export declare const isBodyLocked: (state: EditorState) => boolean;
36
36
  export declare const isEditAllowed: (state: EditorState) => boolean;
37
37
  export declare const createToggleButton: (listener: () => void) => HTMLButtonElement;
38
38
  export declare const getInsertPos: (type: ManuscriptNodeType, parent: ManuscriptNode, pos: number) => number;
39
+ export declare const filterBlockNodes: (fragment: Fragment, predicate: (node: ProseMirrorNode) => boolean) => Fragment;
39
40
  export declare const getLastTitleNode: (state: ManuscriptEditorState) => {
40
41
  node: ProseMirrorNode;
41
42
  pos: number;
@@ -1,13 +1,14 @@
1
+ import { TrackedChange } from '@manuscripts/track-changes-plugin';
1
2
  import { ManuscriptEditorState } from '@manuscripts/transform';
2
3
  import { Node, ResolvedPos } from 'prosemirror-model';
3
- import { Selection } from 'prosemirror-state';
4
+ import { NodeSelection, Selection } from 'prosemirror-state';
4
5
  import { Mappable } from 'prosemirror-transform';
5
6
  interface NodesSelectionJSON {
6
- type: 'inlineNodes';
7
+ type: 'inlineNodes' | 'nodes';
7
8
  startNode: ResolvedPos;
8
9
  endNode: ResolvedPos;
9
10
  }
10
- export declare class NodesSelection extends Selection {
11
+ export declare class InlineNodesSelection extends Selection {
11
12
  $startNode: ResolvedPos;
12
13
  $endNode: ResolvedPos;
13
14
  constructor($from: ResolvedPos, $to: ResolvedPos);
@@ -15,5 +16,11 @@ export declare class NodesSelection extends Selection {
15
16
  map(doc: Node, mapping: Mappable): Selection;
16
17
  toJSON(): NodesSelectionJSON;
17
18
  }
18
- export declare const getSelectionChangeGroup: (state: ManuscriptEditorState) => import("@manuscripts/track-changes-plugin").TrackedChange[] | undefined;
19
+ export declare class NodesSelection extends NodeSelection {
20
+ $startNode: ResolvedPos;
21
+ $endNode: ResolvedPos;
22
+ constructor($from: ResolvedPos, $to: ResolvedPos);
23
+ toJSON(): NodesSelectionJSON;
24
+ }
25
+ export declare const getSelectionChangeGroup: (state: ManuscriptEditorState) => TrackedChange[] | undefined;
19
26
  export {};
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.5.2";
1
+ export declare const VERSION = "3.5.3";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
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.5.2",
4
+ "version": "3.5.3",
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
  "@iarna/word-count": "1.1.2",
41
41
  "@manuscripts/json-schema": "2.2.12",
42
42
  "@manuscripts/style-guide": "3.3.0",
43
- "@manuscripts/track-changes-plugin": "2.0.11",
43
+ "@manuscripts/track-changes-plugin": "2.0.12",
44
44
  "@manuscripts/transform": "4.2.14",
45
45
  "@popperjs/core": "2.11.8",
46
46
  "citeproc": "2.4.63",
@@ -1064,7 +1064,7 @@ figure.block:has(.equation.selected-suggestion) {
1064
1064
  [data-track-op='node_split'][data-track-status='pending']
1065
1065
  .block,
1066
1066
  .tracking-visible
1067
- .selected-suggestion[data-track-op='move'][data-track-status='pending']
1067
+ .selected-suggestion:is([data-track-op='move'], [data-track-op='structure'])
1068
1068
  .block,
1069
1069
  .block:has(
1070
1070
  figure.selected-suggestion,
@@ -1079,7 +1079,7 @@ figure.block:has(.equation.selected-suggestion) {
1079
1079
  .tracking-visible .selected-suggestion[data-track-op='node_split'] .block,
1080
1080
  .tracking-visible figure.block:has(.equation.set_attrs.selected-suggestion),
1081
1081
  .tracking-visible
1082
- .selected-suggestion[data-track-op='move'][data-track-status='pending']
1082
+ .selected-suggestion:is([data-track-op='move'], [data-track-op='structure'])
1083
1083
  .block {
1084
1084
  box-shadow: inset 6px 0 0 var(--updated-border-color),
1085
1085
  inset 9px 0 0 var(--updated-bg-color) !important;