@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
|
@@ -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 { ManuscriptEditorView, schema } from '@manuscripts/transform'
|
|
18
|
+
import { Plugin, TextSelection } from 'prosemirror-state'
|
|
19
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
20
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
21
|
+
|
|
22
|
+
import { EditorProps } from '../configs/ManuscriptsEditor'
|
|
23
|
+
import { addAuthorIcon } from '../icons'
|
|
24
|
+
import { handleEnterKey } from '../lib/navigation-utils'
|
|
25
|
+
import { findInsertionPosition } from '../lib/utils'
|
|
26
|
+
|
|
27
|
+
const createAddSubtitleButton = (handler: () => void) => {
|
|
28
|
+
const button = document.createElement('span')
|
|
29
|
+
button.className = 'add-subtitle'
|
|
30
|
+
button.innerHTML = `${addAuthorIcon} <span class="add-subtitle-text">Add subtitle</span>`
|
|
31
|
+
|
|
32
|
+
button.tabIndex = 0
|
|
33
|
+
const activate = (event: MouseEvent | KeyboardEvent) => {
|
|
34
|
+
event.preventDefault()
|
|
35
|
+
handler()
|
|
36
|
+
}
|
|
37
|
+
button.addEventListener('mousedown', (e) => {
|
|
38
|
+
activate(e)
|
|
39
|
+
})
|
|
40
|
+
button.addEventListener('keydown', handleEnterKey(activate))
|
|
41
|
+
|
|
42
|
+
return button
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default (props: EditorProps) =>
|
|
46
|
+
new Plugin<null>({
|
|
47
|
+
props: {
|
|
48
|
+
decorations: (state) => {
|
|
49
|
+
const can = props.getCapabilities()
|
|
50
|
+
if (!can.editArticle) {
|
|
51
|
+
return DecorationSet.empty
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let titleHasContent = false
|
|
55
|
+
let titlePos = -1
|
|
56
|
+
let hasSubtitles = false
|
|
57
|
+
|
|
58
|
+
state.doc.descendants((node, pos) => {
|
|
59
|
+
if (node.type === schema.nodes.title && node.textContent.trim()) {
|
|
60
|
+
titleHasContent = true
|
|
61
|
+
titlePos = pos
|
|
62
|
+
} else if (node.type === schema.nodes.subtitles) {
|
|
63
|
+
hasSubtitles = true
|
|
64
|
+
return false // Stop early if subtitles found
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Show button only if title has content and no subtitles exist
|
|
69
|
+
if (titleHasContent && !hasSubtitles) {
|
|
70
|
+
const titleNode = state.doc.nodeAt(titlePos)
|
|
71
|
+
if (titleNode) {
|
|
72
|
+
const titleEndPos = titlePos + titleNode.nodeSize
|
|
73
|
+
|
|
74
|
+
return DecorationSet.create(state.doc, [
|
|
75
|
+
Decoration.widget(titleEndPos, (view: ManuscriptEditorView) => {
|
|
76
|
+
return createAddSubtitleButton(() => {
|
|
77
|
+
const subtitlesNode = schema.nodes.subtitles.create(
|
|
78
|
+
{ id: uuidv4() },
|
|
79
|
+
[schema.nodes.subtitle.create({ id: uuidv4() })]
|
|
80
|
+
)
|
|
81
|
+
const pos = findInsertionPosition(
|
|
82
|
+
schema.nodes.subtitles,
|
|
83
|
+
state.doc
|
|
84
|
+
)
|
|
85
|
+
const tr = view.state.tr.insert(pos, subtitlesNode)
|
|
86
|
+
const subtitlePos = pos + 1
|
|
87
|
+
tr.setSelection(TextSelection.create(tr.doc, subtitlePos))
|
|
88
|
+
|
|
89
|
+
view.dispatch(tr)
|
|
90
|
+
view.focus()
|
|
91
|
+
})
|
|
92
|
+
},
|
|
93
|
+
{ side: -1 }
|
|
94
|
+
),
|
|
95
|
+
])
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return DecorationSet.empty
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
})
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* This plugin handles indexing of affiliations that are supposed to be index in accordance with their order in the document.
|
|
19
|
+
* It also provides that information to components that display that indexing. Additionally it deletes affiliations that have been detached and not used anymore.
|
|
20
|
+
*/
|
|
21
|
+
import { isDeleted } from '@manuscripts/track-changes-plugin'
|
|
22
|
+
import {
|
|
23
|
+
isAffiliationNode,
|
|
24
|
+
isContributorNode,
|
|
25
|
+
ManuscriptNode,
|
|
26
|
+
} from '@manuscripts/transform'
|
|
27
|
+
import { isEqual } from 'lodash'
|
|
28
|
+
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
29
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
AffiliationAttrs,
|
|
33
|
+
authorComparator,
|
|
34
|
+
ContributorAttrs,
|
|
35
|
+
} from '../lib/authors'
|
|
36
|
+
|
|
37
|
+
export interface PluginState {
|
|
38
|
+
version: string
|
|
39
|
+
indexedAffiliationIds: Map<string, number> // key is affiliation id
|
|
40
|
+
contributors: ContributorAttrs[]
|
|
41
|
+
affiliations: AffiliationAttrs[]
|
|
42
|
+
decorations: DecorationSet
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const affiliationsKey = new PluginKey<PluginState>('affiliations')
|
|
46
|
+
|
|
47
|
+
let id = 1
|
|
48
|
+
export const buildPluginState = (
|
|
49
|
+
doc: ManuscriptNode,
|
|
50
|
+
$old?: PluginState
|
|
51
|
+
): PluginState => {
|
|
52
|
+
const nodes: [ManuscriptNode, number][] = []
|
|
53
|
+
const contributors: ContributorAttrs[] = []
|
|
54
|
+
const affiliations: AffiliationAttrs[] = []
|
|
55
|
+
const deletedContribId = new Set<string>()
|
|
56
|
+
|
|
57
|
+
doc.descendants((node, pos) => {
|
|
58
|
+
const attrs = node.attrs
|
|
59
|
+
if (isAffiliationNode(node)) {
|
|
60
|
+
nodes.push([node, pos])
|
|
61
|
+
affiliations.push(attrs as AffiliationAttrs)
|
|
62
|
+
}
|
|
63
|
+
if (isContributorNode(node)) {
|
|
64
|
+
isDeleted(node) && deletedContribId.add(attrs.id)
|
|
65
|
+
nodes.push([node, pos])
|
|
66
|
+
contributors.push(attrs as ContributorAttrs)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
$old &&
|
|
72
|
+
isEqual(contributors, $old.contributors) &&
|
|
73
|
+
isEqual(affiliations, $old.affiliations)
|
|
74
|
+
) {
|
|
75
|
+
return $old
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Only index affiliations that are assigned to active contributors
|
|
79
|
+
const iAffiliations = new Set<string>()
|
|
80
|
+
contributors
|
|
81
|
+
.filter((contrib) => !deletedContribId.has(contrib.id))
|
|
82
|
+
.sort(authorComparator)
|
|
83
|
+
.forEach((attrs) => {
|
|
84
|
+
attrs.affiliationIDs?.forEach((aff) => {
|
|
85
|
+
iAffiliations.add(aff)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Create index map only for affiliated affiliations
|
|
90
|
+
const indexedAffiliationIds = new Map<string, number>(
|
|
91
|
+
[...iAffiliations].map((id, i) => [id, i + 1])
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const version = String(id++)
|
|
95
|
+
const decorations: Decoration[] = []
|
|
96
|
+
nodes.forEach(([node, pos]) => {
|
|
97
|
+
decorations.push(
|
|
98
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
99
|
+
version,
|
|
100
|
+
})
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
version,
|
|
106
|
+
indexedAffiliationIds,
|
|
107
|
+
contributors,
|
|
108
|
+
affiliations,
|
|
109
|
+
decorations: DecorationSet.create(doc, decorations),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default () => {
|
|
114
|
+
return new Plugin<PluginState>({
|
|
115
|
+
key: affiliationsKey,
|
|
116
|
+
|
|
117
|
+
state: {
|
|
118
|
+
init(config, instance): PluginState {
|
|
119
|
+
return buildPluginState(instance.doc)
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
apply(tr, value, oldState, newState): PluginState {
|
|
123
|
+
const $old = affiliationsKey.getState(oldState)
|
|
124
|
+
if (!tr.docChanged && $old) {
|
|
125
|
+
return $old
|
|
126
|
+
} else {
|
|
127
|
+
return buildPluginState(newState.doc, $old)
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
props: {
|
|
133
|
+
decorations: (state) => affiliationsKey.getState(state)?.decorations,
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
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 { skipTracking } from '@manuscripts/track-changes-plugin'
|
|
17
|
+
import {
|
|
18
|
+
AltTitleNode,
|
|
19
|
+
AltTitlesSectionNode,
|
|
20
|
+
ManuscriptEditorView,
|
|
21
|
+
schema,
|
|
22
|
+
SubtitlesNode,
|
|
23
|
+
TitleNode,
|
|
24
|
+
} from '@manuscripts/transform'
|
|
25
|
+
import { Node as ProseMirrorNode } from 'prosemirror-model'
|
|
26
|
+
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
27
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
28
|
+
|
|
29
|
+
import { createToggleButton, findInsertionPosition } from '../lib/utils'
|
|
30
|
+
|
|
31
|
+
export interface PluginState {
|
|
32
|
+
collapsed: boolean
|
|
33
|
+
title: [TitleNode, number] | undefined
|
|
34
|
+
subtitles: [SubtitlesNode, number] | undefined
|
|
35
|
+
runningTitle: [AltTitleNode, number] | undefined
|
|
36
|
+
shortTitle: [AltTitleNode, number] | undefined
|
|
37
|
+
altTitlesSection: [AltTitlesSectionNode, number] | undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getTitlesData(doc: ProseMirrorNode) {
|
|
41
|
+
let title: [TitleNode, number] | undefined
|
|
42
|
+
let subtitles: [SubtitlesNode, number] | undefined
|
|
43
|
+
let runningTitle: [AltTitleNode, number] | undefined
|
|
44
|
+
let shortTitle: [AltTitleNode, number] | undefined
|
|
45
|
+
let altTitlesSection: [AltTitlesSectionNode, number] | undefined
|
|
46
|
+
|
|
47
|
+
doc.descendants((node, pos) => {
|
|
48
|
+
if (title && runningTitle && shortTitle && altTitlesSection) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
if (node.type === schema.nodes.title) {
|
|
52
|
+
// if title is empty we don't allow to edit alt titles
|
|
53
|
+
title = [node as TitleNode, pos]
|
|
54
|
+
}
|
|
55
|
+
if (node.type === schema.nodes.subtitles) {
|
|
56
|
+
subtitles = [node as SubtitlesNode, pos]
|
|
57
|
+
}
|
|
58
|
+
if (node.type === schema.nodes.alt_titles) {
|
|
59
|
+
altTitlesSection = [node as AltTitlesSectionNode, pos]
|
|
60
|
+
}
|
|
61
|
+
if (node.type === schema.nodes.alt_title) {
|
|
62
|
+
if (node.attrs.type === 'running') {
|
|
63
|
+
runningTitle = [node as AltTitleNode, pos]
|
|
64
|
+
}
|
|
65
|
+
if (node.attrs.type === 'short') {
|
|
66
|
+
shortTitle = [node as AltTitleNode, pos]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
return { title, subtitles, runningTitle, shortTitle, altTitlesSection }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function selectionInAltTitles(from: number, to: number, state: PluginState) {
|
|
74
|
+
if (state.runningTitle && state.shortTitle) {
|
|
75
|
+
const range = {
|
|
76
|
+
from: Math.min(state.runningTitle[1], state.shortTitle[1]),
|
|
77
|
+
to: Math.max(
|
|
78
|
+
state.runningTitle[1] + state.runningTitle[0].nodeSize,
|
|
79
|
+
state.shortTitle[1] + state.shortTitle[0].nodeSize
|
|
80
|
+
),
|
|
81
|
+
}
|
|
82
|
+
// if selection overlaps with the range of alt titles, the selection is in the titles
|
|
83
|
+
return Math.max(from, range.from) <= Math.min(to, range.to)
|
|
84
|
+
}
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const altTitlesKey = new PluginKey<PluginState>('altTitles')
|
|
89
|
+
|
|
90
|
+
export default () => {
|
|
91
|
+
return new Plugin<PluginState>({
|
|
92
|
+
key: altTitlesKey,
|
|
93
|
+
state: {
|
|
94
|
+
init: (_, state) => {
|
|
95
|
+
return { collapsed: true, ...getTitlesData(state.doc) }
|
|
96
|
+
},
|
|
97
|
+
apply: (tr, value) => {
|
|
98
|
+
let newState = value
|
|
99
|
+
if (tr.docChanged) {
|
|
100
|
+
newState = { ...newState, ...getTitlesData(tr.doc) }
|
|
101
|
+
}
|
|
102
|
+
if (
|
|
103
|
+
tr.selectionSet &&
|
|
104
|
+
selectionInAltTitles(tr.selection.from, tr.selection.to, newState)
|
|
105
|
+
) {
|
|
106
|
+
// if selection was set in the titles - open them
|
|
107
|
+
newState = { ...newState, collapsed: false }
|
|
108
|
+
}
|
|
109
|
+
if (tr.getMeta(altTitlesKey)) {
|
|
110
|
+
newState = {
|
|
111
|
+
...newState,
|
|
112
|
+
...tr.getMeta(altTitlesKey),
|
|
113
|
+
...getTitlesData(tr.doc),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return newState
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
appendTransaction: (transactions, _, newState) => {
|
|
120
|
+
// in appendTransaction we check if alt_titles nodes exist before opening them for the first time because they are optional
|
|
121
|
+
const tr = newState.tr
|
|
122
|
+
if (
|
|
123
|
+
!transactions.some((tr) => tr.getMeta(altTitlesKey)) ||
|
|
124
|
+
!altTitlesKey.getState(newState)
|
|
125
|
+
) {
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { title, runningTitle, shortTitle, altTitlesSection } =
|
|
130
|
+
//eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
131
|
+
altTitlesKey.getState(newState)!
|
|
132
|
+
|
|
133
|
+
const schema = newState.schema
|
|
134
|
+
|
|
135
|
+
if (!title) {
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!altTitlesSection) {
|
|
140
|
+
const pos = findInsertionPosition(schema.nodes.alt_titles, tr.doc)
|
|
141
|
+
const section = schema.nodes.alt_titles.create({}, [
|
|
142
|
+
schema.nodes.alt_title.create({
|
|
143
|
+
type: 'running',
|
|
144
|
+
}),
|
|
145
|
+
schema.nodes.alt_title.create({ type: 'short' }),
|
|
146
|
+
])
|
|
147
|
+
tr.insert(pos, section)
|
|
148
|
+
} else {
|
|
149
|
+
const endPos = altTitlesSection[1] + altTitlesSection[0].nodeSize - 1
|
|
150
|
+
if (!runningTitle) {
|
|
151
|
+
const title = schema.nodes.alt_title.create({
|
|
152
|
+
type: 'running',
|
|
153
|
+
})
|
|
154
|
+
tr.insert(endPos, title)
|
|
155
|
+
}
|
|
156
|
+
if (!shortTitle) {
|
|
157
|
+
const title = schema.nodes.alt_title.create({ type: 'short' })
|
|
158
|
+
const newPos = tr.mapping.map(endPos)
|
|
159
|
+
tr.insert(newPos, title)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (tr.docChanged) {
|
|
163
|
+
return tr.setMeta(altTitlesKey, 'created-missing-titles')
|
|
164
|
+
} else {
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
props: {
|
|
169
|
+
decorations: (state) => {
|
|
170
|
+
const decorations: Decoration[] = []
|
|
171
|
+
const pState = altTitlesKey.getState(state)
|
|
172
|
+
if (!pState || !pState.title) {
|
|
173
|
+
return DecorationSet.empty
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!pState.collapsed) {
|
|
177
|
+
state.doc.descendants((node, pos) => {
|
|
178
|
+
if (node.type === state.schema.nodes.alt_titles) {
|
|
179
|
+
decorations.push(
|
|
180
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
181
|
+
class: 'alt-titles-section-open',
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
} else if (pState.title[0].textContent.length) {
|
|
187
|
+
// showing opening button only when titles are collapsed
|
|
188
|
+
const titleEnd = pState.title[0].nodeSize + pState.title[1]
|
|
189
|
+
decorations.push(
|
|
190
|
+
Decoration.widget(
|
|
191
|
+
titleEnd - 1,
|
|
192
|
+
(view: ManuscriptEditorView) => {
|
|
193
|
+
return createToggleButton((e) => {
|
|
194
|
+
const tr = view.state.tr.setMeta(altTitlesKey, {
|
|
195
|
+
collapsed: false,
|
|
196
|
+
})
|
|
197
|
+
view.dispatch(skipTracking(tr))
|
|
198
|
+
// Only auto-focus first alt title on keyboard interaction, not mouse clicks
|
|
199
|
+
const isKeyboardEvent = e.type === 'keydown'
|
|
200
|
+
if (isKeyboardEvent) {
|
|
201
|
+
const altTitle = view.dom.querySelector(
|
|
202
|
+
'.alt-title-text'
|
|
203
|
+
) as HTMLElement
|
|
204
|
+
altTitle?.focus()
|
|
205
|
+
}
|
|
206
|
+
}, 'alternative titles')
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
side: -1,
|
|
210
|
+
key: 'title-' + titleEnd,
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
return DecorationSet.create(state.doc, decorations)
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
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
|
+
BibliographyItemAttrs,
|
|
19
|
+
buildCiteprocCitation,
|
|
20
|
+
CitationNode,
|
|
21
|
+
isBibliographyElementNode,
|
|
22
|
+
isBibliographyItemNode,
|
|
23
|
+
isCitationNode,
|
|
24
|
+
ManuscriptNode,
|
|
25
|
+
} from '@manuscripts/transform'
|
|
26
|
+
import * as Citeproc from 'citeproc'
|
|
27
|
+
import { isEqual } from 'lodash'
|
|
28
|
+
import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
|
|
29
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
30
|
+
|
|
31
|
+
import { CSLProps } from '../configs/ManuscriptsEditor'
|
|
32
|
+
import { PopperManager } from '../lib/popper'
|
|
33
|
+
|
|
34
|
+
export const bibliographyKey = new PluginKey<PluginState>('bibliography')
|
|
35
|
+
|
|
36
|
+
export interface PluginState {
|
|
37
|
+
version: string
|
|
38
|
+
citationNodes: [CitationNode, number][]
|
|
39
|
+
citations: Citeproc.Citation[]
|
|
40
|
+
bibliographyItems: Map<string, BibliographyItemAttrs>
|
|
41
|
+
renderedCitations: Map<string, string>
|
|
42
|
+
citationCounts: Map<string, number>
|
|
43
|
+
engine: Citeproc.Engine
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface BibliographyProps {
|
|
47
|
+
cslProps: CSLProps
|
|
48
|
+
popper: PopperManager
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* This plugin generates labels for inline citations using citeproc-js.
|
|
53
|
+
* The citation labels are regenerated when any relevant content changes.
|
|
54
|
+
*/
|
|
55
|
+
export default (props: BibliographyProps) => {
|
|
56
|
+
return new Plugin<PluginState>({
|
|
57
|
+
key: bibliographyKey,
|
|
58
|
+
state: {
|
|
59
|
+
init(config, instance): PluginState {
|
|
60
|
+
return buildBibliographyPluginState(instance.doc, props.cslProps)
|
|
61
|
+
},
|
|
62
|
+
apply(tr, value): PluginState {
|
|
63
|
+
if (!tr.steps.length) {
|
|
64
|
+
return value
|
|
65
|
+
}
|
|
66
|
+
return buildBibliographyPluginState(tr.doc, props.cslProps, value)
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
props: {
|
|
70
|
+
decorations(state) {
|
|
71
|
+
const bib = getBibliographyPluginState(state)
|
|
72
|
+
|
|
73
|
+
return DecorationSet.create(
|
|
74
|
+
state.doc,
|
|
75
|
+
bib ? buildDecorations(bib, state.doc) : []
|
|
76
|
+
)
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let version = 1
|
|
83
|
+
const buildBibliographyPluginState = (
|
|
84
|
+
doc: ManuscriptNode,
|
|
85
|
+
csl: CSLProps,
|
|
86
|
+
$old?: PluginState
|
|
87
|
+
): PluginState => {
|
|
88
|
+
const nodesMap = new Map<string, [CitationNode, number]>()
|
|
89
|
+
const bibliographyItems = new Map<string, BibliographyItemAttrs>()
|
|
90
|
+
doc.descendants((node, pos) => {
|
|
91
|
+
if (isCitationNode(node)) {
|
|
92
|
+
nodesMap.set(node.attrs.id, [node, pos])
|
|
93
|
+
}
|
|
94
|
+
if (isBibliographyItemNode(node)) {
|
|
95
|
+
bibliographyItems.set(node.attrs.id, node.attrs)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const nodes = Array.from(nodesMap.values())
|
|
100
|
+
const citations = nodes.map(([node]) => buildCiteprocCitation(node.attrs))
|
|
101
|
+
|
|
102
|
+
const $new: Partial<PluginState> = {
|
|
103
|
+
citationNodes: nodes,
|
|
104
|
+
citations,
|
|
105
|
+
bibliographyItems,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//TODO remove. There should always be a csl style
|
|
109
|
+
if (!csl.style) {
|
|
110
|
+
return $new as PluginState
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
$old &&
|
|
115
|
+
isEqual(citations, $old.citations) &&
|
|
116
|
+
isEqual(bibliographyItems, $old.bibliographyItems)
|
|
117
|
+
) {
|
|
118
|
+
$new.version = $old.version
|
|
119
|
+
$new.citationCounts = $old.citationCounts
|
|
120
|
+
$new.engine = $old.engine
|
|
121
|
+
$new.renderedCitations = $old.renderedCitations
|
|
122
|
+
} else {
|
|
123
|
+
const citationCounts = new Map()
|
|
124
|
+
const rids = nodes.flatMap((e) => e[0].attrs.rids)
|
|
125
|
+
rids.forEach((rid) => {
|
|
126
|
+
const count = citationCounts.get(rid) || 0
|
|
127
|
+
citationCounts.set(rid, count + 1)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const engine = new Citeproc.Engine(
|
|
131
|
+
{
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
133
|
+
retrieveLocale: () => csl.locale!,
|
|
134
|
+
retrieveItem: (id: string) => {
|
|
135
|
+
const item = bibliographyItems.get(id)
|
|
136
|
+
if (!item) {
|
|
137
|
+
throw Error(`Missing bibliography item with id ${id}`)
|
|
138
|
+
}
|
|
139
|
+
return item as CSL.Data
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
csl.style,
|
|
143
|
+
'en-US'
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
//create new citations since citeproc modifies the ones passed
|
|
147
|
+
const citationTexts = engine.rebuildProcessorState(
|
|
148
|
+
nodes.map(([node]) => buildCiteprocCitation(node.attrs))
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
$new.version = String(version++)
|
|
152
|
+
$new.citationCounts = citationCounts
|
|
153
|
+
$new.engine = engine
|
|
154
|
+
$new.renderedCitations = new Map(citationTexts.map((i) => [i[0], i[2]]))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return $new as PluginState
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const buildDecorations = (state: PluginState, doc: ManuscriptNode) => {
|
|
161
|
+
const decorations: Decoration[] = []
|
|
162
|
+
|
|
163
|
+
let hasMissingItems = false
|
|
164
|
+
|
|
165
|
+
for (const [node, pos] of state.citationNodes) {
|
|
166
|
+
const rids = node.attrs.rids
|
|
167
|
+
if (rids.length) {
|
|
168
|
+
for (const rid of rids) {
|
|
169
|
+
if (!state.bibliographyItems.has(rid)) {
|
|
170
|
+
decorations.push(
|
|
171
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
172
|
+
class: 'citation-missing',
|
|
173
|
+
})
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
hasMissingItems = true
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
decorations.push(
|
|
181
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
182
|
+
class: 'citation-empty',
|
|
183
|
+
})
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
decorations.push(
|
|
187
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
188
|
+
version: state.version,
|
|
189
|
+
})
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (hasMissingItems) {
|
|
194
|
+
doc.descendants((node, pos) => {
|
|
195
|
+
if (isBibliographyElementNode(node)) {
|
|
196
|
+
decorations.push(
|
|
197
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
198
|
+
missing: 'true',
|
|
199
|
+
})
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
decorations.push(
|
|
203
|
+
Decoration.widget(pos, () => {
|
|
204
|
+
const el = document.createElement('div')
|
|
205
|
+
el.className = 'bibliography-missing'
|
|
206
|
+
el.textContent = `The bibliography could not be generated, due to a missing library item.`
|
|
207
|
+
return el
|
|
208
|
+
})
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Use decorations to trigger bibliography element update.
|
|
216
|
+
* This is a way to communicate from the plugin to the bibliography element node without actually changing the node.
|
|
217
|
+
* We had to do that due to the absence of an actual node change.
|
|
218
|
+
* @TODO Look for a neater solution (using non-trackable attributes on bibliography_element is a proposed solution)
|
|
219
|
+
*/
|
|
220
|
+
doc.descendants((node, pos) => {
|
|
221
|
+
if (isBibliographyElementNode(node)) {
|
|
222
|
+
decorations.push(
|
|
223
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
224
|
+
version: state.version,
|
|
225
|
+
})
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
return decorations
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export const getBibliographyPluginState = (state: EditorState) => {
|
|
234
|
+
return bibliographyKey.getState(state)
|
|
235
|
+
}
|