@manuscripts/body-editor 3.17.2 → 3.17.3

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.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.canInsertCrossReference = 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 = exports.selectAllIsolating = void 0;
18
+ 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.canInsertCrossReference = exports.insertInlineCitation = exports.insertLink = exports.insertSectionLabel = exports.findPosBeforeFirstSubsection = exports.insertBreak = exports.deleteBlock = exports.insertBlock = exports.insertAttachment = exports.updateSupplementWeblink = exports.convertLinkToSupplementWeblink = 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 = exports.selectAllIsolating = exports.ignoreAtomBlockNodeForward = 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");
@@ -325,6 +325,28 @@ const insertSupplementWeblink = (url, title, view) => {
325
325
  return true;
326
326
  };
327
327
  exports.insertSupplementWeblink = insertSupplementWeblink;
328
+ const convertLinkToSupplementWeblink = (pos, node, view) => {
329
+ const href = node.attrs.href;
330
+ if (!href) {
331
+ return false;
332
+ }
333
+ const text = node.textContent.trim();
334
+ const id = (0, transform_1.generateNodeID)(transform_1.schema.nodes.supplement);
335
+ const supplement = transform_1.schema.nodes.supplement.createAndFill({ id, href }, createAndFillCaption());
336
+ const crossReference = transform_1.schema.nodes.cross_reference.create({
337
+ id: (0, transform_1.generateNodeID)(transform_1.schema.nodes.cross_reference),
338
+ rids: [id],
339
+ label: text,
340
+ });
341
+ const tr = view.state.tr;
342
+ tr.replaceWith(pos, pos + node.nodeSize, crossReference);
343
+ (0, doc_1.upsertSupplementsSection)(tr, supplement);
344
+ tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, pos));
345
+ view.focus();
346
+ view.dispatch(tr);
347
+ return true;
348
+ };
349
+ exports.convertLinkToSupplementWeblink = convertLinkToSupplementWeblink;
328
350
  const updateSupplementWeblink = (pos, url, title, view) => {
329
351
  const node = view.state.doc.nodeAt(pos);
330
352
  if (!node) {
@@ -89,7 +89,7 @@ const AddReferenceActions = (0, styled_components_1.default)(style_guide_1.Butto
89
89
  }
90
90
  }
91
91
  `;
92
- const ReferenceSearch = ({ query: initialQuery, sources, items, onAdd, onCite, onCancel, }) => {
92
+ const ReferenceSearch = ({ query: initialQuery, sources, items, onAdd, onCite, onCancel }) => {
93
93
  const [query, setQuery] = (0, react_1.useState)(initialQuery || '');
94
94
  const [selections, setSelections] = (0, react_1.useState)(new Map());
95
95
  const toggleSelection = (item) => {
@@ -17,7 +17,7 @@ const style_guide_1 = require("@manuscripts/style-guide");
17
17
  const userProfile = {
18
18
  _id: 'MPUserProfile:1',
19
19
  userID: 'MPUser:1',
20
- connectID: 'connectID1'
20
+ connectID: 'connectID1',
21
21
  };
22
22
  const history = (0, history_1.createMemoryHistory)();
23
23
  const buildProps = (doc) => ({
@@ -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.17.2';
4
+ exports.VERSION = '3.17.3';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -19,14 +19,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.LinkEditableView = void 0;
22
+ const style_guide_1 = require("@manuscripts/style-guide");
23
+ const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
22
24
  const transform_1 = require("@manuscripts/transform");
23
25
  const prosemirror_state_1 = require("prosemirror-state");
26
+ const commands_1 = require("../commands");
24
27
  const LinkForm_1 = require("../components/views/LinkForm");
25
28
  const url_1 = require("../lib/url");
26
29
  const creators_1 = require("./creators");
27
30
  const link_1 = require("./link");
28
31
  const ReactSubView_1 = __importDefault(require("./ReactSubView"));
29
- const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
30
32
  class LinkEditableView extends link_1.LinkView {
31
33
  constructor() {
32
34
  super(...arguments);
@@ -41,7 +43,7 @@ class LinkEditableView extends link_1.LinkView {
41
43
  this.dom.addEventListener('click', this.handleClick);
42
44
  };
43
45
  this.selectNode = () => {
44
- if (!(0, track_changes_plugin_1.isDeleted)(this.node)) {
46
+ if (!(0, track_changes_plugin_1.isDeleted)(this.node) && !this.node.attrs.href) {
45
47
  this.showForm();
46
48
  }
47
49
  };
@@ -51,6 +53,50 @@ class LinkEditableView extends link_1.LinkView {
51
53
  this.deselectNode = () => {
52
54
  this.closeForm();
53
55
  };
56
+ this.showContextMenu = () => {
57
+ if (!this.props.getCapabilities().editArticle) {
58
+ return;
59
+ }
60
+ this.closeForm();
61
+ const href = this.node.attrs.href;
62
+ const componentProps = {
63
+ actions: [
64
+ {
65
+ label: 'Edit',
66
+ action: this.handleEdit,
67
+ icon: 'Edit',
68
+ },
69
+ {
70
+ label: 'Convert to Weblink',
71
+ action: this.handleConvertToWebLink,
72
+ icon: 'ConvertToWebLink',
73
+ },
74
+ {
75
+ label: 'Open Link',
76
+ action: this.handleOpenLink,
77
+ icon: 'ExternalLink',
78
+ disabled: !(0, url_1.allowedHref)(href),
79
+ },
80
+ ],
81
+ };
82
+ this.popperContainer = (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'link-context-menu']);
83
+ this.props.popper.show(this.dom, this.popperContainer, 'bottom');
84
+ };
85
+ this.handleEdit = () => {
86
+ this.closeForm();
87
+ this.showForm();
88
+ };
89
+ this.handleConvertToWebLink = () => {
90
+ this.closeForm();
91
+ (0, commands_1.convertLinkToSupplementWeblink)(this.getPos(), this.node, this.view);
92
+ };
93
+ this.handleOpenLink = () => {
94
+ const href = this.node.attrs.href;
95
+ if ((0, url_1.allowedHref)(href)) {
96
+ window.open(href, '_blank', 'noopener,noreferrer');
97
+ }
98
+ this.closeForm();
99
+ };
54
100
  this.showForm = () => {
55
101
  if (!this.props.getCapabilities().editArticle) {
56
102
  return;
@@ -71,9 +117,14 @@ class LinkEditableView extends link_1.LinkView {
71
117
  this.props.popper.show(this.dom, this.popperContainer, 'bottom');
72
118
  };
73
119
  this.handleClick = (e) => {
74
- if (this.props.getCapabilities().editArticle) {
75
- e.preventDefault();
120
+ if (!this.props.getCapabilities().editArticle) {
121
+ return;
122
+ }
123
+ e.preventDefault();
124
+ if ((0, track_changes_plugin_1.isDeleted)(this.node) || !this.node.attrs.href) {
125
+ return;
76
126
  }
127
+ this.showContextMenu();
77
128
  };
78
129
  this.handleCancel = () => {
79
130
  const attrs = this.node.attrs;
@@ -307,6 +307,27 @@ export const insertSupplementWeblink = (url, title, view) => {
307
307
  view.dispatch(tr.scrollIntoView());
308
308
  return true;
309
309
  };
310
+ export const convertLinkToSupplementWeblink = (pos, node, view) => {
311
+ const href = node.attrs.href;
312
+ if (!href) {
313
+ return false;
314
+ }
315
+ const text = node.textContent.trim();
316
+ const id = generateNodeID(schema.nodes.supplement);
317
+ const supplement = schema.nodes.supplement.createAndFill({ id, href }, createAndFillCaption());
318
+ const crossReference = schema.nodes.cross_reference.create({
319
+ id: generateNodeID(schema.nodes.cross_reference),
320
+ rids: [id],
321
+ label: text,
322
+ });
323
+ const tr = view.state.tr;
324
+ tr.replaceWith(pos, pos + node.nodeSize, crossReference);
325
+ upsertSupplementsSection(tr, supplement);
326
+ tr.setSelection(TextSelection.create(tr.doc, pos));
327
+ view.focus();
328
+ view.dispatch(tr);
329
+ return true;
330
+ };
310
331
  export const updateSupplementWeblink = (pos, url, title, view) => {
311
332
  const node = view.state.doc.nodeAt(pos);
312
333
  if (!node) {
@@ -50,7 +50,7 @@ const AddReferenceActions = styled(ButtonGroup) `
50
50
  }
