mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-30 04:12:15 +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,50 +58,57 @@ 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>
|
||||||
}) => {
|
>(
|
||||||
const [showWarning, setShowWarning] = useState(false);
|
(
|
||||||
useEffect(() => {
|
{ rightItems = ['syncUser', 'themeModeSwitch'], children, ...props },
|
||||||
setShowWarning(shouldShowWarning());
|
ref
|
||||||
}, []);
|
) => {
|
||||||
const [open] = useSidebarStatus();
|
const [showWarning, setShowWarning] = useState(false);
|
||||||
const { t } = useTranslation();
|
useEffect(() => {
|
||||||
|
setShowWarning(shouldShowWarning());
|
||||||
|
}, []);
|
||||||
|
const [open] = useSidebarStatus();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledHeaderContainer hasWarning={showWarning}>
|
<StyledHeaderContainer ref={ref} hasWarning={showWarning} {...props}>
|
||||||
<BrowserWarning
|
<BrowserWarning
|
||||||
show={showWarning}
|
show={showWarning}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowWarning(false);
|
setShowWarning(false);
|
||||||
}}
|
}}
|
||||||
/>
|
|
||||||
<StyledHeader
|
|
||||||
hasWarning={showWarning}
|
|
||||||
data-testid="editor-header-items"
|
|
||||||
data-tauri-drag-region
|
|
||||||
>
|
|
||||||
<SidebarSwitch
|
|
||||||
visible={!open}
|
|
||||||
tooltipContent={t('Expand sidebar')}
|
|
||||||
testid="sliderBar-arrowButton-expand"
|
|
||||||
/>
|
/>
|
||||||
|
<StyledHeader
|
||||||
|
hasWarning={showWarning}
|
||||||
|
data-testid="editor-header-items"
|
||||||
|
data-tauri-drag-region
|
||||||
|
>
|
||||||
|
<SidebarSwitch
|
||||||
|
visible={!open}
|
||||||
|
tooltipContent={t('Expand sidebar')}
|
||||||
|
testid="sliderBar-arrowButton-expand"
|
||||||
|
/>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
<StyledHeaderRightSide>
|
<StyledHeaderRightSide>
|
||||||
{useMemo(
|
{useMemo(
|
||||||
() =>
|
() =>
|
||||||
rightItems.map(itemName => {
|
rightItems.map(itemName => {
|
||||||
const Item = HeaderRightItems[itemName];
|
const Item = HeaderRightItems[itemName];
|
||||||
return <Item key={itemName} />;
|
return <Item key={itemName} />;
|
||||||
}),
|
}),
|
||||||
[rightItems]
|
[rightItems]
|
||||||
)}
|
)}
|
||||||
</StyledHeaderRightSide>
|
</StyledHeaderRightSide>
|
||||||
</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,32 +31,45 @@ 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);
|
) => {
|
||||||
// fixme(himself65): remove this atom and move it to props
|
const page = blockSuiteWorkspace.getPage(pageId);
|
||||||
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
|
// fixme(himself65): remove this atom and move it to props
|
||||||
if (!page) {
|
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
|
||||||
throw new PageNotFoundError(blockSuiteWorkspace, pageId);
|
if (!page) {
|
||||||
}
|
throw new PageNotFoundError(blockSuiteWorkspace, pageId);
|
||||||
const pageMeta = usePageMeta(blockSuiteWorkspace).find(
|
}
|
||||||
meta => meta.id === pageId
|
const pageMeta = usePageMeta(blockSuiteWorkspace).find(
|
||||||
);
|
meta => meta.id === pageId
|
||||||
assertExists(pageMeta);
|
);
|
||||||
const title = pageMeta.title;
|
assertExists(pageMeta);
|
||||||
const { trash: isTrash } = pageMeta;
|
const title = pageMeta.title;
|
||||||
const [openTips, setOpenTips] = useOpenTips();
|
const { trash: isTrash } = pageMeta;
|
||||||
const isMac = () => {
|
const [openTips, setOpenTips] = useOpenTips();
|
||||||
const env = getEnvironment();
|
const isMac = () => {
|
||||||
return env.isBrowser && env.isMacOs;
|
const env = getEnvironment();
|
||||||
};
|
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,50 +95,56 @@ export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
|||||||
</StyledQuickSearchTipButton>
|
</StyledQuickSearchTipButton>
|
||||||
</StyledQuickSearchTipContent>
|
</StyledQuickSearchTipContent>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Header
|
<Header
|
||||||
rightItems={
|
ref={ref}
|
||||||
// fixme(himself65): other right items not supported in public mode
|
rightItems={
|
||||||
isPublic || isPreview
|
// fixme(himself65): other right items not supported in public mode
|
||||||
? ['themeModeSwitch']
|
isPublic || isPreview
|
||||||
: isTrash
|
? ['themeModeSwitch']
|
||||||
? ['trashButtonGroup']
|
: isTrash
|
||||||
: ['syncUser', 'themeModeSwitch', 'editorOptionMenu']
|
? ['trashButtonGroup']
|
||||||
}
|
: ['syncUser', 'themeModeSwitch', 'editorOptionMenu']
|
||||||
>
|
}
|
||||||
{children}
|
{...props}
|
||||||
{!isPublic && (
|
>
|
||||||
<StyledTitleContainer data-tauri-drag-region>
|
{children}
|
||||||
<StyledTitleWrapper>
|
{!isPublic && (
|
||||||
<StyledSwitchWrapper>
|
<StyledTitleContainer data-tauri-drag-region>
|
||||||
<EditorModeSwitch
|
<StyledTitleWrapper>
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
<StyledSwitchWrapper>
|
||||||
pageId={pageId}
|
<EditorModeSwitch
|
||||||
style={{
|
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||||
marginRight: '12px',
|
pageId={pageId}
|
||||||
}}
|
style={{
|
||||||
/>
|
marginRight: '12px',
|
||||||
</StyledSwitchWrapper>
|
|
||||||
<StyledTitle>{title || 'Untitled'}</StyledTitle>
|
|
||||||
<QuickSearchTips
|
|
||||||
data-testid="quick-search-tips"
|
|
||||||
content={tipsContent()}
|
|
||||||
placement="bottom"
|
|
||||||
open={openTips}
|
|
||||||
offset={[0, -5]}
|
|
||||||
>
|
|
||||||
<StyledSearchArrowWrapper>
|
|
||||||
<QuickSearchButton
|
|
||||||
onClick={() => {
|
|
||||||
setOpenQuickSearch(true);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</StyledSearchArrowWrapper>
|
</StyledSwitchWrapper>
|
||||||
</QuickSearchTips>
|
<StyledTitle>{title || 'Untitled'}</StyledTitle>
|
||||||
</StyledTitleWrapper>
|
<QuickSearchTips
|
||||||
</StyledTitleContainer>
|
data-testid="quick-search-tips"
|
||||||
)}
|
content={TipsContent}
|
||||||
</Header>
|
placement="bottom"
|
||||||
);
|
popperRef={popperRef}
|
||||||
};
|
open={openTips}
|
||||||
|
offset={[0, -5]}
|
||||||
|
>
|
||||||
|
<StyledSearchArrowWrapper>
|
||||||
|
<QuickSearchButton
|
||||||
|
onClick={() => {
|
||||||
|
setOpenQuickSearch(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledSearchArrowWrapper>
|
||||||
|
</QuickSearchTips>
|
||||||
|
</StyledTitleWrapper>
|
||||||
|
</StyledTitleContainer>
|
||||||
|
)}
|
||||||
|
</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