@manuscripts/body-editor 3.12.67 → 3.13.1

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,243 @@
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
+ CHANGE_OPERATION,
18
+ CHANGE_STATUS,
19
+ isTracked,
20
+ TrackedAttrs,
21
+ TrackedChange,
22
+ } from '@manuscripts/track-changes-plugin'
23
+ import {
24
+ DataTrackedAttrs,
25
+ ManuscriptEditorState,
26
+ ManuscriptNode,
27
+ schema,
28
+ } from '@manuscripts/transform'
29
+ import { ResolvedPos } from 'prosemirror-model'
30
+ import { Plugin, PluginKey } from 'prosemirror-state'
31
+ import { Decoration, DecorationSet } from 'prosemirror-view'
32
+
33
+ import { isTextSelection } from '../commands'
34
+ import { getSelectionChangeGroup } from '../selection'
35
+
36
+ export const selectedSuggestionKey = new PluginKey<PluginState>(
37
+ 'selected-suggestion'
38
+ )
39
+
40
+ type EffectiveSelection = {
41
+ node: ManuscriptNode
42
+ from: number
43
+ to: number
44
+ }
45
+ export interface PluginState {
46
+ decorations: DecorationSet
47
+ suggestion?: TrackedAttrs
48
+ }
49
+
50
+ const EMPTY: PluginState = {
51
+ decorations: DecorationSet.empty,
52
+ }
53
+
54
+ /**
55
+ * This plugin is responsible for designating a single suggestion as
56
+ * "selected" based on the current
57
+ * [editor selection]{@link ManuscriptEditorState#selection}.
58
+ *
59
+ * The selected suggestion is wrapped with a "selected-suggestion"
60
+ * decoration to apply different styling to it in the editor.
61
+ *
62
+ * For node views that render other nodes (e.g. the `bibliography_element`
63
+ * view is responsible for rendering `bibliography_item` nodes), the
64
+ * decoration has to be applied manually, for which the
65
+ * selected suggestion (if any) is [provided in the state]{@link PluginState#suggestion}.
66
+ */
67
+ export default () => {
68
+ return new Plugin<PluginState>({
69
+ key: selectedSuggestionKey,
70
+ state: {
71
+ init: (_, state) => buildPluginState(state),
72
+ apply: (tr, value, oldState, newState) => buildPluginState(newState),
73
+ },
74
+ props: {
75
+ decorations: (state) => {
76
+ const suggestion = selectedSuggestionKey.getState(state)
77
+ return suggestion?.decorations || DecorationSet.empty
78
+ },
79
+ },
80
+ })
81
+ }
82
+
83
+ const buildPluginState = (state: ManuscriptEditorState): PluginState => {
84
+ const selection = state.selection
85
+ const changes = getSelectionChangeGroup(state)
86
+ if (changes.length) {
87
+ return buildGroupOfChangesDecoration(state.doc, changes)
88
+ }
89
+ const $pos =
90
+ isTextSelection(selection) && selection.$cursor
91
+ ? selection.$cursor
92
+ : selection.$to
93
+ if (!$pos) {
94
+ return EMPTY
95
+ }
96
+ const effective = getEffectiveSelection($pos)
97
+
98
+ if (!effective) {
99
+ return EMPTY
100
+ }
101
+ if (effective.node.isText) {
102
+ return buildTextDecoration(state.doc, effective)
103
+ } else {
104
+ return buildNodeDecoration(state.doc, effective)
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Find the node containing `$pos` with the _least depth_ (closest to the root)
110
+ * that changed. This is mainly for cases where inserting a node automatically
111
+ * inserts a bunch of children (e.g. inserting a `figure_element` automatically
112
+ * inserts a `figure`, `figure_caption`, etc.)
113
+ * @param $pos
114
+ */
115
+ const getEffectiveSelection = ($pos: ResolvedPos) => {
116
+ let current
117
+ for (let depth = $pos.depth; depth > 0; depth--) {
118
+ const node = $pos.node(depth)
119
+ // @TODO - abstract reference processing to tc-plugin
120
+ if (
121
+ node.attrs.dataTracked &&
122
+ !node.attrs.dataTracked?.find(
123
+ (c: DataTrackedAttrs) => c.operation === 'reference'
124
+ )
125
+ ) {
126
+ current = {
127
+ node,
128
+ from: $pos.before(depth),
129
+ to: $pos.after(depth),
130
+ }
131
+ }
132
+ }
133
+ if (current) {
134
+ return current
135
+ }
136
+ const parent = $pos.parent
137
+ // if selection is longer than a text node by a single char, still select the previous node
138
+ // @TODO - define how selection should behave when multiple text nodes are selected at once - which tracked-changes should be highlighted? (maybe multiple highlights)
139
+ const child = parent.childBefore(Math.max($pos.parentOffset - 1, 0))
140
+ const node = child.node
141
+ if (node) {
142
+ const from = $pos.start() + child.offset
143
+ const to = from + node.nodeSize
144
+ return {
145
+ node,
146
+ from,
147
+ to,
148
+ }
149
+ }
150
+ }
151
+
152
+ const buildNodeDecoration = (
153
+ doc: ManuscriptNode,
154
+ selection: EffectiveSelection
155
+ ) => {
156
+ const node = selection.node
157
+ const suggestion = node.attrs.dataTracked?.[0]
158
+ if (!suggestion?.status || suggestion.status === CHANGE_STATUS.rejected) {
159
+ return EMPTY
160
+ }
161
+ const from = selection.from
162
+ const to = selection.to
163
+ //hack for keywords since they're really inline nodes but not
164
+ //marked as such in the schema
165
+ const inline = node.type === schema.nodes.keyword || node.isInline
166
+ const decorationType = inline ? Decoration.inline : Decoration.node
167
+ const decoration = decorationType(from, to, {
168
+ class: 'selected-suggestion',
169
+ })
170
+ return {
171
+ suggestion,
172
+ decorations: DecorationSet.create(doc, [decoration]),
173
+ }
174
+ }
175
+
176
+ const buildTextDecoration = (
177
+ doc: ManuscriptNode,
178
+ selection: EffectiveSelection
179
+ ) => {
180
+ const node = selection.node
181
+ let suggestion = getTrackedMark(node)?.attrs.dataTracked as TrackedAttrs
182
+
183
+ if (!suggestion) {
184
+ for (const mark of node.marks) {
185
+ if (isTracked(mark)) {
186
+ suggestion = mark.attrs.dataTracked[0] as TrackedAttrs
187
+ }
188
+ }
189
+ }
190
+
191
+ if (!suggestion) {
192
+ return EMPTY
193
+ }
194
+ const from = selection.from
195
+ const to = selection.to
196
+
197
+ const decoration = Decoration.inline(from, to, {
198
+ nodeName: 'span',
199
+ class: 'selected-suggestion',
200
+ })
201
+ return {
202
+ suggestion,
203
+ decorations: DecorationSet.create(doc, [decoration]),
204
+ }
205
+ }
206
+
207
+ const buildGroupOfChangesDecoration = (
208
+ doc: ManuscriptNode,
209
+ changes: TrackedChange[]
210
+ ) => {
211
+ const decorations = []
212
+ if (changes[0].dataTracked.operation === CHANGE_OPERATION.structure) {
213
+ changes.map((c) =>
214
+ decorations.push(
215
+ Decoration.node(c.from, c.to, { class: 'selected-suggestion' })
216
+ )
217
+ )
218
+ } else {
219
+ const from = changes[0].from,
220
+ to = changes[changes.length - 1].to
221
+ decorations.push(
222
+ Decoration.inline(from, to, {
223
+ class: 'selected-suggestion',
224
+ })
225
+ )
226
+ }
227
+ return {
228
+ decorations: DecorationSet.create(doc, decorations),
229
+ suggestion: changes[0].dataTracked,
230
+ }
231
+ }
232
+
233
+ const trackedMarkTypes = new Set([
234
+ schema.marks.tracked_insert,
235
+ schema.marks.tracked_delete,
236
+ ])
237
+ const getTrackedMark = (node: ManuscriptNode) => {
238
+ for (const mark of node.marks) {
239
+ if (trackedMarkTypes.has(mark.type)) {
240
+ return mark
241
+ }
242
+ }
243
+ }
@@ -0,0 +1,143 @@
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 { skipTracking } from '@manuscripts/track-changes-plugin'
17
+ import { ManuscriptNode, schema } from '@manuscripts/transform'
18
+ import { Fragment } from 'prosemirror-model'
19
+ import { EditorState, Plugin, TextSelection } from 'prosemirror-state'
20
+ import { CellSelection, selectedRect } from 'prosemirror-tables'
21
+ import { findParentNodeOfTypeClosestToPos } from 'prosemirror-utils'
22
+
23
+ import { isTextSelection } from '../commands'
24
+
25
+ /**
26
+ * This plugins fixes cursos jump caused by tables-fixes plugin from prosemirror tables
27
+ * also will include table_element node with the selection, if the selection start/end from table node
28
+ */
29
+ export default () => {
30
+ return new Plugin<null>({
31
+ appendTransaction: (transactions, oldState, newState) => {
32
+ if (
33
+ isTextSelection(newState.selection) &&
34
+ !oldState.selection.eq(newState.selection)
35
+ ) {
36
+ const slice = newState.selection.$from.doc.slice(
37
+ newState.selection.from,
38
+ newState.selection.to
39
+ )
40
+ const selectionTable = getTable(slice.content)
41
+ const selection = addTableElementToSelection(newState, selectionTable)
42
+ if (selection) {
43
+ const newTr = newState.tr
44
+ newTr.setSelection(selection)
45
+ return newTr
46
+ }
47
+ }
48
+ if (
49
+ isTableShapeSelected(newState) &&
50
+ !oldState.selection.eq(newState.selection)
51
+ ) {
52
+ const contentNode = findParentNodeOfTypeClosestToPos(
53
+ newState.selection.$from,
54
+ schema.nodes.table_element
55
+ )
56
+ if (contentNode) {
57
+ const newTr = newState.tr
58
+ newTr.setSelection(
59
+ TextSelection.create(
60
+ newState.doc,
61
+ contentNode.pos,
62
+ contentNode.pos + contentNode.node.nodeSize
63
+ )
64
+ )
65
+ return newTr
66
+ }
67
+ }
68
+
69
+ const tablesFixedTr = transactions.find((tr) => tr.getMeta('fix-tables$'))
70
+ if (!tablesFixedTr) {
71
+ return null
72
+ }
73
+
74
+ // const pos = tablesFixedTr.selection.$anchor.pos;
75
+ const newTr = newState.tr
76
+ skipTracking(newTr)
77
+ newTr.setMeta('origin', 'tables-cursor-fix')
78
+ newTr.setSelection(
79
+ TextSelection.create(newState.doc, tablesFixedTr.selection.$anchor.pos)
80
+ )
81
+
82
+ return newTr
83
+ },
84
+ })
85
+ }
86
+
87
+ const isTableShapeSelected = (state: EditorState) => {
88
+ if (state.selection instanceof CellSelection) {
89
+ const rect = selectedRect(state)
90
+ return (
91
+ rect.left === 0 &&
92
+ rect.top === 0 &&
93
+ rect.right === rect.map.width &&
94
+ rect.bottom === rect.map.height
95
+ )
96
+ }
97
+ return false
98
+ }
99
+
100
+ const getTable = (fragment?: Fragment) => {
101
+ let table: ManuscriptNode | undefined
102
+ fragment?.descendants((node) => {
103
+ if (node?.type === schema.nodes.table) {
104
+ table = node
105
+ return false
106
+ }
107
+ })
108
+ return table
109
+ }
110
+
111
+ /**
112
+ * Expand selection to include table_element node
113
+ */
114
+ const addTableElementToSelection = (
115
+ state: EditorState,
116
+ selectedTable?: ManuscriptNode
117
+ ) => {
118
+ const { doc, selection } = state
119
+ const fromContentNode = findParentNodeOfTypeClosestToPos(
120
+ selection.$from,
121
+ schema.nodes.table_element
122
+ )
123
+ const toContentNode = findParentNodeOfTypeClosestToPos(
124
+ selection.$to,
125
+ schema.nodes.table_element
126
+ )
127
+ const docTableElement = fromContentNode?.node || toContentNode?.node
128
+
129
+ if (
130
+ selectedTable?.nodeSize !== getTable(docTableElement?.content)?.nodeSize
131
+ ) {
132
+ return undefined
133
+ }
134
+
135
+ const from =
136
+ ((fromContentNode && toContentNode) || fromContentNode) &&
137
+ fromContentNode.pos
138
+ const to =
139
+ ((fromContentNode && toContentNode) || toContentNode) &&
140
+ toContentNode.pos + toContentNode.node.nodeSize
141
+
142
+ return TextSelection.create(doc, from || selection.from, to || selection.to)
143
+ }
@@ -0,0 +1,263 @@
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 { schema } from '@manuscripts/transform'
18
+ import { Node } from 'prosemirror-model'
19
+ import { Plugin } from 'prosemirror-state'
20
+ import { Decoration, DecorationSet } from 'prosemirror-view'
21
+
22
+ import { insertTransAbstract, insertTransGraphicalAbstract } from '../commands'
23
+ import { EditorProps } from '../configs/ManuscriptsEditor'
24
+ import { addAuthorIcon, translateIcon } from '../icons'
25
+ import { getLanguage, getLanguageLabel } from '../lib/languages'
26
+ import { templateAllows } from '../lib/template'
27
+ import {
28
+ handleEnterKey,
29
+ createKeyboardInteraction,
30
+ } from '../lib/navigation-utils'
31
+
32
+ const createMenuItem = (
33
+ props: EditorProps,
34
+ contents: string,
35
+ handler: EventListener,
36
+ isSelected = false,
37
+ tabIndex: number
38
+ ) => {
39
+ const item = document.createElement('div')
40
+ item.className = `menu-item ${isSelected ? 'selected' : ''}`
41
+ item.textContent = contents
42
+ item.tabIndex = tabIndex
43
+
44
+ item.addEventListener('mousedown', handler)
45
+ item.addEventListener('keydown', handleEnterKey(handler))
46
+ return item
47
+ }
48
+ interface MenuInstance {
49
+ menu: HTMLElement
50
+ destroy: () => void
51
+ }
52
+ const createLanguageMenu = (
53
+ props: EditorProps,
54
+ selectedCode: string,
55
+ onSelect: (code: string) => void
56
+ ): MenuInstance => {
57
+ const menu = document.createElement('div')
58
+ menu.className = 'language menu'
59
+ const menuItems: HTMLElement[] = []
60
+
61
+ const removeKeydownListener = createKeyboardInteraction({
62
+ container: document,
63
+ navigation: {
64
+ getItems: () => menuItems,
65
+ arrowKeys: {
66
+ forward: 'ArrowDown',
67
+ backward: 'ArrowUp',
68
+ },
69
+ },
70
+ })
71
+ const destroy = () => {
72
+ removeKeydownListener()
73
+ props.popper.destroy()
74
+ }
75
+ props.languages.forEach((language, index) => {
76
+ const item = createMenuItem(
77
+ props,
78
+ getLanguageLabel(language),
79
+ () => {
80
+ onSelect(language.code)
81
+ destroy()
82
+ },
83
+ selectedCode === language.code,
84
+ index === 0 ? 0 : -1
85
+ )
86
+ menuItems.push(item)
87
+ menu.appendChild(item)
88
+ })
89
+
90
+ return { menu, destroy }
91
+ }
92
+
93
+ const getInsertionPos = (doc: Node, nodePos: number): number | null => {
94
+ const node = doc.nodeAt(nodePos)
95
+ return node ? nodePos + node.nodeSize : null
96
+ }
97
+
98
+ export default (props: EditorProps) =>
99
+ new Plugin<null>({
100
+ props: {
101
+ decorations: (state) => {
102
+ const can = props.getCapabilities()
103
+ const canEditTransAbstract =
104
+ can.editArticle &&
105
+ templateAllows(state, schema.nodes.trans_abstract) &&
106
+ insertTransAbstract(state)
107
+ const canEditTransGraphicalAbstract =
108
+ can.editArticle &&
109
+ templateAllows(state, schema.nodes.trans_graphical_abstract)
110
+
111
+ const widgets: Decoration[] = []
112
+
113
+ state.doc.descendants((node, pos, parent) => {
114
+ const isAbstractSection =
115
+ (node.type === schema.nodes.section ||
116
+ node.type === schema.nodes.graphical_abstract_section) &&
117
+ parent?.type === schema.nodes.abstracts
118
+
119
+ // Show "Add translation" for abstract sections
120
+ if (isAbstractSection) {
121
+ const isGraphical =
122
+ node.type === schema.nodes.graphical_abstract_section
123
+ const category = props.sectionCategories.get(node.attrs.category)
124
+
125
+ const canEdit = isGraphical
126
+ ? canEditTransGraphicalAbstract &&
127
+ category &&
128
+ insertTransGraphicalAbstract(category)(state)
129
+ : canEditTransAbstract
130
+
131
+ if (canEdit) {
132
+ widgets.push(
133
+ Decoration.widget(
134
+ pos + 1,
135
+ (view) => {
136
+ const $span = document.createElement('span')
137
+ $span.tabIndex = 0
138
+ $span.className = 'add-trans-abstract'
139
+ $span.title = 'Add translation'
140
+ $span.innerHTML = `${addAuthorIcon} <span class="add-trans-abstract-text">Add translation</span>`
141
+
142
+ const handleActivate = (event: Event) => {
143
+ event.preventDefault()
144
+ event.stopPropagation()
145
+ const insertPos = getInsertionPos(view.state.doc, pos)
146
+ if (insertPos == null) {
147
+ return
148
+ }
149
+ if (isGraphical && category) {
150
+ insertTransGraphicalAbstract(category, insertPos)(
151
+ view.state,
152
+ view.dispatch,
153
+ view
154
+ )
155
+ } else {
156
+ insertTransAbstract(
157
+ view.state,
158
+ view.dispatch,
159
+ node.attrs.category,
160
+ insertPos
161
+ )
162
+ }
163
+ }
164
+
165
+ $span.addEventListener('mousedown', handleActivate)
166
+ $span.addEventListener(
167
+ 'keydown',
168
+ handleEnterKey(handleActivate)
169
+ )
170
+ return $span
171
+ },
172
+ { key: `add-trans-${node.attrs.id}-${pos}` }
173
+ )
174
+ )
175
+ }
176
+ }
177
+
178
+ // Language selector for trans_abstract and trans_graphical_abstract nodes
179
+ const isTransNode =
180
+ node.type === schema.nodes.trans_abstract ||
181
+ node.type === schema.nodes.trans_graphical_abstract
182
+
183
+ if (isTransNode) {
184
+ const canEdit =
185
+ node.type === schema.nodes.trans_abstract
186
+ ? canEditTransAbstract
187
+ : canEditTransGraphicalAbstract
188
+
189
+ widgets.push(
190
+ Decoration.widget(
191
+ pos + 1,
192
+ (view) => {
193
+ const $btn = document.createElement('span')
194
+ $btn.className = 'language-selector-btn'
195
+ $btn.setAttribute('data-cy', 'language-selector-btn')
196
+ $btn.contentEditable = 'false'
197
+ $btn.tabIndex = canEdit ? 0 : -1
198
+
199
+ const code = node.attrs.lang || 'en'
200
+ const lang = getLanguage(code, props.languages)
201
+ const label = getLanguageLabel(lang)
202
+ $btn.innerHTML = `<span>${label}</span> ${translateIcon}`
203
+
204
+ if (canEdit) {
205
+ let menuInstance: MenuInstance | null = null
206
+ const handleOpenMenu = (event: Event) => {
207
+ event.preventDefault()
208
+ event.stopPropagation()
209
+
210
+ props.popper.destroy() // Ensure any existing popper is closed
211
+
212
+ const handleSelect = (code: string) => {
213
+ const tr = view.state.tr.setNodeAttribute(
214
+ pos,
215
+ 'lang',
216
+ code
217
+ )
218
+ view.dispatch(tr)
219
+ }
220
+
221
+ menuInstance = createLanguageMenu(
222
+ props,
223
+ code,
224
+ handleSelect
225
+ )
226
+
227
+ props.popper.show(
228
+ $btn,
229
+ menuInstance.menu,
230
+ 'bottom-end',
231
+ false
232
+ )
233
+ }
234
+
235
+ createKeyboardInteraction({
236
+ container: $btn,
237
+ additionalKeys: {
238
+ Enter: handleOpenMenu,
239
+ Escape: (e) => {
240
+ e.preventDefault()
241
+ menuInstance?.destroy()
242
+ menuInstance = null
243
+ },
244
+ },
245
+ })
246
+
247
+ $btn.addEventListener('mousedown', handleOpenMenu)
248
+ }
249
+
250
+ return $btn
251
+ },
252
+ {
253
+ key: `lang-selector-${node.attrs.id}-${pos}-${node.attrs.lang}`,
254
+ }
255
+ )
256
+ )
257
+ }
258
+ })
259
+
260
+ return DecorationSet.create(state.doc, widgets)
261
+ },
262
+ },
263
+ })
package/src/rules.ts ADDED
@@ -0,0 +1,60 @@
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 { schema } from '@manuscripts/transform'
18
+ import {
19
+ ellipsis,
20
+ emDash,
21
+ InputRule,
22
+ inputRules,
23
+ smartQuotes,
24
+ } from 'prosemirror-inputrules'
25
+ import { Node as ProsemirrorNode, NodeType } from 'prosemirror-model'
26
+ import { TextSelection } from 'prosemirror-state'
27
+
28
+ const creatingInputRule = (regexp: RegExp, nodeType: NodeType) =>
29
+ new InputRule(regexp, (state, match, start, end) => {
30
+ const tr = state.tr.delete(start, end)
31
+ const range = tr.doc.resolve(start).blockRange()
32
+ if (range) {
33
+ const node = nodeType.createAndFill() as ProsemirrorNode
34
+ tr.replaceRangeWith(range.start, range.end, node)
35
+ const $anchor = tr.doc.resolve(range.start)
36
+ tr.setSelection(TextSelection.between($anchor, $anchor))
37
+ return tr
38
+ }
39
+ return null
40
+ })
41
+
42
+ export default inputRules({
43
+ rules: [
44
+ ...smartQuotes,
45
+ ellipsis,
46
+ emDash,
47
+
48
+ // * bullet | ordered list
49
+ creatingInputRule(/^(\d+[.)]\s|\s*\*\s)$/, schema.nodes.list),
50
+
51
+ // > blockquote
52
+ creatingInputRule(/^\s*>\s$/, schema.nodes.blockquote_element),
53
+
54
+ // ``` listing
55
+ creatingInputRule(/^```$/, schema.nodes.listing_element),
56
+
57
+ // # section (heading)
58
+ creatingInputRule(/^#\s$/, schema.nodes.section),
59
+ ],
60
+ })