@manuscripts/body-editor 3.12.66 → 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,745 @@
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 { TriangleCollapsedIcon } from '@manuscripts/style-guide'
18
+ import {
19
+ isInGraphicalAbstractSection,
20
+ isSectionTitleNode,
21
+ ListNode,
22
+ ManuscriptEditorView,
23
+ ManuscriptNode,
24
+ ManuscriptNodeType,
25
+ nodeNames,
26
+ Nodes,
27
+ schema,
28
+ } from '@manuscripts/transform'
29
+ import { Attrs, ResolvedPos } from 'prosemirror-model'
30
+ import { TextSelection } from 'prosemirror-state'
31
+
32
+ import { handleEnterKey, createKeyboardInteraction } from './navigation-utils'
33
+ import { findChildrenByType } from 'prosemirror-utils'
34
+ import React, { createElement } from 'react'
35
+ import { renderToStaticMarkup } from 'react-dom/server'
36
+
37
+ import {
38
+ addNodeComment,
39
+ createBlock,
40
+ findPosBeforeFirstSubsection,
41
+ insertGeneralTableFootnote,
42
+ insertInlineTableFootnote,
43
+ } from '../commands'
44
+ import { PopperManager } from './popper'
45
+ import { createPositionOptions } from './position-menu'
46
+ import { templateAllows } from './template'
47
+ import {
48
+ getMatchingChild,
49
+ isChildOfNodeTypes,
50
+ isSelectionInNode,
51
+ } from './utils'
52
+
53
+ const popper = new PopperManager()
54
+
55
+ const readonlyTypes = [
56
+ schema.nodes.keywords,
57
+ schema.nodes.bibliography_element,
58
+ schema.nodes.bibliography_section,
59
+ schema.nodes.footnotes_section,
60
+ ]
61
+
62
+ const isBoxElementSectionTitle = ($pos: ResolvedPos, node: ManuscriptNode) =>
63
+ isSectionTitleNode(node) &&
64
+ $pos.node($pos.depth - 1).type === schema.nodes.box_element
65
+
66
+ export const sectionLevel = (depth: number) => {
67
+ switch (depth) {
68
+ case 1:
69
+ return 'Section'
70
+ default:
71
+ return 'Sub' + 'sub'.repeat(depth - 2) + 'section'
72
+ }
73
+ }
74
+
75
+ type InsertableNodes = Nodes | 'subsection'
76
+
77
+ const hasAny = <T>(set: Set<T>, ...items: T[]) => {
78
+ return items.some((i) => set.has(i))
79
+ }
80
+
81
+ export const contextMenuBtnClass = 'btn-context-menu'
82
+ const contextSubmenuBtnClass = 'context-submenu-trigger'
83
+
84
+ export class ContextMenu {
85
+ private readonly node: ManuscriptNode
86
+ private readonly view: ManuscriptEditorView
87
+ private readonly getPos: () => number
88
+ private menuItems: HTMLElement[] = []
89
+
90
+ public constructor(
91
+ node: ManuscriptNode,
92
+ view: ManuscriptEditorView,
93
+ getPos: () => number
94
+ ) {
95
+ this.node = node
96
+ this.view = view
97
+ this.getPos = getPos
98
+ }
99
+
100
+ public showAddMenu = (target: Element) => {
101
+ this.menuItems = []
102
+ const menu = document.createElement('div')
103
+ menu.className = 'menu'
104
+ const $pos = this.resolvePos()
105
+ const insertPos = $pos.after($pos.depth)
106
+ const endPos = $pos.end()
107
+ const types = this.insertableTypes(insertPos, endPos)
108
+
109
+ const insertNode = (
110
+ type: ManuscriptNodeType,
111
+ pos?: number,
112
+ attrs?: Attrs
113
+ ) => {
114
+ const { state, dispatch } = this.view
115
+
116
+ if (pos === undefined) {
117
+ pos = this.getPos() + this.node.nodeSize
118
+ }
119
+
120
+ createBlock(type, pos, state, dispatch, attrs)
121
+ }
122
+
123
+ if (hasAny(types, 'section', 'subsection')) {
124
+ menu.appendChild(
125
+ this.createMenuSection((section: HTMLElement) => {
126
+ const sectionTitle = $pos.node($pos.depth).child(0).textContent
127
+ const itemTitle = sectionTitle
128
+ ? `“${this.trimTitle(sectionTitle, 30)}”`
129
+ : 'This Section'
130
+ if (
131
+ types.has('section') &&
132
+ !isChildOfNodeTypes(this.view.state.doc, $pos.pos, [
133
+ schema.nodes.abstracts,
134
+ schema.nodes.backmatter,
135
+ ])
136
+ ) {
137
+ const labelPosition = 'After'
138
+ const level = sectionLevel($pos.depth - 1)
139
+ const label = `New ${level} ${labelPosition} ${itemTitle}`
140
+
141
+ section.appendChild(
142
+ this.createMenuItem(label, () => {
143
+ insertNode(schema.nodes.section, insertPos)
144
+ popper.destroy()
145
+ })
146
+ )
147
+ }
148
+
149
+ if (types.has('subsection')) {
150
+ const insPos = findPosBeforeFirstSubsection($pos) || endPos
151
+ const level = sectionLevel($pos.depth)
152
+ const label = `New ${level} to ${itemTitle}`
153
+
154
+ section.appendChild(
155
+ this.createMenuItem(label, () => {
156
+ insertNode(schema.nodes.section, insPos)
157
+ popper.destroy()
158
+ })
159
+ )
160
+ }
161
+ })
162
+ )
163
+ }
164
+ if (hasAny(types, 'paragraph', 'list')) {
165
+ menu.appendChild(
166
+ this.createMenuSection((section: HTMLElement) => {
167
+ if (types.has('paragraph')) {
168
+ section.appendChild(
169
+ this.createMenuItem('Paragraph', () => {
170
+ insertNode(schema.nodes.paragraph)
171
+ popper.destroy()
172
+ })
173
+ )
174
+ }
175
+
176
+ if (types.has('list')) {
177
+ section.appendChild(
178
+ this.createMenuItem('Bulleted List', () => {
179
+ insertNode(schema.nodes.list, undefined, {
180
+ listStyleType: 'bullet',
181
+ })
182
+ popper.destroy()
183
+ })
184
+ )
185
+ section.appendChild(
186
+ this.createMenuItem('Ordered List', () => {
187
+ insertNode(schema.nodes.list, undefined, {
188
+ listStyleType: 'order',
189
+ })
190
+ popper.destroy()
191
+ })
192
+ )
193
+ }
194
+ })
195
+ )
196
+ }
197
+
198
+ if (hasAny(types, 'figure_element', 'equation_element')) {
199
+ menu.appendChild(
200
+ this.createMenuSection((section: HTMLElement) => {
201
+ if (types.has('figure_element')) {
202
+ section.appendChild(
203
+ this.createMenuItem('Figure Panel', () => {
204
+ insertNode(schema.nodes.figure_element)
205
+ popper.destroy()
206
+ })
207
+ )
208
+ }
209
+
210
+ if (types.has('equation_element')) {
211
+ section.appendChild(
212
+ this.createMenuItem('Equation', () => {
213
+ insertNode(schema.nodes.equation_element)
214
+ popper.destroy()
215
+ })
216
+ )
217
+ }
218
+ })
219
+ )
220
+ }
221
+
222
+ if (hasAny(types, 'blockquote_element')) {
223
+ menu.appendChild(
224
+ this.createMenuSection((section: HTMLElement) => {
225
+ if (types.has('blockquote_element')) {
226
+ section.appendChild(
227
+ this.createMenuItem('Block Quote', () => {
228
+ insertNode(schema.nodes.blockquote_element)
229
+ popper.destroy()
230
+ })
231
+ )
232
+ }
233
+ })
234
+ )
235
+ }
236
+
237
+ popper.show(target, menu, 'right', true)
238
+
239
+ this.addPopperEventListeners()
240
+ }
241
+
242
+ public showEditMenu = (target: Element) => {
243
+ this.menuItems = []
244
+ const menu = document.createElement('div')
245
+ menu.className = 'menu'
246
+
247
+ const $pos = this.resolvePos()
248
+ const isBox = isBoxElementSectionTitle($pos, this.node)
249
+ const type = isBox ? schema.nodes.box_element : this.node.type
250
+
251
+ if (
252
+ type === schema.nodes.figure_element ||
253
+ type === schema.nodes.image_element
254
+ ) {
255
+ const figure = getMatchingChild(
256
+ this.node,
257
+ (node) => node.type === schema.nodes.figure
258
+ )
259
+
260
+ if (figure) {
261
+ const attrType = figure.attrs.type
262
+ const submenuOptions = createPositionOptions(
263
+ schema.nodes.figure,
264
+ figure,
265
+ attrType,
266
+ this.view,
267
+ () => popper.destroy()
268
+ )
269
+ const submenuLabel = 'Position'
270
+ const submenu = this.createSubmenu(submenuLabel, submenuOptions)
271
+ menu.appendChild(submenu)
272
+ }
273
+ }
274
+
275
+ if (type === schema.nodes.list) {
276
+ menu.appendChild(
277
+ this.createMenuSection((section: HTMLElement) => {
278
+ const list = this.node as ListNode
279
+ const type = list.attrs.listStyleType
280
+ if (type === 'simple' || type === 'bullet') {
281
+ section.appendChild(
282
+ this.createMenuItem('Change to Numbered List', () => {
283
+ const tr = this.view.state.tr
284
+ tr.setNodeAttribute(this.getPos(), 'listStyleType', 'order')
285
+ this.view.dispatch(tr)
286
+ popper.destroy()
287
+ })
288
+ )
289
+ } else {
290
+ section.appendChild(
291
+ this.createMenuItem('Change to Bulleted list', () => {
292
+ const tr = this.view.state.tr
293
+ tr.setNodeAttribute(this.getPos(), 'listStyleType', 'bullet')
294
+ this.view.dispatch(tr)
295
+ popper.destroy()
296
+ })
297
+ )
298
+ }
299
+ })
300
+ )
301
+ }
302
+
303
+ if (type === schema.nodes.pullquote_element) {
304
+ const figure = findChildrenByType(this.node, schema.nodes.quote_image)
305
+ if (!figure.length) {
306
+ menu.appendChild(
307
+ this.createMenuItem('Add Image', () => {
308
+ const tr = this.view.state.tr
309
+ tr.insert(this.getPos() + 1, schema.nodes.quote_image.create())
310
+ this.view.dispatch(tr)
311
+ popper.destroy()
312
+ })
313
+ )
314
+ } else {
315
+ const found = figure[0]
316
+ menu.appendChild(
317
+ this.createMenuItem('Remove Image', () => {
318
+ const tr = this.view.state.tr
319
+ tr.delete(
320
+ this.getPos() + 1 + found.pos,
321
+ this.getPos() + 1 + found.pos + found.node.nodeSize
322
+ )
323
+ this.view.dispatch(tr)
324
+ popper.destroy()
325
+ })
326
+ )
327
+ }
328
+ }
329
+
330
+ const commentTarget = this.getCommentTarget()
331
+ menu.appendChild(
332
+ this.createMenuSection((section: HTMLElement) => {
333
+ section.appendChild(
334
+ this.createMenuItem('Comment', () => {
335
+ addNodeComment(commentTarget, this.view.state, this.view.dispatch)
336
+ popper.destroy()
337
+ })
338
+ )
339
+ })
340
+ )
341
+
342
+ if (type === schema.nodes.table_element) {
343
+ const items: Node[] = []
344
+ const isInsideNode = isSelectionInNode(this.view.state, this.node)
345
+ if (isInsideNode && insertInlineTableFootnote(this.view.state)) {
346
+ const item = this.createMenuItem('Add Reference Note', () => {
347
+ insertInlineTableFootnote(this.view.state, this.view.dispatch)
348
+ })
349
+ items.push(item)
350
+ }
351
+ const pos = this.getPos()
352
+ if (insertGeneralTableFootnote([this.node, pos], this.view.state)) {
353
+ const item = this.createMenuItem('Add General Note', () => {
354
+ insertGeneralTableFootnote(
355
+ [this.node, pos],
356
+ this.view.state,
357
+ this.view.dispatch
358
+ )
359
+ })
360
+ items.push(item)
361
+ }
362
+ if (items.length) {
363
+ menu.append(this.createMenuSection((e) => e.append(...items)))
364
+ }
365
+ }
366
+
367
+ if (
368
+ !(
369
+ type === schema.nodes.figure_element &&
370
+ isInGraphicalAbstractSection($pos)
371
+ ) &&
372
+ !readonlyTypes.includes(type) &&
373
+ !readonlyTypes.includes($pos.parent.type)
374
+ ) {
375
+ menu.appendChild(
376
+ this.createMenuSection((section: HTMLElement) => {
377
+ let nodeName = nodeNames.get(type) || ''
378
+ if (type === schema.nodes.section_title) {
379
+ nodeName = nodeNames.get(schema.nodes.section) as string
380
+ }
381
+ section.appendChild(
382
+ this.createMenuItem(`Delete ${nodeName}`, () => {
383
+ this.deleteNode(type)
384
+ popper.destroy()
385
+ })
386
+ )
387
+ })
388
+ )
389
+ }
390
+
391
+ if (type === schema.nodes.box_element) {
392
+ const tr = this.view.state.tr
393
+ const boxElementNode = $pos.node($pos.depth - 1)
394
+ const boxStartPos = $pos.start($pos.depth - 1)
395
+
396
+ const captionTitle = findChildrenByType(
397
+ boxElementNode,
398
+ schema.nodes.caption_title,
399
+ false
400
+ )[0]
401
+ const caption = findChildrenByType(
402
+ boxElementNode,
403
+ schema.nodes.caption,
404
+ false
405
+ )[0]
406
+ const hasLabel = captionTitle
407
+
408
+ menu.insertBefore(
409
+ this.createMenuItem(hasLabel ? 'Delete Label' : 'Add Label', () => {
410
+ if (hasLabel) {
411
+ const captionNode = captionTitle.node
412
+ const captionPos = boxStartPos + captionTitle.pos
413
+
414
+ tr.delete(
415
+ captionPos,
416
+ captionPos + captionNode.nodeSize + (caption?.node.nodeSize || 0)
417
+ )
418
+ } else {
419
+ const captionTitle = schema.nodes.caption_title.create()
420
+ tr.insert(boxStartPos, captionTitle)
421
+ }
422
+
423
+ this.view.dispatch(tr)
424
+ popper.destroy()
425
+ }),
426
+
427
+ menu.firstChild
428
+ )
429
+ }
430
+
431
+ popper.show(target, menu, 'right', true)
432
+
433
+ this.addPopperEventListeners()
434
+ }
435
+
436
+ private createSubmenuTrigger = (contents: string) => {
437
+ const item = document.createElement('div')
438
+ item.className = 'menu-item'
439
+ item.tabIndex = 0
440
+ const textNode = document.createTextNode(contents)
441
+ item.innerHTML = renderToStaticMarkup(createElement(TriangleCollapsedIcon))
442
+ item.prepend(textNode)
443
+ item.classList.add(contextSubmenuBtnClass)
444
+
445
+ item.addEventListener('mousedown', this.toggleSubmenu)
446
+ item.addEventListener(
447
+ 'keydown',
448
+ handleEnterKey((e) => {
449
+ const target = e.target as HTMLElement
450
+ this.toggleSubmenu(e)
451
+ const submenuContent = target.nextElementSibling
452
+ if (submenuContent?.classList.contains('show')) {
453
+ const firstItem = submenuContent.querySelector(
454
+ '.menu-item'
455
+ ) as HTMLElement
456
+ firstItem?.focus()
457
+ }
458
+ })
459
+ )
460
+
461
+ this.menuItems.push(item)
462
+ return item
463
+ }
464
+
465
+ private createMenuItem = (
466
+ contents: string,
467
+ handler: EventListener,
468
+ IconComponent: React.FC | string | null = null,
469
+ selected = false
470
+ ) => {
471
+ const item = document.createElement('div')
472
+ item.className = 'menu-item'
473
+ item.setAttribute('tabindex', '0')
474
+ selected && item.classList.add('selected')
475
+ if (IconComponent) {
476
+ if (typeof IconComponent === 'string') {
477
+ item.innerHTML = IconComponent
478
+ } else {
479
+ item.innerHTML = renderToStaticMarkup(createElement(IconComponent))
480
+ }
481
+ }
482
+ const textNode = document.createTextNode(contents)
483
+ item.appendChild(textNode)
484
+
485
+ item.addEventListener('mousedown', (event) => {
486
+ event.preventDefault()
487
+ handler(event)
488
+ })
489
+
490
+ item.addEventListener('keydown', handleEnterKey(handler))
491
+
492
+ this.menuItems.push(item)
493
+
494
+ return item
495
+ }
496
+
497
+ private createMenuSection = (
498
+ createMenuItems: (section: HTMLElement) => void,
499
+ isSubmenu = false
500
+ ) => {
501
+ const section = document.createElement('div')
502
+ section.className = 'menu-section'
503
+ isSubmenu && section.classList.add('menu')
504
+ createMenuItems(section)
505
+ return section
506
+ }
507
+
508
+ private createSubmenu = (
509
+ submenuLabel: string,
510
+ items: {
511
+ title: string
512
+ action: () => void
513
+ IconComponent: React.FC | string | null
514
+ iconName: string
515
+ selected: boolean
516
+ }[]
517
+ ) => {
518
+ const submenu = document.createElement('div')
519
+ submenu.classList.add('context-submenu')
520
+ submenu.append(
521
+ this.createSubmenuTrigger(submenuLabel),
522
+ this.createMenuSection((section: HTMLElement) => {
523
+ items.forEach(({ title, action, IconComponent, selected }) => {
524
+ section.appendChild(
525
+ this.createMenuItem(title, action, IconComponent, selected)
526
+ )
527
+ })
528
+ }, true)
529
+ )
530
+
531
+ return submenu
532
+ }
533
+
534
+ private insertableTypes = (
535
+ insertPos: number,
536
+ endPos: number
537
+ ): Set<InsertableNodes> => {
538
+ const { nodes } = schema
539
+
540
+ const insertable = new Set<InsertableNodes>()
541
+
542
+ const doc = this.view.state.doc
543
+
544
+ const getPos = (pos?: number) => {
545
+ if (pos === undefined) {
546
+ pos = this.getPos() + this.node.nodeSize
547
+ }
548
+ return pos
549
+ }
550
+
551
+ const canInsertAt = (type: ManuscriptNodeType, pos?: number) => {
552
+ const $pos = doc.resolve(getPos(pos))
553
+ const index = $pos.index()
554
+ return $pos.parent.canReplaceWith(index, index, type)
555
+ }
556
+
557
+ const checkNode = (node: Nodes, pos?: number) => {
558
+ if (!templateAllows(this.view.state, nodes[node])) {
559
+ return
560
+ }
561
+ canInsertAt(nodes[node], pos) && insertable.add(node)
562
+ }
563
+
564
+ if (
565
+ canInsertAt(nodes.section, endPos) &&
566
+ templateAllows(this.view.state, nodes.section)
567
+ ) {
568
+ insertable.add('subsection')
569
+ }
570
+ checkNode('section', insertPos)
571
+ checkNode('paragraph')
572
+ checkNode('list')
573
+ checkNode('figure_element')
574
+ checkNode('table_element')
575
+ checkNode('equation_element')
576
+ checkNode('blockquote_element')
577
+ checkNode('pullquote_element')
578
+
579
+ return insertable
580
+ }
581
+
582
+ private deleteNode = (nodeType: ManuscriptNodeType) => {
583
+ switch (nodeType.name) {
584
+ case 'section_title': {
585
+ const $pos = this.resolvePos()
586
+
587
+ this.view.dispatch(
588
+ this.view.state.tr.delete($pos.before(), $pos.after())
589
+ )
590
+
591
+ break
592
+ }
593
+
594
+ case 'box_element': {
595
+ const $pos = this.resolvePos()
596
+
597
+ this.view.dispatch(
598
+ this.view.state.tr.delete(
599
+ $pos.before($pos.depth - 1),
600
+ $pos.after($pos.depth - 1)
601
+ )
602
+ )
603
+
604
+ break
605
+ }
606
+
607
+ case 'subtitles': {
608
+ const pos = this.getPos()
609
+ const tr = this.view.state.tr
610
+
611
+ tr.delete(pos, pos + this.node.nodeSize)
612
+
613
+ // For subtitles, set selection to title end
614
+ const titleNode = findChildrenByType(tr.doc, schema.nodes.title)[0]
615
+ const titleEndPos = titleNode.pos + titleNode.node.nodeSize - 1
616
+ tr.setSelection(TextSelection.create(tr.doc, titleEndPos))
617
+
618
+ this.view.dispatch(tr)
619
+ break
620
+ }
621
+
622
+ default: {
623
+ const pos = this.getPos()
624
+
625
+ this.view.dispatch(
626
+ this.view.state.tr.delete(pos, pos + this.node.nodeSize)
627
+ )
628
+
629
+ break
630
+ }
631
+ }
632
+ }
633
+
634
+ private resolvePos = () => this.view.state.doc.resolve(this.getPos())
635
+
636
+ private addPopperEventListeners = () => {
637
+ const mouseListener: EventListener = (event) => {
638
+ const target = event.target as HTMLElement
639
+ // if target is one of btn-context-menu buttons, do not destroy popper
640
+ if (
641
+ target.classList.contains(contextMenuBtnClass) ||
642
+ target.classList.contains(contextSubmenuBtnClass)
643
+ ) {
644
+ return
645
+ }
646
+ window.requestAnimationFrame(() => {
647
+ window.removeEventListener('mousedown', mouseListener)
648
+ popper.destroy()
649
+ })
650
+ }
651
+
652
+ window.addEventListener('mousedown', mouseListener)
653
+
654
+ window.requestAnimationFrame(() => {
655
+ const popperContainer = popper.getContainer()
656
+ if (popperContainer) {
657
+ // Attach navigation listener to container - automatically removed when container is removed
658
+ createKeyboardInteraction({
659
+ container: popperContainer,
660
+ navigation: {
661
+ getItems: () => {
662
+ const activeElement = document.activeElement as HTMLElement
663
+ const openSubmenu = activeElement?.closest(
664
+ '.menu-section.menu.show'
665
+ )
666
+ // only return submenu items
667
+ if (openSubmenu) {
668
+ return this.menuItems.filter((item) =>
669
+ openSubmenu.contains(item)
670
+ )
671
+ }
672
+ return this.menuItems.filter((item) => {
673
+ const menuSection = item.closest('.menu-section')
674
+ if (menuSection && menuSection.classList.contains('menu')) {
675
+ return menuSection.classList.contains('show')
676
+ }
677
+ return true
678
+ })
679
+ },
680
+ arrowKeys: {
681
+ forward: 'ArrowDown',
682
+ backward: 'ArrowUp',
683
+ },
684
+ getCurrentElement: () => document.activeElement as HTMLElement,
685
+ },
686
+ additionalKeys: {
687
+ ArrowRight: (event: KeyboardEvent) => {
688
+ const target = event.target as HTMLElement
689
+ if (target.classList.contains(contextSubmenuBtnClass)) {
690
+ this.toggleSubmenu(event)
691
+ const submenuContent = target.nextElementSibling
692
+ if (submenuContent?.classList.contains('show')) {
693
+ const firstItem = submenuContent.querySelector(
694
+ '.menu-item'
695
+ ) as HTMLElement
696
+ firstItem?.focus()
697
+ }
698
+ }
699
+ },
700
+ ArrowLeft: (event: KeyboardEvent) => {
701
+ const target = event.target as HTMLElement
702
+ // Close submenu if we're inside one and focus back to the trigger
703
+ const submenu = target.closest('.context-submenu')
704
+ if (submenu) {
705
+ const trigger = submenu.querySelector(
706
+ `.${contextSubmenuBtnClass}`
707
+ ) as HTMLElement
708
+ if (trigger) {
709
+ const submenuContent = trigger.nextElementSibling
710
+ submenuContent?.classList.toggle('show')
711
+ trigger.focus()
712
+ }
713
+ }
714
+ },
715
+ },
716
+ })
717
+ }
718
+
719
+ // Focus the first menu item when the menu opens
720
+ this.menuItems[0]?.focus()
721
+ })
722
+ }
723
+
724
+ private trimTitle = (title: string, max: number) => {
725
+ return title.length > max ? title.substring(0, max) + '…' : title
726
+ }
727
+
728
+ private getCommentTarget = () => {
729
+ if (this.node.type === schema.nodes.section_title) {
730
+ const $pos = this.resolvePos()
731
+ const parent = $pos.parent
732
+ if (parent.type === schema.nodes.keywords) {
733
+ const groups = findChildrenByType(parent, schema.nodes.keyword_group)
734
+ return groups.length ? groups[0].node : this.node
735
+ }
736
+ return parent
737
+ }
738
+ return this.node
739
+ }
740
+
741
+ private toggleSubmenu = (ev: Event) => {
742
+ const submenu = (ev.target as HTMLElement).nextElementSibling
743
+ submenu?.classList.toggle('show')
744
+ }
745
+ }