@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.
- package/dist/cjs/components/toolbar/type-selector/styles.js +2 -1
- package/dist/cjs/versions.js +1 -1
- package/dist/es/components/toolbar/type-selector/styles.js +2 -1
- package/dist/es/versions.js +1 -1
- package/dist/types/components/toolbar/type-selector/styles.d.ts +2 -2
- package/dist/types/lib/footnotes.d.ts +4 -2
- package/dist/types/lib/utils.d.ts +5 -4
- package/dist/types/versions.d.ts +1 -1
- package/package.json +37 -36
- package/{dist/es/lib/statistics.js → src/__mocks__/styleMock.ts} +2 -14
- package/src/assets.ts +20 -0
- package/src/classes/collabProvider.ts +38 -0
- package/src/clipboard.ts +54 -0
- package/src/commands.ts +2109 -0
- package/src/components/ChangeHandlingForm.tsx +54 -0
- package/src/components/LanguageDropdown/index.tsx +343 -0
- package/src/components/affiliations/AffiliationForm.tsx +238 -0
- package/src/components/affiliations/AffiliationItem.tsx +151 -0
- package/src/components/affiliations/AffiliationList.tsx +71 -0
- package/src/components/affiliations/AffiliationsModal.tsx +676 -0
- package/src/components/affiliations/AffiliationsPanel.tsx +76 -0
- package/src/components/affiliations/CreateAffiliationModal.tsx +135 -0
- package/src/components/authors/AuthorDetailsForm.tsx +438 -0
- package/src/components/authors/AuthorList.tsx +81 -0
- package/src/components/authors/AuthorsModal.tsx +642 -0
- package/src/components/authors/AuthorsPanel.tsx +69 -0
- package/src/components/authors/CreateAuthorModal.tsx +172 -0
- package/src/components/authors/CreditDrawer.tsx +71 -0
- package/src/components/authors/DraggableAuthor.tsx +228 -0
- package/src/components/authors/useManageAffiliations.tsx +77 -0
- package/src/components/authors/useManageCredit.tsx +63 -0
- package/src/components/authors-affiliations/AuthorsAndAffiliationsModals.tsx +170 -0
- package/src/components/authors-affiliations/GenericPanel.tsx +164 -0
- package/src/components/authors-affiliations/ModalTabs.tsx +100 -0
- package/src/components/awards/AwardForm.tsx +338 -0
- package/src/components/awards/AwardModal.tsx +120 -0
- package/src/components/awards/DeleteAwardDiaolog.tsx +52 -0
- package/src/components/dialog/ConfirmationDialog.tsx +131 -0
- package/src/components/form/CreateModalStyles.tsx +37 -0
- package/src/components/form/FormFooter.tsx +54 -0
- package/src/components/form/FormPlaceholder.tsx +71 -0
- package/src/components/form/ModalFormActions.tsx +88 -0
- package/src/components/form/UnsavedLabel.tsx +49 -0
- package/src/components/hooks/use-debounce.ts +56 -0
- package/src/components/hooks/useAffiliationShowsErrorIndicator.ts +51 -0
- package/src/components/hooks/useAuthorShowsErrorIndicator.ts +73 -0
- package/src/components/keyboard-shortcuts-modal/FormattedShortcut.tsx +56 -0
- package/src/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.tsx +283 -0
- package/src/components/keyboard-shortcuts-modal/keyboard-shortcuts.ts +304 -0
- package/src/components/keywords/AddKeywordInline.tsx +284 -0
- package/src/components/keywords/DeleteKeywordDialog.tsx +52 -0
- package/src/components/modal-drawer/GenericDrawer.tsx +65 -0
- package/src/components/modal-drawer/GenericDrawerGroup.tsx +125 -0
- package/src/components/outline/DraggableTree.tsx +438 -0
- package/src/components/outline/ManuscriptOutline.tsx +127 -0
- package/src/components/outline/Outline.tsx +141 -0
- package/src/components/references/BibliographyItemSource.ts +80 -0
- package/src/components/references/CitationEditor.tsx +301 -0
- package/src/components/references/CitationViewer.tsx +62 -0
- package/src/components/references/ImportBibliographyForm.tsx +232 -0
- package/src/components/references/ImportBibliographyModal.tsx +159 -0
- package/src/components/references/ReferenceForm/PersonDropDown.tsx +93 -0
- package/src/components/references/ReferenceForm/ReferenceForm.tsx +701 -0
- package/src/components/references/ReferenceForm/config.ts +133 -0
- package/src/components/references/ReferenceForm/styled-components.ts +156 -0
- package/src/components/references/ReferenceLine.tsx +43 -0
- package/src/components/references/ReferenceSearch.tsx +163 -0
- package/src/components/references/ReferenceSearchResults.tsx +113 -0
- package/src/components/references/ReferenceSearchResultsPlaceholder.tsx +71 -0
- package/src/components/references/ReferenceSearchSection.tsx +124 -0
- package/src/components/references/ReferencesEditor.tsx +61 -0
- package/src/components/references/ReferencesModal.tsx +352 -0
- package/src/components/references/SearchInput.tsx +98 -0
- package/src/components/toolbar/InsertEmbedDialog.tsx +222 -0
- package/src/components/toolbar/InsertTableDialog.tsx +208 -0
- package/src/components/toolbar/ListMenuItem.tsx +203 -0
- package/src/components/toolbar/helpers.ts +547 -0
- package/src/components/toolbar/type-selector/OptionComponent.tsx +89 -0
- package/src/components/toolbar/type-selector/TypeSelector.tsx +157 -0
- package/src/components/toolbar/type-selector/styles.ts +79 -0
- package/src/components/views/CrossReferenceItems.tsx +199 -0
- package/src/components/views/DeleteEmbedDialog.tsx +83 -0
- package/src/components/views/DeleteFootnoteDialog.tsx +54 -0
- package/src/components/views/FigureDropdown.tsx +423 -0
- package/src/components/views/FootnotesSelector.tsx +255 -0
- package/src/components/views/InsertSpecialCharacter.tsx +245 -0
- package/src/components/views/LinkForm.tsx +220 -0
- package/src/components/views/TableCellContextMenu.tsx +241 -0
- package/src/configs/ManuscriptsEditor.ts +140 -0
- package/src/configs/editor-plugins.ts +119 -0
- package/src/configs/editor-views.ts +139 -0
- package/src/icons.ts +62 -0
- package/src/index.ts +65 -0
- package/src/keys/highlight.ts +65 -0
- package/src/keys/index.ts +33 -0
- package/src/keys/keyword.ts +69 -0
- package/src/keys/list.ts +156 -0
- package/src/keys/misc.ts +112 -0
- package/src/keys/title.ts +221 -0
- package/src/lib/__tests__/__fixtures__/doc.json +388 -0
- package/src/lib/__tests__/__snapshots__/paste.test.ts.snap +24 -0
- package/src/lib/__tests__/authors.test.ts +202 -0
- package/src/lib/__tests__/helpers.test.ts +46 -0
- package/src/lib/__tests__/paste.test.ts +57 -0
- package/src/lib/__tests__/plugins.test.ts +176 -0
- package/src/lib/__tests__/url.test.ts +31 -0
- package/src/lib/array-reducer.ts +68 -0
- package/src/lib/authors-and-affiliations.ts +81 -0
- package/src/lib/authors.ts +67 -0
- package/src/lib/capabilities.ts +55 -0
- package/src/lib/codemirror.ts +37 -0
- package/src/lib/comments.ts +123 -0
- package/src/lib/context-menu.ts +745 -0
- package/src/lib/copy.ts +30 -0
- package/src/lib/crossref.ts +90 -0
- package/src/lib/dnd.ts +29 -0
- package/src/lib/doc.ts +167 -0
- package/src/lib/dompurify.ts +28 -0
- package/src/lib/files.ts +180 -0
- package/src/lib/footnotes.ts +83 -0
- package/src/lib/get-media-type.ts +105 -0
- package/src/lib/helpers.ts +199 -0
- package/src/lib/hierarchy.ts +117 -0
- package/src/lib/languages.ts +37 -0
- package/src/lib/lists.ts +56 -0
- package/src/lib/math.ts +111 -0
- package/src/lib/media.ts +334 -0
- package/src/lib/navigation-utils.ts +235 -0
- package/src/lib/normalize.ts +70 -0
- package/src/lib/paste.ts +304 -0
- package/src/lib/placeholder.ts +32 -0
- package/src/lib/platform.ts +18 -0
- package/src/lib/plugins.ts +22 -0
- package/src/lib/popper.ts +180 -0
- package/src/lib/position-menu.ts +184 -0
- package/src/lib/references.ts +94 -0
- package/src/lib/template.ts +29 -0
- package/src/lib/track-changes-utils.ts +34 -0
- package/{dist/types/lib/statistics.d.ts → src/lib/url.ts} +11 -8
- package/src/lib/use-do-with-debounce.ts +39 -0
- package/src/lib/utils.ts +323 -0
- package/src/lib/view.ts +100 -0
- package/src/menus.tsx +684 -0
- package/src/node-type-icons.tsx +83 -0
- package/src/plugins/__tests__/__fixtures__/deleted-footnote.json +140 -0
- package/src/plugins/__tests__/__fixtures__/inserted-footnote.json +153 -0
- package/src/plugins/__tests__/footnotes.test.ts +67 -0
- package/src/plugins/accessibility_element.ts +219 -0
- package/src/plugins/add-subtitle.ts +102 -0
- package/src/plugins/affiliations.ts +136 -0
- package/src/plugins/alt-titles.ts +219 -0
- package/src/plugins/bibliography.ts +235 -0
- package/src/plugins/comments.ts +328 -0
- package/src/plugins/cross-references.ts +70 -0
- package/src/plugins/detect-inconsistency/detect-inconsistency-utils.ts +102 -0
- package/src/plugins/detect-inconsistency/index.ts +55 -0
- package/src/plugins/detect-inconsistency/validators.ts +283 -0
- package/src/plugins/doi.ts +66 -0
- package/src/plugins/editor-props.ts +39 -0
- package/src/plugins/elements.ts +60 -0
- package/src/plugins/footnotes.ts +298 -0
- package/src/plugins/link.ts +53 -0
- package/src/plugins/lock-body.ts +136 -0
- package/src/plugins/move-node.ts +55 -0
- package/src/plugins/objects.ts +125 -0
- package/src/plugins/paragraphs.ts +90 -0
- package/src/plugins/persist.ts +64 -0
- package/src/plugins/persistent-cursor.ts +66 -0
- package/src/plugins/placeholder.ts +219 -0
- package/src/plugins/prevent-empty.ts +50 -0
- package/src/plugins/search-replace/index.ts +157 -0
- package/src/plugins/search-replace/lib.ts +95 -0
- package/src/plugins/section_category.ts +212 -0
- package/src/plugins/section_title/autocompletion.ts +87 -0
- package/src/plugins/section_title/index.ts +105 -0
- package/src/plugins/selected-suggestion.ts +243 -0
- package/src/plugins/tables-cursor-fix.ts +143 -0
- package/src/plugins/translations.ts +263 -0
- package/src/rules.ts +60 -0
- package/src/selection.ts +112 -0
- package/src/testing/default-capabilities.ts +48 -0
- package/src/testing/default-editor-data.ts +73 -0
- package/src/testing/empty-editor-doc.json +62 -0
- package/src/testing/polyfill-dom.ts +43 -0
- package/src/testing/section-categories.json +161 -0
- package/src/testing/setup-editor.ts +55 -0
- package/src/tests.ts +42 -0
- package/src/toolbar.tsx +259 -0
- package/src/types/@citation-js/core.d.ts +16 -0
- package/src/types/@citation-js/index.d.ts +16 -0
- package/src/types/@citation-js/plugin-bibtex.d.ts +16 -0
- package/src/types/@citation-js/plugin-csl.d.ts +16 -0
- package/src/types/@citation-js/plugin-doi.d.ts +16 -0
- package/src/types/@citation-js/plugin-enw.d.ts +16 -0
- package/src/types/@citation-js/plugin-pubmed.d.ts +16 -0
- package/src/types/@citation-js/plugin-ris.d.ts +16 -0
- package/src/types/citeproc.d.ts +124 -0
- package/src/types/css.d.ts +16 -0
- package/src/types/json.d.ts +17 -0
- package/src/types/prosemirror-dev-tools.d.ts +16 -0
- package/src/types/prosemirror-dropcursor.d.ts +16 -0
- package/src/types/styled-components.d.ts +23 -0
- package/src/types.ts +49 -0
- package/src/useEditor.ts +244 -0
- package/src/versions.ts +3 -0
- package/src/views/ReactSubView.tsx +145 -0
- package/src/views/abstracts.ts +46 -0
- package/src/views/accessibility_element.ts +86 -0
- package/src/views/affiliations.ts +254 -0
- package/src/views/alt_title.ts +85 -0
- package/src/views/alt_titles_section.ts +97 -0
- package/src/views/attachment.ts +160 -0
- package/src/views/attachments.ts +72 -0
- package/src/views/author_notes.ts +96 -0
- package/src/views/award.ts +194 -0
- package/src/views/awards.ts +59 -0
- package/src/views/base_node_view.ts +85 -0
- package/src/views/bibliography_element.ts +313 -0
- package/src/views/block_view.ts +100 -0
- package/src/views/blockquote_element.ts +26 -0
- package/src/views/blockquote_element_editable.ts +24 -0
- package/src/views/box_element.ts +33 -0
- package/src/views/caption.ts +37 -0
- package/src/views/caption_title.ts +33 -0
- package/src/views/citation.ts +67 -0
- package/src/views/citation_editable.ts +312 -0
- package/src/views/contributors.ts +341 -0
- package/src/views/creators.ts +82 -0
- package/src/views/cross_reference.ts +74 -0
- package/src/views/cross_reference_editable.ts +113 -0
- package/src/views/editable_block.ts +141 -0
- package/src/views/embed.ts +345 -0
- package/src/views/empty.ts +26 -0
- package/src/views/equation.ts +47 -0
- package/src/views/equation_editable.ts +84 -0
- package/src/views/equation_element.ts +29 -0
- package/src/views/equation_element_editable.ts +21 -0
- package/src/views/figure.ts +84 -0
- package/src/views/figure_editable.ts +361 -0
- package/src/views/figure_element.ts +197 -0
- package/src/views/figure_element_editable.ts +21 -0
- package/src/views/footnote.ts +236 -0
- package/src/views/footnotes_element.ts +54 -0
- package/src/views/general_table_footnote.ts +140 -0
- package/src/views/hero_image.ts +76 -0
- package/src/views/hero_image_editable.ts +21 -0
- package/src/views/image_element.ts +373 -0
- package/src/views/image_element_editable.ts +21 -0
- package/src/views/inline_equation.ts +47 -0
- package/src/views/inline_equation_editable.ts +70 -0
- package/src/views/inline_footnote.ts +264 -0
- package/src/views/inline_footnote_editable.ts +20 -0
- package/src/views/keyword.ts +127 -0
- package/src/views/keyword_group.ts +109 -0
- package/src/views/link.ts +27 -0
- package/src/views/link_editable.ts +169 -0
- package/src/views/list.ts +46 -0
- package/src/views/list_item.ts +26 -0
- package/src/views/paragraph.ts +26 -0
- package/src/views/paragraph_editable.ts +21 -0
- package/src/views/placeholder.ts +35 -0
- package/src/views/placeholder_element.ts +38 -0
- package/src/views/placeholder_element_editable.ts +21 -0
- package/src/views/pullquote_element.ts +106 -0
- package/src/views/pullquote_element_editable.ts +24 -0
- package/src/views/quote_image_editable.ts +84 -0
- package/src/views/section.ts +64 -0
- package/src/views/section_label.ts +20 -0
- package/src/views/section_title.ts +87 -0
- package/src/views/section_title_editable.ts +21 -0
- package/src/views/subtitle.ts +42 -0
- package/src/views/subtitle_editable.ts +20 -0
- package/src/views/subtitles.ts +34 -0
- package/src/views/subtitles_editable.ts +21 -0
- package/src/views/supplement.ts +236 -0
- package/src/views/supplement_editable.ts +20 -0
- package/src/views/supplements.ts +65 -0
- package/src/views/supplements_editable.ts +21 -0
- package/src/views/table_cell.ts +150 -0
- package/src/views/table_element.ts +33 -0
- package/src/views/table_element_editable.ts +21 -0
- package/src/views/table_element_footer.ts +41 -0
- package/src/views/title.ts +46 -0
- package/src/views/title_editable.ts +20 -0
- package/src/views/trans_graphical_abstract.ts +55 -0
- package/src/views/trans_graphical_abstract_editable.ts +21 -0
- package/src/views/translated_abstract.ts +53 -0
- package/src/views/translated_abstract_editable.ts +21 -0
- package/dist/cjs/lib/statistics.js +0 -38
package/src/lib/media.ts
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
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
|
+
ManuscriptEditorView,
|
|
19
|
+
ManuscriptNode,
|
|
20
|
+
schema,
|
|
21
|
+
} from '@manuscripts/transform'
|
|
22
|
+
|
|
23
|
+
import { openEmbedDialog } from '../components/toolbar/InsertEmbedDialog'
|
|
24
|
+
import { handleEnterKey } from './navigation-utils'
|
|
25
|
+
import {
|
|
26
|
+
FigureOptions,
|
|
27
|
+
FigureOptionsProps,
|
|
28
|
+
} from '../components/views/FigureDropdown'
|
|
29
|
+
import { EditorProps } from '../configs/ManuscriptsEditor'
|
|
30
|
+
import { fileCorruptedIcon } from '../icons'
|
|
31
|
+
import { Trackable } from '../types'
|
|
32
|
+
import ReactSubView from '../views/ReactSubView'
|
|
33
|
+
import { FileAttachment } from './files'
|
|
34
|
+
|
|
35
|
+
export const createUnsupportedFormat = (
|
|
36
|
+
filename: string,
|
|
37
|
+
canEdit: boolean
|
|
38
|
+
): HTMLElement => {
|
|
39
|
+
const element = document.createElement('div')
|
|
40
|
+
element.classList.add('figure', 'placeholder')
|
|
41
|
+
|
|
42
|
+
const instructions = document.createElement('div')
|
|
43
|
+
instructions.classList.add('instructions')
|
|
44
|
+
|
|
45
|
+
const iconHtml = fileCorruptedIcon
|
|
46
|
+
|
|
47
|
+
instructions.innerHTML = `
|
|
48
|
+
<div>
|
|
49
|
+
<div class="unsupported-icon-wrapper">${iconHtml}</div>
|
|
50
|
+
<div>${filename}</div>
|
|
51
|
+
<div class="unsupported-format-label">
|
|
52
|
+
Unsupported file format
|
|
53
|
+
</div>
|
|
54
|
+
<div>
|
|
55
|
+
${canEdit ? 'Click to add media' : 'No media here yet…'}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
`
|
|
59
|
+
|
|
60
|
+
element.appendChild(instructions)
|
|
61
|
+
return element
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export enum MediaType {
|
|
65
|
+
Media = 'media',
|
|
66
|
+
Figure = 'figure',
|
|
67
|
+
ExternalLink = 'external_link',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const MediaLabels: Record<MediaType, string> = {
|
|
71
|
+
[MediaType.Media]: 'media',
|
|
72
|
+
[MediaType.Figure]: 'figure',
|
|
73
|
+
[MediaType.ExternalLink]: 'a file to link',
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const createMediaPlaceholder = (
|
|
77
|
+
mediaType: MediaType = MediaType.Media,
|
|
78
|
+
view?: ManuscriptEditorView,
|
|
79
|
+
getPos?: () => number,
|
|
80
|
+
props?: EditorProps
|
|
81
|
+
): HTMLElement => {
|
|
82
|
+
const element = document.createElement('div')
|
|
83
|
+
element.classList.add('figure', 'placeholder')
|
|
84
|
+
element.tabIndex = 0
|
|
85
|
+
const instructions = document.createElement('div')
|
|
86
|
+
instructions.classList.add('instructions')
|
|
87
|
+
|
|
88
|
+
instructions.innerHTML = `
|
|
89
|
+
<div class="placeholder-content">
|
|
90
|
+
<p>Drag or click here to upload ${MediaLabels[mediaType]} <br>
|
|
91
|
+
${
|
|
92
|
+
mediaType === MediaType.ExternalLink
|
|
93
|
+
? ` or drag items here from the file <a data-action='open-other-files'>'Other files'</a> in the inspector.</p>`
|
|
94
|
+
: `or drag items here from the file inspector tabs <br>
|
|
95
|
+
<a data-action='open-other-files'>'Other files'</a> |
|
|
96
|
+
<a data-action='open-supplement-files'>'Supplements'</a>`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
${
|
|
100
|
+
mediaType === MediaType.Media && view && getPos
|
|
101
|
+
? "| <a data-action='add-external-link'>'External link'</a>"
|
|
102
|
+
: ''
|
|
103
|
+
}
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
`
|
|
107
|
+
|
|
108
|
+
if (mediaType === MediaType.Media && view && getPos) {
|
|
109
|
+
const embedLink = instructions.querySelector(
|
|
110
|
+
"[data-action='add-external-link']"
|
|
111
|
+
) as HTMLElement
|
|
112
|
+
embedLink.tabIndex = 0
|
|
113
|
+
if (embedLink) {
|
|
114
|
+
embedLink.addEventListener('click', (e) => {
|
|
115
|
+
e.stopPropagation()
|
|
116
|
+
e.preventDefault()
|
|
117
|
+
openEmbedDialog(view, getPos())
|
|
118
|
+
})
|
|
119
|
+
embedLink.addEventListener(
|
|
120
|
+
'keydown',
|
|
121
|
+
handleEnterKey((e) => {
|
|
122
|
+
e.stopPropagation()
|
|
123
|
+
e.preventDefault()
|
|
124
|
+
openEmbedDialog(view, getPos())
|
|
125
|
+
})
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const links = instructions.querySelectorAll<HTMLElement>(
|
|
131
|
+
`[data-action='open-other-files'], [data-action='open-supplement-files'] `
|
|
132
|
+
)
|
|
133
|
+
links.forEach((link) => {
|
|
134
|
+
link.tabIndex = 0
|
|
135
|
+
link.addEventListener(
|
|
136
|
+
'keydown',
|
|
137
|
+
handleEnterKey((event) => {
|
|
138
|
+
event.preventDefault()
|
|
139
|
+
event.stopPropagation()
|
|
140
|
+
props?.onEditorClick(event)
|
|
141
|
+
})
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
element.appendChild(instructions)
|
|
146
|
+
return element
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface FileHandlers {
|
|
150
|
+
handleDownload?: () => void
|
|
151
|
+
handleUpload?: () => void
|
|
152
|
+
handleReplace?: (file: FileAttachment, isSupplement?: boolean) => void
|
|
153
|
+
handleReplaceEmbed?: () => void
|
|
154
|
+
handleDetach?: () => void
|
|
155
|
+
handleDelete?: () => void
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
type Handleable = { href?: string; src?: string }
|
|
159
|
+
|
|
160
|
+
export const createFileHandlers = <T extends Handleable>(
|
|
161
|
+
attrs: T,
|
|
162
|
+
fileAttrKey: keyof Handleable,
|
|
163
|
+
view: ManuscriptEditorView,
|
|
164
|
+
props: EditorProps,
|
|
165
|
+
setAttr: (value: string) => void
|
|
166
|
+
): FileHandlers => {
|
|
167
|
+
const handlers: FileHandlers = {}
|
|
168
|
+
|
|
169
|
+
const controlAttr = attrs[fileAttrKey]
|
|
170
|
+
const files = props.getFiles()
|
|
171
|
+
const file = controlAttr && files.find((f) => f.id === controlAttr)
|
|
172
|
+
const can = props.getCapabilities()
|
|
173
|
+
|
|
174
|
+
if (controlAttr && file) {
|
|
175
|
+
handlers.handleDownload = () => {
|
|
176
|
+
props.fileManagement.download(file)
|
|
177
|
+
}
|
|
178
|
+
handlers.handleDetach = () => {
|
|
179
|
+
setAttr('')
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (can.replaceFile) {
|
|
184
|
+
handlers.handleReplace = (file: FileAttachment, isSupplement = false) => {
|
|
185
|
+
setAttr(file.id)
|
|
186
|
+
if (isSupplement) {
|
|
187
|
+
const tr = view.state.tr
|
|
188
|
+
view.state.doc.descendants((node, pos) => {
|
|
189
|
+
if (node.type === schema.nodes.supplement) {
|
|
190
|
+
const attr = node.attrs[fileAttrKey]
|
|
191
|
+
if (attr === file.id) {
|
|
192
|
+
tr.delete(pos, pos + node.nodeSize)
|
|
193
|
+
view.dispatch(tr)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
node.type !== schema.nodes.supplements &&
|
|
199
|
+
node.type !== schema.nodes.manuscript
|
|
200
|
+
) {
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return handlers
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const createReactTools = <T extends ManuscriptNode>(
|
|
212
|
+
node: Trackable<T>,
|
|
213
|
+
view: ManuscriptEditorView,
|
|
214
|
+
getPos: () => number,
|
|
215
|
+
props: EditorProps,
|
|
216
|
+
handlers: FileHandlers,
|
|
217
|
+
isEmbed: boolean,
|
|
218
|
+
hasSiblings: () => boolean
|
|
219
|
+
): HTMLDivElement | null => {
|
|
220
|
+
if (!props.dispatch || !props.theme) {
|
|
221
|
+
return null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const can = props.getCapabilities()
|
|
225
|
+
|
|
226
|
+
const componentProps: FigureOptionsProps = {
|
|
227
|
+
can,
|
|
228
|
+
getDoc: () => view.state.doc,
|
|
229
|
+
getFiles: props.getFiles,
|
|
230
|
+
onDownload: handlers.handleDownload,
|
|
231
|
+
onUpload: handlers.handleUpload,
|
|
232
|
+
onDetach: handlers.handleDetach,
|
|
233
|
+
onReplace: handlers.handleReplace,
|
|
234
|
+
onReplaceEmbed: handlers.handleReplaceEmbed,
|
|
235
|
+
onDelete: handlers.handleDelete,
|
|
236
|
+
isEmbed,
|
|
237
|
+
hasSiblings,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return ReactSubView(props, FigureOptions, componentProps, node, getPos, view)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const createFileUploader = (
|
|
244
|
+
handler: (file: File) => Promise<void>,
|
|
245
|
+
accept = '*/*'
|
|
246
|
+
) => {
|
|
247
|
+
const handleFileChange = async (e: Event) => {
|
|
248
|
+
const target = e.target as HTMLInputElement
|
|
249
|
+
if (target && target.files && target.files.length) {
|
|
250
|
+
await handler(target.files[0])
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const input = document.createElement('input')
|
|
255
|
+
input.accept = accept
|
|
256
|
+
input.type = 'file'
|
|
257
|
+
input.addEventListener('change', handleFileChange)
|
|
258
|
+
|
|
259
|
+
return () => input.click()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export const addInteractionHandlers = (
|
|
263
|
+
element: HTMLElement,
|
|
264
|
+
uploadFn: (file: File) => Promise<void>,
|
|
265
|
+
accept = '*/*'
|
|
266
|
+
): void => {
|
|
267
|
+
const handlePlaceholderInteraction = (target: HTMLElement) => {
|
|
268
|
+
if (target.dataset && target.dataset.action) {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
const input = document.createElement('input')
|
|
272
|
+
input.type = 'file'
|
|
273
|
+
input.accept = accept
|
|
274
|
+
input.addEventListener('change', async (e) => {
|
|
275
|
+
const files = (e.target as HTMLInputElement).files
|
|
276
|
+
if (files && files.length > 0) {
|
|
277
|
+
await uploadFn(files[0])
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
input.click()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const handlePlaceholderClick = (event: Event) => {
|
|
284
|
+
const target = event.target as HTMLElement
|
|
285
|
+
handlePlaceholderInteraction(target)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
element.addEventListener('click', handlePlaceholderClick)
|
|
289
|
+
element.addEventListener(
|
|
290
|
+
'keydown',
|
|
291
|
+
handleEnterKey(() => {
|
|
292
|
+
const target = document.activeElement as HTMLElement
|
|
293
|
+
handlePlaceholderInteraction(target)
|
|
294
|
+
})
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
element.addEventListener('mouseenter', () => {
|
|
298
|
+
element.classList.toggle('over', true)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
element.addEventListener('mouseleave', () => {
|
|
302
|
+
element.classList.toggle('over', false)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
element.addEventListener('dragenter', (event) => {
|
|
306
|
+
event.preventDefault()
|
|
307
|
+
element.classList.toggle('over', true)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
element.addEventListener('dragleave', (event) => {
|
|
311
|
+
event.preventDefault()
|
|
312
|
+
element.classList.toggle('over', false)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
element.addEventListener('dragover', (e) => {
|
|
316
|
+
const dragEvent = e as DragEvent
|
|
317
|
+
if (dragEvent.dataTransfer && dragEvent.dataTransfer.items) {
|
|
318
|
+
for (const item of dragEvent.dataTransfer.items) {
|
|
319
|
+
if (item.kind === 'file') {
|
|
320
|
+
e.preventDefault()
|
|
321
|
+
dragEvent.dataTransfer.dropEffect = 'copy'
|
|
322
|
+
break
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
element.addEventListener('drop', async (e) => {
|
|
329
|
+
if (e.dataTransfer && e.dataTransfer.files.length) {
|
|
330
|
+
e.preventDefault()
|
|
331
|
+
await uploadFn(e.dataTransfer.files[0])
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
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 {
|
|
18
|
+
ManuscriptEditorState,
|
|
19
|
+
ManuscriptEditorView,
|
|
20
|
+
} from '@manuscripts/transform'
|
|
21
|
+
import { EditorView } from 'prosemirror-view'
|
|
22
|
+
import { Selection } from 'prosemirror-state'
|
|
23
|
+
|
|
24
|
+
import { Dispatch } from '../commands'
|
|
25
|
+
import { findParentNodeWithIdValue } from './utils'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* TODO: Reuse this utility in other areas related to navigation implemented in LEAN-5090
|
|
29
|
+
* (e.g., inspector components, toolbar and other components with similar navigation patterns)
|
|
30
|
+
*/
|
|
31
|
+
export function focusNextElement(
|
|
32
|
+
elements: Element[] | HTMLElement[],
|
|
33
|
+
currentIndex: number,
|
|
34
|
+
direction: 'forward' | 'backward'
|
|
35
|
+
): void {
|
|
36
|
+
const nextIndex =
|
|
37
|
+
direction === 'forward'
|
|
38
|
+
? (currentIndex + 1) % elements.length
|
|
39
|
+
: (currentIndex - 1 + elements.length) % elements.length
|
|
40
|
+
const nextElement = elements[nextIndex] as HTMLElement
|
|
41
|
+
nextElement?.focus()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a keydown handler that triggers an action when Enter key is pressed.
|
|
46
|
+
* Automatically prevents default behavior.
|
|
47
|
+
*/
|
|
48
|
+
export function handleEnterKey(action: (event: KeyboardEvent) => void) {
|
|
49
|
+
return (event: KeyboardEvent) => {
|
|
50
|
+
if (event.key === 'Enter') {
|
|
51
|
+
event.preventDefault()
|
|
52
|
+
action(event)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handles arrow key navigation between elements.
|
|
58
|
+
export function handleArrowNavigation(
|
|
59
|
+
event: KeyboardEvent,
|
|
60
|
+
elements: HTMLElement[],
|
|
61
|
+
currentElement: HTMLElement,
|
|
62
|
+
keys: {
|
|
63
|
+
forward: 'ArrowDown' | 'ArrowRight'
|
|
64
|
+
backward: 'ArrowUp' | 'ArrowLeft'
|
|
65
|
+
}
|
|
66
|
+
): void {
|
|
67
|
+
const { forward, backward } = keys
|
|
68
|
+
if (event.key !== forward && event.key !== backward) {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
event.preventDefault()
|
|
73
|
+
if (elements.length === 0) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
const currentIndex = elements.indexOf(currentElement)
|
|
77
|
+
if (currentIndex === -1) {
|
|
78
|
+
// Focus first element when current element not found
|
|
79
|
+
elements[0]?.focus()
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const direction = event.key === forward ? 'forward' : 'backward'
|
|
84
|
+
focusNextElement(elements, currentIndex, direction)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type ArrowKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight'
|
|
88
|
+
type SupportedKey = ArrowKey | 'Enter' | 'Escape' | 'Tab'
|
|
89
|
+
|
|
90
|
+
type KeyHandlers = Partial<Record<SupportedKey, (event: KeyboardEvent) => void>>
|
|
91
|
+
|
|
92
|
+
type NavigationConfig = {
|
|
93
|
+
/** Array of focusable items within the container */
|
|
94
|
+
getItems: () => HTMLElement[]
|
|
95
|
+
/** Arrow key configuration for navigation */
|
|
96
|
+
arrowKeys: {
|
|
97
|
+
forward: 'ArrowDown' | 'ArrowRight'
|
|
98
|
+
backward: 'ArrowUp' | 'ArrowLeft'
|
|
99
|
+
}
|
|
100
|
+
/** Optional override for the current focused element */
|
|
101
|
+
getCurrentElement?: (event: KeyboardEvent) => HTMLElement | null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type KeyboardInteractionOptions = {
|
|
105
|
+
/** The container element (menu, dropdown, list, etc.) or window/document */
|
|
106
|
+
container: HTMLElement | Window | Document
|
|
107
|
+
/** Optional keyboard navigation capability (within items) */
|
|
108
|
+
navigation?: NavigationConfig
|
|
109
|
+
/** Optional custom key handlers */
|
|
110
|
+
additionalKeys?: KeyHandlers
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function createKeyboardInteraction(
|
|
114
|
+
options: KeyboardInteractionOptions
|
|
115
|
+
): () => void {
|
|
116
|
+
const { container, additionalKeys, navigation } = options
|
|
117
|
+
|
|
118
|
+
const handleKeydown: EventListener = (event) => {
|
|
119
|
+
const e = event as KeyboardEvent
|
|
120
|
+
|
|
121
|
+
const key = e.key as SupportedKey
|
|
122
|
+
const handler = additionalKeys?.[key]
|
|
123
|
+
|
|
124
|
+
if (handler) {
|
|
125
|
+
e.preventDefault()
|
|
126
|
+
handler(e)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle navigation
|
|
131
|
+
if (!navigation) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
const { getItems, arrowKeys, getCurrentElement } = navigation
|
|
135
|
+
|
|
136
|
+
const currentElement = getCurrentElement
|
|
137
|
+
? getCurrentElement(e)
|
|
138
|
+
: (e.target as HTMLElement)
|
|
139
|
+
|
|
140
|
+
if (!currentElement) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const list = getItems()
|
|
145
|
+
if (!list.length) {
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
handleArrowNavigation(e, list, currentElement, arrowKeys)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
container.addEventListener('keydown', handleKeydown)
|
|
152
|
+
|
|
153
|
+
return () => {
|
|
154
|
+
container.removeEventListener('keydown', handleKeydown)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const focusNearestElement = (
|
|
159
|
+
state: ManuscriptEditorState,
|
|
160
|
+
dispatch?: Dispatch,
|
|
161
|
+
view?: ManuscriptEditorView
|
|
162
|
+
) => {
|
|
163
|
+
if (!view) {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
// Only intercept when editor text is the current focus context
|
|
167
|
+
const active = document.activeElement
|
|
168
|
+
if (!active || !active.classList.contains('manuscript-editor')) {
|
|
169
|
+
return false // let browser handle Tab
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const { from } = view.state.selection
|
|
173
|
+
const coords = view.coordsAtPos(from)
|
|
174
|
+
const container = getCursorContainer(view)
|
|
175
|
+
let target = findNearestTabbable(container, coords.top)
|
|
176
|
+
// TODO: Consider a more generic solution based on hierarchy-walking to find the nearest tabbable element.
|
|
177
|
+
// Currently only looks one level up, which is sufficient to resolve focus issue when inside supplement item children.
|
|
178
|
+
if (!target) {
|
|
179
|
+
const parent = findParentNodeWithIdValue(view.state.selection)
|
|
180
|
+
|
|
181
|
+
const grandparent = parent
|
|
182
|
+
? findParentNodeWithIdValue({
|
|
183
|
+
$from: view.state.doc.resolve(parent.pos),
|
|
184
|
+
} as Selection)
|
|
185
|
+
: null
|
|
186
|
+
if (grandparent) {
|
|
187
|
+
const dom = view.nodeDOM(grandparent.pos)
|
|
188
|
+
|
|
189
|
+
if (dom instanceof HTMLElement) {
|
|
190
|
+
target = findNearestTabbable(dom, coords.top)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!target) {
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
target.focus()
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function getCursorContainer(view: EditorView): HTMLElement {
|
|
203
|
+
const scoped = findParentNodeWithIdValue(view.state.selection)
|
|
204
|
+
if (scoped) {
|
|
205
|
+
const dom = view.nodeDOM(scoped.pos)
|
|
206
|
+
if (dom instanceof HTMLElement) {
|
|
207
|
+
return dom
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return view.dom as HTMLElement
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function findNearestTabbable(
|
|
215
|
+
container: HTMLElement,
|
|
216
|
+
verticalPosition: number
|
|
217
|
+
): HTMLElement | null {
|
|
218
|
+
const tabbables = container.querySelectorAll<HTMLElement>(
|
|
219
|
+
'a[href], button, [tabindex]:not([tabindex="-1"])'
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
let target: HTMLElement | null = null
|
|
223
|
+
let minDistance: number | null = null
|
|
224
|
+
|
|
225
|
+
tabbables.forEach((el) => {
|
|
226
|
+
const rect = el.getBoundingClientRect()
|
|
227
|
+
const distance = Math.abs(rect.top - verticalPosition)
|
|
228
|
+
if (minDistance === null || distance < minDistance) {
|
|
229
|
+
minDistance = distance
|
|
230
|
+
target = el
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
return target
|
|
235
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { generateNodeID, Nodes, schema } from '@manuscripts/transform'
|
|
18
|
+
|
|
19
|
+
import { AffiliationAttrs, ContributorAttrs } from './authors'
|
|
20
|
+
|
|
21
|
+
const trim = (value: string | undefined) => (value ?? '').trim()
|
|
22
|
+
|
|
23
|
+
export const normalizeAffiliation = (
|
|
24
|
+
a: AffiliationAttrs
|
|
25
|
+
): AffiliationAttrs => ({
|
|
26
|
+
...a,
|
|
27
|
+
institution: trim(a.institution),
|
|
28
|
+
department: trim(a.department),
|
|
29
|
+
addressLine1: trim(a.addressLine1),
|
|
30
|
+
addressLine2: trim(a.addressLine2),
|
|
31
|
+
addressLine3: trim(a.addressLine3),
|
|
32
|
+
postCode: trim(a.postCode),
|
|
33
|
+
country: trim(a.country),
|
|
34
|
+
county: trim(a.county),
|
|
35
|
+
city: trim(a.city),
|
|
36
|
+
email: a.email ?? { href: '', text: '' },
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const normalizeAuthor = (author: ContributorAttrs) => {
|
|
40
|
+
const basic: ContributorAttrs = {
|
|
41
|
+
id: author.id,
|
|
42
|
+
role: trim(author.role),
|
|
43
|
+
affiliationIDs: (author.affiliationIDs || []).sort(),
|
|
44
|
+
given: trim(author.given),
|
|
45
|
+
family: trim(author.family),
|
|
46
|
+
email: trim(author.email),
|
|
47
|
+
isCorresponding: author.isCorresponding || false,
|
|
48
|
+
ORCID: trim(author.ORCID),
|
|
49
|
+
isAuthenticated: author.isAuthenticated || false,
|
|
50
|
+
priority: author.priority,
|
|
51
|
+
isJointContributor: author.isJointContributor || false,
|
|
52
|
+
degrees: Array.isArray(author.degrees) ? author.degrees : [],
|
|
53
|
+
footnoteIDs: author.footnoteIDs || [],
|
|
54
|
+
correspIDs: author.correspIDs || [],
|
|
55
|
+
prefix: trim(author.prefix),
|
|
56
|
+
suffix: trim(author.suffix),
|
|
57
|
+
creditRoles: Array.isArray(author.creditRoles) ? author.creditRoles : [],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return basic
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type WithID = { id: string | undefined }
|
|
64
|
+
|
|
65
|
+
export const checkID = <T extends WithID>(attrs: T, nodeType: Nodes) => {
|
|
66
|
+
return {
|
|
67
|
+
...attrs,
|
|
68
|
+
id: attrs.id || generateNodeID(schema.nodes[nodeType]),
|
|
69
|
+
}
|
|
70
|
+
}
|