@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,90 @@
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 { isParagraphNode, ManuscriptNode } from '@manuscripts/transform'
18
+ import { Plugin } from 'prosemirror-state'
19
+
20
+ const isTextBlock = (node: ManuscriptNode): boolean => {
21
+ return node.type === node.type.schema.nodes.text_block
22
+ }
23
+
24
+ const isTextBlockOrParagraph = (node: ManuscriptNode): boolean => {
25
+ return isParagraphNode(node) || isTextBlock(node)
26
+ }
27
+
28
+ const isTableCell = (node: ManuscriptNode): boolean => {
29
+ const { schema } = node.type
30
+ return (
31
+ node.type === schema.nodes.table_cell ||
32
+ node.type === schema.nodes.table_header
33
+ )
34
+ }
35
+
36
+ /**
37
+ * This plugin enforces a rule that there can never be more than one adjacent empty paragraph.
38
+ */
39
+ export default () => {
40
+ return new Plugin<null>({
41
+ appendTransaction: (transactions, oldState, newState) => {
42
+ const positionsToJoin: number[] = []
43
+
44
+ const tr = newState.tr
45
+
46
+ if (!transactions.some((tr) => tr.docChanged || tr.getMeta('INIT'))) {
47
+ return null
48
+ }
49
+
50
+ const joinAdjacentParagraphs =
51
+ (parent: ManuscriptNode, pos: number) =>
52
+ (node: ManuscriptNode, offset: number, index: number) => {
53
+ const nodePos = pos + offset
54
+
55
+ // Skip joining for table cells - allow multiple adjacent empty paragraphs/text_blocks there
56
+ if (isTableCell(parent)) {
57
+ node.forEach(joinAdjacentParagraphs(node, nodePos + 1))
58
+ return
59
+ }
60
+
61
+ if (
62
+ isTextBlockOrParagraph(node) &&
63
+ node.childCount === 0 &&
64
+ index < parent.childCount - 1
65
+ ) {
66
+ const nextNode = parent.child(index + 1)
67
+
68
+ if (isTextBlockOrParagraph(nextNode) && nextNode.childCount === 0) {
69
+ positionsToJoin.push(nodePos + node.nodeSize)
70
+ }
71
+ }
72
+
73
+ node.forEach(joinAdjacentParagraphs(node, nodePos + 1))
74
+ }
75
+
76
+ newState.doc.forEach(joinAdjacentParagraphs(newState.doc, 0))
77
+
78
+ if (positionsToJoin.length) {
79
+ // execute the joins in reverse order so the positions don't change
80
+ positionsToJoin.reverse()
81
+
82
+ for (const nodePos of positionsToJoin) {
83
+ tr.join(nodePos)
84
+ }
85
+ tr.setMeta('origin', 'paragraphs')
86
+ return tr
87
+ }
88
+ },
89
+ })
90
+ }
@@ -0,0 +1,64 @@
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
+ import { clear, skipTracking } from '@manuscripts/track-changes-plugin'
17
+ import {
18
+ generateNodeID,
19
+ isHighlightMarkerNode,
20
+ ManuscriptNode,
21
+ schema,
22
+ } from '@manuscripts/transform'
23
+ import { Plugin } from 'prosemirror-state'
24
+ import { isInit } from '../lib/plugins'
25
+
26
+ /**
27
+ * This plugin ensures that all nodes which need ids (i.e. `id` is defined in the node spec's attributes) are given an id, and that there aren't any duplicate ids in the document.
28
+ */
29
+
30
+ const isManuscriptNode = (node: ManuscriptNode | null) => {
31
+ return node && node.type === schema.nodes.manuscript
32
+ }
33
+
34
+ export default () => {
35
+ return new Plugin<null>({
36
+ appendTransaction(transactions, oldState, newState) {
37
+ // only scan if nodes have changed
38
+ if (!transactions.some((tr) => tr.docChanged || isInit(tr))) {
39
+ return null
40
+ }
41
+ const ids = new Set<string>()
42
+ const tr = newState.tr
43
+ clear(newState.doc).descendants((node, pos, parent) => {
44
+ if (
45
+ !(node.type.spec.attrs && 'id' in node.type.spec.attrs) ||
46
+ isHighlightMarkerNode(node) ||
47
+ isManuscriptNode(node) ||
48
+ isManuscriptNode(parent)
49
+ ) {
50
+ return
51
+ }
52
+ let id = node.attrs.id
53
+ if (!id || ids.has(id)) {
54
+ id = generateNodeID(node.type)
55
+ tr.setNodeAttribute(pos, 'id', id)
56
+ }
57
+ ids.add(id)
58
+ })
59
+ skipTracking(tr)
60
+ tr.setMeta('origin', 'persist')
61
+ return tr
62
+ },
63
+ })
64
+ }
@@ -0,0 +1,66 @@
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 { ManuscriptEditorView } from '@manuscripts/transform'
18
+ import { Plugin, PluginKey } from 'prosemirror-state'
19
+ import { Decoration, DecorationSet } from 'prosemirror-view'
20
+
21
+ export const persistentCursor = new PluginKey<{ on: boolean }>(
22
+ 'persistent-cursor'
23
+ )
24
+
25
+ /**
26
+ * This plugin creates a fake text cursor when the editor is out of focus for better UX.
27
+ */
28
+ export default () => {
29
+ return new Plugin({
30
+ state: {
31
+ init(_, state) {
32
+ return { on: false }
33
+ },
34
+ apply(tr, value) {
35
+ if (tr.getMeta(persistentCursor)) {
36
+ return tr.getMeta(persistentCursor)
37
+ }
38
+ return value
39
+ },
40
+ },
41
+ props: {
42
+ decorations(state) {
43
+ const selection = state.selection
44
+ if (this.getState(state)?.on && selection.from === selection.to) {
45
+ const decorations = [
46
+ Decoration.widget(selection.to, (view: ManuscriptEditorView) => {
47
+ const cursor = document.createElement('span')
48
+ cursor.classList.add('cursor-placeholder')
49
+ return cursor
50
+ }),
51
+ ]
52
+ return DecorationSet.create(state.doc, decorations)
53
+ }
54
+ return DecorationSet.empty
55
+ },
56
+ handleDOMEvents: {
57
+ focus(view, event) {
58
+ if (this.getState(view.state)?.on) {
59
+ const newTr = view.state.tr.setMeta(persistentCursor, { on: false })
60
+ view.dispatch(newTr)
61
+ }
62
+ },
63
+ },
64
+ },
65
+ })
66
+ }
@@ -0,0 +1,219 @@
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
+ isFootnoteNode,
19
+ isGeneralTableFootnoteNode,
20
+ isPullquoteElement,
21
+ ManuscriptEditorView,
22
+ ManuscriptNode,
23
+ schema,
24
+ } from '@manuscripts/transform'
25
+ import { Plugin, TextSelection } from 'prosemirror-state'
26
+ import { findParentNodeOfTypeClosestToPos } from 'prosemirror-utils'
27
+ import { Decoration, DecorationSet } from 'prosemirror-view'
28
+
29
+ const placeholderMap: Record<string, string> = {
30
+ subtitle: 'Type subtitle here...',
31
+ }
32
+
33
+ const placeholderWidget =
34
+ (placeholder: string) =>
35
+ (view: ManuscriptEditorView, getPos: () => number | undefined) => {
36
+ const element = document.createElement('span')
37
+ element.className = 'placeholder-text'
38
+ element.setAttribute('aria-hidden', 'true')
39
+ element.dataset.placeholder = placeholder
40
+ element.addEventListener('click', (event: MouseEvent) => {
41
+ event.preventDefault()
42
+ const pos = getPos() as number
43
+ const selection = TextSelection.create(view.state.tr.doc, pos)
44
+ view.dispatch(view.state.tr.setSelection(selection))
45
+ })
46
+ return element
47
+ }
48
+
49
+ const backmatterWidget = (direction: 'top' | 'bottom') => {
50
+ const element = document.createElement('div')
51
+ element.className = 'backmatter-border-placeholder'
52
+ const line = document.createElement('div')
53
+ line.className = direction
54
+ element.appendChild(line)
55
+ return element
56
+ }
57
+
58
+ const getParagraphPlaceholderText = (
59
+ parent: ManuscriptNode | null,
60
+ node: ManuscriptNode
61
+ ) => {
62
+ if (!parent) {
63
+ return
64
+ }
65
+ if (isPullquoteElement(parent) && !node.textContent.length) {
66
+ let otherParagraphHasContent = false
67
+ parent.descendants((child) => {
68
+ if (child !== node && child.type === child.type.schema.nodes.text_block) {
69
+ otherParagraphHasContent = true
70
+ }
71
+ })
72
+ if (otherParagraphHasContent) {
73
+ return
74
+ }
75
+ return 'Insert pull quote here'
76
+ }
77
+ if (parent.textContent.length) {
78
+ return
79
+ }
80
+ if (parent.type === schema.nodes.body) {
81
+ return 'Start typing here...'
82
+ }
83
+ if (isFootnoteNode(parent) || isGeneralTableFootnoteNode(parent)) {
84
+ return 'Type new footnote here'
85
+ }
86
+ if (parent.type === schema.nodes.trans_abstract) {
87
+ return 'Type here'
88
+ }
89
+ }
90
+
91
+ const isTextBlockOfQuoteElement = (
92
+ node: ManuscriptNode,
93
+ parent: ManuscriptNode | null
94
+ ) =>
95
+ node.type === schema.nodes.text_block &&
96
+ !!parent &&
97
+ (parent.type === schema.nodes.pullquote_element ||
98
+ parent.type === schema.nodes.blockquote_element)
99
+
100
+ /**
101
+ * This plugin adds a placeholder decoration to empty nodes
102
+ */
103
+ export default () =>
104
+ new Plugin<null>({
105
+ props: {
106
+ decorations: (state) => {
107
+ const decorations: Decoration[] = []
108
+
109
+ state.doc.descendants((node, pos, parent) => {
110
+ if (!node.isAtom && node.type.isBlock && node.childCount === 0) {
111
+ if (node.type === node.type.schema.nodes.attribution) {
112
+ decorations.push(
113
+ Decoration.widget(
114
+ pos + 1,
115
+ placeholderWidget('Insert reference here')
116
+ )
117
+ )
118
+ }
119
+ if (
120
+ node.type === node.type.schema.nodes.paragraph ||
121
+ isTextBlockOfQuoteElement(node, parent)
122
+ ) {
123
+ const text = getParagraphPlaceholderText(parent, node)
124
+ if (text) {
125
+ decorations.push(
126
+ Decoration.widget(pos + 1, placeholderWidget(text))
127
+ )
128
+ }
129
+ } else if (node.type === node.type.schema.nodes.section_title) {
130
+ const $pos = state.doc.resolve(pos)
131
+
132
+ let placeholderText = 'Type heading here'
133
+
134
+ const boxElement = findParentNodeOfTypeClosestToPos(
135
+ $pos,
136
+ schema.nodes.box_element
137
+ )
138
+
139
+ if (boxElement) {
140
+ const section = findParentNodeOfTypeClosestToPos(
141
+ $pos,
142
+ schema.nodes.section
143
+ )
144
+
145
+ // If the section is a direct child of the box_element,
146
+ // it's the top-level section and should have the box placeholder
147
+ if (section && section.depth === boxElement.depth + 1) {
148
+ placeholderText = 'Optional box title...'
149
+ }
150
+ }
151
+
152
+ if (
153
+ findParentNodeOfTypeClosestToPos($pos, schema.nodes.supplements)
154
+ ) {
155
+ placeholderText = 'Supplements'
156
+ }
157
+
158
+ decorations.push(
159
+ Decoration.widget(pos + 1, placeholderWidget(placeholderText))
160
+ )
161
+ } else if (node.type === node.type.schema.nodes.trans_abstract) {
162
+ decorations.push(
163
+ Decoration.widget(
164
+ pos + 1,
165
+ placeholderWidget('Type new abstract title here')
166
+ )
167
+ )
168
+ } else if (node.type === schema.nodes.caption_title) {
169
+ decorations.push(
170
+ Decoration.widget(
171
+ pos + 1,
172
+ placeholderWidget('Caption title...'),
173
+ {
174
+ key: node.attrs.id,
175
+ }
176
+ )
177
+ )
178
+ } else if (
179
+ node.type === schema.nodes.text_block &&
180
+ parent &&
181
+ parent.type === schema.nodes.caption
182
+ ) {
183
+ decorations.push(
184
+ Decoration.widget(pos + 1, placeholderWidget('Caption...'))
185
+ )
186
+ } else {
187
+ const placeholder = placeholderMap[node.type.name]
188
+ decorations.push(
189
+ Decoration.node(pos, pos + node.nodeSize, {
190
+ class: 'empty-node',
191
+ ...(placeholder && { 'data-placeholder': placeholder }),
192
+ })
193
+ )
194
+ }
195
+ } else if (node.type === schema.nodes.backmatter) {
196
+ decorations.push(
197
+ Decoration.widget(pos + 1, backmatterWidget('top'))
198
+ )
199
+ const nextNode = parent && parent.nodeAt(pos + node.nodeSize)
200
+ if (
201
+ nextNode &&
202
+ (nextNode.type === schema.nodes.supplements ||
203
+ nextNode.type === schema.nodes.hero_image ||
204
+ nextNode.type === schema.nodes.attachments)
205
+ ) {
206
+ decorations.push(
207
+ Decoration.widget(
208
+ pos + node.nodeSize - 1,
209
+ backmatterWidget('bottom')
210
+ )
211
+ )
212
+ }
213
+ }
214
+ })
215
+
216
+ return DecorationSet.create(state.doc, decorations)
217
+ },
218
+ },
219
+ })
@@ -0,0 +1,50 @@
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 { skipTracking } from '@manuscripts/track-changes-plugin'
18
+ import { schema } from '@manuscripts/transform'
19
+ import { Plugin } from 'prosemirror-state'
20
+
21
+ /**
22
+ * This plugin prevent complete removal of minimal allowed content - empty title and empty paragraph in the only section in the doc
23
+ */
24
+ export default () => {
25
+ return new Plugin<null>({
26
+ appendTransaction(transactions, oldState, newState) {
27
+ let emptyBody = false
28
+ let bodyPos = -1
29
+ newState.doc.descendants((node, pos) => {
30
+ if (bodyPos >= 0) {
31
+ return false
32
+ }
33
+ if (node.type === schema.nodes.body) {
34
+ bodyPos = pos
35
+ emptyBody = node.childCount == 0
36
+ }
37
+ })
38
+
39
+ if (emptyBody) {
40
+ const tr = newState.tr
41
+ tr.insert(bodyPos + 1, schema.nodes.paragraph.create())
42
+ tr.setMeta('prevent-empty', true)
43
+ skipTracking(tr)
44
+ return tr
45
+ }
46
+
47
+ return null
48
+ },
49
+ })
50
+ }
@@ -0,0 +1,157 @@
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 { EditorState, Plugin, PluginKey, Selection } from 'prosemirror-state'
18
+ import { Decoration, DecorationSet } from 'prosemirror-view'
19
+
20
+ import { getClosestMatch, getMatches } from './lib'
21
+
22
+ export type SearchReplacePluginState = {
23
+ value: string
24
+ matches: Array<{ from: number; to: number }>
25
+ replaceValue: string
26
+ active: boolean // if the plugin actively display matches
27
+ activeAdvanced: boolean // enabling advanced view for search and replace
28
+ currentMatch: number
29
+ caseSensitive: boolean
30
+ ignoreDiacritics: boolean
31
+ highlightCurrent: boolean
32
+ }
33
+
34
+ export const searchReplaceKey = new PluginKey<SearchReplacePluginState>(
35
+ 'findReplace'
36
+ )
37
+
38
+ function buildPluginState(
39
+ state: EditorState,
40
+ oldData?: SearchReplacePluginState,
41
+ newData?: Partial<SearchReplacePluginState>,
42
+ selection?: Selection,
43
+ pointerChanged?: boolean
44
+ ) {
45
+ // this is needed to allow components that update this plugin states to update it partially and not always the entire state
46
+ const data: SearchReplacePluginState = {
47
+ value: '',
48
+ replaceValue: '',
49
+ active: false,
50
+ activeAdvanced: false,
51
+ currentMatch: -1, // index of a currently selected match
52
+ highlightCurrent: false,
53
+ matches: [],
54
+ caseSensitive: false,
55
+ ignoreDiacritics: true,
56
+ ...oldData,
57
+ ...newData,
58
+ }
59
+
60
+ // creating a new set of matches if search value has changed and not falsy
61
+ data.matches = getMatches(
62
+ state.doc,
63
+ data.value,
64
+ data.caseSensitive,
65
+ data.ignoreDiacritics
66
+ )
67
+
68
+ // if user changes pointer position - we drop previous index so if next time user move selection it will be recalculated against the new pointer
69
+ if (pointerChanged && selection && data.matches.length) {
70
+ data.currentMatch = getClosestMatch('right', selection, data.matches)
71
+ }
72
+
73
+ // pointer was changed not by the UI of Find & Search, stop highlight as some other component is managing the selection
74
+ if (!newData && pointerChanged) {
75
+ data.highlightCurrent = false
76
+ }
77
+
78
+ // setting new currentMatch if search value is updated
79
+ if (
80
+ selection &&
81
+ newData?.value &&
82
+ oldData?.value !== newData?.value &&
83
+ data.matches.length
84
+ ) {
85
+ data.currentMatch = getClosestMatch('right', selection, data.matches)
86
+ }
87
+ return data
88
+ }
89
+
90
+ export default () => {
91
+ return new Plugin<SearchReplacePluginState>({
92
+ key: searchReplaceKey,
93
+ state: {
94
+ init(config, instance) {
95
+ return buildPluginState(instance)
96
+ },
97
+ apply(tr, value, oldState, newState) {
98
+ const $old = searchReplaceKey.getState(oldState)
99
+ const selectionChanged = !!tr.getMeta('pointer') || tr.selectionSet
100
+ if (!$old || tr.getMeta(searchReplaceKey) || selectionChanged) {
101
+ return buildPluginState(
102
+ newState,
103
+ $old,
104
+ tr.getMeta(searchReplaceKey),
105
+ newState.selection,
106
+ selectionChanged
107
+ )
108
+ }
109
+ return $old
110
+ },
111
+ },
112
+ props: {
113
+ decorations: (state) => {
114
+ const pluginState = searchReplaceKey.getState(state)
115
+
116
+ if (
117
+ !pluginState ||
118
+ !pluginState.value ||
119
+ (!pluginState.active && !pluginState.activeAdvanced)
120
+ ) {
121
+ return DecorationSet.empty
122
+ }
123
+ const decorations: Decoration[] = []
124
+
125
+ pluginState.matches.forEach(({ from, to }, i) => {
126
+ let className = 'search-result'
127
+ if (pluginState.highlightCurrent && pluginState.currentMatch === i) {
128
+ className = className + ' search-result-selected'
129
+ }
130
+ decorations.push(
131
+ Decoration.inline(from, to, {
132
+ class: className,
133
+ })
134
+ )
135
+ })
136
+
137
+ return DecorationSet.create(state.doc, decorations)
138
+ },
139
+ },
140
+ view() {
141
+ return {
142
+ update() {
143
+ const element = document.querySelector(
144
+ '.search-result.search-result-selected'
145
+ )
146
+ if (element) {
147
+ // @TODO - there is an identical function scrollIntoView in article-editor, think about maybe unifying
148
+ const rect = element.getBoundingClientRect()
149
+ if (rect.bottom > window.innerHeight || rect.top < 150) {
150
+ element.scrollIntoView()
151
+ }
152
+ }
153
+ },
154
+ }
155
+ },
156
+ })
157
+ }