@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.
@@ -15,8 +15,8 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.addNodeComment = exports.createAndFillTableElement = 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.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 = 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);
@@ -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
- supplements.push({
99
- node,
100
- pos,
101
- file: getFile(node.attrs.href),
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;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MATHJAX_VERSION = exports.VERSION = void 0;
4
- exports.VERSION = '3.13.19';
4
+ exports.VERSION = '3.14.0';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -23,6 +23,7 @@ class CaptionTitleView extends base_node_view_1.BaseNodeView {
23
23
  super(...arguments);
24
24
  this.initialise = () => {
25
25
  this.createDOM();
26
+ this.updateContents();
26
27
  };
27
28
  }
28
29
  createDOM() {
@@ -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 === this.node.attrs.href);
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');
@@ -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';
@@ -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
- supplements.push({
95
- node,
96
- pos,
97
- file: getFile(node.attrs.href),
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
+ };
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.13.19';
1
+ export const VERSION = '3.14.0';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -20,6 +20,7 @@ export class CaptionTitleView extends BaseNodeView {
20
20
  super(...arguments);
21
21
  this.initialise = () => {
22
22
  this.createDOM();
23
+ this.updateContents();
23
24
  };
24
25
  }
25
26
  createDOM() {
@@ -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 === this.node.attrs.href);
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');
@@ -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;
@@ -0,0 +1,2 @@
1
+ import { ManuscriptEditorView } from '@manuscripts/transform';
2
+ export declare const openDeleteSupplementDialog: (view: ManuscriptEditorView, pos: number) => void;
@@ -24,3 +24,4 @@ export declare const leadingHalfLeftIcon: string;
24
24
  export declare const fileMainDocumentIcon: string;
25
25
  export declare const tickIcon: string;
26
26
  export declare const ORCIDIcon: string;
27
+ export declare const webLinkIcon: string;
@@ -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
+ };
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.13.19";
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.13.19",
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.6.6",
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 &ldquo;{label}&rdquo;?'
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
- supplements.push({
165
- node,
166
- pos,
167
- file: getFile(node.attrs.href),
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
@@ -1,3 +1,3 @@
1
1
  //THIS FILE WAS GENERATED BY versions.mjs
2
- export const VERSION = '3.13.19';
2
+ export const VERSION = '3.14.0';
3
3
  export const MATHJAX_VERSION = '3.2.2';
@@ -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'
@@ -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 === this.node.attrs.href)
218
+ const file = files.find((f) => f.id === href)
194
219
 
195
220
  if (file) {
196
221
  const iconElement = document.createElement('span')
@@ -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
  }