A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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};