feat(core): add default filter rules for all docs (#12197)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a filter to exclude empty journals from document collections.
  - Added a live-updating list of non-trashed document IDs for improved document management.

- **Improvements**
  - Default filters in the document explorer have been updated to show all items, including trashed ones, unless filters are applied.
  - Enhanced filtering in workspace views to automatically exclude empty journals and trashed documents.
  - Increased the responsiveness of grouped document updates for a smoother user experience.

- **Bug Fixes**
  - Removed unnecessary debug output from the document synchronization, member search, session management, and member selector components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
EYHN
2025-05-12 02:05:11 +00:00
parent 6fb7f51ea2
commit a6394b7988
11 changed files with 71 additions and 16 deletions
@@ -27,14 +27,7 @@ export const createDocExplorerContext = () =>
selectMode$: new LiveData<boolean>(false),
selectedDocIds$: new LiveData<string[]>([]),
prevCheckAnchorId$: new LiveData<string | null>(null),
filters$: new LiveData<ExplorerPreference['filters']>([
{
type: 'system',
key: 'trash',
value: 'false',
method: 'is',
},
]),
filters$: new LiveData<ExplorerPreference['filters']>([]),
groupBy$: new LiveData<ExplorerPreference['groupBy']>(undefined),
orderBy$: new LiveData<ExplorerPreference['orderBy']>(undefined),
displayProperties$: new LiveData<ExplorerPreference['displayProperties']>(
@@ -90,7 +90,6 @@ export const MemberSelector = ({
if (value.length > 0) {
setFocusedInlineIndex(selected.length);
}
console.log('onInputChange', value);
debouncedSearch(value.trim());
},
[debouncedSearch, selected.length]
@@ -143,7 +143,25 @@ export const AllPage = () => {
const collectionRulesService = useService(CollectionRulesService);
useEffect(() => {
const subscription = collectionRulesService
.watch(filters ?? [], groupBy, orderBy)
.watch(
[
...(filters ?? []),
{
type: 'system',
key: 'empty-journal',
method: 'is',
value: 'false',
},
{
type: 'system',
key: 'trash',
method: 'is',
value: 'false',
},
],
groupBy,
orderBy
)
.subscribe({
next: result => {
explorerContextValue.groups$.next(result.groups);
@@ -141,7 +141,6 @@ export class AuthSession extends Entity {
async updateLabel(label: string) {
await this.store.updateLabel(label);
console.log('updateLabel');
await this.waitForRevalidation();
}
@@ -106,7 +106,6 @@ export class DocCreatedByUpdatedBySyncService extends Service {
.pipe(
map(allDocsCreatedBy => {
let missingCreatedBy = false;
console.log(allDocsCreatedBy);
for (const createdBy of allDocsCreatedBy.values()) {
if (!createdBy) {
missingCreatedBy = true;
@@ -0,0 +1,39 @@
import type { DocsService } from '@affine/core/modules/doc';
import { Service } from '@toeverything/infra';
import { combineLatest, map, type Observable } from 'rxjs';
import type { FilterProvider } from '../../provider';
import type { FilterParams } from '../../types';
export class EmptyJournalFilterProvider
extends Service
implements FilterProvider
{
constructor(private readonly docsService: DocsService) {
super();
}
filter$(params: FilterParams): Observable<Set<string>> {
return combineLatest([
this.docsService.allDocsUpdatedDate$(),
this.docsService.propertyValues$('journal'),
]).pipe(
map(([updatedAts, journalValues]) => {
const match = new Set<string>();
for (const { id, updatedDate } of updatedAts) {
const isJournal = journalValues.get(id);
const isEmptyJournal = updatedDate === undefined && isJournal;
if (
(params.value === 'false' && !isEmptyJournal) ||
(params.value === 'true' && isEmptyJournal)
) {
match.add(id);
}
}
return match;
})
);
}
}
@@ -9,6 +9,7 @@ import { CreatedAtFilterProvider } from './impls/filters/created-at';
import { CreatedByFilterProvider } from './impls/filters/created-by';
import { DatePropertyFilterProvider } from './impls/filters/date';
import { DocPrimaryModeFilterProvider } from './impls/filters/doc-primary-mode';
import { EmptyJournalFilterProvider } from './impls/filters/empty-journal';
import { JournalFilterProvider } from './impls/filters/journal';
import { PropertyFilterProvider } from './impls/filters/property';
import { SystemFilterProvider } from './impls/filters/system';
@@ -114,6 +115,9 @@ export function configureCollectionRulesModule(framework: Framework) {
.impl(FilterProvider('system:updatedBy'), UpdatedByFilterProvider, [
DocsService,
])
.impl(FilterProvider('system:empty-journal'), EmptyJournalFilterProvider, [
DocsService,
])
// --------------- Group By ---------------
.impl(GroupByProvider('system'), SystemGroupByProvider)
.impl(GroupByProvider('property'), PropertyGroupByProvider, [
@@ -159,7 +159,7 @@ export class CollectionRulesService extends Service {
}[];
filterErrors: any[];
}> = grouped$.pipe(
throttleTime(500), // throttle the results to avoid too many re-renders
throttleTime(300, undefined, { leading: false, trailing: true }), // throttle the results to avoid too many re-renders
map(({ grouped, ordered, filtered, filterErrors }) => {
const result: { key: string; items: string[] }[] = [];
@@ -51,6 +51,11 @@ export class DocRecordList extends Entity {
[]
);
public readonly nonTrashDocsIds$ = LiveData.from<string[]>(
this.store.watchNonTrashDocIds(),
[]
);
public readonly isReady$ = LiveData.from(
this.store.watchDocListReady(),
false
@@ -147,7 +147,7 @@ export class DocsStore extends Store {
this.workspaceService.workspace.rootYDoc.getMap('meta'),
'pages'
).pipe(
switchMap(yjsObserveDeep),
switchMap(pages => yjsObservePath(pages, '*.trash')),
map(meta => {
if (meta instanceof YArray) {
return meta
@@ -165,7 +165,7 @@ export class DocsStore extends Store {
this.workspaceService.workspace.rootYDoc.getMap('meta'),
'pages'
).pipe(
switchMap(yjsObserveDeep),
switchMap(pages => yjsObservePath(pages, '*.trash')),
map(meta => {
if (meta instanceof YArray) {
return meta
@@ -66,7 +66,6 @@ export class MemberSearchService extends Service {
}
search(searchText?: string) {
console.log('search', searchText);
this.reset();
this.searchText$.setValue(searchText ?? '');
this.loadMore();