@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,245 @@
1
+ /*!
2
+ * © 2025 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import {
17
+ ButtonGroup,
18
+ CloseButton,
19
+ IconButton,
20
+ IconButtonGroup,
21
+ ModalBody,
22
+ ModalContainer,
23
+ ModalHeader,
24
+ ModalSidebar,
25
+ ModalSidebarHeader,
26
+ ModalSidebarTitle,
27
+ PrimaryButton,
28
+ SidebarContent,
29
+ StyledModal,
30
+ withNavigableListItem,
31
+ withListNavigation,
32
+ } from '@manuscripts/style-guide'
33
+ import { EditorView } from 'prosemirror-view'
34
+ import React, { MouseEvent, useState } from 'react'
35
+ import Select, { OptionProps, SingleValue } from 'react-select'
36
+ import styled from 'styled-components'
37
+
38
+ import { getEditorProps } from '../../plugins/editor-props'
39
+ import ReactSubView from '../../views/ReactSubView'
40
+
41
+ // Ranges brought from: https://www.unicode.org/Public/UCD/latest/ucd/Blocks.txt
42
+ const unicodeRanges = [
43
+ { label: 'Latin', value: [0x0100, 0x024f] },
44
+ { label: 'Greek and Coptic', value: [0x0370, 0x03ff] },
45
+ { label: 'Mathematical Operators', value: [0x2200, 0x22ff] },
46
+ { label: 'Arrows', value: [0x2190, 0x21ff] },
47
+ ]
48
+
49
+ type OptionType = { value: number[]; label: string }
50
+
51
+ const reservedCharacters = new Set([
52
+ 0x0380, 0x0381, 0x0382, 0x0383, 0x03a2, 0x0378, 0x0379, 0x038b, 0x038d,
53
+ ])
54
+
55
+ const generateCharacters = (start: number, end: number) =>
56
+ Array.from({ length: end - start + 1 }, (_, i) => start + i)
57
+ .filter((c) => !reservedCharacters.has(c))
58
+ .map((c) => String.fromCharCode(c))
59
+
60
+ const InsertSpecialCharacterDialog: React.FC<{
61
+ view: EditorView
62
+ }> = ({ view }) => {
63
+ const [isOpen, setOpen] = useState(true)
64
+ const [range, setRange] = useState(unicodeRanges[0].value)
65
+
66
+ const handleRangeChange = (range: SingleValue<OptionType>) =>
67
+ range && setRange(range.value)
68
+
69
+ const handleClose = () => setOpen(false)
70
+
71
+ const addCharacter = (event: MouseEvent<HTMLButtonElement>) =>
72
+ view.dispatch(
73
+ view.state.tr.insertText(
74
+ event.currentTarget.value,
75
+ view.state.selection.from
76
+ )
77
+ )
78
+
79
+ return (
80
+ <StyledModal
81
+ isOpen={isOpen}
82
+ onRequestClose={handleClose}
83
+ shouldCloseOnOverlayClick={true}
84
+ >
85
+ <Container data-cy="special-characters-modal">
86
+ <ModalHeader>
87
+ <CloseButton onClick={handleClose} data-cy="modal-close-button" />
88
+ </ModalHeader>
89
+ <StyledModalBody>
90
+ <StyledModalSidebar>
91
+ <ModalSidebarHeader>
92
+ <ModalSidebarTitle>Insert special characters</ModalSidebarTitle>
93
+ </ModalSidebarHeader>
94
+ <StyledSidebarContent>
95
+ <Select<OptionType>
96
+ onChange={handleRangeChange}
97
+ classNamePrefix={'special-characters-ranges-select'}
98
+ defaultValue={unicodeRanges[0]}
99
+ options={unicodeRanges}
100
+ components={{
101
+ Option: OptionComponent,
102
+ }}
103
+ menuPosition="fixed"
104
+ />
105
+ <CharactersSetContainer>
106
+ <CharactersSet>
107
+ {generateCharacters(range[0], range[1]).map((character) => (
108
+ <Character
109
+ key={character}
110
+ value={character}
111
+ onClick={addCharacter}
112
+ data-cy="special-character"
113
+ >
114
+ {character}
115
+ </Character>
116
+ ))}
117
+ </CharactersSet>
118
+ </CharactersSetContainer>
119
+ </StyledSidebarContent>
120
+ <ButtonsContainer>
121
+ <PrimaryButton onClick={handleClose}>Close</PrimaryButton>
122
+ </ButtonsContainer>
123
+ </StyledModalSidebar>
124
+ </StyledModalBody>
125
+ </Container>
126
+ </StyledModal>
127
+ )
128
+ }
129
+
130
+ const OptionComponent: React.FC<OptionProps<OptionType, false>> = ({
131
+ innerProps,
132
+ innerRef,
133
+ data,
134
+ isFocused,
135
+ isSelected,
136
+ }) => {
137
+ return (
138
+ <OptionWrapper
139
+ {...innerProps}
140
+ ref={innerRef}
141
+ focused={isFocused}
142
+ selected={isSelected}
143
+ >
144
+ {data.label}
145
+ </OptionWrapper>
146
+ )
147
+ }
148
+
149
+ const Container = styled(ModalContainer)`
150
+ padding: 8px;
151
+ `
152
+
153
+ const StyledModalSidebar = styled(ModalSidebar)`
154
+ background: white;
155
+ width: 30vw;
156
+ `
157
+
158
+ const StyledModalBody = styled(ModalBody)`
159
+ height: 60vh;
160
+ `
161
+
162
+ const StyledSidebarContent = styled(SidebarContent)`
163
+ display: flex;
164
+ flex-direction: column;
165
+ `
166
+
167
+ const ButtonsContainer = styled(ButtonGroup)`
168
+ padding-top: ${(props) => props.theme.grid.unit * 5}px;
169
+ `
170
+
171
+ const OptionWrapper = styled.div<{ focused?: boolean; selected?: boolean }>`
172
+ padding-left: ${(props) => props.theme.grid.unit * 4}px;
173
+ padding-top: ${(props) => props.theme.grid.unit * 2}px;
174
+ padding-bottom: ${(props) => props.theme.grid.unit * 2}px;
175
+
176
+ background-color: ${(props) => {
177
+ if (props.selected) {
178
+ return props.theme.colors.background.selected
179
+ }
180
+ if (props.focused) {
181
+ return props.theme.colors.background.fifth
182
+ }
183
+ return 'transparent'
184
+ }};
185
+
186
+ &:hover {
187
+ background-color: ${(props) => props.theme.colors.background.fifth};
188
+ }
189
+ `
190
+
191
+ const CharactersSetContainer = styled.div`
192
+ flex: 1;
193
+ overflow-y: scroll;
194
+ margin: 18px 0;
195
+ padding: 8px;
196
+ `
197
+
198
+ const CharactersSet = withListNavigation(styled(IconButtonGroup)`
199
+ display: grid;
200
+ grid-template-columns: repeat(auto-fit, minmax(28px, max-content));
201
+ border: 1px solid #ddd;
202
+ `)
203
+
204
+ const Character = withNavigableListItem(styled(IconButton)`
205
+ border-bottom: 1px solid #ddd;
206
+ border-right: 1px solid #ddd;
207
+ border-radius: unset;
208
+
209
+ :hover,
210
+ :focus {
211
+ background-color: #f0f0f0 !important;
212
+ }
213
+
214
+ :active,
215
+ :focus {
216
+ color: inherit !important;
217
+ border-bottom: 1px solid #ddd !important;
218
+ border-right: 1px solid #ddd !important;
219
+ }
220
+
221
+ &&:not([disabled]):focus-visible {
222
+ outline: 1px solid ${(props) => props.theme.colors.outline.focus};
223
+ outline-offset: 2px;
224
+ }
225
+ `)
226
+
227
+ export const openInsertSpecialCharacterDialog = (view?: EditorView) => {
228
+ if (!view) {
229
+ return
230
+ }
231
+
232
+ const { state } = view
233
+ const props = getEditorProps(state)
234
+
235
+ const dialog = ReactSubView(
236
+ props,
237
+ InsertSpecialCharacterDialog,
238
+ { view },
239
+ state.doc,
240
+ () => 0,
241
+ view
242
+ )
243
+
244
+ document.body.appendChild(dialog)
245
+ }
@@ -0,0 +1,220 @@
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
+ DeleteIcon,
19
+ FormContainer,
20
+ FormRow,
21
+ Label,
22
+ PrimaryButton,
23
+ SecondaryButton,
24
+ TextField,
25
+ InputErrorText,
26
+ withFocusTrap,
27
+ } from '@manuscripts/style-guide'
28
+ import React, { useCallback, useState } from 'react'
29
+ import styled from 'styled-components'
30
+
31
+ import { allowedHref } from '../../lib/url'
32
+
33
+ const Actions = styled.div`
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: space-between;
37
+ margin-top: 16px;
38
+ `
39
+
40
+ const ActionGroup = styled.span`
41
+ display: flex;
42
+ align-items: center;
43
+
44
+ button:not(:last-of-type) {
45
+ margin-right: 4px;
46
+ }
47
+ `
48
+
49
+ const RemoveButton = styled(SecondaryButton)`
50
+ display: flex;
51
+ justify-content: space-between;
52
+ align-items: center;
53
+ gap: 8px;
54
+
55
+ svg .icon_element {
56
+ fill: #6e6e6e;
57
+ }
58
+ `
59
+
60
+ export const Open = styled.a`
61
+ display: inline-block;
62
+ margin-left: ${(props) => props.theme.grid.unit * 2}px;
63
+ margin-top: 4px;
64
+ text-transform: uppercase;
65
+ color: inherit;
66
+ font-size: ${(props) => props.theme.font.size.small};
67
+ text-decoration: none;
68
+
69
+ &:after {
70
+ content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
71
+ margin-left: 4px;
72
+ }
73
+
74
+ &&:not([disabled]):focus-visible {
75
+ outline: 2px solid ${(props) => props.theme.colors.outline.focus};
76
+ outline-offset: 3px;
77
+ }
78
+ `
79
+
80
+ const Form = withFocusTrap(styled.form``)
81
+
82
+ export interface LinkValue {
83
+ text: string
84
+ href: string
85
+ title?: string
86
+ }
87
+
88
+ export interface LinkFormProps {
89
+ onCancel: () => void
90
+ onRemove: () => void
91
+ onSave: (value: LinkValue) => void
92
+ value: LinkValue
93
+ }
94
+
95
+ export const LinkForm: React.FC<LinkFormProps> = ({
96
+ onCancel,
97
+ onRemove,
98
+ onSave,
99
+ value,
100
+ }) => {
101
+ const [href, setHref] = useState(value.href)
102
+ const [text, setText] = useState(value.text)
103
+ const [title, setTitle] = useState(value.title || '')
104
+ const [errors, setErrors] = useState<Record<string, string>>({})
105
+
106
+ const validate = useCallback((currentHref: string, currentText: string) => {
107
+ const newErrors: Record<string, string> = {}
108
+ if (!currentHref) {
109
+ newErrors.href = 'URL is required'
110
+ } else if (!allowedHref(currentHref)) {
111
+ newErrors.href = 'Please enter a valid URL'
112
+ }
113
+ if (!currentText) {
114
+ newErrors.text = 'Text is required'
115
+ }
116
+ setErrors(newErrors)
117
+ return Object.keys(newErrors).length === 0
118
+ }, [])
119
+
120
+ const handleHrefChange = useCallback(
121
+ (e: React.ChangeEvent<HTMLInputElement>) => {
122
+ const newValue = e.target.value
123
+ setHref(newValue)
124
+ if (errors.href) {
125
+ validate(newValue, text)
126
+ }
127
+ },
128
+ [errors.href, text, validate]
129
+ )
130
+
131
+ const handleTextChange = useCallback(
132
+ (e: React.ChangeEvent<HTMLInputElement>) => {
133
+ const newValue = e.target.value
134
+ setText(newValue)
135
+ if (errors.text) {
136
+ validate(href, newValue)
137
+ }
138
+ },
139
+ [errors.text, href, validate]
140
+ )
141
+
142
+ const handleSubmit = useCallback(
143
+ (event: React.FormEvent<HTMLFormElement>) => {
144
+ event.preventDefault()
145
+ if (validate(href, text)) {
146
+ onSave({ href, text, title })
147
+ }
148
+ },
149
+ [href, text, title, onSave, validate]
150
+ )
151
+
152
+ return (
153
+ <Form onSubmit={handleSubmit} noValidate={true}>
154
+ <FormContainer>
155
+ <FormRow>
156
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
157
+ <Label>URL*</Label>
158
+ {href && allowedHref(href) && (
159
+ <Open href={href} target={'_blank'} rel={'noopener'} />
160
+ )}
161
+ </div>
162
+
163
+ <TextField
164
+ type={'text'}
165
+ name={'href'}
166
+ value={href}
167
+ autoComplete={'off'}
168
+ error={!!errors.href}
169
+ onChange={handleHrefChange}
170
+ />
171
+
172
+ {errors.href && <InputErrorText>{errors.href}</InputErrorText>}
173
+ </FormRow>
174
+
175
+ <FormRow>
176
+ <Label>Text</Label>
177
+
178
+ <TextField
179
+ type={'text'}
180
+ name={'text'}
181
+ value={text}
182
+ autoComplete={'off'}
183
+ error={!!errors.text}
184
+ onChange={handleTextChange}
185
+ />
186
+ {errors.text && <InputErrorText>{errors.text}</InputErrorText>}
187
+ </FormRow>
188
+
189
+ <FormRow>
190
+ <Label>Title</Label>
191
+
192
+ <TextField
193
+ type={'text'}
194
+ name={'title'}
195
+ value={title}
196
+ autoComplete={'off'}
197
+ required={false}
198
+ onChange={(e) => setTitle(e.target.value)}
199
+ />
200
+ </FormRow>
201
+
202
+ <Actions>
203
+ <ActionGroup>
204
+ <RemoveButton type={'button'} onClick={onRemove}>
205
+ <DeleteIcon />
206
+ <span>Remove Link</span>
207
+ </RemoveButton>
208
+ </ActionGroup>
209
+
210
+ <ActionGroup>
211
+ <SecondaryButton type={'button'} onClick={onCancel}>
212
+ Cancel
213
+ </SecondaryButton>
214
+ <PrimaryButton type={'submit'}>Save</PrimaryButton>
215
+ </ActionGroup>
216
+ </Actions>
217
+ </FormContainer>
218
+ </Form>
219
+ )
220
+ }
@@ -0,0 +1,241 @@
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
+ Category,
18
+ DeleteIcon,
19
+ Dialog,
20
+ IconTextButton,
21
+ PlusIcon,
22
+ } from '@manuscripts/style-guide'
23
+ import { skipTracking } from '@manuscripts/track-changes-plugin'
24
+ import { schema } from '@manuscripts/transform'
25
+ import { Command, EditorState } from 'prosemirror-state'
26
+ import {
27
+ CellSelection,
28
+ deleteColumn,
29
+ deleteRow,
30
+ mergeCells,
31
+ selectedRect,
32
+ splitCell,
33
+ } from 'prosemirror-tables'
34
+ import { EditorView } from 'prosemirror-view'
35
+ import React, { Dispatch, useState } from 'react'
36
+ import styled from 'styled-components'
37
+
38
+ import { addColumns, addHeaderRow, addRows } from '../../commands'
39
+ import { getEditorProps } from '../../plugins/editor-props'
40
+
41
+ /**
42
+ * Return the number of selected rows/columns
43
+ */
44
+ const getSelectedCellsCount = (state: EditorState) => {
45
+ const { selection } = state
46
+ const selectedCells = { rows: 1, columns: 1 }
47
+ if (selection instanceof CellSelection) {
48
+ const rect = selectedRect(state)
49
+ selectedCells.rows = rect.bottom - rect.top
50
+ selectedCells.columns = rect.right - rect.left
51
+ }
52
+ return selectedCells
53
+ }
54
+
55
+ const isHeaderCellSelected = (state: EditorState) => {
56
+ if (state.selection instanceof CellSelection) {
57
+ const anchorNode = state.selection.$anchorCell.node(
58
+ state.selection.$anchorCell.depth
59
+ ).firstChild
60
+ const headNode = state.selection.$headCell.node(
61
+ state.selection.$headCell.depth
62
+ ).firstChild
63
+ return (
64
+ anchorNode?.type === schema.nodes.table_header ||
65
+ headNode?.type === schema.nodes.table_header
66
+ )
67
+ }
68
+
69
+ return (
70
+ state.doc.nodeAt(state.selection.from)?.type === schema.nodes.table_header
71
+ )
72
+ }
73
+
74
+ const TableChangeWarningDialog: React.FC<{
75
+ isOpen: boolean
76
+ primaryAction: () => void
77
+ secondaryAction: () => void
78
+ }> = ({ isOpen, primaryAction, secondaryAction }) => (
79
+ <Dialog
80
+ isOpen={isOpen}
81
+ category={Category.confirmation}
82
+ header={"This change can't be tracked"}
83
+ message={"This action won't be marked as change. Do you want to continue?"}
84
+ actions={{
85
+ primary: { action: primaryAction, title: 'Ok' },
86
+ secondary: { action: secondaryAction, title: 'Cancel' },
87
+ }}
88
+ />
89
+ )
90
+
91
+ export const ContextMenu: React.FC<{
92
+ view: EditorView
93
+ close: () => void
94
+ onCancelColumnDialog: () => void
95
+ }> = ({ view, close, onCancelColumnDialog }) => {
96
+ const runCommand = (command: Command, noTracking?: boolean) => {
97
+ command(view.state, (tr) =>
98
+ view.dispatch((noTracking && skipTracking(tr)) || tr)
99
+ )
100
+ close()
101
+ }
102
+
103
+ const [rowDeleteAction, setRowDeleteAction] = useState<Command>()
104
+ const [columnAction, setColumnAction] = useState<Command>()
105
+
106
+ const isCellSelectionMerged = mergeCells(view.state)
107
+ const isCellSelectionSplittable = splitCell(view.state)
108
+ const count = getSelectedCellsCount(view.state)
109
+ const rows = count.rows > 1 ? `${count.rows} rows` : `row`
110
+ const columns = count.columns > 1 ? `${count.columns} columns` : `column`
111
+ const headerPosition = isHeaderCellSelected(view.state) ? 'below' : 'above'
112
+
113
+ const showWarningForTCPlugin = (
114
+ action: Dispatch<() => Command>,
115
+ command: () => Command
116
+ ) => {
117
+ const props = getEditorProps(view.state)
118
+ if (!props.getCapabilities().editWithoutTracking) {
119
+ action(command)
120
+ } else {
121
+ runCommand(command(), true)
122
+ }
123
+ }
124
+
125
+ return (
126
+ <MenuDropdownList className={'table-ctx'}>
127
+ <ActionButton
128
+ disabled={isHeaderCellSelected(view.state)}
129
+ onClick={() => runCommand(addRows('top'))}
130
+ >
131
+ <PlusIcon /> Insert {rows} above
132
+ </ActionButton>
133
+ <ActionButton onClick={() => runCommand(addRows('bottom'))}>
134
+ <PlusIcon /> Insert {rows} below
135
+ </ActionButton>
136
+ <ActionButton
137
+ onClick={() =>
138
+ showWarningForTCPlugin(setColumnAction, () => addColumns('left'))
139
+ }
140
+ >
141
+ <PlusIcon /> Insert {columns} to the left
142
+ </ActionButton>
143
+ <ActionButton
144
+ onClick={() =>
145
+ showWarningForTCPlugin(setColumnAction, () => addColumns('right'))
146
+ }
147
+ >
148
+ <PlusIcon /> Insert {columns} to the right
149
+ </ActionButton>
150
+ <Separator />
151
+ <ActionButton
152
+ disabled={count.rows !== 1}
153
+ onClick={() => runCommand(addHeaderRow(headerPosition))}
154
+ >
155
+ <PlusIcon /> Insert header row {headerPosition}
156
+ </ActionButton>
157
+ <Separator />
158
+ <ActionButton
159
+ onClick={() =>
160
+ showWarningForTCPlugin(setRowDeleteAction, () => deleteRow)
161
+ }
162
+ >
163
+ <GrayDeleteIcon /> Delete
164
+ {isHeaderCellSelected(view.state) ? ' header ' : ''} {rows}
165
+ </ActionButton>
166
+ <ActionButton
167
+ onClick={() =>
168
+ showWarningForTCPlugin(setColumnAction, () => deleteColumn)
169
+ }
170
+ >
171
+ <GrayDeleteIcon /> Delete {columns}
172
+ </ActionButton>
173
+ {(isCellSelectionMerged || isCellSelectionSplittable) && <Separator />}
174
+ {isCellSelectionMerged && (
175
+ <ActionButton onClick={() => runCommand(mergeCells, true)}>
176
+ Merge cells
177
+ </ActionButton>
178
+ )}
179
+ {isCellSelectionSplittable && (
180
+ <ActionButton onClick={() => runCommand(splitCell, true)}>
181
+ Split cells
182
+ </ActionButton>
183
+ )}
184
+ <TableChangeWarningDialog
185
+ isOpen={!!rowDeleteAction}
186
+ primaryAction={() => {
187
+ if (rowDeleteAction) {
188
+ runCommand(rowDeleteAction, true)
189
+ setRowDeleteAction(undefined)
190
+ }
191
+ }}
192
+ secondaryAction={() => setRowDeleteAction(undefined)}
193
+ />
194
+ <TableChangeWarningDialog
195
+ isOpen={!!columnAction}
196
+ primaryAction={() => {
197
+ if (columnAction) {
198
+ runCommand(columnAction, true)
199
+ setColumnAction(undefined)
200
+ }
201
+ }}
202
+ secondaryAction={() => {
203
+ setColumnAction(undefined)
204
+ onCancelColumnDialog()
205
+ }}
206
+ />
207
+ </MenuDropdownList>
208
+ )
209
+ }
210
+
211
+ const MenuDropdownList = styled.div`
212
+ display: flex;
213
+ flex-direction: column;
214
+ `
215
+
216
+ const ActionButton = styled(IconTextButton)`
217
+ padding: 8px 16px;
218
+ justify-content: flex-start;
219
+
220
+ :hover {
221
+ background: #f2fbfc;
222
+ }
223
+ :disabled {
224
+ color: #353535;
225
+ background-color: #ffffff;
226
+ opacity: 0.4;
227
+ }
228
+ `
229
+
230
+ const Separator = styled.div`
231
+ height: 0;
232
+ border-bottom: 1px solid #e2e2e2;
233
+ margin: 4px 0;
234
+ `
235
+
236
+ const GrayDeleteIcon = styled(DeleteIcon)`
237
+ path,
238
+ rect {
239
+ fill: #333333;
240
+ }
241
+ `