A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 1781 lines 72 kB view raw
1import { 2 copyPlainText, 3 isMac, 4 isNotCtrl, 5 isOnlyMeta, 6 updateHotkeyTip, 7 writeText 8} from "../../protyle/util/compatibility"; 9import {matchAuxiliaryHotKey, matchHotKey} from "../../protyle/util/hotKey"; 10import { 11 hasClosestBlock, 12 hasClosestByAttribute, 13 hasClosestByClassName, 14 hasTopClosestByTag, 15} from "../../protyle/util/hasClosest"; 16import {newFile} from "../../util/newFile"; 17import {Constants} from "../../constants"; 18import {openSetting} from "../../config"; 19import {getInstanceById, saveLayout} from "../../layout/util"; 20import {getActiveTab, getDockByType, switchTabByIndex} from "../../layout/tabUtil"; 21import {Tab} from "../../layout/Tab"; 22import {Editor} from "../../editor"; 23import {setEditMode} from "../../protyle/util/setEditMode"; 24import {rename} from "../../editor/rename"; 25import {Files} from "../../layout/dock/Files"; 26import {newDailyNote} from "../../util/mount"; 27import {hideElements} from "../../protyle/ui/hideElements"; 28import {fetchPost} from "../../util/fetch"; 29import {goBack, goForward} from "../../util/backForward"; 30import {onGet} from "../../protyle/util/onGet"; 31import {getDisplayName, getNotebookName} from "../../util/pathName"; 32import {openFileById} from "../../editor/util"; 33import {getAllDocks, getAllModels, getAllTabs} from "../../layout/getAll"; 34import {focusBlock, focusByOffset, focusByRange, getSelectionOffset} from "../../protyle/util/selection"; 35import {initFileMenu, initNavigationMenu} from "../../menus/navigation"; 36import {bindMenuKeydown} from "../../menus/Menu"; 37import {Dialog} from "../../dialog"; 38import {unicode2Emoji} from "../../emoji"; 39import {deleteFiles} from "../../editor/deleteFile"; 40import {escapeHtml} from "../../util/escape"; 41import {syncGuide} from "../../sync/syncGuide"; 42import {duplicateBlock, getStartEndElement, goEnd, goHome} from "../../protyle/wysiwyg/commonHotkey"; 43import {getNextFileLi, getPreviousFileLi} from "../../protyle/wysiwyg/getBlock"; 44import {Backlink} from "../../layout/dock/Backlink"; 45/// #if !BROWSER 46import {setZoom} from "../../layout/topBar"; 47import {ipcRenderer} from "electron"; 48/// #endif 49import {openHistory} from "../../history/history"; 50import {openCard, openCardByData} from "../../card/openCard"; 51import {lockScreen} from "../../dialog/processSystem"; 52import {isWindow} from "../../util/functions"; 53import {reloadProtyle} from "../../protyle/util/reload"; 54import {fullscreen} from "../../protyle/breadcrumb/action"; 55import {openRecentDocs} from "../../business/openRecentDocs"; 56import {App} from "../../index"; 57import {openBacklink, openGraph, openOutline, toggleDockBar} from "../../layout/dock/util"; 58import {workspaceMenu} from "../../menus/workspace"; 59import {resize} from "../../protyle/util/resize"; 60import {Search} from "../../search"; 61import {Custom} from "../../layout/dock/Custom"; 62import {transaction} from "../../protyle/wysiwyg/transaction"; 63import {quickMakeCard} from "../../card/makeCard"; 64import {getContentByInlineHTML} from "../../protyle/wysiwyg/keydown"; 65import {searchKeydown} from "./searchKeydown"; 66import {historyKeydown} from "../../history/keydown"; 67import {zoomOut} from "../../menus/protyle"; 68import {getPlainText} from "../../protyle/util/paste"; 69import {commandPanel, execByCommand} from "./command/panel"; 70import {filterHotkey} from "./commonHotkey"; 71import {setReadOnly} from "../../config/util/setReadOnly"; 72import {copyPNGByLink} from "../../menus/util"; 73import {globalCommand} from "./command/global"; 74import {duplicateCompletely} from "../../protyle/render/av/action"; 75import {copyTextByType} from "../../protyle/toolbar/util"; 76import {onlyProtyleCommand} from "./command/protyle"; 77import {cancelDrag} from "./dragover"; 78import {bindAVPanelKeydown} from "../../protyle/render/av/keydown"; 79 80const switchDialogEvent = (app: App, event: MouseEvent) => { 81 event.preventDefault(); 82 let target = event.target as HTMLElement; 83 while (target !== switchDialog.element) { 84 if (target.classList.contains("b3-list-item")) { 85 const currentType = target.getAttribute("data-type"); 86 if (currentType) { 87 if (currentType === "riffCard") { 88 openCard(app); 89 } else { 90 getDockByType(currentType).toggleModel(currentType, true); 91 } 92 } else { 93 const currentId = target.getAttribute("data-id"); 94 getAllTabs().find(item => { 95 if (item.id === currentId) { 96 item.parent.switchTab(item.headElement); 97 item.parent.showHeading(); 98 return true; 99 } 100 }); 101 } 102 switchDialog.destroy(); 103 switchDialog = undefined; 104 break; 105 } 106 target = target.parentElement; 107 } 108}; 109 110const dialogArrow = (app: App, element: HTMLElement, event: KeyboardEvent) => { 111 let currentLiElement = element.querySelector(".b3-list-item--focus"); 112 if (currentLiElement) { 113 currentLiElement.classList.remove("b3-list-item--focus"); 114 if (event.key === "ArrowUp") { 115 if (currentLiElement.previousElementSibling) { 116 currentLiElement.previousElementSibling.classList.add("b3-list-item--focus"); 117 } else { 118 currentLiElement.parentElement.lastElementChild.classList.add("b3-list-item--focus"); 119 } 120 } else if (event.key === "ArrowDown") { 121 if (currentLiElement.nextElementSibling) { 122 currentLiElement.nextElementSibling.classList.add("b3-list-item--focus"); 123 } else { 124 currentLiElement.parentElement.firstElementChild.classList.add("b3-list-item--focus"); 125 } 126 } else if (event.key === "ArrowLeft" || event.key === "ArrowRight") { 127 const sideElement = currentLiElement.parentElement.previousElementSibling || currentLiElement.parentElement.nextElementSibling; 128 if (sideElement) { 129 const tempLiElement = sideElement.querySelector(`[data-index="${currentLiElement.getAttribute("data-index")}"]`) || sideElement.lastElementChild; 130 if (tempLiElement) { 131 tempLiElement.classList.add("b3-list-item--focus"); 132 } else { 133 currentLiElement.classList.add("b3-list-item--focus"); 134 } 135 } else { 136 currentLiElement.classList.add("b3-list-item--focus"); 137 } 138 } else if (event.key === "Enter") { 139 const currentType = currentLiElement.getAttribute("data-type"); 140 if (currentType) { 141 if (currentType === "riffCard") { 142 openCard(app); 143 } else { 144 getDockByType(currentType).toggleModel(currentType, true); 145 } 146 } else { 147 openFileById({ 148 app, 149 id: currentLiElement.getAttribute("data-node-id"), 150 action: [Constants.CB_GET_FOCUS, Constants.CB_GET_SCROLL] 151 }); 152 } 153 hideElements(["dialog"]); 154 return; 155 } 156 currentLiElement = element.querySelector(".b3-list-item--focus"); 157 const rootId = currentLiElement.getAttribute("data-node-id"); 158 const pathElement = element.querySelector(".switch-doc__path"); 159 if (rootId) { 160 fetchPost("/api/filetree/getFullHPathByID", { 161 id: rootId 162 }, (response) => { 163 pathElement.innerHTML = escapeHtml(response.data); 164 }); 165 } else { 166 pathElement.innerHTML = currentLiElement.querySelector(".b3-list-item__text").innerHTML; 167 } 168 const currentRect = currentLiElement.getBoundingClientRect(); 169 const currentParentRect = currentLiElement.parentElement.getBoundingClientRect(); 170 if (currentRect.top < currentParentRect.top) { 171 currentLiElement.scrollIntoView(true); 172 } else if (currentRect.bottom > currentParentRect.bottom) { 173 currentLiElement.scrollIntoView(false); 174 } 175 } 176}; 177 178const editKeydown = (app: App, event: KeyboardEvent) => { 179 let protyle: IProtyle; 180 let range: Range; 181 if (getSelection().rangeCount > 0) { 182 range = getSelection().getRangeAt(0); 183 } 184 const activePanelElement = document.querySelector(".layout__tab--active"); 185 let isFileFocus = false; 186 if (activePanelElement && activePanelElement.classList.contains("sy__file")) { 187 isFileFocus = true; 188 } 189 if (range) { 190 window.siyuan.dialogs.find(item => { 191 if (item.editors) { 192 Object.keys(item.editors).find(key => { 193 if (item.editors[key].protyle.element.contains(range.startContainer)) { 194 protyle = item.editors[key].protyle; 195 // https://github.com/siyuan-note/siyuan/issues/9384 196 isFileFocus = false; 197 return true; 198 } 199 }); 200 if (protyle) { 201 return true; 202 } 203 } 204 }); 205 } 206 const activeTab = getActiveTab(); 207 if (!protyle && activeTab) { 208 if (activeTab.model instanceof Editor) { 209 protyle = activeTab.model.editor.protyle; 210 } else if (activeTab.model instanceof Search) { 211 if (activeTab.model.element.querySelector("#searchUnRefPanel").classList.contains("fn__none")) { 212 protyle = activeTab.model.editors.edit.protyle; 213 } else { 214 protyle = activeTab.model.editors.unRefEdit.protyle; 215 } 216 } else if (activeTab.model instanceof Custom && activeTab.model.editors?.length > 0) { 217 if (range) { 218 activeTab.model.editors.find(item => { 219 if (item.protyle.element.contains(range.startContainer)) { 220 protyle = item.protyle; 221 return true; 222 } 223 }); 224 } 225 } 226 if (!protyle) { 227 return; 228 } 229 } else if (!protyle) { 230 if (!protyle && range) { 231 window.siyuan.blockPanels.find(item => { 232 item.editors.find(editorItem => { 233 if (editorItem.protyle.element.contains(range.startContainer)) { 234 protyle = editorItem.protyle; 235 return true; 236 } 237 }); 238 if (protyle) { 239 return true; 240 } 241 }); 242 } 243 const models = getAllModels(); 244 if (!protyle) { 245 models.backlink.find(item => { 246 if (item.element.classList.contains("layout__tab--active")) { 247 if (range) { 248 item.editors.find(editor => { 249 if (editor.protyle.element.contains(range.startContainer)) { 250 protyle = editor.protyle; 251 return true; 252 } 253 }); 254 } 255 if (!protyle && item.editors.length > 0) { 256 protyle = item.editors[0].protyle; 257 } 258 return true; 259 } 260 }); 261 } 262 if (!protyle) { 263 models.editor.find(item => { 264 if (item.parent.headElement.classList.contains("item--focus")) { 265 protyle = item.editor.protyle; 266 return true; 267 } 268 }); 269 } 270 if (!protyle) { 271 return false; 272 } 273 } 274 if (!isFileFocus && matchHotKey(window.siyuan.config.keymap.general.replace.custom, event)) { 275 execByCommand({ 276 command: "replace", 277 app, 278 protyle, 279 previousRange: range 280 }); 281 event.preventDefault(); 282 return true; 283 } 284 if (!isFileFocus && matchHotKey(window.siyuan.config.keymap.general.search.custom, event)) { 285 execByCommand({ 286 command: "search", 287 app, 288 protyle, 289 previousRange: range 290 }); 291 event.preventDefault(); 292 return true; 293 } 294 if (!isFileFocus && matchHotKey(window.siyuan.config.keymap.editor.general.quickMakeCard.custom, event) && !window.siyuan.config.readonly) { 295 if (protyle.title?.editElement.contains(range.startContainer)) { 296 quickMakeCard(protyle, [protyle.title.element]); 297 } else { 298 const selectElement: Element[] = []; 299 protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select").forEach(item => { 300 selectElement.push(item); 301 }); 302 if (selectElement.length === 0) { 303 const nodeElement = hasClosestBlock(range.startContainer); 304 if (nodeElement) { 305 selectElement.push(nodeElement); 306 } 307 } 308 quickMakeCard(protyle, selectElement); 309 } 310 event.preventDefault(); 311 return true; 312 } 313 if (!isFileFocus && matchHotKey(window.siyuan.config.keymap.general.addToDatabase.custom, event)) { 314 execByCommand({ 315 command: "addToDatabase", 316 app, 317 protyle, 318 previousRange: range 319 }); 320 event.preventDefault(); 321 return true; 322 } 323 if (!isFileFocus && matchHotKey(window.siyuan.config.keymap.editor.general.spaceRepetition.custom, event) && !window.siyuan.config.readonly) { 324 fetchPost("/api/riff/getTreeRiffDueCards", {rootID: protyle.block.rootID}, (response) => { 325 openCardByData(app, response.data, "doc", protyle.block.rootID, protyle.title?.editElement.textContent || window.siyuan.languages.untitled); 326 }); 327 event.preventDefault(); 328 return true; 329 } 330 if (!isFileFocus && matchHotKey(window.siyuan.config.keymap.general.move.custom, event)) { 331 execByCommand({ 332 command: "move", 333 app, 334 protyle, 335 previousRange: range 336 }); 337 event.preventDefault(); 338 return true; 339 } 340 341 if (!isFileFocus && !event.repeat && !protyle.disabled && 342 matchHotKey(window.siyuan.config.keymap.editor.general.duplicate.custom, event)) { 343 event.preventDefault(); 344 event.stopPropagation(); 345 let selectsElement: HTMLElement[] = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")); 346 if (selectsElement.length === 0) { 347 const nodeElement = hasClosestBlock(range.startContainer); 348 if (nodeElement) { 349 selectsElement = [nodeElement]; 350 } 351 } 352 duplicateBlock(selectsElement, protyle); 353 return true; 354 } 355 356 const target = event.target as HTMLElement; 357 if (target.tagName !== "TABLE" && ["INPUT", "TEXTAREA"].includes(target.tagName)) { 358 return false; 359 } 360 // ctrl+home 光标移动到顶 361 if (!event.altKey && !event.shiftKey && isOnlyMeta(event) && event.key === "Home") { 362 goHome(protyle); 363 hideElements(["select"], protyle); 364 event.stopPropagation(); 365 event.preventDefault(); 366 return; 367 } 368 // ctrl+end 光标移动到尾 369 if (!event.altKey && !event.shiftKey && isOnlyMeta(event) && event.key === "End") { 370 goEnd(protyle); 371 hideElements(["select"], protyle); 372 event.stopPropagation(); 373 event.preventDefault(); 374 return; 375 } 376 if (matchHotKey(window.siyuan.config.keymap.editor.general.exitFocus.custom, event)) { 377 event.preventDefault(); 378 zoomOut({protyle, id: protyle.block.rootID, focusId: protyle.block.id}); 379 return true; 380 } 381 if (matchHotKey(window.siyuan.config.keymap.editor.general.switchReadonly.custom, event)) { 382 event.preventDefault(); 383 onlyProtyleCommand({ 384 protyle, 385 command: "switchReadonly", 386 previousRange: range, 387 }); 388 return true; 389 } 390 if (matchHotKey(window.siyuan.config.keymap.editor.general.switchAdjust.custom, event)) { 391 event.preventDefault(); 392 onlyProtyleCommand({ 393 protyle, 394 command: "switchAdjust", 395 previousRange: range, 396 }); 397 return true; 398 } 399 400 if (matchHotKey(window.siyuan.config.keymap.editor.general.backlinks.custom, event)) { 401 event.preventDefault(); 402 if (range) { 403 const refElement = hasClosestByAttribute(range.startContainer, "data-type", "block-ref"); 404 if (refElement) { 405 openBacklink({ 406 app: protyle.app, 407 blockId: refElement.dataset.id, 408 }); 409 return true; 410 } 411 } 412 openBacklink({ 413 app: protyle.app, 414 blockId: protyle.block.id, 415 rootId: protyle.block.rootID, 416 useBlockId: protyle.block.showAll, 417 title: protyle.title ? (protyle.title.editElement.textContent || window.siyuan.languages.untitled) : null, 418 }); 419 return true; 420 } 421 if (matchHotKey(window.siyuan.config.keymap.editor.general.graphView.custom, event)) { 422 event.preventDefault(); 423 if (range) { 424 const refElement = hasClosestByAttribute(range.startContainer, "data-type", "block-ref"); 425 if (refElement) { 426 openGraph({ 427 app: protyle.app, 428 blockId: refElement.dataset.id, 429 }); 430 return true; 431 } 432 } 433 openGraph({ 434 app: protyle.app, 435 blockId: protyle.block.id, 436 rootId: protyle.block.rootID, 437 useBlockId: protyle.block.showAll, 438 title: protyle.title ? (protyle.title.editElement.textContent || window.siyuan.languages.untitled) : null, 439 }); 440 return true; 441 } 442 if (matchHotKey(window.siyuan.config.keymap.editor.general.outline.custom, event)) { 443 event.preventDefault(); 444 const offset = getSelectionOffset(target); 445 openOutline({ 446 app, 447 rootId: protyle.block.rootID, 448 title: protyle.options.render.title ? (protyle.title.editElement.textContent || window.siyuan.languages.untitled) : "", 449 isPreview: !protyle.preview.element.classList.contains("fn__none") 450 }); 451 // switchWnd 后,range会被清空,需要重新设置 452 focusByOffset(target, offset.start, offset.end); 453 return true; 454 } 455 if (matchHotKey(window.siyuan.config.keymap.editor.general.copyPlainText.custom, event)) { 456 const nodeElement = hasClosestBlock(range.startContainer); 457 if (!nodeElement) { 458 return false; 459 } 460 if (range.toString() === "") { 461 const selectsElement: HTMLElement[] = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")); 462 let html = ""; 463 if (selectsElement.length === 0) { 464 selectsElement.push(nodeElement); 465 } 466 selectsElement.forEach(item => { 467 html += getPlainText(item) + "\n"; 468 }); 469 copyPlainText(html.trimEnd()); 470 } else { 471 copyPlainText(range.toString()); 472 } 473 event.preventDefault(); 474 return true; 475 } 476 if (matchHotKey(window.siyuan.config.keymap.editor.general.duplicateCompletely.custom, event)) { 477 const nodeElement = hasClosestBlock(range.startContainer); 478 if (!nodeElement || !nodeElement.classList.contains("av")) { 479 return false; 480 } 481 duplicateCompletely(protyle, nodeElement); 482 event.preventDefault(); 483 return true; 484 } 485 if (matchHotKey(window.siyuan.config.keymap.editor.general.refresh.custom, event)) { 486 reloadProtyle(protyle, true); 487 event.preventDefault(); 488 return true; 489 } 490 if (matchHotKey(window.siyuan.config.keymap.editor.general.fullscreen.custom, event)) { 491 fullscreen(protyle.element); 492 resize(protyle); 493 event.preventDefault(); 494 return true; 495 } 496 if (matchHotKey(window.siyuan.config.keymap.editor.general.preview.custom, event)) { 497 setEditMode(protyle, "preview"); 498 saveLayout(); 499 event.preventDefault(); 500 return true; 501 } 502 if (matchHotKey(window.siyuan.config.keymap.editor.general.wysiwyg.custom, event) && !protyle.options.backlinkData) { 503 setEditMode(protyle, "wysiwyg"); 504 protyle.scroll.lastScrollTop = 0; 505 fetchPost("/api/filetree/getDoc", { 506 id: protyle.block.id, 507 size: protyle.block.id === protyle.block.rootID ? window.siyuan.config.editor.dynamicLoadBlocks : Constants.SIZE_GET_MAX, 508 }, getResponse => { 509 onGet({ 510 data: getResponse, 511 protyle, 512 action: protyle.block.id === protyle.block.rootID ? [Constants.CB_GET_FOCUS, Constants.CB_GET_HTML, Constants.CB_GET_UNUNDO] : [Constants.CB_GET_ALL, Constants.CB_GET_FOCUS, Constants.CB_GET_UNUNDO, Constants.CB_GET_HTML] 513 }); 514 }); 515 saveLayout(); 516 event.preventDefault(); 517 return true; 518 } 519 if (range && !isFileFocus && matchHotKey(window.siyuan.config.keymap.editor.general.copyBlockRef.custom, event)) { 520 event.preventDefault(); 521 event.stopPropagation(); 522 if (hasClosestByClassName(range.startContainer, "protyle-title")) { 523 copyTextByType([protyle.block.rootID], "ref"); 524 } else { 525 const nodeElement = hasClosestBlock(range.startContainer); 526 if (!nodeElement) { 527 return false; 528 } 529 const selectImgElement = nodeElement.querySelector(".img--select"); 530 if (selectImgElement) { 531 copyPNGByLink(selectImgElement.querySelector("img").getAttribute("src")); 532 return true; 533 } 534 const ids = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")).map(item => item.getAttribute("data-node-id")); 535 if (ids.length > 0) { 536 copyTextByType(ids, "ref"); 537 return true; 538 } 539 if (range.toString() !== "") { 540 getContentByInlineHTML(range, (content) => { 541 writeText(`((${nodeElement.getAttribute("data-node-id")} "${content.trim()}"))`); 542 }); 543 } else { 544 copyTextByType([nodeElement.getAttribute("data-node-id")], "ref"); 545 } 546 } 547 return true; 548 } 549 if (hasClosestByClassName(target, "protyle-title__input")) { 550 return false; 551 } 552 // 没有光标时,无法撤销 https://ld246.com/article/1624021111567 553 if (matchHotKey(window.siyuan.config.keymap.editor.general.undo.custom, event)) { 554 protyle.undo.undo(protyle); 555 event.preventDefault(); 556 return true; 557 } 558 if (matchHotKey(window.siyuan.config.keymap.editor.general.redo.custom, event)) { 559 protyle.undo.redo(protyle); 560 event.preventDefault(); 561 return true; 562 } 563 return false; 564}; 565 566const fileTreeKeydown = (app: App, event: KeyboardEvent) => { 567 const dockFile = getDockByType("file"); 568 if (!dockFile) { 569 return false; 570 } 571 const files = dockFile.data.file as Files; 572 if (typeof dockFile.data.file === "boolean") { 573 return true; 574 } 575 576 if (matchHotKey(window.siyuan.config.keymap.general.selectOpen1.custom, event)) { 577 event.preventDefault(); 578 globalCommand("selectOpen1", app); 579 return; 580 } 581 582 if (!files.element.parentElement.classList.contains("layout__tab--active")) { 583 return false; 584 } 585 586 let matchCommand = false; 587 app.plugins.find(item => { 588 item.commands.find(command => { 589 if (command.fileTreeCallback && matchHotKey(command.customHotkey, event)) { 590 matchCommand = true; 591 command.fileTreeCallback(files); 592 return true; 593 } 594 }); 595 if (matchCommand) { 596 return true; 597 } 598 }); 599 if (matchCommand) { 600 return true; 601 } 602 603 const liElements = Array.from(files.element.querySelectorAll(".b3-list-item--focus")); 604 if (liElements.length === 0) { 605 if (event.key.startsWith("Arrow") && isNotCtrl(event)) { 606 const liElement = files.element.querySelector(".b3-list-item"); 607 if (liElement) { 608 liElement.classList.add("b3-list-item--focus"); 609 files.lastSelectedElement = liElement; 610 } 611 event.preventDefault(); 612 } 613 return false; 614 } 615 const topULElement = hasTopClosestByTag(liElements[0], "UL"); 616 if (!topULElement) { 617 return false; 618 } 619 const notebookId = topULElement.getAttribute("data-url"); 620 const pathString = liElements[0].getAttribute("data-path"); 621 const isFile = liElements[0].getAttribute("data-type") === "navigation-file"; 622 const ids: string[] = []; 623 liElements.forEach(item => { 624 if (item.getAttribute("data-type") === "navigation-file") { 625 ids.push(item.getAttribute("data-node-id")); 626 } 627 }); 628 629 if (matchHotKey(window.siyuan.config.keymap.editor.general.spaceRepetition.custom, event) && !window.siyuan.config.readonly) { 630 if (isFile) { 631 const id = liElements[0].getAttribute("data-node-id"); 632 fetchPost("/api/riff/getTreeRiffDueCards", {rootID: id}, (response) => { 633 openCardByData(app, response.data, "doc", id, getDisplayName(liElements[0].getAttribute("data-name"), false, true)); 634 }); 635 } else { 636 fetchPost("/api/riff/getNotebookRiffDueCards", {notebook: notebookId}, (response) => { 637 openCardByData(app, response.data, "notebook", notebookId, getNotebookName(notebookId)); 638 }); 639 } 640 event.preventDefault(); 641 return true; 642 } 643 644 if (matchHotKey(window.siyuan.config.keymap.editor.general.quickMakeCard.custom, event)) { 645 if (ids.length > 0) { 646 transaction(undefined, [{ 647 action: "addFlashcards", 648 deckID: Constants.QUICK_DECK_ID, 649 blockIDs: ids, 650 }], [{ 651 action: "removeFlashcards", 652 deckID: Constants.QUICK_DECK_ID, 653 blockIDs: ids, 654 }]); 655 } 656 event.preventDefault(); 657 return true; 658 } 659 660 if (matchHotKey(window.siyuan.config.keymap.general.addToDatabase.custom, event)) { 661 execByCommand({ 662 command: "addToDatabase", 663 app, 664 fileLiElements: liElements 665 }); 666 event.preventDefault(); 667 return true; 668 } 669 670 if (matchHotKey(window.siyuan.config.keymap.editor.general.rename.custom, event)) { 671 window.siyuan.menus.menu.remove(); 672 rename({ 673 notebookId, 674 path: pathString, 675 name: isFile ? getDisplayName(liElements[0].getAttribute("data-name"), false, true) : getNotebookName(notebookId), 676 type: isFile ? "file" : "notebook", 677 }); 678 event.preventDefault(); 679 return true; 680 } 681 682 if (matchHotKey("⌘/", event)) { 683 const liRect = liElements[0].getBoundingClientRect(); 684 if (isFile) { 685 initFileMenu(app, notebookId, pathString, liElements[0]).popup({ 686 x: liRect.right - 15, 687 y: liRect.top + 15 688 }); 689 } else { 690 initNavigationMenu(app, liElements[0] as HTMLElement).popup({x: liRect.right - 15, y: liRect.top + 15}); 691 } 692 return true; 693 } 694 695 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.duplicate.custom, event)) { 696 event.preventDefault(); 697 event.stopPropagation(); 698 ids.forEach(item => { 699 fetchPost("/api/filetree/duplicateDoc", { 700 id: item, 701 }); 702 }); 703 return true; 704 } 705 706 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.copyBlockRef.custom, event)) { 707 event.preventDefault(); 708 event.stopPropagation(); 709 copyTextByType(ids, "ref"); 710 return true; 711 } 712 713 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.copyBlockEmbed.custom, event)) { 714 event.preventDefault(); 715 event.stopPropagation(); 716 copyTextByType(ids, "blockEmbed"); 717 return true; 718 } 719 720 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.copyProtocol.custom, event)) { 721 event.preventDefault(); 722 event.stopPropagation(); 723 copyTextByType(ids, "protocol"); 724 return true; 725 } 726 727 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.copyProtocolInMd.custom, event)) { 728 event.preventDefault(); 729 event.stopPropagation(); 730 copyTextByType(ids, "protocolMd"); 731 return true; 732 } 733 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.copyHPath.custom, event)) { 734 event.preventDefault(); 735 event.stopPropagation(); 736 copyTextByType(ids, "hPath"); 737 return true; 738 } 739 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.copyID.custom, event)) { 740 event.preventDefault(); 741 event.stopPropagation(); 742 copyTextByType(ids, "id"); 743 return true; 744 } 745 746 if (isFile && matchHotKey(window.siyuan.config.keymap.general.move.custom, event)) { 747 window.siyuan.menus.menu.remove(); 748 execByCommand({ 749 command: "move", 750 app, 751 fileLiElements: liElements 752 }); 753 event.preventDefault(); 754 return true; 755 } 756 757 if (isFile && matchHotKey(window.siyuan.config.keymap.editor.general.insertRight.custom, event)) { 758 window.siyuan.menus.menu.remove(); 759 openFileById({ 760 app, 761 id: liElements[0].getAttribute("data-node-id"), 762 action: [Constants.CB_GET_FOCUS], 763 position: "right", 764 }); 765 event.preventDefault(); 766 return true; 767 } 768 769 if (matchHotKey(window.siyuan.config.keymap.general.replace.custom, event)) { 770 window.siyuan.menus.menu.remove(); 771 execByCommand({ 772 command: "replace", 773 app, 774 fileLiElements: liElements, 775 }); 776 event.preventDefault(); 777 return true; 778 } 779 if (matchHotKey(window.siyuan.config.keymap.general.search.custom, event)) { 780 window.siyuan.menus.menu.remove(); 781 execByCommand({ 782 command: "search", 783 app, 784 fileLiElements: liElements, 785 }); 786 event.preventDefault(); 787 return true; 788 } 789 const target = event.target as HTMLElement; 790 if (["INPUT", "TEXTAREA"].includes(target.tagName) || 791 hasClosestByAttribute(target, "contenteditable", null) || 792 hasClosestByClassName(target, "protyle", true)) { 793 return false; 794 } 795 if (event.shiftKey) { 796 if (event.key === "ArrowUp") { 797 const startEndElement = getStartEndElement(liElements); 798 let previousElement: Element; 799 if (startEndElement.startElement.getBoundingClientRect().top >= startEndElement.endElement.getBoundingClientRect().top) { 800 previousElement = getPreviousFileLi(startEndElement.endElement) as Element; 801 if (previousElement) { 802 previousElement.classList.add("b3-list-item--focus"); 803 previousElement.setAttribute("select-end", "true"); 804 startEndElement.endElement.removeAttribute("select-end"); 805 } 806 } else { 807 startEndElement.endElement.classList.remove("b3-list-item--focus"); 808 startEndElement.endElement.removeAttribute("select-end"); 809 previousElement = getPreviousFileLi(startEndElement.endElement) as Element; 810 if (previousElement) { 811 previousElement.setAttribute("select-end", "true"); 812 } 813 } 814 if (previousElement) { 815 const previousRect = previousElement.getBoundingClientRect(); 816 const fileRect = files.element.getBoundingClientRect(); 817 if (previousRect.top < fileRect.top || previousRect.bottom > fileRect.bottom) { 818 previousElement.scrollIntoView(previousRect.top < fileRect.top); 819 } 820 } 821 } else if (event.key === "ArrowDown") { 822 const startEndElement = getStartEndElement(liElements); 823 let nextElement: Element; 824 if (startEndElement.startElement.getBoundingClientRect().top <= startEndElement.endElement.getBoundingClientRect().top) { 825 nextElement = getNextFileLi(startEndElement.endElement) as Element; 826 if (nextElement) { 827 nextElement.classList.add("b3-list-item--focus"); 828 nextElement.setAttribute("select-end", "true"); 829 startEndElement.endElement.removeAttribute("select-end"); 830 } 831 } else { 832 startEndElement.endElement.classList.remove("b3-list-item--focus"); 833 startEndElement.endElement.removeAttribute("select-end"); 834 nextElement = getNextFileLi(startEndElement.endElement) as Element; 835 if (nextElement) { 836 nextElement.setAttribute("select-end", "true"); 837 } 838 } 839 if (nextElement) { 840 const nextRect = nextElement.getBoundingClientRect(); 841 const fileRect = files.element.getBoundingClientRect(); 842 if (nextRect.top < fileRect.top || nextRect.bottom > fileRect.bottom) { 843 nextElement.scrollIntoView(nextRect.top < fileRect.top); 844 } 845 } 846 } 847 return; 848 } else if (isNotCtrl(event)) { 849 files.element.querySelector('[select-end="true"]')?.removeAttribute("select-end"); 850 files.element.querySelector('[select-start="true"]')?.removeAttribute("select-start"); 851 if ((event.key === "ArrowRight" && !liElements[0].querySelector(".b3-list-item__arrow--open") && !liElements[0].querySelector(".b3-list-item__toggle").classList.contains("fn__hidden")) || 852 (event.key === "ArrowLeft" && liElements[0].querySelector(".b3-list-item__arrow--open"))) { 853 files.getLeaf(liElements[0], notebookId); 854 liElements.forEach((item, index) => { 855 if (index !== 0) { 856 item.classList.remove("b3-list-item--focus"); 857 } 858 }); 859 event.preventDefault(); 860 return true; 861 } 862 if (event.key === "ArrowLeft") { 863 let parentElement = liElements[0].parentElement.previousElementSibling; 864 if (parentElement) { 865 if (parentElement.tagName !== "LI") { 866 parentElement = files.element.querySelector(".b3-list-item"); 867 } 868 liElements.forEach((item) => { 869 item.classList.remove("b3-list-item--focus"); 870 }); 871 parentElement.classList.add("b3-list-item--focus"); 872 files.lastSelectedElement = parentElement; 873 const parentRect = parentElement.getBoundingClientRect(); 874 const fileRect = files.element.getBoundingClientRect(); 875 if (parentRect.top < fileRect.top || parentRect.bottom > fileRect.bottom) { 876 parentElement.scrollIntoView(parentRect.top < fileRect.top); 877 } 878 } 879 event.preventDefault(); 880 return true; 881 } 882 if (event.key === "ArrowDown" || event.key === "ArrowRight") { 883 let nextElement = liElements[0]; 884 while (nextElement) { 885 if (nextElement.nextElementSibling) { 886 if (nextElement.nextElementSibling.tagName === "UL") { 887 nextElement = nextElement.nextElementSibling.firstElementChild; 888 } else { 889 nextElement = nextElement.nextElementSibling; 890 } 891 break; 892 } else { 893 if (nextElement.parentElement.classList.contains("fn__flex-1")) { 894 break; 895 } else { 896 nextElement = nextElement.parentElement; 897 } 898 } 899 } 900 if (nextElement.classList.contains("b3-list-item")) { 901 liElements.forEach((item) => { 902 item.classList.remove("b3-list-item--focus"); 903 }); 904 nextElement.classList.add("b3-list-item--focus"); 905 files.lastSelectedElement = nextElement; 906 const nextRect = nextElement.getBoundingClientRect(); 907 const fileRect = files.element.getBoundingClientRect(); 908 if (nextRect.top < fileRect.top || nextRect.bottom > fileRect.bottom) { 909 nextElement.scrollIntoView(nextRect.top < fileRect.top); 910 } 911 } 912 event.preventDefault(); 913 return true; 914 } 915 if (event.key === "ArrowUp") { 916 let previousElement = liElements[0]; 917 while (previousElement) { 918 if (previousElement.previousElementSibling) { 919 if (previousElement.previousElementSibling.tagName === "LI") { 920 previousElement = previousElement.previousElementSibling; 921 } else { 922 const liElements = previousElement.previousElementSibling.querySelectorAll(".b3-list-item"); 923 previousElement = liElements[liElements.length - 1]; 924 } 925 break; 926 } else { 927 if (previousElement.parentElement.classList.contains("fn__flex-1")) { 928 break; 929 } else { 930 previousElement = previousElement.parentElement; 931 } 932 } 933 } 934 if (previousElement.classList.contains("b3-list-item")) { 935 liElements.forEach((item) => { 936 item.classList.remove("b3-list-item--focus"); 937 }); 938 previousElement.classList.add("b3-list-item--focus"); 939 files.lastSelectedElement = previousElement; 940 const previousRect = previousElement.getBoundingClientRect(); 941 const fileRect = files.element.getBoundingClientRect(); 942 if (previousRect.top < fileRect.top || previousRect.bottom > fileRect.bottom) { 943 previousElement.scrollIntoView(previousRect.top < fileRect.top); 944 } 945 } 946 event.preventDefault(); 947 return true; 948 } 949 } 950 if (event.key === "Delete" || (event.key === "Backspace" && isMac())) { 951 window.siyuan.menus.menu.remove(); 952 if (document.querySelector(`.b3-dialog--open[data-key="${Constants.DIALOG_CONFIRM}"]`)) { 953 return; 954 } 955 deleteFiles(liElements); 956 return true; 957 } 958 if (event.key === "Enter") { 959 window.siyuan.menus.menu.remove(); 960 liElements.forEach(item => { 961 if (item.getAttribute("data-type") === "navigation-file") { 962 openFileById({app, id: item.getAttribute("data-node-id"), action: [Constants.CB_GET_FOCUS]}); 963 } else { 964 const itemTopULElement = hasTopClosestByTag(item, "UL"); 965 if (itemTopULElement) { 966 files.getLeaf(item, itemTopULElement.getAttribute("data-url")); 967 } 968 } 969 }); 970 return true; 971 } 972}; 973 974const panelTreeKeydown = (app: App, event: KeyboardEvent) => { 975 // 面板折叠展开操作 976 const target = event.target as HTMLElement; 977 if (["INPUT", "TEXTAREA"].includes(target.tagName) || 978 hasClosestByAttribute(target, "contenteditable", null) || 979 hasClosestByClassName(target, "protyle", true)) { 980 return false; 981 } 982 983 let activePanelElement = document.querySelector(".layout__tab--active"); 984 if (!activePanelElement) { 985 Array.from(document.querySelectorAll(".layout__wnd--active .layout-tab-container > div")).find(item => { 986 if (!item.classList.contains("fn__none") && item.className.indexOf("sy__") > -1) { 987 activePanelElement = item; 988 return true; 989 } 990 }); 991 } 992 if (!activePanelElement) { 993 return false; 994 } 995 if (activePanelElement.className.indexOf("sy__") === -1) { 996 return false; 997 } 998 999 let matchCommand = false; 1000 app.plugins.find(item => { 1001 item.commands.find(command => { 1002 if (command.dockCallback && matchHotKey(command.customHotkey, event)) { 1003 matchCommand = true; 1004 command.dockCallback(activePanelElement as HTMLElement); 1005 return true; 1006 } 1007 }); 1008 if (matchCommand) { 1009 return true; 1010 } 1011 }); 1012 if (matchCommand) { 1013 return true; 1014 } 1015 if (!matchHotKey(window.siyuan.config.keymap.editor.general.collapse.custom, event) && 1016 !matchHotKey(window.siyuan.config.keymap.editor.general.expand.custom, event) && 1017 !event.key.startsWith("Arrow") && event.key !== "Enter") { 1018 return false; 1019 } 1020 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.collapse.custom, event)) { 1021 const collapseElement = activePanelElement.querySelector('.block__icon[data-type="collapse"]'); 1022 if (collapseElement) { 1023 collapseElement.dispatchEvent(new CustomEvent("click")); 1024 event.preventDefault(); 1025 return true; 1026 } 1027 } 1028 if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.expand.custom, event)) { 1029 const expandElement = activePanelElement.querySelector('.block__icon[data-type="expand"]'); 1030 if (expandElement) { 1031 expandElement.dispatchEvent(new CustomEvent("click")); 1032 event.preventDefault(); 1033 return true; 1034 } 1035 } 1036 if (activePanelElement.classList.contains("sy__inbox") || 1037 activePanelElement.classList.contains("sy__globalGraph") || 1038 activePanelElement.classList.contains("sy__graph")) { 1039 return false; 1040 } 1041 const model = (getInstanceById(activePanelElement.getAttribute("data-id"), window.siyuan.layout.layout) as Tab)?.model; 1042 if (!model) { 1043 return false; 1044 } 1045 let activeItemElement = activePanelElement.querySelector(".b3-list-item--focus"); 1046 if (!activeItemElement) { 1047 activeItemElement = activePanelElement.querySelector(".b3-list .b3-list-item"); 1048 if (activeItemElement) { 1049 activeItemElement.classList.add("b3-list-item--focus"); 1050 } 1051 return false; 1052 } 1053 1054 let tree = (model as Backlink).tree; 1055 if (activeItemElement.parentElement.parentElement.classList.contains("backlinkMList")) { 1056 tree = (model as Backlink).mTree; 1057 } 1058 if (!tree) { 1059 return false; 1060 } 1061 if (event.key === "Enter") { 1062 tree.click(activeItemElement); 1063 event.preventDefault(); 1064 return true; 1065 } 1066 const arrowElement = activeItemElement.querySelector(".b3-list-item__arrow"); 1067 if ((event.key === "ArrowRight" && !arrowElement.classList.contains("b3-list-item__arrow--open") && !arrowElement.parentElement.classList.contains("fn__hidden")) || 1068 (event.key === "ArrowLeft" && arrowElement.classList.contains("b3-list-item__arrow--open") && !arrowElement.parentElement.classList.contains("fn__hidden"))) { 1069 tree.toggleBlocks(activeItemElement); 1070 event.preventDefault(); 1071 return true; 1072 } 1073 const ulElement = hasClosestByClassName(activeItemElement, "b3-list"); 1074 if (!ulElement) { 1075 return false; 1076 } 1077 if (event.key === "ArrowLeft") { 1078 let parentElement = activeItemElement.parentElement.previousElementSibling; 1079 if (parentElement) { 1080 if (parentElement.tagName !== "LI") { 1081 parentElement = ulElement.querySelector(".b3-list-item"); 1082 } 1083 activeItemElement.classList.remove("b3-list-item--focus"); 1084 parentElement.classList.add("b3-list-item--focus"); 1085 const parentRect = parentElement.getBoundingClientRect(); 1086 const scrollRect = ulElement.parentElement.getBoundingClientRect(); 1087 if (parentRect.top < scrollRect.top || parentRect.bottom > scrollRect.bottom) { 1088 parentElement.scrollIntoView(parentRect.top < scrollRect.top); 1089 } 1090 } 1091 event.preventDefault(); 1092 return true; 1093 } 1094 if (event.key === "ArrowDown" || event.key === "ArrowRight") { 1095 let nextElement = activeItemElement; 1096 while (nextElement) { 1097 if (nextElement.nextElementSibling) { 1098 if (nextElement.nextElementSibling.tagName === "UL") { 1099 if (nextElement.nextElementSibling.classList.contains("fn__none")) { // 遇到折叠内容 1100 if (nextElement.nextElementSibling.nextElementSibling) { 1101 nextElement = nextElement.nextElementSibling.nextElementSibling; 1102 } 1103 } else { 1104 nextElement = nextElement.nextElementSibling.firstElementChild; 1105 } 1106 } else if (nextElement.nextElementSibling.classList.contains("protyle")) { // backlink 1107 if (nextElement.nextElementSibling.nextElementSibling) { 1108 nextElement = nextElement.nextElementSibling.nextElementSibling; 1109 } 1110 } else { 1111 nextElement = nextElement.nextElementSibling; 1112 } 1113 break; 1114 } else { 1115 if (nextElement.parentElement.classList.contains("fn__flex-1")) { 1116 break; 1117 } else { 1118 nextElement = nextElement.parentElement; 1119 } 1120 } 1121 } 1122 if (nextElement.classList.contains("b3-list-item") && !nextElement.classList.contains("b3-list-item--focus")) { 1123 activeItemElement.classList.remove("b3-list-item--focus"); 1124 nextElement.classList.add("b3-list-item--focus"); 1125 const nextRect = nextElement.getBoundingClientRect(); 1126 const scrollRect = ulElement.parentElement.getBoundingClientRect(); 1127 if (nextRect.top < scrollRect.top || nextRect.bottom > scrollRect.bottom) { 1128 nextElement.scrollIntoView(nextRect.top < scrollRect.top); 1129 } 1130 } 1131 event.preventDefault(); 1132 return true; 1133 } 1134 if (event.key === "ArrowUp") { 1135 let previousElement = activeItemElement; 1136 while (previousElement) { 1137 if (previousElement.previousElementSibling) { 1138 if (previousElement.previousElementSibling.tagName === "LI") { 1139 previousElement = previousElement.previousElementSibling; 1140 } else if (previousElement.previousElementSibling.classList.contains("protyle")) { 1141 if (previousElement.previousElementSibling.previousElementSibling) { 1142 previousElement = previousElement.previousElementSibling.previousElementSibling; 1143 } 1144 } else if (previousElement.previousElementSibling.tagName === "UL" && previousElement.previousElementSibling.classList.contains("fn__none")) { // 遇到折叠内容 1145 if (previousElement.previousElementSibling.previousElementSibling) { 1146 previousElement = previousElement.previousElementSibling.previousElementSibling; 1147 } 1148 } else { 1149 const liElements = previousElement.previousElementSibling.querySelectorAll(".b3-list-item"); 1150 previousElement = liElements[liElements.length - 1]; 1151 } 1152 break; 1153 } else { 1154 if (previousElement.parentElement.classList.contains("fn__flex-1")) { 1155 break; 1156 } else { 1157 previousElement = previousElement.parentElement; 1158 } 1159 } 1160 } 1161 if (previousElement.classList.contains("b3-list-item") && !previousElement.classList.contains("b3-list-item--focus")) { 1162 activeItemElement.classList.remove("b3-list-item--focus"); 1163 previousElement.classList.add("b3-list-item--focus"); 1164 const previousRect = previousElement.getBoundingClientRect(); 1165 const scrollRect = ulElement.parentElement.getBoundingClientRect(); 1166 if (previousRect.top < scrollRect.top || previousRect.bottom > scrollRect.bottom) { 1167 previousElement.scrollIntoView(previousRect.top < scrollRect.top); 1168 } 1169 } 1170 event.preventDefault(); 1171 return true; 1172 } 1173 return false; 1174}; 1175 1176let switchDialog: Dialog; 1177export const windowKeyDown = (app: App, event: KeyboardEvent) => { 1178 if (filterHotkey(event, app)) { 1179 return; 1180 } 1181 if (switchDialog && 1182 (matchAuxiliaryHotKey(window.siyuan.config.keymap.general.goToEditTabNext.custom, event) || 1183 matchAuxiliaryHotKey(window.siyuan.config.keymap.general.goToEditTabPrev.custom, event)) 1184 && event.key.startsWith("Arrow")) { 1185 dialogArrow(app, switchDialog.element, event); 1186 return; 1187 } 1188 1189 if (searchKeydown(app, event)) { 1190 event.preventDefault(); 1191 event.stopPropagation(); 1192 return; 1193 } 1194 1195 const isTabWindow = isWindow(); 1196 if (matchHotKey(window.siyuan.config.keymap.general.goToEditTabNext.custom, event) || 1197 matchHotKey(window.siyuan.config.keymap.general.goToEditTabPrev.custom, event)) { 1198 if (switchDialog && switchDialog.element.parentElement) { 1199 return; 1200 } 1201 let tabHtml = ""; 1202 let currentTabElement = document.querySelector(".layout__wnd--active ul.layout-tab-bar > .item--focus"); 1203 if (!currentTabElement) { 1204 currentTabElement = document.querySelector("ul.layout-tab-bar > .item--focus"); 1205 } 1206 if (currentTabElement) { 1207 const currentId = currentTabElement.getAttribute("data-id"); 1208 getAllTabs().sort((itemA, itemB) => { 1209 return itemA.headElement.getAttribute("data-activetime") > itemB.headElement.getAttribute("data-activetime") ? -1 : 1; 1210 }).forEach((item, index) => { 1211 let icon = `<svg class="b3-list-item__graphic"><use xlink:href="#${item.icon}"></use></svg>`; 1212 let rootId = ""; 1213 const initData = item.headElement.getAttribute("data-initdata"); 1214 if (item.model instanceof Editor) { 1215 rootId = ` data-node-id="${item.model.editor.protyle.block.rootID}"`; 1216 icon = unicode2Emoji(item.docIcon || window.siyuan.storage[Constants.LOCAL_IMAGES].file, "b3-list-item__graphic", true); 1217 } else if (initData) { 1218 const initDataObj = JSON.parse(initData); 1219 if (initDataObj.instance === "Editor") { 1220 rootId = ` data-node-id="${initDataObj.rootId}"`; 1221 icon = unicode2Emoji(item.docIcon || window.siyuan.storage[Constants.LOCAL_IMAGES].file, "b3-list-item__graphic", true); 1222 } 1223 } 1224 tabHtml += `<li data-index="${index}" data-id="${item.id}"${rootId} class="b3-list-item${currentId === item.id ? " b3-list-item--focus" : ""}"${currentId === item.id ? ' data-original="true"' : ""}>${icon}<span class="b3-list-item__text">${escapeHtml(item.title)}</span></li>`; 1225 }); 1226 } 1227 let dockHtml = ""; 1228 if (!isTabWindow) { 1229 dockHtml = `<ul class="b3-list b3-list--background" style="overflow: auto;width: 200px;"> 1230<li data-type="riffCard" data-index="0" class="b3-list-item${!tabHtml ? " b3-list-item--focus" : ""}"> 1231 <svg class="b3-list-item__graphic"><use xlink:href="#iconRiffCard"></use></svg> 1232 <span class="b3-list-item__text">${window.siyuan.languages.riffCard}</span> 1233 <span class="b3-list-item__meta">${updateHotkeyTip(window.siyuan.config.keymap.general.riffCard.custom)}</span> 1234</li>`; 1235 getAllDocks().forEach((item, index) => { 1236 dockHtml += `<li data-type="${item.type}" data-index="${index + 1}" class="b3-list-item"> 1237 <svg class="b3-list-item__graphic"><use xlink:href="#${item.icon}"></use></svg> 1238 <span class="b3-list-item__text">${item.title}</span> 1239 <span class="b3-list-item__meta">${updateHotkeyTip(item.hotkey || "")}</span> 1240</li>`; 1241 }); 1242 dockHtml = dockHtml + "</ul>"; 1243 } 1244 hideElements(["dialog"]); 1245 switchDialog = new Dialog({ 1246 positionId: Constants.DIALOG_SWITCHTAB, 1247 title: window.siyuan.languages.switchTab, 1248 content: `<div class="fn__flex-column switch-doc"> 1249 <input style="opacity: 0;height: 0.1px;box-sizing: border-box;margin: 0;padding: 0;border: 0;"> 1250 <div class="fn__flex" style="overflow:auto;">${dockHtml} 1251 <ul${!isTabWindow ? "" : ' style="border-left:0"'} class="b3-list b3-list--background fn__flex-1">${tabHtml}</ul> 1252 </div> 1253 <div class="switch-doc__path"></div> 1254</div>`, 1255 }); 1256 switchDialog.element.setAttribute("data-key", Constants.DIALOG_SWITCHTAB); 1257 // 需移走光标,否则编辑器会继续监听并执行按键操作 1258 switchDialog.element.querySelector("input").focus(); 1259 if (isMac()) { 1260 switchDialog.element.addEventListener("contextmenu", (event) => { 1261 switchDialogEvent(app, event); 1262 }); 1263 } 1264 switchDialog.element.addEventListener("click", (event) => { 1265 switchDialogEvent(app, event); 1266 }); 1267 return; 1268 } 1269 1270 if (isNotCtrl(event) && !event.shiftKey && !event.altKey && 1271 (event.key.startsWith("Arrow") || event.key === "Enter")) { 1272 const openRecentDocsDialog = window.siyuan.dialogs.find(item => { 1273 if (item.element.getAttribute("data-key") === Constants.DIALOG_RECENTDOCS) { 1274 return true; 1275 } 1276 }); 1277 if (openRecentDocsDialog) { 1278 event.preventDefault(); 1279 dialogArrow(app, openRecentDocsDialog.element, event); 1280 return; 1281 } 1282 } 1283 1284 if (matchHotKey(window.siyuan.config.keymap.general.recentDocs.custom, event)) { 1285 openRecentDocs(); 1286 event.preventDefault(); 1287 return; 1288 } 1289 1290 if (bindMenuKeydown(event)) { 1291 event.preventDefault(); 1292 return; 1293 } 1294 1295 if (bindAVPanelKeydown(event)) { 1296 event.preventDefault(); 1297 return; 1298 } 1299 1300 if (["Home", "End", "ArrowUp", "ArrowDown"].includes(event.key)) { 1301 let matchDialog: Dialog; 1302 // 需找到最顶层的,因此不能用 find 1303 window.siyuan.dialogs.forEach(item => { 1304 if ([Constants.DIALOG_VIEWCARDS, Constants.DIALOG_HISTORYCOMPARE].includes(item.element.getAttribute("data-key"))) { 1305 matchDialog = item; 1306 } 1307 }); 1308 if (matchDialog) { 1309 if (matchDialog.element.getAttribute("data-key") === Constants.DIALOG_VIEWCARDS) { 1310 matchDialog.element.dispatchEvent(new CustomEvent("click", {detail: event.key.toLowerCase()})); 1311 } else if (matchDialog.element.getAttribute("data-key") === Constants.DIALOG_HISTORYCOMPARE) { 1312 historyKeydown(event, matchDialog); 1313 } 1314 event.preventDefault(); 1315 return; 1316 } 1317 } 1318 1319 const target = event.target as HTMLElement; 1320 /// #if !BROWSER 1321 if (matchHotKey("⌘=", event) && !hasClosestByClassName(target, "pdf__outer")) { 1322 setZoom("zoomIn"); 1323 event.preventDefault(); 1324 return; 1325 } 1326 if (matchHotKey("⌘0", event)) { 1327 setZoom("restore"); 1328 event.preventDefault(); 1329 return; 1330 } 1331 if (matchHotKey("⌘-", event) && !hasClosestByClassName(target, "pdf__outer")) { 1332 setZoom("zoomOut"); 1333 event.preventDefault(); 1334 return; 1335 } 1336 /// #endif 1337 1338 if (!isTabWindow && matchHotKey(window.siyuan.config.keymap.general.syncNow.custom, event)) { 1339 event.preventDefault(); 1340 syncGuide(app); 1341 return; 1342 } 1343 if (matchHotKey(window.siyuan.config.keymap.general.commandPanel.custom, event)) { 1344 event.preventDefault(); 1345 commandPanel(app); 1346 return; 1347 } 1348 if (matchHotKey(window.siyuan.config.keymap.general.editReadonly.custom, event)) { 1349 event.preventDefault(); 1350 setReadOnly(!window.siyuan.config.editor.readOnly); 1351 return; 1352 } 1353 if (matchHotKey(window.siyuan.config.keymap.general.lockScreen.custom, event)) { 1354 lockScreen(app); 1355 event.preventDefault(); 1356 return; 1357 } 1358 if (matchHotKey(window.siyuan.config.keymap.general.dataHistory.custom, event)) { 1359 openHistory(app); 1360 event.preventDefault(); 1361 return; 1362 } 1363 if (!isTabWindow && matchHotKey(window.siyuan.config.keymap.general.toggleDock.custom, event)) { 1364 toggleDockBar(document.querySelector("#barDock use")); 1365 event.preventDefault(); 1366 return; 1367 } 1368 if (!isTabWindow && !window.siyuan.config.readonly && matchHotKey(window.siyuan.config.keymap.general.config.custom, event)) { 1369 openSetting(app); 1370 event.preventDefault(); 1371 return; 1372 } 1373 if (matchHotKey("⌘A", event) && !["INPUT", "TEXTAREA"].includes(target.tagName)) { 1374 event.preventDefault(); 1375 return; 1376 } 1377 const matchDock = getAllDocks().find(item => { 1378 if (matchHotKey(item.hotkey, event)) { 1379 getDockByType(item.type).toggleModel(item.type); 1380 event.preventDefault(); 1381 return true; 1382 } 1383 }); 1384 if (matchDock) { 1385 return; 1386 } 1387 if (!isTabWindow && matchHotKey(window.siyuan.config.keymap.general.riffCard.custom, event)) { 1388 openCard(app); 1389 if (document.activeElement) { 1390 (document.activeElement as HTMLElement).blur(); 1391 } 1392 event.preventDefault(); 1393 return; 1394 } 1395 if (!isTabWindow && matchHotKey(window.siyuan.config.keymap.general.dailyNote.custom, event)) { 1396 newDailyNote(app); 1397 event.stopPropagation(); 1398 event.preventDefault(); 1399 return; 1400 } 1401 if (matchHotKey(window.siyuan.config.keymap.general.newFile.custom, event)) { 1402 newFile({ 1403 app, 1404 useSavePath: true 1405 }); 1406 event.preventDefault(); 1407 return; 1408 } 1409 // https://github.com/siyuan-note/siyuan/issues/8913#issuecomment-1679720605 1410 const confirmDialogElement = document.querySelector('.b3-dialog--open[data-key="dialog-confirm"]'); 1411 if (confirmDialogElement) { 1412 if (event.key === "Enter") { 1413 confirmDialogElement.dispatchEvent(new CustomEvent("click", {detail: event.key})); 1414 event.preventDefault(); 1415 return; 1416 } else if (event.key === "Escape") { 1417 confirmDialogElement.dispatchEvent(new CustomEvent("click", {detail: event.key})); 1418 event.preventDefault(); 1419 return; 1420 } 1421 } 1422 1423 if (event.key === "Escape" && !event.isComposing) { 1424 cancelDrag(); 1425 const imgPreviewElement = document.querySelector(".protyle-img"); 1426 if (imgPreviewElement) { 1427 imgPreviewElement.remove(); 1428 return; 1429 } 1430 1431 if (!window.siyuan.menus.menu.element.classList.contains("fn__none")) { 1432 if (window.siyuan.dialogs.length > 0 && 1433 window.siyuan.menus.menu.element.style.zIndex < (window.siyuan.dialogs[0].element.querySelector(".b3-dialog") as HTMLElement).style.zIndex) { 1434 // 窗口高于菜单时,先关闭窗口,如 av 修改列 icon 时 1435 } else { 1436 window.siyuan.menus.menu.remove(true); 1437 return; 1438 } 1439 } 1440 1441 // 需放在 menus 后,否则资源列中添加资源会先关闭菜单 1442 // 需放在 dialog 前,否则属性面板中修改日期会先关闭 dialog,只剩修改界面 1443 const avElement = document.querySelector(".av__panel"); 1444 if (avElement) { 1445 const selectCellElement = document.querySelector(".av__cell--select"); 1446 if (selectCellElement) { 1447 focusBlock(hasClosestBlock(selectCellElement) as HTMLElement); 1448 } 1449 avElement.remove(); 1450 return; 1451 } 1452 1453 // 闪卡长按 Esc 光标定位到闪卡按钮上 https://github.com/siyuan-note/siyuan/issues/12989 1454 // https://github.com/siyuan-note/siyuan/issues/14730 1455 if (event.repeat && document.activeElement && hasClosestByClassName(document.activeElement, "card__action")) { 1456 return; 1457 } 1458 1459 if (window.siyuan.dialogs.length > 0) { 1460 window.siyuan.dialogs[window.siyuan.dialogs.length - 1].destroy(); 1461 return; 1462 } 1463 1464 // remove blockpopover 1465 const maxEditLevels: { [key: string]: number } = {oid: 0}; 1466 window.siyuan.blockPanels.forEach((item) => { 1467 if ((item.targetElement || typeof item.x === "number") && item.element.getAttribute("data-pin") === "true") { 1468 const level = parseInt(item.element.getAttribute("data-level")); 1469 const oid = item.element.getAttribute("data-oid"); 1470 if (maxEditLevels[oid]) { 1471 if (level > maxEditLevels[oid]) { 1472 maxEditLevels[oid] = level; 1473 } 1474 } else { 1475 maxEditLevels[oid] = 1; 1476 } 1477 } 1478 }); 1479 let destroyBlock = false; 1480 for (let i = 0; i < window.siyuan.blockPanels.length; i++) { 1481 const item = window.siyuan.blockPanels[i]; 1482 if ((item.targetElement || typeof item.x === "number") && item.element.getAttribute("data-pin") === "false") { 1483 item.destroy(); 1484 destroyBlock = true; 1485 i--; 1486 } 1487 } 1488 if (destroyBlock) { 1489 return; 1490 } 1491 1492 // 光标在文档树等面板中,按 Esc 回到编辑器中 https://github.com/siyuan-note/siyuan/issues/4289 1493 if (getSelection().rangeCount > 0) { 1494 const range = getSelection().getRangeAt(0); 1495 if (hasClosestByClassName(range.startContainer, "protyle-content", true)) { 1496 focusByRange(range); 1497 return; 1498 } 1499 } 1500 const lastBackStack = window.siyuan.backStack[window.siyuan.backStack.length - 1]; 1501 if (lastBackStack && lastBackStack.protyle.toolbar.range) { 1502 focusByRange(lastBackStack.protyle.toolbar.range); 1503 } else { 1504 const editor = getAllModels().editor[0]; 1505 if (editor) { 1506 focusBlock(editor.editor.protyle.wysiwyg.element.firstElementChild); 1507 } 1508 } 1509 event.preventDefault(); 1510 return; 1511 } 1512 1513 if (!isTabWindow && matchHotKey(window.siyuan.config.keymap.general.mainMenu.custom, event)) { 1514 workspaceMenu(app, document.querySelector("#barWorkspace").getBoundingClientRect()); 1515 event.preventDefault(); 1516 return; 1517 } 1518 1519 if (matchHotKey(window.siyuan.config.keymap.general.goForward.custom, event)) { 1520 goForward(app); 1521 event.preventDefault(); 1522 return; 1523 } 1524 1525 if (matchHotKey(window.siyuan.config.keymap.general.goBack.custom, event)) { 1526 goBack(app); 1527 event.preventDefault(); 1528 return; 1529 } 1530 1531 // close tab 1532 if (matchHotKey(window.siyuan.config.keymap.general.closeTab.custom, event) && !event.repeat) { 1533 execByCommand({ 1534 command: "closeTab" 1535 }); 1536 event.preventDefault(); 1537 return; 1538 } 1539 1540 if (matchHotKey(window.siyuan.config.keymap.general.recentClosed.custom, event)) { 1541 execByCommand({ 1542 command: "recentClosed", 1543 app 1544 }); 1545 event.preventDefault(); 1546 return; 1547 } 1548 1549 if (matchHotKey(window.siyuan.config.keymap.general.goToTab1.custom, event) && !event.repeat) { 1550 switchTabByIndex(0); 1551 event.preventDefault(); 1552 return; 1553 } 1554 1555 if (matchHotKey(window.siyuan.config.keymap.general.goToTab2.custom, event) && !event.repeat) { 1556 switchTabByIndex(1); 1557 event.preventDefault(); 1558 return; 1559 } 1560 if (matchHotKey(window.siyuan.config.keymap.general.goToTab3.custom, event) && !event.repeat) { 1561 switchTabByIndex(2); 1562 event.preventDefault(); 1563 return; 1564 } 1565 if (matchHotKey(window.siyuan.config.keymap.general.goToTab4.custom, event) && !event.repeat) { 1566 switchTabByIndex(3); 1567 event.preventDefault(); 1568 return; 1569 } 1570 if (matchHotKey(window.siyuan.config.keymap.general.goToTab5.custom, event) && !event.repeat) { 1571 switchTabByIndex(4); 1572 event.preventDefault(); 1573 return; 1574 } 1575 if (matchHotKey(window.siyuan.config.keymap.general.goToTab6.custom, event) && !event.repeat) { 1576 switchTabByIndex(5); 1577 event.preventDefault(); 1578 return; 1579 } 1580 if (matchHotKey(window.siyuan.config.keymap.general.goToTab7.custom, event) && !event.repeat) { 1581 switchTabByIndex(6); 1582 event.preventDefault(); 1583 return; 1584 } 1585 if (matchHotKey(window.siyuan.config.keymap.general.goToTab8.custom, event) && !event.repeat) { 1586 switchTabByIndex(7); 1587 event.preventDefault(); 1588 return; 1589 } 1590 if (matchHotKey(window.siyuan.config.keymap.general.goToTab9.custom, event) && !event.repeat) { 1591 switchTabByIndex(-1); 1592 event.preventDefault(); 1593 return; 1594 } 1595 if (matchHotKey(window.siyuan.config.keymap.general.goToTabNext.custom, event) && !event.repeat) { 1596 switchTabByIndex(-3); 1597 event.preventDefault(); 1598 return; 1599 } 1600 if (matchHotKey(window.siyuan.config.keymap.general.goToTabPrev.custom, event) && !event.repeat) { 1601 switchTabByIndex(-2); 1602 event.preventDefault(); 1603 return; 1604 } 1605 if (matchHotKey(window.siyuan.config.keymap.general.closeOthers.custom, event) && !event.repeat) { 1606 execByCommand({ 1607 command: "closeOthers" 1608 }); 1609 event.preventDefault(); 1610 return; 1611 } 1612 if (matchHotKey(window.siyuan.config.keymap.general.closeAll.custom, event) && !event.repeat) { 1613 execByCommand({ 1614 command: "closeAll" 1615 }); 1616 event.preventDefault(); 1617 return; 1618 } 1619 if (matchHotKey(window.siyuan.config.keymap.general.closeUnmodified.custom, event) && !event.repeat) { 1620 execByCommand({ 1621 command: "closeUnmodified" 1622 }); 1623 event.preventDefault(); 1624 return; 1625 } 1626 if (matchHotKey(window.siyuan.config.keymap.general.closeLeft.custom, event) && !event.repeat) { 1627 execByCommand({ 1628 command: "closeLeft" 1629 }); 1630 event.preventDefault(); 1631 return; 1632 } 1633 if (matchHotKey(window.siyuan.config.keymap.general.closeRight.custom, event) && !event.repeat) { 1634 execByCommand({ 1635 command: "closeRight" 1636 }); 1637 event.preventDefault(); 1638 return; 1639 } 1640 if (matchHotKey(window.siyuan.config.keymap.general.splitLR.custom, event) && !event.repeat) { 1641 event.preventDefault(); 1642 globalCommand("splitLR", app); 1643 return; 1644 } 1645 if (matchHotKey(window.siyuan.config.keymap.general.splitMoveR.custom, event) && !event.repeat) { 1646 event.preventDefault(); 1647 globalCommand("splitMoveR", app); 1648 return; 1649 } 1650 if (matchHotKey(window.siyuan.config.keymap.general.splitTB.custom, event) && !event.repeat) { 1651 event.preventDefault(); 1652 globalCommand("splitTB", app); 1653 return; 1654 } 1655 if (matchHotKey(window.siyuan.config.keymap.general.tabToWindow.custom, event) && !event.repeat) { 1656 event.preventDefault(); 1657 globalCommand("tabToWindow", app); 1658 return; 1659 } 1660 if (matchHotKey(window.siyuan.config.keymap.general.splitMoveB.custom, event) && !event.repeat) { 1661 event.preventDefault(); 1662 globalCommand("splitMoveB", app); 1663 return; 1664 } 1665 if (matchHotKey(window.siyuan.config.keymap.general.stickSearch.custom, event)) { 1666 globalCommand("stickSearch", app); 1667 event.preventDefault(); 1668 return; 1669 } 1670 if (matchHotKey(window.siyuan.config.keymap.general.unsplit.custom, event) && !event.repeat) { 1671 event.preventDefault(); 1672 globalCommand("unsplit", app); 1673 return; 1674 } 1675 if (matchHotKey(window.siyuan.config.keymap.general.unsplitAll.custom, event) && !event.repeat) { 1676 event.preventDefault(); 1677 globalCommand("unsplitAll", app); 1678 return; 1679 } 1680 if (editKeydown(app, event)) { 1681 return; 1682 } 1683 1684 // 文件树的操作 1685 if (!isTabWindow && fileTreeKeydown(app, event)) { 1686 return; 1687 } 1688 1689 // 面板的操作 1690 if (!isTabWindow && panelTreeKeydown(app, event)) { 1691 return; 1692 } 1693 1694 let matchCommand = false; 1695 app.plugins.find(item => { 1696 item.commands.find(command => { 1697 if (command.callback && 1698 !command.fileTreeCallback && !command.editorCallback && !command.dockCallback && !command.globalCallback 1699 && matchHotKey(command.customHotkey, event)) { 1700 matchCommand = true; 1701 command.callback(); 1702 return true; 1703 } 1704 }); 1705 if (matchCommand) { 1706 return true; 1707 } 1708 }); 1709 if (matchCommand) { 1710 event.stopPropagation(); 1711 event.preventDefault(); 1712 return true; 1713 } 1714 1715 if (matchHotKey(window.siyuan.config.keymap.general.replace.custom, event)) { 1716 execByCommand({ 1717 command: "replace", 1718 app, 1719 }); 1720 event.preventDefault(); 1721 return; 1722 } 1723 if (matchHotKey(window.siyuan.config.keymap.general.globalSearch.custom, event)) { 1724 execByCommand({ 1725 command: "globalSearch", 1726 app, 1727 }); 1728 event.preventDefault(); 1729 return; 1730 } 1731 if (!hasClosestByClassName(target, "pdf__outer") && matchHotKey(window.siyuan.config.keymap.general.search.custom, event)) { 1732 execByCommand({ 1733 command: "search", 1734 app, 1735 }); 1736 event.preventDefault(); 1737 return; 1738 } 1739 // https://github.com/siyuan-note/insider/issues/445 1740 if (matchHotKey("⌘S", event)) { 1741 event.preventDefault(); 1742 return true; 1743 } 1744}; 1745 1746export const sendGlobalShortcut = (app: App) => { 1747 /// #if !BROWSER 1748 const hotkeys = [window.siyuan.config.keymap.general.toggleWin.custom]; 1749 app.plugins.forEach(plugin => { 1750 plugin.commands.forEach(command => { 1751 if (command.globalCallback) { 1752 hotkeys.push(command.customHotkey); 1753 } 1754 }); 1755 }); 1756 ipcRenderer.send(Constants.SIYUAN_HOTKEY, { 1757 languages: window.siyuan.languages["_trayMenu"], 1758 hotkeys 1759 }); 1760 /// #endif 1761}; 1762 1763 1764export const sendUnregisterGlobalShortcut = (app: App) => { 1765 /// #if !BROWSER 1766 ipcRenderer.send(Constants.SIYUAN_CMD, { 1767 cmd: "unregisterGlobalShortcut", 1768 accelerator: window.siyuan.config.keymap.general.toggleWin.custom 1769 }); 1770 app.plugins.forEach(plugin => { 1771 plugin.commands.forEach(command => { 1772 if (command.globalCallback) { 1773 ipcRenderer.send(Constants.SIYUAN_CMD, { 1774 cmd: "unregisterGlobalShortcut", 1775 accelerator: command.customHotkey 1776 }); 1777 } 1778 }); 1779 }); 1780 /// #endif 1781};