mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-26 16:28:55 +03:00
fix: quick search tips follow when resize (#1580)
Co-authored-by: himself65 <himself65@outlook.com>
This commit is contained in:
parent
5ac6632276
commit
bc32b07bf0
@ -1,3 +1,4 @@
|
|||||||
|
import { EditorContainer } from '@blocksuite/editor';
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
import { atom, createStore } from 'jotai';
|
import { atom, createStore } from 'jotai';
|
||||||
import { atomWithStorage } from 'jotai/utils';
|
import { atomWithStorage } from 'jotai/utils';
|
||||||
@ -8,6 +9,8 @@ import { RemWorkspace, RemWorkspaceFlavour } from '../shared';
|
|||||||
// workspace necessary atoms
|
// workspace necessary atoms
|
||||||
export const currentWorkspaceIdAtom = atom<string | null>(null);
|
export const currentWorkspaceIdAtom = atom<string | null>(null);
|
||||||
export const currentPageIdAtom = atom<string | null>(null);
|
export const currentPageIdAtom = atom<string | null>(null);
|
||||||
|
export const currentEditorAtom = atom<Readonly<EditorContainer> | null>(null);
|
||||||
|
|
||||||
// If the workspace is locked, it means that the user maybe updating the workspace
|
// If the workspace is locked, it means that the user maybe updating the workspace
|
||||||
// from local to remote or vice versa
|
// from local to remote or vice versa
|
||||||
export const workspaceLockAtom = atom(false);
|
export const workspaceLockAtom = atom(false);
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { CloseIcon } from '@blocksuite/icons';
|
import { CloseIcon } from '@blocksuite/icons';
|
||||||
import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react';
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
HTMLAttributes,
|
||||||
|
PropsWithChildren,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status';
|
import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status';
|
||||||
import { SidebarSwitch } from '../../affine/sidebar-switch';
|
import { SidebarSwitch } from '../../affine/sidebar-switch';
|
||||||
@ -51,10 +58,14 @@ export type HeaderProps = PropsWithChildren<{
|
|||||||
rightItems?: HeaderRightItemNames[];
|
rightItems?: HeaderRightItemNames[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const Header: React.FC<HeaderProps> = ({
|
export const Header = forwardRef<
|
||||||
rightItems = ['syncUser', 'themeModeSwitch'],
|
HTMLDivElement,
|
||||||
children,
|
HeaderProps & HTMLAttributes<HTMLDivElement>
|
||||||
}) => {
|
>(
|
||||||
|
(
|
||||||
|
{ rightItems = ['syncUser', 'themeModeSwitch'], children, ...props },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const [showWarning, setShowWarning] = useState(false);
|
const [showWarning, setShowWarning] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowWarning(shouldShowWarning());
|
setShowWarning(shouldShowWarning());
|
||||||
@ -63,7 +74,7 @@ export const Header: React.FC<HeaderProps> = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledHeaderContainer hasWarning={showWarning}>
|
<StyledHeaderContainer ref={ref} hasWarning={showWarning} {...props}>
|
||||||
<BrowserWarning
|
<BrowserWarning
|
||||||
show={showWarning}
|
show={showWarning}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@ -95,6 +106,9 @@ export const Header: React.FC<HeaderProps> = ({
|
|||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
</StyledHeaderContainer>
|
</StyledHeaderContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Header.displayName = 'Header';
|
||||||
|
|
||||||
export default Header;
|
export default Header;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { QuickSearchTips } from '@affine/component';
|
import { PopperProps, QuickSearchTips } from '@affine/component';
|
||||||
import { getEnvironment } from '@affine/env';
|
import { getEnvironment } from '@affine/env';
|
||||||
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import React from 'react';
|
import { forwardRef, HTMLAttributes, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
import { openQuickSearchModalAtom } from '../../../atoms';
|
import { currentEditorAtom, openQuickSearchModalAtom } from '../../../atoms';
|
||||||
import { useOpenTips } from '../../../hooks/affine/use-is-first-load';
|
import { useOpenTips } from '../../../hooks/affine/use-is-first-load';
|
||||||
import { usePageMeta } from '../../../hooks/use-page-meta';
|
import { usePageMeta } from '../../../hooks/use-page-meta';
|
||||||
|
import { useElementResizeEffect } from '../../../hooks/use-workspaces';
|
||||||
import { BlockSuiteWorkspace } from '../../../shared';
|
import { BlockSuiteWorkspace } from '../../../shared';
|
||||||
import { PageNotFoundError } from '../../affine/affine-error-eoundary';
|
import { PageNotFoundError } from '../../affine/affine-error-eoundary';
|
||||||
import { QuickSearchButton } from '../../pure/quick-search-button';
|
import { QuickSearchButton } from '../../pure/quick-search-button';
|
||||||
@ -30,13 +31,14 @@ export type BlockSuiteEditorHeaderProps = React.PropsWithChildren<{
|
|||||||
isPreview?: boolean;
|
isPreview?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
export const BlockSuiteEditorHeader = forwardRef<
|
||||||
blockSuiteWorkspace,
|
HTMLDivElement,
|
||||||
pageId,
|
BlockSuiteEditorHeaderProps & HTMLAttributes<HTMLDivElement>
|
||||||
children,
|
>(
|
||||||
isPublic,
|
(
|
||||||
isPreview,
|
{ blockSuiteWorkspace, pageId, children, isPublic, isPreview, ...props },
|
||||||
}) => {
|
ref
|
||||||
|
) => {
|
||||||
const page = blockSuiteWorkspace.getPage(pageId);
|
const page = blockSuiteWorkspace.getPage(pageId);
|
||||||
// fixme(himself65): remove this atom and move it to props
|
// fixme(himself65): remove this atom and move it to props
|
||||||
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
|
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
|
||||||
@ -54,8 +56,20 @@ export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
|||||||
const env = getEnvironment();
|
const env = getEnvironment();
|
||||||
return env.isBrowser && env.isMacOs;
|
return env.isBrowser && env.isMacOs;
|
||||||
};
|
};
|
||||||
const tipsContent = () => {
|
|
||||||
return (
|
const popperRef: PopperProps['popperRef'] = useRef(null);
|
||||||
|
|
||||||
|
useElementResizeEffect(
|
||||||
|
useAtomValue(currentEditorAtom),
|
||||||
|
useCallback(() => {
|
||||||
|
if (!openTips || !popperRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
popperRef.current.update();
|
||||||
|
}, [openTips])
|
||||||
|
);
|
||||||
|
|
||||||
|
const TipsContent = (
|
||||||
<StyledQuickSearchTipContent>
|
<StyledQuickSearchTipContent>
|
||||||
<div>
|
<div>
|
||||||
Click button
|
Click button
|
||||||
@ -81,9 +95,10 @@ export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
|||||||
</StyledQuickSearchTipButton>
|
</StyledQuickSearchTipButton>
|
||||||
</StyledQuickSearchTipContent>
|
</StyledQuickSearchTipContent>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Header
|
<Header
|
||||||
|
ref={ref}
|
||||||
rightItems={
|
rightItems={
|
||||||
// fixme(himself65): other right items not supported in public mode
|
// fixme(himself65): other right items not supported in public mode
|
||||||
isPublic || isPreview
|
isPublic || isPreview
|
||||||
@ -92,6 +107,7 @@ export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
|||||||
? ['trashButtonGroup']
|
? ['trashButtonGroup']
|
||||||
: ['syncUser', 'themeModeSwitch', 'editorOptionMenu']
|
: ['syncUser', 'themeModeSwitch', 'editorOptionMenu']
|
||||||
}
|
}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{!isPublic && (
|
{!isPublic && (
|
||||||
@ -109,8 +125,9 @@ export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
|||||||
<StyledTitle>{title || 'Untitled'}</StyledTitle>
|
<StyledTitle>{title || 'Untitled'}</StyledTitle>
|
||||||
<QuickSearchTips
|
<QuickSearchTips
|
||||||
data-testid="quick-search-tips"
|
data-testid="quick-search-tips"
|
||||||
content={tipsContent()}
|
content={TipsContent}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
|
popperRef={popperRef}
|
||||||
open={openTips}
|
open={openTips}
|
||||||
offset={[0, -5]}
|
offset={[0, -5]}
|
||||||
>
|
>
|
||||||
@ -127,4 +144,7 @@ export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
|||||||
)}
|
)}
|
||||||
</Header>
|
</Header>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BlockSuiteEditorHeader.displayName = 'BlockSuiteEditorHeader';
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import type { EditorContainer } from '@blocksuite/editor';
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
import { assertExists, Page } from '@blocksuite/store';
|
import { assertExists, Page } from '@blocksuite/store';
|
||||||
|
import { useSetAtom } from 'jotai';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { currentEditorAtom } from '../atoms';
|
||||||
import { useBlockSuiteWorkspacePageTitle } from '../hooks/use-blocksuite-workspace-page-title';
|
import { useBlockSuiteWorkspacePageTitle } from '../hooks/use-blocksuite-workspace-page-title';
|
||||||
import { usePageMeta } from '../hooks/use-page-meta';
|
import { usePageMeta } from '../hooks/use-page-meta';
|
||||||
import { BlockSuiteWorkspace } from '../shared';
|
import { BlockSuiteWorkspace } from '../shared';
|
||||||
@ -45,6 +47,7 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
|
|||||||
const meta = usePageMeta(blockSuiteWorkspace).find(
|
const meta = usePageMeta(blockSuiteWorkspace).find(
|
||||||
meta => meta.id === pageId
|
meta => meta.id === pageId
|
||||||
);
|
);
|
||||||
|
const setEditor = useSetAtom(currentEditorAtom);
|
||||||
assertExists(meta);
|
assertExists(meta);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -68,7 +71,13 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
|
|||||||
// fixme: remove mode from meta
|
// fixme: remove mode from meta
|
||||||
mode={isPublic ? 'page' : meta.mode ?? 'page'}
|
mode={isPublic ? 'page' : meta.mode ?? 'page'}
|
||||||
page={page}
|
page={page}
|
||||||
onInit={onInit}
|
onInit={useCallback(
|
||||||
|
(page: Page, editor: Readonly<EditorContainer>) => {
|
||||||
|
setEditor(editor);
|
||||||
|
onInit(page, editor);
|
||||||
|
},
|
||||||
|
[onInit, setEditor]
|
||||||
|
)}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { nanoid } from '@blocksuite/store';
|
import { nanoid } from '@blocksuite/store';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import { jotaiWorkspacesAtom, workspacesAtom } from '../atoms';
|
import { jotaiWorkspacesAtom, workspacesAtom } from '../atoms';
|
||||||
import { WorkspacePlugins } from '../plugins';
|
import { WorkspacePlugins } from '../plugins';
|
||||||
@ -71,3 +71,25 @@ export function useWorkspacesHelper() {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useElementResizeEffect = (
|
||||||
|
element: Element | null,
|
||||||
|
fn: () => void | (() => () => void),
|
||||||
|
// TODO: add throttle
|
||||||
|
throttle = 0
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let dispose: void | (() => void);
|
||||||
|
const resizeObserver = new ResizeObserver(entries => {
|
||||||
|
dispose = fn();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(element);
|
||||||
|
return () => {
|
||||||
|
dispose?.();
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [element, fn]);
|
||||||
|
};
|
||||||
|
@ -19,7 +19,7 @@ test.describe('Local first favorite items ui', () => {
|
|||||||
const cell = page.getByRole('cell', {
|
const cell = page.getByRole('cell', {
|
||||||
name: 'this is a new page to favorite',
|
name: 'this is a new page to favorite',
|
||||||
});
|
});
|
||||||
expect(cell).not.toBeUndefined();
|
await expect(cell).toBeVisible();
|
||||||
await cell.click();
|
await cell.click();
|
||||||
await clickPageMoreActions(page);
|
await clickPageMoreActions(page);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user