diff --git a/app/src/search/assets.ts b/app/src/search/assets.ts index 5177cba35..8ee1a6eec 100644 --- a/app/src/search/assets.ts +++ b/app/src/search/assets.ts @@ -1,47 +1,54 @@ +/// #if !BROWSER +import {shell} from "electron"; +import * as path from "path"; +/// #endif import {Constants} from "../constants"; import {fetchPost} from "../util/fetch"; import {upDownHint} from "../util/upDownHint"; import {escapeHtml} from "../util/escape"; +import {setStorageVal} from "../protyle/util/compatibility"; +import {getQueryTip} from "./util"; +import {MenuItem} from "../menus/Menu"; +import {Dialog} from "../dialog"; export const openSearchAsset = (element: Element, isStick: boolean) => { - const localSearch = window.siyuan.storage[Constants.LOCAL_SEARCHASSET] as ISearchAssetOption - let methodText = window.siyuan.languages.keyword; - if (localSearch.method === 1) { - methodText = window.siyuan.languages.querySyntax; - } else if (localSearch.method === 2) { - methodText = "SQL"; - } else if (localSearch.method === 3) { - methodText = window.siyuan.languages.regex; + window.siyuan.menus.menu.remove(); + element.previousElementSibling.classList.add("fn__none"); + element.classList.remove("fn__none"); + if (element.innerHTML) { + (element.querySelector("#searchAssetInput") as HTMLInputElement).select(); + return; } + const localSearch = window.siyuan.storage[Constants.LOCAL_SEARCHASSET] as ISearchAssetOption const loadingElement = element.nextElementSibling; loadingElement.classList.remove("fn__none"); + let enterTip = "" + /// #if !BROWSER + enterTip = `Enter/Double Click ${window.siyuan.languages.showInFolder}` + /// #endif element.innerHTML = `
- +
- + - + - + - - - - @@ -49,26 +56,24 @@ export const openSearchAsset = (element: Element, isStick: boolean) => {
- + - + - +
-
+
↑/↓ ${window.siyuan.languages.searchTip1} - Enter/Double Click ${window.siyuan.languages.searchTip2} + ${enterTip} Esc ${window.siyuan.languages.searchTip5}
` - element.previousElementSibling.classList.add("fn__none"); - element.classList.remove("fn__none") const searchPanelElement = element.querySelector("#searchAssetList"); if (element.querySelector("#searchAssetList").innerHTML !== "") { return @@ -79,42 +84,57 @@ export const openSearchAsset = (element: Element, isStick: boolean) => { if (event.isComposing) { return; } - inputEvent(localSearch, element, 1); + assetInputEvent(element, localSearch); }); searchInputElement.addEventListener("input", (event: InputEvent) => { if (event.isComposing) { return; } - inputEvent(localSearch, element, 1); + assetInputEvent(element, localSearch); }); searchInputElement.addEventListener("blur", () => { - // saveKeyList("keys", searchInputElement.value); + if (!searchInputElement.value) { + return + } + let list: string[] = window.siyuan.storage[Constants.LOCAL_SEARCHASSET].keys; + list.splice(0, 0, searchInputElement.value); + list = Array.from(new Set(list)); + if (list.length > window.siyuan.config.search.limit) { + list.splice(window.siyuan.config.search.limit, list.length - window.siyuan.config.search.limit); + } + window.siyuan.storage[Constants.LOCAL_SEARCHASSET].k = searchInputElement.value; + window.siyuan.storage[Constants.LOCAL_SEARCHASSET].keys = list; + setStorageVal(Constants.LOCAL_SEARCHASSET, window.siyuan.storage[Constants.LOCAL_SEARCHASSET]); }); const historyElement = element.querySelector("#searchAssetHistoryList") - const lineHeight = 30; + const lineHeight = 28; searchInputElement.addEventListener("keydown", (event: KeyboardEvent) => { - let currentList: HTMLElement = searchPanelElement.querySelector(".b3-list-item--focus"); - if (!currentList || event.isComposing) { + if (event.isComposing) { return; } + let currentList: HTMLElement = searchPanelElement.querySelector(".b3-list-item--focus"); const isHistory = !historyElement.classList.contains("fn__none"); if (event.key === "Enter") { if (!isHistory) { - // TODO open folder + if (currentList) { + /// #if !BROWSER + shell.showItemInFolder(path.join(window.siyuan.config.system.dataDir, currentList.lastElementChild.getAttribute("aria-label"))); + /// #endif + } } else { searchInputElement.value = historyElement.querySelector(".b3-list-item--focus").textContent.trim(); - inputEvent(localSearch, element, 1); - toggleSearchHistory(historyElement, searchInputElement); + assetInputEvent(element, localSearch); + toggleAssetHistory(historyElement, searchInputElement); } event.preventDefault(); } if (event.key === "ArrowDown" && event.altKey) { - toggleSearchHistory(historyElement, searchInputElement); + toggleAssetHistory(historyElement, searchInputElement); return; } if (isHistory) { if (event.key === "Escape") { - toggleSearchHistory(historyElement, searchInputElement); + toggleAssetHistory(historyElement, searchInputElement); } else { upDownHint(historyElement, event); } @@ -122,7 +142,7 @@ export const openSearchAsset = (element: Element, isStick: boolean) => { event.preventDefault(); return; } - if (!isHistory) { + if (!currentList) { return; } if (event.key === "ArrowDown") { @@ -152,34 +172,83 @@ export const openSearchAsset = (element: Element, isStick: boolean) => { } event.preventDefault(); } + renderPreview(element.querySelector('#searchAssetPreview'), currentList.dataset.id, searchInputElement.value, localSearch.method); }); - inputEvent(localSearch, element); + assetInputEvent(element, localSearch); } - -const inputEvent = (localSearch: ISearchAssetOption, element: Element, page = 1) => { - const searchInputElement = element.querySelector("#searchAssetInput") as HTMLInputElement - fetchPost("/api/search/fullTextSearchAssetContent", { - page, - query: searchInputElement.value, - types: localSearch.types, - method: localSearch.method, - orderBy: localSearch.sort - }, (response) => { - element.nextElementSibling.classList.add("fn__none") - console.log(response) - }) +let inputTimeout: number +export const assetInputEvent = (element: Element, localSearch?: ISearchAssetOption, page = 1,) => { + element.nextElementSibling.classList.remove("fn__none") + clearTimeout(inputTimeout); + inputTimeout = window.setTimeout(() => { + if (!localSearch) { + localSearch = window.siyuan.storage[Constants.LOCAL_SEARCHASSET] as ISearchAssetOption + } + const previousElement = element.querySelector('[data-type="assetPrevious"]') + if (page > 1) { + previousElement.removeAttribute("disabled"); + } else { + previousElement.setAttribute("disabled", "disabled"); + } + const searchInputElement = element.querySelector("#searchAssetInput") as HTMLInputElement + fetchPost("/api/search/fullTextSearchAssetContent", { + page, + query: searchInputElement.value, + types: localSearch.types, + method: localSearch.method, + orderBy: localSearch.sort + }, (response) => { + element.nextElementSibling.classList.add("fn__none") + const nextElement = element.querySelector('[data-type="assetNext"]') + const previewElement = element.querySelector('#searchAssetPreview') + if (page < response.data.pageCount) { + nextElement.removeAttribute("disabled"); + } else { + nextElement.setAttribute("disabled", "disabled"); + } + let resultHTML = ""; + response.data.assetContents.forEach((item: { + content: string + ext: string + id: string + path: string + name: string + hSize: string + }, index: number) => { + resultHTML += `
+${item.ext} + +${item.content} +${item.hSize} +${item.name} +
`; + }); + if (response.data.assetContents.length > 0) { + previewElement.classList.remove("fn__none"); + element.querySelector(".search__drag").classList.remove("fn__none"); + renderPreview(previewElement, response.data.assetContents[0].id, searchInputElement.value, localSearch.method); + } else { + previewElement.classList.add("fn__none"); + element.querySelector(".search__drag").classList.add("fn__none"); + } + element.querySelector("#searchAssetResult").innerHTML = `${page}/${response.data.pageCount || 1} +${window.siyuan.languages.total} ${response.data.matchedAssetCount}`; + element.querySelector("#searchAssetList").innerHTML = resultHTML || `
+ ${window.siyuan.languages.emptyContent} +
` + }) + }, Constants.TIMEOUT_INPUT); } - export const reIndexAssets = (loadingElement: HTMLElement) => { loadingElement.classList.remove("fn__none") fetchPost("/api/asset/fullReindexAssetContent", {}, (response) => { - loadingElement.classList.add("fn__none") + // assetInputEvent() }) } -const toggleSearchHistory = (historyElement: Element, searchInputElement: HTMLInputElement) => { +export const toggleAssetHistory = (historyElement: Element, searchInputElement: HTMLInputElement) => { if (historyElement.classList.contains("fn__none")) { const keys = window.siyuan.storage[Constants.LOCAL_SEARCHASSET].keys; if (!keys || keys.length === 0) { @@ -200,3 +269,120 @@ const toggleSearchHistory = (historyElement: Element, searchInputElement: HTMLIn historyElement.classList.add("fn__none"); } }; + +export const renderPreview = (element: Element, id: string, query: string, queryMethod: number) => { + fetchPost('/api/search/getAssetContent', {id, query, queryMethod}, (response) => { + element.innerHTML = response.data.assetContent.content; + }) +} + +export const assetMethodMenu = (target: HTMLElement, cb:() => void) => { + const method = window.siyuan.storage[Constants.LOCAL_SEARCHASSET].method + if (!window.siyuan.menus.menu.element.classList.contains("fn__none") && + window.siyuan.menus.menu.element.getAttribute("data-name") === "searchMethod") { + window.siyuan.menus.menu.remove(); + return; + } + window.siyuan.menus.menu.remove(); + window.siyuan.menus.menu.element.setAttribute("data-name", "searchMethod"); + window.siyuan.menus.menu.append(new MenuItem({ + iconHTML: Constants.ZWSP, + label: window.siyuan.languages.keyword, + current: method === 0, + click() { + window.siyuan.storage[Constants.LOCAL_SEARCHASSET].method = 0 + cb(); + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({ + iconHTML: Constants.ZWSP, + label: window.siyuan.languages.querySyntax, + current: method === 1, + click() { + window.siyuan.storage[Constants.LOCAL_SEARCHASSET].method = 1 + cb(); + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({ + iconHTML: Constants.ZWSP, + label: "SQL", + current: method === 2, + click() { + window.siyuan.storage[Constants.LOCAL_SEARCHASSET].method = 2 + cb(); + } + }).element); + window.siyuan.menus.menu.append(new MenuItem({ + iconHTML: Constants.ZWSP, + label: window.siyuan.languages.regex, + current: method === 3, + click() { + window.siyuan.storage[Constants.LOCAL_SEARCHASSET].method = 3 + cb(); + } + }).element); + const rect = target.getBoundingClientRect(); + window.siyuan.menus.menu.popup({x: rect.right, y: rect.bottom}, true); +} + +export const assetFilterMenu = (assetsElement:Element) => { + const localData = window.siyuan.storage[Constants.LOCAL_SEARCHASSET].types + const filterDialog = new Dialog({ + title: window.siyuan.languages.type, + content: `
+ + + + + +
+
+
+ +
`, + width: "520px", + height: "70vh", + }); + const btnsElement = filterDialog.element.querySelectorAll(".b3-button"); + btnsElement[0].addEventListener("click", () => { + filterDialog.destroy(); + }); + btnsElement[1].addEventListener("click", () => { + filterDialog.element.querySelectorAll(".b3-switch").forEach((item: HTMLInputElement) => { + localData[item.getAttribute("data-type")] = item.checked; + }); + assetInputEvent(assetsElement); + setStorageVal(Constants.LOCAL_SEARCHASSET, window.siyuan.storage[Constants.LOCAL_SEARCHASSET]); + filterDialog.destroy(); + }); +}; diff --git a/app/src/search/util.ts b/app/src/search/util.ts index 13a5e16a7..94a6f017c 100644 --- a/app/src/search/util.ts +++ b/app/src/search/util.ts @@ -1,4 +1,8 @@ import {getAllModels} from "../layout/getAll"; +/// #if !BROWSER +import {shell} from "electron"; +import * as path from "path"; +/// #endif import {Constants} from "../constants"; import {escapeAttr, escapeGreat, escapeHtml} from "../util/escape"; import {fetchPost} from "../util/fetch"; @@ -19,7 +23,14 @@ import {matchHotKey} from "../protyle/util/hotKey"; import {filterMenu, getKeyByLiElement, initCriteriaMenu, moreMenu, queryMenu, saveCriterion} from "./menu"; import {App} from "../index"; import {upDownHint} from "../util/upDownHint"; -import {openSearchAsset, reIndexAssets} from "./assets"; +import { + assetFilterMenu, + assetInputEvent, + assetMethodMenu, + openSearchAsset, + renderPreview, + toggleAssetHistory +} from "./assets"; const toggleReplaceHistory = (replaceHistoryElement: Element, historyElement: Element, replaceInputElement: HTMLInputElement) => { if (replaceHistoryElement.classList.contains("fn__none")) { @@ -236,7 +247,7 @@ export const genSearch = (app: App, config: ISearchOption, element: Element, clo const replaceHistoryElement = element.querySelector("#replaceHistoryList"); const historyElement = element.querySelector("#searchHistoryList"); - const lineHeight = 30; + const lineHeight = 28; const edit = new Protyle(app, element.querySelector("#searchPreview") as HTMLElement, { blockId: "", render: { @@ -502,6 +513,7 @@ export const genSearch = (app: App, config: ISearchOption, element: Element, clo event.preventDefault(); break; } else if (target.id === "searchAssetClose") { + window.siyuan.menus.menu.remove(); assetsElement.classList.add("fn__none") assetsElement.previousElementSibling.classList.remove("fn__none") searchInputElement.select(); @@ -624,6 +636,20 @@ export const genSearch = (app: App, config: ISearchOption, element: Element, clo event.stopPropagation(); event.preventDefault(); break; + } else if (target.id === "assetFilter") { + assetFilterMenu(assetsElement); + event.stopPropagation(); + event.preventDefault(); + break; + } else if (target.id === "assetSyntaxCheck") { + assetMethodMenu(target, () => { + element.querySelector("#assetSyntaxCheck").setAttribute("aria-label", getQueryTip(window.siyuan.storage[Constants.LOCAL_SEARCHASSET].method)); + assetInputEvent(assetsElement); + setStorageVal(Constants.LOCAL_SEARCHASSET, window.siyuan.storage[Constants.LOCAL_SEARCHASSET]); + }) + event.stopPropagation(); + event.preventDefault(); + break; } else if (target.id === "searchSyntaxCheck") { queryMenu(config, () => { element.querySelector("#searchSyntaxCheck").setAttribute("aria-label", getQueryTip(config.method)); @@ -640,6 +666,11 @@ export const genSearch = (app: App, config: ISearchOption, element: Element, clo event.stopPropagation(); event.preventDefault(); return; + } else if (target.id === "assetHistoryBtn") { + toggleAssetHistory(target.nextElementSibling.nextElementSibling, target.nextElementSibling as HTMLInputElement); + event.stopPropagation(); + event.preventDefault(); + return; } else if (target.id === "replaceHistoryBtn") { toggleReplaceHistory(replaceHistoryElement, historyElement, replaceInputElement); event.stopPropagation(); @@ -650,8 +681,8 @@ export const genSearch = (app: App, config: ISearchOption, element: Element, clo event.stopPropagation(); event.preventDefault(); break; - } else if (type === "reindexAssets") { - reIndexAssets(element.querySelector(".fn__loading--top")); + } else if (type === "assetRefresh") { + assetInputEvent(assetsElement); event.stopPropagation(); event.preventDefault(); break; @@ -667,65 +698,85 @@ export const genSearch = (app: App, config: ISearchOption, element: Element, clo event.preventDefault(); break; } else if (target.classList.contains("b3-list-item")) { + const searchAssetInputElement = element.querySelector("#searchAssetInput") as HTMLInputElement if (target.parentElement.id === "searchHistoryList") { searchInputElement.value = target.textContent; config.page = 1; inputTimeout = inputEvent(element, config, inputTimeout, edit, true); + } else if (target.parentElement.id === "searchAssetHistoryList") { + searchAssetInputElement.value = target.textContent; + assetInputEvent(assetsElement); } else if (target.parentElement.id === "replaceHistoryList") { replaceInputElement.value = target.textContent; replaceHistoryElement.classList.add("fn__none"); } else if (type === "search-new") { newFileByName(app, searchInputElement.value); } else if (type === "search-item") { + const isAsset = target.dataset.id; if (event.detail === 1) { clickTimeout = window.setTimeout(() => { - if (event.altKey) { - const id = target.getAttribute("data-node-id"); - fetchPost("/api/block/checkBlockFold", {id}, (foldResponse) => { - openFileById({ - app, - id, - action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT], - zoomIn: foldResponse.data, - position: "right" + if (isAsset) { + if (!target.classList.contains("b3-list-item--focus")) { + assetsElement.querySelector(".b3-list-item--focus").classList.remove("b3-list-item--focus"); + target.classList.add("b3-list-item--focus"); + renderPreview(element.querySelector('#searchAssetPreview'), target.dataset.id, searchAssetInputElement.value, window.siyuan.storage[Constants.LOCAL_SEARCHASSET].method); + searchAssetInputElement.focus(); + } + } else { + if (event.altKey) { + const id = target.getAttribute("data-node-id"); + fetchPost("/api/block/checkBlockFold", {id}, (foldResponse) => { + openFileById({ + app, + id, + action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT], + zoomIn: foldResponse.data, + position: "right" + }); + if (closeCB) { + closeCB(); + } }); - if (closeCB) { - closeCB(); - } - }); - } else if (!target.classList.contains("b3-list-item--focus")) { - searchPanelElement.querySelector(".b3-list-item--focus").classList.remove("b3-list-item--focus"); - target.classList.add("b3-list-item--focus"); - getArticle({ - edit, - id: target.getAttribute("data-node-id"), - config, - value: searchInputElement.value, - }); - searchInputElement.focus(); - } else if (target.classList.contains("b3-list-item--focus")) { - renderNextSearchMark({ - edit, - id: target.getAttribute("data-node-id"), - target, - }); - searchInputElement.focus(); + } else if (!target.classList.contains("b3-list-item--focus")) { + searchPanelElement.querySelector(".b3-list-item--focus").classList.remove("b3-list-item--focus"); + target.classList.add("b3-list-item--focus"); + getArticle({ + edit, + id: target.getAttribute("data-node-id"), + config, + value: searchInputElement.value, + }); + searchInputElement.focus(); + } else if (target.classList.contains("b3-list-item--focus")) { + renderNextSearchMark({ + edit, + id: target.getAttribute("data-node-id"), + target, + }); + searchInputElement.focus(); + } } }, Constants.TIMEOUT_DBLCLICK); } else if (event.detail === 2 && !event.ctrlKey) { clearTimeout(clickTimeout); - const id = target.getAttribute("data-node-id"); - fetchPost("/api/block/checkBlockFold", {id}, (foldResponse) => { - openFileById({ - app, - id, - action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT], - zoomIn: foldResponse.data + if (isAsset) { + /// #if !BROWSER + shell.showItemInFolder(path.join(window.siyuan.config.system.dataDir, target.lastElementChild.getAttribute("aria-label"))); + /// #endif + } else { + const id = target.getAttribute("data-node-id"); + fetchPost("/api/block/checkBlockFold", {id}, (foldResponse) => { + openFileById({ + app, + id, + action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT], + zoomIn: foldResponse.data + }); + if (closeCB) { + closeCB(); + } }); - if (closeCB) { - closeCB(); - } - }); + } } window.siyuan.menus.menu.remove(); } else if (target.querySelector(".b3-list-item__toggle")) { @@ -740,6 +791,7 @@ export const genSearch = (app: App, config: ISearchOption, element: Element, clo } historyElement.classList.add("fn__none"); replaceHistoryElement.classList.add("fn__none"); + element.querySelector("#searchAssetHistoryList")?.classList.add("fn__none"); }, false); searchInputElement.addEventListener("compositionend", (event: InputEvent) => { @@ -927,7 +979,7 @@ export const genSearch = (app: App, config: ISearchOption, element: Element, clo return edit; }; -const getQueryTip = (method: number) => { +export const getQueryTip = (method: number) => { let methodTip = window.siyuan.languages.searchMethod + " "; switch (method) { case 0: