A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1import {
2 hasClosestBlock,
3 hasClosestByClassName,
4 hasClosestByTag,
5 hasTopClosestByClassName,
6 isInAVBlock,
7 isInEmbedBlock
8} from "../util/hasClosest";
9import {getIconByType} from "../../editor/getIcon";
10import {enterBack, iframeMenu, setFold, tableMenu, videoMenu, zoomOut} from "../../menus/protyle";
11import {MenuItem} from "../../menus/Menu";
12import {copySubMenu, openAttr, openFileAttr, openWechatNotify} from "../../menus/commonMenuItem";
13import {
14 copyPlainText,
15 isInAndroid,
16 isInHarmony,
17 isMac,
18 isOnlyMeta,
19 openByMobile,
20 updateHotkeyTip,
21 writeText
22} from "../util/compatibility";
23import {
24 transaction,
25 turnsIntoOneTransaction,
26 turnsIntoTransaction,
27 turnsOneInto,
28 updateBatchTransaction,
29 updateTransaction
30} from "../wysiwyg/transaction";
31import {removeBlock} from "../wysiwyg/remove";
32import {focusBlock, focusByRange, getEditorRange} from "../util/selection";
33import {hideElements} from "../ui/hideElements";
34import {highlightRender} from "../render/highlightRender";
35import {blockRender} from "../render/blockRender";
36import {getContenteditableElement, getTopAloneElement, isNotEditBlock} from "../wysiwyg/getBlock";
37import * as dayjs from "dayjs";
38import {fetchPost} from "../../util/fetch";
39import {cancelSB, genEmptyElement, getLangByType, insertEmptyBlock, jumpToParent,} from "../../block/util";
40import {countBlockWord} from "../../layout/status";
41import {Constants} from "../../constants";
42import {mathRender} from "../render/mathRender";
43import {duplicateBlock} from "../wysiwyg/commonHotkey";
44import {movePathTo, useShell} from "../../util/pathName";
45import {hintMoveBlock} from "../hint/extend";
46import {makeCard, quickMakeCard} from "../../card/makeCard";
47import {transferBlockRef} from "../../menus/block";
48import {isMobile} from "../../util/functions";
49import {AIActions} from "../../ai/actions";
50import {activeBlur, renderTextMenu, showKeyboardToolbarUtil} from "../../mobile/util/keyboardToolbar";
51import {hideTooltip} from "../../dialog/tooltip";
52import {appearanceMenu} from "../toolbar/Font";
53import {setPosition} from "../../util/setPosition";
54import {emitOpenMenu} from "../../plugin/EventBus";
55import {insertAttrViewBlockAnimation, updateHeader} from "../render/av/row";
56import {avContextmenu, duplicateCompletely} from "../render/av/action";
57import {getPlainText} from "../util/paste";
58import {addEditorToDatabase} from "../render/av/addToDatabase";
59import {processClonePHElement} from "../render/util";
60/// #if !MOBILE
61import {openFileById} from "../../editor/util";
62import * as path from "path";
63/// #endif
64import {checkFold} from "../../util/noRelyPCFunction";
65import {clearSelect} from "../util/clearSelect";
66
67export class Gutter {
68 public element: HTMLElement;
69 private gutterTip: string;
70
71 constructor(protyle: IProtyle) {
72 if (isMac()) {
73 this.gutterTip = window.siyuan.languages.gutterTip.replace("⌥→", updateHotkeyTip(window.siyuan.config.keymap.general.enter.custom));
74 } else {
75 this.gutterTip = window.siyuan.languages.gutterTip.replace("⌥→", updateHotkeyTip(window.siyuan.config.keymap.general.enter.custom))
76 .replace("⌘↑", updateHotkeyTip(window.siyuan.config.keymap.editor.general.collapse.custom))
77 .replace("⌥⌘A", updateHotkeyTip(window.siyuan.config.keymap.editor.general.attr.custom))
78 .replace(/⌘/g, "Ctrl+").replace(/⌥/g, "Alt+").replace(/⇧/g, "Shift+").replace(/⌃/g, "Ctrl+");
79 }
80 if (protyle.options.backlinkData) {
81 this.gutterTip = this.gutterTip.replace(window.siyuan.languages.enter, window.siyuan.languages.openBy);
82 }
83 this.element = document.createElement("div");
84 this.element.className = "protyle-gutters";
85 this.element.addEventListener("dragstart", (event: DragEvent & { target: HTMLElement }) => {
86 hideTooltip();
87 window.siyuan.menus.menu.remove();
88 const buttonElement = event.target.parentElement;
89 let selectIds: string[] = [];
90 let selectElements: Element[] = [];
91 let avElement: Element;
92 if (buttonElement.dataset.rowId) {
93 avElement = Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-node-id="${buttonElement.dataset.nodeId}"]`)).find((item: HTMLElement) => {
94 if (!isInEmbedBlock(item) && !isInAVBlock(item)) {
95 return true;
96 }
97 });
98 if (avElement.querySelector('.block__icon[data-type="av-sort"]')?.classList.contains("block__icon--active")) {
99 const bodyElements = avElement.querySelectorAll(".av__body");
100 if (bodyElements.length === 1) {
101 event.preventDefault();
102 event.stopPropagation();
103 return;
104 } else if (["template", "created", "updated"].includes(bodyElements[0].getAttribute("data-dtype"))) {
105 event.preventDefault();
106 event.stopPropagation();
107 return;
108 }
109 }
110 const rowElement = avElement.querySelector(`.av__body${buttonElement.dataset.groupId ? `[data-group-id="${buttonElement.dataset.groupId}"]` : ""} .av__row[data-id="${buttonElement.dataset.rowId}"]`);
111 if (!rowElement.classList.contains("av__row--select")) {
112 avElement.querySelectorAll(".av__row--select:not(.av__row--header)").forEach(item => {
113 item.classList.remove("av__row--select");
114 item.querySelector("use").setAttribute("xlink:href", "#iconUncheck");
115 });
116 }
117 rowElement.classList.add("av__row--select");
118 rowElement.querySelector(".av__firstcol use").setAttribute("xlink:href", "#iconCheck");
119 updateHeader(rowElement as HTMLElement);
120 avElement.querySelectorAll(".av__row--select:not(.av__row--header)").forEach(item => {
121 const groupId = (hasClosestByClassName(item, "av__body") as HTMLElement)?.dataset.groupId || "";
122 selectIds.push(item.getAttribute("data-id") + (groupId ? "@" + groupId : ""));
123 selectElements.push(item);
124 });
125 } else {
126 const gutterId = buttonElement.getAttribute("data-node-id");
127 selectElements = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"));
128 let selectedIncludeGutter = false;
129 selectElements.forEach((item => {
130 const itemId = item.getAttribute("data-node-id");
131 if (itemId === gutterId) {
132 selectedIncludeGutter = true;
133 }
134 selectIds.push(itemId);
135 }));
136 if (!selectedIncludeGutter) {
137 let gutterNodeElement: HTMLElement;
138 Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${gutterId}"]`)).find((item: HTMLElement) => {
139 if (!isInEmbedBlock(item) && this.isMatchNode(item)) {
140 gutterNodeElement = item;
141 return true;
142 }
143 });
144 if (gutterNodeElement) {
145 selectElements.forEach((item => {
146 item.classList.remove("protyle-wysiwyg--select");
147 }));
148 gutterNodeElement.classList.add("protyle-wysiwyg--select");
149 selectElements = [gutterNodeElement];
150 selectIds = [gutterId];
151 }
152 }
153 }
154
155 const ghostElement = document.createElement("div");
156 ghostElement.className = protyle.wysiwyg.element.className;
157 selectElements.forEach(item => {
158 if (item.querySelector("iframe")) {
159 const type = item.getAttribute("data-type");
160 const embedElement = genEmptyElement();
161 embedElement.classList.add("protyle-wysiwyg--select");
162 getContenteditableElement(embedElement).innerHTML = `<svg class="svg"><use xlink:href="${buttonElement.querySelector("use").getAttribute("xlink:href")}"></use></svg> ${getLangByType(type)}`;
163 ghostElement.append(embedElement);
164 } else {
165 ghostElement.append(processClonePHElement(item.cloneNode(true) as Element));
166 }
167 });
168 ghostElement.setAttribute("style", `position:fixed;opacity:.1;width:${selectElements[0].clientWidth}px;padding:0;`);
169 document.body.append(ghostElement);
170 event.dataTransfer.setDragImage(ghostElement, 0, 0);
171 setTimeout(() => {
172 ghostElement.remove();
173 });
174 buttonElement.style.opacity = "0.38";
175 window.siyuan.dragElement = avElement as HTMLElement || protyle.wysiwyg.element;
176 event.dataTransfer.setData(`${Constants.SIYUAN_DROP_GUTTER}${buttonElement.getAttribute("data-type")}${Constants.ZWSP}${buttonElement.getAttribute("data-subtype")}${Constants.ZWSP}${selectIds}${Constants.ZWSP}${window.siyuan.config.system.workspaceDir}`,
177 protyle.wysiwyg.element.innerHTML);
178 });
179 this.element.addEventListener("dragend", () => {
180 this.element.querySelectorAll("button").forEach((item) => {
181 item.style.opacity = "";
182 });
183 window.siyuan.dragElement = undefined;
184 });
185 this.element.addEventListener("click", (event: MouseEvent & { target: HTMLInputElement }) => {
186 const buttonElement = hasClosestByTag(event.target, "BUTTON");
187 if (!buttonElement) {
188 return;
189 }
190 event.preventDefault();
191 event.stopPropagation();
192 hideTooltip();
193 clearSelect(["av", "img"], protyle.wysiwyg.element);
194 const id = buttonElement.getAttribute("data-node-id");
195 if (!id) {
196 if (buttonElement.getAttribute("disabled")) {
197 return;
198 }
199 buttonElement.setAttribute("disabled", "disabled");
200 let foldElement: Element;
201 Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${(buttonElement.previousElementSibling || buttonElement.nextElementSibling).getAttribute("data-node-id")}"]`)).find(item => {
202 if (!isInEmbedBlock(item) && this.isMatchNode(item)) {
203 foldElement = item;
204 return true;
205 }
206 });
207 if (!foldElement) {
208 return;
209 }
210 if (event.altKey) {
211 // 折叠所有子集
212 let hasFold = true;
213 Array.from(foldElement.children).find((ulElement) => {
214 if (ulElement.classList.contains("list")) {
215 const foldElement = Array.from(ulElement.children).find((listItemElement) => {
216 if (listItemElement.classList.contains("li")) {
217 if (listItemElement.getAttribute("fold") !== "1" && listItemElement.childElementCount > 3) {
218 hasFold = false;
219 return true;
220 }
221 }
222 });
223 if (foldElement) {
224 return true;
225 }
226 }
227 });
228 const doOperations: IOperation[] = [];
229 const undoOperations: IOperation[] = [];
230 Array.from(foldElement.children).forEach((ulElement) => {
231 if (ulElement.classList.contains("list")) {
232 Array.from(ulElement.children).forEach((listItemElement) => {
233 if (listItemElement.classList.contains("li")) {
234 if (hasFold) {
235 listItemElement.removeAttribute("fold");
236 } else if (listItemElement.childElementCount > 3) {
237 listItemElement.setAttribute("fold", "1");
238 }
239 const listId = listItemElement.getAttribute("data-node-id");
240 doOperations.push({
241 action: "setAttrs",
242 id: listId,
243 data: JSON.stringify({fold: hasFold ? "" : "1"})
244 });
245 undoOperations.push({
246 action: "setAttrs",
247 id: listId,
248 data: JSON.stringify({fold: hasFold ? "1" : ""})
249 });
250 }
251 });
252 }
253 });
254 transaction(protyle, doOperations, undoOperations);
255 buttonElement.removeAttribute("disabled");
256 } else {
257 const foldStatus = setFold(protyle, foldElement).fold;
258 if (foldStatus === 1) {
259 (buttonElement.firstElementChild as HTMLElement).style.transform = "";
260 } else if (foldStatus === 0) {
261 (buttonElement.firstElementChild as HTMLElement).style.transform = "rotate(90deg)";
262 }
263 }
264 hideElements(["select"], protyle);
265 window.siyuan.menus.menu.remove();
266 return;
267 }
268 const gutterRect = buttonElement.getBoundingClientRect();
269 if (buttonElement.dataset.type === "NodeAttributeViewRowMenu" || buttonElement.dataset.type === "NodeAttributeViewRow") {
270 const rowElement = Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-node-id="${buttonElement.dataset.nodeId}"] .av__row[data-id="${buttonElement.dataset.rowId}"]`)).find((item: HTMLElement) => {
271 if (!isInEmbedBlock(item)) {
272 return true;
273 }
274 });
275 if (!rowElement) {
276 return;
277 }
278 const blockElement = hasClosestBlock(rowElement);
279 if (!blockElement) {
280 return;
281 }
282 if (buttonElement.dataset.type === "NodeAttributeViewRow") {
283 const avID = blockElement.getAttribute("data-av-id");
284 const srcIDs = [Lute.NewNodeID()];
285 const previousID = event.altKey ? (rowElement.previousElementSibling.getAttribute("data-id") || "") : buttonElement.dataset.rowId;
286 const newUpdated = dayjs().format("YYYYMMDDHHmmss");
287 const groupID = rowElement.parentElement.getAttribute("data-group-id");
288 transaction(protyle, [{
289 action: "insertAttrViewBlock",
290 avID,
291 previousID,
292 srcs: [{
293 itemID: Lute.NewNodeID(),
294 id: srcIDs[0],
295 isDetached: true,
296 content: ""
297 }],
298 blockID: id,
299 groupID,
300 }, {
301 action: "doUpdateUpdated",
302 id,
303 data: newUpdated,
304 }], [{
305 action: "removeAttrViewBlock",
306 srcIDs,
307 avID,
308 }, {
309 action: "doUpdateUpdated",
310 id,
311 data: blockElement.getAttribute("updated")
312 }]);
313 insertAttrViewBlockAnimation({protyle, blockElement, srcIDs, previousId: previousID, groupID});
314 if (event.altKey) {
315 this.element.querySelectorAll("button").forEach(item => {
316 item.dataset.rowId = srcIDs[0];
317 });
318 }
319 blockElement.setAttribute("updated", newUpdated);
320 } else {
321 if (!protyle.disabled && event.shiftKey) {
322 const blockId = rowElement.querySelector('[data-dtype="block"] .av__celltext--ref')?.getAttribute("data-id");
323 if (blockId) {
324 fetchPost("/api/attr/getBlockAttrs", {id: blockId}, (response) => {
325 openFileAttr(response.data, "av", protyle);
326 });
327 return;
328 }
329 }
330 avContextmenu(protyle, rowElement as HTMLElement, {
331 x: gutterRect.left,
332 y: gutterRect.bottom,
333 w: gutterRect.width,
334 h: gutterRect.height,
335 isLeft: true
336 });
337 }
338 return;
339 }
340 if (isOnlyMeta(event)) {
341 if (protyle.options.backlinkData) {
342 checkFold(id, (zoomIn, action) => {
343 openFileById({
344 app: protyle.app,
345 id,
346 action,
347 zoomIn
348 });
349 });
350 } else {
351 zoomOut({protyle, id});
352 }
353 } else if (event.altKey) {
354 let foldElement: Element;
355 Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${id}"]`)).find(item => {
356 if (!isInEmbedBlock(item) && this.isMatchNode(item)) {
357 foldElement = item;
358 return true;
359 }
360 });
361 if (!foldElement) {
362 return;
363 }
364 if (buttonElement.getAttribute("data-type") === "NodeListItem" && foldElement.parentElement.getAttribute("data-node-id")) {
365 // 折叠同级
366 let hasFold = true;
367 Array.from(foldElement.parentElement.children).find((listItemElement) => {
368 if (listItemElement.classList.contains("li")) {
369 if (listItemElement.getAttribute("fold") !== "1" && listItemElement.childElementCount > 3) {
370 hasFold = false;
371 return true;
372 }
373 }
374 });
375 (buttonElement.parentElement.querySelector("[data-type='fold'] > svg") as HTMLElement).style.transform = hasFold ? "rotate(90deg)" : "";
376 const doOperations: IOperation[] = [];
377 const undoOperations: IOperation[] = [];
378 Array.from(foldElement.parentElement.children).find((listItemElement) => {
379 if (listItemElement.classList.contains("li")) {
380 if (hasFold) {
381 listItemElement.removeAttribute("fold");
382 } else if (listItemElement.childElementCount > 3) {
383 listItemElement.setAttribute("fold", "1");
384 }
385 const listId = listItemElement.getAttribute("data-node-id");
386 doOperations.push({
387 action: "setAttrs",
388 id: listId,
389 data: JSON.stringify({fold: hasFold ? "" : "1"})
390 });
391 undoOperations.push({
392 action: "setAttrs",
393 id: listId,
394 data: JSON.stringify({fold: hasFold ? "1" : ""})
395 });
396 }
397 });
398 transaction(protyle, doOperations, undoOperations);
399 } else {
400 const hasFold = setFold(protyle, foldElement).fold;
401 const foldArrowElement = buttonElement.parentElement.querySelector("[data-type='fold'] > svg") as HTMLElement;
402 if (hasFold !== -1 && foldArrowElement) {
403 foldArrowElement.style.transform = hasFold === 0 ? "rotate(90deg)" : "";
404 }
405 }
406 foldElement.classList.remove("protyle-wysiwyg--hl");
407 } else if (event.shiftKey && !protyle.disabled) {
408 // 不使用 window.siyuan.shiftIsPressed ,否则窗口未激活时按 Shift 点击块标无法打开属性面板 https://github.com/siyuan-note/siyuan/issues/15075
409 openAttr(protyle.wysiwyg.element.querySelector(`[data-node-id="${id}"]`), "bookmark", protyle);
410 } else if (!window.siyuan.ctrlIsPressed && !window.siyuan.altIsPressed && !window.siyuan.shiftIsPressed) {
411 this.renderMenu(protyle, buttonElement);
412 // https://ld246.com/article/1648433751993
413 if (!protyle.toolbar.range) {
414 protyle.toolbar.range = getEditorRange(protyle.wysiwyg.element.querySelector(`[data-node-id="${id}"]`) || protyle.wysiwyg.element.firstElementChild);
415 }
416 /// #if MOBILE
417 window.siyuan.menus.menu.fullscreen();
418 /// #else
419 window.siyuan.menus.menu.popup({x: gutterRect.left, y: gutterRect.bottom, isLeft: true});
420 const popoverElement = hasTopClosestByClassName(protyle.element, "block__popover", true);
421 window.siyuan.menus.menu.element.setAttribute("data-from", popoverElement ? popoverElement.dataset.level + "popover" : "app");
422 focusByRange(protyle.toolbar.range);
423 /// #endif
424 }
425 });
426 this.element.addEventListener("contextmenu", (event: MouseEvent & { target: HTMLInputElement }) => {
427 const buttonElement = hasClosestByTag(event.target, "BUTTON");
428 if (!buttonElement || buttonElement.getAttribute("data-type") === "fold") {
429 return;
430 }
431 if (!window.siyuan.ctrlIsPressed && !window.siyuan.altIsPressed && !window.siyuan.shiftIsPressed) {
432 hideTooltip();
433 clearSelect(["av", "img"], protyle.wysiwyg.element);
434 const gutterRect = buttonElement.getBoundingClientRect();
435 if (buttonElement.dataset.type === "NodeAttributeViewRowMenu") {
436 const rowElement = Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-node-id="${buttonElement.dataset.nodeId}"] .av__row[data-id="${buttonElement.dataset.rowId}"]`)).find((item: HTMLElement) => {
437 if (!isInEmbedBlock(item)) {
438 return true;
439 }
440 });
441 if (rowElement) {
442 avContextmenu(protyle, rowElement as HTMLElement, {
443 x: gutterRect.left,
444 y: gutterRect.bottom,
445 w: gutterRect.width,
446 h: gutterRect.height,
447 isLeft: true
448 });
449 }
450 } else if (buttonElement.dataset.type !== "NodeAttributeViewRow") {
451 this.renderMenu(protyle, buttonElement);
452 if (!protyle.toolbar.range) {
453 protyle.toolbar.range = getEditorRange(
454 protyle.wysiwyg.element.querySelector(`[data-node-id="${buttonElement.getAttribute("data-node-id")}"]`) ||
455 protyle.wysiwyg.element.firstElementChild);
456 }
457 /// #if MOBILE
458 window.siyuan.menus.menu.fullscreen();
459 /// #else
460 window.siyuan.menus.menu.popup({x: gutterRect.left, y: gutterRect.bottom, isLeft: true});
461 const popoverElement = hasTopClosestByClassName(protyle.element, "block__popover", true);
462 window.siyuan.menus.menu.element.setAttribute("data-from", popoverElement ? popoverElement.dataset.level + "popover" : "app");
463 focusByRange(protyle.toolbar.range);
464 /// #endif
465 }
466 }
467 event.preventDefault();
468 event.stopPropagation();
469 });
470 this.element.addEventListener("mouseleave", (event: MouseEvent & { target: HTMLInputElement }) => {
471 Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--hl, .av__row--hl")).forEach(item => {
472 item.classList.remove("protyle-wysiwyg--hl", "av__row--hl");
473 });
474 event.preventDefault();
475 event.stopPropagation();
476 });
477 // https://github.com/siyuan-note/siyuan/issues/12751
478 this.element.addEventListener("mousewheel", (event) => {
479 hideElements(["gutter"], protyle);
480 event.stopPropagation();
481 }, {passive: true});
482 }
483
484 public isMatchNode(item: Element) {
485 const itemRect = item.getBoundingClientRect();
486 // 原本为4,由于 https://github.com/siyuan-note/siyuan/issues/12166 改为 6
487 let gutterTop = this.element.getBoundingClientRect().top + 6;
488 if (itemRect.height < Math.floor(window.siyuan.config.editor.fontSize * 1.625) + 8) {
489 gutterTop = gutterTop - (itemRect.height - this.element.clientHeight) / 2;
490 }
491 return itemRect.top <= gutterTop && itemRect.bottom >= gutterTop;
492 }
493
494 private turnsOneInto(options: {
495 menuId?: string,
496 id: string,
497 icon: string,
498 label: string,
499 protyle: IProtyle,
500 nodeElement: Element,
501 accelerator?: string
502 type: string,
503 level?: number
504 }) {
505 return {
506 id: options.menuId,
507 icon: options.icon,
508 label: options.label,
509 accelerator: options.accelerator,
510 click() {
511 turnsOneInto(options);
512 }
513 };
514 }
515
516 private turnsIntoOne(options: {
517 menuId?: string,
518 accelerator?: string,
519 icon?: string,
520 label: string,
521 protyle: IProtyle,
522 selectsElement: Element[],
523 type: TTurnIntoOne,
524 level?: TTurnIntoOneSub,
525 }) {
526 return {
527 id: options.menuId,
528 icon: options.icon,
529 label: options.label,
530 accelerator: options.accelerator,
531 click() {
532 turnsIntoOneTransaction(options);
533 }
534 };
535 }
536
537 private turnsInto(options: {
538 menuId?: string,
539 icon?: string,
540 label: string,
541 protyle: IProtyle,
542 selectsElement: Element[],
543 type: TTurnInto,
544 level?: number,
545 isContinue?: boolean,
546 accelerator?: string,
547 }) {
548 return {
549 id: options.menuId,
550 icon: options.icon,
551 label: options.label,
552 accelerator: options.accelerator,
553 click() {
554 turnsIntoTransaction(options);
555 }
556 };
557 }
558
559 private showMobileAppearance(protyle: IProtyle) {
560 const toolbarElement = document.getElementById("keyboardToolbar");
561 const dynamicElements = toolbarElement.querySelectorAll("#keyboardToolbar .keyboard__dynamic");
562 dynamicElements[0].classList.add("fn__none");
563 dynamicElements[1].classList.remove("fn__none");
564 toolbarElement.querySelector('.keyboard__action[data-type="text"]').classList.add("protyle-toolbar__item--current");
565 toolbarElement.querySelector('.keyboard__action[data-type="done"] use').setAttribute("xlink:href", "#iconCloseRound");
566 toolbarElement.classList.remove("fn__none");
567 const oldScrollTop = protyle.contentElement.scrollTop + 333.5; // toolbarElement.clientHeight
568 renderTextMenu(protyle, toolbarElement);
569 showKeyboardToolbarUtil(oldScrollTop);
570 }
571
572 public renderMultipleMenu(protyle: IProtyle, selectsElement: Element[]) {
573 let isList = false;
574 let isContinue = false;
575 selectsElement.find((item, index) => {
576 if (item.classList.contains("li")) {
577 isList = true;
578 return true;
579 }
580 if (item.nextElementSibling && selectsElement[index + 1] &&
581 item.nextElementSibling === selectsElement[index + 1]) {
582 isContinue = true;
583 } else if (index !== selectsElement.length - 1) {
584 isContinue = false;
585 return true;
586 }
587 });
588 if (!isList && !protyle.disabled) {
589 const turnIntoSubmenu: IMenu[] = [];
590 if (isContinue) {
591 turnIntoSubmenu.push(this.turnsIntoOne({
592 menuId: "list",
593 icon: "iconList",
594 label: window.siyuan.languages.list,
595 protyle,
596 accelerator: window.siyuan.config.keymap.editor.insert.list.custom,
597 selectsElement,
598 type: "Blocks2ULs"
599 }));
600 turnIntoSubmenu.push(this.turnsIntoOne({
601 menuId: "orderedList",
602 icon: "iconOrderedList",
603 label: window.siyuan.languages["ordered-list"],
604 accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom,
605 protyle,
606 selectsElement,
607 type: "Blocks2OLs"
608 }));
609 turnIntoSubmenu.push(this.turnsIntoOne({
610 menuId: "check",
611 icon: "iconCheck",
612 label: window.siyuan.languages.check,
613 accelerator: window.siyuan.config.keymap.editor.insert.check.custom,
614 protyle,
615 selectsElement,
616 type: "Blocks2TLs"
617 }));
618 turnIntoSubmenu.push(this.turnsIntoOne({
619 menuId: "quote",
620 icon: "iconQuote",
621 label: window.siyuan.languages.quote,
622 accelerator: window.siyuan.config.keymap.editor.insert.quote.custom,
623 protyle,
624 selectsElement,
625 type: "Blocks2Blockquote"
626 }));
627 }
628 turnIntoSubmenu.push(this.turnsInto({
629 menuId: "paragraph",
630 icon: "iconParagraph",
631 label: window.siyuan.languages.paragraph,
632 accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom,
633 protyle,
634 selectsElement,
635 type: "Blocks2Ps",
636 isContinue
637 }));
638 turnIntoSubmenu.push(this.turnsInto({
639 menuId: "heading1",
640 icon: "iconH1",
641 label: window.siyuan.languages.heading1,
642 accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom,
643 protyle,
644 selectsElement,
645 level: 1,
646 type: "Blocks2Hs",
647 isContinue
648 }));
649 turnIntoSubmenu.push(this.turnsInto({
650 menuId: "heading2",
651 icon: "iconH2",
652 label: window.siyuan.languages.heading2,
653 accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom,
654 protyle,
655 selectsElement,
656 level: 2,
657 type: "Blocks2Hs",
658 isContinue
659 }));
660 turnIntoSubmenu.push(this.turnsInto({
661 menuId: "heading3",
662 icon: "iconH3",
663 label: window.siyuan.languages.heading3,
664 accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom,
665 protyle,
666 selectsElement,
667 level: 3,
668 type: "Blocks2Hs",
669 isContinue
670 }));
671 turnIntoSubmenu.push(this.turnsInto({
672 menuId: "heading4",
673 icon: "iconH4",
674 label: window.siyuan.languages.heading4,
675 accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom,
676 protyle,
677 selectsElement,
678 level: 4,
679 type: "Blocks2Hs",
680 isContinue
681 }));
682 turnIntoSubmenu.push(this.turnsInto({
683 menuId: "heading5",
684 icon: "iconH5",
685 label: window.siyuan.languages.heading5,
686 accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom,
687 protyle,
688 selectsElement,
689 level: 5,
690 type: "Blocks2Hs",
691 isContinue
692 }));
693 turnIntoSubmenu.push(this.turnsInto({
694 menuId: "heading6",
695 icon: "iconH6",
696 label: window.siyuan.languages.heading6,
697 accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom,
698 protyle,
699 selectsElement,
700 level: 6,
701 type: "Blocks2Hs",
702 isContinue
703 }));
704 window.siyuan.menus.menu.append(new MenuItem({
705 id: "turnInto",
706 icon: "iconRefresh",
707 label: window.siyuan.languages.turnInto,
708 type: "submenu",
709 submenu: turnIntoSubmenu
710 }).element);
711 if (isContinue && !(selectsElement[0].parentElement.classList.contains("sb") &&
712 selectsElement.length + 1 === selectsElement[0].parentElement.childElementCount)) {
713 window.siyuan.menus.menu.append(new MenuItem({
714 id: "mergeSuperBlock",
715 icon: "iconSuper",
716 label: window.siyuan.languages.merge + " " + window.siyuan.languages.superBlock,
717 type: "submenu",
718 submenu: [this.turnsIntoOne({
719 menuId: "hLayout",
720 label: window.siyuan.languages.hLayout,
721 accelerator: window.siyuan.config.keymap.editor.general.hLayout.custom,
722 icon: "iconSplitLR",
723 protyle,
724 selectsElement,
725 type: "BlocksMergeSuperBlock",
726 level: "col"
727 }), this.turnsIntoOne({
728 menuId: "vLayout",
729 label: window.siyuan.languages.vLayout,
730 accelerator: window.siyuan.config.keymap.editor.general.vLayout.custom,
731 icon: "iconSplitTB",
732 protyle,
733 selectsElement,
734 type: "BlocksMergeSuperBlock",
735 level: "row"
736 })]
737 }).element);
738 }
739 }
740 if (!protyle.disabled) {
741 window.siyuan.menus.menu.append(new MenuItem({
742 id: "ai",
743 icon: "iconSparkles",
744 label: window.siyuan.languages.ai,
745 accelerator: window.siyuan.config.keymap.editor.general.ai.custom,
746 click() {
747 AIActions(selectsElement, protyle);
748 }
749 }).element);
750 }
751 const copyMenu: IMenu[] = (copySubMenu(Array.from(selectsElement).map(item => item.getAttribute("data-node-id")), true, selectsElement[0]) as IMenu[]).concat([{
752 id: "copyPlainText",
753 iconHTML: "",
754 label: window.siyuan.languages.copyPlainText,
755 accelerator: window.siyuan.config.keymap.editor.general.copyPlainText.custom,
756 click() {
757 let html = "";
758 selectsElement.forEach((item: HTMLElement) => {
759 html += getPlainText(item) + "\n";
760 });
761 copyPlainText(html.trimEnd());
762 focusBlock(selectsElement[0]);
763 }
764 }, {
765 id: "copy",
766 iconHTML: "",
767 label: window.siyuan.languages.copy,
768 accelerator: "⌘C",
769 click() {
770 if (isNotEditBlock(selectsElement[0])) {
771 focusBlock(selectsElement[0]);
772 } else {
773 focusByRange(getEditorRange(selectsElement[0]));
774 }
775 document.execCommand("copy");
776 }
777 }]);
778 const copyTextRefMenu = this.genCopyTextRef(selectsElement);
779 if (copyTextRefMenu) {
780 copyMenu.splice(7, 0, copyTextRefMenu);
781 }
782 if (!protyle.disabled) {
783 copyMenu.push({
784 id: "duplicate",
785 iconHTML: "",
786 label: window.siyuan.languages.duplicate,
787 accelerator: window.siyuan.config.keymap.editor.general.duplicate.custom,
788 click() {
789 duplicateBlock(selectsElement, protyle);
790 }
791 });
792 }
793 window.siyuan.menus.menu.append(new MenuItem({
794 id: "copy",
795 label: window.siyuan.languages.copy,
796 icon: "iconCopy",
797 type: "submenu",
798 submenu: copyMenu,
799 }).element);
800 if (!protyle.disabled) {
801 window.siyuan.menus.menu.append(new MenuItem({
802 id: "cut",
803 label: window.siyuan.languages.cut,
804 accelerator: "⌘X",
805 icon: "iconCut",
806 click: () => {
807 focusBlock(selectsElement[0]);
808 document.execCommand("cut");
809 }
810 }).element);
811 window.siyuan.menus.menu.append(new MenuItem({
812 id: "move",
813 label: window.siyuan.languages.move,
814 accelerator: window.siyuan.config.keymap.general.move.custom,
815 icon: "iconMove",
816 click: () => {
817 movePathTo((toPath) => {
818 hintMoveBlock(toPath[0], selectsElement, protyle);
819 });
820 }
821 }).element);
822 window.siyuan.menus.menu.append(new MenuItem({
823 id: "addToDatabase",
824 label: window.siyuan.languages.addToDatabase,
825 accelerator: window.siyuan.config.keymap.general.addToDatabase.custom,
826 icon: "iconDatabase",
827 click: () => {
828 addEditorToDatabase(protyle, getEditorRange(selectsElement[0]));
829 }
830 }).element);
831 window.siyuan.menus.menu.append(new MenuItem({
832 id: "delete",
833 label: window.siyuan.languages.delete,
834 icon: "iconTrashcan",
835 accelerator: "⌫",
836 click: () => {
837 protyle.breadcrumb?.hide();
838 removeBlock(protyle, selectsElement[0], getEditorRange(selectsElement[0]), "Backspace");
839 }
840 }).element);
841
842 window.siyuan.menus.menu.append(new MenuItem({id: "separator_appearance", type: "separator"}).element);
843 const appearanceElement = new MenuItem({
844 id: "appearance",
845 label: window.siyuan.languages.appearance,
846 icon: "iconFont",
847 accelerator: window.siyuan.config.keymap.editor.insert.appearance.custom,
848 click: () => {
849 /// #if MOBILE
850 this.showMobileAppearance(protyle);
851 /// #else
852 protyle.toolbar.element.classList.add("fn__none");
853 protyle.toolbar.subElement.innerHTML = "";
854 protyle.toolbar.subElement.style.width = "";
855 protyle.toolbar.subElement.style.padding = "";
856 protyle.toolbar.subElement.append(appearanceMenu(protyle, selectsElement));
857 protyle.toolbar.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
858 protyle.toolbar.subElement.classList.remove("fn__none");
859 protyle.toolbar.subElementCloseCB = undefined;
860 const position = selectsElement[0].getBoundingClientRect();
861 setPosition(protyle.toolbar.subElement, position.left, position.top);
862 /// #endif
863 }
864 }).element;
865 window.siyuan.menus.menu.append(appearanceElement);
866 if (!isMobile()) {
867 appearanceElement.lastElementChild.classList.add("b3-menu__submenu--row");
868 }
869 this.genAlign(selectsElement, protyle);
870 this.genWidths(selectsElement, protyle);
871 // this.genHeights(selectsElement, protyle);
872 }
873 if (!window.siyuan.config.readonly) {
874 window.siyuan.menus.menu.append(new MenuItem({
875 id: "separator_quickMakeCard",
876 type: "separator"
877 }).element);
878 const allCardsMade = !selectsElement.some(item => !item.hasAttribute(Constants.CUSTOM_RIFF_DECKS) && item.getAttribute("data-type") !== "NodeThematicBreak");
879 window.siyuan.menus.menu.append(new MenuItem({
880 id: allCardsMade ? "removeCard" : "quickMakeCard",
881 label: allCardsMade ? window.siyuan.languages.removeCard : window.siyuan.languages.quickMakeCard,
882 accelerator: window.siyuan.config.keymap.editor.general.quickMakeCard.custom,
883 icon: "iconRiffCard",
884 click() {
885 quickMakeCard(protyle, selectsElement);
886 }
887 }).element);
888 window.siyuan.menus.menu.append(new MenuItem({
889 id: "addToDeck",
890 label: window.siyuan.languages.addToDeck,
891 icon: "iconRiffCard",
892 ignore: !window.siyuan.config.flashcard.deck,
893 click() {
894 const ids: string[] = [];
895 selectsElement.forEach(item => {
896 if (item.getAttribute("data-type") === "NodeThematicBreak") {
897 return;
898 }
899 ids.push(item.getAttribute("data-node-id"));
900 });
901 makeCard(protyle.app, ids);
902 }
903 }).element);
904 }
905
906 if (protyle?.app?.plugins) {
907 emitOpenMenu({
908 plugins: protyle.app.plugins,
909 type: "click-blockicon",
910 detail: {
911 protyle,
912 blockElements: selectsElement,
913 },
914 separatorPosition: "top",
915 });
916 }
917
918 return window.siyuan.menus.menu;
919 }
920
921 public renderMenu(protyle: IProtyle, buttonElement: Element) {
922 if (!buttonElement) {
923 return;
924 }
925 hideElements(["util", "toolbar", "hint"], protyle);
926 window.siyuan.menus.menu.remove();
927 if (isMobile()) {
928 activeBlur();
929 }
930 const id = buttonElement.getAttribute("data-node-id");
931 const selectsElement = protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select");
932 if (selectsElement.length > 1) {
933 window.siyuan.menus.menu.element.setAttribute("data-name", Constants.MENU_BLOCK_MULTI);
934 const match = Array.from(selectsElement).find(item => {
935 if (id === item.getAttribute("data-node-id")) {
936 return true;
937 }
938 });
939 if (match) {
940 return this.renderMultipleMenu(protyle, Array.from(selectsElement));
941 }
942 } else {
943 window.siyuan.menus.menu.element.setAttribute("data-name", Constants.MENU_BLOCK_SINGLE);
944 }
945
946 let nodeElement: Element;
947 if (buttonElement.tagName === "BUTTON") {
948 Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${id}"]`)).find(item => {
949 if (!isInEmbedBlock(item) && this.isMatchNode(item)) {
950 nodeElement = item;
951 return true;
952 }
953 });
954 } else {
955 nodeElement = buttonElement;
956 }
957 if (!nodeElement) {
958 return;
959 }
960 const type = nodeElement.getAttribute("data-type");
961 const subType = nodeElement.getAttribute("data-subtype");
962 const turnIntoSubmenu: IMenu[] = [];
963 hideElements(["select"], protyle);
964 nodeElement.classList.add("protyle-wysiwyg--select");
965 countBlockWord([id], protyle.block.rootID);
966 // "heading1-6", "list", "ordered-list", "check", "quote", "code", "table", "line", "math", "paragraph"
967 if (type === "NodeParagraph" && !protyle.disabled) {
968 turnIntoSubmenu.push(this.turnsIntoOne({
969 menuId: "list",
970 icon: "iconList",
971 label: window.siyuan.languages.list,
972 accelerator: window.siyuan.config.keymap.editor.insert.list.custom,
973 protyle,
974 selectsElement: [nodeElement],
975 type: "Blocks2ULs"
976 }));
977 turnIntoSubmenu.push(this.turnsIntoOne({
978 menuId: "orderedList",
979 icon: "iconOrderedList",
980 label: window.siyuan.languages["ordered-list"],
981 accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom,
982 protyle,
983 selectsElement: [nodeElement],
984 type: "Blocks2OLs"
985 }));
986 turnIntoSubmenu.push(this.turnsIntoOne({
987 menuId: "check",
988 icon: "iconCheck",
989 label: window.siyuan.languages.check,
990 accelerator: window.siyuan.config.keymap.editor.insert.check.custom,
991 protyle,
992 selectsElement: [nodeElement],
993 type: "Blocks2TLs"
994 }));
995 turnIntoSubmenu.push(this.turnsIntoOne({
996 menuId: "quote",
997 icon: "iconQuote",
998 label: window.siyuan.languages.quote,
999 accelerator: window.siyuan.config.keymap.editor.insert.quote.custom,
1000 protyle,
1001 selectsElement: [nodeElement],
1002 type: "Blocks2Blockquote"
1003 }));
1004 turnIntoSubmenu.push(this.turnsInto({
1005 menuId: "heading1",
1006 icon: "iconH1",
1007 label: window.siyuan.languages.heading1,
1008 accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom,
1009 protyle,
1010 selectsElement: [nodeElement],
1011 level: 1,
1012 type: "Blocks2Hs",
1013 }));
1014 turnIntoSubmenu.push(this.turnsInto({
1015 menuId: "heading2",
1016 icon: "iconH2",
1017 label: window.siyuan.languages.heading2,
1018 accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom,
1019 protyle,
1020 selectsElement: [nodeElement],
1021 level: 2,
1022 type: "Blocks2Hs",
1023 }));
1024 turnIntoSubmenu.push(this.turnsInto({
1025 menuId: "heading3",
1026 icon: "iconH3",
1027 label: window.siyuan.languages.heading3,
1028 accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom,
1029 protyle,
1030 selectsElement: [nodeElement],
1031 level: 3,
1032 type: "Blocks2Hs",
1033 }));
1034 turnIntoSubmenu.push(this.turnsInto({
1035 menuId: "heading4",
1036 icon: "iconH4",
1037 label: window.siyuan.languages.heading4,
1038 accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom,
1039 protyle,
1040 selectsElement: [nodeElement],
1041 level: 4,
1042 type: "Blocks2Hs",
1043 }));
1044 turnIntoSubmenu.push(this.turnsInto({
1045 menuId: "heading5",
1046 icon: "iconH5",
1047 label: window.siyuan.languages.heading5,
1048 accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom,
1049 protyle,
1050 selectsElement: [nodeElement],
1051 level: 5,
1052 type: "Blocks2Hs",
1053 }));
1054 turnIntoSubmenu.push(this.turnsInto({
1055 menuId: "heading6",
1056 icon: "iconH6",
1057 label: window.siyuan.languages.heading6,
1058 accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom,
1059 protyle,
1060 selectsElement: [nodeElement],
1061 level: 6,
1062 type: "Blocks2Hs",
1063 }));
1064 } else if (type === "NodeHeading" && !protyle.disabled) {
1065 turnIntoSubmenu.push(this.turnsInto({
1066 menuId: "paragraph",
1067 icon: "iconParagraph",
1068 label: window.siyuan.languages.paragraph,
1069 accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom,
1070 protyle,
1071 selectsElement: [nodeElement],
1072 type: "Blocks2Ps",
1073 }));
1074 turnIntoSubmenu.push(this.turnsIntoOne({
1075 menuId: "quote",
1076 icon: "iconQuote",
1077 label: window.siyuan.languages.quote,
1078 accelerator: window.siyuan.config.keymap.editor.insert.quote.custom,
1079 protyle,
1080 selectsElement: [nodeElement],
1081 type: "Blocks2Blockquote"
1082 }));
1083 if (subType !== "h1") {
1084 turnIntoSubmenu.push(this.turnsInto({
1085 menuId: "heading1",
1086 icon: "iconH1",
1087 label: window.siyuan.languages.heading1,
1088 accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom,
1089 protyle,
1090 selectsElement: [nodeElement],
1091 level: 1,
1092 type: "Blocks2Hs",
1093 }));
1094 }
1095 if (subType !== "h2") {
1096 turnIntoSubmenu.push(this.turnsInto({
1097 menuId: "heading2",
1098 icon: "iconH2",
1099 label: window.siyuan.languages.heading2,
1100 accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom,
1101 protyle,
1102 selectsElement: [nodeElement],
1103 level: 2,
1104 type: "Blocks2Hs",
1105 }));
1106 }
1107 if (subType !== "h3") {
1108 turnIntoSubmenu.push(this.turnsInto({
1109 menuId: "heading3",
1110 icon: "iconH3",
1111 label: window.siyuan.languages.heading3,
1112 accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom,
1113 protyle,
1114 selectsElement: [nodeElement],
1115 level: 3,
1116 type: "Blocks2Hs",
1117 }));
1118 }
1119 if (subType !== "h4") {
1120 turnIntoSubmenu.push(this.turnsInto({
1121 menuId: "heading4",
1122 icon: "iconH4",
1123 label: window.siyuan.languages.heading4,
1124 accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom,
1125 protyle,
1126 selectsElement: [nodeElement],
1127 level: 4,
1128 type: "Blocks2Hs",
1129 }));
1130 }
1131 if (subType !== "h5") {
1132 turnIntoSubmenu.push(this.turnsInto({
1133 menuId: "heading5",
1134 icon: "iconH5",
1135 label: window.siyuan.languages.heading5,
1136 accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom,
1137 protyle,
1138 selectsElement: [nodeElement],
1139 level: 5,
1140 type: "Blocks2Hs",
1141 }));
1142 }
1143 if (subType !== "h6") {
1144 turnIntoSubmenu.push(this.turnsInto({
1145 menuId: "heading6",
1146 icon: "iconH6",
1147 label: window.siyuan.languages.heading6,
1148 accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom,
1149 protyle,
1150 selectsElement: [nodeElement],
1151 level: 6,
1152 type: "Blocks2Hs",
1153 }));
1154 }
1155 } else if (type === "NodeList" && !protyle.disabled) {
1156 turnIntoSubmenu.push(this.turnsOneInto({
1157 menuId: "paragraph",
1158 id,
1159 icon: "iconParagraph",
1160 label: window.siyuan.languages.paragraph,
1161 accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom,
1162 protyle,
1163 nodeElement,
1164 type: "CancelList"
1165 }));
1166 turnIntoSubmenu.push(this.turnsIntoOne({
1167 menuId: "quote",
1168 icon: "iconQuote",
1169 label: window.siyuan.languages.quote,
1170 accelerator: window.siyuan.config.keymap.editor.insert.quote.custom,
1171 protyle,
1172 selectsElement: [nodeElement],
1173 type: "Blocks2Blockquote"
1174 }));
1175 if (nodeElement.getAttribute("data-subtype") === "o") {
1176 turnIntoSubmenu.push(this.turnsOneInto({
1177 menuId: "list",
1178 id,
1179 icon: "iconList",
1180 label: window.siyuan.languages.list,
1181 accelerator: window.siyuan.config.keymap.editor.insert.list.custom,
1182 protyle,
1183 nodeElement,
1184 type: "OL2UL"
1185 }));
1186 turnIntoSubmenu.push(this.turnsOneInto({
1187 menuId: "check",
1188 id,
1189 icon: "iconCheck",
1190 label: window.siyuan.languages.check,
1191 accelerator: window.siyuan.config.keymap.editor.insert.check.custom,
1192 protyle,
1193 nodeElement,
1194 type: "UL2TL"
1195 }));
1196 } else if (nodeElement.getAttribute("data-subtype") === "t") {
1197 turnIntoSubmenu.push(this.turnsOneInto({
1198 menuId: "list",
1199 id,
1200 icon: "iconList",
1201 label: window.siyuan.languages.list,
1202 accelerator: window.siyuan.config.keymap.editor.insert.list.custom,
1203 protyle,
1204 nodeElement,
1205 type: "TL2UL"
1206 }));
1207 turnIntoSubmenu.push(this.turnsOneInto({
1208 menuId: "orderedList",
1209 id,
1210 icon: "iconOrderedList",
1211 label: window.siyuan.languages["ordered-list"],
1212 accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom,
1213 protyle,
1214 nodeElement,
1215 type: "TL2OL"
1216 }));
1217 } else {
1218 turnIntoSubmenu.push(this.turnsOneInto({
1219 menuId: "orderedList",
1220 id,
1221 icon: "iconOrderedList",
1222 label: window.siyuan.languages["ordered-list"],
1223 accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom,
1224 protyle,
1225 nodeElement,
1226 type: "UL2OL"
1227 }));
1228 turnIntoSubmenu.push(this.turnsOneInto({
1229 menuId: "check",
1230 id,
1231 icon: "iconCheck",
1232 label: window.siyuan.languages.check,
1233 accelerator: window.siyuan.config.keymap.editor.insert.check.custom,
1234 protyle,
1235 nodeElement,
1236 type: "OL2TL"
1237 }));
1238 }
1239 } else if (type === "NodeBlockquote" && !protyle.disabled) {
1240 turnIntoSubmenu.push(this.turnsOneInto({
1241 menuId: "paragraph",
1242 id,
1243 icon: "iconParagraph",
1244 label: window.siyuan.languages.paragraph,
1245 accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom,
1246 protyle,
1247 nodeElement,
1248 type: "CancelBlockquote"
1249 }));
1250 }
1251 if (turnIntoSubmenu.length > 0 && !protyle.disabled) {
1252 window.siyuan.menus.menu.append(new MenuItem({
1253 id: "turnInto",
1254 icon: "iconRefresh",
1255 label: window.siyuan.languages.turnInto,
1256 type: "submenu",
1257 submenu: turnIntoSubmenu
1258 }).element);
1259 }
1260 if (!protyle.disabled && !nodeElement.classList.contains("hr")) {
1261 window.siyuan.menus.menu.append(new MenuItem({
1262 id: "ai",
1263 icon: "iconSparkles",
1264 label: window.siyuan.languages.ai,
1265 accelerator: window.siyuan.config.keymap.editor.general.ai.custom,
1266 click() {
1267 AIActions([nodeElement], protyle);
1268 }
1269 }).element);
1270 }
1271
1272 const copyMenu = (copySubMenu([id], true, nodeElement) as IMenu[]).concat([{
1273 id: "copyPlainText",
1274 iconHTML: "",
1275 label: window.siyuan.languages.copyPlainText,
1276 accelerator: window.siyuan.config.keymap.editor.general.copyPlainText.custom,
1277 click() {
1278 copyPlainText(getPlainText(nodeElement as HTMLElement).trimEnd());
1279 focusBlock(nodeElement);
1280 }
1281 }, {
1282 id: type === "NodeAttributeView" ? "copyMirror" : "copy",
1283 iconHTML: "",
1284 label: type === "NodeAttributeView" ? window.siyuan.languages.copyMirror : window.siyuan.languages.copy,
1285 accelerator: "⌘C",
1286 click() {
1287 if (isNotEditBlock(nodeElement)) {
1288 focusBlock(nodeElement);
1289 } else {
1290 focusByRange(getEditorRange(nodeElement));
1291 }
1292 document.execCommand("copy");
1293 }
1294 }]);
1295 const copyTextRefMenu = this.genCopyTextRef([nodeElement]);
1296 if (copyTextRefMenu) {
1297 copyMenu.splice(7, 0, copyTextRefMenu);
1298 }
1299 if (type === "NodeAttributeView") {
1300 copyMenu.splice(6, 0, {
1301 iconHTML: "",
1302 label: window.siyuan.languages.copyAVID,
1303 click() {
1304 writeText(nodeElement.getAttribute("data-av-id"));
1305 }
1306 });
1307 if (!protyle.disabled) {
1308 copyMenu.push({
1309 id: "duplicateMirror",
1310 iconHTML: "",
1311 label: window.siyuan.languages.duplicateMirror,
1312 accelerator: window.siyuan.config.keymap.editor.general.duplicate.custom,
1313 click() {
1314 duplicateBlock([nodeElement], protyle);
1315 }
1316 });
1317 copyMenu.push({
1318 id: "duplicateCompletely",
1319 iconHTML: "",
1320 label: window.siyuan.languages.duplicateCompletely,
1321 accelerator: window.siyuan.config.keymap.editor.general.duplicateCompletely.custom,
1322 click() {
1323 duplicateCompletely(protyle, nodeElement as HTMLElement);
1324 }
1325 });
1326 }
1327 } else if (!protyle.disabled) {
1328 copyMenu.push({
1329 id: "duplicate",
1330 iconHTML: "",
1331 label: window.siyuan.languages.duplicate,
1332 accelerator: window.siyuan.config.keymap.editor.general.duplicate.custom,
1333 click() {
1334 duplicateBlock([nodeElement], protyle);
1335 }
1336 });
1337 }
1338 window.siyuan.menus.menu.append(new MenuItem({
1339 id: "copy",
1340 icon: "iconCopy",
1341 label: window.siyuan.languages.copy,
1342 type: "submenu",
1343 submenu: copyMenu
1344 }).element);
1345 if (!protyle.disabled) {
1346 window.siyuan.menus.menu.append(new MenuItem({
1347 id: "cut",
1348 icon: "iconCut",
1349 label: window.siyuan.languages.cut,
1350 accelerator: "⌘X",
1351 click: () => {
1352 focusBlock(nodeElement);
1353 document.execCommand("cut");
1354 }
1355 }).element);
1356 window.siyuan.menus.menu.append(new MenuItem({
1357 id: "move",
1358 icon: "iconMove",
1359 label: window.siyuan.languages.move,
1360 accelerator: window.siyuan.config.keymap.general.move.custom,
1361 click: () => {
1362 movePathTo((toPath) => {
1363 hintMoveBlock(toPath[0], [nodeElement], protyle);
1364 });
1365 }
1366 }).element);
1367 window.siyuan.menus.menu.append(new MenuItem({
1368 id: "addToDatabase",
1369 icon: "iconDatabase",
1370 label: window.siyuan.languages.addToDatabase,
1371 accelerator: window.siyuan.config.keymap.general.addToDatabase.custom,
1372 click: () => {
1373 addEditorToDatabase(protyle, getEditorRange(nodeElement));
1374 }
1375 }).element);
1376 window.siyuan.menus.menu.append(new MenuItem({
1377 id: "delete",
1378 icon: "iconTrashcan",
1379 label: window.siyuan.languages.delete,
1380 accelerator: "⌫",
1381 click: () => {
1382 protyle.breadcrumb?.hide();
1383 removeBlock(protyle, nodeElement, getEditorRange(nodeElement), "Backspace");
1384 }
1385 }).element);
1386 }
1387 if (type === "NodeSuperBlock" && !protyle.disabled) {
1388 window.siyuan.menus.menu.append(new MenuItem({
1389 id: "separator_cancelSuperBlock",
1390 type: "separator"
1391 }).element);
1392 const isCol = nodeElement.getAttribute("data-sb-layout") === "col";
1393 window.siyuan.menus.menu.append(new MenuItem({
1394 id: "cancelSuperBlock",
1395 label: window.siyuan.languages.cancel + " " + window.siyuan.languages.superBlock,
1396 accelerator: window.siyuan.config.keymap.editor.general[isCol ? "hLayout" : "vLayout"].custom,
1397 async click() {
1398 const sbData = await cancelSB(protyle, nodeElement);
1399 transaction(protyle, sbData.doOperations, sbData.undoOperations);
1400 focusBlock(protyle.wysiwyg.element.querySelector(`[data-node-id="${sbData.previousId}"]`));
1401 hideElements(["gutter"], protyle);
1402 }
1403 }).element);
1404 window.siyuan.menus.menu.append(new MenuItem({
1405 id: "turnInto" + (isCol ? "VLayout" : "HLayout"),
1406 accelerator: window.siyuan.config.keymap.editor.general[isCol ? "vLayout" : "hLayout"].custom,
1407 label: window.siyuan.languages.turnInto + " " + window.siyuan.languages[isCol ? "vLayout" : "hLayout"],
1408 click() {
1409 const oldHTML = nodeElement.outerHTML;
1410 if (isCol) {
1411 nodeElement.setAttribute("data-sb-layout", "row");
1412 } else {
1413 nodeElement.setAttribute("data-sb-layout", "col");
1414 }
1415 nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
1416 updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML);
1417 focusByRange(protyle.toolbar.range);
1418 hideElements(["gutter"], protyle);
1419 }
1420 }).element);
1421 } else if (type === "NodeCodeBlock" && !protyle.disabled && !nodeElement.getAttribute("data-subtype")) {
1422 window.siyuan.menus.menu.append(new MenuItem({id: "separator_code", type: "separator"}).element);
1423 const linewrap = nodeElement.getAttribute("linewrap");
1424 const ligatures = nodeElement.getAttribute("ligatures");
1425 const linenumber = nodeElement.getAttribute("linenumber");
1426
1427 window.siyuan.menus.menu.append(new MenuItem({
1428 id: "code",
1429 type: "submenu",
1430 icon: "iconCode",
1431 label: window.siyuan.languages.code,
1432 submenu: [{
1433 id: "md31",
1434 iconHTML: "",
1435 label: `<div class="fn__flex" style="margin-bottom: 4px"><span>${window.siyuan.languages.md31}</span><span class="fn__space fn__flex-1"></span>
1436<input type="checkbox" class="b3-switch fn__flex-center"${linewrap === "true" ? " checked" : ((window.siyuan.config.editor.codeLineWrap && linewrap !== "false") ? " checked" : "")}></div>`,
1437 bind(element) {
1438 element.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
1439 const inputElement = element.querySelector("input");
1440 if (event.target.tagName !== "INPUT") {
1441 inputElement.checked = !inputElement.checked;
1442 }
1443 nodeElement.setAttribute("linewrap", inputElement.checked.toString());
1444 nodeElement.querySelector(".hljs").removeAttribute("data-render");
1445 highlightRender(nodeElement);
1446 fetchPost("/api/attr/setBlockAttrs", {
1447 id,
1448 attrs: {linewrap: inputElement.checked.toString()}
1449 });
1450 window.siyuan.menus.menu.remove();
1451 });
1452 }
1453 }, {
1454 id: "md2",
1455 iconHTML: "",
1456 label: `<div class="fn__flex" style="margin-bottom: 4px"><span>${window.siyuan.languages.md2}</span><span class="fn__space fn__flex-1"></span>
1457<input type="checkbox" class="b3-switch fn__flex-center"${ligatures === "true" ? " checked" : ((window.siyuan.config.editor.codeLigatures && ligatures !== "false") ? " checked" : "")}></div>`,
1458 bind(element) {
1459 element.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
1460 const inputElement = element.querySelector("input");
1461 if (event.target.tagName !== "INPUT") {
1462 inputElement.checked = !inputElement.checked;
1463 }
1464 nodeElement.setAttribute("ligatures", inputElement.checked.toString());
1465 nodeElement.querySelector(".hljs").removeAttribute("data-render");
1466 highlightRender(nodeElement);
1467 fetchPost("/api/attr/setBlockAttrs", {
1468 id,
1469 attrs: {ligatures: inputElement.checked.toString()}
1470 });
1471 window.siyuan.menus.menu.remove();
1472 });
1473 }
1474 }, {
1475 id: "md27",
1476 iconHTML: "",
1477 label: `<div class="fn__flex" style="margin-bottom: 4px"><span>${window.siyuan.languages.md27}</span><span class="fn__space fn__flex-1"></span>
1478<input type="checkbox" class="b3-switch fn__flex-center"${linenumber === "true" ? " checked" : ((window.siyuan.config.editor.codeSyntaxHighlightLineNum && linenumber !== "false") ? " checked" : "")}></div>`,
1479 bind(element) {
1480 element.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
1481 const inputElement = element.querySelector("input");
1482 if (event.target.tagName !== "INPUT") {
1483 inputElement.checked = !inputElement.checked;
1484 }
1485 nodeElement.setAttribute("linenumber", inputElement.checked.toString());
1486 nodeElement.querySelector(".hljs").removeAttribute("data-render");
1487 highlightRender(nodeElement);
1488 fetchPost("/api/attr/setBlockAttrs", {
1489 id,
1490 attrs: {linenumber: inputElement.checked.toString()}
1491 });
1492 window.siyuan.menus.menu.remove();
1493 });
1494 }
1495 }]
1496 }).element);
1497 } else if (type === "NodeCodeBlock" && !protyle.disabled && ["echarts", "mindmap"].includes(nodeElement.getAttribute("data-subtype"))) {
1498 window.siyuan.menus.menu.append(new MenuItem({id: "separator_chart", type: "separator"}).element);
1499 const height = (nodeElement as HTMLElement).style.height;
1500 let html = nodeElement.outerHTML;
1501 window.siyuan.menus.menu.append(new MenuItem({
1502 id: "chart",
1503 label: window.siyuan.languages.chart,
1504 icon: "iconCode",
1505 submenu: [{
1506 id: "height",
1507 iconHTML: "",
1508 type: "readonly",
1509 label: `<div class="fn__flex"><input class="b3-text-field fn__flex-1" value="${height ? parseInt(height) : "420"}" step="1" min="148" style="margin: 4px 8px 4px 0" placeholder="${window.siyuan.languages.height}"><span class="fn__flex-center">px</span></div>`,
1510 bind: (element) => {
1511 element.querySelector("input").addEventListener("change", (event) => {
1512 const newHeight = ((event.target as HTMLInputElement).value || "420") + "px";
1513 (nodeElement as HTMLElement).style.height = newHeight;
1514 updateTransaction(protyle, id, nodeElement.outerHTML, html);
1515 html = nodeElement.outerHTML;
1516 event.stopPropagation();
1517 const renderElement = nodeElement.querySelector('[contenteditable="false"]') as HTMLElement;
1518 if (renderElement) {
1519 renderElement.style.height = newHeight;
1520 const chartInstance = window.echarts.getInstanceById(renderElement.getAttribute("_echarts_instance_"));
1521 if (chartInstance) {
1522 chartInstance.resize();
1523 }
1524 }
1525 });
1526 }
1527 }, {
1528 id: "update",
1529 label: window.siyuan.languages.update,
1530 icon: "iconEdit",
1531 click() {
1532 protyle.toolbar.showRender(protyle, nodeElement);
1533 }
1534 }]
1535 }).element);
1536 } else if (type === "NodeTable" && !protyle.disabled) {
1537 let range = getEditorRange(nodeElement);
1538 const tableElement = nodeElement.querySelector("table");
1539 if (!tableElement.contains(range.startContainer)) {
1540 range = getEditorRange(tableElement.querySelector("th"));
1541 }
1542 const cellElement = hasClosestByTag(range.startContainer, "TD") || hasClosestByTag(range.startContainer, "TH");
1543 if (cellElement) {
1544 window.siyuan.menus.menu.append(new MenuItem({id: "separator_table", type: "separator"}).element);
1545 window.siyuan.menus.menu.append(new MenuItem({
1546 id: "table",
1547 type: "submenu",
1548 icon: "iconTable",
1549 label: window.siyuan.languages.table,
1550 submenu: tableMenu(protyle, nodeElement, cellElement as HTMLTableCellElement, range).menus as IMenu[]
1551 }).element);
1552 }
1553 } else if (type === "NodeAttributeView") {
1554 window.siyuan.menus.menu.append(new MenuItem({id: "separator_exportCSV", type: "separator"}).element);
1555 window.siyuan.menus.menu.append(new MenuItem({
1556 id: "exportCSV",
1557 icon: "iconDatabase",
1558 label: window.siyuan.languages.export + " CSV",
1559 click() {
1560 fetchPost("/api/export/exportAttributeView", {
1561 id: nodeElement.getAttribute("data-av-id"),
1562 blockID: id,
1563 }, response => {
1564 openByMobile(response.data.zip);
1565 });
1566 }
1567 }).element);
1568 window.siyuan.menus.menu.append(new MenuItem({
1569 id: "showDatabaseInFolder",
1570 icon: "iconFolder",
1571 label: window.siyuan.languages.showInFolder,
1572 click() {
1573 useShell("showItemInFolder", path.join(window.siyuan.config.system.dataDir, "storage", "av", nodeElement.getAttribute("data-av-id")) + ".json");
1574 }
1575 }).element);
1576 } else if ((type === "NodeVideo" || type === "NodeAudio") && !protyle.disabled) {
1577 window.siyuan.menus.menu.append(new MenuItem({id: "separator_VideoOrAudio", type: "separator"}).element);
1578 window.siyuan.menus.menu.append(new MenuItem({
1579 id: type === "NodeVideo" ? "assetVideo" : "assetAudio",
1580 type: "submenu",
1581 icon: type === "NodeVideo" ? "iconVideo" : "iconRecord",
1582 label: window.siyuan.languages.assets,
1583 submenu: videoMenu(protyle, nodeElement, type)
1584 }).element);
1585 } else if (type === "NodeIFrame" && !protyle.disabled) {
1586 window.siyuan.menus.menu.append(new MenuItem({id: "separator_IFrame", type: "separator"}).element);
1587 window.siyuan.menus.menu.append(new MenuItem({
1588 id: "assetIFrame",
1589 type: "submenu",
1590 icon: "iconLanguage",
1591 label: window.siyuan.languages.assets,
1592 submenu: iframeMenu(protyle, nodeElement)
1593 }).element);
1594 } else if (type === "NodeHTMLBlock" && !protyle.disabled) {
1595 window.siyuan.menus.menu.append(new MenuItem({id: "separator_html", type: "separator"}).element);
1596 window.siyuan.menus.menu.append(new MenuItem({
1597 id: "html",
1598 icon: "iconHTML5",
1599 label: "HTML",
1600 click() {
1601 protyle.toolbar.showRender(protyle, nodeElement);
1602 }
1603 }).element);
1604 } else if (type === "NodeBlockQueryEmbed" && !protyle.disabled) {
1605 window.siyuan.menus.menu.append(new MenuItem({id: "separator_blockEmbed", type: "separator"}).element);
1606 const breadcrumb = nodeElement.getAttribute("breadcrumb");
1607 window.siyuan.menus.menu.append(new MenuItem({
1608 id: "blockEmbed",
1609 type: "submenu",
1610 icon: "iconSQL",
1611 label: window.siyuan.languages.blockEmbed,
1612 submenu: [{
1613 id: "refresh",
1614 icon: "iconRefresh",
1615 label: `${window.siyuan.languages.refresh} SQL`,
1616 click() {
1617 nodeElement.removeAttribute("data-render");
1618 blockRender(protyle, nodeElement);
1619 }
1620 }, {
1621 id: "update",
1622 icon: "iconEdit",
1623 label: `${window.siyuan.languages.update} SQL`,
1624 click() {
1625 protyle.toolbar.showRender(protyle, nodeElement);
1626 }
1627 }, {
1628 type: "separator"
1629 }, {
1630 id: "embedBlockBreadcrumb",
1631 label: `<div class="fn__flex" style="margin-bottom: 4px"><span>${window.siyuan.languages.embedBlockBreadcrumb}</span><span class="fn__space fn__flex-1"></span>
1632<input type="checkbox" class="b3-switch fn__flex-center"${breadcrumb === "true" ? " checked" : ((window.siyuan.config.editor.embedBlockBreadcrumb && breadcrumb !== "false") ? " checked" : "")}></div>`,
1633 bind(element) {
1634 element.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
1635 const inputElement = element.querySelector("input");
1636 if (event.target.tagName !== "INPUT") {
1637 inputElement.checked = !inputElement.checked;
1638 }
1639 nodeElement.setAttribute("breadcrumb", inputElement.checked.toString());
1640 fetchPost("/api/attr/setBlockAttrs", {
1641 id,
1642 attrs: {breadcrumb: inputElement.checked.toString()}
1643 });
1644 nodeElement.removeAttribute("data-render");
1645 blockRender(protyle, nodeElement);
1646 window.siyuan.menus.menu.remove();
1647 });
1648 }
1649 }, {
1650 id: "headingEmbedMode",
1651 label: window.siyuan.languages.headingEmbedMode,
1652 type: "submenu",
1653 submenu: [{
1654 id: "showHeadingWithBlocks",
1655 label: window.siyuan.languages.showHeadingWithBlocks,
1656 iconHTML: "",
1657 checked: nodeElement.getAttribute("custom-heading-mode") === "0",
1658 click() {
1659 nodeElement.setAttribute("custom-heading-mode", "0");
1660 fetchPost("/api/attr/setBlockAttrs", {
1661 id,
1662 attrs: {"custom-heading-mode": "0"}
1663 });
1664 nodeElement.removeAttribute("data-render");
1665 blockRender(protyle, nodeElement);
1666 }
1667 }, {
1668 id: "showHeadingOnlyTitle",
1669 label: window.siyuan.languages.showHeadingOnlyTitle,
1670 iconHTML: "",
1671 checked: nodeElement.getAttribute("custom-heading-mode") === "1",
1672 click() {
1673 nodeElement.setAttribute("custom-heading-mode", "1");
1674 fetchPost("/api/attr/setBlockAttrs", {
1675 id,
1676 attrs: {"custom-heading-mode": "1"}
1677 });
1678 nodeElement.removeAttribute("data-render");
1679 blockRender(protyle, nodeElement);
1680 }
1681 }, {
1682 id: "showHeadingOnlyBlocks",
1683 label: window.siyuan.languages.showHeadingOnlyBlocks,
1684 iconHTML: "",
1685 checked: nodeElement.getAttribute("custom-heading-mode") === "2",
1686 click() {
1687 nodeElement.setAttribute("custom-heading-mode", "2");
1688 fetchPost("/api/attr/setBlockAttrs", {
1689 id,
1690 attrs: {"custom-heading-mode": "2"}
1691 });
1692 nodeElement.removeAttribute("data-render");
1693 blockRender(protyle, nodeElement);
1694 }
1695 }, {
1696 id: "default",
1697 label: window.siyuan.languages.default,
1698 iconHTML: "",
1699 checked: !nodeElement.getAttribute("custom-heading-mode"),
1700 click() {
1701 nodeElement.removeAttribute("custom-heading-mode");
1702 fetchPost("/api/attr/setBlockAttrs", {
1703 id,
1704 attrs: {"custom-heading-mode": ""}
1705 });
1706 nodeElement.removeAttribute("data-render");
1707 blockRender(protyle, nodeElement);
1708 }
1709 }]
1710 }]
1711 }).element);
1712 } else if (type === "NodeHeading" && !protyle.disabled) {
1713 window.siyuan.menus.menu.append(new MenuItem({id: "separator_1", type: "separator"}).element);
1714 const headingSubMenu = [];
1715 if (subType !== "h1") {
1716 headingSubMenu.push(this.genHeadingTransform(protyle, id, 1));
1717 }
1718 if (subType !== "h2") {
1719 headingSubMenu.push(this.genHeadingTransform(protyle, id, 2));
1720 }
1721 if (subType !== "h3") {
1722 headingSubMenu.push(this.genHeadingTransform(protyle, id, 3));
1723 }
1724 if (subType !== "h4") {
1725 headingSubMenu.push(this.genHeadingTransform(protyle, id, 4));
1726 }
1727 if (subType !== "h5") {
1728 headingSubMenu.push(this.genHeadingTransform(protyle, id, 5));
1729 }
1730 if (subType !== "h6") {
1731 headingSubMenu.push(this.genHeadingTransform(protyle, id, 6));
1732 }
1733 window.siyuan.menus.menu.append(new MenuItem({
1734 id: "tWithSubtitle",
1735 type: "submenu",
1736 icon: "iconRefresh",
1737 label: window.siyuan.languages.tWithSubtitle,
1738 submenu: headingSubMenu
1739 }).element);
1740 window.siyuan.menus.menu.append(new MenuItem({
1741 id: "copyHeadings1",
1742 icon: "iconCopy",
1743 label: `${window.siyuan.languages.copy} ${window.siyuan.languages.headings1}`,
1744 click() {
1745 fetchPost("/api/block/getHeadingChildrenDOM", {
1746 id,
1747 removeFoldAttr: nodeElement.getAttribute("fold") !== "1"
1748 }, (response) => {
1749 if (isInAndroid()) {
1750 window.JSAndroid.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
1751 } else if (isInHarmony()) {
1752 window.JSHarmony.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
1753 } else {
1754 writeText(response.data + Constants.ZWSP);
1755 }
1756 });
1757 }
1758 }).element);
1759 window.siyuan.menus.menu.append(new MenuItem({
1760 id: "cutHeadings1",
1761 icon: "iconCut",
1762 label: `${window.siyuan.languages.cut} ${window.siyuan.languages.headings1}`,
1763 click() {
1764 fetchPost("/api/block/getHeadingChildrenDOM", {
1765 id,
1766 removeFoldAttr: nodeElement.getAttribute("fold") !== "1"
1767 }, (response) => {
1768 if (isInAndroid()) {
1769 window.JSAndroid.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
1770 } else if (isInHarmony()) {
1771 window.JSHarmony.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
1772 } else {
1773 writeText(response.data + Constants.ZWSP);
1774 }
1775 fetchPost("/api/block/getHeadingDeleteTransaction", {
1776 id,
1777 }, (deleteResponse) => {
1778 deleteResponse.data.doOperations.forEach((operation: IOperation) => {
1779 protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`).forEach((itemElement: HTMLElement) => {
1780 itemElement.remove();
1781 });
1782 });
1783 if (protyle.wysiwyg.element.childElementCount === 0) {
1784 const newID = Lute.NewNodeID();
1785 const emptyElement = genEmptyElement(false, false, newID);
1786 protyle.wysiwyg.element.insertAdjacentElement("afterbegin", emptyElement);
1787 deleteResponse.data.doOperations.push({
1788 action: "insert",
1789 data: emptyElement.outerHTML,
1790 id: newID,
1791 parentID: protyle.block.parentID
1792 });
1793 deleteResponse.data.undoOperations.push({
1794 action: "delete",
1795 id: newID,
1796 });
1797 focusBlock(emptyElement);
1798 }
1799 transaction(protyle, deleteResponse.data.doOperations, deleteResponse.data.undoOperations);
1800 });
1801 });
1802 }
1803 }).element);
1804 window.siyuan.menus.menu.append(new MenuItem({
1805 id: "deleteHeadings1",
1806 icon: "iconTrashcan",
1807 label: `${window.siyuan.languages.delete} ${window.siyuan.languages.headings1}`,
1808 click() {
1809 fetchPost("/api/block/getHeadingDeleteTransaction", {
1810 id,
1811 }, (response) => {
1812 response.data.doOperations.forEach((operation: IOperation) => {
1813 protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`).forEach((itemElement: HTMLElement) => {
1814 itemElement.remove();
1815 });
1816 });
1817 if (protyle.wysiwyg.element.childElementCount === 0) {
1818 const newID = Lute.NewNodeID();
1819 const emptyElement = genEmptyElement(false, false, newID);
1820 protyle.wysiwyg.element.insertAdjacentElement("afterbegin", emptyElement);
1821 response.data.doOperations.push({
1822 action: "insert",
1823 data: emptyElement.outerHTML,
1824 id: newID,
1825 parentID: protyle.block.parentID
1826 });
1827 response.data.undoOperations.push({
1828 action: "delete",
1829 id: newID,
1830 });
1831 focusBlock(emptyElement);
1832 }
1833 transaction(protyle, response.data.doOperations, response.data.undoOperations);
1834 });
1835 }
1836 }).element);
1837 }
1838 window.siyuan.menus.menu.append(new MenuItem({id: "separator_2", type: "separator"}).element);
1839 if (!protyle.options.backlinkData) {
1840 window.siyuan.menus.menu.append(new MenuItem({
1841 id: "enter",
1842 accelerator: `${updateHotkeyTip(window.siyuan.config.keymap.general.enter.custom)}/${updateHotkeyTip("⌘" + window.siyuan.languages.click)}`,
1843 label: window.siyuan.languages.enter,
1844 click: () => {
1845 zoomOut({protyle, id});
1846 }
1847 }).element);
1848 window.siyuan.menus.menu.append(new MenuItem({
1849 id: "enterBack",
1850 accelerator: window.siyuan.config.keymap.general.enterBack.custom,
1851 label: window.siyuan.languages.enterBack,
1852 click: () => {
1853 enterBack(protyle, id);
1854 }
1855 }).element);
1856 } else {
1857 /// #if !MOBILE
1858 window.siyuan.menus.menu.append(new MenuItem({
1859 id: "enter",
1860 accelerator: `${updateHotkeyTip(window.siyuan.config.keymap.general.enter.custom)}/${updateHotkeyTip("⌘" + window.siyuan.languages.click)}`,
1861 label: window.siyuan.languages.openBy,
1862 click: () => {
1863 checkFold(id, (zoomIn, action) => {
1864 openFileById({
1865 app: protyle.app,
1866 id,
1867 action,
1868 zoomIn
1869 });
1870 });
1871 }
1872 }).element);
1873 /// #endif
1874 }
1875 if (!protyle.disabled) {
1876 window.siyuan.menus.menu.append(new MenuItem({
1877 id: "insertBefore",
1878 icon: "iconBefore",
1879 label: window.siyuan.languages.insertBefore,
1880 accelerator: window.siyuan.config.keymap.editor.general.insertBefore.custom,
1881 click() {
1882 hideElements(["select"], protyle);
1883 countBlockWord([], protyle.block.rootID);
1884 insertEmptyBlock(protyle, "beforebegin", id);
1885 }
1886 }).element);
1887 window.siyuan.menus.menu.append(new MenuItem({
1888 id: "insertAfter",
1889 icon: "iconAfter",
1890 label: window.siyuan.languages.insertAfter,
1891 accelerator: window.siyuan.config.keymap.editor.general.insertAfter.custom,
1892 click() {
1893 hideElements(["select"], protyle);
1894 countBlockWord([], protyle.block.rootID);
1895 insertEmptyBlock(protyle, "afterend", id);
1896 }
1897 }).element);
1898 const countElement = nodeElement.lastElementChild.querySelector(".protyle-attr--refcount");
1899 if (countElement && countElement.textContent) {
1900 transferBlockRef(id);
1901 }
1902 }
1903 window.siyuan.menus.menu.append(new MenuItem({
1904 id: "jumpTo",
1905 type: "submenu",
1906 label: window.siyuan.languages.jumpTo,
1907 submenu: [{
1908 id: "jumpToParentPrev",
1909 iconHTML: "",
1910 label: window.siyuan.languages.jumpToParentPrev,
1911 accelerator: window.siyuan.config.keymap.editor.general.jumpToParentPrev.custom,
1912 click() {
1913 hideElements(["select"], protyle);
1914 jumpToParent(protyle, nodeElement, "previous");
1915 }
1916 }, {
1917 iconHTML: "",
1918 id: "jumpToParentNext",
1919 label: window.siyuan.languages.jumpToParentNext,
1920 accelerator: window.siyuan.config.keymap.editor.general.jumpToParentNext.custom,
1921 click() {
1922 hideElements(["select"], protyle);
1923 jumpToParent(protyle, nodeElement, "next");
1924 }
1925 }, {
1926 iconHTML: "",
1927 id: "jumpToParent",
1928 label: window.siyuan.languages.jumpToParent,
1929 accelerator: window.siyuan.config.keymap.editor.general.jumpToParent.custom,
1930 click() {
1931 hideElements(["select"], protyle);
1932 jumpToParent(protyle, nodeElement, "parent");
1933 }
1934 }]
1935 }).element);
1936
1937 window.siyuan.menus.menu.append(new MenuItem({id: "separator_3", type: "separator"}).element);
1938
1939 if (type !== "NodeThematicBreak") {
1940 window.siyuan.menus.menu.append(new MenuItem({
1941 id: "fold",
1942 label: window.siyuan.languages.fold,
1943 accelerator: `${updateHotkeyTip(window.siyuan.config.keymap.editor.general.collapse.custom)}/${updateHotkeyTip("⌥" + window.siyuan.languages.click)}`,
1944 click() {
1945 setFold(protyle, nodeElement);
1946 focusBlock(nodeElement);
1947 }
1948 }).element);
1949 if (!protyle.disabled) {
1950 window.siyuan.menus.menu.append(new MenuItem({
1951 id: "attr",
1952 label: window.siyuan.languages.attr,
1953 icon: "iconAttr",
1954 accelerator: window.siyuan.config.keymap.editor.general.attr.custom + "/" + updateHotkeyTip("⇧" + window.siyuan.languages.click),
1955 click() {
1956 openAttr(nodeElement, "bookmark", protyle);
1957 }
1958 }).element);
1959 }
1960 }
1961 if (!protyle.disabled) {
1962 const appearanceElement = new MenuItem({
1963 id: "appearance",
1964 label: window.siyuan.languages.appearance,
1965 icon: "iconFont",
1966 accelerator: window.siyuan.config.keymap.editor.insert.appearance.custom,
1967 click: () => {
1968 /// #if MOBILE
1969 this.showMobileAppearance(protyle);
1970 /// #else
1971 protyle.toolbar.element.classList.add("fn__none");
1972 protyle.toolbar.subElement.innerHTML = "";
1973 protyle.toolbar.subElement.style.width = "";
1974 protyle.toolbar.subElement.style.padding = "";
1975 protyle.toolbar.subElement.append(appearanceMenu(protyle, [nodeElement]));
1976 protyle.toolbar.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
1977 protyle.toolbar.subElement.classList.remove("fn__none");
1978 protyle.toolbar.subElementCloseCB = undefined;
1979 const position = nodeElement.getBoundingClientRect();
1980 setPosition(protyle.toolbar.subElement, position.left, position.top);
1981 /// #endif
1982 }
1983 }).element;
1984 window.siyuan.menus.menu.append(appearanceElement);
1985 if (!isMobile()) {
1986 appearanceElement.lastElementChild.classList.add("b3-menu__submenu--row");
1987 }
1988 this.genAlign([nodeElement], protyle);
1989 this.genWidths([nodeElement], protyle);
1990 // this.genHeights([nodeElement], protyle);
1991 }
1992 window.siyuan.menus.menu.append(new MenuItem({id: "separator_4", type: "separator"}).element);
1993 if (window.siyuan.config.cloudRegion === 0 &&
1994 !["NodeThematicBreak", "NodeBlockQueryEmbed", "NodeIFrame", "NodeHTMLBlock", "NodeWidget", "NodeVideo", "NodeAudio"].includes(type) &&
1995 getContenteditableElement(nodeElement)?.textContent.trim() !== "" &&
1996 (type !== "NodeCodeBlock" || (type === "NodeCodeBlock" && !nodeElement.getAttribute("data-subtype")))) {
1997 window.siyuan.menus.menu.append(new MenuItem({
1998 id: "wechatReminder",
1999 icon: "iconMp",
2000 label: window.siyuan.languages.wechatReminder,
2001 ignore: window.siyuan.config.readonly,
2002 click() {
2003 openWechatNotify(nodeElement);
2004 }
2005 }).element);
2006 }
2007 if (type !== "NodeThematicBreak" && !window.siyuan.config.readonly) {
2008 const isCardMade = nodeElement.hasAttribute(Constants.CUSTOM_RIFF_DECKS);
2009 window.siyuan.menus.menu.append(new MenuItem({
2010 id: isCardMade ? "removeCard" : "quickMakeCard",
2011 icon: "iconRiffCard",
2012 label: isCardMade ? window.siyuan.languages.removeCard : window.siyuan.languages.quickMakeCard,
2013 accelerator: window.siyuan.config.keymap.editor.general.quickMakeCard.custom,
2014 click() {
2015 quickMakeCard(protyle, [nodeElement]);
2016 }
2017 }).element);
2018 window.siyuan.menus.menu.append(new MenuItem({
2019 id: "addToDeck",
2020 label: window.siyuan.languages.addToDeck,
2021 ignore: !window.siyuan.config.flashcard.deck,
2022 icon: "iconRiffCard",
2023 click() {
2024 makeCard(protyle.app, [id]);
2025 }
2026 }).element);
2027 window.siyuan.menus.menu.append(new MenuItem({id: "separator_5", type: "separator"}).element);
2028 }
2029
2030 if (protyle?.app?.plugins) {
2031 emitOpenMenu({
2032 plugins: protyle.app.plugins,
2033 type: "click-blockicon",
2034 detail: {
2035 protyle,
2036 blockElements: [nodeElement]
2037 },
2038 separatorPosition: "bottom",
2039 });
2040 }
2041
2042 let updateHTML = nodeElement.getAttribute("updated") || "";
2043 if (updateHTML) {
2044 updateHTML = `${window.siyuan.languages.modifiedAt} ${dayjs(updateHTML).format("YYYY-MM-DD HH:mm:ss")}<br>`;
2045 }
2046 window.siyuan.menus.menu.append(new MenuItem({
2047 id: "updateAndCreatedAt",
2048 iconHTML: "",
2049 type: "readonly",
2050 label: `${updateHTML}${window.siyuan.languages.createdAt} ${dayjs(id.substr(0, 14)).format("YYYY-MM-DD HH:mm:ss")}`,
2051 }).element);
2052 return window.siyuan.menus.menu;
2053 }
2054
2055 private genHeadingTransform(protyle: IProtyle, id: string, level: number) {
2056 return {
2057 id: "heading" + level,
2058 iconHTML: "",
2059 icon: "iconHeading" + level,
2060 label: window.siyuan.languages["heading" + level],
2061 click() {
2062 fetchPost("/api/block/getHeadingLevelTransaction", {
2063 id,
2064 level
2065 }, (response) => {
2066 response.data.doOperations.forEach((operation: IOperation, index: number) => {
2067 protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`).forEach((itemElement: HTMLElement) => {
2068 itemElement.outerHTML = operation.data;
2069 });
2070 // 使用 outer 后元素需要重新查询
2071 protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${operation.id}"]`).forEach((itemElement: HTMLElement) => {
2072 mathRender(itemElement);
2073 });
2074 if (index === 0) {
2075 focusBlock(protyle.wysiwyg.element.querySelector(`[data-node-id="${operation.id}"]`), protyle.wysiwyg.element, true);
2076 }
2077 });
2078 transaction(protyle, response.data.doOperations, response.data.undoOperations);
2079 });
2080 }
2081 };
2082 }
2083
2084 private genClick(nodeElements: Element[], protyle: IProtyle, cb: (e: HTMLElement) => void) {
2085 updateBatchTransaction(nodeElements, protyle, cb);
2086 focusBlock(nodeElements[0]);
2087 }
2088
2089 private genAlign(nodeElements: Element[], protyle: IProtyle) {
2090 window.siyuan.menus.menu.append(new MenuItem({
2091 id: "layout",
2092 label: window.siyuan.languages.layout,
2093 type: "submenu",
2094 submenu: [{
2095 id: "alignLeft",
2096 icon: "iconAlignLeft",
2097 label: window.siyuan.languages.alignLeft,
2098 accelerator: window.siyuan.config.keymap.editor.general.alignLeft.custom,
2099 click: () => {
2100 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2101 if (e.classList.contains("av")) {
2102 e.style.justifyContent = "";
2103 } else {
2104 e.style.textAlign = "left";
2105 }
2106 });
2107 }
2108 }, {
2109 id: "alignCenter",
2110 icon: "iconAlignCenter",
2111 label: window.siyuan.languages.alignCenter,
2112 accelerator: window.siyuan.config.keymap.editor.general.alignCenter.custom,
2113 click: () => {
2114 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2115 if (e.classList.contains("av")) {
2116 e.style.justifyContent = "center";
2117 } else {
2118 e.style.textAlign = "center";
2119 }
2120 });
2121 }
2122 }, {
2123 id: "alignRight",
2124 icon: "iconAlignRight",
2125 label: window.siyuan.languages.alignRight,
2126 accelerator: window.siyuan.config.keymap.editor.general.alignRight.custom,
2127 click: () => {
2128 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2129 if (e.classList.contains("av")) {
2130 e.style.justifyContent = "flex-end";
2131 } else {
2132 e.style.textAlign = "right";
2133 }
2134 });
2135 }
2136 }, {
2137 id: "justify",
2138 icon: "iconMenu",
2139 label: window.siyuan.languages.justify,
2140 click: () => {
2141 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2142 e.style.textAlign = "justify";
2143 });
2144 }
2145 }, {
2146 id: "separator_1",
2147 type: "separator"
2148 }, {
2149 id: "ltr",
2150 icon: "iconLtr",
2151 label: window.siyuan.languages.ltr,
2152 accelerator: window.siyuan.config.keymap.editor.general.ltr.custom,
2153 click: () => {
2154 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2155 e.style.direction = "ltr";
2156 });
2157 }
2158 }, {
2159 id: "rtl",
2160 icon: "iconRtl",
2161 label: window.siyuan.languages.rtl,
2162 accelerator: window.siyuan.config.keymap.editor.general.rtl.custom,
2163 click: () => {
2164 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2165 if (!e.classList.contains("av")) {
2166 e.style.direction = "rtl";
2167 }
2168 });
2169 }
2170 }, {
2171 id: "separator_2",
2172 type: "separator"
2173 }, {
2174 id: "clearFontStyle",
2175 icon: "iconTrashcan",
2176 label: window.siyuan.languages.clearFontStyle,
2177 click: () => {
2178 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2179 if (e.classList.contains("av")) {
2180 e.style.justifyContent = "";
2181 } else {
2182 e.style.textAlign = "";
2183 e.style.direction = "";
2184 }
2185 });
2186 }
2187 }]
2188 }).element);
2189 }
2190
2191 private updateNodeElements(nodeElements: Element[], protyle: IProtyle, inputElement: HTMLInputElement) {
2192 const undoOperations: IOperation[] = [];
2193 const operations: IOperation[] = [];
2194 nodeElements.forEach((e) => {
2195 undoOperations.push({
2196 action: "update",
2197 id: e.getAttribute("data-node-id"),
2198 data: e.outerHTML
2199 });
2200 });
2201 inputElement.addEventListener(inputElement.type === "number" ? "blur" : "change", () => {
2202 nodeElements.forEach((e: HTMLElement) => {
2203 operations.push({
2204 action: "update",
2205 id: e.getAttribute("data-node-id"),
2206 data: e.outerHTML
2207 });
2208 });
2209 transaction(protyle, operations, undoOperations);
2210 window.siyuan.menus.menu.remove();
2211 focusBlock(nodeElements[0]);
2212 });
2213 }
2214
2215 private genWidths(nodeElements: Element[], protyle: IProtyle) {
2216 let rangeElement: HTMLInputElement;
2217 const firstElement = nodeElements[0] as HTMLElement;
2218 const styles: IMenu[] = [{
2219 id: "widthInput",
2220 iconHTML: "",
2221 type: "readonly",
2222 label: `<div class="fn__flex"><input class="b3-text-field fn__flex-1" value="${firstElement.style.width.endsWith("px") ? parseInt(firstElement.style.width) : ""}" type="number" style="margin: 4px 8px 4px 0" placeholder="${window.siyuan.languages.width}"><span class="fn__flex-center">px</span></div>`,
2223 bind: (element) => {
2224 const inputElement = element.querySelector("input");
2225 inputElement.addEventListener("input", () => {
2226 nodeElements.forEach((item: HTMLElement) => {
2227 item.style.width = inputElement.value + "px";
2228 item.style.flex = "none";
2229 });
2230 rangeElement.value = "0";
2231 rangeElement.parentElement.setAttribute("aria-label", inputElement.value + "px");
2232 });
2233 this.updateNodeElements(nodeElements, protyle, inputElement);
2234 }
2235 }];
2236 ["25%", "33%", "50%", "67%", "75%", "100%"].forEach((item) => {
2237 styles.push({
2238 id: "width_" + item,
2239 iconHTML: "",
2240 label: item,
2241 click: () => {
2242 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2243 e.style.width = item;
2244 e.style.flex = "none";
2245 });
2246 }
2247 });
2248 });
2249 styles.push({
2250 id: "separator_1",
2251 type: "separator"
2252 });
2253 const width = firstElement.style.width.endsWith("%") ? parseInt(firstElement.style.width) : 0;
2254 window.siyuan.menus.menu.append(new MenuItem({
2255 id: "width",
2256 label: window.siyuan.languages.width,
2257 submenu: styles.concat([{
2258 id: "widthDrag",
2259 iconHTML: "",
2260 type: "readonly",
2261 label: `<div style="margin: 4px 0;" aria-label="${firstElement.style.width.endsWith("px") ? firstElement.style.width : (firstElement.style.width || window.siyuan.languages.default)}" class="b3-tooltips b3-tooltips__n"><input style="box-sizing: border-box" value="${width}" class="b3-slider fn__block" max="100" min="1" step="1" type="range"></div>`,
2262 bind: (element) => {
2263 rangeElement = element.querySelector("input");
2264 rangeElement.addEventListener("input", () => {
2265 nodeElements.forEach((e: HTMLElement) => {
2266 e.style.width = rangeElement.value + "%";
2267 e.style.flex = "none";
2268 });
2269 rangeElement.parentElement.setAttribute("aria-label", `${rangeElement.value}%`);
2270 });
2271 this.updateNodeElements(nodeElements, protyle, rangeElement);
2272 }
2273 }, {
2274 id: "separator_2",
2275 type: "separator"
2276 }, {
2277 id: "default",
2278 iconHTML: "",
2279 label: window.siyuan.languages.default,
2280 click: () => {
2281 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2282 if (e.style.width) {
2283 e.style.width = "";
2284 e.style.flex = "";
2285 }
2286 });
2287 }
2288 }]),
2289 }).element);
2290 }
2291
2292 // TODO https://github.com/siyuan-note/siyuan/issues/11055
2293 private genHeights(nodeElements: Element[], protyle: IProtyle) {
2294 const matchHeight = nodeElements.find(item => {
2295 if (!item.classList.contains("p") && !item.classList.contains("code-block") && !item.classList.contains("render-node")) {
2296 return true;
2297 }
2298 });
2299 if (matchHeight) {
2300 return;
2301 }
2302 let rangeElement: HTMLInputElement;
2303 const firstElement = nodeElements[0] as HTMLElement;
2304 const styles: IMenu[] = [{
2305 id: "heightInput",
2306 iconHTML: "",
2307 type: "readonly",
2308 label: `<div class="fn__flex"><input class="b3-text-field fn__flex-1" value="${firstElement.style.height.endsWith("px") ? parseInt(firstElement.style.height) : ""}" type="number" style="margin: 4px 8px 4px 0" placeholder="${window.siyuan.languages.height}"><span class="fn__flex-center">px</span></div>`,
2309 bind: (element) => {
2310 const inputElement = element.querySelector("input");
2311 inputElement.addEventListener("input", () => {
2312 nodeElements.forEach((item: HTMLElement) => {
2313 item.style.height = inputElement.value + "px";
2314 item.style.flex = "none";
2315 });
2316 rangeElement.value = "0";
2317 rangeElement.parentElement.setAttribute("aria-label", inputElement.value + "px");
2318 });
2319 this.updateNodeElements(nodeElements, protyle, inputElement);
2320 }
2321 }];
2322 ["25%", "33%", "50%", "67%", "75%", "100%"].forEach((item) => {
2323 styles.push({
2324 id: "height_" + item,
2325 iconHTML: "",
2326 label: item,
2327 click: () => {
2328 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2329 e.style.height = item;
2330 e.style.flex = "none";
2331 });
2332 }
2333 });
2334 });
2335 styles.push({
2336 type: "separator"
2337 });
2338 const height = firstElement.style.height.endsWith("%") ? parseInt(firstElement.style.height) : 0;
2339 window.siyuan.menus.menu.append(new MenuItem({
2340 id: "heightDrag",
2341 label: window.siyuan.languages.height,
2342 submenu: styles.concat([{
2343 iconHTML: "",
2344 type: "readonly",
2345 label: `<div style="margin: 4px 0;" aria-label="${firstElement.style.height.endsWith("px") ? firstElement.style.height : (firstElement.style.height || window.siyuan.languages.default)}" class="b3-tooltips b3-tooltips__n"><input style="box-sizing: border-box" value="${height}" class="b3-slider fn__block" max="100" min="1" step="1" type="range"></div>`,
2346 bind: (element) => {
2347 rangeElement = element.querySelector("input");
2348 rangeElement.addEventListener("input", () => {
2349 nodeElements.forEach((e: HTMLElement) => {
2350 e.style.height = rangeElement.value + "%";
2351 e.style.flex = "none";
2352 });
2353 rangeElement.parentElement.setAttribute("aria-label", `${rangeElement.value}%`);
2354 });
2355 this.updateNodeElements(nodeElements, protyle, rangeElement);
2356 }
2357 }, {
2358 type: "separator"
2359 }, {
2360 id: "default",
2361 iconHTML: "",
2362 label: window.siyuan.languages.default,
2363 click: () => {
2364 this.genClick(nodeElements, protyle, (e: HTMLElement) => {
2365 if (e.style.height) {
2366 e.style.height = "";
2367 e.style.overflow = "";
2368 }
2369 });
2370 }
2371 }]),
2372 }).element);
2373 }
2374
2375 private genCopyTextRef(selectsElement: Element[]): false | IMenu {
2376 if (isNotEditBlock(selectsElement[0])) {
2377 return false;
2378 }
2379 return {
2380 id: "copyText",
2381 iconHTML: "",
2382 accelerator: window.siyuan.config.keymap.editor.general.copyText.custom,
2383 label: window.siyuan.languages.copyText,
2384 click() {
2385 // 用于标识复制文本 *
2386 selectsElement[0].setAttribute("data-reftext", "true");
2387 focusByRange(getEditorRange(selectsElement[0]));
2388 document.execCommand("copy");
2389 }
2390 };
2391 }
2392
2393 public render(protyle: IProtyle, element: Element, wysiwyg: HTMLElement, target?: Element) {
2394 // https://github.com/siyuan-note/siyuan/issues/4659
2395 if (protyle.title && protyle.title.element.getAttribute("data-render") !== "true") {
2396 return;
2397 }
2398 // 防止划选时触碰图标导致 hl 无法移除
2399 const selectElement = wysiwyg.parentElement.parentElement.querySelector(".protyle-select");
2400 if (selectElement && !selectElement.classList.contains("fn__none")) {
2401 return;
2402 }
2403 let html = "";
2404 let nodeElement = element;
2405 let space = 0;
2406 let index = 0;
2407 let listItem;
2408 let hideParent = false;
2409 while (nodeElement) {
2410 let parentElement = hasClosestBlock(nodeElement.parentElement);
2411 if (!isInEmbedBlock(nodeElement)) {
2412 let type;
2413 if (!hideParent) {
2414 type = nodeElement.getAttribute("data-type");
2415 }
2416 let dataNodeId = nodeElement.getAttribute("data-node-id");
2417 if (type === "NodeAttributeView" && target) {
2418 const rowElement = hasClosestByClassName(target, "av__row");
2419 if (rowElement && !rowElement.classList.contains("av__row--header") && rowElement.dataset.id) {
2420 element = rowElement;
2421 const bodyElement = hasClosestByClassName(rowElement, "av__body") as HTMLElement;
2422 let iconAriaLabel = isMac() ? window.siyuan.languages.rowTip : window.siyuan.languages.rowTip.replace("⇧", "Shift+");
2423 if (protyle.disabled) {
2424 iconAriaLabel = window.siyuan.languages.rowTip.substring(0, window.siyuan.languages.rowTip.indexOf("<br"));
2425 } else if (rowElement.querySelector('[data-dtype="block"]')?.getAttribute("data-detached") === "true") {
2426 iconAriaLabel = window.siyuan.languages.rowTip.substring(0, window.siyuan.languages.rowTip.lastIndexOf("<br"));
2427 }
2428 html = `<button data-type="NodeAttributeViewRowMenu" data-node-id="${dataNodeId}" data-row-id="${rowElement.dataset.id}" data-group-id="${bodyElement.dataset.groupId || ""}" class="ariaLabel" data-position="parentW" aria-label="${iconAriaLabel}"><svg><use xlink:href="#iconDrag"></use></svg><span ${protyle.disabled ? "" : 'draggable="true" class="fn__grab"'}></span></button>`;
2429 if (!protyle.disabled) {
2430 html = `<button data-type="NodeAttributeViewRow" data-node-id="${dataNodeId}" data-row-id="${rowElement.dataset.id}" data-group-id="${bodyElement.dataset.groupId || ""}" class="ariaLabel" data-position="parentW" aria-label="${isMac() ? window.siyuan.languages.addBelowAbove : window.siyuan.languages.addBelowAbove.replace("⌥", "Alt+")}"><svg><use xlink:href="#iconAdd"></use></svg></button>${html}`;
2431 }
2432 break;
2433 }
2434 }
2435 if (index === 0) {
2436 // 不单独显示,要不然在块的间隔中,gutter 会跳来跳去的
2437 if (["NodeBlockquote", "NodeList", "NodeSuperBlock"].includes(type)) {
2438 return;
2439 }
2440 const topElement = getTopAloneElement(nodeElement);
2441 listItem = topElement.querySelector(".li") || topElement.querySelector(".list");
2442 // 嵌入块中有列表时块标显示位置错误 https://github.com/siyuan-note/siyuan/issues/6254
2443 if (isInEmbedBlock(listItem) || isInAVBlock(listItem)) {
2444 listItem = undefined;
2445 }
2446 // 标题必须显示
2447 if (topElement !== nodeElement && type !== "NodeHeading") {
2448 nodeElement = topElement;
2449 parentElement = hasClosestBlock(nodeElement.parentElement);
2450 type = nodeElement.getAttribute("data-type");
2451 dataNodeId = nodeElement.getAttribute("data-node-id");
2452 }
2453 }
2454 if (type === "NodeListItem" && index === 1) {
2455 // 列表项中第一层不显示
2456 html = "";
2457 }
2458 index += 1;
2459 let gutterTip = this.gutterTip;
2460 if (protyle.disabled) {
2461 gutterTip = this.gutterTip.split("<br>").splice(0, 2).join("<br>");
2462 }
2463
2464 let popoverHTML = "";
2465 if (protyle.options.backlinkData) {
2466 popoverHTML = `class="popover__block" data-id="${dataNodeId}"`;
2467 }
2468 const buttonHTML = `<button class="ariaLabel" data-position="parentW" aria-label="${gutterTip}"
2469data-type="${type}" data-subtype="${nodeElement.getAttribute("data-subtype")}" data-node-id="${dataNodeId}">
2470 <svg><use xlink:href="#${getIconByType(type, nodeElement.getAttribute("data-subtype"))}"></use></svg>
2471 <span ${popoverHTML} ${protyle.disabled ? "" : 'draggable="true"'}></span>
2472</button>`;
2473 if (!hideParent) {
2474 html = buttonHTML + html;
2475 }
2476 let foldHTML = "";
2477 if (type === "NodeListItem" && nodeElement.childElementCount > 3 || type === "NodeHeading") {
2478 const fold = nodeElement.getAttribute("fold");
2479 foldHTML = `<button class="ariaLabel" data-position="parentW" aria-label="${window.siyuan.languages.fold}"
2480data-type="fold" style="cursor:inherit;"><svg style="width: 10px${fold && fold === "1" ? "" : ";transform:rotate(90deg)"}"><use xlink:href="#iconPlay"></use></svg></button>`;
2481 }
2482 if (type === "NodeListItem" || type === "NodeList") {
2483 listItem = nodeElement;
2484 if (type === "NodeListItem" && nodeElement.childElementCount > 3) {
2485 html = buttonHTML + foldHTML;
2486 }
2487 }
2488 if (type === "NodeHeading") {
2489 html = html + foldHTML;
2490 }
2491 if (type === "NodeBlockquote") {
2492 space += 8;
2493 }
2494 if (nodeElement.previousElementSibling && nodeElement.previousElementSibling.getAttribute("data-node-id")) {
2495 // 前一个块存在时,只显示到当前层级
2496 hideParent = true;
2497 // 由于折叠块的第二个子块在界面上不显示,因此移除块标 https://github.com/siyuan-note/siyuan/issues/14304
2498 if (parentElement && parentElement.getAttribute("fold") === "1") {
2499 return;
2500 }
2501 // 列表项中的引述块中的第二个段落块块标和引述块左侧样式重叠
2502 if (parentElement && parentElement.getAttribute("data-type") === "NodeBlockquote") {
2503 space += 8;
2504 }
2505 }
2506 }
2507
2508 if (parentElement) {
2509 nodeElement = parentElement;
2510 } else {
2511 break;
2512 }
2513 }
2514 let match = true;
2515 const buttonsElement = this.element.querySelectorAll("button");
2516 if (buttonsElement.length !== html.split("</button>").length - 1) {
2517 match = false;
2518 } else {
2519 Array.from(buttonsElement).find(item => {
2520 const id = item.getAttribute("data-node-id");
2521 if (id && html.indexOf(id) === -1) {
2522 match = false;
2523 return true;
2524 }
2525 const rowId = item.getAttribute("data-row-id");
2526 if ((rowId && html.indexOf(rowId) === -1) || (!rowId && html.indexOf("NodeAttributeViewRowMenu") > -1)) {
2527 match = false;
2528 return true;
2529 }
2530 });
2531 }
2532 // 防止抖动 https://github.com/siyuan-note/siyuan/issues/4166
2533 if (match && this.element.childElementCount > 0) {
2534 this.element.classList.remove("fn__none");
2535 return;
2536 }
2537 this.element.innerHTML = html;
2538 this.element.classList.remove("fn__none");
2539 this.element.style.width = "";
2540 const contentTop = wysiwyg.parentElement.getBoundingClientRect().top;
2541 let rect = element.getBoundingClientRect();
2542 let marginHeight = 0;
2543 if (listItem && !window.siyuan.config.editor.rtl && getComputedStyle(element).direction !== "rtl") {
2544 rect = listItem.firstElementChild.getBoundingClientRect();
2545 space = 0;
2546 } else if (nodeElement.getAttribute("data-type") === "NodeBlockQueryEmbed") {
2547 rect = nodeElement.getBoundingClientRect();
2548 space = 0;
2549 } else if (!element.classList.contains("av__row")) {
2550 if (rect.height < Math.floor(window.siyuan.config.editor.fontSize * 1.625) + 8 ||
2551 (rect.height > Math.floor(window.siyuan.config.editor.fontSize * 1.625) + 8 && rect.height < Math.floor(window.siyuan.config.editor.fontSize * 1.625) * 2 + 8)) {
2552 marginHeight = (rect.height - this.element.clientHeight) / 2;
2553 } else if ((nodeElement.getAttribute("data-type") === "NodeAttributeView" || element.getAttribute("data-type") === "NodeAttributeView") &&
2554 contentTop < rect.top) {
2555 marginHeight = 8;
2556 }
2557 }
2558 this.element.style.top = `${Math.max(rect.top, contentTop) + marginHeight}px`;
2559 let left = rect.left - this.element.clientWidth - space;
2560 if ((nodeElement.getAttribute("data-type") === "NodeBlockQueryEmbed" && this.element.childElementCount === 1)) {
2561 // 嵌入块为列表时
2562 left = nodeElement.getBoundingClientRect().left - this.element.clientWidth - space;
2563 } else if (element.classList.contains("av__row")) {
2564 // 为数据库行
2565 left = nodeElement.getBoundingClientRect().left - this.element.clientWidth - space + parseInt(getComputedStyle(nodeElement).paddingLeft);
2566 }
2567 this.element.style.left = `${left}px`;
2568 if (left < this.element.parentElement.getBoundingClientRect().left) {
2569 this.element.style.width = "24px";
2570 // 需加 2,否则和折叠标题无法对齐
2571 this.element.style.left = `${rect.left - this.element.clientWidth - space / 2 + 3}px`;
2572 html = "";
2573 Array.from(this.element.children).reverse().forEach((item, index) => {
2574 if (index !== 0) {
2575 (item.firstElementChild as HTMLElement).style.height = "14px";
2576 }
2577 html += item.outerHTML;
2578 });
2579 this.element.innerHTML = html;
2580 } else {
2581 this.element.querySelectorAll("svg").forEach(item => {
2582 item.style.height = "";
2583 });
2584 }
2585 }
2586}