feat(core): cmd+f search in doc function (#7040)
You can use the cmd+F shortcut key to trigger the FindInPage function.
This commit is contained in:
@@ -15,6 +15,8 @@ export const openStarAFFiNEModalAtom = atom(false);
|
|||||||
export const openIssueFeedbackModalAtom = atom(false);
|
export const openIssueFeedbackModalAtom = atom(false);
|
||||||
export const openHistoryTipsModalAtom = atom(false);
|
export const openHistoryTipsModalAtom = atom(false);
|
||||||
|
|
||||||
|
export const rightSidebarWidthAtom = atom(320);
|
||||||
|
|
||||||
export type SettingAtom = Pick<
|
export type SettingAtom = Pick<
|
||||||
SettingProps,
|
SettingProps,
|
||||||
'activeTab' | 'workspaceMetadata'
|
'activeTab' | 'workspaceMetadata'
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { FindInPageService } from '@affine/core/modules/find-in-page/services/find-in-page';
|
||||||
|
import { registerAffineCommand, useService } from '@toeverything/infra';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
export function useRegisterFindInPageCommands() {
|
||||||
|
const findInPage = useService(FindInPageService).findInPage;
|
||||||
|
const toggleVisible = useCallback(() => {
|
||||||
|
findInPage.toggleVisible();
|
||||||
|
}, [findInPage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!environment.isDesktop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const unsubs: Array<() => void> = [];
|
||||||
|
unsubs.push(
|
||||||
|
registerAffineCommand({
|
||||||
|
id: `editor:find-in-page`,
|
||||||
|
keyBinding: {
|
||||||
|
binding: '$mod+f',
|
||||||
|
},
|
||||||
|
icon: null,
|
||||||
|
label: '',
|
||||||
|
run() {
|
||||||
|
toggleVisible();
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubs.forEach(unsub => unsub());
|
||||||
|
};
|
||||||
|
}, [toggleVisible]);
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
resolveDragEndIntent,
|
resolveDragEndIntent,
|
||||||
useGlobalDNDHelper,
|
useGlobalDNDHelper,
|
||||||
} from '../hooks/affine/use-global-dnd-helper';
|
} from '../hooks/affine/use-global-dnd-helper';
|
||||||
|
import { useRegisterFindInPageCommands } from '../hooks/affine/use-register-find-in-page-commands';
|
||||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||||
import { useRegisterWorkspaceCommands } from '../hooks/use-register-workspace-commands';
|
import { useRegisterWorkspaceCommands } from '../hooks/use-register-workspace-commands';
|
||||||
import { useRegisterNavigationCommands } from '../modules/navigation/view/use-register-navigation-commands';
|
import { useRegisterNavigationCommands } from '../modules/navigation/view/use-register-navigation-commands';
|
||||||
@@ -117,6 +118,7 @@ export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => {
|
|||||||
|
|
||||||
useRegisterWorkspaceCommands();
|
useRegisterWorkspaceCommands();
|
||||||
useRegisterNavigationCommands();
|
useRegisterNavigationCommands();
|
||||||
|
useRegisterFindInPageCommands();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// hotfix for blockVersions
|
// hotfix for blockVersions
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { cmdFind } from '@affine/electron-api';
|
||||||
|
import { Entity, LiveData } from '@toeverything/infra';
|
||||||
|
import { Observable, of, switchMap } from 'rxjs';
|
||||||
|
|
||||||
|
type FindInPageResult = {
|
||||||
|
requestId: number;
|
||||||
|
activeMatchOrdinal: number;
|
||||||
|
matches: number;
|
||||||
|
finalUpdate: boolean;
|
||||||
|
};
|
||||||
|
export class FindInPage extends Entity {
|
||||||
|
// modal open/close
|
||||||
|
|
||||||
|
readonly searchText$ = new LiveData<string | null>(null);
|
||||||
|
private readonly direction$ = new LiveData<'forward' | 'backward'>('forward');
|
||||||
|
readonly isSearching$ = new LiveData(false);
|
||||||
|
|
||||||
|
readonly visible$ = new LiveData(false);
|
||||||
|
|
||||||
|
readonly result$ = LiveData.from(
|
||||||
|
this.searchText$.pipe(
|
||||||
|
switchMap(searchText => {
|
||||||
|
if (!searchText) {
|
||||||
|
return of(null);
|
||||||
|
} else {
|
||||||
|
return new Observable<FindInPageResult>(subscriber => {
|
||||||
|
const handleResult = (result: FindInPageResult) => {
|
||||||
|
subscriber.next(result);
|
||||||
|
if (result.finalUpdate) {
|
||||||
|
subscriber.complete();
|
||||||
|
this.isSearching$.next(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.isSearching$.next(true);
|
||||||
|
cmdFind
|
||||||
|
?.findInPage(searchText, {
|
||||||
|
forward: this.direction$.value === 'forward',
|
||||||
|
})
|
||||||
|
.then(() => cmdFind?.onFindInPageResult(handleResult))
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
this.isSearching$.next(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cmdFind?.offFindInPageResult(handleResult);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ requestId: 0, activeMatchOrdinal: 0, matches: 0, finalUpdate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
findInPage(searchText: string) {
|
||||||
|
this.searchText$.next(searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateResult(result: FindInPageResult) {
|
||||||
|
this.result$.next(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeVisible(visible: boolean) {
|
||||||
|
this.visible$.next(visible);
|
||||||
|
if (!visible) {
|
||||||
|
this.stopFindInPage('clearSelection');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleVisible() {
|
||||||
|
const nextVisible = !this.visible$.value;
|
||||||
|
this.visible$.next(nextVisible);
|
||||||
|
if (!nextVisible) {
|
||||||
|
this.stopFindInPage('clearSelection');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backward() {
|
||||||
|
if (!this.searchText$.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.direction$.next('backward');
|
||||||
|
this.searchText$.next(this.searchText$.value);
|
||||||
|
cmdFind?.onFindInPageResult(result => this.updateResult(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
forward() {
|
||||||
|
if (!this.searchText$.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.direction$.next('forward');
|
||||||
|
this.searchText$.next(this.searchText$.value);
|
||||||
|
cmdFind?.onFindInPageResult(result => this.updateResult(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
stopFindInPage(
|
||||||
|
action: 'clearSelection' | 'keepSelection' | 'activateSelection'
|
||||||
|
) {
|
||||||
|
if (action === 'clearSelection') {
|
||||||
|
this.searchText$.next(null);
|
||||||
|
}
|
||||||
|
cmdFind?.stopFindInPage(action).catch(e => console.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
8
packages/frontend/core/src/modules/find-in-page/index.ts
Normal file
8
packages/frontend/core/src/modules/find-in-page/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type { Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { FindInPage } from './entities/find-in-page';
|
||||||
|
import { FindInPageService } from './services/find-in-page';
|
||||||
|
|
||||||
|
export function configureFindInPageModule(framework: Framework) {
|
||||||
|
framework.service(FindInPageService).entity(FindInPage);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { FindInPage } from '../entities/find-in-page';
|
||||||
|
|
||||||
|
export class FindInPageService extends Service {
|
||||||
|
public readonly findInPage = this.framework.createEntity(FindInPage);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { createVar, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const panelWidthVar = createVar('panel-width');
|
||||||
|
|
||||||
|
export const container = style({
|
||||||
|
vars: {
|
||||||
|
[panelWidthVar]: '0px',
|
||||||
|
},
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '8px 12px 8px 8px',
|
||||||
|
position: 'fixed',
|
||||||
|
right: '28px',
|
||||||
|
top: '80px',
|
||||||
|
transform: `translateX(calc(${panelWidthVar} * -1))`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const leftContent = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const input = style({
|
||||||
|
padding: '0 10px',
|
||||||
|
height: '32px',
|
||||||
|
gap: '8px',
|
||||||
|
color: cssVar('iconColor'),
|
||||||
|
background: cssVar('white10'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const count = style({
|
||||||
|
color: cssVar('textSecondaryColor'),
|
||||||
|
fontSize: cssVar('fontXs'),
|
||||||
|
userSelect: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const arrowButton = style({
|
||||||
|
padding: '4px',
|
||||||
|
fontSize: '24px',
|
||||||
|
width: '32px',
|
||||||
|
height: '32px',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: cssVar('borderColor'),
|
||||||
|
alignItems: 'baseline',
|
||||||
|
background: 'transparent',
|
||||||
|
selectors: {
|
||||||
|
'&.backward': {
|
||||||
|
marginLeft: '8px',
|
||||||
|
borderRadius: '4px 0 0 4px',
|
||||||
|
},
|
||||||
|
'&.forward': {
|
||||||
|
borderLeft: 'none',
|
||||||
|
borderRadius: '0 4px 4px 0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { Button, Input, Modal } from '@affine/component';
|
||||||
|
import { rightSidebarWidthAtom } from '@affine/core/atoms';
|
||||||
|
import {
|
||||||
|
ArrowDownSmallIcon,
|
||||||
|
ArrowUpSmallIcon,
|
||||||
|
SearchIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useDebouncedValue } from 'foxact/use-debounced-value';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { useCallback, useDeferredValue, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { RightSidebarService } from '../../right-sidebar';
|
||||||
|
import { FindInPageService } from '../services/find-in-page';
|
||||||
|
import * as styles from './find-in-page-modal.css';
|
||||||
|
export const FindInPageModal = () => {
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const debouncedValue = useDebouncedValue(value, 300);
|
||||||
|
const deferredValue = useDeferredValue(debouncedValue);
|
||||||
|
|
||||||
|
const findInPage = useService(FindInPageService).findInPage;
|
||||||
|
const visible = useLiveData(findInPage.visible$);
|
||||||
|
const result = useLiveData(findInPage.result$);
|
||||||
|
const isSearching = useLiveData(findInPage.isSearching$);
|
||||||
|
|
||||||
|
const rightSidebarWidth = useAtomValue(rightSidebarWidthAtom);
|
||||||
|
const rightSidebar = useService(RightSidebarService).rightSidebar;
|
||||||
|
const frontView = useLiveData(rightSidebar.front$);
|
||||||
|
const open = useLiveData(rightSidebar.isOpen$) && frontView !== undefined;
|
||||||
|
|
||||||
|
const handleSearch = useCallback(() => {
|
||||||
|
findInPage.findInPage(deferredValue);
|
||||||
|
}, [deferredValue, findInPage]);
|
||||||
|
|
||||||
|
const handleBackWard = useCallback(() => {
|
||||||
|
findInPage.backward();
|
||||||
|
}, [findInPage]);
|
||||||
|
|
||||||
|
const handleForward = useCallback(() => {
|
||||||
|
findInPage.forward();
|
||||||
|
}, [findInPage]);
|
||||||
|
|
||||||
|
const onChangeVisible = useCallback(
|
||||||
|
(visible: boolean) => {
|
||||||
|
if (!visible) {
|
||||||
|
findInPage.stopFindInPage('clearSelection');
|
||||||
|
}
|
||||||
|
findInPage.onChangeVisible(visible);
|
||||||
|
},
|
||||||
|
[findInPage]
|
||||||
|
);
|
||||||
|
const handleDone = useCallback(() => {
|
||||||
|
onChangeVisible(false);
|
||||||
|
}, [onChangeVisible]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// add keyboard event listener for arrow up and down
|
||||||
|
const keyArrowDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
handleForward();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const keyArrowUp = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
handleBackWard();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', keyArrowDown);
|
||||||
|
document.addEventListener('keydown', keyArrowUp);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', keyArrowDown);
|
||||||
|
document.removeEventListener('keydown', keyArrowUp);
|
||||||
|
};
|
||||||
|
}, [findInPage, handleBackWard, handleForward]);
|
||||||
|
|
||||||
|
const panelWidth = assignInlineVars({
|
||||||
|
[styles.panelWidthVar]: open ? `${rightSidebarWidth}px` : '0',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// auto search when value change
|
||||||
|
if (deferredValue) {
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
}, [deferredValue, handleSearch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// clear highlight when value is empty
|
||||||
|
if (value.length === 0) {
|
||||||
|
findInPage.stopFindInPage('keepSelection');
|
||||||
|
}
|
||||||
|
}, [value, findInPage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={visible}
|
||||||
|
onOpenChange={onChangeVisible}
|
||||||
|
overlayOptions={{
|
||||||
|
hidden: true,
|
||||||
|
}}
|
||||||
|
withoutCloseButton
|
||||||
|
width={398}
|
||||||
|
height={48}
|
||||||
|
minHeight={48}
|
||||||
|
contentOptions={{
|
||||||
|
className: styles.container,
|
||||||
|
style: panelWidth,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={styles.leftContent}>
|
||||||
|
<Input
|
||||||
|
onChange={setValue}
|
||||||
|
value={isSearching ? '' : value}
|
||||||
|
onEnter={handleSearch}
|
||||||
|
autoFocus
|
||||||
|
preFix={<SearchIcon fontSize={20} />}
|
||||||
|
endFix={
|
||||||
|
<div className={styles.count}>
|
||||||
|
{value.length > 0 && result && result.matches !== 0 ? (
|
||||||
|
<>
|
||||||
|
<span>{result?.activeMatchOrdinal || 0}</span>
|
||||||
|
<span>/</span>
|
||||||
|
<span>{result?.matches || 0}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span>No matches</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
width: 239,
|
||||||
|
}}
|
||||||
|
className={styles.input}
|
||||||
|
inputStyle={{
|
||||||
|
padding: '0',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={clsx(styles.arrowButton, 'backward')}
|
||||||
|
onClick={handleBackWard}
|
||||||
|
>
|
||||||
|
<ArrowUpSmallIcon />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={clsx(styles.arrowButton, 'forward')}
|
||||||
|
onClick={handleForward}
|
||||||
|
>
|
||||||
|
<ArrowDownSmallIcon />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button type="primary" onClick={handleDone}>
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { configureInfraModules, type Framework } from '@toeverything/infra';
|
|||||||
|
|
||||||
import { configureCloudModule } from './cloud';
|
import { configureCloudModule } from './cloud';
|
||||||
import { configureCollectionModule } from './collection';
|
import { configureCollectionModule } from './collection';
|
||||||
|
import { configureFindInPageModule } from './find-in-page';
|
||||||
import { configureNavigationModule } from './navigation';
|
import { configureNavigationModule } from './navigation';
|
||||||
import { configurePermissionsModule } from './permissions';
|
import { configurePermissionsModule } from './permissions';
|
||||||
import { configureWorkspacePropertiesModule } from './properties';
|
import { configureWorkspacePropertiesModule } from './properties';
|
||||||
@@ -26,6 +27,7 @@ export function configureCommonModules(framework: Framework) {
|
|||||||
configurePermissionsModule(framework);
|
configurePermissionsModule(framework);
|
||||||
configureShareDocsModule(framework);
|
configureShareDocsModule(framework);
|
||||||
configureTelemetryModule(framework);
|
configureTelemetryModule(framework);
|
||||||
|
configureFindInPageModule(framework);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function configureImpls(framework: Framework) {
|
export function configureImpls(framework: Framework) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ResizePanel } from '@affine/component/resize-panel';
|
import { ResizePanel } from '@affine/component/resize-panel';
|
||||||
|
import { rightSidebarWidthAtom } from '@affine/core/atoms';
|
||||||
import { appSettingAtom, useLiveData, useService } from '@toeverything/infra';
|
import { appSettingAtom, useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtom, useAtomValue } from 'jotai';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { RightSidebarService } from '../services/right-sidebar';
|
import { RightSidebarService } from '../services/right-sidebar';
|
||||||
@@ -12,7 +13,8 @@ const MAX_SIDEBAR_WIDTH = 800;
|
|||||||
|
|
||||||
export const RightSidebarContainer = () => {
|
export const RightSidebarContainer = () => {
|
||||||
const { clientBorder } = useAtomValue(appSettingAtom);
|
const { clientBorder } = useAtomValue(appSettingAtom);
|
||||||
const [width, setWidth] = useState(300);
|
|
||||||
|
const [width, setWidth] = useAtom(rightSidebarWidthAtom);
|
||||||
const [resizing, setResizing] = useState(false);
|
const [resizing, setResizing] = useState(false);
|
||||||
const rightSidebar = useService(RightSidebarService).rightSidebar;
|
const rightSidebar = useService(RightSidebarService).rightSidebar;
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
}, [doc, globalContext, isActiveView, mode]);
|
}, [doc, globalContext, isActiveView, mode]);
|
||||||
|
|
||||||
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
||||||
|
|
||||||
useRegisterBlocksuiteEditorCommands();
|
useRegisterBlocksuiteEditorCommands();
|
||||||
const title = useLiveData(doc.title$);
|
const title = useLiveData(doc.title$);
|
||||||
usePageDocumentTitle(title);
|
usePageDocumentTitle(title);
|
||||||
|
|||||||
@@ -93,6 +93,11 @@ const AiLoginRequiredModal = lazy(() =>
|
|||||||
default: module.AiLoginRequiredModal,
|
default: module.AiLoginRequiredModal,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
const FindInPageModal = lazy(() =>
|
||||||
|
import('../modules/find-in-page/view/find-in-page-modal').then(module => ({
|
||||||
|
default: module.FindInPageModal,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
export const Setting = () => {
|
export const Setting = () => {
|
||||||
const [{ open, workspaceMetadata, activeTab }, setOpenSettingModalAtom] =
|
const [{ open, workspaceMetadata, activeTab }, setOpenSettingModalAtom] =
|
||||||
@@ -213,6 +218,7 @@ export function CurrentWorkspaceModals() {
|
|||||||
<CloudQuotaModal />
|
<CloudQuotaModal />
|
||||||
)}
|
)}
|
||||||
<AiLoginRequiredModal />
|
<AiLoginRequiredModal />
|
||||||
|
{environment.isDesktop && <FindInPageModal />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
import type {
|
import type {
|
||||||
affine as exposedAffineGlobal,
|
affine as exposedAffineGlobal,
|
||||||
appInfo as exposedAppInfo,
|
appInfo as exposedAppInfo,
|
||||||
|
cmdFind as exposedCmdFind,
|
||||||
} from '@affine/electron/preload/electron-api';
|
} from '@affine/electron/preload/electron-api';
|
||||||
|
|
||||||
type MainHandlers = typeof mainHandlers;
|
type MainHandlers = typeof mainHandlers;
|
||||||
@@ -39,5 +40,8 @@ export const events = (globalThis as any).events as ClientEvents | null;
|
|||||||
export const affine = (globalThis as any).affine as
|
export const affine = (globalThis as any).affine as
|
||||||
| typeof exposedAffineGlobal
|
| typeof exposedAffineGlobal
|
||||||
| null;
|
| null;
|
||||||
|
export const cmdFind = (globalThis as any).cmdFind as
|
||||||
|
| typeof exposedCmdFind
|
||||||
|
| null;
|
||||||
|
|
||||||
export type { UpdateMeta } from '@affine/electron/main/updater/event';
|
export type { UpdateMeta } from '@affine/electron/main/updater/event';
|
||||||
|
|||||||
17
packages/frontend/electron/src/main/find-in-page/handlers.ts
Normal file
17
packages/frontend/electron/src/main/find-in-page/handlers.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export const findInPageHandlers = {
|
||||||
|
findInPage: async (
|
||||||
|
event: Electron.IpcMainInvokeEvent,
|
||||||
|
text: string,
|
||||||
|
options?: Electron.FindInPageOptions
|
||||||
|
) => {
|
||||||
|
const webContents = event.sender;
|
||||||
|
return webContents.findInPage(text, options);
|
||||||
|
},
|
||||||
|
stopFindInPage: async (
|
||||||
|
event: Electron.IpcMainInvokeEvent,
|
||||||
|
action: 'clearSelection' | 'keepSelection' | 'activateSelection'
|
||||||
|
) => {
|
||||||
|
const webContents = event.sender;
|
||||||
|
return webContents.stopFindInPage(action);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './handlers';
|
||||||
@@ -3,6 +3,7 @@ import { ipcMain } from 'electron';
|
|||||||
import { clipboardHandlers } from './clipboard';
|
import { clipboardHandlers } from './clipboard';
|
||||||
import { configStorageHandlers } from './config-storage';
|
import { configStorageHandlers } from './config-storage';
|
||||||
import { exportHandlers } from './export';
|
import { exportHandlers } from './export';
|
||||||
|
import { findInPageHandlers } from './find-in-page';
|
||||||
import { getLogFilePath, logger, revealLogFile } from './logger';
|
import { getLogFilePath, logger, revealLogFile } from './logger';
|
||||||
import { uiHandlers } from './ui/handlers';
|
import { uiHandlers } from './ui/handlers';
|
||||||
import { updaterHandlers } from './updater';
|
import { updaterHandlers } from './updater';
|
||||||
@@ -24,6 +25,7 @@ export const allHandlers = {
|
|||||||
export: exportHandlers,
|
export: exportHandlers,
|
||||||
updater: updaterHandlers,
|
updater: updaterHandlers,
|
||||||
configStorage: configStorageHandlers,
|
configStorage: configStorageHandlers,
|
||||||
|
findInPage: findInPageHandlers,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerHandlers = () => {
|
export const registerHandlers = () => {
|
||||||
|
|||||||
@@ -169,6 +169,16 @@ async function createWindow(additionalArguments: string[]) {
|
|||||||
uiSubjects.onFullScreen$.next(false);
|
uiSubjects.onFullScreen$.next(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
browserWindow.webContents.on('found-in-page', (_event, result) => {
|
||||||
|
const { requestId, activeMatchOrdinal, matches, finalUpdate } = result;
|
||||||
|
browserWindow.webContents.send('found-in-page-result', {
|
||||||
|
requestId,
|
||||||
|
activeMatchOrdinal,
|
||||||
|
matches,
|
||||||
|
finalUpdate,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL for main window.
|
* URL for main window.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { contextBridge } from 'electron';
|
import { contextBridge } from 'electron';
|
||||||
|
|
||||||
import { affine, appInfo, getElectronAPIs } from './electron-api';
|
import { affine, appInfo, cmdFind, getElectronAPIs } from './electron-api';
|
||||||
|
|
||||||
const { apis, events } = getElectronAPIs();
|
const { apis, events } = getElectronAPIs();
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ contextBridge.exposeInMainWorld('events', events);
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld('affine', affine);
|
contextBridge.exposeInMainWorld('affine', affine);
|
||||||
|
contextBridge.exposeInMainWorld('cmdFind', cmdFind);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to expose affine APIs to window object!', error);
|
console.error('Failed to expose affine APIs to window object!', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,20 @@ export const affine = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cmdFind = {
|
||||||
|
findInPage: (text: string, options?: Electron.FindInPageOptions) =>
|
||||||
|
ipcRenderer.invoke('findInPage:findInPage', text, options),
|
||||||
|
stopFindInPage: (
|
||||||
|
action: 'clearSelection' | 'keepSelection' | 'activateSelection'
|
||||||
|
) => ipcRenderer.invoke('findInPage:stopFindInPage', action),
|
||||||
|
onFindInPageResult: (callBack: (data: any) => void) =>
|
||||||
|
ipcRenderer.on('found-in-page-result', (_event, data) => callBack(data)),
|
||||||
|
offFindInPageResult: (callBack: (data: any) => void) =>
|
||||||
|
ipcRenderer.removeListener('found-in-page-result', (_event, data) =>
|
||||||
|
callBack(data)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
export function getElectronAPIs() {
|
export function getElectronAPIs() {
|
||||||
const mainAPIs = getMainAPIs();
|
const mainAPIs = getMainAPIs();
|
||||||
const helperAPIs = getHelperAPIs();
|
const helperAPIs = getHelperAPIs();
|
||||||
|
|||||||
Reference in New Issue
Block a user