Merge branch 'canary' into stable

This commit is contained in:
李华桥
2023-12-01 16:12:17 +08:00
147 changed files with 1775 additions and 1993 deletions
@@ -1,69 +0,0 @@
import { isBrowser } from '@affine/env/constant';
import type { UpdateMeta } from '@toeverything/infra/type';
import { atomWithObservable, atomWithStorage } from 'jotai/utils';
import { Observable } from 'rxjs';
// todo: move to utils?
function rpcToObservable<
T,
H extends () => Promise<T>,
E extends (callback: (t: T) => void) => () => void,
>(
initialValue: T | null,
{
event,
handler,
onSubscribe,
}: {
event?: E;
handler?: H;
onSubscribe?: () => void;
}
): Observable<T | null> {
return new Observable<T | null>(subscriber => {
subscriber.next(initialValue);
onSubscribe?.();
if (!isBrowser || !environment.isDesktop || !event) {
subscriber.complete();
return;
}
handler?.()
.then(t => {
subscriber.next(t);
})
.catch(err => {
subscriber.error(err);
});
return event(t => {
subscriber.next(t);
});
});
}
export const updateReadyAtom = atomWithObservable(() => {
return rpcToObservable(null as UpdateMeta | null, {
event: window.events?.updater.onUpdateReady,
});
});
export const updateAvailableAtom = atomWithObservable(() => {
return rpcToObservable(null as UpdateMeta | null, {
event: window.events?.updater.onUpdateAvailable,
onSubscribe: () => {
window.apis?.updater.checkForUpdatesAndNotify().catch(err => {
console.error(err);
});
},
});
});
export const downloadProgressAtom = atomWithObservable(() => {
return rpcToObservable(null as number | null, {
event: window.events?.updater.onDownloadProgress,
});
});
export const changelogCheckedAtom = atomWithStorage<Record<string, boolean>>(
'affine:client-changelog-checked',
{}
);
@@ -1,18 +1,21 @@
import { isBrowser, Unreachable } from '@affine/env/constant';
import { Unreachable } from '@affine/env/constant';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons';
import { Tooltip } from '@toeverything/components/tooltip';
import clsx from 'clsx';
import { atom, useAtomValue, useSetAtom } from 'jotai';
import { startTransition, useCallback, useState } from 'react';
import * as styles from './index.css';
import {
changelogCheckedAtom,
currentChangelogUnreadAtom,
currentVersionAtom,
downloadProgressAtom,
updateAvailableAtom,
updateReadyAtom,
} from './index.jotai';
useAppUpdater,
} from '@toeverything/hooks/use-app-updater';
import clsx from 'clsx';
import { useAtomValue, useSetAtom } from 'jotai';
import { startTransition, useCallback } from 'react';
import * as styles from './index.css';
export interface AddPageButtonPureProps {
onClickUpdate: () => void;
@@ -29,26 +32,6 @@ export interface AddPageButtonPureProps {
style?: React.CSSProperties;
}
const currentVersionAtom = atom(async () => {
if (!isBrowser) {
return null;
}
const currentVersion = await window.apis?.updater.currentVersion();
return currentVersion;
});
const currentChangelogUnreadAtom = atom(async get => {
if (!isBrowser) {
return false;
}
const mapping = get(changelogCheckedAtom);
const currentVersion = await get(currentVersionAtom);
if (currentVersion) {
return !mapping[currentVersion];
}
return false;
});
export function AppUpdaterButtonPure({
updateReady,
onClickUpdate,
@@ -198,12 +181,12 @@ export function AppUpdaterButton({
const currentChangelogUnread = useAtomValue(currentChangelogUnreadAtom);
const updateReady = useAtomValue(updateReadyAtom);
const updateAvailable = useAtomValue(updateAvailableAtom);
const currentVersion = useAtomValue(currentVersionAtom);
const downloadProgress = useAtomValue(downloadProgressAtom);
const currentVersion = useAtomValue(currentVersionAtom);
const { quitAndInstall, appQuitting } = useAppUpdater();
const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom);
const [appQuitting, setAppQuitting] = useState(false);
const onDismissCurrentChangelog = useCallback(() => {
const dismissCurrentChangelog = useCallback(() => {
if (!currentVersion) {
return;
}
@@ -216,13 +199,10 @@ export function AppUpdaterButton({
})
);
}, [currentVersion, setChangelogCheckAtom]);
const onClickUpdate = useCallback(() => {
const handleClickUpdate = useCallback(() => {
if (updateReady) {
setAppQuitting(true);
window.apis?.updater.quitAndInstall().catch(err => {
// TODO: add error toast here
console.error(err);
});
quitAndInstall();
} else if (updateAvailable) {
if (updateAvailable.allowAutoUpdate) {
// wait for download to finish
@@ -234,23 +214,25 @@ export function AppUpdaterButton({
}
} else if (currentChangelogUnread) {
window.open(runtimeConfig.changelogUrl, '_blank');
onDismissCurrentChangelog();
dismissCurrentChangelog();
} else {
throw new Unreachable();
}
}, [
currentChangelogUnread,
currentVersion,
onDismissCurrentChangelog,
updateAvailable,
updateReady,
quitAndInstall,
updateAvailable,
currentChangelogUnread,
dismissCurrentChangelog,
currentVersion,
]);
return (
<AppUpdaterButtonPure
appQuitting={appQuitting}
updateReady={!!updateReady}
onClickUpdate={onClickUpdate}
onDismissCurrentChangelog={onDismissCurrentChangelog}
onClickUpdate={handleClickUpdate}
onDismissCurrentChangelog={dismissCurrentChangelog}
currentChangelogUnread={currentChangelogUnread}
updateAvailable={updateAvailable}
downloadProgress={downloadProgress}
@@ -259,5 +241,3 @@ export function AppUpdaterButton({
/>
);
}
export * from './index.jotai';
@@ -2,8 +2,10 @@ import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
export const APP_SIDEBAR_OPEN = 'app-sidebar-open';
export const appSidebarOpenAtom = atomWithStorage(APP_SIDEBAR_OPEN, true);
export const appSidebarFloatingAtom = atom(false);
export const isMobile = window.innerWidth < 768;
export const appSidebarOpenAtom = atomWithStorage(APP_SIDEBAR_OPEN, !isMobile);
export const appSidebarFloatingAtom = atom(isMobile);
export const appSidebarResizingAtom = atom(false);
export const appSidebarWidthAtom = atomWithStorage(
@@ -10,9 +10,9 @@ export const RootBlockHub = () => {
const div = ref.current;
if (blockHub) {
if (div.hasChildNodes()) {
div.removeChild(div.firstChild as ChildNode);
(div.firstChild as ChildNode).remove();
}
div.appendChild(blockHub);
div.append(blockHub);
}
}
}, [blockHub]);
@@ -192,9 +192,9 @@ const BlockSuiteEditorImpl = ({
if (!container) {
return;
}
container.appendChild(editor);
container.append(editor);
return () => {
container.removeChild(editor);
editor.remove();
};
}, [editor]);
@@ -4,9 +4,11 @@ import { typesystem } from './typesystem';
type MatcherData<Data, Type extends TType = TType> = { type: Type; data: Data };
export class Matcher<Data, Type extends TType = TType> {
private list: MatcherData<Data, Type>[] = [];
private readonly list: MatcherData<Data, Type>[] = [];
constructor(private _match?: (type: Type, target: TType) => boolean) {}
constructor(
private readonly _match?: (type: Type, target: TType) => boolean
) {}
register(type: Type, data: Data) {
this.list.push({ type, data });
@@ -92,8 +92,8 @@ export type ValueOfData<T extends DataDefine> = T extends DataDefine<infer R>
export class DataDefine<Data extends DataTypeShape = Record<string, unknown>> {
constructor(
private config: DataDefineConfig<Data>,
private dataMap: Map<string, DataDefine>
private readonly config: DataDefineConfig<Data>,
private readonly dataMap: Map<string, DataDefine>
) {}
create(data?: Data): TDataType<Data> {
@@ -195,7 +195,7 @@ export class Typesystem {
): boolean {
if (superType.type === 'typeRef') {
// TODO both are ref
if (context && sub.type != 'typeRef') {
if (context && sub.type !== 'typeRef') {
context[superType.name] = sub;
}
// TODO bound
@@ -141,7 +141,7 @@ filterMatcher.register(
name: 'is',
defaultArgs: () => [true],
impl: (value, target) => {
return value == target;
return value === target;
},
}
);
@@ -209,7 +209,7 @@ filterMatcher.register(
defaultArgs: () => [],
impl: tags => {
const safeTags = safeArray(tags);
return safeTags.length == 0;
return safeTags.length === 0;
},
}
);
@@ -1,5 +1,4 @@
import type { Tag } from '@affine/env/filter';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import { EdgelessIcon, PageIcon, ToggleCollapseIcon } from '@blocksuite/icons';
@@ -19,93 +18,8 @@ import {
useAtom,
useAtomValue,
} from './scoped-atoms';
import type {
PageGroupDefinition,
PageGroupProps,
PageListItemProps,
PageListProps,
} from './types';
import { type DateKey } from './types';
import { betweenDaysAgo, shallowEqual, withinDaysAgo } from './utils';
// todo: optimize date matchers
const getDateGroupDefinitions = (key: DateKey): PageGroupDefinition[] => [
{
id: 'today',
label: <Trans i18nKey="com.affine.today" />,
match: item => withinDaysAgo(new Date(item[key] ?? item.createDate), 1),
},
{
id: 'yesterday',
label: <Trans i18nKey="com.affine.yesterday" />,
match: item => betweenDaysAgo(new Date(item[key] ?? item.createDate), 1, 2),
},
{
id: 'last7Days',
label: <Trans i18nKey="com.affine.last7Days" />,
match: item => betweenDaysAgo(new Date(item[key] ?? item.createDate), 2, 7),
},
{
id: 'last30Days',
label: <Trans i18nKey="com.affine.last30Days" />,
match: item =>
betweenDaysAgo(new Date(item[key] ?? item.createDate), 7, 30),
},
{
id: 'moreThan30Days',
label: <Trans i18nKey="com.affine.moreThan30Days" />,
match: item => !withinDaysAgo(new Date(item[key] ?? item.createDate), 30),
},
];
const pageGroupDefinitions = {
createDate: getDateGroupDefinitions('createDate'),
updatedDate: getDateGroupDefinitions('updatedDate'),
// add more here later
// todo: some page group definitions maybe dynamic
};
export function pagesToPageGroups(
pages: PageMeta[],
key?: DateKey
): PageGroupProps[] {
if (!key) {
return [
{
id: 'all',
items: pages,
allItems: pages,
},
];
}
// assume pages are already sorted, we will use the page order to determine the group order
const groupDefs = pageGroupDefinitions[key];
const groups: PageGroupProps[] = [];
for (const page of pages) {
// for a single page, there could be multiple groups that it belongs to
const matchedGroups = groupDefs.filter(def => def.match(page));
for (const groupDef of matchedGroups) {
const group = groups.find(g => g.id === groupDef.id);
if (group) {
group.items.push(page);
} else {
const label =
typeof groupDef.label === 'function'
? groupDef.label()
: groupDef.label;
groups.push({
id: groupDef.id,
label: label,
items: [page],
allItems: pages,
});
}
}
}
return groups;
}
import type { PageGroupProps, PageListItemProps, PageListProps } from './types';
import { shallowEqual } from './utils';
export const PageGroupHeader = ({ id, items, label }: PageGroupProps) => {
const [collapseState, setCollapseState] = useAtom(pageGroupCollapseStateAtom);
@@ -0,0 +1,85 @@
import { Trans } from '@affine/i18n';
import type { PageMeta } from '@blocksuite/store';
import type { PageGroupDefinition, PageGroupProps } from './types';
import { type DateKey } from './types';
import { betweenDaysAgo, withinDaysAgo } from './utils';
// todo: optimize date matchers
const getDateGroupDefinitions = (key: DateKey): PageGroupDefinition[] => [
{
id: 'today',
label: <Trans i18nKey="com.affine.today" />,
match: item => withinDaysAgo(new Date(item[key] ?? item.createDate), 1),
},
{
id: 'yesterday',
label: <Trans i18nKey="com.affine.yesterday" />,
match: item => betweenDaysAgo(new Date(item[key] ?? item.createDate), 1, 2),
},
{
id: 'last7Days',
label: <Trans i18nKey="com.affine.last7Days" />,
match: item => betweenDaysAgo(new Date(item[key] ?? item.createDate), 2, 7),
},
{
id: 'last30Days',
label: <Trans i18nKey="com.affine.last30Days" />,
match: item =>
betweenDaysAgo(new Date(item[key] ?? item.createDate), 7, 30),
},
{
id: 'moreThan30Days',
label: <Trans i18nKey="com.affine.moreThan30Days" />,
match: item => !withinDaysAgo(new Date(item[key] ?? item.createDate), 30),
},
];
const pageGroupDefinitions = {
createDate: getDateGroupDefinitions('createDate'),
updatedDate: getDateGroupDefinitions('updatedDate'),
// add more here later
// todo: some page group definitions maybe dynamic
};
export function pagesToPageGroups(
pages: PageMeta[],
key?: DateKey
): PageGroupProps[] {
if (!key) {
return [
{
id: 'all',
items: pages,
allItems: pages,
},
];
}
// assume pages are already sorted, we will use the page order to determine the group order
const groupDefs = pageGroupDefinitions[key];
const groups: PageGroupProps[] = [];
for (const page of pages) {
// for a single page, there could be multiple groups that it belongs to
const matchedGroups = groupDefs.filter(def => def.match(page));
for (const groupDef of matchedGroups) {
const group = groups.find(g => g.id === groupDef.id);
if (group) {
group.items.push(page);
} else {
const label =
typeof groupDef.label === 'function'
? groupDef.label()
: groupDef.label;
groups.push({
id: groupDef.id,
label: label,
items: [page],
allItems: pages,
});
}
}
}
return groups;
}
@@ -4,7 +4,7 @@ import { atom } from 'jotai';
import { selectAtom } from 'jotai/utils';
import { createIsolation } from 'jotai-scope';
import { pagesToPageGroups } from './page-group';
import { pagesToPageGroups } from './pages-to-page-group';
import type {
PageListProps,
PageMetaRecord,
@@ -10,9 +10,9 @@ import * as styles from './page-list.css';
export function isToday(date: Date): boolean {
const today = new Date();
return (
date.getDate() == today.getDate() &&
date.getMonth() == today.getMonth() &&
date.getFullYear() == today.getFullYear()
date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear()
);
}
@@ -162,10 +162,10 @@ export function shallowEqual(objA: any, objB: any) {
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
for (const key of keysA) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
!Object.is(objA[keysA[i]], objB[keysA[i]])
!Object.prototype.hasOwnProperty.call(objB, key) ||
!Object.is(objA[key], objB[key])
) {
return false;
}
@@ -5,7 +5,7 @@ import type {
} from '@affine/env/filter';
import type { PropertiesMeta } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { FilteredIcon } from '@blocksuite/icons';
import { FilterIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import { Menu } from '@toeverything/components/menu';
import { useCallback, useState } from 'react';
@@ -75,7 +75,7 @@ export const CollectionList = ({
<Button
className={styles.filterMenuTrigger}
type="default"
icon={<FilteredIcon />}
icon={<FilterIcon />}
data-testid="create-first-filter"
>
{t['com.affine.filter']()}
@@ -99,7 +99,7 @@ export const CollectionList = ({
<Button
className={styles.filterMenuTrigger}
type="default"
icon={<FilteredIcon />}
icon={<FilterIcon />}
data-testid="create-first-filter"
>
{t['com.affine.filter']()}
@@ -1,13 +1,6 @@
import {
createEmptyCollection,
useEditCollectionName,
} from '@affine/component/page-list';
import type { Collection } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { SaveIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import { Modal } from '@toeverything/components/modal';
import { nanoid } from 'nanoid';
import { useCallback, useMemo, useState } from 'react';
import Input from '../../../ui/input';
@@ -127,40 +120,3 @@ export const CreateCollection = ({
</div>
);
};
interface SaveAsCollectionButtonProps {
onConfirm: (collection: Collection) => Promise<void>;
}
export const SaveAsCollectionButton = ({
onConfirm,
}: SaveAsCollectionButtonProps) => {
const t = useAFFiNEI18N();
const { open, node } = useEditCollectionName({
title: t['com.affine.editCollection.saveCollection'](),
showTips: true,
});
const handleClick = useCallback(() => {
open('')
.then(name => {
return onConfirm(createEmptyCollection(nanoid(), { name }));
})
.catch(err => {
console.error(err);
});
}, [open, onConfirm]);
return (
<>
<Button
onClick={handleClick}
data-testid="save-as-collection"
icon={<SaveIcon />}
size="large"
style={{ padding: '7px 8px' }}
>
{t['com.affine.editCollection.saveCollection']()}
</Button>
{node}
</>
);
};
@@ -6,7 +6,7 @@ import { Button } from '@toeverything/components/button';
import { Modal } from '@toeverything/components/modal';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { RadioButton, RadioButtonGroup } from '../../../../index';
import { RadioButton, RadioButtonGroup } from '../../../../ui/button';
import * as styles from './edit-collection.css';
import { PagesMode } from './pages-mode';
import { RulesMode } from './rules-mode';
@@ -1,12 +1,7 @@
import {
type AllPageListConfig,
filterPageByRules,
} from '@affine/component/page-list';
import type { Filter } from '@affine/env/filter';
import type { PageMeta } from '@blocksuite/store';
import { Modal } from '@toeverything/components/modal';
import { type MouseEvent, useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import type { AllPageListConfig } from './edit-collection';
import { SelectPage } from './select-page';
export const useSelectPage = ({
allPageListConfig,
@@ -60,47 +55,3 @@ export const useSelectPage = ({
}),
};
};
export const useFilter = (list: PageMeta[]) => {
const [filters, changeFilters] = useState<Filter[]>([]);
const [showFilter, setShowFilter] = useState(false);
const clickFilter = useCallback(
(e: MouseEvent) => {
if (showFilter || filters.length !== 0) {
e.stopPropagation();
e.preventDefault();
setShowFilter(!showFilter);
}
},
[filters.length, showFilter]
);
const onCreateFilter = useCallback(
(filter: Filter) => {
changeFilters([...filters, filter]);
setShowFilter(true);
},
[filters]
);
return {
showFilter,
filters,
updateFilters: changeFilters,
clickFilter,
createFilter: onCreateFilter,
filteredList: list.filter(v => {
if (v.trash) {
return false;
}
return filterPageByRules(filters, [], v);
}),
};
};
export const useSearch = (list: PageMeta[]) => {
const [value, onChange] = useState('');
return {
searchText: value,
updateSearchText: onChange,
searchedList: value
? list.filter(v => v.title.toLowerCase().includes(value.toLowerCase()))
: list,
};
};
@@ -1,8 +1,3 @@
import {
type AllPageListConfig,
FilterList,
VirtualizedPageList,
} from '@affine/component/page-list';
import type { Collection } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { FilterIcon } from '@blocksuite/icons';
@@ -11,10 +6,14 @@ import { Menu } from '@toeverything/components/menu';
import clsx from 'clsx';
import { type ReactNode, useCallback } from 'react';
import { FilterList } from '../../filter/filter-list';
import { VariableSelect } from '../../filter/vars';
import { VirtualizedPageList } from '../../virtualized-page-list';
import type { AllPageListConfig } from './edit-collection';
import * as styles from './edit-collection.css';
import { useFilter, useSearch } from './hooks';
import { EmptyList } from './select-page';
import { useFilter } from './use-filter';
import { useSearch } from './use-search';
export const PagesMode = ({
switchMode,
@@ -6,13 +6,15 @@ import { Menu } from '@toeverything/components/menu';
import clsx from 'clsx';
import { useCallback, useState } from 'react';
import { VirtualizedPageList } from '../..';
import { FilterList } from '../../filter';
import { VariableSelect } from '../../filter/vars';
import { VirtualizedPageList } from '../../virtualized-page-list';
import { AffineShapeIcon } from '../affine-shape';
import type { AllPageListConfig } from './edit-collection';
import * as styles from './edit-collection.css';
import { useFilter, useSearch } from './hooks';
import { useFilter } from './use-filter';
import { useSearch } from './use-search';
export const SelectPage = ({
allPageListConfig,
init,
@@ -0,0 +1,40 @@
import type { Filter } from '@affine/env/filter';
import type { PageMeta } from '@blocksuite/store';
import { type MouseEvent, useCallback, useState } from 'react';
import { filterPageByRules } from '../../use-collection-manager';
export const useFilter = (list: PageMeta[]) => {
const [filters, changeFilters] = useState<Filter[]>([]);
const [showFilter, setShowFilter] = useState(false);
const clickFilter = useCallback(
(e: MouseEvent) => {
if (showFilter || filters.length !== 0) {
e.stopPropagation();
e.preventDefault();
setShowFilter(!showFilter);
}
},
[filters.length, showFilter]
);
const onCreateFilter = useCallback(
(filter: Filter) => {
changeFilters([...filters, filter]);
setShowFilter(true);
},
[filters]
);
return {
showFilter,
filters,
updateFilters: changeFilters,
clickFilter,
createFilter: onCreateFilter,
filteredList: list.filter(v => {
if (v.trash) {
return false;
}
return filterPageByRules(filters, [], v);
}),
};
};
@@ -0,0 +1,13 @@
import type { PageMeta } from '@blocksuite/store';
import { useState } from 'react';
export const useSearch = (list: PageMeta[]) => {
const [value, onChange] = useState('');
return {
searchText: value,
updateSearchText: onChange,
searchedList: value
? list.filter(v => v.title.toLowerCase().includes(value.toLowerCase()))
: list,
};
};
@@ -4,4 +4,5 @@ export * from './collection-list';
export * from './collection-operations';
export * from './create-collection';
export * from './edit-collection/edit-collection';
export * from './save-as-collection-button';
export * from './use-edit-collection';
@@ -0,0 +1,46 @@
import type { Collection } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { SaveIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import { nanoid } from 'nanoid';
import { useCallback } from 'react';
import { createEmptyCollection } from '../use-collection-manager';
import { useEditCollectionName } from './use-edit-collection';
interface SaveAsCollectionButtonProps {
onConfirm: (collection: Collection) => Promise<void>;
}
export const SaveAsCollectionButton = ({
onConfirm,
}: SaveAsCollectionButtonProps) => {
const t = useAFFiNEI18N();
const { open, node } = useEditCollectionName({
title: t['com.affine.editCollection.saveCollection'](),
showTips: true,
});
const handleClick = useCallback(() => {
open('')
.then(name => {
return onConfirm(createEmptyCollection(nanoid(), { name }));
})
.catch(err => {
console.error(err);
});
}, [open, onConfirm]);
return (
<>
<Button
onClick={handleClick}
data-testid="save-as-collection"
icon={<SaveIcon />}
size="large"
style={{ padding: '7px 8px' }}
>
{t['com.affine.editCollection.saveCollection']()}
</Button>
{node}
</>
);
};
@@ -1,12 +1,13 @@
import {
type AllPageListConfig,
CreateCollectionModal,
EditCollectionModal,
type EditCollectionMode,
} from '@affine/component/page-list';
import type { Collection } from '@affine/env/filter';
import { useCallback, useState } from 'react';
import { CreateCollectionModal } from './create-collection';
import {
type AllPageListConfig,
EditCollectionModal,
type EditCollectionMode,
} from './edit-collection/edit-collection';
export const useEditCollection = (config: AllPageListConfig) => {
const [data, setData] = useState<{
collection: Collection;
@@ -8,17 +8,19 @@ export const WorkspaceDetailSkeleton = () => {
<>
<SettingHeader title={<Skeleton />} subtitle={<Skeleton />} />
{new Array(3).fill(0).map((_, index) => {
return (
<SettingWrapper title={<Skeleton />} key={index}>
<SettingRow
name={<Skeleton />}
desc={<Skeleton />}
spreadCol={false}
></SettingRow>
</SettingWrapper>
);
})}
{Array.from({ length: 3 })
.fill(0)
.map((_, index) => {
return (
<SettingWrapper title={<Skeleton />} key={index}>
<SettingRow
name={<Skeleton />}
desc={<Skeleton />}
spreadCol={false}
></SettingRow>
</SettingWrapper>
);
})}
</>
);
};
@@ -26,9 +26,11 @@ export const WorkspaceListItemSkeleton = () => {
export const WorkspaceListSkeleton = () => {
return (
<>
{new Array(5).fill(0).map((_, index) => {
return <WorkspaceListItemSkeleton key={index} />;
})}
{Array.from({ length: 5 })
.fill(0)
.map((_, index) => {
return <WorkspaceListItemSkeleton key={index} />;
})}
</>
);
};
@@ -61,6 +61,7 @@ export const dropdownIcon = style({
export const radioButton = style({
flexGrow: 1,
flex: 1,
selectors: {
'&:not(:last-of-type)': {
marginRight: '4px',
@@ -72,7 +73,8 @@ export const radioButtonContent = style({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '24px',
height: '28px',
padding: '4px 8px',
borderRadius: '8px',
filter: 'drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.1))',
whiteSpace: 'nowrap',
@@ -84,7 +84,7 @@ export const playCheckAnimation = async (refElement: Element) => {
border-radius: 50%;
font-size: inherit;
`;
refElement.appendChild(sparkingEl);
refElement.append(sparkingEl);
await sparkingEl.animate(
[
@@ -29,7 +29,7 @@ export const Skeleton = ({
const style = {
width,
height,
...(_style || {}),
..._style,
};
return (
@@ -46,7 +46,7 @@ const createToastContainer = (portal?: HTMLElement) => {
data-testid="affine-toast-container"
></div>`;
const element = htmlToElement<HTMLDivElement>(template);
portal.appendChild(element);
portal.append(element);
return element;
};
@@ -98,7 +98,7 @@ const createAndShowNewToast = (
const toastElement = htmlToElement<HTMLDivElement>(toastTemplate);
// message is not trusted
toastElement.textContent = message;
ToastContainer.appendChild(toastElement);
ToastContainer.append(toastElement);
logger.debug(`toast with message: "${message}"`);
window.dispatchEvent(
new CustomEvent('affine-toast:emit', { detail: message })