@manuscripts/body-editor 3.12.66 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2022 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
|
+
CommentNode,
|
|
18
|
+
ManuscriptNode,
|
|
19
|
+
ManuscriptTransaction,
|
|
20
|
+
schema,
|
|
21
|
+
} from '@manuscripts/transform'
|
|
22
|
+
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
23
|
+
import { findChildrenByType, NodeWithPos } from 'prosemirror-utils'
|
|
24
|
+
import { Decoration, DecorationSet, EditorView } from 'prosemirror-view'
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
Comment,
|
|
28
|
+
CommentAttrs,
|
|
29
|
+
CommentKey,
|
|
30
|
+
CommentRange,
|
|
31
|
+
CommentSelection,
|
|
32
|
+
createCommentMarker,
|
|
33
|
+
getCommentKey,
|
|
34
|
+
HighlightMarkerAttrs,
|
|
35
|
+
InlineComment,
|
|
36
|
+
isReply,
|
|
37
|
+
NodeComment,
|
|
38
|
+
} from '../lib/comments'
|
|
39
|
+
|
|
40
|
+
export const commentsKey = new PluginKey<PluginState>('comments')
|
|
41
|
+
const COMMENT_SELECTION = 'comment-selection'
|
|
42
|
+
const EMPTY_SELECTION = {}
|
|
43
|
+
|
|
44
|
+
export interface PluginState {
|
|
45
|
+
decorations: DecorationSet
|
|
46
|
+
comments: Map<string, Comment>
|
|
47
|
+
commentsByKey: Map<CommentKey, Comment[]>
|
|
48
|
+
selection?: CommentSelection
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const handleCommentMarkerInteraction = (
|
|
52
|
+
view: EditorView,
|
|
53
|
+
target: HTMLElement | null
|
|
54
|
+
) => {
|
|
55
|
+
const state = view.state
|
|
56
|
+
const com = commentsKey.getState(state)
|
|
57
|
+
const marker = target?.closest('[data-key]') as HTMLElement
|
|
58
|
+
|
|
59
|
+
// Don't dispatch a transaction if both empty
|
|
60
|
+
if (!marker && !com?.selection) {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const tr = state.tr
|
|
65
|
+
if (marker) {
|
|
66
|
+
const key = marker.dataset.key as CommentKey
|
|
67
|
+
setCommentSelection(tr, key, undefined, false)
|
|
68
|
+
} else {
|
|
69
|
+
clearCommentSelection(tr)
|
|
70
|
+
}
|
|
71
|
+
view.dispatch(tr)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* This plugin creates a icon decoration for both inline and block comment.
|
|
76
|
+
*/
|
|
77
|
+
export default () => {
|
|
78
|
+
return new Plugin<PluginState>({
|
|
79
|
+
key: commentsKey,
|
|
80
|
+
state: {
|
|
81
|
+
init: (_, state) => {
|
|
82
|
+
return buildPluginState(state.doc)
|
|
83
|
+
},
|
|
84
|
+
apply: (tr, value) => {
|
|
85
|
+
if (tr.docChanged || tr.getMeta(COMMENT_SELECTION)) {
|
|
86
|
+
const selection = tr.getMeta(COMMENT_SELECTION) || value.selection
|
|
87
|
+
return buildPluginState(
|
|
88
|
+
tr.doc,
|
|
89
|
+
selection === EMPTY_SELECTION ? undefined : selection
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
return value
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
props: {
|
|
96
|
+
decorations: (state) => commentsKey.getState(state)?.decorations,
|
|
97
|
+
handleClick: (view: EditorView, pos: number, e: MouseEvent) => {
|
|
98
|
+
const target = e.target as HTMLElement
|
|
99
|
+
return handleCommentMarkerInteraction(view, target)
|
|
100
|
+
},
|
|
101
|
+
handleDOMEvents: {
|
|
102
|
+
keydown: (view: EditorView, e: KeyboardEvent) => {
|
|
103
|
+
if (e.key !== 'Enter') {
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
const target = document.activeElement as HTMLElement
|
|
107
|
+
const marker = target?.closest('[data-key]') as HTMLElement | null
|
|
108
|
+
if (!marker) {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
e.preventDefault()
|
|
112
|
+
handleCommentMarkerInteraction(view, target)
|
|
113
|
+
return true
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const setCommentSelection = (
|
|
121
|
+
tr: ManuscriptTransaction,
|
|
122
|
+
key: CommentKey,
|
|
123
|
+
id: string | undefined,
|
|
124
|
+
isNew: boolean
|
|
125
|
+
) => {
|
|
126
|
+
tr.setMeta(COMMENT_SELECTION, {
|
|
127
|
+
key,
|
|
128
|
+
id,
|
|
129
|
+
isNew,
|
|
130
|
+
})
|
|
131
|
+
tr.setMeta('origin', commentsKey)
|
|
132
|
+
return tr
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const clearCommentSelection = (tr: ManuscriptTransaction) => {
|
|
136
|
+
tr.setMeta(COMMENT_SELECTION, EMPTY_SELECTION)
|
|
137
|
+
tr.setMeta('origin', commentsKey)
|
|
138
|
+
return tr
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const findCommentRanges = (doc: ManuscriptNode) => {
|
|
142
|
+
const nodes = findChildrenByType(doc, schema.nodes.highlight_marker)
|
|
143
|
+
|
|
144
|
+
const ranges = new Map<string, CommentRange>()
|
|
145
|
+
|
|
146
|
+
let start
|
|
147
|
+
|
|
148
|
+
for (const { node, pos } of nodes) {
|
|
149
|
+
const attrs = node.attrs as HighlightMarkerAttrs
|
|
150
|
+
const position = attrs.position
|
|
151
|
+
if (position === 'point') {
|
|
152
|
+
ranges.set(attrs.id, {
|
|
153
|
+
pos: pos + 1,
|
|
154
|
+
size: 0,
|
|
155
|
+
})
|
|
156
|
+
} else if (position === 'start') {
|
|
157
|
+
start = pos + 1
|
|
158
|
+
} else if (position === 'end' && start) {
|
|
159
|
+
ranges.set(attrs.id, {
|
|
160
|
+
pos: start,
|
|
161
|
+
size: pos - start,
|
|
162
|
+
})
|
|
163
|
+
start = undefined
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return ranges
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const findComments = (doc: ManuscriptNode) => {
|
|
170
|
+
const comments = findChildrenByType(doc, schema.nodes.comment)
|
|
171
|
+
|
|
172
|
+
const commentsByTarget = new Map<string, NodeWithPos[]>()
|
|
173
|
+
for (const comment of comments) {
|
|
174
|
+
const targetID = comment.node.attrs.target
|
|
175
|
+
const list = commentsByTarget.get(targetID) || []
|
|
176
|
+
commentsByTarget.set(targetID, [...list, comment])
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return commentsByTarget
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const buildPluginState = (
|
|
183
|
+
doc: ManuscriptNode,
|
|
184
|
+
selection?: CommentSelection
|
|
185
|
+
) => {
|
|
186
|
+
const commentsByTarget = findComments(doc)
|
|
187
|
+
const ranges = findCommentRanges(doc)
|
|
188
|
+
|
|
189
|
+
const decorations: Decoration[] = []
|
|
190
|
+
const allComments: Comment[] = []
|
|
191
|
+
|
|
192
|
+
doc.descendants((node, pos) => {
|
|
193
|
+
const id = node.attrs.id
|
|
194
|
+
const comments = commentsByTarget.get(id)
|
|
195
|
+
if (!comments || !comments.length) {
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const target = {
|
|
200
|
+
node,
|
|
201
|
+
pos,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const nodeComments: NodeComment[] = []
|
|
205
|
+
const highlightComments: InlineComment[] = []
|
|
206
|
+
const pointComments: InlineComment[] = []
|
|
207
|
+
|
|
208
|
+
comments
|
|
209
|
+
.map((c) => {
|
|
210
|
+
const attrs = c.node.attrs as CommentAttrs
|
|
211
|
+
const id = attrs.id
|
|
212
|
+
const range = ranges.get(id)
|
|
213
|
+
const key = getCommentKey(attrs, range, node)
|
|
214
|
+
return {
|
|
215
|
+
key,
|
|
216
|
+
node: c.node as CommentNode,
|
|
217
|
+
pos: c.pos,
|
|
218
|
+
target,
|
|
219
|
+
range,
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
.forEach((c) => {
|
|
223
|
+
allComments.push(c)
|
|
224
|
+
if (!isReply(c)) {
|
|
225
|
+
if (!c.range) {
|
|
226
|
+
nodeComments.push(c)
|
|
227
|
+
} else if (c.range.size) {
|
|
228
|
+
highlightComments.push(c as InlineComment)
|
|
229
|
+
} else {
|
|
230
|
+
pointComments.push(c as InlineComment)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
for (const comment of highlightComments) {
|
|
236
|
+
const key = comment.key
|
|
237
|
+
const range = comment.range
|
|
238
|
+
decorations.push(createHighlightDecoration(key, range, selection))
|
|
239
|
+
}
|
|
240
|
+
if (pointComments.length) {
|
|
241
|
+
groupByKey(pointComments).forEach((comments, key) => {
|
|
242
|
+
const count = comments.length
|
|
243
|
+
const pos = comments[0].range.pos
|
|
244
|
+
decorations.push(
|
|
245
|
+
createMarkerDecoration(key, 'span', pos, count, selection)
|
|
246
|
+
)
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
if (nodeComments.length) {
|
|
250
|
+
const count = nodeComments.length
|
|
251
|
+
const key = nodeComments[0].key
|
|
252
|
+
const tagName = node.isAtom ? 'span' : 'div'
|
|
253
|
+
const dpos = getDecorationPos(node, pos)
|
|
254
|
+
decorations.push(
|
|
255
|
+
createMarkerDecoration(key, tagName, dpos, count, selection)
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
decorations: DecorationSet.create(doc, decorations),
|
|
262
|
+
comments: new Map(allComments.map((c) => [c.node.attrs.id, c])),
|
|
263
|
+
commentsByKey: groupByKey(allComments),
|
|
264
|
+
selection,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const groupByKey = <T extends Comment>(comments: T[]): Map<CommentKey, T[]> => {
|
|
269
|
+
const map = new Map()
|
|
270
|
+
for (const comment of comments) {
|
|
271
|
+
const key = comment.key
|
|
272
|
+
const value = map.get(key) || []
|
|
273
|
+
map.set(key, [...value, comment])
|
|
274
|
+
}
|
|
275
|
+
return map
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const getDecorationPos = (node: ManuscriptNode, pos: number) => {
|
|
279
|
+
switch (node.type) {
|
|
280
|
+
case schema.nodes.affiliations:
|
|
281
|
+
case schema.nodes.paragraph:
|
|
282
|
+
case schema.nodes.embed:
|
|
283
|
+
case schema.nodes.contributors:
|
|
284
|
+
case schema.nodes.hero_image:
|
|
285
|
+
case schema.nodes.supplements:
|
|
286
|
+
return pos
|
|
287
|
+
case schema.nodes.keywords:
|
|
288
|
+
return pos + 2
|
|
289
|
+
default:
|
|
290
|
+
return pos + 1
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const createHighlightDecoration = (
|
|
295
|
+
key: CommentKey,
|
|
296
|
+
range: CommentRange,
|
|
297
|
+
selection: CommentSelection | undefined
|
|
298
|
+
) => {
|
|
299
|
+
const classNames = ['highlight']
|
|
300
|
+
if (key === selection?.key) {
|
|
301
|
+
classNames.push('selected-comment')
|
|
302
|
+
}
|
|
303
|
+
return Decoration.inline(range.pos, range.pos + range.size, {
|
|
304
|
+
nodeName: 'span',
|
|
305
|
+
class: classNames.join(' '),
|
|
306
|
+
'data-key': key,
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const createMarkerDecoration = (
|
|
311
|
+
key: CommentKey,
|
|
312
|
+
tagName: string,
|
|
313
|
+
pos: number,
|
|
314
|
+
count: number,
|
|
315
|
+
selection: CommentSelection | undefined
|
|
316
|
+
) => {
|
|
317
|
+
const isSelected = selection?.key === key
|
|
318
|
+
const toDOM = () => {
|
|
319
|
+
const marker = createCommentMarker(tagName, key, count)
|
|
320
|
+
if (isSelected) {
|
|
321
|
+
marker.classList.add('selected-comment')
|
|
322
|
+
}
|
|
323
|
+
return marker
|
|
324
|
+
}
|
|
325
|
+
return Decoration.widget(pos, toDOM, {
|
|
326
|
+
key: `${key}-${count}-${isSelected}`,
|
|
327
|
+
})
|
|
328
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2024 Atypon Systems LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { schema } from '@manuscripts/transform'
|
|
18
|
+
import { isEqual } from 'lodash'
|
|
19
|
+
import { Node } from 'prosemirror-model'
|
|
20
|
+
import { Plugin } from 'prosemirror-state'
|
|
21
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
22
|
+
|
|
23
|
+
import { objectsKey } from './objects'
|
|
24
|
+
|
|
25
|
+
export default () => {
|
|
26
|
+
return new Plugin({
|
|
27
|
+
state: {
|
|
28
|
+
init() {
|
|
29
|
+
// Initialize decorations with an empty set
|
|
30
|
+
return DecorationSet.empty
|
|
31
|
+
},
|
|
32
|
+
apply(tr, oldDecorationSet, oldState, newState) {
|
|
33
|
+
let decoSet = oldDecorationSet
|
|
34
|
+
// Check if document or targets have changed
|
|
35
|
+
const oldTargets = objectsKey.getState(oldState)
|
|
36
|
+
const newTargets = objectsKey.getState(newState)
|
|
37
|
+
if (tr.docChanged && !isEqual(oldTargets, newTargets)) {
|
|
38
|
+
const decorations = createDecorations(tr.doc)
|
|
39
|
+
decoSet = DecorationSet.create(tr.doc, decorations)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return decoSet
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
props: {
|
|
46
|
+
decorations(state) {
|
|
47
|
+
return this.getState(state)
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Helper function to create decorations for specific node types.
|
|
55
|
+
* @param {Node} doc - The document to scan.
|
|
56
|
+
* @returns {Decoration[]} - An array of decorations.
|
|
57
|
+
*/
|
|
58
|
+
function createDecorations(doc: Node): Decoration[] {
|
|
59
|
+
const decorations: Decoration[] = []
|
|
60
|
+
doc.descendants((node, pos) => {
|
|
61
|
+
if (node.type === schema.nodes.cross_reference) {
|
|
62
|
+
decorations.push(
|
|
63
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
64
|
+
class: `decorated-${Date.now()}`,
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
return decorations
|
|
70
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
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 { ManuscriptEditorState, ManuscriptNode } from '@manuscripts/transform'
|
|
18
|
+
import { NodeSelection } from 'prosemirror-state'
|
|
19
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
20
|
+
|
|
21
|
+
import { EditorProps } from '../../configs/ManuscriptsEditor'
|
|
22
|
+
import { affiliationsKey } from '../affiliations'
|
|
23
|
+
import { getBibliographyPluginState } from '../bibliography'
|
|
24
|
+
import { footnotesKey } from '../footnotes'
|
|
25
|
+
import { objectsKey } from '../objects'
|
|
26
|
+
import { ValidatorContext, validators } from './validators'
|
|
27
|
+
|
|
28
|
+
export type Inconsistency = {
|
|
29
|
+
type: 'warning'
|
|
30
|
+
category: 'missing-reference' | 'not-used' | 'empty-content'
|
|
31
|
+
severity: 'error' | 'warning'
|
|
32
|
+
message: string
|
|
33
|
+
nodeDescription: string
|
|
34
|
+
node: ManuscriptNode
|
|
35
|
+
pos: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type PluginState = {
|
|
39
|
+
decorations: DecorationSet
|
|
40
|
+
inconsistencies: Array<Inconsistency>
|
|
41
|
+
showDecorations: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const createDecoration = (
|
|
45
|
+
node: ManuscriptNode,
|
|
46
|
+
pos: number,
|
|
47
|
+
selectedPos: number | null
|
|
48
|
+
) => {
|
|
49
|
+
const classNames = ['inconsistency-highlight']
|
|
50
|
+
if (selectedPos === pos) {
|
|
51
|
+
classNames.push('selected-suggestion')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Decoration.node(pos, pos + node.nodeSize, {
|
|
55
|
+
class: classNames.join(' '),
|
|
56
|
+
'data-inconsistency-type': 'warning',
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const buildPluginState = (
|
|
61
|
+
state: ManuscriptEditorState,
|
|
62
|
+
props: EditorProps,
|
|
63
|
+
showDecorations: boolean
|
|
64
|
+
): PluginState => {
|
|
65
|
+
const inconsistencies: Inconsistency[] = []
|
|
66
|
+
const decorations: Decoration[] = []
|
|
67
|
+
|
|
68
|
+
const selection = state.selection
|
|
69
|
+
let selectedPos: number | null = null
|
|
70
|
+
|
|
71
|
+
if (selection instanceof NodeSelection) {
|
|
72
|
+
selectedPos = selection.from
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const context: ValidatorContext = {
|
|
76
|
+
pluginStates: {
|
|
77
|
+
affiliations: affiliationsKey.getState(state)?.indexedAffiliationIds,
|
|
78
|
+
bibliography: getBibliographyPluginState(state)?.bibliographyItems,
|
|
79
|
+
objects: objectsKey.getState(state),
|
|
80
|
+
footnotes: footnotesKey.getState(state)?.footnotesElementIDs,
|
|
81
|
+
footnotesElements: footnotesKey.getState(state)?.footnotesElements,
|
|
82
|
+
},
|
|
83
|
+
showDecorations,
|
|
84
|
+
selectedPos,
|
|
85
|
+
decorations,
|
|
86
|
+
props,
|
|
87
|
+
doc: state.doc,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
state.doc.descendants((node, pos) => {
|
|
91
|
+
const validator = validators[node.type.name]
|
|
92
|
+
if (validator) {
|
|
93
|
+
inconsistencies.push(...validator(node, pos, context))
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
decorations: DecorationSet.create(state.doc, decorations),
|
|
99
|
+
inconsistencies,
|
|
100
|
+
showDecorations,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
// This plugin is used to detect inconsistencies in the document.
|
|
18
|
+
// The plugin will check for cross-references, footnotes, table-footnotes, and citations.
|
|
19
|
+
// If one of these doesn't have a linked node, it will add a warning to the node.
|
|
20
|
+
|
|
21
|
+
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
22
|
+
import { DecorationSet } from 'prosemirror-view'
|
|
23
|
+
|
|
24
|
+
import { EditorProps } from '../../configs/ManuscriptsEditor'
|
|
25
|
+
import { buildPluginState, PluginState } from './detect-inconsistency-utils'
|
|
26
|
+
|
|
27
|
+
export { Inconsistency } from './detect-inconsistency-utils'
|
|
28
|
+
|
|
29
|
+
export const detectInconsistencyKey = new PluginKey<PluginState>(
|
|
30
|
+
'detectInconsistency'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
export default (props: EditorProps) => {
|
|
34
|
+
return new Plugin<PluginState>({
|
|
35
|
+
key: detectInconsistencyKey,
|
|
36
|
+
state: {
|
|
37
|
+
init: (_, state) => buildPluginState(state, props, false),
|
|
38
|
+
apply: (tr, value, newState) => {
|
|
39
|
+
const showDecorations =
|
|
40
|
+
tr.getMeta(detectInconsistencyKey) !== undefined
|
|
41
|
+
? tr.getMeta(detectInconsistencyKey)
|
|
42
|
+
: value.showDecorations
|
|
43
|
+
return buildPluginState(newState, props, showDecorations)
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
props: {
|
|
47
|
+
decorations: (state) => {
|
|
48
|
+
const pluginState = detectInconsistencyKey.getState(state)
|
|
49
|
+
return pluginState?.showDecorations
|
|
50
|
+
? pluginState.decorations
|
|
51
|
+
: DecorationSet.empty
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
}
|