@manuscripts/body-editor 3.2.14 → 3.2.16

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.
@@ -34,6 +34,7 @@ const dnd_1 = require("../../lib/dnd");
34
34
  const track_changes_utils_1 = require("../../lib/track-changes-utils");
35
35
  const utils_1 = require("../../lib/utils");
36
36
  const node_type_icons_1 = require("../../node-type-icons");
37
+ const section_title_1 = require("../../plugins/section_title");
37
38
  const Outline_1 = require("./Outline");
38
39
  const excludedTypes = [
39
40
  transform_1.schema.nodes.table,
@@ -104,13 +105,22 @@ const DraggableTree = ({ tree, view, depth, can, }) => {
104
105
  ? (0, utils_1.isBodyLocked)(view.state) || !can?.editArticle
105
106
  : true;
106
107
  const { node, items, parent } = tree;
108
+ const sectionTitleState = view
109
+ ? section_title_1.sectionTitleKey.getState(view.state)
110
+ : undefined;
107
111
  const itemText = (node) => {
108
112
  const text = (0, transform_1.nodeTitle)(node);
113
+ let sectionNumber = node.type.name === 'section' && sectionTitleState
114
+ ? sectionTitleState.get(node.attrs.id) ?? ''
115
+ : '';
116
+ sectionNumber = sectionNumber ? `${sectionNumber}.` : '';
109
117
  if (text) {
110
- return text.trim();
118
+ return `${sectionNumber}${sectionNumber ? ' ' : ''}${text.trim()}`;
111
119
  }
112
120
  const placeholder = (0, transform_1.nodeTitlePlaceholder)(node.type);
113
- return react_1.default.createElement(Outline_1.OutlineItemPlaceholder, null, placeholder);
121
+ return (react_1.default.createElement(Outline_1.OutlineItemPlaceholder, null,
122
+ sectionNumber && `${sectionNumber} `,
123
+ placeholder));
114
124
  };
115
125
  const toggleOpen = () => {
116
126
  setOpen(!isOpen);
@@ -59,6 +59,6 @@ const ManuscriptOutline = (props) => {
59
59
  setValues(undefined);
60
60
  }
61
61
  }, [debouncedProps, props.can]);
62
- return values ? react_1.default.createElement(DraggableTree_1.DraggableTree, { ...values, depth: 0 }) : null;
62
+ return values && values.view ? react_1.default.createElement(DraggableTree_1.DraggableTree, { ...values, depth: 0 }) : null;
63
63
  };
64
64
  exports.ManuscriptOutline = ManuscriptOutline;
@@ -32,6 +32,7 @@ const alt_titles_1 = __importDefault(require("../plugins/alt-titles"));
32
32
  const bibliography_1 = __importDefault(require("../plugins/bibliography"));
33
33
  const comments_1 = __importDefault(require("../plugins/comments"));
34
34
  const cross_references_1 = __importDefault(require("../plugins/cross-references"));
35
+ const detect_inconsistency_1 = __importDefault(require("../plugins/detect-inconsistency"));
35
36
  const doi_1 = __importDefault(require("../plugins/doi"));
36
37
  const editor_props_1 = __importDefault(require("../plugins/editor-props"));
37
38
  const elements_1 = __importDefault(require("../plugins/elements"));
@@ -81,6 +82,7 @@ exports.default = (props) => {
81
82
  (0, doi_1.default)(),
82
83
  (0, section_category_1.default)(props),
83
84
  (0, cross_references_1.default)(),
85
+ (0, detect_inconsistency_1.default)(),
84
86
  (0, search_replace_1.default)(),
85
87
  (0, lock_body_1.default)(),
86
88
  (0, alt_titles_1.default)(),
package/dist/cjs/index.js CHANGED
@@ -29,7 +29,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
29
29
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
30
30
  };
