A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 2586 lines 126 kB view raw
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}