feat(core): support better battery save mode (#13383)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a Document Summary module, enabling live and cached document summaries with cloud revalidation. * Added a feature flag for enabling battery save mode. * Added explicit pause and resume controls for sync operations, accessible via UI events and programmatically. * **Improvements** * Enhanced sync and indexing logic to support pausing, resuming, and battery save mode, with improved job prioritization. * Updated navigation and preview components to use the new document summary service and improved priority handling. * Improved logging and state reporting for sync and indexing processes. * Refined backlink handling with reactive loading states and cloud revalidation. * Replaced backlink and link management to use a new dedicated document links service. * Enhanced workspace engine to conditionally enable battery save mode based on feature flags and workspace flavor. * **Bug Fixes** * Removed unnecessary debug console logs from various components for cleaner output. * **Refactor** * Replaced battery save mode methods with explicit pause/resume methods throughout the app and services. * Modularized and streamlined document summary and sync-related code for better maintainability. * Restructured backlink components to improve visibility handling and data fetching. * Simplified and improved document backlink data fetching with retry and loading state management. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,7 +1,20 @@
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import {
|
||||
catchErrorInto,
|
||||
effect,
|
||||
Entity,
|
||||
exhaustMapWithTrailing,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
smartRetry,
|
||||
} from '@toeverything/infra';
|
||||
import { tap } from 'rxjs';
|
||||
|
||||
import type { DocService } from '../../doc';
|
||||
import type { DocService, DocsService } from '../../doc';
|
||||
import type { DocsSearchService } from '../../docs-search';
|
||||
import type { FeatureFlagService } from '../../feature-flag';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
|
||||
export interface Backlink {
|
||||
docId: string;
|
||||
@@ -17,13 +30,128 @@ export interface Backlink {
|
||||
export class DocBacklinks extends Entity {
|
||||
constructor(
|
||||
private readonly docsSearchService: DocsSearchService,
|
||||
private readonly docService: DocService
|
||||
private readonly docService: DocService,
|
||||
private readonly docsService: DocsService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceService: WorkspaceService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
backlinks$ = LiveData.from<Backlink[]>(
|
||||
this.docsSearchService.watchRefsTo(this.docService.doc.id),
|
||||
[]
|
||||
backlinks$ = new LiveData<Backlink[] | undefined>(undefined);
|
||||
|
||||
isLoading$ = new LiveData<boolean>(false);
|
||||
error$ = new LiveData<any>(undefined);
|
||||
|
||||
revalidateFromCloud = effect(
|
||||
exhaustMapWithTrailing(() =>
|
||||
fromPromise(async () => {
|
||||
const searchFromCloud =
|
||||
this.featureFlagService.flags.enable_battery_save_mode &&
|
||||
this.workspaceService.workspace.flavour !== 'local';
|
||||
const { buckets } = await this.docsSearchService.indexer.aggregate(
|
||||
'block',
|
||||
{
|
||||
type: 'boolean',
|
||||
occur: 'must',
|
||||
queries: [
|
||||
{
|
||||
type: 'match',
|
||||
field: 'refDocId',
|
||||
match: this.docService.doc.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
'docId',
|
||||
{
|
||||
hits: {
|
||||
fields: [
|
||||
'docId',
|
||||
'blockId',
|
||||
'parentBlockId',
|
||||
'parentFlavour',
|
||||
'additional',
|
||||
'markdownPreview',
|
||||
],
|
||||
pagination: {
|
||||
limit: 5, // the max number of backlinks to show for each doc
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
limit: 100,
|
||||
},
|
||||
prefer: searchFromCloud ? 'remote' : 'local',
|
||||
}
|
||||
);
|
||||
return buckets.flatMap(bucket => {
|
||||
const title =
|
||||
this.docsService.list.doc$(bucket.key).value?.title$.value ?? '';
|
||||
|
||||
if (bucket.key === this.docService.doc.id) {
|
||||
// Ignore if it is a link to the current document.
|
||||
return [];
|
||||
}
|
||||
|
||||
return bucket.hits.nodes.map(node => {
|
||||
const blockId = node.fields.blockId ?? '';
|
||||
const markdownPreview = node.fields.markdownPreview ?? '';
|
||||
const additional =
|
||||
typeof node.fields.additional === 'string'
|
||||
? node.fields.additional
|
||||
: node.fields.additional[0];
|
||||
|
||||
const additionalData: {
|
||||
displayMode?: string;
|
||||
noteBlockId?: string;
|
||||
} = JSON.parse(additional || '{}');
|
||||
|
||||
const displayMode = additionalData.displayMode ?? '';
|
||||
const noteBlockId = additionalData.noteBlockId ?? '';
|
||||
const parentBlockId =
|
||||
typeof node.fields.parentBlockId === 'string'
|
||||
? node.fields.parentBlockId
|
||||
: node.fields.parentBlockId[0];
|
||||
const parentFlavour =
|
||||
typeof node.fields.parentFlavour === 'string'
|
||||
? node.fields.parentFlavour
|
||||
: node.fields.parentFlavour[0];
|
||||
|
||||
return {
|
||||
docId: bucket.key,
|
||||
blockId: typeof blockId === 'string' ? blockId : blockId[0],
|
||||
title: title,
|
||||
markdownPreview:
|
||||
typeof markdownPreview === 'string'
|
||||
? markdownPreview
|
||||
: markdownPreview[0],
|
||||
displayMode:
|
||||
typeof displayMode === 'string' ? displayMode : displayMode[0],
|
||||
noteBlockId:
|
||||
typeof noteBlockId === 'string' ? noteBlockId : noteBlockId[0],
|
||||
parentBlockId:
|
||||
typeof parentBlockId === 'string'
|
||||
? parentBlockId
|
||||
: parentBlockId[0],
|
||||
parentFlavour:
|
||||
typeof parentFlavour === 'string'
|
||||
? parentFlavour
|
||||
: parentFlavour[0],
|
||||
};
|
||||
});
|
||||
});
|
||||
}).pipe(
|
||||
smartRetry(),
|
||||
tap(backlinks => {
|
||||
this.backlinks$.value = backlinks;
|
||||
}),
|
||||
catchErrorInto(this.error$),
|
||||
onStart(() => {
|
||||
this.isLoading$.value = true;
|
||||
}),
|
||||
onComplete(() => {
|
||||
this.isLoading$.value = false;
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocScope } from '../doc/scopes/doc';
|
||||
import { DocService } from '../doc/services/doc';
|
||||
import { DocsService } from '../doc/services/docs';
|
||||
import { DocsSearchService } from '../docs-search';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { FeatureFlagService } from '../feature-flag';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { DocBacklinks } from './entities/doc-backlinks';
|
||||
import { DocLinks } from './entities/doc-links';
|
||||
import { DocLinksService } from './services/doc-links';
|
||||
@@ -17,6 +19,12 @@ export function configureDocLinksModule(framework: Framework) {
|
||||
.scope(WorkspaceScope)
|
||||
.scope(DocScope)
|
||||
.service(DocLinksService)
|
||||
.entity(DocBacklinks, [DocsSearchService, DocService])
|
||||
.entity(DocBacklinks, [
|
||||
DocsSearchService,
|
||||
DocService,
|
||||
DocsService,
|
||||
FeatureFlagService,
|
||||
WorkspaceService,
|
||||
])
|
||||
.entity(DocLinks, [DocsSearchService, DocService]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user