@manuscripts/body-editor 3.12.67 → 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,90 @@
|
|
|
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 { isParagraphNode, ManuscriptNode } from '@manuscripts/transform'
|
|
18
|
+
import { Plugin } from 'prosemirror-state'
|
|
19
|
+
|
|
20
|
+
const isTextBlock = (node: ManuscriptNode): boolean => {
|
|
21
|
+
return node.type === node.type.schema.nodes.text_block
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const isTextBlockOrParagraph = (node: ManuscriptNode): boolean => {
|
|
25
|
+
return isParagraphNode(node) || isTextBlock(node)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isTableCell = (node: ManuscriptNode): boolean => {
|
|
29
|
+
const { schema } = node.type
|
|
30
|
+
return (
|
|
31
|
+
node.type === schema.nodes.table_cell ||
|
|
32
|
+
node.type === schema.nodes.table_header
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* This plugin enforces a rule that there can never be more than one adjacent empty paragraph.
|
|
38
|
+
*/
|
|
39
|
+
export default () => {
|
|
40
|
+
return new Plugin<null>({
|
|
41
|
+
appendTransaction: (transactions, oldState, newState) => {
|
|
42
|
+
const positionsToJoin: number[] = []
|
|
43
|
+
|
|
44
|
+
const tr = newState.tr
|
|
45
|
+
|
|
46
|
+
if (!transactions.some((tr) => tr.docChanged || tr.getMeta('INIT'))) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const joinAdjacentParagraphs =
|
|
51
|
+
(parent: ManuscriptNode, pos: number) =>
|
|
52
|
+
(node: ManuscriptNode, offset: number, index: number) => {
|
|
53
|
+
const nodePos = pos + offset
|
|
54
|
+
|
|
55
|
+
// Skip joining for table cells - allow multiple adjacent empty paragraphs/text_blocks there
|
|
56
|
+
if (isTableCell(parent)) {
|
|
57
|
+
node.forEach(joinAdjacentParagraphs(node, nodePos + 1))
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
isTextBlockOrParagraph(node) &&
|
|
63
|
+
node.childCount === 0 &&
|
|
64
|
+
index < parent.childCount - 1
|
|
65
|
+
) {
|
|
66
|
+
const nextNode = parent.child(index + 1)
|
|
67
|
+
|
|
68
|
+
if (isTextBlockOrParagraph(nextNode) && nextNode.childCount === 0) {
|
|
69
|
+
positionsToJoin.push(nodePos + node.nodeSize)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
node.forEach(joinAdjacentParagraphs(node, nodePos + 1))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
newState.doc.forEach(joinAdjacentParagraphs(newState.doc, 0))
|
|
77
|
+
|
|
78
|
+
if (positionsToJoin.length) {
|
|
79
|
+
// execute the joins in reverse order so the positions don't change
|
|
80
|
+
positionsToJoin.reverse()
|
|
81
|
+
|
|
82
|
+
for (const nodePos of positionsToJoin) {
|
|
83
|
+
tr.join(nodePos)
|
|
84
|
+
}
|
|
85
|
+
tr.setMeta('origin', 'paragraphs')
|
|
86
|
+
return tr
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2019 Atypon Systems LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { clear, skipTracking } from '@manuscripts/track-changes-plugin'
|
|
17
|
+
import {
|
|
18
|
+
generateNodeID,
|
|
19
|
+
isHighlightMarkerNode,
|
|
20
|
+
ManuscriptNode,
|
|
21
|
+
schema,
|
|
22
|
+
} from '@manuscripts/transform'
|
|
23
|
+
import { Plugin } from 'prosemirror-state'
|
|
24
|
+
import { isInit } from '../lib/plugins'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* This plugin ensures that all nodes which need ids (i.e. `id` is defined in the node spec's attributes) are given an id, and that there aren't any duplicate ids in the document.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const isManuscriptNode = (node: ManuscriptNode | null) => {
|
|
31
|
+
return node && node.type === schema.nodes.manuscript
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default () => {
|
|
35
|
+
return new Plugin<null>({
|
|
36
|
+
appendTransaction(transactions, oldState, newState) {
|
|
37
|
+
// only scan if nodes have changed
|
|
38
|
+
if (!transactions.some((tr) => tr.docChanged || isInit(tr))) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
const ids = new Set<string>()
|
|
42
|
+
const tr = newState.tr
|
|
43
|
+
clear(newState.doc).descendants((node, pos, parent) => {
|
|
44
|
+
if (
|
|
45
|
+
!(node.type.spec.attrs && 'id' in node.type.spec.attrs) ||
|
|
46
|
+
isHighlightMarkerNode(node) ||
|
|
47
|
+
isManuscriptNode(node) ||
|
|
48
|
+
isManuscriptNode(parent)
|
|
49
|
+
) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
let id = node.attrs.id
|
|
53
|
+
if (!id || ids.has(id)) {
|
|
54
|
+
id = generateNodeID(node.type)
|
|
55
|
+
tr.setNodeAttribute(pos, 'id', id)
|
|
56
|
+
}
|
|
57
|
+
ids.add(id)
|
|
58
|
+
})
|
|
59
|
+
skipTracking(tr)
|
|
60
|
+
tr.setMeta('origin', 'persist')
|
|
61
|
+
return tr
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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 } from '@manuscripts/transform'
|
|
18
|
+
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
19
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
20
|
+
|
|
21
|
+
export const persistentCursor = new PluginKey<{ on: boolean }>(
|
|
22
|
+
'persistent-cursor'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This plugin creates a fake text cursor when the editor is out of focus for better UX.
|
|
27
|
+
*/
|
|
28
|
+
export default () => {
|
|
29
|
+
return new Plugin({
|
|
30
|
+
state: {
|
|
31
|
+
init(_, state) {
|
|
32
|
+
return { on: false }
|
|
33
|
+
},
|
|
34
|
+
apply(tr, value) {
|
|
35
|
+
if (tr.getMeta(persistentCursor)) {
|
|
36
|
+
return tr.getMeta(persistentCursor)
|
|
37
|
+
}
|
|
38
|
+
return value
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
props: {
|
|
42
|
+
decorations(state) {
|
|
43
|
+
const selection = state.selection
|
|
44
|
+
if (this.getState(state)?.on && selection.from === selection.to) {
|
|
45
|
+
const decorations = [
|
|
46
|
+
Decoration.widget(selection.to, (view: ManuscriptEditorView) => {
|
|
47
|
+
const cursor = document.createElement('span')
|
|
48
|
+
cursor.classList.add('cursor-placeholder')
|
|
49
|
+
return cursor
|
|
50
|
+
}),
|
|
51
|
+
]
|
|
52
|
+
return DecorationSet.create(state.doc, decorations)
|
|
53
|
+
}
|
|
54
|
+
return DecorationSet.empty
|
|
55
|
+
},
|
|
56
|
+
handleDOMEvents: {
|
|
57
|
+
focus(view, event) {
|
|
58
|
+
if (this.getState(view.state)?.on) {
|
|
59
|
+
const newTr = view.state.tr.setMeta(persistentCursor, { on: false })
|
|
60
|
+
view.dispatch(newTr)
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
isFootnoteNode,
|
|
19
|
+
isGeneralTableFootnoteNode,
|
|
20
|
+
isPullquoteElement,
|
|
21
|
+
ManuscriptEditorView,
|
|
22
|
+
ManuscriptNode,
|
|
23
|
+
schema,
|
|
24
|
+
} from '@manuscripts/transform'
|
|
25
|
+
import { Plugin, TextSelection } from 'prosemirror-state'
|
|
26
|
+
import { findParentNodeOfTypeClosestToPos } from 'prosemirror-utils'
|
|
27
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
28
|
+
|
|
29
|
+
const placeholderMap: Record<string, string> = {
|
|
30
|
+
subtitle: 'Type subtitle here...',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const placeholderWidget =
|
|
34
|
+
(placeholder: string) =>
|
|
35
|
+
(view: ManuscriptEditorView, getPos: () => number | undefined) => {
|
|
36
|
+
const element = document.createElement('span')
|
|
37
|
+
element.className = 'placeholder-text'
|
|
38
|
+
element.setAttribute('aria-hidden', 'true')
|
|
39
|
+
element.dataset.placeholder = placeholder
|
|
40
|
+
element.addEventListener('click', (event: MouseEvent) => {
|
|
41
|
+
event.preventDefault()
|
|
42
|
+
const pos = getPos() as number
|
|
43
|
+
const selection = TextSelection.create(view.state.tr.doc, pos)
|
|
44
|
+
view.dispatch(view.state.tr.setSelection(selection))
|
|
45
|
+
})
|
|
46
|
+
return element
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const backmatterWidget = (direction: 'top' | 'bottom') => {
|
|
50
|
+
const element = document.createElement('div')
|
|
51
|
+
element.className = 'backmatter-border-placeholder'
|
|
52
|
+
const line = document.createElement('div')
|
|
53
|
+
line.className = direction
|
|
54
|
+
element.appendChild(line)
|
|
55
|
+
return element
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const getParagraphPlaceholderText = (
|
|
59
|
+
parent: ManuscriptNode | null,
|
|
60
|
+
node: ManuscriptNode
|
|
61
|
+
) => {
|
|
62
|
+
if (!parent) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if (isPullquoteElement(parent) && !node.textContent.length) {
|
|
66
|
+
let otherParagraphHasContent = false
|
|
67
|
+
parent.descendants((child) => {
|
|
68
|
+
if (child !== node && child.type === child.type.schema.nodes.text_block) {
|
|
69
|
+
otherParagraphHasContent = true
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
if (otherParagraphHasContent) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
return 'Insert pull quote here'
|
|
76
|
+
}
|
|
77
|
+
if (parent.textContent.length) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if (parent.type === schema.nodes.body) {
|
|
81
|
+
return 'Start typing here...'
|
|
82
|
+
}
|
|
83
|
+
if (isFootnoteNode(parent) || isGeneralTableFootnoteNode(parent)) {
|
|
84
|
+
return 'Type new footnote here'
|
|
85
|
+
}
|
|
86
|
+
if (parent.type === schema.nodes.trans_abstract) {
|
|
87
|
+
return 'Type here'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const isTextBlockOfQuoteElement = (
|
|
92
|
+
node: ManuscriptNode,
|
|
93
|
+
parent: ManuscriptNode | null
|
|
94
|
+
) =>
|
|
95
|
+
node.type === schema.nodes.text_block &&
|
|
96
|
+
!!parent &&
|
|
97
|
+
(parent.type === schema.nodes.pullquote_element ||
|
|
98
|
+
parent.type === schema.nodes.blockquote_element)
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* This plugin adds a placeholder decoration to empty nodes
|
|
102
|
+
*/
|
|
103
|
+
export default () =>
|
|
104
|
+
new Plugin<null>({
|
|
105
|
+
props: {
|
|
106
|
+
decorations: (state) => {
|
|
107
|
+
const decorations: Decoration[] = []
|
|
108
|
+
|
|
109
|
+
state.doc.descendants((node, pos, parent) => {
|
|
110
|
+
if (!node.isAtom && node.type.isBlock && node.childCount === 0) {
|
|
111
|
+
if (node.type === node.type.schema.nodes.attribution) {
|
|
112
|
+
decorations.push(
|
|
113
|
+
Decoration.widget(
|
|
114
|
+
pos + 1,
|
|
115
|
+
placeholderWidget('Insert reference here')
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
node.type === node.type.schema.nodes.paragraph ||
|
|
121
|
+
isTextBlockOfQuoteElement(node, parent)
|
|
122
|
+
) {
|
|
123
|
+
const text = getParagraphPlaceholderText(parent, node)
|
|
124
|
+
if (text) {
|
|
125
|
+
decorations.push(
|
|
126
|
+
Decoration.widget(pos + 1, placeholderWidget(text))
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
} else if (node.type === node.type.schema.nodes.section_title) {
|
|
130
|
+
const $pos = state.doc.resolve(pos)
|
|
131
|
+
|
|
132
|
+
let placeholderText = 'Type heading here'
|
|
133
|
+
|
|
134
|
+
const boxElement = findParentNodeOfTypeClosestToPos(
|
|
135
|
+
$pos,
|
|
136
|
+
schema.nodes.box_element
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if (boxElement) {
|
|
140
|
+
const section = findParentNodeOfTypeClosestToPos(
|
|
141
|
+
$pos,
|
|
142
|
+
schema.nodes.section
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// If the section is a direct child of the box_element,
|
|
146
|
+
// it's the top-level section and should have the box placeholder
|
|
147
|
+
if (section && section.depth === boxElement.depth + 1) {
|
|
148
|
+
placeholderText = 'Optional box title...'
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
findParentNodeOfTypeClosestToPos($pos, schema.nodes.supplements)
|
|
154
|
+
) {
|
|
155
|
+
placeholderText = 'Supplements'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
decorations.push(
|
|
159
|
+
Decoration.widget(pos + 1, placeholderWidget(placeholderText))
|
|
160
|
+
)
|
|
161
|
+
} else if (node.type === node.type.schema.nodes.trans_abstract) {
|
|
162
|
+
decorations.push(
|
|
163
|
+
Decoration.widget(
|
|
164
|
+
pos + 1,
|
|
165
|
+
placeholderWidget('Type new abstract title here')
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
} else if (node.type === schema.nodes.caption_title) {
|
|
169
|
+
decorations.push(
|
|
170
|
+
Decoration.widget(
|
|
171
|
+
pos + 1,
|
|
172
|
+
placeholderWidget('Caption title...'),
|
|
173
|
+
{
|
|
174
|
+
key: node.attrs.id,
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
} else if (
|
|
179
|
+
node.type === schema.nodes.text_block &&
|
|
180
|
+
parent &&
|
|
181
|
+
parent.type === schema.nodes.caption
|
|
182
|
+
) {
|
|
183
|
+
decorations.push(
|
|
184
|
+
Decoration.widget(pos + 1, placeholderWidget('Caption...'))
|
|
185
|
+
)
|
|
186
|
+
} else {
|
|
187
|
+
const placeholder = placeholderMap[node.type.name]
|
|
188
|
+
decorations.push(
|
|
189
|
+
Decoration.node(pos, pos + node.nodeSize, {
|
|
190
|
+
class: 'empty-node',
|
|
191
|
+
...(placeholder && { 'data-placeholder': placeholder }),
|
|
192
|
+
})
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
} else if (node.type === schema.nodes.backmatter) {
|
|
196
|
+
decorations.push(
|
|
197
|
+
Decoration.widget(pos + 1, backmatterWidget('top'))
|
|
198
|
+
)
|
|
199
|
+
const nextNode = parent && parent.nodeAt(pos + node.nodeSize)
|
|
200
|
+
if (
|
|
201
|
+
nextNode &&
|
|
202
|
+
(nextNode.type === schema.nodes.supplements ||
|
|
203
|
+
nextNode.type === schema.nodes.hero_image ||
|
|
204
|
+
nextNode.type === schema.nodes.attachments)
|
|
205
|
+
) {
|
|
206
|
+
decorations.push(
|
|
207
|
+
Decoration.widget(
|
|
208
|
+
pos + node.nodeSize - 1,
|
|
209
|
+
backmatterWidget('bottom')
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
return DecorationSet.create(state.doc, decorations)
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
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 { skipTracking } from '@manuscripts/track-changes-plugin'
|
|
18
|
+
import { schema } from '@manuscripts/transform'
|
|
19
|
+
import { Plugin } from 'prosemirror-state'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* This plugin prevent complete removal of minimal allowed content - empty title and empty paragraph in the only section in the doc
|
|
23
|
+
*/
|
|
24
|
+
export default () => {
|
|
25
|
+
return new Plugin<null>({
|
|
26
|
+
appendTransaction(transactions, oldState, newState) {
|
|
27
|
+
let emptyBody = false
|
|
28
|
+
let bodyPos = -1
|
|
29
|
+
newState.doc.descendants((node, pos) => {
|
|
30
|
+
if (bodyPos >= 0) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
if (node.type === schema.nodes.body) {
|
|
34
|
+
bodyPos = pos
|
|
35
|
+
emptyBody = node.childCount == 0
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (emptyBody) {
|
|
40
|
+
const tr = newState.tr
|
|
41
|
+
tr.insert(bodyPos + 1, schema.nodes.paragraph.create())
|
|
42
|
+
tr.setMeta('prevent-empty', true)
|
|
43
|
+
skipTracking(tr)
|
|
44
|
+
return tr
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return null
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
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 { EditorState, Plugin, PluginKey, Selection } from 'prosemirror-state'
|
|
18
|
+
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
19
|
+
|
|
20
|
+
import { getClosestMatch, getMatches } from './lib'
|
|
21
|
+
|
|
22
|
+
export type SearchReplacePluginState = {
|
|
23
|
+
value: string
|
|
24
|
+
matches: Array<{ from: number; to: number }>
|
|
25
|
+
replaceValue: string
|
|
26
|
+
active: boolean // if the plugin actively display matches
|
|
27
|
+
activeAdvanced: boolean // enabling advanced view for search and replace
|
|
28
|
+
currentMatch: number
|
|
29
|
+
caseSensitive: boolean
|
|
30
|
+
ignoreDiacritics: boolean
|
|
31
|
+
highlightCurrent: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const searchReplaceKey = new PluginKey<SearchReplacePluginState>(
|
|
35
|
+
'findReplace'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
function buildPluginState(
|
|
39
|
+
state: EditorState,
|
|
40
|
+
oldData?: SearchReplacePluginState,
|
|
41
|
+
newData?: Partial<SearchReplacePluginState>,
|
|
42
|
+
selection?: Selection,
|
|
43
|
+
pointerChanged?: boolean
|
|
44
|
+
) {
|
|
45
|
+
// this is needed to allow components that update this plugin states to update it partially and not always the entire state
|
|
46
|
+
const data: SearchReplacePluginState = {
|
|
47
|
+
value: '',
|
|
48
|
+
replaceValue: '',
|
|
49
|
+
active: false,
|
|
50
|
+
activeAdvanced: false,
|
|
51
|
+
currentMatch: -1, // index of a currently selected match
|
|
52
|
+
highlightCurrent: false,
|
|
53
|
+
matches: [],
|
|
54
|
+
caseSensitive: false,
|
|
55
|
+
ignoreDiacritics: true,
|
|
56
|
+
...oldData,
|
|
57
|
+
...newData,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// creating a new set of matches if search value has changed and not falsy
|
|
61
|
+
data.matches = getMatches(
|
|
62
|
+
state.doc,
|
|
63
|
+
data.value,
|
|
64
|
+
data.caseSensitive,
|
|
65
|
+
data.ignoreDiacritics
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// if user changes pointer position - we drop previous index so if next time user move selection it will be recalculated against the new pointer
|
|
69
|
+
if (pointerChanged && selection && data.matches.length) {
|
|
70
|
+
data.currentMatch = getClosestMatch('right', selection, data.matches)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// pointer was changed not by the UI of Find & Search, stop highlight as some other component is managing the selection
|
|
74
|
+
if (!newData && pointerChanged) {
|
|
75
|
+
data.highlightCurrent = false
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// setting new currentMatch if search value is updated
|
|
79
|
+
if (
|
|
80
|
+
selection &&
|
|
81
|
+
newData?.value &&
|
|
82
|
+
oldData?.value !== newData?.value &&
|
|
83
|
+
data.matches.length
|
|
84
|
+
) {
|
|
85
|
+
data.currentMatch = getClosestMatch('right', selection, data.matches)
|
|
86
|
+
}
|
|
87
|
+
return data
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default () => {
|
|
91
|
+
return new Plugin<SearchReplacePluginState>({
|
|
92
|
+
key: searchReplaceKey,
|
|
93
|
+
state: {
|
|
94
|
+
init(config, instance) {
|
|
95
|
+
return buildPluginState(instance)
|
|
96
|
+
},
|
|
97
|
+
apply(tr, value, oldState, newState) {
|
|
98
|
+
const $old = searchReplaceKey.getState(oldState)
|
|
99
|
+
const selectionChanged = !!tr.getMeta('pointer') || tr.selectionSet
|
|
100
|
+
if (!$old || tr.getMeta(searchReplaceKey) || selectionChanged) {
|
|
101
|
+
return buildPluginState(
|
|
102
|
+
newState,
|
|
103
|
+
$old,
|
|
104
|
+
tr.getMeta(searchReplaceKey),
|
|
105
|
+
newState.selection,
|
|
106
|
+
selectionChanged
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
return $old
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
props: {
|
|
113
|
+
decorations: (state) => {
|
|
114
|
+
const pluginState = searchReplaceKey.getState(state)
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
!pluginState ||
|
|
118
|
+
!pluginState.value ||
|
|
119
|
+
(!pluginState.active && !pluginState.activeAdvanced)
|
|
120
|
+
) {
|
|
121
|
+
return DecorationSet.empty
|
|
122
|
+
}
|
|
123
|
+
const decorations: Decoration[] = []
|
|
124
|
+
|
|
125
|
+
pluginState.matches.forEach(({ from, to }, i) => {
|
|
126
|
+
let className = 'search-result'
|
|
127
|
+
if (pluginState.highlightCurrent && pluginState.currentMatch === i) {
|
|
128
|
+
className = className + ' search-result-selected'
|
|
129
|
+
}
|
|
130
|
+
decorations.push(
|
|
131
|
+
Decoration.inline(from, to, {
|
|
132
|
+
class: className,
|
|
133
|
+
})
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
return DecorationSet.create(state.doc, decorations)
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
view() {
|
|
141
|
+
return {
|
|
142
|
+
update() {
|
|
143
|
+
const element = document.querySelector(
|
|
144
|
+
'.search-result.search-result-selected'
|
|
145
|
+
)
|
|
146
|
+
if (element) {
|
|
147
|
+
// @TODO - there is an identical function scrollIntoView in article-editor, think about maybe unifying
|
|
148
|
+
const rect = element.getBoundingClientRect()
|
|
149
|
+
if (rect.bottom > window.innerHeight || rect.top < 150) {
|
|
150
|
+
element.scrollIntoView()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
}
|