@modusoperandi/licit 1.3.1 → 1.3.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.
- package/LinkSetURLCommand.js +1 -1
- package/LinkSetURLCommand.js.flow +1 -1
- package/LinkTooltipPlugin.js +140 -188
- package/LinkTooltipPlugin.js.flow +139 -195
- package/bom.xml +333 -332
- package/package.json +1 -1
- package/ui/LinkTooltip.js +4 -85
- package/ui/LinkTooltip.js.flow +3 -77
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
EditorState,
|
|
5
|
-
Plugin,
|
|
6
|
-
PluginKey,
|
|
7
|
-
TextSelection,
|
|
8
|
-
} from 'prosemirror-state';
|
|
3
|
+
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
|
|
9
4
|
import { EditorView } from 'prosemirror-view';
|
|
10
5
|
|
|
11
6
|
import { MARK_LINK } from './MarkNames.js';
|
|
12
|
-
import { hideSelectionPlaceholder } from './SelectionPlaceholderPlugin.js';
|
|
13
7
|
import {
|
|
14
|
-
applyMark,
|
|
15
|
-
findNodesWithSameMark,
|
|
16
8
|
atAnchorTopCenter,
|
|
17
9
|
createPopUp,
|
|
10
|
+
findNodesWithSameMark,
|
|
18
11
|
} from '@modusoperandi/licit-ui-commands';
|
|
19
12
|
import lookUpElement from './lookUpElement.js';
|
|
20
13
|
import LinkTooltip from './ui/LinkTooltip.js';
|
|
21
|
-
import
|
|
22
|
-
|
|
14
|
+
import scrollIntoView from 'smooth-scroll-into-view-if-needed';
|
|
15
|
+
|
|
16
|
+
import sanitizeURL from './sanitizeURL.js';
|
|
23
17
|
|
|
24
|
-
// https://prosemirror.net/examples/tooltip/
|
|
25
18
|
const SPEC = {
|
|
26
|
-
// [FS] IRAD-1005 2020-07-07
|
|
27
|
-
// Upgrade outdated packages.
|
|
28
19
|
key: new PluginKey('LinkTooltipPlugin'),
|
|
20
|
+
props: {
|
|
21
|
+
handleDOMEvents: {
|
|
22
|
+
mouseover(view, event) {
|
|
23
|
+
const pluginView = view.dom._linkTooltipView;
|
|
24
|
+
return pluginView?._handleMouseOver(event) ?? false;
|
|
25
|
+
},
|
|
26
|
+
mouseout(view, event) {
|
|
27
|
+
const pluginView = view.dom._linkTooltipView;
|
|
28
|
+
return pluginView?._handleMouseOut.call(view, event);
|
|
29
|
+
},
|
|
30
|
+
click(view, event) {
|
|
31
|
+
const pluginView = view.dom._linkTooltipView;
|
|
32
|
+
return pluginView?._handleClick.call(view, event);
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
29
36
|
view(editorView: EditorView) {
|
|
30
|
-
|
|
37
|
+
const pluginView = new LinkTooltipView(editorView);
|
|
38
|
+
// Store the instance for use in DOM handlers
|
|
39
|
+
editorView.dom._linkTooltipView = pluginView;
|
|
40
|
+
return pluginView;
|
|
31
41
|
},
|
|
32
42
|
};
|
|
33
43
|
|
|
@@ -36,225 +46,159 @@ class LinkTooltipPlugin extends Plugin {
|
|
|
36
46
|
super(SPEC);
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
|
-
|
|
40
49
|
class LinkTooltipView {
|
|
41
|
-
_anchorEl = null;
|
|
42
50
|
_popup = null;
|
|
43
|
-
|
|
51
|
+
_anchorEl = null;
|
|
52
|
+
_view = null;
|
|
44
53
|
|
|
45
|
-
constructor(
|
|
46
|
-
this.
|
|
54
|
+
constructor(view) {
|
|
55
|
+
this._view = view;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
view.state.tr.doc.descendants((node, pos) => {
|
|
53
|
-
if (node.attrs.styleName && node.attrs.innerLink === selectionId) {
|
|
54
|
-
tocItemPos = { position: pos, textContent: node.textContent };
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
return tocItemPos;
|
|
59
|
-
}
|
|
58
|
+
_handleMouseOver(event) {
|
|
59
|
+
const anchor = event.target?.closest('a');
|
|
60
|
+
if (!anchor || anchor === this._anchorEl) return;
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
const
|
|
62
|
+
this._anchorEl = anchor;
|
|
63
|
+
const href = anchor.getAttribute('href');
|
|
64
|
+
this._popup?.close();
|
|
65
|
+
this._popup = createPopUp(
|
|
66
|
+
LinkTooltip,
|
|
67
|
+
{
|
|
68
|
+
href,
|
|
69
|
+
selectionId: null,
|
|
70
|
+
editorView: this._view,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
anchor,
|
|
74
|
+
autoDismiss: true,
|
|
75
|
+
onClose: () => {
|
|
76
|
+
this._popup = null;
|
|
77
|
+
this._anchorEl = null;
|
|
78
|
+
},
|
|
79
|
+
position: atAnchorTopCenter,
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
_handleClick(event) {
|
|
84
|
+
const { state } = this.dom._linkTooltipView._view;
|
|
63
85
|
const { doc, selection, schema } = state;
|
|
64
|
-
const markType = schema.marks[MARK_LINK];
|
|
65
|
-
if (!markType) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
86
|
const { from, to } = selection;
|
|
69
|
-
const
|
|
87
|
+
const markType = schema.marks[MARK_LINK];
|
|
70
88
|
|
|
89
|
+
const result = findNodesWithSameMark(doc, from, to, markType);
|
|
71
90
|
if (!result) {
|
|
72
|
-
|
|
73
|
-
return;
|
|
91
|
+
return false;
|
|
74
92
|
}
|
|
75
|
-
const domFound =
|
|
93
|
+
const domFound = this.dom._linkTooltipView._view.domAtPos(from);
|
|
76
94
|
if (!domFound) {
|
|
77
|
-
|
|
78
|
-
return;
|
|
95
|
+
return false;
|
|
79
96
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return;
|
|
97
|
+
|
|
98
|
+
const anchor = lookUpElement(domFound.node, (el) => el.nodeName === 'A');
|
|
99
|
+
if (!anchor) {
|
|
100
|
+
return false;
|
|
84
101
|
}
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
|
|
103
|
+
const href = anchor.getAttribute('href');
|
|
104
|
+
const selectionId = anchor.getAttribute('selectionid');
|
|
105
|
+
|
|
106
|
+
const tocItemPos = this.dom._linkTooltipView.getInnerlinkSelected_position(
|
|
107
|
+
this.dom._linkTooltipView._view,
|
|
87
108
|
result.mark.attrs.selectionId
|
|
88
109
|
);
|
|
89
|
-
const popup = this._popup;
|
|
90
|
-
const viewPops = {
|
|
91
|
-
editorState: state,
|
|
92
|
-
editorView: view,
|
|
93
|
-
href: result.mark.attrs.href,
|
|
94
|
-
selectionId_: result.mark.attrs.selectionId,
|
|
95
|
-
onCancel: this._onCancel,
|
|
96
|
-
onEdit: this._onEdit,
|
|
97
|
-
onRemove: this._onRemove,
|
|
98
|
-
tocItemPos_: tocItemPos,
|
|
99
|
-
};
|
|
100
110
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
this.dom._linkTooltipView.jumpLink(
|
|
112
|
+
this.dom._linkTooltipView._view,
|
|
113
|
+
tocItemPos,
|
|
114
|
+
href,
|
|
115
|
+
selectionId
|
|
116
|
+
);
|
|
117
|
+
event.preventDefault(); // prevent default browser navigation
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_handleMouseOut(event) {
|
|
122
|
+
if (this._anchorEl && !this._anchorEl.contains(event.relatedTarget)) {
|
|
123
|
+
this._popup?.close();
|
|
124
|
+
this._popup = null;
|
|
125
|
+
this._anchorEl = null;
|
|
112
126
|
}
|
|
113
127
|
}
|
|
114
128
|
|
|
115
129
|
destroy() {
|
|
116
130
|
this._popup?.close();
|
|
117
|
-
this._editor?.close();
|
|
118
131
|
}
|
|
119
132
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
_onClose = (): void => {
|
|
126
|
-
this._anchorEl = null;
|
|
127
|
-
this._editor = null;
|
|
128
|
-
this._popup = null;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
showTocList = async (view) => {
|
|
132
|
-
let storeTOCvalue = [];
|
|
133
|
-
const TOCselectedNode = [];
|
|
134
|
-
|
|
135
|
-
const stylePromise = view.runtime;
|
|
136
|
-
if (stylePromise === null || undefined) {
|
|
137
|
-
return TOCselectedNode;
|
|
133
|
+
jumpLink = (view: EditorView, tocItemPos, href, selectionId): void => {
|
|
134
|
+
if (selectionId || (selectionId === 0 && tocItemPos)) {
|
|
135
|
+
this.jumpInnerLink(view, tocItemPos);
|
|
138
136
|
} else {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
storeTOCvalue = styles
|
|
142
|
-
.filter(
|
|
143
|
-
(
|
|
144
|
-
style // Added TOT/TOF selected styles to be listed as well
|
|
145
|
-
) =>
|
|
146
|
-
style?.styles?.toc === true ||
|
|
147
|
-
style?.styles?.tot === true ||
|
|
148
|
-
style?.styles?.tof === true
|
|
149
|
-
)
|
|
150
|
-
.map((style) => style?.styleName);
|
|
151
|
-
view.state.tr.doc.descendants((node, pos) => {
|
|
152
|
-
if (node.attrs.styleName) {
|
|
153
|
-
for (let i = 0; i <= storeTOCvalue.length; i++) {
|
|
154
|
-
if (storeTOCvalue[i] === node.attrs.styleName) {
|
|
155
|
-
TOCselectedNode.push({ node_: node, pos_: pos });
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
return TOCselectedNode;
|
|
137
|
+
this._openLink(href);
|
|
161
138
|
}
|
|
162
139
|
};
|
|
163
140
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
141
|
+
jumpInnerLink = (view: EditorView, tocItemPos): void => {
|
|
142
|
+
const transaction = view.state.tr;
|
|
143
|
+
const tr = transaction.setSelection(
|
|
144
|
+
TextSelection.create(transaction.doc, tocItemPos.position + 1)
|
|
145
|
+
);
|
|
146
|
+
view.dispatch(tr.scrollIntoView(true));
|
|
147
|
+
const dom = view.domAtPos(tocItemPos?.position + 1).node;
|
|
148
|
+
if (dom?.scrollIntoView) {
|
|
149
|
+
dom.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
168
150
|
}
|
|
151
|
+
};
|
|
169
152
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
153
|
+
_openLink = (href: string): void => {
|
|
154
|
+
if (this.isBookMarkHref(href)) {
|
|
155
|
+
const id = href.substr(1);
|
|
156
|
+
const el = document.getElementById(id);
|
|
157
|
+
if (el) {
|
|
158
|
+
const { onCancel, editorView } = this.props;
|
|
159
|
+
onCancel(editorView);
|
|
160
|
+
(async () => {
|
|
161
|
+
// https://www.npmjs.com/package/smooth-scroll-into-view-if-needed
|
|
162
|
+
await scrollIntoView(el, {
|
|
163
|
+
scrollMode: 'if-needed',
|
|
164
|
+
behavior: 'smooth',
|
|
165
|
+
});
|
|
166
|
+
})();
|
|
167
|
+
}
|
|
176
168
|
return;
|
|
177
169
|
}
|
|
170
|
+
if (href) {
|
|
171
|
+
const url = sanitizeURL(href);
|
|
172
|
+
let popupString;
|
|
173
|
+
|
|
174
|
+
if (this._view.editable) {
|
|
175
|
+
popupString = 'Any unsaved changes will be lost';
|
|
176
|
+
} else {
|
|
177
|
+
popupString = '';
|
|
178
|
+
}
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
TOCselectedNode_: tocItemsNode,
|
|
186
|
-
view_: view,
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
this._editor = createPopUp(LinkURLEditor, viewPops, {
|
|
190
|
-
onClose: (value) => {
|
|
191
|
-
this._editor = null;
|
|
192
|
-
this._onEditEnd(view, selection, value);
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
});
|
|
180
|
+
if (this._view?.runtime?.openLinkDialog) {
|
|
181
|
+
this._view.runtime.openLinkDialog(url, popupString);
|
|
182
|
+
} else {
|
|
183
|
+
window.open(url);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
196
186
|
};
|
|
197
187
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this._onEditEnd(view, view.state.selection, null);
|
|
188
|
+
isBookMarkHref = (href: string): boolean => {
|
|
189
|
+
return !!href && href.startsWith('#') && href.length >= 2;
|
|
201
190
|
};
|
|
202
191
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let tr = hideSelectionPlaceholder(state);
|
|
210
|
-
|
|
211
|
-
if (url !== undefined) {
|
|
212
|
-
const { schema } = state;
|
|
213
|
-
const markType = schema.marks[MARK_LINK];
|
|
214
|
-
if (markType) {
|
|
215
|
-
const result = findNodesWithSameMark(
|
|
216
|
-
tr.doc,
|
|
217
|
-
initialSelection.from,
|
|
218
|
-
initialSelection.to,
|
|
219
|
-
markType
|
|
220
|
-
);
|
|
221
|
-
if (result) {
|
|
222
|
-
const linkSelection = TextSelection.create(
|
|
223
|
-
tr.doc,
|
|
224
|
-
result.from.pos,
|
|
225
|
-
result.to.pos + 1
|
|
226
|
-
);
|
|
227
|
-
tr = tr.setSelection(linkSelection);
|
|
228
|
-
let selectionId;
|
|
229
|
-
let href;
|
|
230
|
-
if (url === null) {
|
|
231
|
-
selectionId = null;
|
|
232
|
-
href = null;
|
|
233
|
-
} else if (url.includes(INNER_LINK)) {
|
|
234
|
-
selectionId = url.split(INNER_LINK)[0];
|
|
235
|
-
href = url.split(INNER_LINK)[1];
|
|
236
|
-
} else {
|
|
237
|
-
selectionId = null;
|
|
238
|
-
href = url;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const attrs = href ? { href, selectionId } : null;
|
|
242
|
-
tr = applyMark(tr, schema, markType, attrs);
|
|
243
|
-
|
|
244
|
-
// [FS] IRAD-1005 2020-07-09
|
|
245
|
-
// Upgrade outdated packages.
|
|
246
|
-
// reset selection to original using the latest doc.
|
|
247
|
-
const origSelection = TextSelection.create(
|
|
248
|
-
tr.doc,
|
|
249
|
-
initialSelection.from,
|
|
250
|
-
initialSelection.to
|
|
251
|
-
);
|
|
252
|
-
tr = tr.setSelection(origSelection);
|
|
192
|
+
getInnerlinkSelected_position = (view: EditorView, selectionId): void => {
|
|
193
|
+
let tocItemPos = null;
|
|
194
|
+
if (selectionId) {
|
|
195
|
+
view.state.tr.doc.descendants((node, pos) => {
|
|
196
|
+
if (node.attrs.styleName && node.attrs.innerLink === selectionId) {
|
|
197
|
+
tocItemPos = { position: pos, textContent: node.textContent };
|
|
253
198
|
}
|
|
254
|
-
}
|
|
199
|
+
});
|
|
255
200
|
}
|
|
256
|
-
|
|
257
|
-
view.focus();
|
|
201
|
+
return tocItemPos;
|
|
258
202
|
};
|
|
259
203
|
}
|
|
260
204
|
|