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