mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-23 20:18:42 +08:00
fix: resolve cycle imports and prevent it by oxlint (#5103)
This commit is contained in:
@@ -205,6 +205,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'unicorn/no-unnecessary-await': 'error',
|
'unicorn/no-unnecessary-await': 'error',
|
||||||
|
'unicorn/no-useless-fallback-in-spread': 'error',
|
||||||
'sonarjs/no-all-duplicated-branches': 'error',
|
'sonarjs/no-all-duplicated-branches': 'error',
|
||||||
'sonarjs/no-element-overwrite': 'error',
|
'sonarjs/no-element-overwrite': 'error',
|
||||||
'sonarjs/no-empty-collection': 'error',
|
'sonarjs/no-empty-collection': 'error',
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Run oxlint
|
- name: Run oxlint
|
||||||
# oxlint is fast, so wrong code will fail quickly
|
# oxlint is fast, so wrong code will fail quickly
|
||||||
run: yarn dlx oxlint@latest .
|
run: yarn dlx $(node -e "console.log(require('./package.json').scripts['lint:ox'])")
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
with:
|
with:
|
||||||
@@ -58,8 +58,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git checkout .yarnrc.yml
|
git checkout .yarnrc.yml
|
||||||
yarn lint:prettier
|
yarn lint:prettier
|
||||||
- name: Run circular
|
|
||||||
run: yarn circular
|
|
||||||
- name: Run Type Check
|
- name: Run Type Check
|
||||||
run: yarn typecheck
|
run: yarn typecheck
|
||||||
|
|
||||||
|
|||||||
+1
-20
@@ -1,23 +1,4 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
# check lockfile is up to date
|
yarn lint:ox
|
||||||
yarn install --mode=skip-build --inline-builds --immutable
|
|
||||||
|
|
||||||
# build infra code
|
|
||||||
yarn -T run build:infra
|
|
||||||
|
|
||||||
# generate prisma client type
|
|
||||||
yarn workspace @affine/server prisma generate
|
|
||||||
|
|
||||||
# generate i18n
|
|
||||||
yarn i18n-codegen gen
|
|
||||||
|
|
||||||
# lint staged files
|
|
||||||
yarn exec lint-staged
|
|
||||||
|
|
||||||
# type check
|
|
||||||
yarn typecheck
|
|
||||||
|
|
||||||
# circular dependency check
|
|
||||||
yarn circular
|
|
||||||
|
|||||||
+2
-2
@@ -33,6 +33,7 @@
|
|||||||
"lint:eslint:fix": "yarn lint:eslint --fix",
|
"lint:eslint:fix": "yarn lint:eslint --fix",
|
||||||
"lint:prettier": "prettier --ignore-unknown --cache --check .",
|
"lint:prettier": "prettier --ignore-unknown --cache --check .",
|
||||||
"lint:prettier:fix": "prettier --ignore-unknown --cache --write .",
|
"lint:prettier:fix": "prettier --ignore-unknown --cache --write .",
|
||||||
|
"lint:ox": "oxlint --deny-warnings --import-plugin -D correctness -D nursery -A no-undef -A consistent-type-exports -A default -A named -A ban-ts-comment",
|
||||||
"lint": "yarn lint:eslint && yarn lint:prettier",
|
"lint": "yarn lint:eslint && yarn lint:prettier",
|
||||||
"lint:fix": "yarn lint:eslint:fix && yarn lint:prettier:fix",
|
"lint:fix": "yarn lint:eslint:fix && yarn lint:prettier:fix",
|
||||||
"test": "vitest --run",
|
"test": "vitest --run",
|
||||||
@@ -49,7 +50,6 @@
|
|||||||
"eslint --cache --fix"
|
"eslint --cache --fix"
|
||||||
],
|
],
|
||||||
"*.toml": [
|
"*.toml": [
|
||||||
"prettier --ignore-unknown --write",
|
|
||||||
"taplo format"
|
"taplo format"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -93,12 +93,12 @@
|
|||||||
"happy-dom": "^12.10.3",
|
"happy-dom": "^12.10.3",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^15.1.0",
|
"lint-staged": "^15.1.0",
|
||||||
"madge": "^6.1.0",
|
|
||||||
"msw": "^2.0.8",
|
"msw": "^2.0.8",
|
||||||
"nanoid": "^5.0.3",
|
"nanoid": "^5.0.3",
|
||||||
"nx": "^17.1.3",
|
"nx": "^17.1.3",
|
||||||
"nx-cloud": "^16.5.2",
|
"nx-cloud": "^16.5.2",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
|
"oxlint": "^0.0.18",
|
||||||
"prettier": "^3.1.0",
|
"prettier": "^3.1.0",
|
||||||
"semver": "^7.5.4",
|
"semver": "^7.5.4",
|
||||||
"serve": "^14.2.1",
|
"serve": "^14.2.1",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { Field, ObjectType, Query } from '@nestjs/graphql';
|
import { Field, ObjectType, Query } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { SERVER_FLAVOR } from '../modules';
|
export const { SERVER_FLAVOR } = process.env;
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class ServerConfigType {
|
export class ServerConfigType {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter';
|
|||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
|
|
||||||
import { GqlModule } from '../graphql.module';
|
import { GqlModule } from '../graphql.module';
|
||||||
import { ServerConfigModule } from './config';
|
import { SERVER_FLAVOR, ServerConfigModule } from './config';
|
||||||
import { DocModule } from './doc';
|
import { DocModule } from './doc';
|
||||||
import { PaymentModule } from './payment';
|
import { PaymentModule } from './payment';
|
||||||
import { SelfHostedModule } from './self-hosted';
|
import { SelfHostedModule } from './self-hosted';
|
||||||
@@ -11,8 +11,6 @@ import { SyncModule } from './sync';
|
|||||||
import { UsersModule } from './users';
|
import { UsersModule } from './users';
|
||||||
import { WorkspaceModule } from './workspaces';
|
import { WorkspaceModule } from './workspaces';
|
||||||
|
|
||||||
const { SERVER_FLAVOR } = process.env;
|
|
||||||
|
|
||||||
const BusinessModules: (Type | DynamicModule)[] = [
|
const BusinessModules: (Type | DynamicModule)[] = [
|
||||||
EventEmitterModule.forRoot({
|
EventEmitterModule.forRoot({
|
||||||
global: true,
|
global: true,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { Tag } from '@affine/env/filter';
|
import type { Tag } from '@affine/env/filter';
|
||||||
import { Trans } from '@affine/i18n';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { EdgelessIcon, PageIcon, ToggleCollapseIcon } from '@blocksuite/icons';
|
import { EdgelessIcon, PageIcon, ToggleCollapseIcon } from '@blocksuite/icons';
|
||||||
@@ -19,93 +18,8 @@ import {
|
|||||||
useAtom,
|
useAtom,
|
||||||
useAtomValue,
|
useAtomValue,
|
||||||
} from './scoped-atoms';
|
} from './scoped-atoms';
|
||||||
import type {
|
import type { PageGroupProps, PageListItemProps, PageListProps } from './types';
|
||||||
PageGroupDefinition,
|
import { shallowEqual } from './utils';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PageGroupHeader = ({ id, items, label }: PageGroupProps) => {
|
export const PageGroupHeader = ({ id, items, label }: PageGroupProps) => {
|
||||||
const [collapseState, setCollapseState] = useAtom(pageGroupCollapseStateAtom);
|
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 { selectAtom } from 'jotai/utils';
|
||||||
import { createIsolation } from 'jotai-scope';
|
import { createIsolation } from 'jotai-scope';
|
||||||
|
|
||||||
import { pagesToPageGroups } from './page-group';
|
import { pagesToPageGroups } from './pages-to-page-group';
|
||||||
import type {
|
import type {
|
||||||
PageListProps,
|
PageListProps,
|
||||||
PageMetaRecord,
|
PageMetaRecord,
|
||||||
|
|||||||
@@ -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 { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { SaveIcon } from '@blocksuite/icons';
|
|
||||||
import { Button } from '@toeverything/components/button';
|
import { Button } from '@toeverything/components/button';
|
||||||
import { Modal } from '@toeverything/components/modal';
|
import { Modal } from '@toeverything/components/modal';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import Input from '../../../ui/input';
|
import Input from '../../../ui/input';
|
||||||
@@ -127,40 +120,3 @@ export const CreateCollection = ({
|
|||||||
</div>
|
</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}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import { Button } from '@toeverything/components/button';
|
|||||||
import { Modal } from '@toeverything/components/modal';
|
import { Modal } from '@toeverything/components/modal';
|
||||||
import { type ReactNode, useCallback, useMemo, useState } from 'react';
|
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 * as styles from './edit-collection.css';
|
||||||
import { PagesMode } from './pages-mode';
|
import { PagesMode } from './pages-mode';
|
||||||
import { RulesMode } from './rules-mode';
|
import { RulesMode } from './rules-mode';
|
||||||
|
|||||||
+2
-51
@@ -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 { 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';
|
import { SelectPage } from './select-page';
|
||||||
export const useSelectPage = ({
|
export const useSelectPage = ({
|
||||||
allPageListConfig,
|
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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
+5
-6
@@ -1,8 +1,3 @@
|
|||||||
import {
|
|
||||||
type AllPageListConfig,
|
|
||||||
FilterList,
|
|
||||||
VirtualizedPageList,
|
|
||||||
} from '@affine/component/page-list';
|
|
||||||
import type { Collection } from '@affine/env/filter';
|
import type { Collection } from '@affine/env/filter';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { FilterIcon } from '@blocksuite/icons';
|
import { FilterIcon } from '@blocksuite/icons';
|
||||||
@@ -11,10 +6,14 @@ import { Menu } from '@toeverything/components/menu';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { type ReactNode, useCallback } from 'react';
|
import { type ReactNode, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { FilterList } from '../../filter/filter-list';
|
||||||
import { VariableSelect } from '../../filter/vars';
|
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 * as styles from './edit-collection.css';
|
||||||
import { useFilter, useSearch } from './hooks';
|
|
||||||
import { EmptyList } from './select-page';
|
import { EmptyList } from './select-page';
|
||||||
|
import { useFilter } from './use-filter';
|
||||||
|
import { useSearch } from './use-search';
|
||||||
|
|
||||||
export const PagesMode = ({
|
export const PagesMode = ({
|
||||||
switchMode,
|
switchMode,
|
||||||
|
|||||||
+4
-2
@@ -6,13 +6,15 @@ import { Menu } from '@toeverything/components/menu';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { VirtualizedPageList } from '../..';
|
|
||||||
import { FilterList } from '../../filter';
|
import { FilterList } from '../../filter';
|
||||||
import { VariableSelect } from '../../filter/vars';
|
import { VariableSelect } from '../../filter/vars';
|
||||||
|
import { VirtualizedPageList } from '../../virtualized-page-list';
|
||||||
import { AffineShapeIcon } from '../affine-shape';
|
import { AffineShapeIcon } from '../affine-shape';
|
||||||
import type { AllPageListConfig } from './edit-collection';
|
import type { AllPageListConfig } from './edit-collection';
|
||||||
import * as styles from './edit-collection.css';
|
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 = ({
|
export const SelectPage = ({
|
||||||
allPageListConfig,
|
allPageListConfig,
|
||||||
init,
|
init,
|
||||||
|
|||||||
+40
@@ -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);
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
+13
@@ -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 './collection-operations';
|
||||||
export * from './create-collection';
|
export * from './create-collection';
|
||||||
export * from './edit-collection/edit-collection';
|
export * from './edit-collection/edit-collection';
|
||||||
|
export * from './save-as-collection-button';
|
||||||
export * from './use-edit-collection';
|
export * from './use-edit-collection';
|
||||||
|
|||||||
+46
@@ -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 type { Collection } from '@affine/env/filter';
|
||||||
import { useCallback, useState } from 'react';
|
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) => {
|
export const useEditCollection = (config: AllPageListConfig) => {
|
||||||
const [data, setData] = useState<{
|
const [data, setData] = useState<{
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const Skeleton = ({
|
|||||||
const style = {
|
const style = {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
...(_style || {}),
|
..._style,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export const MANUALLY_STOP = 'manually-stop';
|
||||||
|
|
||||||
|
export enum SyncEngineStep {
|
||||||
|
Stopped = 0,
|
||||||
|
Syncing = 1,
|
||||||
|
Synced = 2,
|
||||||
|
}
|
||||||
@@ -3,16 +3,9 @@ import { Slot } from '@blocksuite/global/utils';
|
|||||||
import type { Doc } from 'yjs';
|
import type { Doc } from 'yjs';
|
||||||
|
|
||||||
import type { Storage } from '../storage';
|
import type { Storage } from '../storage';
|
||||||
|
import { MANUALLY_STOP, SyncEngineStep } from './consts';
|
||||||
import { SyncPeer, type SyncPeerStatus, SyncPeerStep } from './peer';
|
import { SyncPeer, type SyncPeerStatus, SyncPeerStep } from './peer';
|
||||||
|
|
||||||
export const MANUALLY_STOP = 'manually-stop';
|
|
||||||
|
|
||||||
export enum SyncEngineStep {
|
|
||||||
Stopped = 0,
|
|
||||||
Syncing = 1,
|
|
||||||
Synced = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SyncEngineStatus {
|
export interface SyncEngineStatus {
|
||||||
step: SyncEngineStep;
|
step: SyncEngineStep;
|
||||||
local: SyncPeerStatus | null;
|
local: SyncPeerStatus | null;
|
||||||
|
|||||||
@@ -14,5 +14,6 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from './consts';
|
||||||
export * from './engine';
|
export * from './engine';
|
||||||
export * from './peer';
|
export * from './peer';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { applyUpdate, encodeStateAsUpdate, encodeStateVector } from 'yjs';
|
|||||||
import { mergeUpdates, type Storage } from '../storage';
|
import { mergeUpdates, type Storage } from '../storage';
|
||||||
import { AsyncQueue } from '../utils/async-queue';
|
import { AsyncQueue } from '../utils/async-queue';
|
||||||
import { throwIfAborted } from '../utils/throw-if-aborted';
|
import { throwIfAborted } from '../utils/throw-if-aborted';
|
||||||
import { MANUALLY_STOP } from './engine';
|
import { MANUALLY_STOP } from './consts';
|
||||||
|
|
||||||
export enum SyncPeerStep {
|
export enum SyncPeerStep {
|
||||||
Stopped = 0,
|
Stopped = 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user