feat: support search feature
This commit is contained in:
parent
67f5f7f419
commit
cef5ae98eb
|
|
@ -436,7 +436,8 @@
|
|||
"gotIt": "Got it",
|
||||
"retry": "Retry",
|
||||
"uploadFailed": "Upload failed.",
|
||||
"copyLinkOriginal": "Copy link to original"
|
||||
"copyLinkOriginal": "Copy link to original",
|
||||
"search": "Search"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "Welcome!",
|
||||
|
|
@ -3039,5 +3040,7 @@
|
|||
"memberCount_zero": "{{count}} members",
|
||||
"memberCount_one": "{{count}} member",
|
||||
"memberCount_many": "{{count}} members",
|
||||
"memberCount_other": "{{count}} members"
|
||||
"memberCount_other": "{{count}} members",
|
||||
"aiMatch": "AI match",
|
||||
"titleMatch": "Title match"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1679,5 +1679,26 @@ export async function cancelSubscription (workspaceId: string, plan: Subscriptio
|
|||
return;
|
||||
}
|
||||
|
||||
return Promise.reject(res?.data);
|
||||
}
|
||||
|
||||
export async function searchWorkspace (workspaceId: string, query: string) {
|
||||
const url = `/api/search/${workspaceId}`;
|
||||
const res = await axiosInstance?.get<{
|
||||
code: number;
|
||||
data: {
|
||||
object_id: string
|
||||
}[];
|
||||
message: string;
|
||||
}>(url, {
|
||||
params: {
|
||||
query,
|
||||
},
|
||||
});
|
||||
|
||||
if (res?.data.code === 0) {
|
||||
return res?.data.data.map(item => item.object_id);
|
||||
}
|
||||
|
||||
return Promise.reject(res?.data);
|
||||
}
|
||||
|
|
@ -568,4 +568,8 @@ export class AFClientService implements AFService {
|
|||
deleteQuickNote (workspaceId: string, id: string) {
|
||||
return APIService.deleteQuickNote(workspaceId, id);
|
||||
}
|
||||
|
||||
searchWorkspace(workspaceId: string, query: string) {
|
||||
return APIService.searchWorkspace(workspaceId, query);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export class SyncManager {
|
|||
private setupListener () {
|
||||
this.doc.on('update', (_update: Uint8Array, origin: CollabOrigin) => {
|
||||
if (origin === CollabOrigin.Remote) return;
|
||||
|
||||
console.log('Local changes detected. Sending update...', origin);
|
||||
this.debouncedSendUpdate();
|
||||
});
|
||||
}
|
||||
|
|
@ -118,6 +118,7 @@ export class SyncManager {
|
|||
|
||||
public initialize () {
|
||||
if (this.hasUnsyncedChanges) {
|
||||
console.log('Unsynced changes found. Sending update...');
|
||||
// Send an update if there are unsynced changes
|
||||
this.debouncedSendUpdate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export interface WorkspaceService {
|
|||
deleteWorkspace: (workspaceId: string) => Promise<void>;
|
||||
getWorkspaceMembers: (workspaceId: string) => Promise<WorkspaceMember[]>;
|
||||
inviteMembers: (workspaceId: string, emails: string[]) => Promise<void>;
|
||||
searchWorkspace: (workspaceId: string, searchTerm: string) => Promise<string[]>;
|
||||
}
|
||||
|
||||
export interface AppService {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function applyYDoc(doc: Y.Doc, state: Uint8Array) {
|
|||
doc,
|
||||
() => {
|
||||
try {
|
||||
Y.applyUpdate(doc, state);
|
||||
Y.applyUpdate(doc, state, CollabOrigin.Remote);
|
||||
} catch (e) {
|
||||
console.error('Error applying', doc, e);
|
||||
throw e;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function PageIcon({
|
|||
}) {
|
||||
|
||||
const emoji = useMemo(() => {
|
||||
if (view.icon && view.icon.ty === ViewIconType.Emoji) {
|
||||
if (view.icon && view.icon.ty === ViewIconType.Emoji && view.icon.value) {
|
||||
return view.icon.value;
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ function PageIcon({
|
|||
}, [emoji]);
|
||||
|
||||
const icon = useMemo(() => {
|
||||
if (view.icon && view.icon.ty === ViewIconType.Icon) {
|
||||
if (view.icon && view.icon.ty === ViewIconType.Icon && view.icon.value) {
|
||||
const json = JSON.parse(view.icon.value);
|
||||
const cleanSvg = DOMPurify.sanitize(json.iconContent.replaceAll('black', renderColor(json.color)).replace('<svg', '<svg width="100%" height="100%"'), {
|
||||
USE_PROFILES: { svg: true, svgFilters: true },
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import React, { lazy } from 'react';
|
|||
import { Workspaces } from '@/components/app/workspaces';
|
||||
import Outline from 'src/components/app/outline/Outline';
|
||||
import { UIVariant } from '@/application/types';
|
||||
import { Search } from 'src/components/app/search';
|
||||
|
||||
const SideBarBottom = lazy(() => import('@/components/app/SideBarBottom'));
|
||||
|
||||
|
|
@ -41,12 +42,16 @@ function SideBar({
|
|||
className={'flex w-full gap-1 flex-1 flex-col'}
|
||||
>
|
||||
<div
|
||||
className={'px-[10px] bg-bg-base z-[1] flex-col gap-1 justify-around items-center sticky top-12'}
|
||||
className={'px-[10px] bg-bg-base z-[1] flex-col gap-1.5 justify-around items-center sticky top-12'}
|
||||
>
|
||||
<div style={{
|
||||
borderColor: scrollTop > 10 ? 'var(--line-divider)' : undefined,
|
||||
}} className={'flex border-b pb-2 w-full border-transparent'}>
|
||||
<NewPage/>
|
||||
<Search />
|
||||
<div
|
||||
style={{
|
||||
borderColor: scrollTop > 10 ? 'var(--line-divider)' : undefined,
|
||||
}}
|
||||
className={'flex border-b pb-2 w-full border-transparent'}
|
||||
>
|
||||
<NewPage />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -55,7 +60,7 @@ function SideBar({
|
|||
width={drawerWidth}
|
||||
/>
|
||||
|
||||
<SideBarBottom/>
|
||||
<SideBarBottom />
|
||||
</div>
|
||||
</OutlineDrawer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import { View } from '@/application/types';
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
import { findView } from '@/components/_shared/outline/utils';
|
||||
import { useAppOutline, useCurrentWorkspaceId } from '@/components/app/app.hooks';
|
||||
import ViewList from '@/components/app/search/ViewList';
|
||||
import { useService } from '@/components/main/app.hooks';
|
||||
import { debounce } from 'lodash-es';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function BestMatch ({
|
||||
onClose,
|
||||
searchValue
|
||||
}: {
|
||||
onClose: () => void;
|
||||
searchValue: string;
|
||||
}) {
|
||||
const [views, setViews] = React.useState<View[]>([]);
|
||||
const { t } = useTranslation();
|
||||
const outline = useAppOutline();
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
const service = useService();
|
||||
const currentWorkspaceId = useCurrentWorkspaceId()
|
||||
const handleSearch = useCallback(async (searchTerm: string) => {
|
||||
if (!outline) return;
|
||||
if (!currentWorkspaceId || !service) return;
|
||||
if (!searchTerm) {
|
||||
setViews([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const res = await service.searchWorkspace(currentWorkspaceId, searchTerm);
|
||||
const views = res.map(id => {
|
||||
return findView(outline, id);
|
||||
});
|
||||
|
||||
setViews(views.filter(Boolean) as View[]);
|
||||
// eslint-disable-next-line
|
||||
} catch (e: any) {
|
||||
notify.error(e.message);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
}, [currentWorkspaceId, outline, service]);
|
||||
|
||||
const debounceSearch = useMemo(() => {
|
||||
return debounce(handleSearch, 300);
|
||||
}, [handleSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
void debounceSearch(searchValue);
|
||||
}, [searchValue, debounceSearch]);
|
||||
|
||||
|
||||
return <ViewList views={views} title={t('commandPalette.bestMatches')} onClose={onClose} loading={loading} />
|
||||
}
|
||||
|
||||
export default BestMatch;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { useAppRecent } from '@/components/app/app.hooks';
|
||||
import ViewList from '@/components/app/search/ViewList';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function RecentViews ({
|
||||
onClose
|
||||
}: {
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const {
|
||||
recentViews,
|
||||
loadRecentViews
|
||||
} = useAppRecent();
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
setLoading(true);
|
||||
await loadRecentViews?.();
|
||||
setLoading(false);
|
||||
})();
|
||||
}, [loadRecentViews]);
|
||||
|
||||
|
||||
return (
|
||||
<ViewList views={recentViews} title={t('commandPalette.recentHistory')} onClose={onClose} loading={loading} />
|
||||
);
|
||||
}
|
||||
|
||||
export default RecentViews;
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
import { Popover } from '@/components/_shared/popover';
|
||||
import BestMatch from '@/components/app/search/BestMatch';
|
||||
import RecentViews from '@/components/app/search/RecentViews';
|
||||
import TitleMatch from '@/components/app/search/TitleMatch';
|
||||
import { createHotkey, HOT_KEY_NAME } from '@/utils/hotkeys';
|
||||
import { Button, Dialog, InputBase, Tooltip } from '@mui/material';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { ReactComponent as SearchIcon } from '@/assets/search.svg';
|
||||
import { ReactComponent as CheckIcon } from '@/assets/check.svg';
|
||||
import { ReactComponent as DownIcon } from '@/assets/chevron_down.svg';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as CloseIcon } from '@/assets/close.svg';
|
||||
|
||||
enum SEARCH_TYPE {
|
||||
AI_SUGGESTION = 'AI_SUGGESTION',
|
||||
TITLE_MATCH = 'TITLE_MATCH',
|
||||
}
|
||||
|
||||
export function Search () {
|
||||
const [open, setOpen] = React.useState<boolean>(false);
|
||||
const { t } = useTranslation();
|
||||
const [searchValue, setSearchValue] = React.useState<string>('');
|
||||
const [searchType, setSearchType] = React.useState<SEARCH_TYPE>(SEARCH_TYPE.TITLE_MATCH);
|
||||
const [searchTypeAnchorEl, setSearchTypeAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
setSearchValue('');
|
||||
};
|
||||
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
switch (true) {
|
||||
|
||||
case createHotkey(HOT_KEY_NAME.SEARCH)(e):
|
||||
e.preventDefault();
|
||||
setOpen(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
document.addEventListener('keydown', onKeyDown, true);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onKeyDown, true);
|
||||
};
|
||||
}, [onKeyDown]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.currentTarget.blur();
|
||||
setOpen(true);
|
||||
}}
|
||||
startIcon={<SearchIcon className={'w-5 opacity-60 h-5 mr-[1px]'} />}
|
||||
size={'small'}
|
||||
className={'text-sm font-normal py-1.5 justify-start w-full hover:bg-fill-list-hover'}
|
||||
color={'inherit'}
|
||||
>
|
||||
{t('button.search')}
|
||||
</Button>
|
||||
<Dialog
|
||||
disableRestoreFocus={true}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
classes={{ container: 'items-start max-md:mt-auto max-md:items-center mt-[10%]' }}
|
||||
>
|
||||
<div className={'flex gap-2 border-b border-line-default w-full p-4'}>
|
||||
<div className={'w-full flex gap-4 items-center min-w-[500px] max-w-[70vw]'}>
|
||||
<SearchIcon className={'w-5 opacity-60 h-5 mr-[1px]'} />
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
autoFocus={true}
|
||||
className={'flex-1'}
|
||||
fullWidth={true}
|
||||
placeholder={t('commandPalette.placeholder')}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
visibility: searchValue ? 'visible' : 'hidden',
|
||||
}}
|
||||
className={'p-0.5 rounded-full opacity-60 hover:opacity-100 bg-fill-list-hover cursor-pointer'}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setSearchValue('');
|
||||
}}
|
||||
><CloseIcon className={'w-3 h-3'} /></span>
|
||||
<Tooltip title={searchType === SEARCH_TYPE.TITLE_MATCH ? undefined : 'we currently only support searching for pages and content in documents'}>
|
||||
<div
|
||||
onClick={e => {
|
||||
setSearchTypeAnchorEl(e.currentTarget);
|
||||
}}
|
||||
className={'cursor-pointer flex items-center p-1 px-2 text-xs rounded bg-fill-list-hover'}
|
||||
>
|
||||
{
|
||||
searchType === SEARCH_TYPE.TITLE_MATCH ?
|
||||
t('titleMatch') :
|
||||
t('aiMatch')
|
||||
}
|
||||
<DownIcon className={'w-3 h-3 ml-1 opacity-60'} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{!searchValue ? <RecentViews onClose={handleClose} /> : searchType === SEARCH_TYPE.AI_SUGGESTION ? <BestMatch
|
||||
searchValue={searchValue}
|
||||
onClose={handleClose}
|
||||
/> : <TitleMatch
|
||||
searchValue={searchValue}
|
||||
onClose={handleClose}
|
||||
/>}
|
||||
</Dialog>
|
||||
<Popover
|
||||
open={Boolean(searchTypeAnchorEl)}
|
||||
anchorEl={searchTypeAnchorEl}
|
||||
onClose={() => setSearchTypeAnchorEl(null)}
|
||||
slotProps={{
|
||||
paper: {
|
||||
className: 'p-2 w-fit my-2',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{[SEARCH_TYPE.TITLE_MATCH, SEARCH_TYPE.AI_SUGGESTION].map(type => (
|
||||
<div
|
||||
key={type}
|
||||
className={'px-2 py-1.5 text-xs rounded-[8px] flex items-center gap-2 cursor-pointer hover:bg-fill-list-hover'}
|
||||
onClick={() => {
|
||||
setSearchType(type);
|
||||
setSearchTypeAnchorEl(null);
|
||||
}}
|
||||
>
|
||||
{type === SEARCH_TYPE.TITLE_MATCH ? t('titleMatch') : t('aiMatch')}
|
||||
{type === searchType && <CheckIcon className={'w-4 text-function-info h-4 ml-2'} />}
|
||||
</div>
|
||||
))}
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Search;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { filterViews } from '@/components/_shared/outline/utils';
|
||||
import { useAppOutline } from '@/components/app/app.hooks';
|
||||
import ViewList from '@/components/app/search/ViewList';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function TitleMatch ({
|
||||
onClose,
|
||||
searchValue,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
searchValue: string;
|
||||
}) {
|
||||
const outline = useAppOutline();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const views = useMemo(() => {
|
||||
if (!outline) return [];
|
||||
return filterViews(outline, searchValue);
|
||||
}, [outline, searchValue]);
|
||||
|
||||
return (
|
||||
<ViewList
|
||||
views={views}
|
||||
title={t('commandPalette.bestMatches')}
|
||||
onClose={onClose}
|
||||
loading={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default TitleMatch;
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
import { View } from '@/application/types';
|
||||
import PageIcon from '@/components/_shared/view-icon/PageIcon';
|
||||
import { useAppHandlers } from '@/components/app/app.hooks';
|
||||
import { createHotkey, HOT_KEY_NAME } from '@/utils/hotkeys';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function ViewList ({
|
||||
title,
|
||||
views,
|
||||
onClose,
|
||||
loading
|
||||
}: {
|
||||
title: string;
|
||||
views?: View[];
|
||||
onClose: () => void;
|
||||
loading: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedView, setSelectedView] = React.useState<string>('');
|
||||
const { toView: navigateToView } = useAppHandlers();
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!views) return;
|
||||
if (createHotkey(HOT_KEY_NAME.ENTER)(e) && selectedView) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
void navigateToView(selectedView);
|
||||
onClose();
|
||||
} else if (createHotkey(HOT_KEY_NAME.DOWN)(e) || createHotkey(HOT_KEY_NAME.UP)(e) || createHotkey(HOT_KEY_NAME.TAB)(e)) {
|
||||
e.preventDefault();
|
||||
const currentIndex = views.findIndex(view => view.view_id === selectedView);
|
||||
let nextViewId = '';
|
||||
|
||||
if (currentIndex === -1) {
|
||||
nextViewId = views[0].view_id;
|
||||
} else {
|
||||
if (createHotkey(HOT_KEY_NAME.DOWN)(e) || createHotkey(HOT_KEY_NAME.TAB)(e)) {
|
||||
nextViewId = views[(currentIndex + 1) % views.length].view_id;
|
||||
} else {
|
||||
nextViewId = views[(currentIndex - 1 + views.length) % views.length].view_id;
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedView(nextViewId);
|
||||
const el = ref.current?.querySelector(`[data-item-id="${nextViewId}"]`);
|
||||
|
||||
if (el) {
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
}, [navigateToView, onClose, views, selectedView]);
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={'flex flex-col'}
|
||||
>
|
||||
<div className={'px-4 py-2'}>
|
||||
{title}
|
||||
</div>
|
||||
<div className={'flex min-h-[280px] flex-col max-h-[360px] appflowy-scroller overflow-y-auto'}>
|
||||
{views?.length ? views.map(view => (
|
||||
<div
|
||||
data-item-id={view.view_id}
|
||||
style={{
|
||||
backgroundColor: selectedView === view.view_id ? 'var(--fill-list-active)' : undefined
|
||||
}}
|
||||
onClick={() => {
|
||||
void navigateToView(view.view_id);
|
||||
onClose();
|
||||
}}
|
||||
key={view.view_id}
|
||||
className={'flex items-center border-t border-line-default w-full p-4 cursor-pointer hover:bg-fill-list-active gap-2'}
|
||||
>
|
||||
<PageIcon
|
||||
view={view}
|
||||
className={'w-5 h-5'}
|
||||
/>
|
||||
<div className={'text-sm font-normal flex-1 truncate'}>
|
||||
{view.name.trim() || t('menuAppHeader.defaultNewPageName')}
|
||||
</div>
|
||||
</div>
|
||||
)) : <div className={'text-center p-6 text-sm text-text-caption'}>
|
||||
{t('findAndReplace.noResult')}
|
||||
</div>}
|
||||
{loading &&
|
||||
<div className={'text-center text-sm text-text-caption bg-bg-body opacity-75 absolute w-full h-full inset-0 flex items-center justify-center'}>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className={'w-full p-4 flex text-text-caption text-xs gap-2 items-center'}>
|
||||
<span className={'rounded bg-fill-list-hover p-1'}>TAB</span>
|
||||
to navigate
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewList;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './Search'
|
||||
|
|
@ -65,7 +65,7 @@ function NewPage() {
|
|||
onClick={() => setOpen(true)}
|
||||
startIcon={<Add className={'w-5 h-5 mr-[1px]'}/>}
|
||||
size={'small'}
|
||||
className={'text-sm font-normal py-1 justify-start w-full hover:bg-fill-list-hover'}
|
||||
className={'text-sm font-normal py-1.5 justify-start w-full hover:bg-fill-list-hover'}
|
||||
color={'inherit'}
|
||||
>
|
||||
{t('newPageText')}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
--icon-on-toolbar: white;
|
||||
--line-border: #59647a;
|
||||
--line-divider: #384967;
|
||||
--line-default: #2e2e2e;
|
||||
--line-on-toolbar: #99a6b8;
|
||||
--line-card: #384967;
|
||||
--fill-default: #00bcf0;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
--icon-on-toolbar: #ffffff;
|
||||
--line-border: #bdbdbd;
|
||||
--line-divider: #1F232923;
|
||||
--line-default: #f3f4f6;
|
||||
--line-on-toolbar: #4f4f4f;
|
||||
--line-card: #1F23291E;
|
||||
--fill-toolbar: #333333;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const getModifier = () => {
|
|||
|
||||
export enum HOT_KEY_NAME {
|
||||
ENTER = 'enter',
|
||||
TAB = 'tab',
|
||||
CLEAR_CACHE = 'clear-cache',
|
||||
UP = 'up',
|
||||
DOWN = 'down',
|
||||
|
|
@ -62,6 +63,7 @@ export enum HOT_KEY_NAME {
|
|||
TOGGLE_THEME = 'toggle-theme',
|
||||
TOGGLE_SIDEBAR = 'toggle-sidebar',
|
||||
QUICK_NOTE = 'quick-note',
|
||||
SEARCH = 'search',
|
||||
}
|
||||
|
||||
const defaultHotKeys = {
|
||||
|
|
@ -110,6 +112,8 @@ const defaultHotKeys = {
|
|||
[HOT_KEY_NAME.MOVE_CURSOR_TO_TOP]: ['mod+up'],
|
||||
[HOT_KEY_NAME.ENTER]: ['enter'],
|
||||
[HOT_KEY_NAME.QUICK_NOTE]: ['mod+/'],
|
||||
[HOT_KEY_NAME.SEARCH]: ['mod+p'],
|
||||
[HOT_KEY_NAME.TAB]: ['tab'],
|
||||
};
|
||||
|
||||
const replaceModifier = (hotkey: string) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
/**
|
||||
* Do not edit directly
|
||||
* Generated on Tue, 24 Dec 2024 08:57:39 GMT
|
||||
* Generated on Fri, 03 Jan 2025 08:02:02 GMT
|
||||
* Generated from $pnpm css:variables
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
/**
|
||||
* Do not edit directly
|
||||
* Generated on Tue, 24 Dec 2024 08:57:39 GMT
|
||||
* Generated on Fri, 03 Jan 2025 08:02:02 GMT
|
||||
* Generated from $pnpm css:variables
|
||||
*/
|
||||
|
||||
|
|
@ -26,6 +26,7 @@ module.exports = {
|
|||
"line": {
|
||||
"border": "var(--line-border)",
|
||||
"divider": "var(--line-divider)",
|
||||
"default": "var(--line-default)",
|
||||
"on-toolbar": "var(--line-on-toolbar)",
|
||||
"card": "var(--line-card)"
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue