mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-27 14:43:32 +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 { atom, createStore } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
@ -8,6 +9,8 @@ import { RemWorkspace, RemWorkspaceFlavour } from '../shared';
|
||||
// workspace necessary atoms
|
||||
export const currentWorkspaceIdAtom = 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
|
||||
// from local to remote or vice versa
|
||||
export const workspaceLockAtom = atom(false);
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
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 { SidebarSwitch } from '../../affine/sidebar-switch';
|
||||
@ -51,50 +58,57 @@ export type HeaderProps = PropsWithChildren<{
|
||||
rightItems?: HeaderRightItemNames[];
|
||||
}>;
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({
|
||||
rightItems = ['syncUser', 'themeModeSwitch'],
|
||||
children,
|
||||
}) => {
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
useEffect(() => {
|
||||
setShowWarning(shouldShowWarning());
|
||||
}, []);
|
||||
const [open] = useSidebarStatus();
|
||||
const { t } = useTranslation();
|
||||
export const Header = forwardRef<
|
||||
HTMLDivElement,
|
||||
HeaderProps & HTMLAttributes<HTMLDivElement>
|
||||
>(
|
||||
(
|
||||
{ rightItems = ['syncUser', 'themeModeSwitch'], children, ...props },
|
||||
ref
|
||||
) => {
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
useEffect(() => {
|
||||
setShowWarning(shouldShowWarning());
|
||||
}, []);
|
||||
const [open] = useSidebarStatus();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StyledHeaderContainer hasWarning={showWarning}>
|
||||
<BrowserWarning
|
||||
show={showWarning}
|
||||
onClose={() => {
|
||||
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"
|
||||
return (
|
||||
<StyledHeaderContainer ref={ref} hasWarning={showWarning} {...props}>
|
||||
<BrowserWarning
|
||||
show={showWarning}
|
||||
onClose={() => {
|
||||
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"
|
||||
/>
|
||||
|
||||
{children}
|
||||
<StyledHeaderRightSide>
|
||||
{useMemo(
|
||||
() =>
|
||||
rightItems.map(itemName => {
|
||||
const Item = HeaderRightItems[itemName];
|
||||
return <Item key={itemName} />;
|
||||
}),
|
||||
[rightItems]
|
||||
)}
|
||||
</StyledHeaderRightSide>
|
||||
</StyledHeader>
|
||||
</StyledHeaderContainer>
|
||||
);
|
||||
};
|
||||
{children}
|
||||
<StyledHeaderRightSide>
|
||||
{useMemo(
|
||||
() =>
|
||||
rightItems.map(itemName => {
|
||||
const Item = HeaderRightItems[itemName];
|
||||
return <Item key={itemName} />;
|
||||
}),
|
||||
[rightItems]
|
||||
)}
|
||||
</StyledHeaderRightSide>
|
||||
</StyledHeader>
|
||||
</StyledHeaderContainer>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Header.displayName = '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 { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import React from 'react';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
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 { usePageMeta } from '../../../hooks/use-page-meta';
|
||||
import { useElementResizeEffect } from '../../../hooks/use-workspaces';
|
||||
import { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { PageNotFoundError } from '../../affine/affine-error-eoundary';
|
||||
import { QuickSearchButton } from '../../pure/quick-search-button';
|
||||
@ -30,32 +31,45 @@ export type BlockSuiteEditorHeaderProps = React.PropsWithChildren<{
|
||||
isPreview?: boolean;
|
||||
}>;
|
||||
|
||||
export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
||||
blockSuiteWorkspace,
|
||||
pageId,
|
||||
children,
|
||||
isPublic,
|
||||
isPreview,
|
||||
}) => {
|
||||
const page = blockSuiteWorkspace.getPage(pageId);
|
||||
// fixme(himself65): remove this atom and move it to props
|
||||
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
|
||||
if (!page) {
|
||||
throw new PageNotFoundError(blockSuiteWorkspace, pageId);
|
||||
}
|
||||
const pageMeta = usePageMeta(blockSuiteWorkspace).find(
|
||||
meta => meta.id === pageId
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const title = pageMeta.title;
|
||||
const { trash: isTrash } = pageMeta;
|
||||
const [openTips, setOpenTips] = useOpenTips();
|
||||
const isMac = () => {
|
||||
const env = getEnvironment();
|
||||
return env.isBrowser && env.isMacOs;
|
||||
};
|
||||
const tipsContent = () => {
|
||||
return (
|
||||
export const BlockSuiteEditorHeader = forwardRef<
|
||||
HTMLDivElement,
|
||||
BlockSuiteEditorHeaderProps & HTMLAttributes<HTMLDivElement>
|
||||
>(
|
||||
(
|
||||
{ blockSuiteWorkspace, pageId, children, isPublic, isPreview, ...props },
|
||||
ref
|
||||
) => {
|
||||
const page = blockSuiteWorkspace.getPage(pageId);
|
||||
// fixme(himself65): remove this atom and move it to props
|
||||
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
|
||||
if (!page) {
|
||||
throw new PageNotFoundError(blockSuiteWorkspace, pageId);
|
||||
}
|
||||
const pageMeta = usePageMeta(blockSuiteWorkspace).find(
|
||||
meta => meta.id === pageId
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const title = pageMeta.title;
|
||||
const { trash: isTrash } = pageMeta;
|
||||
const [openTips, setOpenTips] = useOpenTips();
|
||||
const isMac = () => {
|
||||
const env = getEnvironment();
|
||||
return env.isBrowser && env.isMacOs;
|
||||
};
|
||||
|
||||
const popperRef: PopperProps['popperRef'] = useRef(null);
|
||||
|
||||
useElementResizeEffect(
|
||||
useAtomValue(currentEditorAtom),
|
||||
useCallback(() => {
|
||||
if (!openTips || !popperRef.current) {
|
||||
return;
|
||||
}
|
||||
popperRef.current.update();
|
||||
}, [openTips])
|
||||
);
|
||||
|
||||
const TipsContent = (
|
||||
<StyledQuickSearchTipContent>
|
||||
<div>
|
||||
Click button
|
||||
@ -81,50 +95,56 @@ export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
||||
</StyledQuickSearchTipButton>
|
||||
</StyledQuickSearchTipContent>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Header
|
||||
rightItems={
|
||||
// fixme(himself65): other right items not supported in public mode
|
||||
isPublic || isPreview
|
||||
? ['themeModeSwitch']
|
||||
: isTrash
|
||||
? ['trashButtonGroup']
|
||||
: ['syncUser', 'themeModeSwitch', 'editorOptionMenu']
|
||||
}
|
||||
>
|
||||
{children}
|
||||
{!isPublic && (
|
||||
<StyledTitleContainer data-tauri-drag-region>
|
||||
<StyledTitleWrapper>
|
||||
<StyledSwitchWrapper>
|
||||
<EditorModeSwitch
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
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);
|
||||
|
||||
return (
|
||||
<Header
|
||||
ref={ref}
|
||||
rightItems={
|
||||
// fixme(himself65): other right items not supported in public mode
|
||||
isPublic || isPreview
|
||||
? ['themeModeSwitch']
|
||||
: isTrash
|
||||
? ['trashButtonGroup']
|
||||
: ['syncUser', 'themeModeSwitch', 'editorOptionMenu']
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{!isPublic && (
|
||||
<StyledTitleContainer data-tauri-drag-region>
|
||||
<StyledTitleWrapper>
|
||||
<StyledSwitchWrapper>
|
||||
<EditorModeSwitch
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
pageId={pageId}
|
||||
style={{
|
||||
marginRight: '12px',
|
||||
}}
|
||||
/>
|
||||
</StyledSearchArrowWrapper>
|
||||
</QuickSearchTips>
|
||||
</StyledTitleWrapper>
|
||||
</StyledTitleContainer>
|
||||
)}
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
</StyledSwitchWrapper>
|
||||
<StyledTitle>{title || 'Untitled'}</StyledTitle>
|
||||
<QuickSearchTips
|
||||
data-testid="quick-search-tips"
|
||||
content={TipsContent}
|
||||
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 { assertExists, Page } from '@blocksuite/store';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import dynamic from 'next/dynamic';
|
||||
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 { usePageMeta } from '../hooks/use-page-meta';
|
||||
import { BlockSuiteWorkspace } from '../shared';
|
||||
@ -45,6 +47,7 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
|
||||
const meta = usePageMeta(blockSuiteWorkspace).find(
|
||||
meta => meta.id === pageId
|
||||
);
|
||||
const setEditor = useSetAtom(currentEditorAtom);
|
||||
assertExists(meta);
|
||||
return (
|
||||
<>
|
||||
@ -68,7 +71,13 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
|
||||
// fixme: remove mode from meta
|
||||
mode={isPublic ? 'page' : meta.mode ?? 'page'}
|
||||
page={page}
|
||||
onInit={onInit}
|
||||
onInit={useCallback(
|
||||
(page: Page, editor: Readonly<EditorContainer>) => {
|
||||
setEditor(editor);
|
||||
onInit(page, editor);
|
||||
},
|
||||
[onInit, setEditor]
|
||||
)}
|
||||
onLoad={onLoad}
|
||||
/>
|
||||
</>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { jotaiWorkspacesAtom, workspacesAtom } from '../atoms';
|
||||
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', {
|
||||
name: 'this is a new page to favorite',
|
||||
});
|
||||
expect(cell).not.toBeUndefined();
|
||||
await expect(cell).toBeVisible();
|
||||
await cell.click();
|
||||
await clickPageMoreActions(page);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user