@manuscripts/body-editor 3.13.19 → 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 +2 -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/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 +2 -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/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 +1 -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/package.json +2 -2
- package/src/commands.ts +42 -0
- package/src/components/views/DeleteSupplementDialog.tsx +88 -0
- package/src/icons.ts +2 -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/supplement.ts +27 -2
- package/src/views/supplements.ts +2 -1
- package/styles/Editor.css +13 -0
package/dist/cjs/commands.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.
|
|
19
|
-
exports.paste = exports.copySelection = exports.exitEditorToContainer = exports.ignoreEnterInSubtitles = exports.insertHeroImage = exports.activateSearchReplace = exports.activateSearch = exports.autoComplete = exports.addColumns = exports.addHeaderRow = exports.addRows = exports.addInlineComment = void 0;
|
|
18
|
+
exports.selectAllIsolating = exports.ignoreAtomBlockNodeForward = exports.isAtEndOfTextBlock = exports.ignoreMetaNodeBackspaceCommand = exports.ignoreAtomBlockNodeBackward = exports.isTextSelection = exports.isAtStartOfTextBlock = exports.insertTransGraphicalAbstract = exports.insertTransAbstract = exports.insertTOCSection = exports.insertBibliographySection = exports.insertList = exports.insertKeywords = exports.insertAward = exports.insertAffiliation = exports.insertContributors = exports.insertGraphicalAbstract = exports.insertBackmatterSection = exports.insertAbstractSection = exports.insertSection = exports.insertHeadshotGrid = exports.insertBoxElement = exports.insertInlineFootnote = exports.insertFootnotesElement = exports.insertTableElementFooter = exports.insertInlineEquation = exports.insertCrossReference = exports.insertInlineCitation = exports.insertLink = exports.insertSectionLabel = exports.findPosBeforeFirstSubsection = exports.insertBreak = exports.deleteBlock = exports.insertBlock = exports.insertAttachment = exports.updateSupplementWeblink = exports.insertSupplementWeblink = exports.insertSupplement = exports.insertTable = exports.insertEmbed = exports.insertFigure = exports.insertGeneralTableFootnote = exports.insertInlineTableFootnote = exports.createBlock = exports.createSelection = exports.canInsert = exports.blockActive = exports.isNodeSelection = exports.markActive = exports.addToStart = void 0;
|
|
19
|
+
exports.paste = exports.copySelection = exports.exitEditorToContainer = exports.ignoreEnterInSubtitles = exports.insertHeroImage = exports.activateSearchReplace = exports.activateSearch = exports.autoComplete = exports.addColumns = exports.addHeaderRow = exports.addRows = exports.addInlineComment = exports.addNodeComment = exports.createAndFillTableElement = void 0;
|
|
20
20
|
const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
|
|
21
21
|
const transform_1 = require("@manuscripts/transform");
|
|
22
22
|
const prosemirror_model_1 = require("prosemirror-model");
|
|
@@ -312,6 +312,34 @@ const insertSupplement = (file, view, setSelection = true) => {
|
|
|
312
312
|
return true;
|
|
313
313
|
};
|
|
314
314
|
exports.insertSupplement = insertSupplement;
|
|
315
|
+
const insertSupplementWeblink = (url, title, view) => {
|
|
316
|
+
const supplement = transform_1.schema.nodes.supplement.createAndFill({
|
|
317
|
+
id: (0, transform_1.generateNodeID)(transform_1.schema.nodes.supplement),
|
|
318
|
+
href: url
|
|
319
|
+
}, createAndFillCaption());
|
|
320
|
+
const tr = view.state.tr;
|
|
321
|
+
const { pos } = (0, doc_1.upsertSupplementsSection)(tr, supplement);
|
|
322
|
+
tr.setSelection(prosemirror_state_1.NodeSelection.create(tr.doc, pos));
|
|
323
|
+
view.focus();
|
|
324
|
+
view.dispatch(tr.scrollIntoView());
|
|
325
|
+
return true;
|
|
326
|
+
};
|
|
327
|
+
exports.insertSupplementWeblink = insertSupplementWeblink;
|
|
328
|
+
const updateSupplementWeblink = (pos, url, title, view) => {
|
|
329
|
+
const node = view.state.doc.nodeAt(pos);
|
|
330
|
+
if (!node) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
const tr = view.state.tr;
|
|
334
|
+
tr.setNodeMarkup(pos, undefined, {
|
|
335
|
+
...node.attrs,
|
|
336
|
+
href: url,
|
|
337
|
+
title: title,
|
|
338
|
+
});
|
|
339
|
+
view.dispatch(tr);
|
|
340
|
+
return true;
|
|
341
|
+
};
|
|
342
|
+
exports.updateSupplementWeblink = updateSupplementWeblink;
|
|
315
343
|
const insertAttachment = (file, state, type, dispatch) => {
|
|
316
344
|
const tr = state.tr;
|
|
317
345
|
const attachments = (0, doc_1.insertAttachmentsNode)(tr);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.openDeleteSupplementDialog = void 0;
|
|
40
|
+
const style_guide_1 = require("@manuscripts/style-guide");
|
|
41
|
+
const react_1 = __importStar(require("react"));
|
|
42
|
+
const supplements_1 = require("../../lib/supplements");
|
|
43
|
+
const editor_props_1 = require("../../plugins/editor-props");
|
|
44
|
+
const ReactSubView_1 = __importDefault(require("../../views/ReactSubView"));
|
|
45
|
+
const DeleteSupplementDialog = ({ label, onDelete }) => {
|
|
46
|
+
const [isOpen, setOpen] = (0, react_1.useState)(true);
|
|
47
|
+
return (react_1.default.createElement(style_guide_1.Dialog, { isOpen: isOpen, category: style_guide_1.Category.confirmation, header: 'Delete supplement', message: 'Are you sure you want to delete \u201C{label}\u201D?', actions: {
|
|
48
|
+
primary: {
|
|
49
|
+
action: () => {
|
|
50
|
+
onDelete();
|
|
51
|
+
setOpen(false);
|
|
52
|
+
},
|
|
53
|
+
title: 'Delete',
|
|
54
|
+
},
|
|
55
|
+
secondary: {
|
|
56
|
+
action: () => setOpen(false),
|
|
57
|
+
title: 'Cancel',
|
|
58
|
+
},
|
|
59
|
+
} }));
|
|
60
|
+
};
|
|
61
|
+
const openDeleteSupplementDialog = (view, pos) => {
|
|
62
|
+
const node = view.state.doc.nodeAt(pos);
|
|
63
|
+
if (!node || node.type.name !== 'supplement') {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const { state } = view;
|
|
67
|
+
const props = (0, editor_props_1.getEditorProps)(state);
|
|
68
|
+
const label = (0, supplements_1.getSupplementDisplayLabel)(node, props.getFiles());
|
|
69
|
+
const dialog = (0, ReactSubView_1.default)(props, DeleteSupplementDialog, {
|
|
70
|
+
label,
|
|
71
|
+
onDelete: () => (0, supplements_1.performDeleteSupplement)(view, pos),
|
|
72
|
+
}, state.doc, () => 0, view);
|
|
73
|
+
document.body.appendChild(dialog);
|
|
74
|
+
};
|
|
75
|
+
exports.openDeleteSupplementDialog = openDeleteSupplementDialog;
|
package/dist/cjs/icons.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ORCIDIcon = exports.tickIcon = exports.fileMainDocumentIcon = exports.leadingHalfLeftIcon = exports.leadingWallpaperIcon = exports.leadingLargeFloatIcon = exports.leadingSmallFloatIcon = exports.leadingHeroImageIcon = exports.linkIcon = exports.translateIcon = exports.draggableIcon = exports.fileCorruptedIcon = exports.imageDefaultIcon = exports.imageLeftIcon = exports.imageRightIcon = exports.plusIcon = exports.lockIcon = exports.scrollIcon = exports.sectionCategoryIcon = exports.editIcon = exports.deleteIcon = exports.cameraIcon = exports.alertIcon = exports.arrowUp = exports.arrowDown = exports.addAuthorIcon = void 0;
|
|
3
|
+
exports.webLinkIcon = exports.ORCIDIcon = exports.tickIcon = exports.fileMainDocumentIcon = exports.leadingHalfLeftIcon = exports.leadingWallpaperIcon = exports.leadingLargeFloatIcon = exports.leadingSmallFloatIcon = exports.leadingHeroImageIcon = exports.linkIcon = exports.translateIcon = exports.draggableIcon = exports.fileCorruptedIcon = exports.imageDefaultIcon = exports.imageLeftIcon = exports.imageRightIcon = exports.plusIcon = exports.lockIcon = exports.scrollIcon = exports.sectionCategoryIcon = exports.editIcon = exports.deleteIcon = exports.cameraIcon = exports.alertIcon = exports.arrowUp = exports.arrowDown = exports.addAuthorIcon = void 0;
|
|
4
4
|
const style_guide_1 = require("@manuscripts/style-guide");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const server_1 = require("react-dom/server");
|
|
@@ -31,3 +31,4 @@ exports.leadingHalfLeftIcon = renderIcon(style_guide_1.LeadingHalfLeftIcon);
|
|
|
31
31
|
exports.fileMainDocumentIcon = renderIcon(style_guide_1.FileMainDocumentIcon);
|
|
32
32
|
exports.tickIcon = renderIcon(style_guide_1.TickIcon);
|
|
33
33
|
exports.ORCIDIcon = renderIcon(style_guide_1.ORCIDIcon);
|
|
34
|
+
exports.webLinkIcon = renderIcon(style_guide_1.WebLinkIcon);
|
package/dist/cjs/index.js
CHANGED
|
@@ -32,7 +32,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
32
32
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
33
33
|
};
|
|
34
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.affiliationLabel = exports.authorLabel = exports.bibliographyItemTypes = exports.metadata = exports.getClosestMatch = exports.getNewMatch = exports.detectInconsistencyPluginKey = exports.searchReplacePluginKey = exports.bibliographyPluginKey = exports.footnotesPluginKey = exports.objectsPluginKey = exports.selectedSuggestionKey = exports.PopperManager = exports.CollabProvider = exports.openKeyboardShortcuts = exports.KeyboardShortcutsModal = exports.LanguageDropdown = exports.TypeSelector = exports.OutlineItemIcon = exports.ManuscriptOutline = void 0;
|
|
35
|
+
exports.affiliationLabel = exports.authorLabel = exports.bibliographyItemTypes = exports.metadata = exports.getClosestMatch = exports.getNewMatch = exports.detectInconsistencyPluginKey = exports.searchReplacePluginKey = exports.bibliographyPluginKey = exports.footnotesPluginKey = exports.objectsPluginKey = exports.selectedSuggestionKey = exports.allowedHref = exports.PopperManager = exports.CollabProvider = exports.openKeyboardShortcuts = exports.KeyboardShortcutsModal = exports.LanguageDropdown = exports.TypeSelector = exports.OutlineItemIcon = exports.ManuscriptOutline = void 0;
|
|
36
36
|
__exportStar(require("./commands"), exports);
|
|
37
37
|
var ManuscriptOutline_1 = require("./components/outline/ManuscriptOutline");
|
|
38
38
|
Object.defineProperty(exports, "ManuscriptOutline", { enumerable: true, get: function () { return ManuscriptOutline_1.ManuscriptOutline; } });
|
|
@@ -57,6 +57,9 @@ __exportStar(require("./lib/capabilities"), exports);
|
|
|
57
57
|
__exportStar(require("./lib/navigation-utils"), exports);
|
|
58
58
|
__exportStar(require("./lib/comments"), exports);
|
|
59
59
|
__exportStar(require("./lib/files"), exports);
|
|
60
|
+
__exportStar(require("./lib/supplements"), exports);
|
|
61
|
+
var url_1 = require("./lib/url");
|
|
62
|
+
Object.defineProperty(exports, "allowedHref", { enumerable: true, get: function () { return url_1.allowedHref; } });
|
|
60
63
|
__exportStar(require("./lib/footnotes"), exports);
|
|
61
64
|
__exportStar(require("./lib/template"), exports);
|
|
62
65
|
__exportStar(require("./lib/doc"), exports);
|
package/dist/cjs/lib/files.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.memoGroupFiles = memoGroupFiles;
|
|
|
5
5
|
const transform_1 = require("@manuscripts/transform");
|
|
6
6
|
const prosemirror_utils_1 = require("prosemirror-utils");
|
|
7
7
|
const track_changes_utils_1 = require("./track-changes-utils");
|
|
8
|
+
const url_1 = require("./url");
|
|
8
9
|
const MISSING_FILE = {
|
|
9
10
|
id: '',
|
|
10
11
|
name: '',
|
|
@@ -33,6 +34,7 @@ const groupFiles = (doc, files) => {
|
|
|
33
34
|
const fileMap = new Map(files.map((f) => [f.id, f]));
|
|
34
35
|
const figures = [];
|
|
35
36
|
const supplements = [];
|
|
37
|
+
const weblinks = [];
|
|
36
38
|
const linkedFiles = [];
|
|
37
39
|
const attachments = [];
|
|
38
40
|
const getFile = (href) => {
|
|
@@ -95,11 +97,20 @@ const groupFiles = (doc, files) => {
|
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
99
|
if (node.type === transform_1.schema.nodes.supplement) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
const href = node.attrs.href;
|
|
101
|
+
if ((0, url_1.allowedHref)(href)) {
|
|
102
|
+
weblinks.push({
|
|
103
|
+
node: node,
|
|
104
|
+
pos,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
supplements.push({
|
|
109
|
+
node,
|
|
110
|
+
pos,
|
|
111
|
+
file: getFile(href),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
103
114
|
}
|
|
104
115
|
if (node.type === transform_1.schema.nodes.attachment) {
|
|
105
116
|
attachments.push({
|
|
@@ -112,6 +123,7 @@ const groupFiles = (doc, files) => {
|
|
|
112
123
|
return {
|
|
113
124
|
figures,
|
|
114
125
|
supplements,
|
|
126
|
+
weblinks,
|
|
115
127
|
attachments,
|
|
116
128
|
linkedFiles,
|
|
117
129
|
others: [...fileMap.values()],
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* © 2026 Atypon Systems LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.deleteSupplementAtPos = exports.performDeleteSupplement = exports.getSupplementDisplayLabel = void 0;
|
|
19
|
+
const transform_1 = require("@manuscripts/transform");
|
|
20
|
+
const prosemirror_utils_1 = require("prosemirror-utils");
|
|
21
|
+
const url_1 = require("./url");
|
|
22
|
+
const getSupplementDisplayLabel = (node, files) => {
|
|
23
|
+
const href = node.attrs.href;
|
|
24
|
+
if ((0, url_1.allowedHref)(href)) {
|
|
25
|
+
return href;
|
|
26
|
+
}
|
|
27
|
+
const file = files.find((f) => f.id === href);
|
|
28
|
+
return file?.name ?? 'Untitled supplement';
|
|
29
|
+
};
|
|
30
|
+
exports.getSupplementDisplayLabel = getSupplementDisplayLabel;
|
|
31
|
+
const performDeleteSupplement = (view, pos) => {
|
|
32
|
+
const node = view.state.doc.nodeAt(pos);
|
|
33
|
+
if (!node || node.type !== transform_1.schema.nodes.supplement) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const from = pos;
|
|
37
|
+
const to = from + node.nodeSize;
|
|
38
|
+
const { from: deleteFrom, to: deleteTo } = (0, exports.deleteSupplementAtPos)(view.state.doc, from, to);
|
|
39
|
+
view.dispatch(view.state.tr.delete(deleteFrom, deleteTo));
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
exports.performDeleteSupplement = performDeleteSupplement;
|
|
43
|
+
const deleteSupplementAtPos = (doc, from, to) => {
|
|
44
|
+
const resolvedPos = doc.resolve(from);
|
|
45
|
+
const supplementsNodeWithPos = (0, prosemirror_utils_1.findParentNodeClosestToPos)(resolvedPos, (node) => node.type === transform_1.schema.nodes.supplements);
|
|
46
|
+
if (!supplementsNodeWithPos) {
|
|
47
|
+
return { from, to, deleteWholeSection: false };
|
|
48
|
+
}
|
|
49
|
+
const { node: supplementsNode, pos: supplementsPos } = supplementsNodeWithPos;
|
|
50
|
+
const supplements = (0, prosemirror_utils_1.findChildrenByType)(supplementsNode, transform_1.schema.nodes.supplement);
|
|
51
|
+
const isDeletingLastSupplement = supplements.length === 1;
|
|
52
|
+
if (isDeletingLastSupplement) {
|
|
53
|
+
return {
|
|
54
|
+
from: supplementsPos,
|
|
55
|
+
to: supplementsPos + supplementsNode.nodeSize,
|
|
56
|
+
deleteWholeSection: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return { from, to, deleteWholeSection: false };
|
|
60
|
+
};
|
|
61
|
+
exports.deleteSupplementAtPos = deleteSupplementAtPos;
|
package/dist/cjs/versions.js
CHANGED
|
@@ -20,6 +20,7 @@ const style_guide_1 = require("@manuscripts/style-guide");
|
|
|
20
20
|
const server_1 = require("react-dom/server");
|
|
21
21
|
const icons_1 = require("../icons");
|
|
22
22
|
const doc_1 = require("../lib/doc");
|
|
23
|
+
const url_1 = require("../lib/url");
|
|
23
24
|
const base_node_view_1 = require("./base_node_view");
|
|
24
25
|
const creators_1 = require("./creators");
|
|
25
26
|
class SupplementView extends base_node_view_1.BaseNodeView {
|
|
@@ -143,8 +144,27 @@ class SupplementView extends base_node_view_1.BaseNodeView {
|
|
|
143
144
|
this.supplementInfoEl = document.createElement('div');
|
|
144
145
|
this.supplementInfoEl.classList.add('supplement-file-info');
|
|
145
146
|
this.supplementInfoEl.contentEditable = 'false';
|
|
147
|
+
const href = this.node.attrs.href;
|
|
148
|
+
if ((0, url_1.allowedHref)(href)) {
|
|
149
|
+
const iconElement = document.createElement('span');
|
|
150
|
+
iconElement.classList.add('supplement-file-icon');
|
|
151
|
+
iconElement.innerHTML = icons_1.webLinkIcon;
|
|
152
|
+
this.supplementInfoEl.appendChild(iconElement);
|
|
153
|
+
const urlLink = document.createElement('a');
|
|
154
|
+
urlLink.classList.add('supplement-weblink-url');
|
|
155
|
+
urlLink.textContent = href;
|
|
156
|
+
if ((0, url_1.allowedHref)(href)) {
|
|
157
|
+
urlLink.href = href;
|
|
158
|
+
urlLink.target = '_blank';
|
|
159
|
+
urlLink.rel = 'noopener noreferrer';
|
|
160
|
+
}
|
|
161
|
+
urlLink.addEventListener('mousedown', (e) => e.stopPropagation());
|
|
162
|
+
this.supplementInfoEl.appendChild(urlLink);
|
|
163
|
+
this.dom.appendChild(this.supplementInfoEl);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
146
166
|
const files = this.props.getFiles();
|
|
147
|
-
const file = files.find((f) => f.id ===
|
|
167
|
+
const file = files.find((f) => f.id === href);
|
|
148
168
|
if (file) {
|
|
149
169
|
const iconElement = document.createElement('span');
|
|
150
170
|
iconElement.classList.add('supplement-file-icon');
|
package/dist/es/commands.js
CHANGED
|
@@ -295,6 +295,32 @@ export const insertSupplement = (file, view, setSelection = true) => {
|
|
|
295
295
|
view.dispatch(tr.scrollIntoView());
|
|
296
296
|
return true;
|
|
297
297
|
};
|
|
298
|
+
export const insertSupplementWeblink = (url, title, view) => {
|
|
299
|
+
const supplement = schema.nodes.supplement.createAndFill({
|
|
300
|
+
id: generateNodeID(schema.nodes.supplement),
|
|
301
|
+
href: url
|
|
302
|
+
}, createAndFillCaption());
|
|
303
|
+
const tr = view.state.tr;
|
|
304
|
+
const { pos } = upsertSupplementsSection(tr, supplement);
|
|
305
|
+
tr.setSelection(NodeSelection.create(tr.doc, pos));
|
|
306
|
+
view.focus();
|
|
307
|
+
view.dispatch(tr.scrollIntoView());
|
|
308
|
+
return true;
|
|
309
|
+
};
|
|
310
|
+
export const updateSupplementWeblink = (pos, url, title, view) => {
|
|
311
|
+
const node = view.state.doc.nodeAt(pos);
|
|
312
|
+
if (!node) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
const tr = view.state.tr;
|
|
316
|
+
tr.setNodeMarkup(pos, undefined, {
|
|
317
|
+
...node.attrs,
|
|
318
|
+
href: url,
|
|
319
|
+
title: title,
|
|
320
|
+
});
|
|
321
|
+
view.dispatch(tr);
|
|
322
|
+
return true;
|
|
323
|
+
};
|
|
298
324
|
export const insertAttachment = (file, state, type, dispatch) => {
|
|
299
325
|
const tr = state.tr;
|
|
300
326
|
const attachments = insertAttachmentsNode(tr);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Category, Dialog, } from '@manuscripts/style-guide';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { getSupplementDisplayLabel, performDeleteSupplement, } from '../../lib/supplements';
|
|
4
|
+
import { getEditorProps } from '../../plugins/editor-props';
|
|
5
|
+
import ReactSubView from '../../views/ReactSubView';
|
|
6
|
+
const DeleteSupplementDialog = ({ label, onDelete }) => {
|
|
7
|
+
const [isOpen, setOpen] = useState(true);
|
|
8
|
+
return (React.createElement(Dialog, { isOpen: isOpen, category: Category.confirmation, header: 'Delete supplement', message: 'Are you sure you want to delete \u201C{label}\u201D?', actions: {
|
|
9
|
+
primary: {
|
|
10
|
+
action: () => {
|
|
11
|
+
onDelete();
|
|
12
|
+
setOpen(false);
|
|
13
|
+
},
|
|
14
|
+
title: 'Delete',
|
|
15
|
+
},
|
|
16
|
+
secondary: {
|
|
17
|
+
action: () => setOpen(false),
|
|
18
|
+
title: 'Cancel',
|
|
19
|
+
},
|
|
20
|
+
} }));
|
|
21
|
+
};
|
|
22
|
+
export const openDeleteSupplementDialog = (view, pos) => {
|
|
23
|
+
const node = view.state.doc.nodeAt(pos);
|
|
24
|
+
if (!node || node.type.name !== 'supplement') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const { state } = view;
|
|
28
|
+
const props = getEditorProps(state);
|
|
29
|
+
const label = getSupplementDisplayLabel(node, props.getFiles());
|
|
30
|
+
const dialog = ReactSubView(props, DeleteSupplementDialog, {
|
|
31
|
+
label,
|
|
32
|
+
onDelete: () => performDeleteSupplement(view, pos),
|
|
33
|
+
}, state.doc, () => 0, view);
|
|
34
|
+
document.body.appendChild(dialog);
|
|
35
|
+
};
|
package/dist/es/icons.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AddAuthorIcon, AlertIcon, ArrowDownCircleIcon, ArrowUpIcon, CameraIcon, DeleteIcon, DraggableIcon, EditIcon, FileCorruptedIcon, FileMainDocumentIcon, ImageDefaultIcon, ImageLeftIcon, ImageRightIcon, LinkIcon, LockIcon, LeadingHalfLeftIcon, LeadingHeroImageIcon, LeadingLargeFloatIcon, LeadingSmallFloatIcon, LeadingWallpaperIcon, ORCIDIcon as ORCID, PlusIcon, ScrollIcon, SectionCategoryIcon, TickIcon, TranslateIcon, } from '@manuscripts/style-guide';
|
|
1
|
+
import { AddAuthorIcon, AlertIcon, ArrowDownCircleIcon, ArrowUpIcon, CameraIcon, DeleteIcon, DraggableIcon, EditIcon, FileCorruptedIcon, FileMainDocumentIcon, ImageDefaultIcon, ImageLeftIcon, ImageRightIcon, LinkIcon, LockIcon, LeadingHalfLeftIcon, LeadingHeroImageIcon, LeadingLargeFloatIcon, LeadingSmallFloatIcon, LeadingWallpaperIcon, ORCIDIcon as ORCID, PlusIcon, ScrollIcon, SectionCategoryIcon, TickIcon, TranslateIcon, WebLinkIcon, } from '@manuscripts/style-guide';
|
|
2
2
|
import { createElement } from 'react';
|
|
3
3
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
4
4
|
const renderIcon = (c) => renderToStaticMarkup(createElement(c));
|
|
@@ -28,3 +28,4 @@ export const leadingHalfLeftIcon = renderIcon(LeadingHalfLeftIcon);
|
|
|
28
28
|
export const fileMainDocumentIcon = renderIcon(FileMainDocumentIcon);
|
|
29
29
|
export const tickIcon = renderIcon(TickIcon);
|
|
30
30
|
export const ORCIDIcon = renderIcon(ORCID);
|
|
31
|
+
export const webLinkIcon = renderIcon(WebLinkIcon);
|
package/dist/es/index.js
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';
|
package/dist/es/lib/files.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { schema } from '@manuscripts/transform';
|
|
2
2
|
import { findChildrenByType } from 'prosemirror-utils';
|
|
3
3
|
import { isHidden } from './track-changes-utils';
|
|
4
|
+
import { allowedHref } from './url';
|
|
4
5
|
const MISSING_FILE = {
|
|
5
6
|
id: '',
|
|
6
7
|
name: '',
|
|
@@ -29,6 +30,7 @@ export const groupFiles = (doc, files) => {
|
|
|
29
30
|
const fileMap = new Map(files.map((f) => [f.id, f]));
|
|
30
31
|
const figures = [];
|
|
31
32
|
const supplements = [];
|
|
33
|
+
const weblinks = [];
|
|
32
34
|
const linkedFiles = [];
|
|
33
35
|
const attachments = [];
|
|
34
36
|
const getFile = (href) => {
|
|
@@ -91,11 +93,20 @@ export const groupFiles = (doc, files) => {
|
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
if (node.type === schema.nodes.supplement) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
const href = node.attrs.href;
|
|
97
|
+
if (allowedHref(href)) {
|
|
98
|
+
weblinks.push({
|
|
99
|
+
node: node,
|
|
100
|
+
pos,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
supplements.push({
|
|
105
|
+
node,
|
|
106
|
+
pos,
|
|
107
|
+
file: getFile(href),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
99
110
|
}
|
|
100
111
|
if (node.type === schema.nodes.attachment) {
|
|
101
112
|
attachments.push({
|
|
@@ -108,6 +119,7 @@ export const groupFiles = (doc, files) => {
|
|
|
108
119
|
return {
|
|
109
120
|
figures,
|
|
110
121
|
supplements,
|
|
122
|
+
weblinks,
|
|
111
123
|
attachments,
|
|
112
124
|
linkedFiles,
|
|
113
125
|
others: [...fileMap.values()],
|
|
@@ -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';
|
|
@@ -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
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";
|
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
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
SectionCategoryIcon,
|
|
41
41
|
TickIcon,
|
|
42
42
|
TranslateIcon,
|
|
43
|
+
WebLinkIcon,
|
|
43
44
|
} from '@manuscripts/style-guide'
|
|
44
45
|
import React, { createElement } from 'react'
|
|
45
46
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
@@ -72,3 +73,4 @@ export const leadingHalfLeftIcon = renderIcon(LeadingHalfLeftIcon)
|
|
|
72
73
|
export const fileMainDocumentIcon = renderIcon(FileMainDocumentIcon)
|
|
73
74
|
export const tickIcon = renderIcon(TickIcon)
|
|
74
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()],
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
|
|
17
|
+
import {
|
|
18
|
+
ManuscriptEditorView,
|
|
19
|
+
ManuscriptNode,
|
|
20
|
+
schema,
|
|
21
|
+
SupplementNode,
|
|
22
|
+
} from '@manuscripts/transform'
|
|
23
|
+
import { findChildrenByType, findParentNodeClosestToPos } from 'prosemirror-utils'
|
|
24
|
+
|
|
25
|
+
import { allowedHref } from './url'
|
|
26
|
+
|
|
27
|
+
export type NodeWeblink = {
|
|
28
|
+
node: SupplementNode
|
|
29
|
+
pos: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const getSupplementDisplayLabel = (
|
|
33
|
+
node: SupplementNode,
|
|
34
|
+
files: { id: string; name: string }[]
|
|
35
|
+
): string => {
|
|
36
|
+
const href = node.attrs.href
|
|
37
|
+
if (allowedHref(href)) {
|
|
38
|
+
return href
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const file = files.find((f) => f.id === href)
|
|
42
|
+
return file?.name ?? 'Untitled supplement'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const performDeleteSupplement = (
|
|
46
|
+
view: ManuscriptEditorView,
|
|
47
|
+
pos: number
|
|
48
|
+
): boolean => {
|
|
49
|
+
const node = view.state.doc.nodeAt(pos)
|
|
50
|
+
if (!node || node.type !== schema.nodes.supplement) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const from = pos
|
|
55
|
+
const to = from + node.nodeSize
|
|
56
|
+
const { from: deleteFrom, to: deleteTo } = deleteSupplementAtPos(
|
|
57
|
+
view.state.doc,
|
|
58
|
+
from,
|
|
59
|
+
to
|
|
60
|
+
)
|
|
61
|
+
view.dispatch(view.state.tr.delete(deleteFrom, deleteTo))
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const deleteSupplementAtPos = (
|
|
66
|
+
doc: ManuscriptNode,
|
|
67
|
+
from: number,
|
|
68
|
+
to: number
|
|
69
|
+
) => {
|
|
70
|
+
const resolvedPos = doc.resolve(from)
|
|
71
|
+
const supplementsNodeWithPos = findParentNodeClosestToPos(
|
|
72
|
+
resolvedPos,
|
|
73
|
+
(node) => node.type === schema.nodes.supplements
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if (!supplementsNodeWithPos) {
|
|
77
|
+
return { from, to, deleteWholeSection: false }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { node: supplementsNode, pos: supplementsPos } = supplementsNodeWithPos
|
|
81
|
+
|
|
82
|
+
const supplements = findChildrenByType(
|
|
83
|
+
supplementsNode,
|
|
84
|
+
schema.nodes.supplement
|
|
85
|
+
)
|
|
86
|
+
const isDeletingLastSupplement = supplements.length === 1
|
|
87
|
+
|
|
88
|
+
if (isDeletingLastSupplement) {
|
|
89
|
+
return {
|
|
90
|
+
from: supplementsPos,
|
|
91
|
+
to: supplementsPos + supplementsNode.nodeSize,
|
|
92
|
+
deleteWholeSection: true,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { from, to, deleteWholeSection: false }
|
|
97
|
+
}
|
package/src/versions.ts
CHANGED
|
@@ -22,7 +22,9 @@ import { createNodeView } from './creators'
|
|
|
22
22
|
export class CaptionTitleView extends BaseNodeView<CaptionTitleNode> {
|
|
23
23
|
public initialise = () => {
|
|
24
24
|
this.createDOM()
|
|
25
|
+
this.updateContents()
|
|
25
26
|
}
|
|
27
|
+
|
|
26
28
|
protected createDOM() {
|
|
27
29
|
this.dom = document.createElement('label')
|
|
28
30
|
this.dom.className = 'caption-title placeholder'
|
package/src/views/supplement.ts
CHANGED
|
@@ -18,12 +18,14 @@ import { getFileIcon } from '@manuscripts/style-guide'
|
|
|
18
18
|
import { SupplementNode } from '@manuscripts/transform'
|
|
19
19
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
20
20
|
|
|
21
|
-
import { draggableIcon } from '../icons'
|
|
21
|
+
import { draggableIcon, webLinkIcon } from '../icons'
|
|
22
22
|
import { findNodeByID } from '../lib/doc'
|
|
23
|
+
import { allowedHref } from '../lib/url'
|
|
23
24
|
import { Trackable } from '../types'
|
|
24
25
|
import { BaseNodeView } from './base_node_view'
|
|
25
26
|
import { createNodeView } from './creators'
|
|
26
27
|
|
|
28
|
+
|
|
27
29
|
export class SupplementView extends BaseNodeView<Trackable<SupplementNode>> {
|
|
28
30
|
private supplementInfoEl: HTMLDivElement
|
|
29
31
|
private static currentDragSupplementId: string | null = null
|
|
@@ -188,9 +190,32 @@ export class SupplementView extends BaseNodeView<Trackable<SupplementNode>> {
|
|
|
188
190
|
this.supplementInfoEl.classList.add('supplement-file-info')
|
|
189
191
|
this.supplementInfoEl.contentEditable = 'false'
|
|
190
192
|
|
|
193
|
+
const href = this.node.attrs.href
|
|
194
|
+
|
|
195
|
+
if (allowedHref(href)) {
|
|
196
|
+
const iconElement = document.createElement('span')
|
|
197
|
+
iconElement.classList.add('supplement-file-icon')
|
|
198
|
+
iconElement.innerHTML = webLinkIcon
|
|
199
|
+
this.supplementInfoEl.appendChild(iconElement)
|
|
200
|
+
|
|
201
|
+
const urlLink = document.createElement('a')
|
|
202
|
+
urlLink.classList.add('supplement-weblink-url')
|
|
203
|
+
urlLink.textContent = href
|
|
204
|
+
if (allowedHref(href)) {
|
|
205
|
+
urlLink.href = href
|
|
206
|
+
urlLink.target = '_blank'
|
|
207
|
+
urlLink.rel = 'noopener noreferrer'
|
|
208
|
+
}
|
|
209
|
+
urlLink.addEventListener('mousedown', (e) => e.stopPropagation())
|
|
210
|
+
this.supplementInfoEl.appendChild(urlLink)
|
|
211
|
+
|
|
212
|
+
this.dom.appendChild(this.supplementInfoEl)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
191
216
|
// Get the file from the file management system
|
|
192
217
|
const files = this.props.getFiles()
|
|
193
|
-
const file = files.find((f) => f.id ===
|
|
218
|
+
const file = files.find((f) => f.id === href)
|
|
194
219
|
|
|
195
220
|
if (file) {
|
|
196
221
|
const iconElement = document.createElement('span')
|
package/src/views/supplements.ts
CHANGED
|
@@ -32,6 +32,7 @@ export class SupplementsView extends BlockView<Trackable<SupplementsNode>> {
|
|
|
32
32
|
this.toggleButton = document.createElement('button')
|
|
33
33
|
this.toggleButton.classList.add('supplements-toggle-btn', 'button-reset')
|
|
34
34
|
this.toggleButton.innerHTML = arrowUp
|
|
35
|
+
|
|
35
36
|
const handleToggle = () => {
|
|
36
37
|
this.collapsed = !this.collapsed
|
|
37
38
|
this.toggleContent()
|
|
@@ -62,4 +63,4 @@ export class SupplementsView extends BlockView<Trackable<SupplementsNode>> {
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
export default createNodeView(SupplementsView)
|
|
66
|
+
export default createNodeView(SupplementsView)
|
package/styles/Editor.css
CHANGED
|
@@ -1248,6 +1248,17 @@
|
|
|
1248
1248
|
font-size: 14px;
|
|
1249
1249
|
}
|
|
1250
1250
|
|
|
1251
|
+
.ProseMirror .supplement-weblink-url {
|
|
1252
|
+
font-size: 14px;
|
|
1253
|
+
color: #20aedf;
|
|
1254
|
+
text-decoration: none;
|
|
1255
|
+
word-break: break-all;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
.ProseMirror .supplement-weblink-url:hover {
|
|
1259
|
+
text-decoration: underline;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1251
1262
|
.ProseMirror .supplements-toggle-btn {
|
|
1252
1263
|
background: none;
|
|
1253
1264
|
border: none;
|
|
@@ -1300,6 +1311,8 @@
|
|
|
1300
1311
|
padding: 8px 12px !important;
|
|
1301
1312
|
font-size: 14px;
|
|
1302
1313
|
display: flex;
|
|
1314
|
+
align-items: center;
|
|
1315
|
+
gap: 8px;
|
|
1303
1316
|
margin-top: -1px;
|
|
1304
1317
|
color: #6e6e6e !important;
|
|
1305
1318
|
}
|