feat: add search component

This commit is contained in:
Xu Quan 2025-07-14 17:10:02 +08:00
parent b3f7080508
commit 8a05aaa526
24 changed files with 616 additions and 55 deletions

View file

@ -0,0 +1,24 @@
import { CSSProperties } from "react";
type Props = {
className?: string;
style?: CSSProperties;
};
const SearchIcon = ({ className, style }: Props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
className={className}
style={style}
>
<path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" />
</svg>
);
export default SearchIcon;

View file

@ -13,6 +13,7 @@ import GripIcon from "./GripIcon";
import LoadingIcon from "./LoadingIcon";
import PinIcon from "./PinIcon";
import StarIcon from "./RootFolderIcon";
import SearchIcon from "./SearchIcon";
import TagIcon from "./TagIcon";
export {
@ -32,4 +33,5 @@ export {
ChevronDown,
ChevronRight,
TagIcon,
SearchIcon
};

View file

@ -0,0 +1,50 @@
import classNames from "classnames";
import { useShallow } from "zustand/react/shallow";
import { SearchIcon } from "src/assets/icons";
import { useExplorer } from "src/hooks/useExplorer";
import { ExplorerStore } from "src/store";
import { SEARCH_SCOPE_TYPE } from "src/store/file/search";
const SearchFiles = () => {
const { useExplorerStore } = useExplorer();
const { changeToSearchMode, focusedFolder, focusedTag, changeSearchScope } =
useExplorerStore(
useShallow((store: ExplorerStore) => ({
changeToSearchMode: store.changeToSearchMode,
focusedFolder: store.focusedFolder,
focusedTag: store.focusedTag,
changeSearchScope: store.changeSearchScope,
}))
);
const getClassNames = () => {
return classNames(
"ffs__action-button-wrapper clickable-icon nav-action-button"
);
};
const onBeginSearch = () => {
changeToSearchMode();
if (focusedFolder) {
changeSearchScope({
type: SEARCH_SCOPE_TYPE.FOLDER,
path: focusedFolder.path,
});
} else if (focusedTag) {
changeSearchScope({
type: SEARCH_SCOPE_TYPE.TAG,
path: focusedTag.fullPath,
});
}
};
return (
<div className={getClassNames()} onClick={onBeginSearch}>
<SearchIcon className="ffs__action-button svg-icon" />
</div>
);
};
export default SearchFiles;

View file

@ -23,11 +23,9 @@ const FilesCount = ({
const [count, setCount] = useState<number | null>(null);
const onHandleVaultChange = (event: VaultChangeEvent) => {
const { file, changeType } = event.detail;
const { file } = event.detail;
if (!isFile(file)) return;
if (["create", "delete", "rename"].includes(changeType)) {
setCount(getFilesCount());
}
setCount(getFilesCount());
};
useEffect(() => {
@ -43,7 +41,7 @@ const FilesCount = ({
useEffect(() => {
setCount(getFilesCount());
}, [childrenLen, includeSubItemFiles]);
}, [childrenLen, includeSubItemFiles, getFilesCount()]);
return (
<div className="ffs__files-count">{showFilesCount ? count : ""}</div>

View file

@ -14,9 +14,9 @@ type Props = {
const FoldersBreadcrumbs = ({ folder, setFolder }: Props) => {
const { useExplorerStore } = useExplorer();
const { getFolderAncestors } = useExplorerStore(
const { getAncestors } = useExplorerStore(
useShallow((store: ExplorerStore) => ({
getFolderAncestors: store.getFolderAncestors,
getAncestors: store.getAncestors,
}))
);
@ -26,7 +26,7 @@ const FoldersBreadcrumbs = ({ folder, setFolder }: Props) => {
const folders = folder.isRoot()
? [folder]
: getFolderAncestors(folder, true);
: getAncestors(folder, true);
return (
<>
{folders.map((f, index) => {

View file

@ -0,0 +1,39 @@
import classNames from "classnames";
import { useShallow } from "zustand/react/shallow";
import { useExplorer } from "src/hooks/useExplorer";
import { SEARCH_TIPS_COPY } from "src/locales";
import { ExplorerStore } from "src/store";
import { SEARCH_FIELD } from "src/store/file/search";
const SearchField = () => {
const { useExplorerStore, plugin } = useExplorer();
const { language } = plugin;
const { searchField, toggleSearchField } = useExplorerStore(
useShallow((store: ExplorerStore) => ({
searchField: store.searchField,
toggleSearchField: store.toggleSearchField,
}))
);
const isTitleOnly = searchField === SEARCH_FIELD.TITLE_ONLY;
const { searchTitleOnly, searchFullText, titleOnly, fullText } =
SEARCH_TIPS_COPY;
const popupCopy = isTitleOnly ? searchFullText : searchTitleOnly;
const textCopy = isTitleOnly ? fullText : titleOnly;
return (
<div
className={classNames("ffs__search--field")}
data-tooltip-position="bottom"
aria-label={popupCopy[language]}
onClick={toggleSearchField}
>
{textCopy[language]}
</div>
);
};
export default SearchField;

View file

@ -0,0 +1,51 @@
import { debounce } from "obsidian";
import { useEffect, useRef } from "react";
import { useShallow } from "zustand/react/shallow";
import { useExplorer } from "src/hooks/useExplorer";
import { SEARCH_TIPS_COPY } from "src/locales";
import { ExplorerStore } from "src/store";
const SearchInput = () => {
const { useExplorerStore, plugin } = useExplorer();
const { language } = plugin;
const { changeQuery, searchByQuery, searchScope, searchField } =
useExplorerStore(
useShallow((store: ExplorerStore) => ({
changeQuery: store.changeQuery,
searchByQuery: store.searchByQuery,
searchScope: store.searchScope,
searchField: store.searchField,
}))
);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setTimeout(() => {
inputRef.current?.focus();
}, 100);
}, []);
const debouncedOnSearch = useRef(debounce(searchByQuery, 300)).current;
useEffect(() => {
searchByQuery();
}, [searchScope, searchField]);
return (
<input
type="search"
ref={inputRef}
placeholder={SEARCH_TIPS_COPY.inputToSearch[language]}
className="ffs__search-input"
onChange={(e) => {
const { value } = e.target;
changeQuery(value);
debouncedOnSearch();
}}
/>
);
};
export default SearchInput;

View file

@ -0,0 +1,46 @@
import { useShallow } from "zustand/react/shallow";
import { FolderIcon, TagIcon } from "src/assets/icons";
import { useExplorer } from "src/hooks/useExplorer";
import { SEARCH_TIPS_COPY } from "src/locales";
import { ExplorerStore } from "src/store";
import { SEARCH_SCOPE_TYPE } from "src/store/file/search";
const SearchScope = () => {
const { useExplorerStore, plugin } = useExplorer();
const { language } = plugin;
const { searchScope, clearSearchScope } = useExplorerStore(
useShallow((store: ExplorerStore) => ({
searchScope: store.searchScope,
clearSearchScope: store.clearSearchScope,
}))
);
const { allNotes } = SEARCH_TIPS_COPY;
const scopeClassName = "ffs__search--scope";
if (!searchScope)
return <div className={scopeClassName}>{allNotes[language]}</div>;
const { FOLDER } = SEARCH_SCOPE_TYPE;
const { type, path } = searchScope;
const scopeIconClassName = "ffs__search--scope-icon";
const IconComponent = type === FOLDER ? FolderIcon : TagIcon;
return (
<div className={scopeClassName}>
<IconComponent className={scopeIconClassName} />
{path}
<span
className="ffs__search--remove-option"
onClick={clearSearchScope}
data-tooltip-position="bottom"
>
</span>
</div>
);
};
export default SearchScope;

View file

@ -0,0 +1,34 @@
import { SearchIcon } from "src/assets/icons";
import SearchField from "./Field";
import SearchInput from "./Input";
import SearchScope from "./Scope";
const Search = () => {
const renderInputRow = () => (
<div className="ffs__search--input-row">
<span className="ffs__search-icon-wrapper">
<SearchIcon className="ffs__search-icon" />
</span>
<SearchInput />
</div>
);
const renderSearchOptions = () => (
<div className="ffs__search--options">
<SearchScope />
{/* <SearchField /> */}
</div>
);
return (
<>
{renderInputRow()}
{renderSearchOptions()}
</>
);
};
export default Search;

View file

@ -7,6 +7,7 @@ import {
} from "src/hooks/useSettingsHandler";
import CreateFile from "../FileActions/CreateFile";
import SearchFiles from "../FileActions/SearchFiles";
import SortFiles from "../FileActions/SortFiles";
import CreateFolder from "../FolderAndTagActions/CreateFolder";
import SortFolders from "../FolderAndTagActions/SortFoldersAndTags";
@ -25,6 +26,7 @@ export const FileActionSection = () => (
<div className="ffs__actions-section nav-buttons-container">
<CreateFile />
<SortFiles />
<SearchFiles />
</div>
);

View file

@ -5,6 +5,7 @@ import FileTree from "../FileTree";
import { ActionsContainer, FileActionSection } from "./Actions";
import { ClosePaneButton, OpenPaneButton } from "./TogglePaneButton";
import ViewModeDisplay from "./ViewModeDisplay";
type Props = {
isFilesCollapsed: boolean;
@ -45,6 +46,7 @@ const VerticalSplitFilesPane = ({
/>
</div>
</ActionsContainer>
<ViewModeDisplay />
<FileTree />
</div>
);

View file

@ -1,10 +1,13 @@
import classNames from "classnames";
import { ReactNode } from "react";
import { useShallow } from "zustand/react/shallow";
import { FolderIcon, TagIcon } from "src/assets/icons";
import { useExplorer } from "src/hooks/useExplorer";
import { ExplorerStore } from "src/store";
import { ViewMode } from "src/store/common";
import { VIEW_MODE, ViewMode } from "src/store/common";
import Search from "../Search";
const ViewModeDisplay = () => {
const { useExplorerStore, plugin } = useExplorer();
@ -21,18 +24,6 @@ const ViewModeDisplay = () => {
}))
);
const labelMap: Record<ViewMode, string> = {
folder: isZh ? "文件夹:" : "Folder: ",
tag: isZh ? "标签:" : "Tag: ",
all: isZh ? "全部文件" : "All Files",
};
const iconClassName = "ffs__view-mode-icon";
const iconMap: Record<ViewMode, ReactNode> = {
folder: <FolderIcon className={iconClassName} />,
tag: <TagIcon className={iconClassName} />,
all: <FolderIcon className={iconClassName} />,
};
const getFolderPath = () => {
if (!focusedFolder) return "";
if (focusedFolder.isRoot()) return "/" + getNameOfFolder(focusedFolder);
@ -49,24 +40,59 @@ const ViewModeDisplay = () => {
</span>
);
const renderPath = () => {
if (viewMode === "folder") {
const renderViewModeTipsHeader = () => {
const iconClassName = "ffs__view-mode-icon";
const iconMap: Record<ViewMode, ReactNode> = {
folder: <FolderIcon className={iconClassName} />,
tag: <TagIcon className={iconClassName} />,
all: <FolderIcon className={iconClassName} />,
};
const labelMap: Record<ViewMode, string> = {
folder: isZh ? "文件夹:" : "Folder: ",
tag: isZh ? "标签:" : "Tag: ",
all: isZh ? "全部文件" : "All Files",
};
return (
<>
{iconMap[viewMode]} {labelMap[viewMode]}
</>
);
};
const maybeRenderPath = () => {
if (viewMode === VIEW_MODE.FOLDER) {
if (!focusedFolder) return "";
const name = getNameOfFolder(focusedFolder);
const path = getFolderPath();
return renderPathContent(name, path);
} else if (viewMode === "tag") {
}
if (viewMode === VIEW_MODE.TAG) {
if (!focusedTag) return "";
return renderPathContent(focusedTag.fullPath, focusedTag.fullPath);
}
};
const maybeRenderViewModeTips = () => (
<div className="ffs__view-mode--main">
{renderViewModeTipsHeader()}
{maybeRenderPath()}
</div>
);
const maybeRenderSearch = () => {
if (viewMode !== VIEW_MODE.SEARCH) return null;
return <Search />;
};
return (
<div className="ffs__view-mode">
<div className="ffs__view-mode--main">
{iconMap[viewMode]} {labelMap[viewMode]}
{renderPath()}
</div>
<div
className={classNames("ffs__view-mode", {
"ffs__view-mode--search": viewMode === VIEW_MODE.SEARCH,
})}
>
{maybeRenderViewModeTips()}
{maybeRenderSearch()}
</div>
);
};

View file

@ -4,6 +4,7 @@ import { useShallow } from "zustand/react/shallow";
import { VaultChangeEvent, VaultChangeEventName } from "src/assets/constants";
import { ExplorerStore } from "src/store";
import { VIEW_MODE } from "src/store/common";
import { FILE_MANUAL_SORT_RULE } from "src/store/file/sort";
import { isFile } from "src/utils";
@ -27,6 +28,9 @@ const useChangeFile = () => {
viewMode,
initOrder,
updateOrder,
generateTagTree,
searchResults,
searchScope,
} = useExplorerStore(
useShallow((store: ExplorerStore) => ({
focusedFolder: store.focusedFolder,
@ -40,6 +44,10 @@ const useChangeFile = () => {
order: store.filesManualSortOrder,
initOrder: store.initFilesManualSortOrder,
updateOrder: store.updateFilePathInManualOrder,
generateTagTree: store.generateTagTree,
tagTree: store.tagTree,
searchResults: store.searchResults,
searchScope: store.searchScope,
}))
);
@ -68,6 +76,7 @@ const useChangeFile = () => {
focusedTag,
includeSubTagFiles,
viewMode,
searchResults,
]);
const maybeInitOrder = async () => {
@ -78,9 +87,16 @@ const useChangeFile = () => {
const onHandleVaultChange = async (event: VaultChangeEvent) => {
const { file, changeType, oldPath } = event.detail;
if (!isFile(file) || changeType === "modify") return;
if (!isFile(file)) return;
setFiles(getVisibleFiles());
if (changeType === "modify" && viewMode === VIEW_MODE.TAG) {
setTimeout(() => {
generateTagTree();
setFiles(getVisibleFiles());
}, 100);
} else {
setFiles(getVisibleFiles());
}
if (changeType === "create") {
await maybeInitOrder();
} else if (changeType === "delete" && isFilePinned(file)) {

View file

@ -145,3 +145,30 @@ export const SORT_TIPS_COPY: Copy = {
zh: "对文件夹和标签排序",
},
};
export const SEARCH_TIPS_COPY: Copy = {
searchTitleOnly: {
en: "Click to search title only",
zh: "点击切换为仅搜索标题",
},
searchFullText: {
en: "Click to switch to full text search",
zh: "点击切换为搜索全文",
},
allNotes: {
en: "Across all notes",
zh: "全部笔记",
},
titleOnly: {
en: "Title only",
zh: "仅标题",
},
fullText: {
en: "Full text",
zh: "全文搜索",
},
inputToSearch: {
en: "Searching...",
zh: "输入并搜索...",
},
};

View file

@ -8,6 +8,7 @@ import { createLocalStorageSlice, LocalStorageSlice } from "./localStorage";
import { createManualSortSlice, ManualSortSlice } from "./manualSort";
import { createPinSlice, PinSlice } from "./pin";
import { createPluginSlice, PluginSlice } from "./plugin";
import { createStructureSlice, StructureSlice } from "./structure";
import { createViewModeSlice, ViewModeSlice } from "./viewMode";
type FolderPath = string;
@ -19,7 +20,7 @@ export type CommonExplorerStore = ViewModeSlice &
LocalStorageSlice &
PluginSlice &
ManualSortSlice &
PinSlice;
PinSlice & StructureSlice;
export const createCommonExplorerStore =
(
@ -31,6 +32,7 @@ export const createCommonExplorerStore =
...createPluginSlice(plugin)(set, get, store),
...createManualSortSlice(plugin)(set, get, store),
...createPinSlice(plugin)(set, get, store),
...createStructureSlice(plugin)(set, get, store),
});
export * from "./viewMode";

View file

@ -0,0 +1,31 @@
import { TAbstractFile, TFolder } from "obsidian";
import { StateCreator } from "zustand";
import FolderFileSplitterPlugin from "src/main";
import { ExplorerStore } from "src/store";
import { TagNode } from "../tag";
export type PinnedItem = TAbstractFile | TagNode;
export type StructureSlice = {
getAncestors: (item: TAbstractFile, includeRoot?: boolean) => TFolder[];
};
export const createStructureSlice =
(
plugin: FolderFileSplitterPlugin
): StateCreator<ExplorerStore, [], [], StructureSlice> =>
(set, get) => ({
getAncestors: (item: TAbstractFile, includeRoot = false): TFolder[] => {
const ancestors: TFolder[] = [];
let current = item.parent;
while (current && !current.isRoot()) {
ancestors.unshift(current);
current = current.parent;
}
return includeRoot ? [get().rootFolder, ...ancestors] : ancestors;
},
});

View file

@ -19,7 +19,7 @@ export type ViewModeSlice = {
canFilesManualSortViewModes: ViewMode[];
canFilesSortViewModes: ViewMode[];
canCreateFilesViewModes: ViewMode[]
canCreateFilesViewModes: ViewMode[];
changeViewMode: (mode: ViewMode) => void;
restoreViewMode: () => void;
@ -58,9 +58,23 @@ export const createViewModeSlice =
});
},
restoreViewMode: () => {
get().restoreDataFromLocalStorage({
const { restoreDataFromLocalStorage, focusedFolder, focusedTag } =
get();
restoreDataFromLocalStorage({
localStorageKey: FFS_VIEW_MODE_KEY,
key: "viewMode",
transform: (mode: ViewMode) => {
const { FOLDER, TAG } = VIEW_MODE;
if (![FOLDER, TAG].includes(mode)) {
if (focusedFolder) {
return FOLDER;
}
if (focusedTag) {
return TAG;
}
return FOLDER;
}
},
});
},

View file

@ -7,6 +7,7 @@ import { createFileActionsSlice, FileActionsSlice } from "./actions";
import { createFocusedFileSlice, FocusedFileSlice } from "./focus";
import { createManualSortFileSlice, ManualSortFileSlice } from "./manualSort";
import { createPinnedFileSlice, PinnedFileSlice } from "./pin";
import { createSearchFileSlice, SearchFileSlice } from "./search";
import { createSortFileSlice, SortFileSlice } from "./sort";
import { createFileStructureSlice, FileStructureSlice } from "./structure";
@ -14,7 +15,7 @@ export type FileExplorerStore = PinnedFileSlice &
SortFileSlice &
ManualSortFileSlice &
FocusedFileSlice &
FileActionsSlice & FileStructureSlice;
FileActionsSlice & FileStructureSlice & SearchFileSlice;
export const createFileExplorerStore =
(
@ -27,4 +28,5 @@ export const createFileExplorerStore =
...createFileActionsSlice(plugin)(set, get, store),
...createFocusedFileSlice(plugin)(set, get, store),
...createFileStructureSlice(plugin)(set, get, store),
...createSearchFileSlice(plugin)(set, get,store)
});

132
src/store/file/search.ts Normal file
View file

@ -0,0 +1,132 @@
import { TFile } from "obsidian";
import { StateCreator } from "zustand";
import FolderFileSplitterPlugin from "src/main";
import { ValueOf } from "src/settings";
import { ExplorerStore } from "..";
export const SEARCH_SCOPE_TYPE = {
FOLDER: "folder",
TAG: "tag",
} as const;
export type SEARCH_SCOPE_TYPE = ValueOf<typeof SEARCH_SCOPE_TYPE>;
export type FolderSearchScope = {
type: "folder";
path: string;
};
export type TagSearchScope = {
type: "tag";
path: string;
};
export type SearchScope = FolderSearchScope | TagSearchScope;
export const SEARCH_FIELD = {
TITLE_ONLY: "titleOnly",
FULL_TEXT: "fullText",
} as const;
export type SearchField = ValueOf<typeof SEARCH_FIELD>;
export const DEFAULT_SEARCH_FIELD: SearchField = SEARCH_FIELD.FULL_TEXT;
export const DEFAULT_QUERY = "";
export interface SearchFileSlice {
searchScope: SearchScope | null;
searchField: SearchField;
query: string;
changeSearchField: (field: SearchField) => void;
toggleSearchField: () => void;
changeSearchScope: (scope: SearchScope) => void;
clearSearchScope: () => void;
changeQuery: (query: string) => void;
clearQuery: () => void;
searchResults: TFile[];
updateSearchResults: (files: TFile[]) => void;
searchByQuery: () => void;
}
export const createSearchFileSlice =
(
plugin: FolderFileSplitterPlugin
): StateCreator<ExplorerStore, [], [], SearchFileSlice> =>
(set, get) => ({
searchScope: null,
searchField: DEFAULT_SEARCH_FIELD,
query: DEFAULT_QUERY,
searchResults: [],
changeSearchField: (field: SearchField) => {
set({
searchField: field,
});
},
toggleSearchField: () => {
const { changeSearchField, searchField } = get();
const { FULL_TEXT, TITLE_ONLY } = SEARCH_FIELD;
changeSearchField(
searchField === TITLE_ONLY ? FULL_TEXT : TITLE_ONLY
);
},
changeSearchScope: (scope: SearchScope) => {
set({
searchScope: scope,
});
},
clearSearchScope: () => {
set({
searchScope: null,
});
},
changeQuery: (query: string) => {
set({
query,
});
},
clearQuery: () => {
set({
query: DEFAULT_QUERY,
});
},
updateSearchResults: (files: TFile[]) => {
set({
searchResults: files,
});
},
searchByQuery: () => {
const {
query,
updateSearchResults,
getFiles,
searchScope,
getTagsOfFile,
getAncestors,
} = get();
const allFiles = getFiles();
let filteredFiles = allFiles.filter((f) =>
f.basename.toLowerCase().includes(query.trim().toLowerCase())
);
// TODO: support search content
const { type, path } = searchScope ?? {};
if (type === SEARCH_SCOPE_TYPE.FOLDER) {
filteredFiles = filteredFiles.filter((f) => {
const parentFolders = getAncestors(f);
return parentFolders.some((f) => f.path === path);
});
} else if (type === SEARCH_SCOPE_TYPE.TAG) {
filteredFiles = filteredFiles.filter((f) => {
const tags = getTagsOfFile(f);
return tags.some((t) => t.fullPath === path);
});
}
updateSearchResults(filteredFiles);
},
});

View file

@ -35,6 +35,7 @@ export const createFileStructureSlice =
getFilesInFolder,
getFilesInTag,
viewMode,
searchResults,
} = get();
switch (viewMode) {
case VIEW_MODE.ALL:
@ -43,6 +44,8 @@ export const createFileStructureSlice =
return focusedFolder ? getFilesInFolder(focusedFolder) : [];
case VIEW_MODE.TAG:
return focusedTag ? getFilesInTag(focusedTag) : [];
case VIEW_MODE.SEARCH:
return searchResults;
default:
return [];
}

View file

@ -15,7 +15,6 @@ export interface FolderStructureSlice {
getNameOfFolder: (folder: TFolder) => string;
getFolderAncestors: (folder: TFolder, includeRoot?: boolean) => TFolder[];
isAnscestorOf: (ancestor: TFolder, folder: TFolder) => boolean;
getFolderLevel: (folder: TFolder) => number;
@ -46,22 +45,9 @@ export const createFolderStructureSlice =
getNameOfFolder: (folder: TFolder) => {
return folder.isRoot() ? plugin.app.vault.getName() : folder.name;
},
getFolderAncestors: (
folder: TFolder,
includeRoot = false
): TFolder[] => {
const ancestors: TFolder[] = [];
let current = folder.parent;
while (current && !current.isRoot()) {
ancestors.unshift(current);
current = current.parent;
}
return includeRoot ? [get().rootFolder, ...ancestors] : ancestors;
},
isAnscestorOf: (ancestor: TFolder, folder: TFolder): boolean => {
const ancestors = get().getFolderAncestors(folder);
const ancestors = get().getAncestors(folder);
return ancestors.some((f) => f.path === ancestor.path);
},
getFolderLevel: (folder: TFolder) => {

View file

@ -93,8 +93,8 @@ export const createToggleFolderSlice =
}
},
expandAncestors: (folder: TFolder) => {
const { getFolderAncestors, expandFolder } = get();
const ancestors = getFolderAncestors(folder);
const { getAncestors, expandFolder } = get();
const ancestors = getAncestors(folder);
ancestors.forEach(expandFolder);
},

View file

@ -38,12 +38,12 @@ export const createExplorerStore = (plugin: FolderFileSplitterPlugin) =>
} = get();
generateTagTree();
restoreViewMode();
restoreLastFocusedFolder();
restoreLastFocusedFile();
restoreExpandedFolderPaths();
restoreLastFocusedTag();
restoreExpandedFolderPaths();
restoreExpandedTagPaths();
restoreViewMode();
await Promise.all([
restoreFolderSortRule(),

View file

@ -474,10 +474,16 @@ div.ffs__file-content {
overflow: hidden;
border-radius: var(--radius-s);
margin: 4px 12px;
display: flex;
flex-direction: column;
align-items: center;
}
.ffs__view-mode:not(.ffs__view-mode--search) {
padding: 4px 8px;
background-color: var(--background-modifier-border);
}
/* Search */
.ffs__view-mode--main {
display: flex;
align-items: center;
@ -497,11 +503,79 @@ div.ffs__file-content {
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 100%;
flex: 1;
vertical-align: bottom;
cursor: pointer;
}
/* Search */
.ffs__search--input-row {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 36px;
}
.ffs__search-icon-wrapper {
position: absolute;
left: 10px;
pointer-events: none;
display: flex;
justify-items: center;
}
.ffs__search-icon {
width: 16px;
height: 16px;
color: var(--icon-color);
}
.ffs__search--input-row input {
padding-left: 30px;
width: 100%;
&:focus {
outline: none;
border-color: var(--background-modifier-border-focus);
box-shadow: 3px var(--background-modifier-border-focus);
}
}
.ffs__search--options {
display: flex;
padding-top: 4px;
gap: 4px;
width: 100%;
height: 34px;
}
.ffs__search--scope-icon {
width: 12px;
height: 12px;
}
.ffs__search--scope,
.ffs__search--field {
display: flex;
align-items: center;
font-size: 12px;
gap: 2px;
padding: 2px 6px;
border: 2px solid var(--background-modifier-border);
border-radius: var(--radius-s);
color: var(--nav-item-color);
}
.ffs__search--field,
.ffs__search--remove-option {
cursor: pointer;
}
.ffs__search--remove-option {
font-size: 8px;
margin-left: 3px;
}
/* Drag and Drop folder and file */
.ffs__drag-overlay {
position: absolute;