31
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.affiliationLabel = exports.authorLabel = exports.bibliographyItemTypes = exports.metadata = exports.getClosestMatch = exports.getNewMatch = exports.searchReplacePluginKey = exports.bibliographyPluginKey = exports.footnotesPluginKey = exports.objectsPluginKey = exports.selectedSuggestionKey = exports.PopperManager = exports.CollabProvider = exports.TypeSelector = exports.OutlineItemIcon = exports.ManuscriptOutline = void 0;
32
+ exports.affiliationLabel = exports.authorLabel = exports.bibliographyItemTypes = exports.metadata = exports.getClosestMatch = exports.getNewMatch = exports.detectInconsistencyPluginKey = exports.searchReplacePluginKey = exports.bibliographyPluginKey = exports.footnotesPluginKey = exports.objectsPluginKey = exports.selectedSuggestionKey = exports.PopperManager = exports.CollabProvider = exports.TypeSelector = exports.OutlineItemIcon = exports.ManuscriptOutline = void 0;
33
33
  __exportStar(require("./commands"), exports);
34
34
  var ManuscriptOutline_1 = require("./components/outline/ManuscriptOutline");
35
35
  Object.defineProperty(exports, "ManuscriptOutline", { enumerable: true, get: function () { return ManuscriptOutline_1.ManuscriptOutline; } });
@@ -65,6 +65,8 @@ var bibliography_1 = require("./plugins/bibliography");
65
65
  Object.defineProperty(exports, "bibliographyPluginKey", { enumerable: true, get: function () { return bibliography_1.bibliographyKey; } });
66
66
  var search_replace_1 = require("./plugins/search-replace");
67
67
  Object.defineProperty(exports, "searchReplacePluginKey", { enumerable: true, get: function () { return search_replace_1.searchReplaceKey; } });
68
+ var detect_inconsistency_1 = require("./plugins/detect-inconsistency");
69
+ Object.defineProperty(exports, "detectInconsistencyPluginKey", { enumerable: true, get: function () { return detect_inconsistency_1.detectInconsistencyKey; } });
68
70
  var lib_1 = require("./plugins/search-replace/lib");
69
71
  Object.defineProperty(exports, "getNewMatch", { enumerable: true, get: function () { return lib_1.getNewMatch; } });
