@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,298 @@
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 { skipTracking } from '@manuscripts/track-changes-plugin'
18
+ import {
19
+ FootnoteNode,
20
+ FootnotesElementNode,
21
+ generateAlphaFootnoteLabel,
22
+ InlineFootnoteNode,
23
+ isTableElementNode,
24
+ ManuscriptNode,
25
+ schema,
26
+ } from '@manuscripts/transform'
27
+ import { isEqual } from 'lodash'
28
+ import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'
29
+ import { findChildrenByType } from 'prosemirror-utils'
30
+ import { Decoration, DecorationSet } from 'prosemirror-view'
31
+
32
+ import { EditorProps } from '../configs/ManuscriptsEditor'
33
+ import { findNodeByID } from '../lib/doc'
34
+ import {
35
+ findFootnotesContainerNode,
36
+ findParentFootnote,
37
+ } from '../lib/footnotes'
38
+ import { isInit } from '../lib/plugins'
39
+
40
+ /**
41
+ * The state for a single footnotes_element.
42
+ */
43
+ export type FootnotesElementState = {
44
+ /**
45
+ * A list of inline_footnote nodes that reference footnote nodes in
46
+ * this element.
47
+ */
48
+ inlineFootnotes: [InlineFootnoteNode, number][]
49
+ /**
50
+ * A set of footnote node IDs that are not referenced from any
51
+ * inline_footnote node.
52
+ */
53
+ unusedFootnoteIDs: Set<string>
54
+ /**
55
+ * A list of footnote nodes, in reference order. i.e. footnote nodes
56
+ * that are referenced earlier appear earlier in the list.
57
+ */
58
+ footnotes: [FootnoteNode, number][]
59
+ element: [FootnotesElementNode, number]
60
+ /**
61
+ * footnote node ID => label
62
+ */
63
+ labels: Map<string, string>
64
+ }
65
+
66
+ export type FootnotesPluginState = {
67
+ /**
68
+ * footnote_element ID => state
69
+ */
70
+ footnotesElements: Map<string, FootnotesElementState>
71
+ /**
72
+ * Tracks the footnote_element node ID corresponding to a few important
73
+ * node IDs. The node IDs tracked are:
74
+ * - inline_footnote node IDs
75
+ * - footnote node IDs
76
+ * - table_element node IDs
77
+ */
78
+ footnotesElementIDs: Map<string, string>
79
+ }
80
+
81
+ export const footnotesKey = new PluginKey<FootnotesPluginState>('footnotes')
82
+
83
+ /**
84
+ * This plugin provides support of footnotes related behaviours
85
+ */
86
+ const buildPluginState = (doc: ManuscriptNode) => {
87
+ const states = new Map()
88
+ const ids = new Map()
89
+ const elements = findChildrenByType(doc, schema.nodes.footnotes_element)
90
+ elements.map(({ node, pos }) => {
91
+ const container = findFootnotesContainerNode(doc, pos)
92
+ const newState = buildFootnotesElementState(
93
+ [container.node, container.pos],
94
+ [node as FootnotesElementNode, pos]
95
+ )
96
+ const elementID = node.attrs.id
97
+ states.set(elementID, newState)
98
+ newState.footnotes.forEach(([n]) => ids.set(n.attrs.id, elementID))
99
+ newState.inlineFootnotes.forEach(([n]) => ids.set(n.attrs.id, elementID))
100
+ ids.set(container.node.attrs.id, elementID)
101
+ })
102
+ return {
103
+ footnotesElements: states,
104
+ footnotesElementIDs: ids,
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Build the state for a single footnotes_element node.
110
+ */
111
+ const buildFootnotesElementState = (
112
+ container: [ManuscriptNode, number],
113
+ element: [FootnotesElementNode, number]
114
+ ): FootnotesElementState => {
115
+ const fn: FootnotesElementState = {
116
+ element,
117
+ inlineFootnotes: [],
118
+ unusedFootnoteIDs: new Set(),
119
+ footnotes: [],
120
+ labels: new Map(),
121
+ }
122
+
123
+ // process footnote nodes
124
+ const footnotes = findChildrenByType(element[0], schema.nodes.footnote)
125
+ const footnoteIDs = new Set()
126
+ footnotes.forEach(({ node, pos }) => {
127
+ fn.footnotes.push([node as FootnoteNode, element[1] + pos + 1])
128
+ // this set will start with every footnote node ID. Used IDs
129
+ // will be removed from it while processing inline_footnote nodes.
130
+ fn.unusedFootnoteIDs.add(node.attrs.id)
131
+ footnoteIDs.add(node.attrs.id)
132
+ })
133
+
134
+ // process inline_footnote nodes
135
+ let index = 0
136
+ const inlineFootnotes = findChildrenByType(
137
+ container[0],
138
+ schema.nodes.inline_footnote
139
+ )
140
+ inlineFootnotes.sort((a, b) => a.pos - b.pos)
141
+ // this is used to track footnote node IDs based on the order of
142
+ // the corresponding inline_footnote nodes. It will be used later to
143
+ // properly generate labels for footnotes, and to order the footnotes
144
+ // in the state object.
145
+ const orderedFootnoteIDs: string[] = []
146
+ inlineFootnotes.forEach(({ node, pos }) => {
147
+ const inlineFootnote = node as InlineFootnoteNode
148
+ const rids = inlineFootnote.attrs.rids.filter((rid) => footnoteIDs.has(rid))
149
+ if (!rids.length) {
150
+ return
151
+ }
152
+ if (container[1]) {
153
+ pos += container[1] + 1
154
+ }
155
+ fn.inlineFootnotes.push([node as InlineFootnoteNode, pos])
156
+ const label = rids
157
+ .map((rid) => {
158
+ //this is to ensure footnotes are processed only once
159
+ if (fn.labels.has(rid)) {
160
+ return fn.labels.get(rid)
161
+ } else {
162
+ const label = isTableElementNode(container[0])
163
+ ? String(++index)
164
+ : generateAlphaFootnoteLabel(index++)
165
+ fn.labels.set(rid, label)
166
+ fn.unusedFootnoteIDs.delete(rid)
167
+ orderedFootnoteIDs.push(rid)
168
+ return label
169
+ }
170
+ })
171
+ .join(', ')
172
+ fn.labels.set(node.attrs.id, label)
173
+ })
174
+ // At this point, the set includes only footnote node IDs that have no
175
+ // corresponding inline_footnote. These should be added to the end
176
+ // of the footnote node list.
177
+ fn.unusedFootnoteIDs.forEach((id) => orderedFootnoteIDs.push(id))
178
+ // Now the footnote list is sorted based on the appearance of the
179
+ // corresponding inline_footnote nodes.
180
+ fn.footnotes.sort(
181
+ ([a], [b]) =>
182
+ orderedFootnoteIDs.indexOf(a.attrs.id) -
183
+ orderedFootnoteIDs.indexOf(b.attrs.id)
184
+ )
185
+
186
+ return fn
187
+ }
188
+
189
+ const hasChanged = (
190
+ $new: FootnotesElementState,
191
+ $old?: FootnotesElementState
192
+ ) => {
193
+ const nids = $new.footnotes.map(([node]) => node.attrs.id)
194
+ const oids = $old?.footnotes.map(([node]) => node.attrs.id)
195
+ return !(
196
+ isEqual(nids, oids) &&
197
+ isEqual($new.unusedFootnoteIDs, $old?.unusedFootnoteIDs)
198
+ )
199
+ }
200
+
201
+ export default (props: EditorProps) => {
202
+ return new Plugin<FootnotesPluginState>({
203
+ key: footnotesKey,
204
+ state: {
205
+ init(config, instance): FootnotesPluginState {
206
+ return buildPluginState(instance.doc)
207
+ },
208
+ apply(tr, value, oldState, newState): FootnotesPluginState {
209
+ const $old = footnotesKey.getState(oldState)
210
+ if (!tr.docChanged && $old) {
211
+ return $old
212
+ }
213
+ return buildPluginState(newState.doc)
214
+ },
215
+ },
216
+ appendTransaction(transactions, oldState, newState) {
217
+ if (!transactions.find((tr) => tr.docChanged || isInit(tr))) {
218
+ return
219
+ }
220
+ const $old = footnotesKey.getState(oldState)
221
+ const $new = footnotesKey.getState(newState)
222
+ if (!$new) {
223
+ return
224
+ }
225
+
226
+ const tr = newState.tr
227
+
228
+ $new.footnotesElements.forEach((newState, key) => {
229
+ const element = newState.element[0]
230
+ const pos = newState.element[1]
231
+ const oldState = $old?.footnotesElements.get(key)
232
+ const footnotes = newState.footnotes.map(([node]) => node)
233
+
234
+ if (hasChanged(newState, oldState)) {
235
+ const newElement = schema.nodes.footnotes_element.create(
236
+ element.attrs,
237
+ // footnotes here is already in the correct order.
238
+ footnotes
239
+ )
240
+ tr.replaceWith(pos, pos + element.nodeSize, newElement)
241
+ }
242
+ })
243
+
244
+ if (!tr.steps.length) {
245
+ return
246
+ }
247
+
248
+ // this is to ensure that the selection remains valid if it
249
+ // was within a footnotes_element node that was replaced.
250
+ const selection = newState.selection
251
+ const selectedFootnote = findParentFootnote(selection)
252
+ if (selectedFootnote) {
253
+ const pos = findNodeByID(tr.doc, selectedFootnote.node.attrs.id)?.pos
254
+ if (pos) {
255
+ tr.setSelection(TextSelection.create(tr.doc, pos + 2))
256
+ }
257
+ }
258
+
259
+ skipTracking(tr)
260
+ return tr
261
+ },
262
+ props: {
263
+ decorations: (state) => {
264
+ const fns = footnotesKey.getState(state)
265
+ if (!fns) {
266
+ return DecorationSet.empty
267
+ }
268
+
269
+ const decorations: Decoration[] = []
270
+ fns.footnotesElements.forEach((fn) => {
271
+ fn.footnotes.forEach(([node, pos]) => {
272
+ const to = pos + node.nodeSize
273
+ const label = fn.labels.get(node.attrs.id)
274
+ decorations.push(Decoration.node(pos, to, {}, { label }))
275
+ })
276
+ fn.inlineFootnotes.forEach(([node, pos]) => {
277
+ const to = pos + node.nodeSize
278
+ const label = fn.labels.get(node.attrs.id)
279
+ decorations.push(Decoration.node(pos, to, {}, { label }))
280
+ })
281
+ })
282
+ if (props.getCapabilities().editArticle) {
283
+ const footnote = findParentFootnote(state.selection)
284
+ if (footnote) {
285
+ const pos = footnote.pos
286
+ const to = pos + footnote.node.nodeSize
287
+ decorations.push(
288
+ Decoration.node(pos, to, {
289
+ class: 'footnote-selected',
290
+ })
291
+ )
292
+ }
293
+ }
294
+ return DecorationSet.create(state.doc, decorations)
295
+ },
296
+ },
297
+ })
298
+ }
@@ -0,0 +1,53 @@
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 {
17
+ CHANGE_STATUS,
18
+ skipTracking,
19
+ TrackChangesAction,
20
+ } from '@manuscripts/track-changes-plugin'
21
+ import { schema } from '@manuscripts/transform'
22
+ import { NodeSelection, Plugin } from 'prosemirror-state'
23
+
24
+ /**
25
+ * This plugin make sure unselected link with empty href get deleted
26
+ */
27
+ export default () =>
28
+ new Plugin({
29
+ appendTransaction(transactions, oldState, newState) {
30
+ if (
31
+ !transactions.some(
32
+ (tr) =>
33
+ oldState.selection instanceof NodeSelection &&
34
+ oldState.selection.node.type === schema.nodes.link &&
35
+ (tr.getMeta(TrackChangesAction.setChangeStatuses)?.status ===
36
+ CHANGE_STATUS.accepted ||
37
+ (tr.getMeta('pointer') && tr.selectionSet))
38
+ )
39
+ ) {
40
+ return null
41
+ }
42
+ const {
43
+ $from: { pos },
44
+ node,
45
+ } = oldState.selection as NodeSelection
46
+ if (!node.content.size || !node.attrs.href) {
47
+ const tr = newState.tr.setMeta('clear-empty-link', true)
48
+ tr.delete(pos, pos + node.nodeSize)
49
+ return skipTracking(tr.insert(pos, node.content))
50
+ }
51
+ return null
52
+ },
53
+ })
@@ -0,0 +1,136 @@
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 { schema } from '@manuscripts/transform'
18
+ import { Node } from 'prosemirror-model'
19
+ import { Plugin } from 'prosemirror-state'
20
+ import { Decoration, DecorationSet } from 'prosemirror-view'
21
+
22
+ import { lockIcon } from '../icons'
23
+ import { isBodyLocked, isSelectionInBody } from '../lib/utils'
24
+
25
+ /**
26
+ * This plugin prevents editing in the body of the document when it is locked.
27
+ * It also adds a lock icon to indicate that the body is non-editable.
28
+ */
29
+ export default () => {
30
+ return new Plugin({
31
+ state: {
32
+ init(_, state) {
33
+ const decorations = isBodyLocked(state) ? getDecorations(state.doc) : []
34
+ return DecorationSet.create(state.doc, decorations)
35
+ },
36
+ apply(tr, oldDecorationSet, oldState, newState) {
37
+ if (
38
+ tr.docChanged ||
39
+ isBodyLocked(oldState) !== isBodyLocked(newState)
40
+ ) {
41
+ const decorations = isBodyLocked(newState)
42
+ ? getDecorations(newState.doc)
43
+ : []
44
+ return DecorationSet.create(newState.doc, decorations)
45
+ }
46
+ return oldDecorationSet
47
+ },
48
+ },
49
+ props: {
50
+ decorations(state) {
51
+ return this.getState(state) || DecorationSet.empty
52
+ },
53
+ handleDOMEvents: {
54
+ beforeinput(view, event) {
55
+ if (isSelectionInBody(view.state) && isBodyLocked(view.state)) {
56
+ event.preventDefault()
57
+ return true
58
+ }
59
+ return false
60
+ },
61
+ keydown(view, event) {
62
+ if (isSelectionInBody(view.state) && isBodyLocked(view.state)) {
63
+ event.preventDefault()
64
+ return true
65
+ }
66
+ return false
67
+ },
68
+ paste(view, event) {
69
+ if (isSelectionInBody(view.state) && isBodyLocked(view.state)) {
70
+ event.preventDefault()
71
+ return true
72
+ }
73
+ return false
74
+ },
75
+ },
76
+ handleClickOn: (view, pos, node, nodePos, event) => {
77
+ if (isSelectionInBody(view.state) && isBodyLocked(view.state)) {
78
+ event.preventDefault()
79
+ return true
80
+ }
81
+ return false
82
+ },
83
+ handleTripleClickOn: (view, pos, node, nodePos, event) => {
84
+ event.preventDefault()
85
+ return true
86
+ },
87
+ },
88
+ })
89
+ }
90
+
91
+ const getDecorations = (doc: Node): Decoration[] => {
92
+ const decorations: Decoration[] = []
93
+ doc.descendants((node, pos) => {
94
+ if (node.type === schema.nodes.body) {
95
+ decorations.push(
96
+ Decoration.node(pos, pos + node.nodeSize, { class: 'non-editable' })
97
+ )
98
+
99
+ decorations.push(
100
+ Decoration.widget(pos + 1, () => {
101
+ const div = document.createElement('div')
102
+ div.innerHTML = lockIcon
103
+ div.className = 'icon'
104
+ return div
105
+ })
106
+ )
107
+
108
+ decorations.push(
109
+ Decoration.widget(
110
+ pos + 1,
111
+ () => {
112
+ const overlay = document.createElement('div')
113
+ overlay.className = 'body-overlay'
114
+ return overlay
115
+ },
116
+ { side: 1 }
117
+ )
118
+ )
119
+ }
120
+
121
+ if (node.type.name === 'figure') {
122
+ decorations.push(
123
+ Decoration.widget(
124
+ pos + 1,
125
+ () => {
126
+ const overlay = document.createElement('div')
127
+ overlay.className = 'figure-overlay'
128
+ return overlay
129
+ },
130
+ { side: 1 }
131
+ )
132
+ )
133
+ }
134
+ })
135
+ return decorations
136
+ }
@@ -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
+ import {
18
+ CHANGE_OPERATION,
19
+ CHANGE_STATUS,
20
+ TrackedAttrs,
21
+ } from '@manuscripts/track-changes-plugin'
22
+ import { Plugin } from 'prosemirror-state'
23
+ import { Decoration, DecorationSet } from 'prosemirror-view'
24
+
25
+ export default () => {
26
+ return new Plugin({
27
+ props: {
28
+ decorations(state) {
29
+ const decos: Decoration[] = []
30
+
31
+ // Find all nodes that are deleted parts of move operations
32
+ state.doc.descendants((node, pos) => {
33
+ const moveTrack = node.attrs.dataTracked?.find(
34
+ (t: TrackedAttrs) =>
35
+ t.operation === CHANGE_OPERATION.delete &&
36
+ t.status === CHANGE_STATUS.pending &&
37
+ t.moveNodeId // Has an associated move operation
38
+ )
39
+
40
+ if (moveTrack) {
41
+ decos.push(
42
+ Decoration.node(pos, pos + node.nodeSize, {
43
+ class: 'move-deleted-node',
44
+ 'data-move-id': moveTrack.moveNodeId,
45
+ 'data-original-id': node.attrs.id,
46
+ })
47
+ )
48
+ }
49
+ })
50
+
51
+ return DecorationSet.create(state.doc, decos)
52
+ },
53
+ },
54
+ })
55
+ }
@@ -0,0 +1,125 @@
1
+ /*!
2
+ * © 2019 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 {
18
+ buildTargets,
19
+ ManuscriptNode,
20
+ schema,
21
+ Target,
22
+ } from '@manuscripts/transform'
23
+ import { Plugin, PluginKey } from 'prosemirror-state'
24
+ import { Decoration, DecorationSet } from 'prosemirror-view'
25
+
26
+ import { findChildren } from 'prosemirror-utils'
27
+ import { clear } from '@manuscripts/track-changes-plugin'
28
+
29
+ export const objectsKey = new PluginKey<Map<string, Target>>('objects')
30
+
31
+ /**
32
+ * This plugin sets the labels for cross-references, and adds the label as a decoration to cross-referenceable elements.
33
+ */
34
+ export default () => {
35
+ return new Plugin<Map<string, Target>>({
36
+ key: objectsKey,
37
+
38
+ state: {
39
+ init: (config, state) => {
40
+ // Build targets from filtered document to exclude moved nodes
41
+ return buildTargets(clear(state.doc).descendants) // @TODO - make sure binding works ok here - same problem as in transform
42
+ },
43
+ apply: (tr) => {
44
+ // TODO: use decorations to track figure deletion?
45
+ // TODO: map decorations?
46
+ // TODO: use setMeta to update labels
47
+
48
+ // Build targets from filtered document to exclude moved nodes
49
+ return buildTargets(clear(tr.doc).descendants)
50
+ },
51
+ },
52
+ props: {
53
+ decorations: (state) => {
54
+ const decorations: Decoration[] = []
55
+ const targets = objectsKey.getState(state)
56
+
57
+ if (targets) {
58
+ state.doc.descendants((node, pos) => {
59
+ const { id } = node.attrs
60
+ if (id) {
61
+ const target = targets.get(id)
62
+
63
+ if (target) {
64
+ const caption = findChildren(
65
+ node,
66
+ (node) =>
67
+ node.type === schema.nodes.caption ||
68
+ node.type === schema.nodes.caption_title,
69
+ false
70
+ )[0]
71
+
72
+ if (caption) {
73
+ let from: number
74
+ let to: number
75
+ if (caption.node.type === schema.nodes.caption_title) {
76
+ from = pos + 1 + caption.pos
77
+ to = from + caption.node.nodeSize
78
+ } else {
79
+ // caption node: decorate its first child (text_block) so the label
80
+ // appears inline before the caption text
81
+ const firstChild = caption.node.firstChild
82
+ from = pos + 1 + caption.pos + 1
83
+ to = from + (firstChild?.nodeSize || caption.node.nodeSize)
84
+ }
85
+
86
+ decorations.push(
87
+ Decoration.node(from, to, {
88
+ 'element-label': target.label,
89
+ })
90
+ )
91
+ } else {
92
+ // No caption at all — fall back to a widget label at the end of the node
93
+ const labelPos = getDecorationPos(target, state.doc, pos)
94
+ const labelNode = document.createElement('span')
95
+ labelNode.className = 'element-label'
96
+ labelNode.textContent = target.label
97
+ decorations.push(
98
+ Decoration.widget(labelPos, labelNode, {
99
+ side: -1,
100
+ key: `element-label-${id}-${target.label}`,
101
+ })
102
+ )
103
+ }
104
+ }
105
+ }
106
+ })
107
+ }
108
+ return DecorationSet.create(state.doc, decorations)
109
+ },
110
+ },
111
+ })
112
+ }
113
+
114
+ /**
115
+ * Returns the position for a widget decoration when there is no caption.
116
+ * Falls back to the end of the node.
117
+ */
118
+ const getDecorationPos = (
119
+ target: Target,
120
+ doc: ManuscriptNode,
121
+ pos: number
122
+ ) => {
123
+ const $pos = doc.resolve(pos + 1)
124
+ return $pos.end()
125
+ }