@manuscripts/body-editor 3.13.18 → 3.14.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/commands.js +30 -2
- package/dist/cjs/components/views/DeleteSupplementDialog.js +75 -0
- package/dist/cjs/icons.js +7 -1
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/lib/files.js +17 -5
- package/dist/cjs/lib/supplements.js +61 -0
- package/dist/cjs/versions.js +1 -1
- package/dist/cjs/views/caption_title.js +1 -0
- package/dist/cjs/views/hero_image.js +108 -1
- package/dist/cjs/views/supplement.js +21 -1
- package/dist/es/commands.js +26 -0
- package/dist/es/components/views/DeleteSupplementDialog.js +35 -0
- package/dist/es/icons.js +7 -1
- package/dist/es/index.js +2 -0
- package/dist/es/lib/files.js +17 -5
- package/dist/es/lib/supplements.js +55 -0
- package/dist/es/versions.js +1 -1
- package/dist/es/views/caption_title.js +1 -0
- package/dist/es/views/hero_image.js +109 -2
- package/dist/es/views/supplement.js +22 -2
- package/dist/types/commands.d.ts +2 -0
- package/dist/types/components/views/DeleteSupplementDialog.d.ts +2 -0
- package/dist/types/icons.d.ts +6 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/files.d.ts +2 -0
- package/dist/types/lib/supplements.d.ts +30 -0
- package/dist/types/versions.d.ts +1 -1
- package/dist/types/views/hero_image.d.ts +9 -2
- package/package.json +2 -2
- package/src/commands.ts +42 -0
- package/src/components/views/DeleteSupplementDialog.tsx +88 -0
- package/src/icons.ts +12 -0
- package/src/index.ts +2 -0
- package/src/lib/files.ts +20 -6
- package/src/lib/supplements.ts +97 -0
- package/src/versions.ts +1 -1
- package/src/views/caption_title.ts +2 -0
- package/src/views/hero_image.ts +144 -5
- package/src/views/supplement.ts +27 -2
- package/src/views/supplements.ts +2 -1
- package/styles/AdvancedEditor.css +57 -0
- package/styles/Editor.css +13 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2026 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 { schema, } from '@manuscripts/transform';
|
|
17
|
+
import { findChildrenByType, findParentNodeClosestToPos } from 'prosemirror-utils';
|
|
18
|
+
import { allowedHref } from './url';
|
|
19
|
+
export const getSupplementDisplayLabel = (node, files) => {
|
|
20
|
+
const href = node.attrs.href;
|
|
21
|
+
if (allowedHref(href)) {
|
|
22
|
+
return href;
|
|
23
|
+
}
|
|
24
|
+
const file = files.find((f) => f.id === href);
|
|
25
|
+
return file?.name ?? 'Untitled supplement';
|
|
26
|
+
};
|
|
27
|
+
export const performDeleteSupplement = (view, pos) => {
|
|
28
|
+
const node = view.state.doc.nodeAt(pos);
|
|
29
|
+
if (!node || node.type !== schema.nodes.supplement) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const from = pos;
|
|
33
|
+
const to = from + node.nodeSize;
|
|
34
|
+
const { from: deleteFrom, to: deleteTo } = deleteSupplementAtPos(view.state.doc, from, to);
|
|
35
|
+
view.dispatch(view.state.tr.delete(deleteFrom, deleteTo));
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
38
|
+
export const deleteSupplementAtPos = (doc, from, to) => {
|
|
39
|
+
const resolvedPos = doc.resolve(from);
|
|
40
|
+
const supplementsNodeWithPos = findParentNodeClosestToPos(resolvedPos, (node) => node.type === schema.nodes.supplements);
|
|
41
|
+
if (!supplementsNodeWithPos) {
|
|
42
|
+
return { from, to, deleteWholeSection: false };
|
|
43
|
+
}
|
|
44
|
+
const { node: supplementsNode, pos: supplementsPos } = supplementsNodeWithPos;
|
|
45
|
+
const supplements = findChildrenByType(supplementsNode, schema.nodes.supplement);
|
|
46
|
+
const isDeletingLastSupplement = supplements.length === 1;
|
|
47
|
+
if (isDeletingLastSupplement) {
|
|
48
|
+
return {
|
|
49
|
+
from: supplementsPos,
|
|
50
|
+
to: supplementsPos + supplementsNode.nodeSize,
|
|
51
|
+
deleteWholeSection: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { from, to, deleteWholeSection: false };
|
|
55
|
+
};
|
package/dist/es/versions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '3.
|
|
1
|
+
export const VERSION = '3.14.0';
|
|
2
2
|
export const MATHJAX_VERSION = '3.2.2';
|
|
@@ -13,25 +13,71 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { arrowUp } from '../icons';
|
|
16
|
+
import { arrowUp, leadingHalfLeftIcon, leadingHeroImageIcon, leadingLargeFloatIcon, leadingSmallFloatIcon, leadingWallpaperIcon, } from '../icons';
|
|
17
17
|
import { handleEnterKey } from '../lib/navigation-utils';
|
|
18
18
|
import BlockView from './block_view';
|
|
19
19
|
import { createNodeView } from './creators';
|
|
20
|
+
const LAYOUT_OPTIONS_CONFIG = [
|
|
21
|
+
{
|
|
22
|
+
label: 'Hero Image',
|
|
23
|
+
icon: leadingHeroImageIcon,
|
|
24
|
+
attr: 'leading',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
label: 'Float Image - Small',
|
|
28
|
+
icon: leadingSmallFloatIcon,
|
|
29
|
+
attr: 'leading-small-float',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Float Image - Large',
|
|
33
|
+
icon: leadingLargeFloatIcon,
|
|
34
|
+
attr: 'leading-large-float',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 'Wallpaper Image',
|
|
38
|
+
icon: leadingWallpaperIcon,
|
|
39
|
+
attr: 'leading-wallpaper',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'Half Image',
|
|
43
|
+
icon: leadingHalfLeftIcon,
|
|
44
|
+
attr: 'leading-half-left',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
20
47
|
export class HeroImageView extends BlockView {
|
|
21
48
|
constructor() {
|
|
22
49
|
super(...arguments);
|
|
23
50
|
this.collapsed = false;
|
|
51
|
+
this.layoutPanelCollapsed = false;
|
|
24
52
|
this.ignoreMutation = () => true;
|
|
25
53
|
this.createElement = () => {
|
|
26
54
|
this.container = document.createElement('div');
|
|
27
55
|
this.container.classList.add('block', 'hero-image-container');
|
|
28
56
|
this.dom.appendChild(this.container);
|
|
57
|
+
this.abortController = new AbortController();
|
|
29
58
|
this.container.appendChild(this.createPanel());
|
|
59
|
+
this.container.appendChild(this.createLayoutPanel());
|
|
60
|
+
this.layoutOptions = this.createLayoutOptions();
|
|
61
|
+
this.layoutOptions.addEventListener('change', this.onSelectOption, {
|
|
62
|
+
signal: this.abortController.signal,
|
|
63
|
+
});
|
|
64
|
+
this.container.appendChild(this.layoutOptions);
|
|
30
65
|
this.contentDOM = document.createElement('figure');
|
|
31
66
|
this.contentDOM.classList.add('figure-block', 'hero-image-figure');
|
|
32
67
|
this.contentDOM.setAttribute('id', this.node.attrs.id);
|
|
33
68
|
this.container.appendChild(this.contentDOM);
|
|
34
69
|
};
|
|
70
|
+
this.onSelectOption = (e) => {
|
|
71
|
+
const target = e.target;
|
|
72
|
+
if (target && target.type === 'radio' && target.name === 'layout-option') {
|
|
73
|
+
const { state, dispatch } = this.view;
|
|
74
|
+
const tr = state.tr.setNodeMarkup(this.getPos(), undefined, {
|
|
75
|
+
...this.node.attrs,
|
|
76
|
+
type: target.value,
|
|
77
|
+
});
|
|
78
|
+
dispatch(tr);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
35
81
|
}
|
|
36
82
|
createPanel() {
|
|
37
83
|
const panel = document.createElement('div');
|
|
@@ -51,10 +97,71 @@ export class HeroImageView extends BlockView {
|
|
|
51
97
|
heroImageToggleBtn.classList.toggle('collapsed', this.collapsed);
|
|
52
98
|
};
|
|
53
99
|
heroImageToggleBtn.onclick = handleToggle;
|
|
54
|
-
heroImageToggleBtn.addEventListener('keydown', handleEnterKey(handleToggle)
|
|
100
|
+
heroImageToggleBtn.addEventListener('keydown', handleEnterKey(handleToggle), {
|
|
101
|
+
signal: this.abortController.signal,
|
|
102
|
+
});
|
|
55
103
|
panel.appendChild(label);
|
|
56
104
|
panel.appendChild(heroImageToggleBtn);
|
|
57
105
|
return panel;
|
|
58
106
|
}
|
|
107
|
+
createLayoutPanel() {
|
|
108
|
+
const panel = document.createElement('div');
|
|
109
|
+
panel.classList.add('panel-header', 'layout-header');
|
|
110
|
+
const label = document.createElement('span');
|
|
111
|
+
label.textContent = 'Layout';
|
|
112
|
+
label.contentEditable = 'false';
|
|
113
|
+
const toggleBtn = document.createElement('button');
|
|
114
|
+
toggleBtn.classList.add('toggle-btn', 'button-reset');
|
|
115
|
+
toggleBtn.innerHTML = arrowUp;
|
|
116
|
+
toggleBtn.classList.toggle('collapsed', this.layoutPanelCollapsed);
|
|
117
|
+
const handleToggle = () => {
|
|
118
|
+
this.layoutPanelCollapsed = !this.layoutPanelCollapsed;
|
|
119
|
+
if (this.layoutOptions) {
|
|
120
|
+
this.layoutOptions.style.display = this.layoutPanelCollapsed
|
|
121
|
+
? 'none'
|
|
122
|
+
: '';
|
|
123
|
+
}
|
|
124
|
+
toggleBtn.classList.toggle('collapsed', this.layoutPanelCollapsed);
|
|
125
|
+
};
|
|
126
|
+
toggleBtn.onclick = handleToggle;
|
|
127
|
+
toggleBtn.addEventListener('keydown', handleEnterKey(handleToggle), {
|
|
128
|
+
signal: this.abortController.signal,
|
|
129
|
+
});
|
|
130
|
+
panel.appendChild(label);
|
|
131
|
+
panel.appendChild(toggleBtn);
|
|
132
|
+
return panel;
|
|
133
|
+
}
|
|
134
|
+
createLayoutOptions() {
|
|
135
|
+
const can = this.props.getCapabilities();
|
|
136
|
+
const optionsGroup = document.createElement('div');
|
|
137
|
+
optionsGroup.className = 'layout-options-group';
|
|
138
|
+
LAYOUT_OPTIONS_CONFIG.forEach(({ label, icon, attr }) => {
|
|
139
|
+
const image = document.createElement('span');
|
|
140
|
+
image.className = 'layout-option-image';
|
|
141
|
+
image.innerHTML = icon;
|
|
142
|
+
const input = document.createElement('input');
|
|
143
|
+
input.type = 'radio';
|
|
144
|
+
input.name = 'layout-option';
|
|
145
|
+
input.value = attr;
|
|
146
|
+
input.disabled = !can.editMetadata;
|
|
147
|
+
input.checked = attr === this.node.attrs.type;
|
|
148
|
+
const inputLabel = document.createElement('span');
|
|
149
|
+
inputLabel.className = 'layout-option-label';
|
|
150
|
+
inputLabel.textContent = label;
|
|
151
|
+
const wrapper = document.createElement('span');
|
|
152
|
+
wrapper.className = 'layout-option-label-wrapper';
|
|
153
|
+
wrapper.append(input, inputLabel);
|
|
154
|
+
const container = document.createElement('div');
|
|
155
|
+
container.className = 'layout-option';
|
|
156
|
+
container.append(image, wrapper);
|
|
157
|
+
optionsGroup.appendChild(container);
|
|
158
|
+
});
|
|
159
|
+
optionsGroup.contentEditable = 'false';
|
|
160
|
+
return optionsGroup;
|
|
161
|
+
}
|
|
162
|
+
destroy() {
|
|
163
|
+
this.abortController?.abort();
|
|
164
|
+
super.destroy();
|
|
165
|
+
}
|
|
59
166
|
}
|
|
60
167
|
export default createNodeView(HeroImageView);
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { getFileIcon } from '@manuscripts/style-guide';
|
|
17
17
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
18
|
-
import { draggableIcon } from '../icons';
|
|
18
|
+
import { draggableIcon, webLinkIcon } from '../icons';
|
|
19
19
|
import { findNodeByID } from '../lib/doc';
|
|
20
|
+
import { allowedHref } from '../lib/url';
|
|
20
21
|
import { BaseNodeView } from './base_node_view';
|
|
21
22
|
import { createNodeView } from './creators';
|
|
22
23
|
export class SupplementView extends BaseNodeView {
|
|
@@ -140,8 +141,27 @@ export class SupplementView extends BaseNodeView {
|
|
|
140
141
|
this.supplementInfoEl = document.createElement('div');
|
|
141
142
|
this.supplementInfoEl.classList.add('supplement-file-info');
|
|
142
143
|
this.supplementInfoEl.contentEditable = 'false';
|
|
144
|
+
const href = this.node.attrs.href;
|
|
145
|
+
if (allowedHref(href)) {
|
|
146
|
+
const iconElement = document.createElement('span');
|
|
147
|
+
iconElement.classList.add('supplement-file-icon');
|
|
148
|
+
iconElement.innerHTML = webLinkIcon;
|
|
149
|
+
this.supplementInfoEl.appendChild(iconElement);
|
|
150
|
+
const urlLink = document.createElement('a');
|
|
151
|
+
urlLink.classList.add('supplement-weblink-url');
|
|
152
|
+
urlLink.textContent = href;
|
|
153
|
+
if (allowedHref(href)) {
|
|
154
|
+
urlLink.href = href;
|
|
155
|
+
urlLink.target = '_blank';
|
|
156
|
+
urlLink.rel = 'noopener noreferrer';
|
|
157
|
+
}
|
|
158
|
+
urlLink.addEventListener('mousedown', (e) => e.stopPropagation());
|
|
159
|
+
this.supplementInfoEl.appendChild(urlLink);
|
|
160
|
+
this.dom.appendChild(this.supplementInfoEl);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
143
163
|
const files = this.props.getFiles();
|
|
144
|
-
const file = files.find((f) => f.id ===
|
|
164
|
+
const file = files.find((f) => f.id === href);
|
|
145
165
|
if (file) {
|
|
146
166
|
const iconElement = document.createElement('span');
|
|
147
167
|
iconElement.classList.add('supplement-file-icon');
|
package/dist/types/commands.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export declare const insertFigure: (file: FileAttachment, state: ManuscriptEdito
|
|
|
34
34
|
export declare const insertEmbed: (state: ManuscriptEditorState, dispatch?: Dispatch, attrs?: Attrs) => boolean;
|
|
35
35
|
export declare const insertTable: (config: TableConfig, state: ManuscriptEditorState, dispatch?: Dispatch) => boolean;
|
|
36
36
|
export declare const insertSupplement: (file: FileAttachment, view: ManuscriptEditorView, setSelection?: boolean) => boolean;
|
|
37
|
+
export declare const insertSupplementWeblink: (url: string, title: string, view: ManuscriptEditorView) => boolean;
|
|
38
|
+
export declare const updateSupplementWeblink: (pos: number, url: string, title: string, view: ManuscriptEditorView) => boolean;
|
|
37
39
|
export declare const insertAttachment: (file: FileAttachment, state: ManuscriptEditorState, type: string, dispatch?: Dispatch) => boolean;
|
|
38
40
|
export declare const insertBlock: (nodeType: ManuscriptNodeType) => (state: ManuscriptEditorState, dispatch?: Dispatch) => boolean;
|
|
39
41
|
export declare const deleteBlock: (typeToDelete: string) => (state: ManuscriptEditorState, dispatch?: Dispatch) => boolean;
|
package/dist/types/icons.d.ts
CHANGED
|
@@ -16,6 +16,12 @@ export declare const fileCorruptedIcon: string;
|
|
|
16
16
|
export declare const draggableIcon: string;
|
|
17
17
|
export declare const translateIcon: string;
|
|
18
18
|
export declare const linkIcon: string;
|
|
19
|
+
export declare const leadingHeroImageIcon: string;
|
|
20
|
+
export declare const leadingSmallFloatIcon: string;
|
|
21
|
+
export declare const leadingLargeFloatIcon: string;
|
|
22
|
+
export declare const leadingWallpaperIcon: string;
|
|
23
|
+
export declare const leadingHalfLeftIcon: string;
|
|
19
24
|
export declare const fileMainDocumentIcon: string;
|
|
20
25
|
export declare const tickIcon: string;
|
|
21
26
|
export declare const ORCIDIcon: string;
|
|
27
|
+
export declare const webLinkIcon: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export * from './lib/capabilities';
|
|
|
29
29
|
export * from './lib/navigation-utils';
|
|
30
30
|
export * from './lib/comments';
|
|
31
31
|
export * from './lib/files';
|
|
32
|
+
export * from './lib/supplements';
|
|
33
|
+
export { allowedHref } from './lib/url';
|
|
32
34
|
export * from './lib/footnotes';
|
|
33
35
|
export * from './lib/template';
|
|
34
36
|
export * from './lib/doc';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ManuscriptNode } from '@manuscripts/transform';
|
|
2
|
+
import { NodeWeblink } from './supplements';
|
|
2
3
|
export type FileAttachment = {
|
|
3
4
|
id: string;
|
|
4
5
|
name: string;
|
|
@@ -18,6 +19,7 @@ export type ElementFiles = {
|
|
|
18
19
|
export type ManuscriptFiles = {
|
|
19
20
|
figures: ElementFiles[];
|
|
20
21
|
supplements: NodeFile[];
|
|
22
|
+
weblinks: NodeWeblink[];
|
|
21
23
|
attachments: NodeFile[];
|
|
22
24
|
linkedFiles: NodeFile[];
|
|
23
25
|
others: FileAttachment[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2026 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 { ManuscriptEditorView, ManuscriptNode, SupplementNode } from '@manuscripts/transform';
|
|
17
|
+
export type NodeWeblink = {
|
|
18
|
+
node: SupplementNode;
|
|
19
|
+
pos: number;
|
|
20
|
+
};
|
|
21
|
+
export declare const getSupplementDisplayLabel: (node: SupplementNode, files: {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
}[]) => string;
|
|
25
|
+
export declare const performDeleteSupplement: (view: ManuscriptEditorView, pos: number) => boolean;
|
|
26
|
+
export declare const deleteSupplementAtPos: (doc: ManuscriptNode, from: number, to: number) => {
|
|
27
|
+
from: number;
|
|
28
|
+
to: number;
|
|
29
|
+
deleteWholeSection: boolean;
|
|
30
|
+
};
|
package/dist/types/versions.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "3.
|
|
1
|
+
export declare const VERSION = "3.14.0";
|
|
2
2
|
export declare const MATHJAX_VERSION = "3.2.2";
|
|
@@ -13,15 +13,22 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { HeroImageNode } from '@manuscripts/transform';
|
|
17
17
|
import { Trackable } from '../types';
|
|
18
18
|
import BlockView from './block_view';
|
|
19
|
-
export declare class HeroImageView extends BlockView<Trackable<
|
|
19
|
+
export declare class HeroImageView extends BlockView<Trackable<HeroImageNode>> {
|
|
20
20
|
private container;
|
|
21
|
+
private layoutOptions;
|
|
22
|
+
private abortController;
|
|
21
23
|
private collapsed;
|
|
24
|
+
private layoutPanelCollapsed;
|
|
22
25
|
ignoreMutation: () => boolean;
|
|
23
26
|
createElement: () => void;
|
|
24
27
|
createPanel(): HTMLDivElement;
|
|
28
|
+
createLayoutPanel(): HTMLDivElement;
|
|
29
|
+
createLayoutOptions(): HTMLDivElement;
|
|
30
|
+
private onSelectOption;
|
|
31
|
+
destroy(): void;
|
|
25
32
|
}
|
|
26
33
|
declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch) => import("../types").NodeViewCreator<HeroImageView>;
|
|
27
34
|
export default _default;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manuscripts/body-editor",
|
|
3
3
|
"description": "Prosemirror components for editing and viewing manuscripts",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.14.0",
|
|
5
5
|
"repository": "github:Atypon-OpenSource/manuscripts-body-editor",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"main": "dist/cjs",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@citation-js/plugin-pubmed": "0.3.0",
|
|
42
42
|
"@citation-js/plugin-ris": "0.7.18",
|
|
43
43
|
"@iarna/word-count": "1.1.2",
|
|
44
|
-
"@manuscripts/style-guide": "3.
|
|
44
|
+
"@manuscripts/style-guide": "3.7.0",
|
|
45
45
|
"@manuscripts/track-changes-plugin": "2.4.0",
|
|
46
46
|
"@manuscripts/transform": "4.4.6",
|
|
47
47
|
"@popperjs/core": "2.11.8",
|
package/src/commands.ts
CHANGED
|
@@ -517,6 +517,48 @@ export const insertSupplement = (
|
|
|
517
517
|
return true
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
+
export const insertSupplementWeblink = (
|
|
521
|
+
url: string,
|
|
522
|
+
title: string,
|
|
523
|
+
view: ManuscriptEditorView
|
|
524
|
+
) => {
|
|
525
|
+
const supplement = schema.nodes.supplement.createAndFill(
|
|
526
|
+
{
|
|
527
|
+
id: generateNodeID(schema.nodes.supplement),
|
|
528
|
+
href: url
|
|
529
|
+
},
|
|
530
|
+
createAndFillCaption()
|
|
531
|
+
) as SupplementNode
|
|
532
|
+
|
|
533
|
+
const tr = view.state.tr
|
|
534
|
+
const { pos } = upsertSupplementsSection(tr, supplement)
|
|
535
|
+
tr.setSelection(NodeSelection.create(tr.doc, pos))
|
|
536
|
+
view.focus()
|
|
537
|
+
view.dispatch(tr.scrollIntoView())
|
|
538
|
+
|
|
539
|
+
return true
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export const updateSupplementWeblink = (
|
|
543
|
+
pos: number,
|
|
544
|
+
url: string,
|
|
545
|
+
title: string,
|
|
546
|
+
view: ManuscriptEditorView
|
|
547
|
+
) => {
|
|
548
|
+
const node = view.state.doc.nodeAt(pos)
|
|
549
|
+
if (!node) {
|
|
550
|
+
return false
|
|
551
|
+
}
|
|
552
|
+
const tr = view.state.tr
|
|
553
|
+
tr.setNodeMarkup(pos, undefined, {
|
|
554
|
+
...node.attrs,
|
|
555
|
+
href: url,
|
|
556
|
+
title: title,
|
|
557
|
+
})
|
|
558
|
+
view.dispatch(tr)
|
|
559
|
+
return true
|
|
560
|
+
}
|
|
561
|
+
|
|
520
562
|
export const insertAttachment = (
|
|
521
563
|
file: FileAttachment,
|
|
522
564
|
state: ManuscriptEditorState,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2026 Atypon Systems LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import {
|
|
17
|
+
Category,
|
|
18
|
+
Dialog,
|
|
19
|
+
} from '@manuscripts/style-guide'
|
|
20
|
+
import { ManuscriptEditorView, SupplementNode } from '@manuscripts/transform'
|
|
21
|
+
import React, { useState } from 'react'
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
getSupplementDisplayLabel,
|
|
25
|
+
performDeleteSupplement,
|
|
26
|
+
} from '../../lib/supplements'
|
|
27
|
+
import { getEditorProps } from '../../plugins/editor-props'
|
|
28
|
+
import ReactSubView from '../../views/ReactSubView'
|
|
29
|
+
|
|
30
|
+
const DeleteSupplementDialog: React.FC<{
|
|
31
|
+
label: string
|
|
32
|
+
onDelete: () => void
|
|
33
|
+
}> = ({ label, onDelete }) => {
|
|
34
|
+
const [isOpen, setOpen] = useState(true)
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Dialog
|
|
38
|
+
isOpen={isOpen}
|
|
39
|
+
category={Category.confirmation}
|
|
40
|
+
header='Delete supplement'
|
|
41
|
+
message='Are you sure you want to delete “{label}”?'
|
|
42
|
+
actions={{
|
|
43
|
+
primary: {
|
|
44
|
+
action: () => {
|
|
45
|
+
onDelete()
|
|
46
|
+
setOpen(false)
|
|
47
|
+
},
|
|
48
|
+
title: 'Delete',
|
|
49
|
+
},
|
|
50
|
+
secondary: {
|
|
51
|
+
action: () => setOpen(false),
|
|
52
|
+
title: 'Cancel',
|
|
53
|
+
},
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const openDeleteSupplementDialog = (
|
|
60
|
+
view: ManuscriptEditorView,
|
|
61
|
+
pos: number
|
|
62
|
+
) => {
|
|
63
|
+
const node = view.state.doc.nodeAt(pos)
|
|
64
|
+
if (!node || node.type.name !== 'supplement') {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { state } = view
|
|
69
|
+
const props = getEditorProps(state)
|
|
70
|
+
const label = getSupplementDisplayLabel(
|
|
71
|
+
node as SupplementNode,
|
|
72
|
+
props.getFiles()
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const dialog = ReactSubView(
|
|
76
|
+
props,
|
|
77
|
+
DeleteSupplementDialog,
|
|
78
|
+
{
|
|
79
|
+
label,
|
|
80
|
+
onDelete: () => performDeleteSupplement(view, pos),
|
|
81
|
+
},
|
|
82
|
+
state.doc,
|
|
83
|
+
() => 0,
|
|
84
|
+
view
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
document.body.appendChild(dialog)
|
|
88
|
+
}
|
package/src/icons.ts
CHANGED
|
@@ -29,12 +29,18 @@ import {
|
|
|
29
29
|
ImageRightIcon,
|
|
30
30
|
LinkIcon,
|
|
31
31
|
LockIcon,
|
|
32
|
+
LeadingHalfLeftIcon,
|
|
33
|
+
LeadingHeroImageIcon,
|
|
34
|
+
LeadingLargeFloatIcon,
|
|
35
|
+
LeadingSmallFloatIcon,
|
|
36
|
+
LeadingWallpaperIcon,
|
|
32
37
|
ORCIDIcon as ORCID,
|
|
33
38
|
PlusIcon,
|
|
34
39
|
ScrollIcon,
|
|
35
40
|
SectionCategoryIcon,
|
|
36
41
|
TickIcon,
|
|
37
42
|
TranslateIcon,
|
|
43
|
+
WebLinkIcon,
|
|
38
44
|
} from '@manuscripts/style-guide'
|
|
39
45
|
import React, { createElement } from 'react'
|
|
40
46
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
@@ -59,6 +65,12 @@ export const fileCorruptedIcon = renderIcon(FileCorruptedIcon)
|
|
|
59
65
|
export const draggableIcon = renderIcon(DraggableIcon)
|
|
60
66
|
export const translateIcon = renderIcon(TranslateIcon)
|
|
61
67
|
export const linkIcon = renderIcon(LinkIcon)
|
|
68
|
+
export const leadingHeroImageIcon = renderIcon(LeadingHeroImageIcon)
|
|
69
|
+
export const leadingSmallFloatIcon = renderIcon(LeadingSmallFloatIcon)
|
|
70
|
+
export const leadingLargeFloatIcon = renderIcon(LeadingLargeFloatIcon)
|
|
71
|
+
export const leadingWallpaperIcon = renderIcon(LeadingWallpaperIcon)
|
|
72
|
+
export const leadingHalfLeftIcon = renderIcon(LeadingHalfLeftIcon)
|
|
62
73
|
export const fileMainDocumentIcon = renderIcon(FileMainDocumentIcon)
|
|
63
74
|
export const tickIcon = renderIcon(TickIcon)
|
|
64
75
|
export const ORCIDIcon = renderIcon(ORCID)
|
|
76
|
+
export const webLinkIcon = renderIcon(WebLinkIcon)
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,8 @@ export * from './lib/capabilities'
|
|
|
33
33
|
export * from './lib/navigation-utils'
|
|
34
34
|
export * from './lib/comments'
|
|
35
35
|
export * from './lib/files'
|
|
36
|
+
export * from './lib/supplements'
|
|
37
|
+
export { allowedHref } from './lib/url'
|
|
36
38
|
export * from './lib/footnotes'
|
|
37
39
|
export * from './lib/template'
|
|
38
40
|
export * from './lib/doc'
|
package/src/lib/files.ts
CHANGED
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { ManuscriptNode, schema } from '@manuscripts/transform'
|
|
16
|
+
import { ManuscriptNode, schema, SupplementNode } from '@manuscripts/transform'
|
|
17
17
|
import { findChildrenByType } from 'prosemirror-utils'
|
|
18
18
|
|
|
19
19
|
import { isHidden } from './track-changes-utils'
|
|
20
20
|
|
|
21
|
+
import { NodeWeblink } from './supplements'
|
|
22
|
+
import { allowedHref } from './url'
|
|
23
|
+
|
|
21
24
|
export type FileAttachment = {
|
|
22
25
|
id: string
|
|
23
26
|
name: string
|
|
@@ -40,6 +43,7 @@ export type ElementFiles = {
|
|
|
40
43
|
export type ManuscriptFiles = {
|
|
41
44
|
figures: ElementFiles[]
|
|
42
45
|
supplements: NodeFile[]
|
|
46
|
+
weblinks: NodeWeblink[]
|
|
43
47
|
attachments: NodeFile[]
|
|
44
48
|
linkedFiles: NodeFile[]
|
|
45
49
|
others: FileAttachment[]
|
|
@@ -95,6 +99,7 @@ export const groupFiles = (
|
|
|
95
99
|
const fileMap = new Map(files.map((f) => [f.id, f]))
|
|
96
100
|
const figures: ElementFiles[] = []
|
|
97
101
|
const supplements: NodeFile[] = []
|
|
102
|
+
const weblinks: NodeWeblink[] = []
|
|
98
103
|
const linkedFiles: NodeFile[] = []
|
|
99
104
|
const attachments: NodeFile[] = []
|
|
100
105
|
|
|
@@ -161,11 +166,19 @@ export const groupFiles = (
|
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
if (node.type === schema.nodes.supplement) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
+
const href = node.attrs.href as string
|
|
170
|
+
if (allowedHref(href)) {
|
|
171
|
+
weblinks.push({
|
|
172
|
+
node: node as SupplementNode,
|
|
173
|
+
pos,
|
|
174
|
+
})
|
|
175
|
+
} else {
|
|
176
|
+
supplements.push({
|
|
177
|
+
node,
|
|
178
|
+
pos,
|
|
179
|
+
file: getFile(href),
|
|
180
|
+
})
|
|
181
|
+
}
|
|
169
182
|
}
|
|
170
183
|
if (node.type === schema.nodes.attachment) {
|
|
171
184
|
attachments.push({
|
|
@@ -179,6 +192,7 @@ export const groupFiles = (
|
|
|
179
192
|
return {
|
|
180
193
|
figures,
|
|
181
194
|
supplements,
|
|
195
|
+
weblinks,
|
|
182
196
|
attachments,
|
|
183
197
|
linkedFiles,
|
|
184
198
|
others: [...fileMap.values()],
|