mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
feat: add change log (#1734)
Co-authored-by: Himself65 <himself65@outlook.com>
This commit is contained in:
@@ -1,5 +1,28 @@
|
|||||||
import { atom } from 'jotai';
|
|
||||||
import { atomWithStorage } from 'jotai/utils';
|
import { atomWithStorage } from 'jotai/utils';
|
||||||
|
|
||||||
export const isFirstLoadAtom = atomWithStorage<boolean>('isFirstLoad', true);
|
export type Visibility = Record<string, boolean>;
|
||||||
export const openTipsAtom = atom<boolean>(false);
|
|
||||||
|
const DEFAULT_VALUE = '0.0.0';
|
||||||
|
//atomWithStorage always uses initial value when first render
|
||||||
|
//https://github.com/pmndrs/jotai/discussions/1737
|
||||||
|
|
||||||
|
function getInitialValue() {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const storedValue = window.localStorage.getItem('lastVersion');
|
||||||
|
if (storedValue) {
|
||||||
|
return JSON.parse(storedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DEFAULT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lastVersionAtom = atomWithStorage(
|
||||||
|
'lastVersion',
|
||||||
|
getInitialValue()
|
||||||
|
);
|
||||||
|
export const guideHiddenAtom = atomWithStorage<Visibility>('guideHidden', {});
|
||||||
|
|
||||||
|
export const guideHiddenUntilNextUpdateAtom = atomWithStorage<Visibility>(
|
||||||
|
'guideHiddenUntilNextUpdate',
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { useTranslation } from '@affine/i18n';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useIsFirstLoad,
|
useGuideHidden,
|
||||||
useOpenTips,
|
useGuideHiddenUntilNextUpdate,
|
||||||
|
useUpdateTipsOnVersionChange,
|
||||||
} from '../../../hooks/affine/use-is-first-load';
|
} from '../../../hooks/affine/use-is-first-load';
|
||||||
import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status';
|
import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status';
|
||||||
import { SidebarSwitchIcon } from './icons';
|
import { SidebarSwitchIcon } from './icons';
|
||||||
@@ -19,10 +20,12 @@ export const SidebarSwitch = ({
|
|||||||
tooltipContent,
|
tooltipContent,
|
||||||
testid = '',
|
testid = '',
|
||||||
}: SidebarSwitchProps) => {
|
}: SidebarSwitchProps) => {
|
||||||
|
useUpdateTipsOnVersionChange();
|
||||||
const [open, setOpen] = useSidebarStatus();
|
const [open, setOpen] = useSidebarStatus();
|
||||||
const [tooltipVisible, setTooltipVisible] = useState(false);
|
const [tooltipVisible, setTooltipVisible] = useState(false);
|
||||||
const [isFirstLoad, setIsFirstLoad] = useIsFirstLoad();
|
const [guideHidden, setGuideHidden] = useGuideHidden();
|
||||||
const [, setOpenTips] = useOpenTips();
|
const [guideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate] =
|
||||||
|
useGuideHiddenUntilNextUpdate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
tooltipContent =
|
tooltipContent =
|
||||||
tooltipContent || (open ? t('Collapse sidebar') : t('Expand sidebar'));
|
tooltipContent || (open ? t('Collapse sidebar') : t('Expand sidebar'));
|
||||||
@@ -41,13 +44,23 @@ export const SidebarSwitch = ({
|
|||||||
onClick={useCallback(() => {
|
onClick={useCallback(() => {
|
||||||
setOpen(!open);
|
setOpen(!open);
|
||||||
setTooltipVisible(false);
|
setTooltipVisible(false);
|
||||||
if (isFirstLoad) {
|
if (guideHiddenUntilNextUpdate['quickSearchTips'] === false) {
|
||||||
setIsFirstLoad(false);
|
setGuideHiddenUntilNextUpdate({
|
||||||
|
...guideHiddenUntilNextUpdate,
|
||||||
|
quickSearchTips: true,
|
||||||
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setOpenTips(true);
|
setGuideHidden({ ...guideHidden, quickSearchTips: false });
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
}, [isFirstLoad, open, setIsFirstLoad, setOpen, setOpenTips])}
|
}, [
|
||||||
|
guideHidden,
|
||||||
|
guideHiddenUntilNextUpdate,
|
||||||
|
open,
|
||||||
|
setGuideHidden,
|
||||||
|
setGuideHiddenUntilNextUpdate,
|
||||||
|
setOpen,
|
||||||
|
])}
|
||||||
onMouseEnter={useCallback(() => {
|
onMouseEnter={useCallback(() => {
|
||||||
setTooltipVisible(true);
|
setTooltipVisible(true);
|
||||||
}, [])}
|
}, [])}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { HTMLAttributes } from 'react';
|
|||||||
import { forwardRef, useCallback, useRef } from 'react';
|
import { forwardRef, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
import { currentEditorAtom, openQuickSearchModalAtom } from '../../../atoms';
|
import { currentEditorAtom, openQuickSearchModalAtom } from '../../../atoms';
|
||||||
import { useOpenTips } from '../../../hooks/affine/use-is-first-load';
|
import { useGuideHidden } 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 { useElementResizeEffect } from '../../../hooks/use-workspaces';
|
||||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||||
@@ -53,7 +53,7 @@ export const BlockSuiteEditorHeader = forwardRef<
|
|||||||
assertExists(pageMeta);
|
assertExists(pageMeta);
|
||||||
const title = pageMeta.title;
|
const title = pageMeta.title;
|
||||||
const { trash: isTrash } = pageMeta;
|
const { trash: isTrash } = pageMeta;
|
||||||
const [openTips, setOpenTips] = useOpenTips();
|
const [isTipsHidden, setTipsHidden] = useGuideHidden();
|
||||||
const isMac = () => {
|
const isMac = () => {
|
||||||
const env = getEnvironment();
|
const env = getEnvironment();
|
||||||
return env.isBrowser && env.isMacOs;
|
return env.isBrowser && env.isMacOs;
|
||||||
@@ -64,11 +64,11 @@ export const BlockSuiteEditorHeader = forwardRef<
|
|||||||
useElementResizeEffect(
|
useElementResizeEffect(
|
||||||
useAtomValue(currentEditorAtom),
|
useAtomValue(currentEditorAtom),
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
if (!openTips || !popperRef.current) {
|
if (isTipsHidden.quickSearchTips || !popperRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
popperRef.current.update();
|
popperRef.current.update();
|
||||||
}, [openTips])
|
}, [isTipsHidden.quickSearchTips])
|
||||||
);
|
);
|
||||||
|
|
||||||
const TipsContent = (
|
const TipsContent = (
|
||||||
@@ -91,7 +91,9 @@ export const BlockSuiteEditorHeader = forwardRef<
|
|||||||
</div>
|
</div>
|
||||||
<StyledQuickSearchTipButton
|
<StyledQuickSearchTipButton
|
||||||
data-testid="quick-search-got-it"
|
data-testid="quick-search-got-it"
|
||||||
onClick={() => setOpenTips(false)}
|
onClick={() =>
|
||||||
|
setTipsHidden({ ...isTipsHidden, quickSearchTips: true })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Got it
|
Got it
|
||||||
</StyledQuickSearchTipButton>
|
</StyledQuickSearchTipButton>
|
||||||
@@ -130,7 +132,7 @@ export const BlockSuiteEditorHeader = forwardRef<
|
|||||||
content={TipsContent}
|
content={TipsContent}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
popperRef={popperRef}
|
popperRef={popperRef}
|
||||||
open={openTips}
|
open={!isTipsHidden.quickSearchTips}
|
||||||
offset={[0, -5]}
|
offset={[0, -5]}
|
||||||
>
|
>
|
||||||
<StyledSearchArrowWrapper>
|
<StyledSearchArrowWrapper>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MuiFade, Tooltip } from '@affine/component';
|
import { MuiFade, Tooltip } from '@affine/component';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { CloseIcon } from '@blocksuite/icons';
|
import { CloseIcon, DoneIcon } from '@blocksuite/icons';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
@@ -23,9 +23,9 @@ const ContactModal = dynamic(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type IslandItemNames = 'contact' | 'shortcuts';
|
export type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts';
|
||||||
export const HelpIsland = ({
|
export const HelpIsland = ({
|
||||||
showList = ['contact', 'shortcuts'],
|
showList = ['whatNew', 'contact', 'shortcuts'],
|
||||||
}: {
|
}: {
|
||||||
showList?: IslandItemNames[];
|
showList?: IslandItemNames[];
|
||||||
}) => {
|
}) => {
|
||||||
@@ -62,6 +62,18 @@ export const HelpIsland = ({
|
|||||||
<StyledAnimateWrapper
|
<StyledAnimateWrapper
|
||||||
style={{ height: spread ? `${showList.length * 44}px` : 0 }}
|
style={{ height: spread ? `${showList.length * 44}px` : 0 }}
|
||||||
>
|
>
|
||||||
|
{showList.includes('whatNew') && (
|
||||||
|
<Tooltip content={t("Discover what's new")} placement="left-end">
|
||||||
|
<StyledIconWrapper
|
||||||
|
data-testid="right-bottom-change-log-icon"
|
||||||
|
onClick={() => {
|
||||||
|
window.open('https://affine.pro', '_blank');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DoneIcon />
|
||||||
|
</StyledIconWrapper>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
{showList.includes('contact') && (
|
{showList.includes('contact') && (
|
||||||
<Tooltip content={t('Contact Us')} placement="left-end">
|
<Tooltip content={t('Contact Us')} placement="left-end">
|
||||||
<StyledIconWrapper
|
<StyledIconWrapper
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { IconButton } from '@affine/component';
|
||||||
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
import { CloseIcon, DoneIcon } from '@blocksuite/icons';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useGuideHidden,
|
||||||
|
useGuideHiddenUntilNextUpdate,
|
||||||
|
} from '../../../../hooks/affine/use-is-first-load';
|
||||||
|
import { StyledListItem } from '../shared-styles';
|
||||||
|
import { StyledLink } from '../style';
|
||||||
|
export const ChangeLog = () => {
|
||||||
|
const [guideHidden, setGuideHidden] = useGuideHidden();
|
||||||
|
const [guideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate] =
|
||||||
|
useGuideHiddenUntilNextUpdate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const onCloseWhatsNew = useCallback(() => {
|
||||||
|
setGuideHiddenUntilNextUpdate({
|
||||||
|
...guideHiddenUntilNextUpdate,
|
||||||
|
changeLog: true,
|
||||||
|
});
|
||||||
|
setGuideHidden({ ...guideHidden, changeLog: true });
|
||||||
|
}, [
|
||||||
|
guideHidden,
|
||||||
|
guideHiddenUntilNextUpdate,
|
||||||
|
setGuideHidden,
|
||||||
|
setGuideHiddenUntilNextUpdate,
|
||||||
|
]);
|
||||||
|
if (guideHiddenUntilNextUpdate.changeLog) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledListItem data-testid="change-log">
|
||||||
|
<StyledLink href={'https://affine.pro'} target="_blank">
|
||||||
|
<DoneIcon />
|
||||||
|
{t("Discover what's new!")}
|
||||||
|
</StyledLink>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
onCloseWhatsNew();
|
||||||
|
}}
|
||||||
|
data-testid="change-log-close-button"
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</StyledListItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChangeLog;
|
||||||
@@ -15,6 +15,7 @@ import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status';
|
|||||||
import { usePageMeta } from '../../../hooks/use-page-meta';
|
import { usePageMeta } from '../../../hooks/use-page-meta';
|
||||||
import type { RemWorkspace } from '../../../shared';
|
import type { RemWorkspace } from '../../../shared';
|
||||||
import { SidebarSwitch } from '../../affine/sidebar-switch';
|
import { SidebarSwitch } from '../../affine/sidebar-switch';
|
||||||
|
import { ChangeLog } from './changeLog';
|
||||||
import Favorite from './favorite';
|
import Favorite from './favorite';
|
||||||
import { Pivots } from './Pivots';
|
import { Pivots } from './Pivots';
|
||||||
import { StyledListItem } from './shared-styles';
|
import { StyledListItem } from './shared-styles';
|
||||||
@@ -86,7 +87,7 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
|||||||
currentWorkspace={currentWorkspace}
|
currentWorkspace={currentWorkspace}
|
||||||
onClick={onOpenWorkspaceListModal}
|
onClick={onOpenWorkspaceListModal}
|
||||||
/>
|
/>
|
||||||
|
<ChangeLog />
|
||||||
<StyledListItem
|
<StyledListItem
|
||||||
data-testid="slider-bar-quick-search-button"
|
data-testid="slider-bar-quick-search-button"
|
||||||
onClick={useCallback(() => {
|
onClick={useCallback(() => {
|
||||||
@@ -117,7 +118,6 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
|||||||
{t('Workspace Settings')}
|
{t('Workspace Settings')}
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
</StyledListItem>
|
</StyledListItem>
|
||||||
|
|
||||||
<StyledListItem
|
<StyledListItem
|
||||||
active={
|
active={
|
||||||
currentPath ===
|
currentPath ===
|
||||||
@@ -133,7 +133,6 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
|||||||
<span data-testid="all-pages">{t('All pages')}</span>
|
<span data-testid="all-pages">{t('All pages')}</span>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
</StyledListItem>
|
</StyledListItem>
|
||||||
|
|
||||||
<Favorite
|
<Favorite
|
||||||
currentPath={currentPath}
|
currentPath={currentPath}
|
||||||
paths={paths}
|
paths={paths}
|
||||||
@@ -148,7 +147,6 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
|||||||
allMetas={pageMeta}
|
allMetas={pageMeta}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<StyledListItem
|
<StyledListItem
|
||||||
active={
|
active={
|
||||||
currentPath ===
|
currentPath ===
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ import {
|
|||||||
import { LocalPlugin } from '../../plugins/local';
|
import { LocalPlugin } from '../../plugins/local';
|
||||||
import type { LocalWorkspace } from '../../shared';
|
import type { LocalWorkspace } from '../../shared';
|
||||||
import { BlockSuiteWorkspace, WorkspaceSubPath } from '../../shared';
|
import { BlockSuiteWorkspace, WorkspaceSubPath } from '../../shared';
|
||||||
import { useIsFirstLoad, useOpenTips } from '../affine/use-is-first-load';
|
import {
|
||||||
|
useGuideHidden,
|
||||||
|
useGuideHiddenUntilNextUpdate,
|
||||||
|
useLastVersion,
|
||||||
|
useTipsDisplayStatus,
|
||||||
|
} from '../affine/use-is-first-load';
|
||||||
import {
|
import {
|
||||||
useRecentlyViewed,
|
useRecentlyViewed,
|
||||||
useSyncRecentViewsWithRouter,
|
useSyncRecentViewsWithRouter,
|
||||||
@@ -348,20 +353,47 @@ describe('useRecentlyViewed', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('useIsFirstLoad', () => {
|
describe('useIsFirstLoad', () => {
|
||||||
test('basic', async () => {
|
test('useLastVersion', async () => {
|
||||||
const firstLoad = renderHook(() => useIsFirstLoad());
|
const lastVersion = renderHook(() => useLastVersion());
|
||||||
const setFirstLoad = firstLoad.result.current[1];
|
const setLastVersion = lastVersion.result.current[1];
|
||||||
expect(firstLoad.result.current[0]).toEqual(true);
|
expect(lastVersion.result.current[0]).toEqual('0.0.0');
|
||||||
setFirstLoad(false);
|
setLastVersion('testVersion');
|
||||||
firstLoad.rerender();
|
lastVersion.rerender();
|
||||||
expect(firstLoad.result.current[0]).toEqual(false);
|
expect(lastVersion.result.current[0]).toEqual('testVersion');
|
||||||
});
|
});
|
||||||
test('useOpenTips', async () => {
|
test('useGuideHidden', async () => {
|
||||||
const openTips = renderHook(() => useOpenTips());
|
const guideHidden = renderHook(() => useGuideHidden());
|
||||||
const setOpenTips = openTips.result.current[1];
|
const setGuideHidden = guideHidden.result.current[1];
|
||||||
expect(openTips.result.current[0]).toEqual(false);
|
expect(guideHidden.result.current[0]).toEqual({});
|
||||||
setOpenTips(true);
|
setGuideHidden({ test: true });
|
||||||
openTips.rerender();
|
guideHidden.rerender();
|
||||||
expect(openTips.result.current[0]).toEqual(true);
|
expect(guideHidden.result.current[0]).toEqual({ test: true });
|
||||||
|
});
|
||||||
|
test('useGuideHiddenUntilNextUpdate', async () => {
|
||||||
|
const guideHiddenUntilNextUpdate = renderHook(() =>
|
||||||
|
useGuideHiddenUntilNextUpdate()
|
||||||
|
);
|
||||||
|
const setGuideHiddenUntilNextUpdate =
|
||||||
|
guideHiddenUntilNextUpdate.result.current[1];
|
||||||
|
expect(guideHiddenUntilNextUpdate.result.current[0]).toEqual({});
|
||||||
|
setGuideHiddenUntilNextUpdate({ test: true });
|
||||||
|
guideHiddenUntilNextUpdate.rerender();
|
||||||
|
expect(guideHiddenUntilNextUpdate.result.current[0]).toEqual({
|
||||||
|
test: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('useTipsDisplayStatus', async () => {
|
||||||
|
const tipsDisplayStatus = renderHook(() => useTipsDisplayStatus());
|
||||||
|
const setTipsDisplayStatus = tipsDisplayStatus.result.current;
|
||||||
|
expect(tipsDisplayStatus.result.current).toEqual({
|
||||||
|
quickSearchTips: {
|
||||||
|
permanentlyHidden: true,
|
||||||
|
hiddenUntilNextUpdate: true,
|
||||||
|
},
|
||||||
|
changeLog: {
|
||||||
|
permanentlyHidden: true,
|
||||||
|
hiddenUntilNextUpdate: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,74 @@
|
|||||||
import { useAtom } from 'jotai';
|
import { config } from '@affine/env';
|
||||||
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { isFirstLoadAtom, openTipsAtom } from '../../atoms/first-load';
|
import {
|
||||||
|
guideHiddenAtom,
|
||||||
|
guideHiddenUntilNextUpdateAtom,
|
||||||
|
lastVersionAtom,
|
||||||
|
} from '../../atoms/first-load';
|
||||||
|
|
||||||
export function useIsFirstLoad() {
|
export function useLastVersion() {
|
||||||
const [isFirstLoad, setIsFirstLoad] = useAtom(isFirstLoadAtom);
|
return useAtom(lastVersionAtom);
|
||||||
return [isFirstLoad, setIsFirstLoad] as const;
|
|
||||||
}
|
}
|
||||||
export function useOpenTips() {
|
|
||||||
const [openTips, setOpenTips] = useAtom(openTipsAtom);
|
export function useGuideHidden() {
|
||||||
return [openTips, setOpenTips] as const;
|
return useAtom(guideHiddenAtom);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGuideHiddenUntilNextUpdate() {
|
||||||
|
return useAtom(guideHiddenUntilNextUpdateAtom);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIPS = {
|
||||||
|
quickSearchTips: true,
|
||||||
|
changeLog: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useTipsDisplayStatus() {
|
||||||
|
const permanentlyHiddenTips = useAtomValue(guideHiddenAtom);
|
||||||
|
const hiddenUntilNextUpdateTips = useAtomValue(
|
||||||
|
guideHiddenUntilNextUpdateAtom
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
quickSearchTips: {
|
||||||
|
permanentlyHidden: permanentlyHiddenTips.quickSearchTips || true,
|
||||||
|
hiddenUntilNextUpdate: hiddenUntilNextUpdateTips.quickSearchTips || true,
|
||||||
|
},
|
||||||
|
changeLog: {
|
||||||
|
permanentlyHidden: permanentlyHiddenTips.changeLog || true,
|
||||||
|
hiddenUntilNextUpdate: hiddenUntilNextUpdateTips.changeLog || true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpdateTipsOnVersionChange() {
|
||||||
|
const [lastVersion, setLastVersion] = useLastVersion();
|
||||||
|
const currentVersion = config.gitVersion;
|
||||||
|
const tipsDisplayStatus = useTipsDisplayStatus();
|
||||||
|
const setPermanentlyHiddenTips = useSetAtom(guideHiddenAtom);
|
||||||
|
const setHiddenUntilNextUpdateTips = useSetAtom(
|
||||||
|
guideHiddenUntilNextUpdateAtom
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastVersion !== currentVersion) {
|
||||||
|
setLastVersion(currentVersion);
|
||||||
|
const newHiddenUntilNextUpdateTips = { ...TIPS };
|
||||||
|
const newPermanentlyHiddenTips = { ...TIPS, changeLog: false };
|
||||||
|
Object.keys(tipsDisplayStatus).forEach(tipKey => {
|
||||||
|
newHiddenUntilNextUpdateTips[tipKey as keyof typeof TIPS] = false;
|
||||||
|
});
|
||||||
|
setHiddenUntilNextUpdateTips(newHiddenUntilNextUpdateTips);
|
||||||
|
setPermanentlyHiddenTips(newPermanentlyHiddenTips);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
currentVersion,
|
||||||
|
lastVersion,
|
||||||
|
setLastVersion,
|
||||||
|
setPermanentlyHiddenTips,
|
||||||
|
setHiddenUntilNextUpdateTips,
|
||||||
|
tipsDisplayStatus,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,7 +257,9 @@ export const WorkspaceLayoutInner: React.FC<React.PropsWithChildren> = ({
|
|||||||
</div>
|
</div>
|
||||||
{!isPublicWorkspace && (
|
{!isPublicWorkspace && (
|
||||||
<HelpIsland
|
<HelpIsland
|
||||||
showList={router.query.pageId ? undefined : ['contact']}
|
showList={
|
||||||
|
router.query.pageId ? undefined : ['whatNew', 'contact']
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledToolWrapper>
|
</StyledToolWrapper>
|
||||||
|
|||||||
@@ -200,5 +200,6 @@
|
|||||||
"Move to": "Move to",
|
"Move to": "Move to",
|
||||||
"Move page to...": "Move page to...",
|
"Move page to...": "Move page to...",
|
||||||
"Remove from Pivots": "Remove from Pivots",
|
"Remove from Pivots": "Remove from Pivots",
|
||||||
"RFP": "Pages can be freely added/removed from pivots, remaining accessible from \"All Pages\"."
|
"RFP": "Pages can be freely added/removed from pivots, remaining accessible from \"All Pages\".",
|
||||||
|
"Discover what's new!": "Discover what's new!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,3 +29,31 @@ test.describe('Open AFFiNE', () => {
|
|||||||
expect(currentWorkspaceName).toEqual('New Workspace 2');
|
expect(currentWorkspaceName).toEqual('New Workspace 2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('AFFiNE change log', () => {
|
||||||
|
test('Open affine in first time after updated', async ({ page }) => {
|
||||||
|
await openHomePage(page);
|
||||||
|
const changeLogItem = page.locator('[data-testid=change-log]');
|
||||||
|
await expect(changeLogItem).toBeVisible();
|
||||||
|
const closeButton = page.locator('[data-testid=change-log-close-button]');
|
||||||
|
await closeButton.click();
|
||||||
|
await expect(changeLogItem).not.toBeVisible();
|
||||||
|
await page.goto('http://localhost:8080');
|
||||||
|
const currentChangeLogItem = page.locator('[data-testid=change-log]');
|
||||||
|
await expect(currentChangeLogItem).not.toBeVisible();
|
||||||
|
});
|
||||||
|
test('Click right-bottom corner change log icon', async ({ page }) => {
|
||||||
|
await openHomePage(page);
|
||||||
|
await waitMarkdownImported(page);
|
||||||
|
await page.locator('[data-testid=help-island]').click();
|
||||||
|
const editorRightBottomChangeLog = page.locator(
|
||||||
|
'[data-testid=right-bottom-change-log-icon]'
|
||||||
|
);
|
||||||
|
expect(await editorRightBottomChangeLog.isVisible()).toEqual(true);
|
||||||
|
await page.getByRole('link', { name: 'All pages' }).click();
|
||||||
|
const normalRightBottomChangeLog = page.locator(
|
||||||
|
'[data-testid=right-bottom-change-log-icon]'
|
||||||
|
);
|
||||||
|
expect(await normalRightBottomChangeLog.isVisible()).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -186,4 +186,22 @@ test.describe('Novice guidance for quick search', () => {
|
|||||||
await page.locator('[data-testid=quick-search-got-it]').click();
|
await page.locator('[data-testid=quick-search-got-it]').click();
|
||||||
await expect(quickSearchTips).not.toBeVisible();
|
await expect(quickSearchTips).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
test('After appearing once, it will not appear a second time', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await openHomePage(page);
|
||||||
|
await waitMarkdownImported(page);
|
||||||
|
const quickSearchTips = page.locator('[data-testid=quick-search-tips]');
|
||||||
|
await expect(quickSearchTips).not.toBeVisible();
|
||||||
|
await page.getByTestId('sliderBar-arrowButton-collapse').click();
|
||||||
|
const sliderBarArea = page.getByTestId('sliderBar');
|
||||||
|
await expect(sliderBarArea).not.toBeVisible();
|
||||||
|
await expect(quickSearchTips).toBeVisible();
|
||||||
|
await page.locator('[data-testid=quick-search-got-it]').click();
|
||||||
|
await expect(quickSearchTips).not.toBeVisible();
|
||||||
|
await page.reload();
|
||||||
|
await page.locator('[data-testid=sliderBar-arrowButton-expand]').click();
|
||||||
|
await page.getByTestId('sliderBar-arrowButton-collapse').click();
|
||||||
|
await expect(quickSearchTips).not.toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user