A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1import {Constants} from "../../constants";
2import {fetchPost} from "../../util/fetch";
3/// #if !BROWSER
4import {sendGlobalShortcut} from "./keydown";
5import {ipcRenderer} from "electron";
6/// #endif
7import {App} from "../../index";
8import {isMac, isNotCtrl, isOnlyMeta} from "../../protyle/util/compatibility";
9import {showPopover} from "../../block/popover";
10
11const matchKeymap = (keymap: Config.IKeys, key1: "general" | "editor", key2?: "general" | "insert" | "heading" | "list" | "table") => {
12 if (key1 === "general") {
13 if (!window.siyuan.config.keymap[key1]) {
14 /// #if !BROWSER
15 ipcRenderer.send(Constants.SIYUAN_CMD, {
16 cmd: "writeLog",
17 msg: "window.siyuan.config.keymap.general is not found"
18 });
19 /// #endif
20 window.siyuan.config.keymap[key1] = keymap as Config.IKeymapGeneral;
21 return false;
22 }
23 } else {
24 if (!window.siyuan.config.keymap[key1]) {
25 /// #if !BROWSER
26 ipcRenderer.send(Constants.SIYUAN_CMD, {
27 cmd: "writeLog",
28 msg: "window.siyuan.config.keymap.editor is not found"
29 });
30 /// #endif
31 window.siyuan.config.keymap[key1] = JSON.parse(JSON.stringify(Constants.SIYUAN_KEYMAP.editor));
32 return false;
33 }
34 if (!window.siyuan.config.keymap[key1][key2]) {
35 /// #if !BROWSER
36 ipcRenderer.send(Constants.SIYUAN_CMD, {
37 cmd: "writeLog",
38 msg: `window.siyuan.config.keymap.editor.${key2} is not found`
39 });
40 /// #endif
41 (window.siyuan.config.keymap[key1][key2] as Config.IKeymapEditor[typeof key2]) = keymap as Config.IKeymapEditor[typeof key2];
42 return false;
43 }
44 }
45 let match = true;
46 Object.keys(keymap).forEach(key => {
47 if (key1 === "general") {
48 if (!window.siyuan.config.keymap[key1][key] || window.siyuan.config.keymap[key1][key].default !== keymap[key].default) {
49 /// #if !BROWSER
50 ipcRenderer.send(Constants.SIYUAN_CMD, {
51 cmd: "writeLog",
52 msg: `window.siyuan.config.keymap.${key1}.${key} is not found or match: ${window.siyuan.config.keymap[key1][key]?.default}`
53 });
54 /// #endif
55 match = false;
56 window.siyuan.config.keymap[key1][key] = keymap[key];
57 }
58 } else {
59 if (!window.siyuan.config.keymap[key1][key2][key] || window.siyuan.config.keymap[key1][key2][key].default !== keymap[key].default) {
60 /// #if !BROWSER
61 ipcRenderer.send(Constants.SIYUAN_CMD, {
62 cmd: "writeLog",
63 msg: `window.siyuan.config.keymap.${key1}.${key2}.${key} is not found or match: ${window.siyuan.config.keymap[key1][key2][key]?.default}`
64 });
65 /// #endif
66 match = false;
67 window.siyuan.config.keymap[key1][key2][key] = keymap[key];
68 }
69 }
70 });
71 return match;
72};
73
74const hasKeymap = (keymap: Record<string, IKeymapItem>, key1: "general" | "editor", key2?: "general" | "insert" | "heading" | "list" | "table") => {
75 let match = true;
76 if (key1 === "editor") {
77 if (Object.keys(window.siyuan.config.keymap[key1][key2]).length !== Object.keys(Constants.SIYUAN_KEYMAP[key1][key2]).length) {
78 Object.keys(window.siyuan.config.keymap[key1][key2]).forEach(item => {
79 if (!Constants.SIYUAN_KEYMAP[key1][key2][item]) {
80 match = false;
81 delete window.siyuan.config.keymap[key1][key2][item];
82 }
83 });
84 }
85 } else {
86 if (Object.keys(window.siyuan.config.keymap[key1]).length !== Object.keys(Constants.SIYUAN_KEYMAP[key1]).length) {
87 Object.keys(window.siyuan.config.keymap[key1]).forEach(item => {
88 if (!Constants.SIYUAN_KEYMAP[key1][item]) {
89 match = false;
90 delete window.siyuan.config.keymap[key1][item];
91 }
92 });
93 }
94 }
95 return match;
96};
97
98export const correctHotkey = (app: App) => {
99 const matchKeymap1 = matchKeymap(Constants.SIYUAN_KEYMAP.general, "general");
100 const matchKeymap2 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.general, "editor", "general");
101 const matchKeymap3 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.insert, "editor", "insert");
102 const matchKeymap4 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.heading, "editor", "heading");
103 const matchKeymap5 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.list, "editor", "list");
104 const matchKeymap6 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.table, "editor", "table");
105
106 const hasKeymap1 = hasKeymap(Constants.SIYUAN_KEYMAP.general, "general");
107 const hasKeymap2 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.general, "editor", "general");
108 const hasKeymap3 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.insert, "editor", "insert");
109 const hasKeymap4 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.heading, "editor", "heading");
110 const hasKeymap5 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.list, "editor", "list");
111 const hasKeymap6 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.table, "editor", "table");
112 if (!window.siyuan.config.readonly &&
113 (!matchKeymap1 || !matchKeymap2 || !matchKeymap3 || !matchKeymap4 || !matchKeymap5 || !matchKeymap6 ||
114 !hasKeymap1 || !hasKeymap2 || !hasKeymap3 || !hasKeymap4 || !hasKeymap5 || !hasKeymap6)) {
115 /// #if !BROWSER
116 ipcRenderer.send(Constants.SIYUAN_CMD, {
117 cmd: "writeLog",
118 msg: "update keymap"
119 });
120 /// #endif
121 fetchPost("/api/setting/setKeymap", {
122 data: window.siyuan.config.keymap
123 }, () => {
124 /// #if !BROWSER
125 sendGlobalShortcut(app);
126 /// #endif
127 });
128 }
129};
130
131export const filterHotkey = (event: KeyboardEvent, app: App) => {
132 // https://github.com/siyuan-note/siyuan/issues/9848 忘记为什么要阻止了 .av__mask 的情况,测了下没问题就先移除
133 if (document.getElementById("progress") || document.getElementById("errorLog") || event.isComposing) {
134 return true;
135 }
136 const target = event.target as HTMLElement;
137 // 点击最近的文档列表会 dispatch keydown 的 Enter https://github.com/siyuan-note/siyuan/issues/12967
138 if (event.isTrusted && isNotCtrl(event) && !event.shiftKey && !event.altKey &&
139 !["INPUT", "TEXTAREA"].includes(target.tagName) &&
140 ["0", "1", "2", "3", "4", "j", "k", "l", ";", "s", " ", "p", "enter", "a", "s", "d", "f", "q", "x"].includes(event.key.toLowerCase())) {
141 let cardElement: Element;
142 window.siyuan.dialogs.find(item => {
143 if (item.element.getAttribute("data-key") === Constants.DIALOG_OPENCARD) {
144 cardElement = item.element;
145 return true;
146 }
147 });
148 if (!cardElement) {
149 cardElement = document.querySelector(`.layout__wnd--active div[data-key="${Constants.DIALOG_OPENCARD}"]:not(.fn__none)`);
150 }
151 if (cardElement) {
152 event.preventDefault();
153 cardElement.dispatchEvent(new CustomEvent("click", {detail: event.key.toLowerCase()}));
154 return true;
155 }
156 }
157
158 // 仅处理以下快捷键操作
159 if (isNotCtrl(event) && event.key !== "Escape" && !event.shiftKey && !event.altKey &&
160 Constants.KEYCODELIST[event.keyCode] !== "PageUp" &&
161 Constants.KEYCODELIST[event.keyCode] !== "PageDown" &&
162 event.key !== "Home" && event.key !== "End" &&
163 !/^F\d{1,2}$/.test(event.key) && event.key.indexOf("Arrow") === -1 && event.key !== "Enter" && event.key !== "Backspace" && event.key !== "Delete") {
164 return true;
165 }
166
167 if (!event.altKey && !event.shiftKey && isOnlyMeta(event)) {
168 if ((isMac() ? event.key === "Meta" : event.key === "Control") || isOnlyMeta(event)) {
169 window.siyuan.ctrlIsPressed = true;
170 if ((event.key === "Meta" || event.key === "Control") &&
171 window.siyuan.config.editor.floatWindowMode === 1 && !event.repeat) {
172 showPopover(app);
173 }
174 } else {
175 window.siyuan.ctrlIsPressed = false;
176 }
177 }
178
179 if (!event.altKey && event.shiftKey && isNotCtrl(event)) {
180 if (event.key === "Shift") {
181 window.siyuan.shiftIsPressed = true;
182 if (!event.repeat) {
183 showPopover(app, true);
184 }
185 } else {
186 window.siyuan.shiftIsPressed = false;
187 }
188 }
189
190 if (event.altKey && !event.shiftKey && isNotCtrl(event)) {
191 if (event.key === "Alt") {
192 window.siyuan.altIsPressed = true;
193 } else {
194 window.siyuan.altIsPressed = false;
195 }
196 }
197};