A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1/// #if !BROWSER
2import * as path from "path";
3/// #endif
4import {matchHotKey} from "../../protyle/util/hotKey";
5import {fetchPost} from "../../util/fetch";
6import {openFileById} from "../../editor/util";
7import {Constants} from "../../constants";
8import {newFileByName} from "../../util/newFile";
9import {App} from "../../index";
10import {Dialog} from "../../dialog";
11import {getAllModels} from "../../layout/getAll";
12import {hasClosestByClassName} from "../../protyle/util/hasClosest";
13import {getArticle, inputEvent, replace} from "../../search/util";
14import {useShell} from "../../util/pathName";
15import {assetInputEvent, renderPreview} from "../../search/assets";
16import {initSearchMenu} from "../../menus/search";
17import {writeText} from "../../protyle/util/compatibility";
18import {checkFold} from "../../util/noRelyPCFunction";
19import {getUnRefList} from "../../search/unRef";
20import {toggleAssetHistory, toggleReplaceHistory, toggleSearchHistory} from "../../search/toggleHistory";
21
22export const searchKeydown = (app: App, event: KeyboardEvent) => {
23 if (getSelection().rangeCount === 0) {
24 return false;
25 }
26 const range = getSelection().getRangeAt(0);
27 if (hasClosestByClassName(range.startContainer, "protyle", true)) {
28 return false;
29 }
30 let element: HTMLElement;
31 let dialog: Dialog;
32 let edit;
33 let unRefEdit;
34 let config: Config.IUILayoutTabSearchConfig;
35 window.siyuan.dialogs.find((item) => {
36 if (item.element.contains(range.startContainer) && item.element.querySelector("#searchList")) {
37 element = item.element.querySelector(".b3-dialog__body");
38 dialog = item;
39 config = dialog.data;
40 edit = dialog.editors.edit;
41 unRefEdit = dialog.editors.unRefEdit;
42 return true;
43 }
44 });
45 if (!element) {
46 getAllModels().search.find((item) => {
47 if (item.element.contains(range.startContainer)) {
48 element = item.element;
49 edit = item.editors.edit;
50 config = item.config;
51 unRefEdit = item.editors.unRefEdit;
52 return true;
53 }
54 });
55 }
56 if (!element) {
57 return false;
58 }
59 const assetsElement = element.querySelector("#searchAssets");
60 const unRefElement = element.querySelector("#searchUnRefPanel");
61 const searchType = assetsElement.classList.contains("fn__none") ? (unRefElement.classList.contains("fn__none") ? "doc" : "unRef") : "asset";
62 const listElement = searchType === "asset" ? assetsElement.querySelector("#searchAssetList") : (searchType === "doc" ? element.querySelector("#searchList") : unRefElement.querySelector("#searchUnRefList"));
63 const searchInputElement = element.querySelector("#searchInput") as HTMLInputElement;
64 if (searchType === "doc" && matchHotKey(window.siyuan.config.keymap.general.newFile.custom, event)) {
65 if (config.method === 0) {
66 newFileByName(app, searchInputElement.value);
67 }
68 return true;
69 }
70 const targetId = (event.target as HTMLElement).id;
71 if (event.key === "ArrowDown" && event.altKey) {
72 if (searchType === "asset") {
73 toggleAssetHistory(assetsElement);
74 } else if (searchType === "doc") {
75 if (targetId === "replaceInput") {
76 toggleReplaceHistory(element.querySelector("#replaceInput"));
77 } else {
78 toggleSearchHistory(element, config, edit);
79 }
80 }
81 return true;
82 }
83 const assetLocal = window.siyuan.storage[Constants.LOCAL_SEARCHASSET] as ISearchAssetOption;
84 if (!window.siyuan.menus.menu.element.classList.contains("fn__none")) {
85 // 不能返回 true,否则历史菜单无法使用快捷键
86 return false;
87 }
88 let currentList: HTMLElement = listElement.querySelector(".b3-list-item--focus");
89 if (!currentList) {
90 return false;
91 }
92 if (currentList.getAttribute("data-type") === "search-new") {
93 if (event.key === "Enter" && config.method === 0) {
94 newFileByName(app, searchInputElement.value);
95 return true;
96 }
97 return false;
98 }
99 if (searchType !== "asset") {
100 if (matchHotKey(window.siyuan.config.keymap.editor.general.insertRight.custom, event)) {
101 const id = currentList.getAttribute("data-node-id");
102 checkFold(id, (zoomIn, action) => {
103 openFileById({
104 app,
105 id,
106 position: "right",
107 action,
108 zoomIn
109 });
110 if (dialog) {
111 dialog.destroy({focus: "false"});
112 }
113 });
114 return true;
115 }
116 const id = currentList.getAttribute("data-node-id");
117 if (matchHotKey("⌘/", event)) {
118 const currentRect = currentList.getBoundingClientRect();
119 initSearchMenu(id).popup({
120 x: currentRect.left + 30,
121 y: currentRect.bottom
122 });
123 return true;
124 }
125 if (matchHotKey(window.siyuan.config.keymap.editor.general.copyBlockRef.custom, event)) {
126 fetchPost("/api/block/getRefText", {id}, (response) => {
127 writeText(`((${id} '${response.data}'))`);
128 });
129 return true;
130 }
131 if (matchHotKey(window.siyuan.config.keymap.editor.general.copyBlockEmbed.custom, event)) {
132 writeText(`{{select * from blocks where id='${id}'}}`);
133 return true;
134 }
135 if (matchHotKey(window.siyuan.config.keymap.editor.general.copyProtocol.custom, event)) {
136 writeText(`siyuan://blocks/${id}`);
137 return true;
138 }
139 if (matchHotKey(window.siyuan.config.keymap.editor.general.copyProtocolInMd.custom, event)) {
140 fetchPost("/api/block/getRefText", {id}, (response) => {
141 writeText(`[${response.data}](siyuan://blocks/${id})`);
142 });
143 return true;
144 }
145 if (matchHotKey(window.siyuan.config.keymap.editor.general.copyHPath.custom, event)) {
146 fetchPost("/api/filetree/getHPathByID", {
147 id
148 }, (response) => {
149 writeText(response.data);
150 });
151 return true;
152 }
153 if (matchHotKey(window.siyuan.config.keymap.editor.general.copyID.custom, event)) {
154 writeText(id);
155 return true;
156 }
157 }
158
159 if (Constants.KEYCODELIST[event.keyCode] === "PageUp") {
160 if (searchType === "asset") {
161 if (!assetsElement.querySelector('[data-type="assetPrevious"]').getAttribute("disabled")) {
162 let currentPage = parseInt(assetsElement.querySelector("#searchAssetResult .fn__flex-center").textContent.split("/")[0]);
163 if (currentPage > 1) {
164 currentPage--;
165 assetInputEvent(assetsElement, assetLocal, currentPage);
166 }
167 }
168 } else if (searchType === "doc") {
169 if (!element.querySelector('[data-type="previous"]').getAttribute("disabled")) {
170 if (config.page > 1) {
171 config.page--;
172 inputEvent(element, config, edit);
173 }
174 }
175 } else if (searchType === "unRef") {
176 if (!element.querySelector('[data-type="unRefPrevious"]').getAttribute("disabled")) {
177 let currentPage = parseInt(unRefElement.querySelector("#searchUnRefResult").textContent);
178 if (currentPage > 1) {
179 currentPage--;
180 getUnRefList(unRefElement, unRefEdit, currentPage);
181 }
182 }
183 }
184 return true;
185 }
186 if (Constants.KEYCODELIST[event.keyCode] === "PageDown") {
187 if (searchType === "asset") {
188 if (!assetsElement.querySelector('[data-type="assetNext"]').getAttribute("disabled")) {
189 const assetPages = assetsElement.querySelector("#searchAssetResult .fn__flex-center").textContent.split("/");
190 let currentPage = parseInt(assetPages[0]);
191 if (currentPage < parseInt(assetPages[1])) {
192 currentPage++;
193 assetInputEvent(assetsElement, assetLocal, currentPage);
194 }
195 }
196 } else if (searchType === "doc") {
197 const nextElement = element.querySelector('[data-type="next"]');
198 if (!nextElement.getAttribute("disabled")) {
199 if (config.page < parseInt(nextElement.parentElement.querySelector("#searchResult").getAttribute("data-pagecount"))) {
200 config.page++;
201 inputEvent(element, config, edit);
202 }
203 }
204 } else if (searchType === "unRef") {
205 if (!element.querySelector('[data-type="unRefNext"]').getAttribute("disabled")) {
206 let currentPage = parseInt(unRefElement.querySelector("#searchUnRefResult").textContent);
207 if (currentPage < parseInt(unRefElement.querySelector("#searchUnRefResult").textContent.split("/")[1])) {
208 currentPage++;
209 getUnRefList(unRefElement, unRefEdit, currentPage);
210 }
211 }
212 }
213 return true;
214 }
215 if (!window.siyuan.menus.menu.element.classList.contains("fn__none")) {
216 return false;
217 }
218 if (event.key === "Enter") {
219 if (searchType !== "asset") {
220 if (targetId === "replaceInput") {
221 replace(element, config, edit, false);
222 } else {
223 const id = currentList.getAttribute("data-node-id");
224 checkFold(id, (zoomIn, action) => {
225 openFileById({
226 app,
227 id,
228 action,
229 zoomIn
230 });
231 if (dialog) {
232 dialog.destroy({focus: "false"});
233 }
234 });
235 }
236 } else {
237 /// #if !BROWSER
238 useShell("showItemInFolder", path.join(window.siyuan.config.system.dataDir, currentList.lastElementChild.getAttribute("aria-label")));
239 /// #endif
240 }
241 return true;
242 }
243 const lineHeight = 28;
244 const assetPreviewElement = assetsElement.querySelector("#searchAssetPreview");
245 if (event.key === "ArrowDown") {
246 currentList.classList.remove("b3-list-item--focus");
247 if (!currentList.nextElementSibling) {
248 if (config.group === 1 && searchType === "doc") {
249 if (currentList.parentElement.nextElementSibling) {
250 currentList.parentElement.nextElementSibling.nextElementSibling.firstElementChild.classList.add("b3-list-item--focus");
251 } else {
252 listElement.children[1].firstElementChild.classList.add("b3-list-item--focus");
253 }
254 } else {
255 listElement.firstElementChild.classList.add("b3-list-item--focus");
256 }
257 } else {
258 currentList.nextElementSibling.classList.add("b3-list-item--focus");
259 }
260 currentList = listElement.querySelector(".b3-list-item--focus");
261 if (listElement.scrollTop < currentList.offsetTop - listElement.clientHeight + lineHeight ||
262 listElement.scrollTop > currentList.offsetTop) {
263 listElement.scrollTop = currentList.offsetTop - listElement.clientHeight + lineHeight;
264 }
265 if (searchType === "asset") {
266 renderPreview(assetPreviewElement, currentList.dataset.id, searchInputElement.value, assetLocal.method);
267 } else if (searchType === "doc") {
268 getArticle({
269 id: currentList.getAttribute("data-node-id"),
270 config,
271 value: searchInputElement.value,
272 edit,
273 });
274 } else {
275 getArticle({
276 id: currentList.getAttribute("data-node-id"),
277 edit: unRefEdit,
278 });
279 }
280 return true;
281 }
282 if (event.key === "ArrowUp") {
283 currentList.classList.remove("b3-list-item--focus");
284 if (!currentList.previousElementSibling) {
285 if (config.group === 1 && searchType === "doc") {
286 if (currentList.parentElement.previousElementSibling.previousElementSibling) {
287 currentList.parentElement.previousElementSibling.previousElementSibling.lastElementChild.classList.add("b3-list-item--focus");
288 } else {
289 listElement.lastElementChild.lastElementChild.classList.add("b3-list-item--focus");
290 }
291 } else {
292 listElement.lastElementChild.classList.add("b3-list-item--focus");
293 }
294 } else {
295 currentList.previousElementSibling.classList.add("b3-list-item--focus");
296 }
297 currentList = listElement.querySelector(".b3-list-item--focus");
298 if (listElement.scrollTop < currentList.offsetTop - listElement.clientHeight + lineHeight ||
299 listElement.scrollTop > currentList.offsetTop - lineHeight * 2) {
300 listElement.scrollTop = currentList.offsetTop - lineHeight * 2;
301 }
302 if (searchType === "asset") {
303 renderPreview(assetPreviewElement, currentList.dataset.id, searchInputElement.value, assetLocal.method);
304 } else if (searchType === "doc") {
305 getArticle({
306 id: currentList.getAttribute("data-node-id"),
307 config,
308 value: searchInputElement.value,
309 edit,
310 });
311 } else if (searchType === "unRef") {
312 getArticle({
313 id: currentList.getAttribute("data-node-id"),
314 edit: unRefEdit,
315 });
316 }
317 return true;
318 }
319 return false;
320};