70
72
  Object.defineProperty(exports, "getClosestMatch", { enumerable: true, get: function () { return lib_1.getClosestMatch; } });
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ /*!
3
+ * © 2025 Atypon Systems LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.buildPluginState = exports.createDecoration = void 0;
19
+ const prosemirror_state_1 = require("prosemirror-state");
20
+ const prosemirror_view_1 = require("prosemirror-view");
21
+ const bibliography_1 = require("../bibliography");
22
+ const footnotes_1 = require("../footnotes");
23
+ const objects_1 = require("../objects");
24
+ const validators_1 = require("./validators");
25
+ const createDecoration = (node, pos, selectedPos) => {
26
+ const classNames = ['inconsistency-highlight'];
27
+ if (selectedPos === pos) {
28
+ classNames.push('selected-suggestion');
29
+ }
30
+ return prosemirror_view_1.Decoration.node(pos, pos + node.nodeSize, {
31
+ class: classNames.join(' '),
32
+ 'data-inconsistency-type': 'warning',
33
+ });
34
+ };
35
+ exports.createDecoration = createDecoration;
36
+ const buildPluginState = (state, showDecorations) => {
37
+ const inconsistencies = [];
38
+ const decorations = [];
39
+ const selection = state.selection;
40
+ let selectedPos = null;
41
+ if (selection instanceof prosemirror_state_1.NodeSelection) {
42
+ selectedPos = selection.from;
43
+ }
44
+ const context = {
45
+ pluginStates: {
46
+ bibliography: (0, bibliography_1.getBibliographyPluginState)(state)?.bibliographyItems,
47
+ objects: objects_1.objectsKey.getState(state),
48
+ footnotes: footnotes_1.footnotesKey.getState(state)?.footnotesElementIDs,
49
+ },
50
+ showDecorations,
51
+ selectedPos,
52
+ decorations,
53
+ };
54
+ state.doc.descendants((node, pos) => {
55
+ const validator = validators_1.validators[node.type.name];
56
+ if (validator) {
57
+ inconsistencies.push(...validator(node, pos, context));
58
+ }
59
+ });
60
+ return {
61
+ decorations: prosemirror_view_1.DecorationSet.create(state.doc, decorations),
62
+ inconsistencies,
63
+ showDecorations,
64
+ };
65
+ };
66
+ exports.buildPluginState = buildPluginState;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /*!
3
+ * © 2025 Atypon Systems LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.detectInconsistencyKey = void 0;
19
+ const prosemirror_state_1 = require("prosemirror-state");
20
+ const prosemirror_view_1 = require("prosemirror-view");
21
+ const detect_inconsistency_utils_1 = require("./detect-inconsistency-utils");
22
+ exports.detectInconsistencyKey = new prosemirror_state_1.PluginKey('detectInconsistency');
23
+ exports.default = () => {
24
+ return new prosemirror_state_1.Plugin({
25
+ key: exports.detectInconsistencyKey,
26
+ state: {
27
+ init: (_, state) => (0, detect_inconsistency_utils_1.buildPluginState)(state, false),
28
+ apply: (tr, value, newState) => {
29
+ const showDecorations = tr.getMeta(exports.detectInconsistencyKey) !== undefined
30
+ ? tr.getMeta(exports.detectInconsistencyKey)
31
+ : value.showDecorations;
32
+ return (0, detect_inconsistency_utils_1.buildPluginState)(newState, showDecorations);
33
+ },
34
+ },
35
+ props: {
36
+ decorations: (state) => {
37
+ const pluginState = exports.detectInconsistencyKey.getState(state);
38
+ return pluginState?.showDecorations
39
+ ? pluginState.decorations
40
+ : prosemirror_view_1.DecorationSet.empty;
41
+ },
42
+ },
43
+ });
44
+ };
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /*!
3
+ * © 2025 Atypon Systems LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.validators = void 0;
19
+ const transform_1 = require("@manuscripts/transform");
20
+ const detect_inconsistency_utils_1 = require("./detect-inconsistency-utils");
21
+ const getNodeDescription = (node) => {
22
+ return transform_1.nodeNames.get(node.type) || node.type?.name || 'node';
23
+ };
24
+ const createWarning = (node, pos, category, severity) => {
25
+ const nodeDescription = getNodeDescription(node);
26
+ const message = category === 'empty-content' ? `Is empty` : `Has no linked reference`;
27
+ return {
28
+ type: 'warning',
29
+ category,
30
+ severity,
31
+ message,
32
+ nodeDescription,
33
+ node,
34
+ pos,
35
+ };
36
+ };
37
+ const validateTitle = (node, pos, context) => {
38
+ const inconsistencies = [];
39
+ const isEmpty = node.content.size === 0;
40
+ if (isEmpty) {
41
+ const inconsistency = createWarning(node, pos, 'empty-content', 'warning');
42
+ inconsistencies.push(inconsistency);
43
+ if (context.showDecorations) {
44
+ context.decorations.push((0, detect_inconsistency_utils_1.createDecoration)(node, pos, context.selectedPos));
45
+ }
46
+ }
47
+ return inconsistencies;
48
+ };
49
+ const validateCrossReference = (node, pos, context) => {
50
+ const inconsistencies = [];
51
+ const figures = Array.from(context.pluginStates.objects?.keys() ?? []);
52
+ const isInFigures = node.attrs.rids.every((rid) => figures.includes(rid));
53
+ if (!isInFigures) {
54
+ const inconsistency = createWarning(node, pos, 'missing-reference', 'warning');
55
+ inconsistencies.push(inconsistency);
56
+ if (context.showDecorations) {
57
+ context.decorations.push((0, detect_inconsistency_utils_1.createDecoration)(node, pos, context.selectedPos));
58
+ }
59
+ }
60
+ return inconsistencies;
61
+ };
62
+ const validateCitation = (node, pos, context) => {
63
+ const inconsistencies = [];
64
+ if (context.pluginStates.bibliography) {
65
+ const isInBibliography = node.attrs.rids.every((rid) => context.pluginStates.bibliography?.get(rid));
66
+ if (!isInBibliography || node.attrs.rids.length === 0) {
67
+ const inconsistency = createWarning(node, pos, 'missing-reference', 'warning');
68
+ inconsistencies.push(inconsistency);
69
+ if (context.showDecorations) {
70
+ context.decorations.push((0, detect_inconsistency_utils_1.createDecoration)(node, pos, context.selectedPos));
71
+ }
72
+ }
73
+ }
74
+ return inconsistencies;
75
+ };
76
+ const validateInlineFootnote = (node, pos, context) => {
77
+ const inconsistencies = [];
78
+ if (context.pluginStates.footnotes) {
79
+ const isInFootnote = node.attrs.rids.every((rid) => context.pluginStates.footnotes?.get(rid));
80
+ if (!isInFootnote) {
81
+ const inconsistency = createWarning(node, pos, 'missing-reference', 'warning');
82
+ inconsistencies.push(inconsistency);
83
+ if (context.showDecorations) {
84
+ context.decorations.push((0, detect_inconsistency_utils_1.createDecoration)(node, pos, context.selectedPos));
85
+ }
86
+ }
87
+ }
88
+ return inconsistencies;
89
+ };
90
+ exports.validators = {
91
+ [transform_1.schema.nodes.title.name]: validateTitle,
92
+ [transform_1.schema.nodes.cross_reference.name]: validateCrossReference,
93
+ [transform_1.schema.nodes.citation.name]: validateCitation,
94
+ [transform_1.schema.nodes.inline_footnote.name]: validateInlineFootnote,
95
+ };
@@ -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.14';
4
+ exports.VERSION = '3.2.16';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -48,7 +48,8 @@ class CitationEditableView extends citation_1.CitationView {
48
48
  element.parentElement?.classList.contains('comment-icon'));
49
49
  };
50
50
  this.handleClick = (event) => {
51
- if (!this.can.seeReferencesButtons) {
51
+ if (!this.can.seeReferencesButtons ||
52
+ this.dom.classList.contains('inconsistency-highlight')) {
52
53
  this.showPopper();
53
54
  }
54
55
  else if (!(0, track_changes_utils_1.isDeleted)(this.node) && event.button === 0) {
@@ -60,7 +61,9 @@ class CitationEditableView extends citation_1.CitationView {
60
61
  };
61
62
  this.selectNode = () => {
62
63
  this.dom.classList.add('ProseMirror-selectednode');
63
- if (this.can.seeReferencesButtons && !(0, track_changes_utils_1.isDeleted)(this.node)) {
64
+ if (this.can.seeReferencesButtons &&
65
+ !(0, track_changes_utils_1.isDeleted)(this.node) &&
66
+ !this.dom.classList.contains('inconsistency-highlight')) {
64
67
  const attrs = this.node.attrs;
65
68
  if (!attrs.rids.length) {
66
69
  this.showPopper();
@@ -8,6 +8,7 @@ import { getDropSide } from '../../lib/dnd';
8
8
  import { isDeleted } from '../../lib/track-changes-utils';
9
9
  import { isBodyLocked } from '../../lib/utils';
10
10
  import { nodeTypeIcon } from '../../node-type-icons';
11
+ import { sectionTitleKey } from '../../plugins/section_title';
11
12
  import { Outline, OutlineItem, OutlineItemArrow, OutlineItemIcon, OutlineItemLink, OutlineItemLinkText, OutlineItemNoArrow, OutlineItemPlaceholder, } from './Outline';
12
13
  const excludedTypes = [
13
14
  schema.nodes.table,
@@ -77,13 +78,22 @@ export const DraggableTree = ({ tree, view, depth, can, }) => {
77
78
  ? isBodyLocked(view.state) || !can?.editArticle
78
79
  : true;
79
80
  const { node, items, parent } = tree;
81
+ const sectionTitleState = view
82
+ ? sectionTitleKey.getState(view.state)
83
+ : undefined;
80
84
  const itemText = (node) => {
81
85
  const text = nodeTitle(node);
86
+ let sectionNumber = node.type.name === 'section' && sectionTitleState
87
+ ? sectionTitleState.get(node.attrs.id) ?? ''
88
+ : '';
89
+ sectionNumber = sectionNumber ? `${sectionNumber}.` : '';
82
90
  if (text) {
83
- return text.trim();
91
+ return `${sectionNumber}${sectionNumber ? ' ' : ''}${text.trim()}`;
84
92
  }
85
93
  const placeholder = nodeTitlePlaceholder(node.type);
86
- return React.createElement(OutlineItemPlaceholder, null, placeholder);
94
+ return (React.createElement(OutlineItemPlaceholder, null,
95
+ sectionNumber && `${sectionNumber} `,
96
+ placeholder));
87
97
  };
88
98
  const toggleOpen = () => {
89
99
  setOpen(!isOpen);
@@ -33,5 +33,5 @@ export const ManuscriptOutline = (props) => {
33
33
  setValues(undefined);
34
34
  }
35
35
  }, [debouncedProps, props.can]);
36
- return values ? React.createElement(DraggableTree, { ...values, depth: 0 }) : null;
36
+ return values && values.view ? React.createElement(DraggableTree, { ...values, depth: 0 }) : null;
37
37
  };
@@ -27,6 +27,7 @@ import alt_titles from '../plugins/alt-titles';
27
27
  import bibliography from '../plugins/bibliography';
28
28
  import comments from '../plugins/comments';
29
29
  import cross_references from '../plugins/cross-references';
30
+ import detect_inconsistency from '../plugins/detect-inconsistency';
30
31
  import doi from '../plugins/doi';
31
32
  import editorProps from '../plugins/editor-props';
32
33
  import elements from '../plugins/elements';
@@ -76,6 +77,7 @@ export default (props) => {
76
77
  doi(),
77
78
  section_category(props),
78
79
  cross_references(),
80
+ detect_inconsistency(),
79
81
  search_replace(),
80
82
  lock_body(),
81
83
  alt_titles(),
package/dist/es/index.js CHANGED
@@ -38,6 +38,7 @@ export { objectsKey as objectsPluginKey } from './plugins/objects';
38
38
  export { footnotesKey as footnotesPluginKey } from './plugins/footnotes';
39
39
  export { bibliographyKey as bibliographyPluginKey } from './plugins/bibliography';
40
40
  export { searchReplaceKey as searchReplacePluginKey, } from './plugins/search-replace';
41
+ export { detectInconsistencyKey as detectInconsistencyPluginKey } from './plugins/detect-inconsistency';
41
42
  export { getNewMatch, getClosestMatch } from './plugins/search-replace/lib';
42
43
  export { metadata, bibliographyItemTypes } from './lib/references';
43
44
  export { authorLabel, affiliationLabel, } from './lib/authors';
@@ -0,0 +1,61 @@
1
+ /*!
2
+ * © 2025 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { NodeSelection } from 'prosemirror-state';
17
+ import { Decoration, DecorationSet } from 'prosemirror-view';
18
+ import { getBibliographyPluginState } from '../bibliography';
19
+ import { footnotesKey } from '../footnotes';
20
+ import { objectsKey } from '../objects';
21
+ import { validators } from './validators';
22
+ export const createDecoration = (node, pos, selectedPos) => {
23
+ const classNames = ['inconsistency-highlight'];
24
+ if (selectedPos === pos) {
25
+ classNames.push('selected-suggestion');
26
+ }
27
+ return Decoration.node(pos, pos + node.nodeSize, {
28
+ class: classNames.join(' '),
29
+ 'data-inconsistency-type': 'warning',
30
+ });
31
+ };
32
+ export const buildPluginState = (state, showDecorations) => {
33
+ const inconsistencies = [];
34
+ const decorations = [];
35
+ const selection = state.selection;
36
+ let selectedPos = null;
37
+ if (selection instanceof NodeSelection) {
38
+ selectedPos = selection.from;
39
+ }
40
+ const context = {
41
+ pluginStates: {
42
+ bibliography: getBibliographyPluginState(state)?.bibliographyItems,
43
+ objects: objectsKey.getState(state),
44
+ footnotes: footnotesKey.getState(state)?.footnotesElementIDs,
45
+ },
46
+ showDecorations,
47
+ selectedPos,
48
+ decorations,
49
+ };
50
+ state.doc.descendants((node, pos) => {
51
+ const validator = validators[node.type.name];
52
+ if (validator) {
53
+ inconsistencies.push(...validator(node, pos, context));
54
+ }
55
+ });
56
+ return {
57
+ decorations: DecorationSet.create(state.doc, decorations),
58
+ inconsistencies,
59
+ showDecorations,
60
+ };
61
+ };
@@ -0,0 +1,41 @@
1
+ /*!
2
+ * © 2025 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { Plugin, PluginKey } from 'prosemirror-state';
17
+ import { DecorationSet } from 'prosemirror-view';
18
+ import { buildPluginState } from './detect-inconsistency-utils';
19
+ export const detectInconsistencyKey = new PluginKey('detectInconsistency');
20
+ export default () => {
21
+ return new Plugin({
22
+ key: detectInconsistencyKey,
23
+ state: {
24
+ init: (_, state) => buildPluginState(state, false),
25
+ apply: (tr, value, newState) => {
26
+ const showDecorations = tr.getMeta(detectInconsistencyKey) !== undefined
27
+ ? tr.getMeta(detectInconsistencyKey)
28
+ : value.showDecorations;
29
+ return buildPluginState(newState, showDecorations);
30
+ },
31
+ },
32
+ props: {
33
+ decorations: (state) => {
34
+ const pluginState = detectInconsistencyKey.getState(state);
35
+ return pluginState?.showDecorations
36
+ ? pluginState.decorations
37
+ : DecorationSet.empty;
38
+ },
39
+ },
40
+ });
41
+ };
@@ -0,0 +1,92 @@
1
+ /*!
2
+ * © 2025 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { nodeNames, schema, } from '@manuscripts/transform';
17
+ import { createDecoration } from './detect-inconsistency-utils';
18
+ const getNodeDescription = (node) => {
19
+ return nodeNames.get(node.type) || node.type?.name || 'node';
20
+ };
21
+ const createWarning = (node, pos, category, severity) => {
22
+ const nodeDescription = getNodeDescription(node);
23
+ const message = category === 'empty-content' ? `Is empty` : `Has no linked reference`;
24
+ return {
25
+ type: 'warning',
26
+ category,
27
+ severity,
28
+ message,
29
+ nodeDescription,
30
+ node,
31
+ pos,
32
+ };
33
+ };
34
+ const validateTitle = (node, pos, context) => {
35
+ const inconsistencies = [];
36
+ const isEmpty = node.content.size === 0;
37
+ if (isEmpty) {
38
+ const inconsistency = createWarning(node, pos, 'empty-content', 'warning');
39
+ inconsistencies.push(inconsistency);
40
+ if (context.showDecorations) {
41
+ context.decorations.push(createDecoration(node, pos, context.selectedPos));
42
+ }
43
+ }
44
+ return inconsistencies;
45
+ };
46
+ const validateCrossReference = (node, pos, context) => {
47
+ const inconsistencies = [];
48
+ const figures = Array.from(context.pluginStates.objects?.keys() ?? []);
49
+ const isInFigures = node.attrs.rids.every((rid) => figures.includes(rid));
50
+ if (!isInFigures) {
51
+ const inconsistency = createWarning(node, pos, 'missing-reference', 'warning');
52
+ inconsistencies.push(inconsistency);
53
+ if (context.showDecorations) {
54
+ context.decorations.push(createDecoration(node, pos, context.selectedPos));
55
+ }
56
+ }
57
+ return inconsistencies;
58
+ };
59
+ const validateCitation = (node, pos, context) => {
60
+ const inconsistencies = [];
61
+ if (context.pluginStates.bibliography) {
62
+ const isInBibliography = node.attrs.rids.every((rid) => context.pluginStates.bibliography?.get(rid));
63
+ if (!isInBibliography || node.attrs.rids.length === 0) {
64
+ const inconsistency = createWarning(node, pos, 'missing-reference', 'warning');
65
+ inconsistencies.push(inconsistency);
66
+ if (context.showDecorations) {
67
+ context.decorations.push(createDecoration(node, pos, context.selectedPos));
68
+ }
69
+ }
70
+ }
71
+ return inconsistencies;
72
+ };
73
+ const validateInlineFootnote = (node, pos, context) => {
74
+ const inconsistencies = [];
75
+ if (context.pluginStates.footnotes) {
76
+ const isInFootnote = node.attrs.rids.every((rid) => context.pluginStates.footnotes?.get(rid));
77
+ if (!isInFootnote) {
78
+ const inconsistency = createWarning(node, pos, 'missing-reference', 'warning');
79
+ inconsistencies.push(inconsistency);
80
+ if (context.showDecorations) {
81
+ context.decorations.push(createDecoration(node, pos, context.selectedPos));
82
+ }
83
+ }
84
+ }
85
+ return inconsistencies;
86
+ };
87
+ export const validators = {
88
+ [schema.nodes.title.name]: validateTitle,
89
+ [schema.nodes.cross_reference.name]: validateCrossReference,
90
+ [schema.nodes.citation.name]: validateCitation,
91
+ [schema.nodes.inline_footnote.name]: validateInlineFootnote,
92
+ };
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.2.14';
1
+ export const VERSION = '3.2.16';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -42,7 +42,8 @@ export class CitationEditableView extends CitationView {
42
42
  element.parentElement?.classList.contains('comment-icon'));
43
43
  };
44
44
  this.handleClick = (event) => {
45
- if (!this.can.seeReferencesButtons) {
45
+ if (!this.can.seeReferencesButtons ||
46
+ this.dom.classList.contains('inconsistency-highlight')) {
46
47
  this.showPopper();
47
48
  }
48
49
  else if (!isDeleted(this.node) && event.button === 0) {
@@ -54,7 +55,9 @@ export class CitationEditableView extends CitationView {
54
55
  };
55
56
  this.selectNode = () => {
56
57
  this.dom.classList.add('ProseMirror-selectednode');
57
- if (this.can.seeReferencesButtons && !isDeleted(this.node)) {
58
+ if (this.can.seeReferencesButtons &&
59
+ !isDeleted(this.node) &&
60
+ !this.dom.classList.contains('inconsistency-highlight')) {
58
61
  const attrs = this.node.attrs;
59
62
  if (!attrs.rids.length) {
60
63
  this.showPopper();
@@ -39,6 +39,8 @@ export { objectsKey as objectsPluginKey } from './plugins/objects';
39
39
  export { footnotesKey as footnotesPluginKey } from './plugins/footnotes';
40
40
  export { bibliographyKey as bibliographyPluginKey } from './plugins/bibliography';
41
41
  export { searchReplaceKey as searchReplacePluginKey, SearchReplacePluginState, } from './plugins/search-replace';
42
+ export { detectInconsistencyKey as detectInconsistencyPluginKey } from './plugins/detect-inconsistency';
42
43
  export { getNewMatch, getClosestMatch } from './plugins/search-replace/lib';
43
44
  export { metadata, bibliographyItemTypes } from './lib/references';
44
45
  export { authorLabel, affiliationLabel, AffiliationAttrs, ContributorAttrs, } from './lib/authors';
46
+ export type { Inconsistency } from './plugins/detect-inconsistency';
@@ -0,0 +1,33 @@
1
+ /*!
2
+ * © 2025 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { ManuscriptEditorState, ManuscriptNode } from '@manuscripts/transform';
17
+ import { Decoration, DecorationSet } from 'prosemirror-view';
18
+ export type Inconsistency = {
19
+ type: 'warning';
20
+ category: 'missing-reference' | 'empty-content';
21
+ severity: 'error' | 'warning';
22
+ message: string;
23
+ nodeDescription: string;
24
+ node: ManuscriptNode;
25
+ pos: number;
26
+ };
27
+ export type PluginState = {
28
+ decorations: DecorationSet;
29
+ inconsistencies: Array<Inconsistency>;
30
+ showDecorations: boolean;
31
+ };
32
+ export declare const createDecoration: (node: ManuscriptNode, pos: number, selectedPos: number | null) => Decoration;
33
+ export declare const buildPluginState: (state: ManuscriptEditorState, showDecorations: boolean) => PluginState;
@@ -0,0 +1,21 @@
1
+ /*!
2
+ * © 2025 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { Plugin, PluginKey } from 'prosemirror-state';
17
+ import { PluginState } from './detect-inconsistency-utils';
18
+ export { Inconsistency } from './detect-inconsistency-utils';
19
+ export declare const detectInconsistencyKey: PluginKey<PluginState>;
20
+ declare const _default: () => Plugin<PluginState>;
21
+ export default _default;
@@ -0,0 +1,30 @@
1
+ /*!
2
+ * © 2025 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { BibliographyItemAttrs, ManuscriptNode, Target } from '@manuscripts/transform';
17
+ import { Decoration } from 'prosemirror-view';
18
+ import { Inconsistency } from './detect-inconsistency-utils';
19
+ export type ValidatorContext = {
20
+ pluginStates: {
21
+ bibliography: Map<string, BibliographyItemAttrs> | undefined;
22
+ objects: Map<string, Target> | undefined;
23
+ footnotes: Map<string, string> | undefined;
24
+ };
25
+ showDecorations: boolean;
26
+ selectedPos: number | null;
27
+ decorations: Decoration[];
28
+ };
29
+ export type NodeValidator = (node: ManuscriptNode, pos: number, context: ValidatorContext) => Inconsistency[];
30
+ export declare const validators: Record<string, NodeValidator>;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.2.14";
1
+ export declare const VERSION = "3.2.16";
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.2.14",
4
+ "version": "3.2.16",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -38,9 +38,9 @@
38
38
  "@citation-js/plugin-pubmed": "0.3.0",
39
39
  "@citation-js/plugin-ris": "0.7.18",
40
40
  "@manuscripts/json-schema": "2.2.12",
41
- "@manuscripts/style-guide": "3.1.3",
42
- "@manuscripts/track-changes-plugin": "2.0.2",
43
- "@manuscripts/transform": "4.2.2",
41
+ "@manuscripts/style-guide": "3.1.4",
42
+ "@manuscripts/track-changes-plugin": "2.0.3",
43
+ "@manuscripts/transform": "4.2.3",
44
44
  "@popperjs/core": "2.11.8",
45
45
  "citeproc": "2.4.63",
46
46
  "codemirror": "5.65.19",
@@ -764,13 +764,28 @@ span.comment-marker {
764
764
 
765
765
  .selected-suggestion:not(.block-container):not(.graphical-abstract):not(
766
766
  .keywords
767
- ):not(figure):not(figure .equation),
767
+ ):not(figure):not(figure .equation):not(.inconsistency-highlight),
768
768
  .footnote-marker-selected {
769
769
  border-width: 2px 0 2px 0 !important;
770
770
  border-style: solid !important;
771
771
  border-color: var(--common-color);
772
772
  }
773
773
 
774
+ .ProseMirror .inconsistency-highlight {
775
+ border: 1px solid #f35143!important;
776
+ background: #fff1f0;
777
+ border-radius: 4px;
778
+ box-sizing: border-box;
779
+ position: relative;
780
+ }
781
+
782
+ .ProseMirror .inconsistency-highlight.selected-suggestion,
783
+ .ProseMirror .inconsistency-highlight.footnote-marker-selected,
784
+ .ProseMirror .inconsistency-highlight.ProseMirror-selectednode {
785
+ border: 2px solid #f35143!important;
786
+ background: #fff1f0;
787
+ }
788
+
774
789
  .block-bullet_list .selected-suggestion p,
775
790
  .block-ordered_list .selected-suggestion p {
776
791
  margin: 0;
package/styles/Editor.css CHANGED
@@ -1001,6 +1001,12 @@
1001
1001
  border-color: transparent;
1002
1002
  }
1003
1003
 
1004
+ .ProseMirror .footnote-marker:empty {
1005
+ vertical-align: baseline;
1006
+ min-height: 18px;
1007
+ min-width: 5px;
1008
+ }
1009
+
1004
1010
  .ProseMirror .footnote .footnote-marker {
1005
1011
  color: inherit;
1006
1012
  cursor: default;