@manuscripts/body-editor 3.12.67 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/dist/cjs/components/toolbar/type-selector/styles.js +2 -1
  2. package/dist/cjs/versions.js +1 -1
  3. package/dist/es/components/toolbar/type-selector/styles.js +2 -1
  4. package/dist/es/versions.js +1 -1
  5. package/dist/types/components/toolbar/type-selector/styles.d.ts +2 -2
  6. package/dist/types/lib/footnotes.d.ts +4 -2
  7. package/dist/types/lib/utils.d.ts +5 -4
  8. package/dist/types/versions.d.ts +1 -1
  9. package/package.json +37 -36
  10. package/{dist/es/lib/statistics.js → src/__mocks__/styleMock.ts} +2 -14
  11. package/src/assets.ts +20 -0
  12. package/src/classes/collabProvider.ts +38 -0
  13. package/src/clipboard.ts +54 -0
  14. package/src/commands.ts +2109 -0
  15. package/src/components/ChangeHandlingForm.tsx +54 -0
  16. package/src/components/LanguageDropdown/index.tsx +343 -0
  17. package/src/components/affiliations/AffiliationForm.tsx +238 -0
  18. package/src/components/affiliations/AffiliationItem.tsx +151 -0
  19. package/src/components/affiliations/AffiliationList.tsx +71 -0
  20. package/src/components/affiliations/AffiliationsModal.tsx +676 -0
  21. package/src/components/affiliations/AffiliationsPanel.tsx +76 -0
  22. package/src/components/affiliations/CreateAffiliationModal.tsx +135 -0
  23. package/src/components/authors/AuthorDetailsForm.tsx +438 -0
  24. package/src/components/authors/AuthorList.tsx +81 -0
  25. package/src/components/authors/AuthorsModal.tsx +642 -0
  26. package/src/components/authors/AuthorsPanel.tsx +69 -0
  27. package/src/components/authors/CreateAuthorModal.tsx +172 -0
  28. package/src/components/authors/CreditDrawer.tsx +71 -0
  29. package/src/components/authors/DraggableAuthor.tsx +228 -0
  30. package/src/components/authors/useManageAffiliations.tsx +77 -0
  31. package/src/components/authors/useManageCredit.tsx +63 -0
  32. package/src/components/authors-affiliations/AuthorsAndAffiliationsModals.tsx +170 -0
  33. package/src/components/authors-affiliations/GenericPanel.tsx +164 -0
  34. package/src/components/authors-affiliations/ModalTabs.tsx +100 -0
  35. package/src/components/awards/AwardForm.tsx +338 -0
  36. package/src/components/awards/AwardModal.tsx +120 -0
  37. package/src/components/awards/DeleteAwardDiaolog.tsx +52 -0
  38. package/src/components/dialog/ConfirmationDialog.tsx +131 -0
  39. package/src/components/form/CreateModalStyles.tsx +37 -0
  40. package/src/components/form/FormFooter.tsx +54 -0
  41. package/src/components/form/FormPlaceholder.tsx +71 -0
  42. package/src/components/form/ModalFormActions.tsx +88 -0
  43. package/src/components/form/UnsavedLabel.tsx +49 -0
  44. package/src/components/hooks/use-debounce.ts +56 -0
  45. package/src/components/hooks/useAffiliationShowsErrorIndicator.ts +51 -0
  46. package/src/components/hooks/useAuthorShowsErrorIndicator.ts +73 -0
  47. package/src/components/keyboard-shortcuts-modal/FormattedShortcut.tsx +56 -0
  48. package/src/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.tsx +283 -0
  49. package/src/components/keyboard-shortcuts-modal/keyboard-shortcuts.ts +304 -0
  50. package/src/components/keywords/AddKeywordInline.tsx +284 -0
  51. package/src/components/keywords/DeleteKeywordDialog.tsx +52 -0
  52. package/src/components/modal-drawer/GenericDrawer.tsx +65 -0
  53. package/src/components/modal-drawer/GenericDrawerGroup.tsx +125 -0
  54. package/src/components/outline/DraggableTree.tsx +438 -0
  55. package/src/components/outline/ManuscriptOutline.tsx +127 -0
  56. package/src/components/outline/Outline.tsx +141 -0
  57. package/src/components/references/BibliographyItemSource.ts +80 -0
  58. package/src/components/references/CitationEditor.tsx +301 -0
  59. package/src/components/references/CitationViewer.tsx +62 -0
  60. package/src/components/references/ImportBibliographyForm.tsx +232 -0
  61. package/src/components/references/ImportBibliographyModal.tsx +159 -0
  62. package/src/components/references/ReferenceForm/PersonDropDown.tsx +93 -0
  63. package/src/components/references/ReferenceForm/ReferenceForm.tsx +701 -0
  64. package/src/components/references/ReferenceForm/config.ts +133 -0
  65. package/src/components/references/ReferenceForm/styled-components.ts +156 -0
  66. package/src/components/references/ReferenceLine.tsx +43 -0
  67. package/src/components/references/ReferenceSearch.tsx +163 -0
  68. package/src/components/references/ReferenceSearchResults.tsx +113 -0
  69. package/src/components/references/ReferenceSearchResultsPlaceholder.tsx +71 -0
  70. package/src/components/references/ReferenceSearchSection.tsx +124 -0
  71. package/src/components/references/ReferencesEditor.tsx +61 -0
  72. package/src/components/references/ReferencesModal.tsx +352 -0
  73. package/src/components/references/SearchInput.tsx +98 -0
  74. package/src/components/toolbar/InsertEmbedDialog.tsx +222 -0
  75. package/src/components/toolbar/InsertTableDialog.tsx +208 -0
  76. package/src/components/toolbar/ListMenuItem.tsx +203 -0
  77. package/src/components/toolbar/helpers.ts +547 -0
  78. package/src/components/toolbar/type-selector/OptionComponent.tsx +89 -0
  79. package/src/components/toolbar/type-selector/TypeSelector.tsx +157 -0
  80. package/src/components/toolbar/type-selector/styles.ts +79 -0
  81. package/src/components/views/CrossReferenceItems.tsx +199 -0
  82. package/src/components/views/DeleteEmbedDialog.tsx +83 -0
  83. package/src/components/views/DeleteFootnoteDialog.tsx +54 -0
  84. package/src/components/views/FigureDropdown.tsx +423 -0
  85. package/src/components/views/FootnotesSelector.tsx +255 -0
  86. package/src/components/views/InsertSpecialCharacter.tsx +245 -0
  87. package/src/components/views/LinkForm.tsx +220 -0
  88. package/src/components/views/TableCellContextMenu.tsx +241 -0
  89. package/src/configs/ManuscriptsEditor.ts +140 -0
  90. package/src/configs/editor-plugins.ts +119 -0
  91. package/src/configs/editor-views.ts +139 -0
  92. package/src/icons.ts +62 -0
  93. package/src/index.ts +65 -0
  94. package/src/keys/highlight.ts +65 -0
  95. package/src/keys/index.ts +33 -0
  96. package/src/keys/keyword.ts +69 -0
  97. package/src/keys/list.ts +156 -0
  98. package/src/keys/misc.ts +112 -0
  99. package/src/keys/title.ts +221 -0
  100. package/src/lib/__tests__/__fixtures__/doc.json +388 -0
  101. package/src/lib/__tests__/__snapshots__/paste.test.ts.snap +24 -0
  102. package/src/lib/__tests__/authors.test.ts +202 -0
  103. package/src/lib/__tests__/helpers.test.ts +46 -0
  104. package/src/lib/__tests__/paste.test.ts +57 -0
  105. package/src/lib/__tests__/plugins.test.ts +176 -0
  106. package/src/lib/__tests__/url.test.ts +31 -0
  107. package/src/lib/array-reducer.ts +68 -0
  108. package/src/lib/authors-and-affiliations.ts +81 -0
  109. package/src/lib/authors.ts +67 -0
  110. package/src/lib/capabilities.ts +55 -0
  111. package/src/lib/codemirror.ts +37 -0
  112. package/src/lib/comments.ts +123 -0
  113. package/src/lib/context-menu.ts +745 -0
  114. package/src/lib/copy.ts +30 -0
  115. package/src/lib/crossref.ts +90 -0
  116. package/src/lib/dnd.ts +29 -0
  117. package/src/lib/doc.ts +167 -0
  118. package/src/lib/dompurify.ts +28 -0
  119. package/src/lib/files.ts +180 -0
  120. package/src/lib/footnotes.ts +83 -0
  121. package/src/lib/get-media-type.ts +105 -0
  122. package/src/lib/helpers.ts +199 -0
  123. package/src/lib/hierarchy.ts +117 -0
  124. package/src/lib/languages.ts +37 -0
  125. package/src/lib/lists.ts +56 -0
  126. package/src/lib/math.ts +111 -0
  127. package/src/lib/media.ts +334 -0
  128. package/src/lib/navigation-utils.ts +235 -0
  129. package/src/lib/normalize.ts +70 -0
  130. package/src/lib/paste.ts +304 -0
  131. package/src/lib/placeholder.ts +32 -0
  132. package/src/lib/platform.ts +18 -0
  133. package/src/lib/plugins.ts +22 -0
  134. package/src/lib/popper.ts +180 -0
  135. package/src/lib/position-menu.ts +184 -0
  136. package/src/lib/references.ts +94 -0
  137. package/src/lib/template.ts +29 -0
  138. package/src/lib/track-changes-utils.ts +34 -0
  139. package/{dist/types/lib/statistics.d.ts → src/lib/url.ts} +11 -8
  140. package/src/lib/use-do-with-debounce.ts +39 -0
  141. package/src/lib/utils.ts +323 -0
  142. package/src/lib/view.ts +100 -0
  143. package/src/menus.tsx +684 -0
  144. package/src/node-type-icons.tsx +83 -0
  145. package/src/plugins/__tests__/__fixtures__/deleted-footnote.json +140 -0
  146. package/src/plugins/__tests__/__fixtures__/inserted-footnote.json +153 -0
  147. package/src/plugins/__tests__/footnotes.test.ts +67 -0
  148. package/src/plugins/accessibility_element.ts +219 -0
  149. package/src/plugins/add-subtitle.ts +102 -0
  150. package/src/plugins/affiliations.ts +136 -0
  151. package/src/plugins/alt-titles.ts +219 -0
  152. package/src/plugins/bibliography.ts +235 -0
  153. package/src/plugins/comments.ts +328 -0
  154. package/src/plugins/cross-references.ts +70 -0
  155. package/src/plugins/detect-inconsistency/detect-inconsistency-utils.ts +102 -0
  156. package/src/plugins/detect-inconsistency/index.ts +55 -0
  157. package/src/plugins/detect-inconsistency/validators.ts +283 -0
  158. package/src/plugins/doi.ts +66 -0
  159. package/src/plugins/editor-props.ts +39 -0
  160. package/src/plugins/elements.ts +60 -0
  161. package/src/plugins/footnotes.ts +298 -0
  162. package/src/plugins/link.ts +53 -0
  163. package/src/plugins/lock-body.ts +136 -0
  164. package/src/plugins/move-node.ts +55 -0
  165. package/src/plugins/objects.ts +125 -0
  166. package/src/plugins/paragraphs.ts +90 -0
  167. package/src/plugins/persist.ts +64 -0
  168. package/src/plugins/persistent-cursor.ts +66 -0
  169. package/src/plugins/placeholder.ts +219 -0
  170. package/src/plugins/prevent-empty.ts +50 -0
  171. package/src/plugins/search-replace/index.ts +157 -0
  172. package/src/plugins/search-replace/lib.ts +95 -0
  173. package/src/plugins/section_category.ts +212 -0
  174. package/src/plugins/section_title/autocompletion.ts +87 -0
  175. package/src/plugins/section_title/index.ts +105 -0
  176. package/src/plugins/selected-suggestion.ts +243 -0
  177. package/src/plugins/tables-cursor-fix.ts +143 -0
  178. package/src/plugins/translations.ts +263 -0
  179. package/src/rules.ts +60 -0
  180. package/src/selection.ts +112 -0
  181. package/src/testing/default-capabilities.ts +48 -0
  182. package/src/testing/default-editor-data.ts +73 -0
  183. package/src/testing/empty-editor-doc.json +62 -0
  184. package/src/testing/polyfill-dom.ts +43 -0
  185. package/src/testing/section-categories.json +161 -0
  186. package/src/testing/setup-editor.ts +55 -0
  187. package/src/tests.ts +42 -0
  188. package/src/toolbar.tsx +259 -0
  189. package/src/types/@citation-js/core.d.ts +16 -0
  190. package/src/types/@citation-js/index.d.ts +16 -0
  191. package/src/types/@citation-js/plugin-bibtex.d.ts +16 -0
  192. package/src/types/@citation-js/plugin-csl.d.ts +16 -0
  193. package/src/types/@citation-js/plugin-doi.d.ts +16 -0
  194. package/src/types/@citation-js/plugin-enw.d.ts +16 -0
  195. package/src/types/@citation-js/plugin-pubmed.d.ts +16 -0
  196. package/src/types/@citation-js/plugin-ris.d.ts +16 -0
  197. package/src/types/citeproc.d.ts +124 -0
  198. package/src/types/css.d.ts +16 -0
  199. package/src/types/json.d.ts +17 -0
  200. package/src/types/prosemirror-dev-tools.d.ts +16 -0
  201. package/src/types/prosemirror-dropcursor.d.ts +16 -0
  202. package/src/types/styled-components.d.ts +23 -0
  203. package/src/types.ts +49 -0
  204. package/src/useEditor.ts +244 -0
  205. package/src/versions.ts +3 -0
  206. package/src/views/ReactSubView.tsx +145 -0
  207. package/src/views/abstracts.ts +46 -0
  208. package/src/views/accessibility_element.ts +86 -0
  209. package/src/views/affiliations.ts +254 -0
  210. package/src/views/alt_title.ts +85 -0
  211. package/src/views/alt_titles_section.ts +97 -0
  212. package/src/views/attachment.ts +160 -0
  213. package/src/views/attachments.ts +72 -0
  214. package/src/views/author_notes.ts +96 -0
  215. package/src/views/award.ts +194 -0
  216. package/src/views/awards.ts +59 -0
  217. package/src/views/base_node_view.ts +85 -0
  218. package/src/views/bibliography_element.ts +313 -0
  219. package/src/views/block_view.ts +100 -0
  220. package/src/views/blockquote_element.ts +26 -0
  221. package/src/views/blockquote_element_editable.ts +24 -0
  222. package/src/views/box_element.ts +33 -0
  223. package/src/views/caption.ts +37 -0
  224. package/src/views/caption_title.ts +33 -0
  225. package/src/views/citation.ts +67 -0
  226. package/src/views/citation_editable.ts +312 -0
  227. package/src/views/contributors.ts +341 -0
  228. package/src/views/creators.ts +82 -0
  229. package/src/views/cross_reference.ts +74 -0
  230. package/src/views/cross_reference_editable.ts +113 -0
  231. package/src/views/editable_block.ts +141 -0
  232. package/src/views/embed.ts +345 -0
  233. package/src/views/empty.ts +26 -0
  234. package/src/views/equation.ts +47 -0
  235. package/src/views/equation_editable.ts +84 -0
  236. package/src/views/equation_element.ts +29 -0
  237. package/src/views/equation_element_editable.ts +21 -0
  238. package/src/views/figure.ts +84 -0
  239. package/src/views/figure_editable.ts +361 -0
  240. package/src/views/figure_element.ts +197 -0
  241. package/src/views/figure_element_editable.ts +21 -0
  242. package/src/views/footnote.ts +236 -0
  243. package/src/views/footnotes_element.ts +54 -0
  244. package/src/views/general_table_footnote.ts +140 -0
  245. package/src/views/hero_image.ts +76 -0
  246. package/src/views/hero_image_editable.ts +21 -0
  247. package/src/views/image_element.ts +373 -0
  248. package/src/views/image_element_editable.ts +21 -0
  249. package/src/views/inline_equation.ts +47 -0
  250. package/src/views/inline_equation_editable.ts +70 -0
  251. package/src/views/inline_footnote.ts +264 -0
  252. package/src/views/inline_footnote_editable.ts +20 -0
  253. package/src/views/keyword.ts +127 -0
  254. package/src/views/keyword_group.ts +109 -0
  255. package/src/views/link.ts +27 -0
  256. package/src/views/link_editable.ts +169 -0
  257. package/src/views/list.ts +46 -0
  258. package/src/views/list_item.ts +26 -0
  259. package/src/views/paragraph.ts +26 -0
  260. package/src/views/paragraph_editable.ts +21 -0
  261. package/src/views/placeholder.ts +35 -0
  262. package/src/views/placeholder_element.ts +38 -0
  263. package/src/views/placeholder_element_editable.ts +21 -0
  264. package/src/views/pullquote_element.ts +106 -0
  265. package/src/views/pullquote_element_editable.ts +24 -0
  266. package/src/views/quote_image_editable.ts +84 -0
  267. package/src/views/section.ts +64 -0
  268. package/src/views/section_label.ts +20 -0
  269. package/src/views/section_title.ts +87 -0
  270. package/src/views/section_title_editable.ts +21 -0
  271. package/src/views/subtitle.ts +42 -0
  272. package/src/views/subtitle_editable.ts +20 -0
  273. package/src/views/subtitles.ts +34 -0
  274. package/src/views/subtitles_editable.ts +21 -0
  275. package/src/views/supplement.ts +236 -0
  276. package/src/views/supplement_editable.ts +20 -0
  277. package/src/views/supplements.ts +65 -0
  278. package/src/views/supplements_editable.ts +21 -0
  279. package/src/views/table_cell.ts +150 -0
  280. package/src/views/table_element.ts +33 -0
  281. package/src/views/table_element_editable.ts +21 -0
  282. package/src/views/table_element_footer.ts +41 -0
  283. package/src/views/title.ts +46 -0
  284. package/src/views/title_editable.ts +20 -0
  285. package/src/views/trans_graphical_abstract.ts +55 -0
  286. package/src/views/trans_graphical_abstract_editable.ts +21 -0
  287. package/src/views/translated_abstract.ts +53 -0
  288. package/src/views/translated_abstract_editable.ts +21 -0
  289. package/dist/cjs/lib/statistics.js +0 -38
