Merge pull request #4473 from toeverything/release-0.8.4

0.8.4 Release proposal
This commit is contained in:
LongYinan
2023-09-26 02:58:00 -05:00
committed by GitHub
36 changed files with 281 additions and 213 deletions
+1
View File
@@ -1,6 +1,7 @@
name: Build Canary Desktop App on Staging Branch name: Build Canary Desktop App on Staging Branch
on: on:
workflow_dispatch:
push: push:
branches: branches:
# 0.6.x-staging # 0.6.x-staging
+1 -1
View File
@@ -60,7 +60,7 @@
"react-resizable-panels": "^0.0.55", "react-resizable-panels": "^0.0.55",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"ses": "^0.18.7", "ses": "^0.18.8",
"swr": "2.2.1", "swr": "2.2.1",
"y-protocols": "^1.0.5", "y-protocols": "^1.0.5",
"yjs": "^13.6.7", "yjs": "^13.6.7",
+1 -2
View File
@@ -1,4 +1,3 @@
import { isDesktop } from '@affine/env/constant';
import { atom, useAtom } from 'jotai'; import { atom, useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
@@ -50,7 +49,7 @@ export const fontStyleOptions = [
}[]; }[];
const appSettingBaseAtom = atomWithStorage<AppSetting>('affine-settings', { const appSettingBaseAtom = atomWithStorage<AppSetting>('affine-settings', {
clientBorder: isDesktop, clientBorder: environment.isDesktop && globalThis.platform !== 'win32',
fullWidthLayout: false, fullWidthLayout: false,
windowFrameStyle: 'frameless', windowFrameStyle: 'frameless',
fontStyle: 'Sans', fontStyle: 'Sans',
@@ -281,11 +281,7 @@ export const CreateWorkspaceModal = ({
console.error(err); console.error(err);
}); });
} else if (mode === 'new') { } else if (mode === 'new') {
setStep( setStep('name-workspace');
environment.isDesktop && runtimeConfig.enableSQLiteProvider
? 'set-db-location'
: 'name-workspace'
);
} else { } else {
setStep(undefined); setStep(undefined);
} }
@@ -24,11 +24,8 @@ export const shortcutKey = style({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
padding: '0 6px', padding: '0 6px',
border: '1px solid var(--affine-border-color)',
borderRadius: '4px', borderRadius: '4px',
background: 'var(--affine-background-tertiary-color)', background: 'var(--affine-background-tertiary-color)',
boxShadow:
'0px 6px 4px 0px rgba(255, 255, 255, 0.24) inset, 0px 0px 0px 0.5px rgba(0, 0, 0, 0.10) inset',
fontSize: 'var(--affine-font-xs)', fontSize: 'var(--affine-font-xs)',
selectors: { selectors: {
'&:not(:last-of-type)': { '&:not(:last-of-type)': {
@@ -25,6 +25,15 @@ export const titleInput = style({
margin: 'auto', margin: 'auto',
width: '100%', width: '100%',
height: '100%', height: '100%',
selectors: {
'&:focus': {
border: '1px solid var(--affine-black-10)',
borderRadius: '8px',
height: '32px',
padding: '6px 8px',
},
},
}); });
export const shadowTitle = style({ export const shadowTitle = style({
visibility: 'hidden', visibility: 'hidden',
@@ -51,7 +51,7 @@ export const AddCollectionButton = ({
onConfirm={setting.saveCollection} onConfirm={setting.saveCollection}
open={show} open={show}
onClose={() => showUpdateCollection(false)} onClose={() => showUpdateCollection(false)}
title={t['Save As New Collection']()} title={t['Save as New Collection']()}
init={defaultCollection} init={defaultCollection}
/> />
</> </>
+10 -4
View File
@@ -8,7 +8,13 @@ import {
} from '@toeverything/infra/atom'; } from '@toeverything/infra/atom';
import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { lazy, Suspense, useCallback, useTransition } from 'react'; import {
lazy,
startTransition,
Suspense,
useCallback,
useTransition,
} from 'react';
import type { SettingAtom } from '../atoms'; import type { SettingAtom } from '../atoms';
import { import {
@@ -119,13 +125,14 @@ export const AllWorkspaceModals = (): ReactElement => {
); );
const { jumpToSubPath } = useNavigateHelper(); const { jumpToSubPath } = useNavigateHelper();
const workspaces = useAtomValue(rootWorkspacesMetadataAtom); const workspaces = useAtomValue(rootWorkspacesMetadataAtom, {
delay: 0,
});
const setWorkspaces = useSetAtom(rootWorkspacesMetadataAtom); const setWorkspaces = useSetAtom(rootWorkspacesMetadataAtom);
const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom( const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom(
currentWorkspaceIdAtom currentWorkspaceIdAtom
); );
const setCurrentPageId = useSetAtom(currentPageIdAtom); const setCurrentPageId = useSetAtom(currentPageIdAtom);
const [isPending, startTransition] = useTransition();
const [, startCloseTransition] = useTransition(); const [, startCloseTransition] = useTransition();
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom); const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
@@ -146,7 +153,6 @@ export const AllWorkspaceModals = (): ReactElement => {
<> <>
<Suspense> <Suspense>
<WorkspaceListModal <WorkspaceListModal
disabled={isPending}
workspaces={workspaces} workspaces={workspaces}
currentWorkspaceId={currentWorkspaceId} currentWorkspaceId={currentWorkspaceId}
open={ open={
-3
View File
@@ -154,9 +154,6 @@ test('windows only check', async ({ page }) => {
test('delete workspace', async ({ page }) => { test('delete workspace', async ({ page }) => {
await page.getByTestId('current-workspace').click(); await page.getByTestId('current-workspace').click();
await page.getByTestId('new-workspace').click(); await page.getByTestId('new-workspace').click();
await page.getByTestId('create-workspace-default-location-button').click({
delay: 100,
});
await page.getByTestId('create-workspace-input').type('Delete Me', { await page.getByTestId('create-workspace-input').type('Delete Me', {
delay: 100, delay: 100,
}); });
+3 -1
View File
@@ -12,6 +12,8 @@ import fs from 'fs-extra';
import type { ElectronApplication, Page } from 'playwright'; import type { ElectronApplication, Page } from 'playwright';
import { _electron as electron } from 'playwright'; import { _electron as electron } from 'playwright';
import { removeWithRetry } from '../tests/utils';
function generateUUID() { function generateUUID() {
return crypto.randomUUID(); return crypto.randomUUID();
} }
@@ -102,7 +104,7 @@ export const test = base.extend<{
}); });
await use(electronApp); await use(electronApp);
try { try {
await fs.rm(clonedDist, { recursive: true, force: true }); await removeWithRetry(clonedDist);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
-11
View File
@@ -1,11 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "./lib",
"baseUrl": ".",
"target": "ESNext"
},
"references": [{ "path": "../../../tests/kit" }],
"include": ["**.spec.ts", "**.test.ts", "fixture.ts"],
"exclude": ["lib"]
}
-1
View File
@@ -113,7 +113,6 @@ module.exports = {
: undefined, : undefined,
// We need the following line for updater // We need the following line for updater
extraResource: ['./resources/app-update.yml'], extraResource: ['./resources/app-update.yml'],
ignore: ['e2e', 'tests'],
}, },
makers, makers,
hooks: { hooks: {
@@ -70,10 +70,13 @@ export const registerUpdater = async () => {
autoUpdater.on('checking-for-update', () => { autoUpdater.on('checking-for-update', () => {
logger.info('Checking for update'); logger.info('Checking for update');
}); });
let downloading = false;
autoUpdater.on('update-available', info => { autoUpdater.on('update-available', info => {
logger.info('Update available', info); logger.info('Update available', info);
if (allowAutoUpdate) { if (allowAutoUpdate && !downloading) {
downloading = true;
autoUpdater?.downloadUpdate().catch(e => { autoUpdater?.downloadUpdate().catch(e => {
downloading = false;
logger.error('Failed to download update', e); logger.error('Failed to download update', e);
}); });
logger.info('Update available, downloading...', info); logger.info('Update available, downloading...', info);
@@ -91,6 +94,7 @@ export const registerUpdater = async () => {
updaterSubjects.downloadProgress.next(e.percent); updaterSubjects.downloadProgress.next(e.percent);
}); });
autoUpdater.on('update-downloaded', e => { autoUpdater.on('update-downloaded', e => {
downloading = false;
updaterSubjects.updateReady.next({ updaterSubjects.updateReady.next({
version: e.version, version: e.version,
allowAutoUpdate, allowAutoUpdate,
-3
View File
@@ -33,9 +33,6 @@
{ {
"path": "./tsconfig.node.json" "path": "./tsconfig.node.json"
}, },
{
"path": "./e2e/tsconfig.json"
},
{ "path": "../../tests/kit" } { "path": "../../tests/kit" }
], ],
"ts-node": { "ts-node": {
+1 -1
View File
@@ -3,7 +3,7 @@
"compilerOptions": { "compilerOptions": {
"composite": true "composite": true
}, },
"include": ["**/__tests__/**/*", "./tests"], "include": ["**/__tests__/**/*", "./tests", "./e2e"],
"references": [ "references": [
{ {
"path": "./tsconfig.json" "path": "./tsconfig.json"
+1 -1
View File
@@ -25,7 +25,7 @@
"concurrently": "^8.2.0", "concurrently": "^8.2.0",
"jest-mock": "^29.6.2", "jest-mock": "^29.6.2",
"serve": "^14.2.0", "serve": "^14.2.0",
"ses": "^0.18.7", "ses": "^0.18.8",
"storybook": "^7.3.1", "storybook": "^7.3.1",
"storybook-dark-mode": "^3.0.1", "storybook-dark-mode": "^3.0.1",
"wait-on": "^7.0.1" "wait-on": "^7.0.1"
+1 -2
View File
@@ -32,8 +32,7 @@
"{projectRoot}/dist", "{projectRoot}/dist",
"{projectRoot}/build", "{projectRoot}/build",
"{projectRoot}/out", "{projectRoot}/out",
"{projectRoot}/storybook-static", "{projectRoot}/storybook-static"
"{workspaceRoot}/packages/i18n/src/i18n-generated.ts"
], ],
"inputs": [ "inputs": [
"{workspaceRoot}/infra/**/*", "{workspaceRoot}/infra/**/*",
+1
View File
@@ -21,6 +21,7 @@
"@affine/jotai": "workspace:*", "@affine/jotai": "workspace:*",
"@affine/workspace": "workspace:*", "@affine/workspace": "workspace:*",
"@dnd-kit/core": "^6.0.8", "@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2", "@dnd-kit/sortable": "^7.0.2",
"@emotion/cache": "^11.11.0", "@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
@@ -35,7 +35,7 @@ export const navWrapperStyle = style({
'&[data-enable-animation="true"]': { '&[data-enable-animation="true"]': {
transition: 'margin-left .3s, width .3s', transition: 'margin-left .3s, width .3s',
}, },
'&.has-background': { '&[data-is-floating="false"].has-background': {
backgroundColor: 'var(--affine-white-60)', backgroundColor: 'var(--affine-white-60)',
borderRight: '1px solid var(--affine-border-color)', borderRight: '1px solid var(--affine-border-color)',
}, },
@@ -2,6 +2,7 @@ import { Skeleton } from '@mui/material';
import { assignInlineVars } from '@vanilla-extract/dynamic'; import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx'; import clsx from 'clsx';
import { useAtom, useAtomValue } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import debounce from 'lodash/debounce';
import type { PropsWithChildren, ReactElement } from 'react'; import type { PropsWithChildren, ReactElement } from 'react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
@@ -77,10 +78,10 @@ export function AppSidebar(props: AppSidebarProps): ReactElement {
setAppSidebarFloating(isFloating && !!open); setAppSidebarFloating(isFloating && !!open);
} }
onResize(); const dOnResize = debounce(onResize, 50);
window.addEventListener('resize', onResize); window.addEventListener('resize', dOnResize);
return () => { return () => {
window.removeEventListener('resize', onResize); window.removeEventListener('resize', dOnResize);
}; };
}, [appSidebarWidth, open, setAppSidebarFloating, setOpen]); }, [appSidebarWidth, open, setAppSidebarFloating, setOpen]);
@@ -47,7 +47,6 @@ export const StyledCard = styled('div')<{
borderRadius: '12px', borderRadius: '12px',
border: `1px solid ${borderColor}`, border: `1px solid ${borderColor}`,
...displayFlex('flex-start', 'flex-start'), ...displayFlex('flex-start', 'flex-start'),
marginBottom: '12px',
transition: 'background .2s', transition: 'background .2s',
alignItems: 'center', alignItems: 'center',
position: 'relative', position: 'relative',
@@ -62,6 +61,12 @@ export const StyledCard = styled('div')<{
'.setting-entry': { '.setting-entry': {
opacity: 1, opacity: 1,
pointerEvents: 'auto', pointerEvents: 'auto',
backgroundColor: 'var(--affine-white-30)',
boxShadow: 'var(--affine-shadow-1)',
':hover': {
background:
'linear-gradient(0deg, var(--affine-hover-color) 0%, var(--affine-hover-color) 100%), var(--affine-white-30)',
},
}, },
}, },
'@media (max-width: 720px)': { '@media (max-width: 720px)': {
@@ -63,9 +63,7 @@ export const Condition = ({
<div className={styles.filterTypeIconStyle}> <div className={styles.filterTypeIconStyle}>
{variableDefineMap[ast.left.name].icon} {variableDefineMap[ast.left.name].icon}
</div> </div>
<div> <FilterTag name={ast.left.name} />
<FilterTag name={ast.left.name} />
</div>
</div> </div>
</Menu> </Menu>
<Menu <Menu
@@ -53,6 +53,8 @@ export const switchStyle = style({
color: 'var(--affine-text-secondary-color)', color: 'var(--affine-text-secondary-color)',
padding: '2px 4px', padding: '2px 4px',
transition: 'all 0.15s ease-in-out', transition: 'all 0.15s ease-in-out',
display: 'flex',
alignItems: 'center',
':hover': { ':hover': {
cursor: 'pointer', cursor: 'pointer',
background: 'var(--affine-hover-color)', background: 'var(--affine-hover-color)',
@@ -62,6 +64,7 @@ export const switchStyle = style({
export const filterTypeStyle = style({ export const filterTypeStyle = style({
fontSize: 'var(--affine-font-sm)', fontSize: 'var(--affine-font-sm)',
display: 'flex', display: 'flex',
alignItems: 'center',
padding: '2px 4px', padding: '2px 4px',
transition: 'all 0.15s ease-in-out', transition: 'all 0.15s ease-in-out',
marginRight: '6px', marginRight: '6px',
@@ -76,5 +79,6 @@ export const filterTypeIconStyle = style({
marginRight: '6px', marginRight: '6px',
padding: '1px 0', padding: '1px 0',
display: 'flex', display: 'flex',
alignItems: 'center',
color: 'var(--affine-icon-color)', color: 'var(--affine-icon-color)',
}); });
@@ -42,9 +42,15 @@ export const createDefaultFilter = (
} }
return { return {
type: 'filter', type: 'filter',
left: { type: 'ref', name: variable.name }, left: {
type: 'ref',
name: variable.name,
},
funcName: data.name, funcName: data.name,
args: data.defaultArgs().map(value => ({ type: 'literal', value })), args: data.defaultArgs().map(value => ({
type: 'literal',
value,
})),
}; };
}; };
@@ -137,7 +143,10 @@ filterMatcher.register(
); );
filterMatcher.register( filterMatcher.register(
tFunction({ args: [tDate.create(), tDate.create()], rt: tBoolean.create() }), tFunction({
args: [tDate.create(), tDate.create()],
rt: tBoolean.create(),
}),
{ {
name: 'after', name: 'after',
defaultArgs: () => { defaultArgs: () => {
@@ -153,7 +162,10 @@ filterMatcher.register(
); );
filterMatcher.register( filterMatcher.register(
tFunction({ args: [tDate.create(), tDate.create()], rt: tBoolean.create() }), tFunction({
args: [tDate.create(), tDate.create()],
rt: tBoolean.create(),
}),
{ {
name: 'before', name: 'before',
defaultArgs: () => [dayjs().endOf('day').valueOf()], defaultArgs: () => [dayjs().endOf('day').valueOf()],
@@ -165,31 +177,35 @@ filterMatcher.register(
}, },
} }
); );
const safeArray = (arr: unknown): LiteralValue[] => {
return Array.isArray(arr) ? arr : [];
};
filterMatcher.register( filterMatcher.register(
tFunction({ args: [tArray(tTag.create())], rt: tBoolean.create() }), tFunction({
args: [tArray(tTag.create())],
rt: tBoolean.create(),
}),
{ {
name: 'is not empty', name: 'is not empty',
defaultArgs: () => [], defaultArgs: () => [],
impl: tags => { impl: tags => {
if (Array.isArray(tags)) { const safeTags = safeArray(tags);
return tags.length > 0; return safeTags.length > 0;
}
return true;
}, },
} }
); );
filterMatcher.register( filterMatcher.register(
tFunction({ args: [tArray(tTag.create())], rt: tBoolean.create() }), tFunction({
args: [tArray(tTag.create())],
rt: tBoolean.create(),
}),
{ {
name: 'is empty', name: 'is empty',
defaultArgs: () => [], defaultArgs: () => [],
impl: tags => { impl: tags => {
if (Array.isArray(tags)) { const safeTags = safeArray(tags);
return tags.length == 0; return safeTags.length == 0;
}
return true;
}, },
} }
); );
@@ -204,10 +220,11 @@ filterMatcher.register(
name: 'contains all', name: 'contains all',
defaultArgs: () => [], defaultArgs: () => [],
impl: (tags, target) => { impl: (tags, target) => {
if (Array.isArray(tags) && Array.isArray(target)) { if (!Array.isArray(target)) {
return target.every(id => tags.includes(id)); return true;
} }
return true; const safeTags = safeArray(tags);
return target.every(id => safeTags.includes(id));
}, },
} }
); );
@@ -222,10 +239,11 @@ filterMatcher.register(
name: 'contains one of', name: 'contains one of',
defaultArgs: () => [], defaultArgs: () => [],
impl: (tags, target) => { impl: (tags, target) => {
if (Array.isArray(tags) && Array.isArray(target)) { if (!Array.isArray(target)) {
return target.some(id => tags.includes(id)); return true;
} }
return true; const safeTags = safeArray(tags);
return target.some(id => safeTags.includes(id));
}, },
} }
); );
@@ -240,10 +258,11 @@ filterMatcher.register(
name: 'does not contains all', name: 'does not contains all',
defaultArgs: () => [], defaultArgs: () => [],
impl: (tags, target) => { impl: (tags, target) => {
if (Array.isArray(tags) && Array.isArray(target)) { if (!Array.isArray(target)) {
return !target.every(id => tags.includes(id)); return true;
} }
return true; const safeTags = safeArray(tags);
return !target.every(id => safeTags.includes(id));
}, },
} }
); );
@@ -258,10 +277,11 @@ filterMatcher.register(
name: 'does not contains one of', name: 'does not contains one of',
defaultArgs: () => [], defaultArgs: () => [],
impl: (tags, target) => { impl: (tags, target) => {
if (Array.isArray(tags) && Array.isArray(target)) { if (!Array.isArray(target)) {
return !target.some(id => tags.includes(id)); return true;
} }
return true; const safeTags = safeArray(tags);
return !target.some(id => safeTags.includes(id));
}, },
} }
); );
@@ -1,19 +1,25 @@
import { useState } from 'react'; import { useState } from 'react';
type SorterConfig<T> = { type SorterConfig<
T extends Record<string | number | symbol, unknown> = Record<
string | number | symbol,
unknown
>,
> = {
data: T[]; data: T[];
key: keyof T; key: keyof T;
order: 'asc' | 'desc' | 'none'; order: 'asc' | 'desc' | 'none';
sortingFn?: (
ctx: {
key: keyof T;
order: 'asc' | 'desc';
},
a: T,
b: T
) => number;
}; };
const defaultSortingFn = <T extends Record<keyof any, unknown>>( const defaultSortingFn: SorterConfig['sortingFn'] = (ctx, a, b) => {
ctx: {
key: keyof T;
order: 'asc' | 'desc';
},
a: T,
b: T
) => {
const valA = a[ctx.key]; const valA = a[ctx.key];
const valB = b[ctx.key]; const valB = b[ctx.key];
const revert = ctx.order === 'desc'; const revert = ctx.order === 'desc';
@@ -33,6 +39,10 @@ const defaultSortingFn = <T extends Record<keyof any, unknown>>(
if (!valB) { if (!valB) {
return 1 * revertSymbol; return 1 * revertSymbol;
} }
if (Array.isArray(valA) && Array.isArray(valB)) {
return (valA.length - valB.length) * revertSymbol;
}
console.warn( console.warn(
'Unsupported sorting type! Please use custom sorting function.', 'Unsupported sorting type! Please use custom sorting function.',
valA, valA,
@@ -43,6 +53,7 @@ const defaultSortingFn = <T extends Record<keyof any, unknown>>(
export const useSorter = <T extends Record<keyof any, unknown>>({ export const useSorter = <T extends Record<keyof any, unknown>>({
data, data,
sortingFn = defaultSortingFn,
...defaultSorter ...defaultSorter
}: SorterConfig<T> & { order: 'asc' | 'desc' }) => { }: SorterConfig<T> & { order: 'asc' | 'desc' }) => {
const [sorter, setSorter] = useState<Omit<SorterConfig<T>, 'data'>>({ const [sorter, setSorter] = useState<Omit<SorterConfig<T>, 'data'>>({
@@ -60,9 +71,8 @@ export const useSorter = <T extends Record<keyof any, unknown>>({
key: sorter.key, key: sorter.key,
order: sorter.order, order: sorter.order,
}; };
// TODO supports custom sorting function const compareFn = (a: T, b: T) => sortingFn(sortCtx, a, b);
const sortingFn = (a: T, b: T) => defaultSortingFn(sortCtx, a, b); const sortedData = data.sort(compareFn);
const sortedData = data.sort(sortingFn);
const shiftOrder = (key?: keyof T) => { const shiftOrder = (key?: keyof T) => {
const orders = ['asc', 'desc', 'none'] as const; const orders = ['asc', 'desc', 'none'] as const;
@@ -22,22 +22,15 @@ import {
import { FilterList } from '../filter'; import { FilterList } from '../filter';
import * as styles from './collection-list.css'; import * as styles from './collection-list.css';
type CreateCollectionProps = { interface EditCollectionModelProps {
init?: Collection;
title?: string; title?: string;
init: Collection; open: boolean;
onConfirm: (collection: Collection) => void;
onConfirmText?: string;
getPageInfo: GetPageInfoById; getPageInfo: GetPageInfoById;
propertiesMeta: PropertiesMeta; propertiesMeta: PropertiesMeta;
}; onClose: () => void;
onConfirm: (view: Collection) => Promise<void>;
type SaveCollectionButtonProps = { }
onConfirm: (collection: Collection) => Promise<void>;
getPageInfo: GetPageInfoById;
propertiesMeta: PropertiesMeta;
filterList: Filter[];
workspaceId: string;
};
export const EditCollectionModel = ({ export const EditCollectionModel = ({
init, init,
@@ -47,15 +40,7 @@ export const EditCollectionModel = ({
getPageInfo, getPageInfo,
propertiesMeta, propertiesMeta,
title, title,
}: { }: EditCollectionModelProps) => {
init?: Collection;
onConfirm: (view: Collection) => Promise<void>;
open: boolean;
onClose: () => void;
title?: string;
getPageInfo: GetPageInfoById;
propertiesMeta: PropertiesMeta;
}) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const onConfirmOnCollection = useCallback( const onConfirmOnCollection = useCallback(
(view: Collection) => { (view: Collection) => {
@@ -95,47 +80,46 @@ export const EditCollectionModel = ({
); );
}; };
const Page = ({ interface PageProps {
id,
onClick,
getPageInfo,
}: {
id: string; id: string;
onClick: (id: string) => void;
getPageInfo: GetPageInfoById; getPageInfo: GetPageInfoById;
}) => { onClick: (id: string) => void;
}
const Page = ({ id, onClick, getPageInfo }: PageProps) => {
const page = getPageInfo(id); const page = getPageInfo(id);
if (!page) { const handleClick = useCallback(() => onClick(id), [id, onClick]);
return null;
}
const icon = page.isEdgeless ? (
<EdgelessIcon
style={{
width: 17.5,
height: 17.5,
}}
/>
) : (
<PageIcon
style={{
width: 17.5,
height: 17.5,
}}
/>
);
const click = () => {
onClick(id);
};
return ( return (
<div className={styles.pageContainer}> <>
<div className={styles.pageIcon}>{icon}</div> {page ? (
<div className={styles.pageTitle}>{page.title}</div> <div className={styles.pageContainer}>
<div onClick={click} className={styles.deleteIcon}> <div className={styles.pageIcon}>
<RemoveIcon /> {page.isEdgeless ? (
</div> <EdgelessIcon style={{ width: 17.5, height: 17.5 }} />
</div> ) : (
<PageIcon style={{ width: 17.5, height: 17.5 }} />
)}
</div>
<div className={styles.pageTitle}>{page.title}</div>
<div onClick={handleClick} className={styles.deleteIcon}>
<RemoveIcon />
</div>
</div>
) : null}
</>
); );
}; };
interface EditCollectionProps {
title?: string;
onConfirmText?: string;
init: Collection;
getPageInfo: GetPageInfoById;
propertiesMeta: PropertiesMeta;
onCancel: () => void;
onConfirm: (collection: Collection) => void;
}
export const EditCollection = ({ export const EditCollection = ({
title, title,
init, init,
@@ -144,9 +128,7 @@ export const EditCollection = ({
onConfirmText, onConfirmText,
getPageInfo, getPageInfo,
propertiesMeta, propertiesMeta,
}: CreateCollectionProps & { }: EditCollectionProps) => {
onCancel: () => void;
}) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [value, onChange] = useState<Collection>(init); const [value, onChange] = useState<Collection>(init);
const removeFromExcludeList = useCallback( const removeFromExcludeList = useCallback(
@@ -223,13 +205,8 @@ export const EditCollection = ({
<FilterList <FilterList
propertiesMeta={propertiesMeta} propertiesMeta={propertiesMeta}
value={value.filterList} value={value.filterList}
onChange={list => onChange={filterList => onChange({ ...value, filterList })}
onChange({ />
...value,
filterList: list,
})
}
></FilterList>
{value.allowList ? ( {value.allowList ? (
<div className={styles.allowList}> <div className={styles.allowList}>
<div className={styles.allowTitle}>With follow pages:</div> <div className={styles.allowTitle}>With follow pages:</div>
@@ -253,12 +230,7 @@ export const EditCollection = ({
data-testid="input-collection-title" data-testid="input-collection-title"
placeholder={t['Untitled Collection']()} placeholder={t['Untitled Collection']()}
defaultValue={value.name} defaultValue={value.name}
onChange={text => onChange={name => onChange({ ...value, name })}
onChange({
...value,
name: text,
})
}
/> />
</div> </div>
</ScrollableContainer> </ScrollableContainer>
@@ -288,6 +260,15 @@ export const EditCollection = ({
</div> </div>
); );
}; };
interface SaveCollectionButtonProps {
getPageInfo: GetPageInfoById;
propertiesMeta: PropertiesMeta;
filterList: Filter[];
workspaceId: string;
onConfirm: (collection: Collection) => Promise<void>;
}
export const SaveCollectionButton = ({ export const SaveCollectionButton = ({
onConfirm, onConfirm,
getPageInfo, getPageInfo,
@@ -316,10 +297,10 @@ export const SaveCollectionButton = ({
size="large" size="large"
style={{ padding: '7px 8px' }} style={{ padding: '7px 8px' }}
> >
{t['Save As New Collection']()} {t['Save as New Collection']()}
</Button> </Button>
<EditCollectionModel <EditCollectionModel
title={t['Save As New Collection']()} title={t['Save as New Collection']()}
propertiesMeta={propertiesMeta} propertiesMeta={propertiesMeta}
init={init} init={init}
onConfirm={onConfirm} onConfirm={onConfirm}
@@ -10,9 +10,13 @@ import {
useSensor, useSensor,
useSensors, useSensors,
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { SortableContext, useSortable } from '@dnd-kit/sortable'; import {
restrictToParentElement,
restrictToVerticalAxis,
} from '@dnd-kit/modifiers';
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import { useMemo } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { WorkspaceCard } from '../../components/card/workspace-card'; import { WorkspaceCard } from '../../components/card/workspace-card';
import { workspaceItemStyle } from './index.css'; import { workspaceItemStyle } from './index.css';
@@ -31,18 +35,20 @@ interface SortableWorkspaceItemProps extends Omit<WorkspaceListProps, 'items'> {
} }
const SortableWorkspaceItem = (props: SortableWorkspaceItemProps) => { const SortableWorkspaceItem = (props: SortableWorkspaceItemProps) => {
const { setNodeRef, attributes, listeners, transform } = useSortable({ const { setNodeRef, attributes, listeners, transform, transition } =
id: props.item.id, useSortable({
}); id: props.item.id,
});
const style: CSSProperties = useMemo( const style: CSSProperties = useMemo(
() => ({ () => ({
transform: transform transform: transform
? `translate3d(${transform.x}px, ${transform.y}px, 0)` ? `translate3d(${transform.x}px, ${transform.y}px, 0)`
: undefined, : undefined,
transition,
pointerEvents: props.disabled ? 'none' : undefined, pointerEvents: props.disabled ? 'none' : undefined,
opacity: props.disabled ? 0.6 : undefined, opacity: props.disabled ? 0.6 : undefined,
}), }),
[props.disabled, transform] [props.disabled, transform, transition]
); );
return ( return (
<div <div
@@ -63,6 +69,8 @@ const SortableWorkspaceItem = (props: SortableWorkspaceItemProps) => {
); );
}; };
const modifiers = [restrictToParentElement, restrictToVerticalAxis];
export const WorkspaceList = (props: WorkspaceListProps) => { export const WorkspaceList = (props: WorkspaceListProps) => {
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor, { useSensor(PointerSensor, {
@@ -71,10 +79,33 @@ export const WorkspaceList = (props: WorkspaceListProps) => {
}, },
}) })
); );
const workspaceList = props.items;
const [optimisticList, setOptimisticList] = useState(workspaceList);
useEffect(() => {
setOptimisticList(workspaceList);
}, [workspaceList]);
const onDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
setOptimisticList(workspaceList => {
const oldIndex = workspaceList.findIndex(w => w.id === active.id);
const newIndex = workspaceList.findIndex(w => w.id === over?.id);
const newList = arrayMove(workspaceList, oldIndex, newIndex);
return newList;
});
props.onDragEnd(event);
}
},
[props]
);
return ( return (
<DndContext sensors={sensors} onDragEnd={props.onDragEnd}> <DndContext sensors={sensors} onDragEnd={onDragEnd} modifiers={modifiers}>
<SortableContext items={props.items}> <SortableContext items={optimisticList}>
{props.items.map(item => ( {optimisticList.map(item => (
<SortableWorkspaceItem {...props} item={item} key={item.id} /> <SortableWorkspaceItem {...props} item={item} key={item.id} />
))} ))}
</SortableContext> </SortableContext>
+19 -19
View File
@@ -1,5 +1,6 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button'; import { Button } from '@toeverything/components/button';
import { useCallback, useMemo } from 'react';
import type { ModalProps } from '../modal'; import type { ModalProps } from '../modal';
import { Modal, ModalCloseButton } from '../modal'; import { Modal, ModalCloseButton } from '../modal';
@@ -39,33 +40,36 @@ export const Confirm = ({
confirmButtonTestId = '', confirmButtonTestId = '',
}: ConfirmProps) => { }: ConfirmProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const cancelText_ = useMemo<string>(() => {
return cancelText === 'Cancel' ? t['Cancel']() : cancelText;
}, [cancelText, t]);
const handleCancel = useCallback(() => {
onCancel?.();
}, [onCancel]);
const handleConfirm = useCallback(() => {
onConfirm?.();
}, [onConfirm]);
return ( return (
<Modal open={open} disablePortal={false}> <Modal open={open} disablePortal={false}>
<StyledModalWrapper> <StyledModalWrapper>
<ModalCloseButton <ModalCloseButton onClick={handleCancel} />
onClick={() => {
onCancel?.();
}}
/>
<StyledConfirmTitle>{title}</StyledConfirmTitle> <StyledConfirmTitle>{title}</StyledConfirmTitle>
<StyledConfirmContent>{content}</StyledConfirmContent> <StyledConfirmContent>{content}</StyledConfirmContent>
{buttonDirection === 'row' ? ( {buttonDirection === 'row' ? (
<StyledRowButtonWrapper> <StyledRowButtonWrapper>
<Button <Button
onClick={() => { onClick={handleCancel}
onCancel?.();
}}
size="large" size="large"
style={{ marginRight: '24px' }} style={{ marginRight: '24px' }}
data-testid={cancelButtonTestId} data-testid={cancelButtonTestId}
> >
{cancelText === 'Cancel' ? t['Cancel']() : cancelText} {cancelText_}
</Button> </Button>
<Button <Button
type={confirmType} type={confirmType}
onClick={() => { onClick={handleConfirm}
onConfirm?.();
}}
size="large" size="large"
data-testid={confirmButtonTestId} data-testid={confirmButtonTestId}
> >
@@ -76,18 +80,14 @@ export const Confirm = ({
<StyledColumnButtonWrapper> <StyledColumnButtonWrapper>
<Button <Button
type={confirmType} type={confirmType}
onClick={() => { onClick={handleConfirm}
onConfirm?.();
}}
style={{ width: '284px', height: '38px', textAlign: 'center' }} style={{ width: '284px', height: '38px', textAlign: 'center' }}
data-testid={confirmButtonTestId} data-testid={confirmButtonTestId}
> >
{confirmText} {confirmText}
</Button> </Button>
<Button <Button
onClick={() => { onClick={handleCancel}
onCancel?.();
}}
style={{ style={{
marginTop: '16px', marginTop: '16px',
width: '284px', width: '284px',
@@ -96,7 +96,7 @@ export const Confirm = ({
}} }}
data-testid={cancelButtonTestId} data-testid={cancelButtonTestId}
> >
{cancelText === 'Cancel' ? t['Cancel']() : cancelText} {cancelText_}
</Button> </Button>
</StyledColumnButtonWrapper> </StyledColumnButtonWrapper>
)} )}
+1
View File
@@ -5,6 +5,7 @@
"sourceRoot": "packages/i18n/src", "sourceRoot": "packages/i18n/src",
"targets": { "targets": {
"build": { "build": {
"outputs": ["{workspaceRoot}/src/i18n-generated.ts"],
"executor": "nx:run-commands", "executor": "nx:run-commands",
"options": { "options": {
"cwd": "packages/i18n", "cwd": "packages/i18n",
+1 -1
View File
@@ -237,7 +237,7 @@
"Retain cached cloud data": "Retain cached cloud data", "Retain cached cloud data": "Retain cached cloud data",
"Retain local cached data": "Retain local cached data", "Retain local cached data": "Retain local cached data",
"Save": "Save", "Save": "Save",
"Save As New Collection": "Save As New Collection", "Save as New Collection": "Save as New Collection",
"Saved then enable AFFiNE Cloud": "All changes are saved locally, click to enable AFFiNE Cloud.", "Saved then enable AFFiNE Cloud": "All changes are saved locally, click to enable AFFiNE Cloud.",
"Select": "Select", "Select": "Select",
"Select All": "Select All", "Select All": "Select All",
+1 -1
View File
@@ -237,7 +237,7 @@
"Retain cached cloud data": "Conserver les données mises en cache dans le cloud", "Retain cached cloud data": "Conserver les données mises en cache dans le cloud",
"Retain local cached data": "Conserver les données du cache local", "Retain local cached data": "Conserver les données du cache local",
"Save": "Enregistrer", "Save": "Enregistrer",
"Save As New Collection": "Enregistrer en tant que nouvelle collection", "Save as New Collection": "Enregistrer en tant que nouvelle collection",
"Saved then enable AFFiNE Cloud": "Toutes les modifications sont sauvegardées localement, cliquez ici pour activer la sauvegarde AFFiNE Cloud", "Saved then enable AFFiNE Cloud": "Toutes les modifications sont sauvegardées localement, cliquez ici pour activer la sauvegarde AFFiNE Cloud",
"Select": "Sélectionner ", "Select": "Sélectionner ",
"Select All": "Sélectionner l'ensemble", "Select All": "Sélectionner l'ensemble",
+1 -1
View File
@@ -237,7 +237,7 @@
"Retain cached cloud data": "保留緩衝的雲數據", "Retain cached cloud data": "保留緩衝的雲數據",
"Retain local cached data": "保留本機緩衝數據", "Retain local cached data": "保留本機緩衝數據",
"Save": "保存", "Save": "保存",
"Save As New Collection": "另存為新收藏集", "Save as New Collection": "另存為新收藏集",
"Saved then enable AFFiNE Cloud": "所有修訂已儲存在本機,點按以啟用 AFFiNE Cloud。", "Saved then enable AFFiNE Cloud": "所有修訂已儲存在本機,點按以啟用 AFFiNE Cloud。",
"Select": "選擇", "Select": "選擇",
"Select All": "全選", "Select All": "全選",
+1 -1
View File
@@ -10,7 +10,7 @@
"tsconfig.json" "tsconfig.json"
], ],
"dependencies": { "dependencies": {
"@endo/static-module-record": "^0.8.1", "@endo/static-module-record": "^0.8.2",
"@swc/core": "^1.3.77", "@swc/core": "^1.3.77",
"@toeverything/infra": "workspace:^", "@toeverything/infra": "workspace:^",
"@vanilla-extract/rollup-plugin": "^1.2.2", "@vanilla-extract/rollup-plugin": "^1.2.2",
+1 -1
View File
@@ -215,7 +215,7 @@
} }
}, },
{ {
"insert": " feely." "insert": " freely."
} }
], ],
"prop:checked": false "prop:checked": false
+8 -1
View File
@@ -50,10 +50,17 @@ export const HeaderItem = ({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const pushLayout = useSetAtom(pushLayoutAtom); const pushLayout = useSetAtom(pushLayoutAtom);
const deleteLayout = useSetAtom(deleteLayoutAtom); const deleteLayout = useSetAtom(deleteLayoutAtom);
const [container, setContainer] = useState<HTMLButtonElement | null>(null);
return ( return (
<Tooltip content={`${open ? 'Collapse' : 'Expand'} table of contents`}> <Tooltip
content={`${open ? 'Collapse' : 'Expand'} table of contents`}
portalOptions={{
container,
}}
>
<IconButton <IconButton
ref={setContainer}
onClick={useCallback(() => { onClick={useCallback(() => {
if (!open) { if (!open) {
setOpen(true); setOpen(true);
+31 -17
View File
@@ -143,6 +143,7 @@ __metadata:
"@blocksuite/lit": 0.0.0-20230830111255-92eab248-nightly "@blocksuite/lit": 0.0.0-20230830111255-92eab248-nightly
"@blocksuite/store": 0.0.0-20230830111255-92eab248-nightly "@blocksuite/store": 0.0.0-20230830111255-92eab248-nightly
"@dnd-kit/core": ^6.0.8 "@dnd-kit/core": ^6.0.8
"@dnd-kit/modifiers": ^6.0.1
"@dnd-kit/sortable": ^7.0.2 "@dnd-kit/sortable": ^7.0.2
"@emotion/cache": ^11.11.0 "@emotion/cache": ^11.11.0
"@emotion/react": ^11.11.1 "@emotion/react": ^11.11.1
@@ -274,7 +275,7 @@ __metadata:
react-resizable-panels: ^0.0.55 react-resizable-panels: ^0.0.55
react-router-dom: ^6.15.0 react-router-dom: ^6.15.0
rxjs: ^7.8.1 rxjs: ^7.8.1
ses: ^0.18.7 ses: ^0.18.8
source-map-loader: ^4.0.1 source-map-loader: ^4.0.1
style-loader: ^3.3.3 style-loader: ^3.3.3
swc-loader: ^0.2.3 swc-loader: ^0.2.3
@@ -573,7 +574,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@affine/plugin-cli@workspace:packages/plugin-cli" resolution: "@affine/plugin-cli@workspace:packages/plugin-cli"
dependencies: dependencies:
"@endo/static-module-record": ^0.8.1 "@endo/static-module-record": ^0.8.2
"@swc/core": ^1.3.77 "@swc/core": ^1.3.77
"@toeverything/infra": "workspace:^" "@toeverything/infra": "workspace:^"
"@vanilla-extract/rollup-plugin": ^1.2.2 "@vanilla-extract/rollup-plugin": ^1.2.2
@@ -723,7 +724,7 @@ __metadata:
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0 react-dom: 18.2.0
serve: ^14.2.0 serve: ^14.2.0
ses: ^0.18.7 ses: ^0.18.8
storybook: ^7.3.1 storybook: ^7.3.1
storybook-addon-react-router-v6: ^2.0.4 storybook-addon-react-router-v6: ^2.0.4
storybook-dark-mode: ^3.0.1 storybook-dark-mode: ^3.0.1
@@ -3861,6 +3862,19 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@dnd-kit/modifiers@npm:^6.0.1":
version: 6.0.1
resolution: "@dnd-kit/modifiers@npm:6.0.1"
dependencies:
"@dnd-kit/utilities": ^3.2.1
tslib: ^2.0.0
peerDependencies:
"@dnd-kit/core": ^6.0.6
react: ">=16.8.0"
checksum: cd31715aac81baa2398558dc7c877a483d1c4c41cdb2f466557fdc41bb742db5cd1b34b3b84332b94ac4a2835e7e5a0e28c64e621936e976399788f1dd029ea4
languageName: node
linkType: hard
"@dnd-kit/sortable@npm:^7.0.2": "@dnd-kit/sortable@npm:^7.0.2":
version: 7.0.2 version: 7.0.2
resolution: "@dnd-kit/sortable@npm:7.0.2" resolution: "@dnd-kit/sortable@npm:7.0.2"
@@ -4411,23 +4425,23 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@endo/env-options@npm:^0.1.3": "@endo/env-options@npm:^0.1.4":
version: 0.1.3 version: 0.1.4
resolution: "@endo/env-options@npm:0.1.3" resolution: "@endo/env-options@npm:0.1.4"
checksum: da8c66865d4d30b0053a00960657dc36f022975a888f0dd6a2f6bb37b9fe731f45a02a2cf263d93b1a40fcb37b25f8ba7076cb8af9e93fd95f496365d9382930 checksum: 6099f0a6b700a60bee7b226aa2a39bb5748e22f25e9606d70e5a66a8e62cbd8c972b0fe578735a658f80bf2ebece62e28c20aa3f16417cbfe6c19a8689966dd3
languageName: node languageName: node
linkType: hard linkType: hard
"@endo/static-module-record@npm:^0.8.1": "@endo/static-module-record@npm:^0.8.2":
version: 0.8.1 version: 0.8.2
resolution: "@endo/static-module-record@npm:0.8.1" resolution: "@endo/static-module-record@npm:0.8.2"
dependencies: dependencies:
"@agoric/babel-generator": ^7.17.6 "@agoric/babel-generator": ^7.17.6
"@babel/parser": ^7.17.3 "@babel/parser": ^7.17.3
"@babel/traverse": ^7.17.3 "@babel/traverse": ^7.17.3
"@babel/types": ^7.17.0 "@babel/types": ^7.17.0
ses: ^0.18.7 ses: ^0.18.8
checksum: c9dc18a5b8ec13008784e30aa15214a67771c325759b42d54362f891d0328b0abf4cafc7be8fa17d539f5f7ca28b9555127e22e9596f5e52d9bf7f3fa90c7963 checksum: 0e65f28e95d4ddd495de4cc7e5f0a47a70b89ba5449660158c8813121ab76aa3a8db7e6b2bb9efd3cb24833f8613a952e3a85a8af9fc9b7e4ad49d1d47a9f06c
languageName: node languageName: node
linkType: hard linkType: hard
@@ -29770,12 +29784,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ses@npm:^0.18.7": "ses@npm:^0.18.8":
version: 0.18.7 version: 0.18.8
resolution: "ses@npm:0.18.7" resolution: "ses@npm:0.18.8"
dependencies: dependencies:
"@endo/env-options": ^0.1.3 "@endo/env-options": ^0.1.4
checksum: 75ac014771d9bc1f747193c6d0f9e7d2d7700a10311ba8d805d9bc78d4c20d4ef40537f0535b1ea6abf06babf67e70f8bd37b2ad68ad54992a0c5ce842181c87 checksum: d7976d2ee218baec021c5cfdfb193d63b52bf2b6cbdbbb90c19d835915a1872b6924910f7fd42bc849eb2de78fc7bdd6e7b4667e1df3c79244cc92d4ede48aa6
languageName: node languageName: node
linkType: hard linkType: hard