51
51
  }
52
52
  `;
53
- export const ReferenceSearch = ({ query: initialQuery, sources, items, onAdd, onCite, onCancel, }) => {
53
+ export const ReferenceSearch = ({ query: initialQuery, sources, items, onAdd, onCite, onCancel }) => {
54
54
  const [query, setQuery] = useState(initialQuery || '');
55
55
  const [selections, setSelections] = useState(new Map());
56
56
  const toggleSelection = (item) => {
@@ -12,7 +12,7 @@ import { defaultTheme } from '@manuscripts/style-guide';
12
12
  const userProfile = {
13
13
  _id: 'MPUserProfile:1',
14
14
  userID: 'MPUser:1',
15
- connectID: 'connectID1'
15
+ connectID: 'connectID1',
16
16
  };
17
17
  const history = createMemoryHistory();
18
18
  const buildProps = (doc) => ({
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.17.2';
1
+ export const VERSION = '3.17.3';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -13,14 +13,16 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { ContextMenu } from '@manuscripts/style-guide';
17
+ import { isDeleted } from '@manuscripts/track-changes-plugin';
16
18
  import { schema } from '@manuscripts/transform';
17
19
  import { TextSelection } from 'prosemirror-state';
20
+ import { convertLinkToSupplementWeblink } from '../commands';
18
21
  import { LinkForm, } from '../components/views/LinkForm';
19
22
  import { allowedHref } from '../lib/url';
20
23
  import { createEditableNodeView } from './creators';
21
24
  import { LinkView } from './link';
22
25
  import ReactSubView from './ReactSubView';
23
- import { isDeleted } from '@manuscripts/track-changes-plugin';
24
26
  export class LinkEditableView extends LinkView {
25
27
  constructor() {
26
28
  super(...arguments);
@@ -35,7 +37,7 @@ export class LinkEditableView extends LinkView {
35
37
  this.dom.addEventListener('click', this.handleClick);
36
38
  };
37
39
  this.selectNode = () => {
38
- if (!isDeleted(this.node)) {
40
+ if (!isDeleted(this.node) && !this.node.attrs.href) {
39
41
  this.showForm();
40
42
  }
41
43
  };
@@ -45,6 +47,50 @@ export class LinkEditableView extends LinkView {
45
47
  this.deselectNode = () => {
46
48
  this.closeForm();
47
49
  };
50
+ this.showContextMenu = () => {
51
+ if (!this.props.getCapabilities().editArticle) {
52
+ return;
53
+ }
54
+ this.closeForm();
55
+ const href = this.node.attrs.href;
56
+ const componentProps = {
57
+ actions: [
58
+ {
59
+ label: 'Edit',
60
+ action: this.handleEdit,
61
+ icon: 'Edit',
62
+ },
63
+ {
64
+ label: 'Convert to Weblink',
65
+ action: this.handleConvertToWebLink,
66
+ icon: 'ConvertToWebLink',
67
+ },
68
+ {
69
+ label: 'Open Link',
70
+ action: this.handleOpenLink,
71
+ icon: 'ExternalLink',
72
+ disabled: !allowedHref(href),
73
+ },
74
+ ],
75
+ };
76
+ this.popperContainer = ReactSubView(this.props, ContextMenu, componentProps, this.node, this.getPos, this.view, ['context-menu', 'link-context-menu']);
77
+ this.props.popper.show(this.dom, this.popperContainer, 'bottom');
78
+ };
79
+ this.handleEdit = () => {
80
+ this.closeForm();
81
+ this.showForm();
82
+ };
83
+ this.handleConvertToWebLink = () => {
84
+ this.closeForm();
85
+ convertLinkToSupplementWeblink(this.getPos(), this.node, this.view);
86
+ };
87
+ this.handleOpenLink = () => {
88
+ const href = this.node.attrs.href;
89
+ if (allowedHref(href)) {
90
+ window.open(href, '_blank', 'noopener,noreferrer');
91
+ }
92
+ this.closeForm();
93
+ };
48
94
  this.showForm = () => {
49
95
  if (!this.props.getCapabilities().editArticle) {
50
96
  return;
@@ -65,9 +111,14 @@ export class LinkEditableView extends LinkView {
65
111
  this.props.popper.show(this.dom, this.popperContainer, 'bottom');
66
112
  };
67
113
  this.handleClick = (e) => {
68
- if (this.props.getCapabilities().editArticle) {
69
- e.preventDefault();
114
+ if (!this.props.getCapabilities().editArticle) {
115
+ return;
116
+ }
117
+ e.preventDefault();
118
+ if (isDeleted(this.node) || !this.node.attrs.href) {
119
+ return;
70
120
  }
121
+ this.showContextMenu();
71
122
  };
72
123
  this.handleCancel = () => {
73
124
  const attrs = this.node.attrs;
@@ -35,6 +35,7 @@ export declare const insertEmbed: (state: ManuscriptEditorState, dispatch?: Disp
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
37
  export declare const insertSupplementWeblink: (url: string, title: string, view: ManuscriptEditorView) => boolean;
38
+ export declare const convertLinkToSupplementWeblink: (pos: number, node: ManuscriptNode, view: ManuscriptEditorView) => boolean;
38
39
  export declare const updateSupplementWeblink: (pos: number, url: string, title: string, view: ManuscriptEditorView) => boolean;
39
40
  export declare const insertAttachment: (file: FileAttachment, state: ManuscriptEditorState, type: string, dispatch?: Dispatch) => boolean;
40
41
  export declare const insertBlock: (nodeType: ManuscriptNodeType) => (state: ManuscriptEditorState, dispatch?: Dispatch) => boolean;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.17.2";
1
+ export declare const VERSION = "3.17.3";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
@@ -23,6 +23,10 @@ export declare class LinkEditableView extends LinkView {
23
23
  selectNode: () => void;
24
24
  destroy: () => void;
25
25
  deselectNode: () => void;
26
+ private showContextMenu;
27
+ private handleEdit;
28
+ private handleConvertToWebLink;
29
+ private handleOpenLink;
26
30
  private showForm;
27
31
  private handleClick;
28
32
  private handleCancel;
@@ -31,5 +35,5 @@ export declare class LinkEditableView extends LinkView {
31
35
  private removeLink;
32
36
  private closeForm;
33
37
  }
34
- declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch) => import("../types").NodeViewCreator<LinkEditableView>;
38
+ declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("../commands").Dispatch) => import("../types").NodeViewCreator<LinkEditableView>;
35
39
  export default _default;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@manuscripts/body-editor",
3
3
  "description": "Prosemirror components for editing and viewing manuscripts",
4
- "version": "3.17.2",
4
+ "version": "3.17.3",
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.7.1",
44
+ "@manuscripts/style-guide": "3.7.2",
45
45
  "@manuscripts/track-changes-plugin": "2.4.0",
46
46
  "@manuscripts/transform": "4.5.2",
47
47
  "@popperjs/core": "2.11.8",
package/src/commands.ts CHANGED
@@ -543,6 +543,40 @@ export const insertSupplementWeblink = (
543
543
  return true
544
544
  }
545
545
 
546
+ export const convertLinkToSupplementWeblink = (
547
+ pos: number,
548
+ node: ManuscriptNode,
549
+ view: ManuscriptEditorView
550
+ ) => {
551
+ const href = node.attrs.href
552
+ if (!href) {
553
+ return false
554
+ }
555
+
556
+ const text = node.textContent.trim()
557
+ const id = generateNodeID(schema.nodes.supplement)
558
+
559
+ const supplement = schema.nodes.supplement.createAndFill(
560
+ { id, href },
561
+ createAndFillCaption()
562
+ ) as SupplementNode
563
+
564
+ const crossReference = schema.nodes.cross_reference.create({
565
+ id: generateNodeID(schema.nodes.cross_reference),
566
+ rids: [id],
567
+ label: text,
568
+ })
569
+
570
+ const tr = view.state.tr
571
+ tr.replaceWith(pos, pos + node.nodeSize, crossReference)
572
+ upsertSupplementsSection(tr, supplement)
573
+ tr.setSelection(TextSelection.create(tr.doc, pos))
574
+
575
+ view.focus()
576
+ view.dispatch(tr)
577
+ return true
578
+ }
579
+
546
580
  export const updateSupplementWeblink = (
547
581
  pos: number,
548
582
  url: string,
@@ -75,14 +75,7 @@ export const ReferenceSearch: React.FC<{
75
75
  onAdd: () => void
76
76
  onCite: (items: BibliographyItemAttrs[]) => void
77
77
  onCancel: () => void
78
- }> = ({
79
- query: initialQuery,
80
- sources,
81
- items,
82
- onAdd,
83
- onCite,
84
- onCancel,
85
- }) => {
78
+ }> = ({ query: initialQuery, sources, items, onAdd, onCite, onCancel }) => {
86
79
  const [query, setQuery] = useState<string>(initialQuery || '')
87
80
  const [selections, setSelections] = useState(
88
81
  new Map<string, BibliographyItemAttrs>()
@@ -34,7 +34,7 @@ import { defaultTheme } from '@manuscripts/style-guide'
34
34
  const userProfile: UserProfile = {
35
35
  _id: 'MPUserProfile:1',
36
36
  userID: 'MPUser:1',
37
- connectID: 'connectID1'
37
+ connectID: 'connectID1',
38
38
  }
39
39
 
40
40
  const history = createMemoryHistory()
package/src/versions.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  //THIS FILE WAS GENERATED BY versions.mjs
2
- export const VERSION = '3.17.2';
2
+ export const VERSION = '3.17.3';
3
3
  export const MATHJAX_VERSION = '3.2.2';
@@ -14,9 +14,12 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ import { ContextMenu, ContextMenuProps } from '@manuscripts/style-guide'
18
+ import { isDeleted } from '@manuscripts/track-changes-plugin'
17
19
  import { schema } from '@manuscripts/transform'
18
20
  import { TextSelection } from 'prosemirror-state'
19
21
 
22
+ import { convertLinkToSupplementWeblink } from '../commands'
20
23
  import {
21
24
  LinkForm,
22
25
  LinkFormProps,
@@ -26,7 +29,6 @@ import { allowedHref } from '../lib/url'
26
29
  import { createEditableNodeView } from './creators'
27
30
  import { LinkView } from './link'
28
31
  import ReactSubView from './ReactSubView'
29
- import { isDeleted } from '@manuscripts/track-changes-plugin'
30
32
 
31
33
  export class LinkEditableView extends LinkView {
32
34
  protected popperContainer: HTMLDivElement
@@ -59,7 +61,7 @@ export class LinkEditableView extends LinkView {
59
61
  }
60
62
 
61
63
  public selectNode = () => {
62
- if (!isDeleted(this.node)) {
64
+ if (!isDeleted(this.node) && !this.node.attrs.href) {
63
65
  this.showForm()
64
66
  }
65
67
  }
@@ -72,6 +74,67 @@ export class LinkEditableView extends LinkView {
72
74
  this.closeForm()
73
75
  }
74
76
 
77
+ private showContextMenu = () => {
78
+ if (!this.props.getCapabilities().editArticle) {
79
+ return
80
+ }
81
+
82
+ this.closeForm()
83
+
84
+ const href = this.node.attrs.href
85
+
86
+ const componentProps: ContextMenuProps = {
87
+ actions: [
88
+ {
89
+ label: 'Edit',
90
+ action: this.handleEdit,
91
+ icon: 'Edit',
92
+ },
93
+ {
94
+ label: 'Convert to Weblink',
95
+ action: this.handleConvertToWebLink,
96
+ icon: 'ConvertToWebLink',
97
+ },
98
+ {
99
+ label: 'Open Link',
100
+ action: this.handleOpenLink,
101
+ icon: 'ExternalLink',
102
+ disabled: !allowedHref(href),
103
+ },
104
+ ],
105
+ }
106
+
107
+ this.popperContainer = ReactSubView(
108
+ this.props,
109
+ ContextMenu,
110
+ componentProps,
111
+ this.node,
112
+ this.getPos,
113
+ this.view,
114
+ ['context-menu', 'link-context-menu']
115
+ )
116
+
117
+ this.props.popper.show(this.dom, this.popperContainer, 'bottom')
118
+ }
119
+
120
+ private handleEdit = () => {
121
+ this.closeForm()
122
+ this.showForm()
123
+ }
124
+
125
+ private handleConvertToWebLink = () => {
126
+ this.closeForm()
127
+ convertLinkToSupplementWeblink(this.getPos(), this.node, this.view)
128
+ }
129
+
130
+ private handleOpenLink = () => {
131
+ const href = this.node.attrs.href
132
+ if (allowedHref(href)) {
133
+ window.open(href, '_blank', 'noopener,noreferrer')
134
+ }
135
+ this.closeForm()
136
+ }
137
+
75
138
  private showForm = () => {
76
139
  if (!this.props.getCapabilities().editArticle) {
77
140
  return
@@ -106,9 +169,15 @@ export class LinkEditableView extends LinkView {
106
169
  }
107
170
 
108
171
  private handleClick = (e: Event) => {
109
- if (this.props.getCapabilities().editArticle) {
110
- e.preventDefault()
172
+ if (!this.props.getCapabilities().editArticle) {
173
+ return
174
+ }
175
+ e.preventDefault()
176
+
177
+ if (isDeleted(this.node) || !this.node.attrs.href) {
178
+ return
111
179
  }
180
+ this.showContextMenu()
112
181
  }
113
182
 
114
183
  private handleCancel = () => {