@@ -0,0 +1,328 @@
1
+ /*!
2
+ * © 2022 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 {
17
+ CommentNode,
18
+ ManuscriptNode,
19
+ ManuscriptTransaction,
20
+ schema,
21
+ } from '@manuscripts/transform'
22
+ import { Plugin, PluginKey } from 'prosemirror-state'
23
+ import { findChildrenByType, NodeWithPos } from 'prosemirror-utils'
24
+ import { Decoration, DecorationSet, EditorView } from 'prosemirror-view'
25
+
26
+ import {
27
+ Comment,
28
+ CommentAttrs,
29
+ CommentKey,
30
+ CommentRange,
31
+ CommentSelection,
32
+ createCommentMarker,
33
+ getCommentKey,
34
+ HighlightMarkerAttrs,
35
+ InlineComment,
36
+ isReply,
37
+ NodeComment,
38
+ } from '../lib/comments'
39
+
40
+ export const commentsKey = new PluginKey<PluginState>('comments')
41
+ const COMMENT_SELECTION = 'comment-selection'
42
+ const EMPTY_SELECTION = {}
43
+
44
+ export interface PluginState {
45
+ decorations: DecorationSet
46
+ comments: Map<string, Comment>
47
+ commentsByKey: Map<CommentKey, Comment[]>
48
+ selection?: CommentSelection
49
+ }
50
+
51
+ const handleCommentMarkerInteraction = (
52
+ view: EditorView,
53
+ target: HTMLElement | null
54
+ ) => {
55
+ const state = view.state
56
+ const com = commentsKey.getState(state)
57
+ const marker = target?.closest('[data-key]') as HTMLElement
58
+
59
+ // Don't dispatch a transaction if both empty
60
+ if (!marker && !com?.selection) {
61
+ return false
62
+ }
63
+
64
+ const tr = state.tr
65
+ if (marker) {
66
+ const key = marker.dataset.key as CommentKey
67
+ setCommentSelection(tr, key, undefined, false)
68
+ } else {
69
+ clearCommentSelection(tr)
70
+ }
71
+ view.dispatch(tr)
72
+ }
73
+
74
+ /**
75
+ * This plugin creates a icon decoration for both inline and block comment.
76
+ */
77
+ export default () => {
78
+ return new Plugin<PluginState>({
79
+ key: commentsKey,
80
+ state: {
81
+ init: (_, state) => {
82
+ return buildPluginState(state.doc)
83
+ },
84
+ apply: (tr, value) => {
85
+ if (tr.docChanged || tr.getMeta(COMMENT_SELECTION)) {
86
+ const selection = tr.getMeta(COMMENT_SELECTION) || value.selection
87
+ return buildPluginState(
88
+ tr.doc,
89
+ selection === EMPTY_SELECTION ? undefined : selection
90
+ )
91
+ }
92
+ return value
93
+ },
94
+ },
95
+ props: {
96
+ decorations: (state) => commentsKey.getState(state)?.decorations,
97
+ handleClick: (view: EditorView, pos: number, e: MouseEvent) => {
98
+ const target = e.target as HTMLElement
99
+ return handleCommentMarkerInteraction(view, target)
100
+ },
101
+ handleDOMEvents: {
102
+ keydown: (view: EditorView, e: KeyboardEvent) => {
103
+ if (e.key !== 'Enter') {
104
+ return false
105
+ }
106
+ const target = document.activeElement as HTMLElement
107
+ const marker = target?.closest('[data-key]') as HTMLElement | null
108
+ if (!marker) {
109
+ return false
110
+ }
111
+ e.preventDefault()
112
+ handleCommentMarkerInteraction(view, target)
113
+ return true
114
+ },
115
+ },
116
+ },
117
+ })
118
+ }
119
+
120
+ export const setCommentSelection = (
121
+ tr: ManuscriptTransaction,
122
+ key: CommentKey,
123
+ id: string | undefined,
124
+ isNew: boolean
125
+ ) => {
126
+ tr.setMeta(COMMENT_SELECTION, {
127
+ key,
128
+ id,
129
+ isNew,
130
+ })
131
+ tr.setMeta('origin', commentsKey)
132
+ return tr
133
+ }
134
+
135
+ export const clearCommentSelection = (tr: ManuscriptTransaction) => {
136
+ tr.setMeta(COMMENT_SELECTION, EMPTY_SELECTION)
137
+ tr.setMeta('origin', commentsKey)
138
+ return tr
139
+ }
140
+
141
+ const findCommentRanges = (doc: ManuscriptNode) => {
142
+ const nodes = findChildrenByType(doc, schema.nodes.highlight_marker)
143
+
144
+ const ranges = new Map<string, CommentRange>()
145
+
146
+ let start
147
+
148
+ for (const { node, pos } of nodes) {
149
+ const attrs = node.attrs as HighlightMarkerAttrs
150
+ const position = attrs.position
151
+ if (position === 'point') {
152
+ ranges.set(attrs.id, {
153
+ pos: pos + 1,
154
+ size: 0,
155
+ })
156
+ } else if (position === 'start') {
157
+ start = pos + 1
158
+ } else if (position === 'end' && start) {
159
+ ranges.set(attrs.id, {
160
+ pos: start,
161
+ size: pos - start,
162
+ })
163
+ start = undefined
164
+ }
165
+ }
166
+ return ranges
167
+ }
168
+
169
+ const findComments = (doc: ManuscriptNode) => {
170
+ const comments = findChildrenByType(doc, schema.nodes.comment)
171
+
172
+ const commentsByTarget = new Map<string, NodeWithPos[]>()
173
+ for (const comment of comments) {
174
+ const targetID = comment.node.attrs.target
175
+ const list = commentsByTarget.get(targetID) || []
176
+ commentsByTarget.set(targetID, [...list, comment])
177
+ }
178
+
179
+ return commentsByTarget
180
+ }
181
+
182
+ const buildPluginState = (
183
+ doc: ManuscriptNode,
184
+ selection?: CommentSelection
185
+ ) => {
186
+ const commentsByTarget = findComments(doc)
187
+ const ranges = findCommentRanges(doc)
188
+
189
+ const decorations: Decoration[] = []
190
+ const allComments: Comment[] = []
191
+
192
+ doc.descendants((node, pos) => {
193
+ const id = node.attrs.id
194
+ const comments = commentsByTarget.get(id)
195
+ if (!comments || !comments.length) {
196
+ return
197
+ }
198
+
199
+ const target = {
200
+ node,
201
+ pos,
202
+ }
203
+
204
+ const nodeComments: NodeComment[] = []
205
+ const highlightComments: InlineComment[] = []
206
+ const pointComments: InlineComment[] = []
207
+
208
+ comments
209
+ .map((c) => {
210
+ const attrs = c.node.attrs as CommentAttrs
211
+ const id = attrs.id
212
+ const range = ranges.get(id)
213
+ const key = getCommentKey(attrs, range, node)
214
+ return {
215
+ key,
216
+ node: c.node as CommentNode,
217
+ pos: c.pos,
218
+ target,
219
+ range,
220
+ }
221
+ })
222
+ .forEach((c) => {
223
+ allComments.push(c)
224
+ if (!isReply(c)) {
225
+ if (!c.range) {
226
+ nodeComments.push(c)
227
+ } else if (c.range.size) {
228
+ highlightComments.push(c as InlineComment)
229
+ } else {
230
+ pointComments.push(c as InlineComment)
231
+ }
232
+ }
233
+ })
234
+
235
+ for (const comment of highlightComments) {
236
+ const key = comment.key
237
+ const range = comment.range
238
+ decorations.push(createHighlightDecoration(key, range, selection))
239
+ }
240
+ if (pointComments.length) {
241
+ groupByKey(pointComments).forEach((comments, key) => {
242
+ const count = comments.length
243
+ const pos = comments[0].range.pos
244
+ decorations.push(
245
+ createMarkerDecoration(key, 'span', pos, count, selection)
246
+ )
247
+ })
248
+ }
249
+ if (nodeComments.length) {
250
+ const count = nodeComments.length
251
+ const key = nodeComments[0].key
252
+ const tagName = node.isAtom ? 'span' : 'div'
253
+ const dpos = getDecorationPos(node, pos)
254
+ decorations.push(
255
+ createMarkerDecoration(key, tagName, dpos, count, selection)
256
+ )
257
+ }
258
+ })
259
+
260
+ return {
261
+ decorations: DecorationSet.create(doc, decorations),
262
+ comments: new Map(allComments.map((c) => [c.node.attrs.id, c])),
263
+ commentsByKey: groupByKey(allComments),
264
+ selection,
265
+ }
266
+ }
267
+
268
+ const groupByKey = <T extends Comment>(comments: T[]): Map<CommentKey, T[]> => {
269
+ const map = new Map()
270
+ for (const comment of comments) {
271
+ const key = comment.key
272
+ const value = map.get(key) || []
273
+ map.set(key, [...value, comment])
274
+ }
275
+ return map
276
+ }
277
+
278
+ const getDecorationPos = (node: ManuscriptNode, pos: number) => {
279
+ switch (node.type) {
280
+ case schema.nodes.affiliations:
281
+ case schema.nodes.paragraph:
282
+ case schema.nodes.embed:
283
+ case schema.nodes.contributors:
284
+ case schema.nodes.hero_image:
285
+ case schema.nodes.supplements:
286
+ return pos
287
+ case schema.nodes.keywords:
288
+ return pos + 2
289
+ default:
290
+ return pos + 1
291
+ }
292
+ }
293
+
294
+ const createHighlightDecoration = (
295
+ key: CommentKey,
296
+ range: CommentRange,
297
+ selection: CommentSelection | undefined
298
+ ) => {
299
+ const classNames = ['highlight']
300
+ if (key === selection?.key) {
301
+ classNames.push('selected-comment')
302
+ }
303
+ return Decoration.inline(range.pos, range.pos + range.size, {
304
+ nodeName: 'span',
305
+ class: classNames.join(' '),
306
+ 'data-key': key,
307
+ })
308
+ }
309
+
310
+ const createMarkerDecoration = (
311
+ key: CommentKey,
312
+ tagName: string,
313
+ pos: number,
314
+ count: number,
315
+ selection: CommentSelection | undefined
316
+ ) => {
317
+ const isSelected = selection?.key === key
318
+ const toDOM = () => {
319
+ const marker = createCommentMarker(tagName, key, count)
320
+ if (isSelected) {
321
+ marker.classList.add('selected-comment')
322
+ }
323
+ return marker
324
+ }
325
+ return Decoration.widget(pos, toDOM, {
326
+ key: `${key}-${count}-${isSelected}`,
327
+ })
328
+ }
@@ -0,0 +1,70 @@
1
+ /*!
2
+ * © 2024 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
+
17
+ import { schema } from '@manuscripts/transform'
18
+ import { isEqual } from 'lodash'
19
+ import { Node } from 'prosemirror-model'
20
+ import { Plugin } from 'prosemirror-state'
21
+ import { Decoration, DecorationSet } from 'prosemirror-view'
22
+
23
+ import { objectsKey } from './objects'
24
+
25
+ export default () => {
26
+ return new Plugin({
27
+ state: {
28
+ init() {
29
+ // Initialize decorations with an empty set
30
+ return DecorationSet.empty
31
+ },
32
+ apply(tr, oldDecorationSet, oldState, newState) {
33
+ let decoSet = oldDecorationSet
34
+ // Check if document or targets have changed
35
+ const oldTargets = objectsKey.getState(oldState)
36
+ const newTargets = objectsKey.getState(newState)
37
+ if (tr.docChanged && !isEqual(oldTargets, newTargets)) {
38
+ const decorations = createDecorations(tr.doc)
39
+ decoSet = DecorationSet.create(tr.doc, decorations)
40
+ }
41
+
42
+ return decoSet
43
+ },
44
+ },
45
+ props: {
46
+ decorations(state) {
47
+ return this.getState(state)
48
+ },
49
+ },
50
+ })
51
+ }
52
+
53
+ /**
54
+ * Helper function to create decorations for specific node types.
55
+ * @param {Node} doc - The document to scan.
56
+ * @returns {Decoration[]} - An array of decorations.
57
+ */
58
+ function createDecorations(doc: Node): Decoration[] {
59
+ const decorations: Decoration[] = []
60
+ doc.descendants((node, pos) => {
61
+ if (node.type === schema.nodes.cross_reference) {
62
+ decorations.push(
63
+ Decoration.node(pos, pos + node.nodeSize, {
64
+ class: `decorated-${Date.now()}`,
65
+ })
66
+ )
67
+ }
68
+ })
69
+ return decorations
70
+ }
@@ -0,0 +1,102 @@
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
+
17
+ import { ManuscriptEditorState, ManuscriptNode } from '@manuscripts/transform'
18
+ import { NodeSelection } from 'prosemirror-state'
19
+ import { Decoration, DecorationSet } from 'prosemirror-view'
20
+
21
+ import { EditorProps } from '../../configs/ManuscriptsEditor'
22
+ import { affiliationsKey } from '../affiliations'
23
+ import { getBibliographyPluginState } from '../bibliography'
24
+ import { footnotesKey } from '../footnotes'
25
+ import { objectsKey } from '../objects'
26
+ import { ValidatorContext, validators } from './validators'
27
+
28
+ export type Inconsistency = {
29
+ type: 'warning'
30
+ category: 'missing-reference' | 'not-used' | 'empty-content'
31
+ severity: 'error' | 'warning'
32
+ message: string
33
+ nodeDescription: string
34
+ node: ManuscriptNode
35
+ pos: number
36
+ }
37
+
38
+ export type PluginState = {
39
+ decorations: DecorationSet
40
+ inconsistencies: Array<Inconsistency>
41
+ showDecorations: boolean
42
+ }
43
+
44
+ export const createDecoration = (
45
+ node: ManuscriptNode,
46
+ pos: number,
47
+ selectedPos: number | null
48
+ ) => {
49
+ const classNames = ['inconsistency-highlight']
50
+ if (selectedPos === pos) {
51
+ classNames.push('selected-suggestion')
52
+ }
53
+
54
+ return Decoration.node(pos, pos + node.nodeSize, {
55
+ class: classNames.join(' '),
56
+ 'data-inconsistency-type': 'warning',
57
+ })
58
+ }
59
+
60
+ export const buildPluginState = (
61
+ state: ManuscriptEditorState,
62
+ props: EditorProps,
63
+ showDecorations: boolean
64
+ ): PluginState => {
65
+ const inconsistencies: Inconsistency[] = []
66
+ const decorations: Decoration[] = []
67
+
68
+ const selection = state.selection
69
+ let selectedPos: number | null = null
70
+
71
+ if (selection instanceof NodeSelection) {
72
+ selectedPos = selection.from
73
+ }
74
+
75
+ const context: ValidatorContext = {
76
+ pluginStates: {
77
+ affiliations: affiliationsKey.getState(state)?.indexedAffiliationIds,
78
+ bibliography: getBibliographyPluginState(state)?.bibliographyItems,
79
+ objects: objectsKey.getState(state),
80
+ footnotes: footnotesKey.getState(state)?.footnotesElementIDs,
81
+ footnotesElements: footnotesKey.getState(state)?.footnotesElements,
82
+ },
83
+ showDecorations,
84
+ selectedPos,
85
+ decorations,
86
+ props,
87
+ doc: state.doc,
88
+ }
89
+
90
+ state.doc.descendants((node, pos) => {
91
+ const validator = validators[node.type.name]
92
+ if (validator) {
93
+ inconsistencies.push(...validator(node, pos, context))
94
+ }
95
+ })
96
+
97
+ return {
98
+ decorations: DecorationSet.create(state.doc, decorations),
99
+ inconsistencies,
100
+ showDecorations,
101
+ }
102
+ }
@@ -0,0 +1,55 @@
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
+
17
+ // This plugin is used to detect inconsistencies in the document.
18
+ // The plugin will check for cross-references, footnotes, table-footnotes, and citations.
19
+ // If one of these doesn't have a linked node, it will add a warning to the node.
20
+
21
+ import { Plugin, PluginKey } from 'prosemirror-state'
22
+ import { DecorationSet } from 'prosemirror-view'
23
+
24
+ import { EditorProps } from '../../configs/ManuscriptsEditor'
25
+ import { buildPluginState, PluginState } from './detect-inconsistency-utils'
26
+
27
+ export { Inconsistency } from './detect-inconsistency-utils'
28
+
29
+ export const detectInconsistencyKey = new PluginKey<PluginState>(
30
+ 'detectInconsistency'
31
+ )
32
+
33
+ export default (props: EditorProps) => {
34
+ return new Plugin<PluginState>({
35
+ key: detectInconsistencyKey,
36
+ state: {
37
+ init: (_, state) => buildPluginState(state, props, false),
38
+ apply: (tr, value, newState) => {
39
+ const showDecorations =
40
+ tr.getMeta(detectInconsistencyKey) !== undefined
41
+ ? tr.getMeta(detectInconsistencyKey)
42
+ : value.showDecorations
43
+ return buildPluginState(newState, props, showDecorations)
44
+ },
45
+ },
46
+ props: {
47
+ decorations: (state) => {
48
+ const pluginState = detectInconsistencyKey.getState(state)
49
+ return pluginState?.showDecorations
50
+ ? pluginState.decorations
51
+ : DecorationSet.empty
52
+ },
53
+ },
54
+ })
55
+ }