@modusoperandi/licit 1.3.1 → 1.3.2
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 +141 -188
- package/LinkTooltipPlugin.js.flow +140 -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,44 @@
|
|
|
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';
|
|
17
|
+
import '@modusoperandi/licit-ui-commands/ui/czi-pop-up.css';
|
|
23
18
|
|
|
24
|
-
// https://prosemirror.net/examples/tooltip/
|
|
25
19
|
const SPEC = {
|
|
26
|
-
// [FS] IRAD-1005 2020-07-07
|
|
27
|
-
// Upgrade outdated packages.
|
|
28
20
|
key: new PluginKey('LinkTooltipPlugin'),
|
|
21
|
+
props: {
|
|
22
|
+
handleDOMEvents: {
|
|
23
|
+
mouseover(view, event) {
|
|
24
|
+
const pluginView = view.dom._linkTooltipView;
|
|
25
|
+
return pluginView?._handleMouseOver(event) ?? false;
|
|
26
|
+
},
|
|
27
|
+
mouseout(view, event) {
|
|
28
|
+
const pluginView = view.dom._linkTooltipView;
|
|
29
|
+
return pluginView?._handleMouseOut.call(view, event);
|
|
30
|
+
},
|
|
31
|
+
click(view, event) {
|
|
32
|
+
const pluginView = view.dom._linkTooltipView;
|
|
33
|
+
return pluginView?._handleClick.call(view, event);
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
29
37
|
view(editorView: EditorView) {
|
|
30
|
-
|
|
38
|
+
const pluginView = new LinkTooltipView(editorView);
|
|
39
|
+
// Store the instance for use in DOM handlers
|
|
40
|
+
editorView.dom._linkTooltipView = pluginView;
|
|
41
|
+
return pluginView;
|
|
31
42
|
},
|
|
32
43
|
};
|
|
33
44
|
|
|
@@ -36,225 +47,159 @@ class LinkTooltipPlugin extends Plugin {
|
|
|
36
47
|
super(SPEC);
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
|
-
|
|
40
50
|
class LinkTooltipView {
|
|
41
|
-
_anchorEl = null;
|
|
42
51
|
_popup = null;
|
|
43
|
-
|
|
52
|
+
_anchorEl = null;
|
|
53
|
+
_view = null;
|
|
44
54
|
|
|
45
|
-
constructor(
|
|
46
|
-
this.
|
|
55
|
+
constructor(view) {
|
|
56
|
+
this._view = view;
|
|
47
57
|
}
|
|
48
58
|
|
|
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
|
-
}
|
|
59
|
+
_handleMouseOver(event) {
|
|
60
|
+
const anchor = event.target?.closest('a');
|
|
61
|
+
if (!anchor || anchor === this._anchorEl) return;
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
+
this._anchorEl = anchor;
|
|
64
|
+
const href = anchor.getAttribute('href');
|
|
65
|
+
this._popup?.close();
|
|
66
|
+
this._popup = createPopUp(
|
|
67
|
+
LinkTooltip,
|
|
68
|
+
{
|
|
69
|
+
href,
|
|
70
|
+
selectionId: null,
|
|
71
|
+
editorView: this._view,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
anchor,
|
|
75
|
+
autoDismiss: true,
|
|
76
|
+
onClose: () => {
|
|
77
|
+
this._popup = null;
|
|
78
|
+
this._anchorEl = null;
|
|
79
|
+
},
|
|
80
|
+
position: atAnchorTopCenter,
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
_handleClick(event) {
|
|
85
|
+
const { state } = this.dom._linkTooltipView._view;
|
|
63
86
|
const { doc, selection, schema } = state;
|
|
64
|
-
const markType = schema.marks[MARK_LINK];
|
|
65
|
-
if (!markType) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
87
|
const { from, to } = selection;
|
|
69
|
-
const
|
|
88
|
+
const markType = schema.marks[MARK_LINK];
|
|
70
89
|
|
|
90
|
+
const result = findNodesWithSameMark(doc, from, to, markType);
|
|
71
91
|
if (!result) {
|
|
72
|
-
|
|
73
|
-
return;
|
|
92
|
+
return false;
|
|
74
93
|
}
|
|
75
|
-
const domFound =
|
|
94
|
+
const domFound = this.dom._linkTooltipView._view.domAtPos(from);
|
|
76
95
|
if (!domFound) {
|
|
77
|
-
|
|
78
|
-
return;
|
|
96
|
+
return false;
|
|
79
97
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return;
|
|
98
|
+
|
|
99
|
+
const anchor = lookUpElement(domFound.node, (el) => el.nodeName === 'A');
|
|
100
|
+
if (!anchor) {
|
|
101
|
+
return false;
|
|
84
102
|
}
|
|
85
|
-
|
|
86
|
-
|
|
103
|
+
|
|
104
|
+
const href = anchor.getAttribute('href');
|
|
105
|
+
const selectionId = anchor.getAttribute('selectionid');
|
|
106
|
+
|
|
107
|
+
const tocItemPos = this.dom._linkTooltipView.getInnerlinkSelected_position(
|
|
108
|
+
this.dom._linkTooltipView._view,
|
|
87
109
|
result.mark.attrs.selectionId
|
|
88
110
|
);
|
|
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
111
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
this.dom._linkTooltipView.jumpLink(
|
|
113
|
+
this.dom._linkTooltipView._view,
|
|
114
|
+
tocItemPos,
|
|
115
|
+
href,
|
|
116
|
+
selectionId
|
|
117
|
+
);
|
|
118
|
+
event.preventDefault(); // prevent default browser navigation
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_handleMouseOut(event) {
|
|
123
|
+
if (this._anchorEl && !this._anchorEl.contains(event.relatedTarget)) {
|
|
124
|
+
this._popup?.close();
|
|
125
|
+
this._popup = null;
|
|
126
|
+
this._anchorEl = null;
|
|
112
127
|
}
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
destroy() {
|
|
116
131
|
this._popup?.close();
|
|
117
|
-
this._editor?.close();
|
|
118
132
|
}
|
|
119
133
|
|
|
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;
|
|
134
|
+
jumpLink = (view: EditorView, tocItemPos, href, selectionId): void => {
|
|
135
|
+
if (selectionId || (selectionId === 0 && tocItemPos)) {
|
|
136
|
+
this.jumpInnerLink(view, tocItemPos);
|
|
138
137
|
} 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;
|
|
138
|
+
this._openLink(href);
|
|
161
139
|
}
|
|
162
140
|
};
|
|
163
141
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
142
|
+
jumpInnerLink = (view: EditorView, tocItemPos): void => {
|
|
143
|
+
const transaction = view.state.tr;
|
|
144
|
+
const tr = transaction.setSelection(
|
|
145
|
+
TextSelection.create(transaction.doc, tocItemPos.position + 1)
|
|
146
|
+
);
|
|
147
|
+
view.dispatch(tr.scrollIntoView(true));
|
|
148
|
+
const dom = view.domAtPos(tocItemPos?.position + 1).node;
|
|
149
|
+
if (dom?.scrollIntoView) {
|
|
150
|
+
dom.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
168
151
|
}
|
|
152
|
+
};
|
|
169
153
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
154
|
+
_openLink = (href: string): void => {
|
|
155
|
+
if (this.isBookMarkHref(href)) {
|
|
156
|
+
const id = href.substr(1);
|
|
157
|
+
const el = document.getElementById(id);
|
|
158
|
+
if (el) {
|
|
159
|
+
const { onCancel, editorView } = this.props;
|
|
160
|
+
onCancel(editorView);
|
|
161
|
+
(async () => {
|
|
162
|
+
// https://www.npmjs.com/package/smooth-scroll-into-view-if-needed
|
|
163
|
+
await scrollIntoView(el, {
|
|
164
|
+
scrollMode: 'if-needed',
|
|
165
|
+
behavior: 'smooth',
|
|
166
|
+
});
|
|
167
|
+
})();
|
|
168
|
+
}
|
|
176
169
|
return;
|
|
177
170
|
}
|
|
171
|
+
if (href) {
|
|
172
|
+
const url = sanitizeURL(href);
|
|
173
|
+
let popupString;
|
|
174
|
+
|
|
175
|
+
if (this._view.editable) {
|
|
176
|
+
popupString = 'Any unsaved changes will be lost';
|
|
177
|
+
} else {
|
|
178
|
+
popupString = '';
|
|
179
|
+
}
|
|
178
180
|
|
|
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
|
-
});
|
|
181
|
+
if (this._view?.runtime?.openLinkDialog) {
|
|
182
|
+
this._view.runtime.openLinkDialog(url, popupString);
|
|
183
|
+
} else {
|
|
184
|
+
window.open(url);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
196
187
|
};
|
|
197
188
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this._onEditEnd(view, view.state.selection, null);
|
|
189
|
+
isBookMarkHref = (href: string): boolean => {
|
|
190
|
+
return !!href && href.startsWith('#') && href.length >= 2;
|
|
201
191
|
};
|
|
202
192
|
|
|
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);
|
|
193
|
+
getInnerlinkSelected_position = (view: EditorView, selectionId): void => {
|
|
194
|
+
let tocItemPos = null;
|
|
195
|
+
if (selectionId) {
|
|
196
|
+
view.state.tr.doc.descendants((node, pos) => {
|
|
197
|
+
if (node.attrs.styleName && node.attrs.innerLink === selectionId) {
|
|
198
|
+
tocItemPos = { position: pos, textContent: node.textContent };
|
|
253
199
|
}
|
|
254
|
-
}
|
|
200
|
+
});
|
|
255
201
|
}
|
|
256
|
-
|
|
257
|
-
view.focus();
|
|
202
|
+
return tocItemPos;
|
|
258
203
|
};
|
|
259
204
|
}
|
|
260
205
|
|