@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,95 @@
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 { isDeleted, isDeletedText } from '@manuscripts/track-changes-plugin'
18
+ import { Node as ProseMirrorNode } from 'prosemirror-model'
19
+
20
+ export function getNewMatch(
21
+ side: 'left' | 'right',
22
+ current: number,
23
+ selection: { from: number; to: number },
24
+ matches: { from: number; to: number }[]
25
+ ) {
26
+ if (current < 0) {
27
+ // it means we need to recalc againt the new pointer
28
+ return getClosestMatch(side, selection, matches)
29
+ }
30
+
31
+ let newMatch = 0
32
+ if (side == 'left') {
33
+ newMatch = current - 1 >= 0 ? current - 1 : matches.length - 1
34
+ }
35
+ if (side == 'right') {
36
+ newMatch = current + 1 < matches.length ? current + 1 : 0
37
+ }
38
+ return newMatch
39
+ }
40
+
41
+ export function getClosestMatch(
42
+ side: 'left' | 'right',
43
+ selection: { from: number; to: number },
44
+ matches: { from: number; to: number }[]
45
+ ) {
46
+ for (let i = 0; i < matches.length; i++) {
47
+ const match = matches[i]
48
+ if (match.from >= selection.from) {
49
+ return side == 'right' ? i : Math.max(0, i - 1)
50
+ }
51
+ }
52
+ return 0
53
+ }
54
+
55
+ function removeDiacritics(str: string) {
56
+ return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
57
+ }
58
+
59
+ export function getMatches(
60
+ doc: ProseMirrorNode,
61
+ value: string,
62
+ caseSensitive: boolean,
63
+ ignoreDiacritics: boolean
64
+ ) {
65
+ if (!value) {
66
+ return []
67
+ }
68
+ let normalised = caseSensitive ? value : value.toLocaleLowerCase()
69
+ if (ignoreDiacritics) {
70
+ normalised = removeDiacritics(normalised)
71
+ }
72
+ const matches: Array<{ from: number; to: number }> = []
73
+
74
+ doc.descendants((node, pos) => {
75
+ if (isDeleted(node) || isDeletedText(node)) {
76
+ return
77
+ }
78
+ if (node.isText && node.text) {
79
+ let base = caseSensitive ? node.text : node.text.toLocaleLowerCase()
80
+ if (ignoreDiacritics) {
81
+ base = removeDiacritics(base)
82
+ }
83
+ let index = base.indexOf(normalised)
84
+ while (index !== -1) {
85
+ matches.push({
86
+ from: pos + index,
87
+ to: pos + index + normalised.length,
88
+ })
89
+ index = base.indexOf(normalised, index + normalised.length)
90
+ }
91
+ }
92
+ })
93
+
94
+ return matches
95
+ }
@@ -0,0 +1,212 @@
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
+ import {
17
+ getGroupCategories,
18
+ isSectionNode,
19
+ schema,
20
+ SectionCategory,
21
+ } from '@manuscripts/transform'
22
+ import { ResolvedPos } from 'prosemirror-model'
23
+ import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
24
+ import {
25
+ findChildrenByType,
26
+ findParentNodeOfTypeClosestToPos,
27
+ } from 'prosemirror-utils'
28
+ import { Decoration, DecorationSet, EditorView } from 'prosemirror-view'
29
+
30
+ import { EditorProps } from '../configs/ManuscriptsEditor'
31
+ import { sectionCategoryIcon } from '../icons'
32
+
33
+ export const sectionCategoryKey = new PluginKey<PluginState>('section-category')
34
+
35
+ export interface PluginState {
36
+ decorations: DecorationSet
37
+ }
38
+
39
+ export default (props: EditorProps) =>
40
+ new Plugin<PluginState>({
41
+ key: sectionCategoryKey,
42
+ state: {
43
+ init: (_, state) => buildPluginState(state, props),
44
+ apply: (tr, value, oldState, newState) => {
45
+ if (!tr.docChanged) {
46
+ return value
47
+ }
48
+ return buildPluginState(newState, props)
49
+ },
50
+ },
51
+ props: {
52
+ decorations: (state) =>
53
+ sectionCategoryKey.getState(state)?.decorations || DecorationSet.empty,
54
+ },
55
+ })
56
+
57
+ const createMenuItem = (
58
+ props: EditorProps,
59
+ contents: string,
60
+ handler: EventListener,
61
+ isDisabled: boolean,
62
+ isSelected: boolean
63
+ ) => {
64
+ const item = document.createElement('div')
65
+ item.classList.add('menu-item')
66
+ if (isSelected) {
67
+ item.classList.add('selected')
68
+ }
69
+ if (isDisabled) {
70
+ item.classList.add('disabled')
71
+ }
72
+ item.textContent = contents
73
+ item.addEventListener('mousedown', (event) => {
74
+ handler(event)
75
+ props.popper.destroy()
76
+ })
77
+ return item
78
+ }
79
+
80
+ const createMenu = (
81
+ props: EditorProps,
82
+ currentCategory: SectionCategory | undefined,
83
+ categories: SectionCategory[],
84
+ usedCategoryIDs: Set<string>,
85
+ onSelect: (category: SectionCategory) => void
86
+ ) => {
87
+ const menu = document.createElement('div')
88
+ menu.className = 'section-category menu'
89
+ categories.forEach((category) => {
90
+ const item = createMenuItem(
91
+ props,
92
+ category.titles[0],
93
+ () => onSelect(category),
94
+ category.isUnique && usedCategoryIDs.has(category.id),
95
+ currentCategory === category
96
+ )
97
+ menu.appendChild(item)
98
+ })
99
+ return menu
100
+ }
101
+
102
+ const createButton = (
103
+ props: EditorProps,
104
+ view: EditorView,
105
+ pos: number,
106
+ currentCategory: SectionCategory | undefined,
107
+ categories: SectionCategory[],
108
+ usedCategoryIDs: Set<string>,
109
+ canEdit = true,
110
+ disabled: boolean
111
+ ) => {
112
+ const handleSelect = (category: SectionCategory) => {
113
+ const tr = view.state.tr
114
+ tr.setNodeAttribute(pos, 'category', category.id)
115
+ view.dispatch(tr)
116
+ }
117
+ const button = document.createElement('button')
118
+ button.innerHTML = sectionCategoryIcon
119
+ button.classList.add('section-category-button')
120
+ button.setAttribute('aria-label', 'Section categories menu')
121
+ if (currentCategory) {
122
+ button.setAttribute('data-tooltip-content', currentCategory.titles[0])
123
+ button.classList.add('assigned')
124
+ }
125
+ if (disabled) {
126
+ button.classList.add('disabled')
127
+ } else if (canEdit) {
128
+ button.addEventListener('mousedown', () => {
129
+ const menu = createMenu(
130
+ props,
131
+ currentCategory,
132
+ categories,
133
+ usedCategoryIDs,
134
+ handleSelect
135
+ )
136
+ props.popper.show(button, menu, 'bottom-end', false)
137
+ })
138
+ }
139
+
140
+ return button
141
+ }
142
+
143
+ const buildPluginState = (
144
+ state: EditorState,
145
+ props: EditorProps
146
+ ): PluginState => {
147
+ const decorations: Decoration[] = []
148
+ const can = props.getCapabilities()
149
+ const categories = props.sectionCategories
150
+ const usedCategoryIDs = getUsedSectionCategoryIDs(state)
151
+ const canEdit = !!can?.editArticle
152
+
153
+ state.doc.descendants((node, pos) => {
154
+ if (node.type === schema.nodes.box_element) {
155
+ return false
156
+ }
157
+ if (isSectionNode(node)) {
158
+ const categoryID = node.attrs.category
159
+ const category = categories.get(categoryID)
160
+ const $pos = state.doc.resolve(pos)
161
+ const group = getGroup($pos)
162
+ const groupCategories = getGroupCategories(categories, group)
163
+
164
+ const numOptions = groupCategories.length
165
+
166
+ const shouldShow = !!category || (canEdit && numOptions >= 2)
167
+
168
+ if (shouldShow) {
169
+ const isEditable = canEdit && numOptions >= 2
170
+
171
+ decorations.push(
172
+ Decoration.widget(pos + 1, (view) =>
173
+ createButton(
174
+ props,
175
+ view,
176
+ pos,
177
+ category,
178
+ groupCategories,
179
+ usedCategoryIDs,
180
+ isEditable,
181
+ categories.size === 0
182
+ )
183
+ )
184
+ )
185
+ }
186
+ return false
187
+ }
188
+ })
189
+
190
+ return { decorations: DecorationSet.create(state.doc, decorations) }
191
+ }
192
+
193
+ const getUsedSectionCategoryIDs = (state: EditorState): Set<string> => {
194
+ const sections = findChildrenByType(state.doc, schema.nodes.section)
195
+ const used = new Set<string>()
196
+ sections.forEach(({ node }) => {
197
+ if (node.attrs.category) {
198
+ used.add(node.attrs.category)
199
+ }
200
+ })
201
+ return used
202
+ }
203
+
204
+ const getGroup = ($pos: ResolvedPos) => {
205
+ if (findParentNodeOfTypeClosestToPos($pos, schema.nodes.abstracts)) {
206
+ return 'abstracts'
207
+ }
208
+ if (findParentNodeOfTypeClosestToPos($pos, schema.nodes.backmatter)) {
209
+ return 'backmatter'
210
+ }
211
+ return 'body'
212
+ }
@@ -0,0 +1,87 @@
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
+ import { ManuscriptNode, schema, SectionCategory } from '@manuscripts/transform'
17
+ import { EditorState } from 'prosemirror-state'
18
+ import { findParentNodeOfTypeClosestToPos } from 'prosemirror-utils'
19
+
20
+ import { getEditorProps } from '../editor-props'
21
+ import { getActualTextContent } from '@manuscripts/track-changes-plugin'
22
+
23
+ function cursorAtTheEndOfText(
24
+ state: EditorState,
25
+ nodeSize: number,
26
+ nodePos: number
27
+ ) {
28
+ const { from, to } = state.selection
29
+ return from === to && to === nodePos + nodeSize - 1
30
+ }
31
+
32
+ const isUpperCase = (test: string) =>
33
+ test === test.toUpperCase() && test.length > 1
34
+
35
+ export function hasAutoCompletionSlack(
36
+ node: ManuscriptNode,
37
+ category: SectionCategory
38
+ ) {
39
+ const titles = category.titles
40
+
41
+ if (titles.length && node.textContent) {
42
+ const actualTextContent = getActualTextContent(node.content)
43
+ const title = titles.find((t) =>
44
+ t.toLowerCase().startsWith(actualTextContent.toLowerCase())
45
+ )
46
+
47
+ if (title) {
48
+ const suggestionPart = title.slice(actualTextContent.length)
49
+ const suggestion = isUpperCase(actualTextContent)
50
+ ? suggestionPart.toUpperCase()
51
+ : suggestionPart
52
+ return {
53
+ suggestion,
54
+ title: isUpperCase(actualTextContent) ? title.toUpperCase() : title,
55
+ }
56
+ }
57
+ }
58
+ return null
59
+ }
60
+
61
+ export function checkForCompletion(state: EditorState) {
62
+ const section = findParentNodeOfTypeClosestToPos(
63
+ state.selection.$from,
64
+ schema.nodes.section
65
+ )
66
+ if (!section) {
67
+ return
68
+ }
69
+
70
+ const title = findParentNodeOfTypeClosestToPos(
71
+ state.selection.$from,
72
+ schema.nodes.section_title
73
+ )
74
+ if (!title || !cursorAtTheEndOfText(state, title.node.nodeSize, title.pos)) {
75
+ return
76
+ }
77
+
78
+ const props = getEditorProps(state)
79
+ const category = props.sectionCategories.get(section.node.attrs.category)
80
+
81
+ if (category) {
82
+ const text = hasAutoCompletionSlack(title.node, category)
83
+ if (text) {
84
+ return text
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,105 @@
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
+ import { schema } from '@manuscripts/transform'
17
+ import { Node as ProseMirrorNode } from 'prosemirror-model'
18
+ import { EditorState, Plugin, PluginKey, Transaction } from 'prosemirror-state'
19
+ import { findChildrenByType } from 'prosemirror-utils'
20
+ import { Decoration, DecorationSet } from 'prosemirror-view'
21
+
22
+ import { checkForCompletion } from './autocompletion'
23
+ import { clear } from '@manuscripts/track-changes-plugin'
24
+
25
+ type NumberingArray = number[]
26
+ export type PluginState = Map<string, string>
27
+
28
+ export const sectionTitleKey = new PluginKey<PluginState>('sectionNumbering')
29
+
30
+ const calculateSectionLevels = (
31
+ node: ProseMirrorNode,
32
+ startPos: number,
33
+ sectionNumberMap: Map<string, string>,
34
+ numbering: NumberingArray = [0]
35
+ ) => {
36
+ clear(node).forEach((childNode, offset) => {
37
+ if (
38
+ childNode.type === schema.nodes.section ||
39
+ childNode.type === schema.nodes.box_element
40
+ ) {
41
+ numbering[numbering.length - 1] += 1
42
+ const sectionNumber = numbering.join('.')
43
+ const sectionStartPos = startPos + offset + 1
44
+
45
+ childNode.forEach((innerChildNode) => {
46
+ if (innerChildNode.type === schema.nodes.section_title) {
47
+ sectionNumberMap.set(childNode.attrs.id, sectionNumber)
48
+ }
49
+ })
50
+
51
+ // Process child nodes to handle subsections
52
+ calculateSectionLevels(childNode, sectionStartPos, sectionNumberMap, [
53
+ ...numbering,
54
+ 0,
55
+ ])
56
+ }
57
+ })
58
+ }
59
+
60
+ const getPluginState = (doc: ProseMirrorNode): PluginState => {
61
+ const bodyNodes = findChildrenByType(doc, schema.nodes.body)
62
+ const bodyNode = bodyNodes[0]
63
+ const sectionNumberMap = new Map<string, string>()
64
+ calculateSectionLevels(bodyNode.node, bodyNode.pos, sectionNumberMap)
65
+
66
+ return sectionNumberMap
67
+ }
68
+
69
+ export default () => {
70
+ return new Plugin<PluginState>({
71
+ key: sectionTitleKey,
72
+ props: {
73
+ decorations(state) {
74
+ const text = checkForCompletion(state)
75
+ if (text) {
76
+ const decoration = Decoration.widget(state.selection.from, () => {
77
+ const node = document.createElement('span')
78
+ node.classList.add('completion-bearer')
79
+ node.dataset.suggest = text.suggestion
80
+ return node
81
+ })
82
+ return DecorationSet.create(state.doc, [decoration])
83
+ }
84
+
85
+ return DecorationSet.empty
86
+ },
87
+ },
88
+ state: {
89
+ init: (_, state: EditorState) => {
90
+ return getPluginState(state.doc)
91
+ },
92
+ apply: (
93
+ tr: Transaction,
94
+ oldSectionNumberMap: PluginState,
95
+ oldState: EditorState,
96
+ newState: EditorState
97
+ ) => {
98
+ if (tr.docChanged) {
99
+ return getPluginState(newState.doc)
100
+ }
101
+ return oldSectionNumberMap
102
+ },
103
+ },
104
+ })
105
+ }