forked from siyuan-note/siyuan
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpopover.ts
More file actions
454 lines (443 loc) · 23.2 KB
/
Copy pathpopover.ts
File metadata and controls
454 lines (443 loc) · 23.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
import {BlockPanel} from "./Panel";
import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName,} from "../protyle/util/hasClosest";
import {fetchPost, fetchSyncPost} from "../util/fetch";
import {hideTooltip, showTooltip, showTooltipIfPointerOver} from "../dialog/tooltip";
import {getIdFromSYProtocol, isLocalPath} from "../util/pathName";
import {App} from "../index";
import {Constants} from "../constants";
import {getCellText} from "../protyle/render/av/cell";
import {isTouchDevice} from "../util/functions";
import {escapeAriaLabel, escapeHtml} from "../util/escape";
let popoverTargetElement: HTMLElement;
export const initBlockPopover = (app: App) => {
let timeout: number;
let timeoutHide: number;
// 编辑器内容块引用/backlinks/tag/bookmark/套娃中使用
document.addEventListener("mouseover", (event: MouseEvent & { target: HTMLElement, path: HTMLElement[] }) => {
if (!window.siyuan.config || !window.siyuan.menus ||
// 拖拽时禁止
window.siyuan.dragElement || document.onmousemove) {
hideTooltip();
return;
}
const aElement = hasClosestByAttribute(event.target, "data-type", "a", true) ||
hasClosestByClassName(event.target, "ariaLabel") ||
hasClosestByAttribute(event.target, "data-type", "tab-header") ||
hasClosestByAttribute(event.target, "data-type", "inline-memo") ||
hasClosestByClassName(event.target, "av__calc--ashow") ||
hasClosestByClassName(event.target, "av__cell");
if (aElement) {
let tooltipClass = "";
let tip = aElement.getAttribute("aria-label") || "";
if (aElement.classList.contains("av__cell") && !aElement.classList.contains("ariaLabel")) {
if (aElement.classList.contains("av__cell--header")) {
const textElement = aElement.querySelector(".av__celltext");
const desc = aElement.getAttribute("data-desc");
if (textElement.scrollWidth > textElement.clientWidth + 0.5 || desc) {
if (desc) {
tip = `${getCellText(aElement)}<div class='ft__on-surface'>${escapeAriaLabel(desc)}</div>`;
} else {
tip = getCellText(aElement);
}
}
} else {
if (aElement.firstElementChild?.getAttribute("data-type") === "url") {
if (aElement.firstElementChild.textContent.indexOf("...") > -1) {
tip = Lute.EscapeHTMLStr(aElement.firstElementChild.getAttribute("data-href"));
tooltipClass = "href";
}
}
tip = "";
if (!tip && aElement.dataset.wrap !== "true" && event.target.dataset.type !== "block-more" && !hasClosestByClassName(event.target, "block__icon")) {
aElement.style.overflow = "auto";
if (aElement.scrollWidth > aElement.clientWidth + 2) {
tip = Lute.EscapeHTMLStr(getCellText(aElement));
}
aElement.style.overflow = "";
}
}
} else if (aElement.parentElement.parentElement.classList.contains("av__views") && aElement.parentElement.classList.contains("layout-tab-bar")) {
const textElement = aElement.querySelector(".item__text");
const desc = aElement.getAttribute("data-desc");
if (textElement.scrollWidth > textElement.clientWidth + 0.5 || desc) {
if (desc) {
tip = `${textElement.textContent}<div class='ft__on-surface'>${escapeAriaLabel(desc)}</div>`;
} else {
tip = textElement.textContent;
}
}
} else if (aElement.classList.contains("av__celltext--url")) {
const title = aElement.getAttribute("data-name") || "";
tip = tip ? `<span style="word-break: break-all">${tip.substring(0, Constants.SIZE_TITLE)}</span>${title ? '<div class="fn__hr"></div><span>' + title + "</span>" : ""}` : title;
tooltipClass = "href";
} else if (aElement.classList.contains("av__calc--ashow") && aElement.clientWidth + 2 < aElement.scrollWidth) {
tip = aElement.lastChild.textContent + " " + aElement.firstElementChild.textContent;
} else if (aElement.getAttribute("data-type") === "setRelationCell") {
const childElement = aElement.querySelector(".b3-menu__label");
if (childElement && childElement.clientWidth < childElement.scrollWidth) {
tip = childElement.textContent;
}
} else if (aElement.classList.contains("protyle-attr--memo")) {
tip = escapeHtml(tip);
}
let tooltipSpace: number | undefined;
if (!tip && aElement.getAttribute("data-type")?.includes("inline-memo")) {
tip = window.DOMPurify.sanitize(aElement.getAttribute("data-inline-memo-content"));
tooltipClass = "memo"; // 为行级备注添加 class https://github.com/siyuan-note/siyuan/issues/6161
tooltipSpace = 0; // tooltip 和备注元素之间不能有空隙 https://github.com/siyuan-note/siyuan/issues/14796#issuecomment-3649757267
}
if (!tip) {
if (aElement.getAttribute("data-type")?.includes("a")) {
tooltipClass = "href"; // 为超链接添加 class https://github.com/siyuan-note/siyuan/issues/11440#issuecomment-2119080691
tooltipSpace = 0;
}
const href = aElement.getAttribute("data-href") || "";
// 链接地址强制换行 https://github.com/siyuan-note/siyuan/issues/11539
if (href) {
tip = `<span style="word-break: break-all">${href.substring(0, Constants.SIZE_TITLE)}</span>`;
}
const title = aElement.getAttribute("data-title");
if (tip && isLocalPath(href) && !aElement.classList.contains("b3-tooltips")) {
let assetTip = tip;
fetchPost("/api/asset/statAsset", {path: href}, (response) => {
if (response.code === 1) {
if (title) {
assetTip += '<div class="fn__hr"></div><span>' + title + "</span>";
}
} else {
assetTip += ` ${response.data.hSize}${title ? '<div class="fn__hr"></div><span>' + title + "</span>" : ""}<br>${window.siyuan.languages.modifiedAt} ${response.data.hUpdated}<br>${window.siyuan.languages.createdAt} ${response.data.hCreated}`;
}
try {
showTooltipIfPointerOver(decodeURIComponent(assetTip), aElement, tooltipClass, event, tooltipSpace);
} catch (e) {
showTooltipIfPointerOver(assetTip, aElement, tooltipClass, event, tooltipSpace);
}
});
tip = "";
} else if (title) {
tip = (tip ? (tip + '<div class="fn__hr"></div>') : "") + "<span>" + title + "</span>";
}
}
const notebookItemElement = hasClosestByClassName(event.target, "b3-list-item__text");
if (notebookItemElement && notebookItemElement.parentElement.getAttribute("data-type") === "navigation-root") {
fetchPost("/api/notebook/getNotebookInfo", {notebook: notebookItemElement.parentElement.parentElement.getAttribute("data-url")}, (response) => {
const boxData = response.data.boxInfo;
const tip = `${boxData.name} <small class='ft__on-surface'>${boxData.hSize}</small>${boxData.docCount !== 0 ? window.siyuan.languages.includeSubFile.replace("x", boxData.docCount) : ""}<br>${window.siyuan.languages.modifiedAt} ${boxData.hMtime}<br>${window.siyuan.languages.createdAt} ${boxData.hCtime}`;
showTooltipIfPointerOver(tip, notebookItemElement);
notebookItemElement.setAttribute("aria-label", tip);
});
}
if (tip && !aElement.classList.contains("b3-tooltips")) {
// https://github.com/siyuan-note/siyuan/issues/11294
try {
showTooltip(decodeURIComponent(tip), aElement, tooltipClass, event, tooltipSpace);
} catch (e) {
// https://ld246.com/article/1718235737991
showTooltip(tip, aElement, tooltipClass, event, tooltipSpace);
}
event.stopPropagation();
} else {
hideTooltip();
}
} else if (!aElement) {
const tipElement = hasClosestByAttribute(event.target, "id", "tooltip", true);
if (!tipElement || (
tipElement && (tipElement.clientHeight >= tipElement.scrollHeight && tipElement.clientWidth >= tipElement.scrollWidth)
)) {
hideTooltip();
}
}
if (window.siyuan.config.editor.floatWindowMode === 1 || window.siyuan.shiftIsPressed) {
clearTimeout(timeoutHide);
timeoutHide = window.setTimeout(() => {
hidePopover(event);
}, Constants.TIMEOUT_INPUT);
if (!getTarget(event, aElement)) {
return;
}
// https://github.com/siyuan-note/siyuan/issues/9007
if (event.relatedTarget && !document.contains(event.relatedTarget as Node)) {
return;
}
if (window.siyuan.ctrlIsPressed) {
clearTimeout(timeoutHide);
showPopover(app);
} else if (window.siyuan.shiftIsPressed) {
clearTimeout(timeoutHide);
showPopover(app, true);
}
return;
}
clearTimeout(timeout);
clearTimeout(timeoutHide);
timeoutHide = window.setTimeout(() => {
if (!hidePopover(event)) {
return;
}
if (!popoverTargetElement && !aElement) {
clearTimeout(timeout);
}
}, Constants.TIMEOUT_INPUT);
timeout = window.setTimeout(() => {
if (!getTarget(event, aElement) || isTouchDevice()) {
return;
}
clearTimeout(timeoutHide);
showPopover(app);
}, window.siyuan.config.editor.floatWindowDelay);
});
};
const hidePopover = (event: MouseEvent & { path: HTMLElement[] }) => {
// pad 端点击后 event.target 不会更新。
const target = isTouchDevice() ? document.elementFromPoint(event.clientX, event.clientY) : event.target as HTMLElement;
if (!target) {
return false;
}
if ((target.id && target.tagName !== "svg" && (target.id.startsWith("minder_node") || target.id.startsWith("kity_") || target.id.startsWith("node_")))
|| target.classList.contains("counter")
|| target.tagName === "circle"
) {
// gutter & mindmap & 文件树上的数字 & 关系图节点不处理
return false;
}
const avPanelElement = hasClosestByClassName(target, "av__panel") || hasClosestByClassName(target, "av__mask");
if (avPanelElement) {
// 浮窗上点击 av 操作,浮窗不能消失
const blockPanel = window.siyuan.blockPanels.find((item) => {
if (item.element.style.zIndex < avPanelElement.style.zIndex) {
return true;
}
});
if (blockPanel) {
return false;
}
} else {
// 浮窗上点击菜单,浮窗不能消失 https://ld246.com/article/1632668091023
const menuElement = hasClosestByClassName(target, "b3-menu");
if (menuElement && menuElement.getAttribute("data-name") !== Constants.MENU_DOC_TREE_MORE) {
const blockPanel = window.siyuan.blockPanels.find((item) => {
if (item.element.style.zIndex < menuElement.style.zIndex) {
return true;
}
});
if (blockPanel) {
return false;
}
}
}
popoverTargetElement = hasClosestByAttribute(target, "data-type", "block-ref") as HTMLElement ||
hasClosestByAttribute(target, "data-type", "virtual-block-ref") as HTMLElement;
if (popoverTargetElement && popoverTargetElement.classList.contains("b3-tooltips")) {
popoverTargetElement = undefined;
}
if (!popoverTargetElement) {
popoverTargetElement = hasClosestByClassName(target, "popover__block") as HTMLElement;
}
const linkElement = hasClosestByAttribute(target, "data-type", "a", true);
if (!popoverTargetElement && linkElement && linkElement.getAttribute("data-href")?.startsWith("siyuan://blocks")) {
popoverTargetElement = linkElement;
}
if (!popoverTargetElement || (popoverTargetElement && window.siyuan.menus.menu.data && window.siyuan.menus.menu.data === popoverTargetElement)) {
// 移动到弹窗的 loading 元素上,但经过 settimeout 后 loading 已经被移除了
// https://ld246.com/article/1673596577519/comment/1673767749885#comments
let targetElement = target;
if (!targetElement.parentElement && event.path && event.path[1]) {
targetElement = event.path[1];
}
const blockElement = hasClosestByClassName(targetElement, "block__popover", true);
const maxEditLevels: { [key: string]: number } = {oid: 0};
window.siyuan.blockPanels.forEach((item) => {
if ((item.targetElement || typeof item.x === "number") && item.element.getAttribute("data-pin") === "true") {
const level = parseInt(item.element.getAttribute("data-level"));
const oid = item.element.getAttribute("data-oid");
if (maxEditLevels[oid]) {
if (level > maxEditLevels[oid]) {
maxEditLevels[oid] = level;
}
} else {
maxEditLevels[oid] = level; // 不能为1,否则 pin 住第三层,第二层会消失
}
}
});
const menuLevel = parseInt(window.siyuan.menus.menu.element.dataset.from);
if (blockElement) {
for (let i = window.siyuan.blockPanels.length - 1; i >= 0; i--) {
const item = window.siyuan.blockPanels[i];
const itemLevel = parseInt(item.element.getAttribute("data-level"));
if ((item.targetElement || typeof item.x === "number") &&
itemLevel > (maxEditLevels[item.element.getAttribute("data-oid")] || 0) &&
item.element.getAttribute("data-pin") === "false" &&
itemLevel > parseInt(blockElement.getAttribute("data-level"))) {
if (menuLevel && menuLevel >= itemLevel) {
// 有 gutter 菜单时不隐藏
break;
} else {
const hasToolbar = item.editors.find(editItem => {
if (!editItem.protyle.toolbar.subElement.classList.contains("fn__none")) {
return true;
}
});
if (hasToolbar) {
break;
}
item.destroy();
}
}
}
} else {
for (let i = window.siyuan.blockPanels.length - 1; i >= 0; i--) {
const item = window.siyuan.blockPanels[i];
const itemLevel = parseInt(item.element.getAttribute("data-level"));
if ((item.targetElement || typeof item.x === "number") && item.element.getAttribute("data-pin") === "false") {
if (menuLevel && menuLevel >= itemLevel) {
// 有 gutter 菜单时不隐藏
break;
} else if (item.targetElement && item.targetElement.classList.contains("protyle-wysiwyg__embed") &&
item.targetElement.contains(targetElement)) {
// 点击嵌入块后浮窗消失后再快速点击嵌入块无法弹出浮窗 https://github.com/siyuan-note/siyuan/issues/12511
break;
} else {
const hasToolbar = item.editors.find(editItem => {
if (!editItem.protyle.toolbar.subElement.classList.contains("fn__none")) {
return true;
}
});
if (hasToolbar) {
break;
}
item.destroy();
}
}
}
}
}
};
const getTarget = (event: MouseEvent & { target: HTMLElement }, aElement: false | HTMLElement) => {
if (window.siyuan.config.editor.floatWindowMode === 2 || hasClosestByClassName(event.target, "history__repo", true)) {
return false;
}
popoverTargetElement = hasClosestByAttribute(event.target, "data-type", "block-ref") as HTMLElement ||
hasClosestByAttribute(event.target, "data-type", "virtual-block-ref") as HTMLElement;
if (popoverTargetElement && popoverTargetElement.classList.contains("b3-tooltips")) {
popoverTargetElement = undefined;
}
if (!popoverTargetElement) {
popoverTargetElement = hasClosestByClassName(event.target, "popover__block") as HTMLElement;
}
if (!popoverTargetElement && aElement) {
if (aElement.getAttribute("data-href")?.startsWith("siyuan://blocks") && aElement.getAttribute("prevent-popover") !== "true") {
popoverTargetElement = aElement;
} else if (aElement.classList.contains("av__cell")) {
const textElement = aElement.querySelector(".av__celltext--url") as HTMLElement;
if (textElement && textElement.dataset.type === "url" && textElement.dataset.href?.startsWith("siyuan://blocks")) {
popoverTargetElement = textElement;
}
}
}
if (!popoverTargetElement || window.siyuan.altIsPressed ||
(window.siyuan.config.editor.floatWindowMode === 0 && window.siyuan.ctrlIsPressed) ||
(popoverTargetElement && popoverTargetElement.getAttribute("prevent-popover") === "true")) {
return false;
}
// https://github.com/siyuan-note/siyuan/issues/4314
if (popoverTargetElement && getSelection().rangeCount > 0) {
const range = getSelection().getRangeAt(0);
if (range.toString() !== "" && popoverTargetElement.contains(range.startContainer)) {
return false;
}
}
return true;
};
export const showPopover = async (app: App, showRef = false) => {
if (!popoverTargetElement || (window.siyuan.menus.menu.data && window.siyuan.menus.menu.data === popoverTargetElement)) {
return;
}
let refDefs: IRefDefs[] = [];
let originalRefBlockIDs: IObject;
const dataId = popoverTargetElement.getAttribute("data-id");
if (dataId) {
// backlink/util/hint 上的弹层
if (showRef) {
const postResponse = await fetchSyncPost("/api/block/getRefIDs", {id: dataId});
refDefs = postResponse.data.refDefs;
originalRefBlockIDs = postResponse.data.originalRefBlockIDs;
} else {
if (dataId.startsWith("[")) {
JSON.parse(dataId).forEach((item: string) => {
refDefs.push({refID: item});
});
} else {
refDefs = [{refID: dataId}];
}
}
} else if (popoverTargetElement.getAttribute("data-type")?.indexOf("virtual-block-ref") > -1) {
const nodeElement = hasClosestBlock(popoverTargetElement);
if (nodeElement) {
const postResponse = await fetchSyncPost("/api/block/getBlockDefIDsByRefText", {
anchor: popoverTargetElement.textContent,
excludeIDs: [nodeElement.getAttribute("data-node-id")]
});
refDefs = postResponse.data.refDefs;
}
} else if (popoverTargetElement.getAttribute("data-type")?.split(" ").includes("a")) {
// 以思源协议开头的链接
refDefs = [{refID: getIdFromSYProtocol(popoverTargetElement.getAttribute("data-href"))}];
} else if (popoverTargetElement.dataset.type === "url") {
// 在 database 的 url 列中以思源协议开头的链接
refDefs = [{refID: getIdFromSYProtocol(popoverTargetElement.textContent.trim())}];
} else if (popoverTargetElement.dataset.popoverUrl) {
// 镜像数据库
const postResponse = await fetchSyncPost(popoverTargetElement.dataset.popoverUrl, {avID: popoverTargetElement.dataset.avId});
refDefs = postResponse.data.refDefs;
} else {
// pdf
let targetId;
let url = "/api/block/getRefIDs";
if (popoverTargetElement.classList.contains("protyle-attr--refcount")) {
// 编辑器中的引用数
targetId = popoverTargetElement.parentElement.parentElement.getAttribute("data-node-id");
} else if (popoverTargetElement.classList.contains("pdf__rect")) {
const relationIds = popoverTargetElement.getAttribute("data-relations");
if (relationIds) {
relationIds.split(",").forEach((item: string) => {
refDefs.push({refID: item});
});
url = "";
} else {
targetId = popoverTargetElement.getAttribute("data-node-id");
url = "/api/block/getRefIDsByFileAnnotationID";
}
} else if (!targetId) {
// 文件树中的引用数
targetId = popoverTargetElement.parentElement.getAttribute("data-node-id");
}
if (url) {
const postResponse = await fetchSyncPost(url, {id: targetId});
refDefs = postResponse.data.refDefs;
originalRefBlockIDs = postResponse.data.originalRefBlockIDs;
}
}
if (refDefs.length === 0) {
return;
}
let hasPin = false;
window.siyuan.blockPanels.find((item) => {
if ((item.targetElement || typeof item.x === "number") && item.element.getAttribute("data-pin") === "true"
&& JSON.stringify(refDefs) === JSON.stringify(item.refDefs)) {
hasPin = true;
return true;
}
});
if (!hasPin && popoverTargetElement.parentElement &&
popoverTargetElement.parentElement.style.opacity !== "0.38" // 反向面板图标拖拽时不应该弹层
) {
window.siyuan.blockPanels.push(new BlockPanel({
app,
targetElement: popoverTargetElement,
isBacklink: showRef || popoverTargetElement.classList.contains("protyle-attr--refcount") || popoverTargetElement.classList.contains("counter"),
refDefs,
originalRefBlockIDs,
}));
}
// 不能清除,否则ctrl 后 shift 就 无效 popoverTargetElement = undefined;
};