Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | 2x 2x 2x 2x 2x 2x 16x 16x 16x 16x 16x 16x 16x 3x 3x 2x 1x 2x 1x 1x 14x 14x 14x 14x 9x 14x 6x 6x 6x 6x 6x 6x 5x 1x 3x 1x 1x 3x 9x 9x 8x 3x 5x 6x 6x 6x 6x | import { DestroyRef, inject, Injectable } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Draft, DraftInput, DraftStorageService, LoggerService } from '@drevo-web/core';
import { debounceTime, Subject } from 'rxjs';
const DRAFT_SAVE_DEBOUNCE_MS = 3000;
@Injectable()
export class DraftEditorService {
private readonly draftStorage = inject(DraftStorageService);
private readonly logger = inject(LoggerService).withContext('DraftEditorService');
private readonly destroyRef = inject(DestroyRef);
private readonly contentSubject = new Subject<DraftInput>();
private lastSavedText: string | undefined;
private lastPendingInput: DraftInput | undefined;
private subscriptionInitialized = false;
private discarded = false;
private readonly activeRoutes = new Set<string>();
async getDraft(route: string): Promise<Draft | undefined> {
try {
const draft = await this.draftStorage.getByRoute(route);
if (draft) {
this.logger.info('Draft found', { route, title: draft.title, time: draft.time });
}
return draft;
} catch (error) {
this.logger.error('Failed to get draft', error);
return undefined;
}
}
onContentChanged(input: DraftInput): void {
this.discarded = false;
this.lastPendingInput = input;
this.activeRoutes.add(input.route);
if (!this.subscriptionInitialized) {
this.initSubscription();
}
this.contentSubject.next(input);
}
async discardDraft(route: string): Promise<void> {
try {
this.discarded = true;
this.lastSavedText = undefined;
this.lastPendingInput = undefined;
this.activeRoutes.delete(route);
await this.draftStorage.deleteByRoute(route);
this.logger.info('Draft discarded', { route });
} catch (error) {
this.logger.error('Failed to discard draft', error);
}
}
flush(): void {
if (this.lastPendingInput && !this.discarded && this.lastPendingInput.text !== this.lastSavedText) {
this.saveDraft(this.lastPendingInput);
this.lastPendingInput = undefined;
}
}
hasActiveSession(route: string): boolean {
return this.activeRoutes.has(route);
}
private initSubscription(): void {
this.subscriptionInitialized = true;
this.contentSubject
.pipe(debounceTime(DRAFT_SAVE_DEBOUNCE_MS), takeUntilDestroyed(this.destroyRef))
.subscribe(input => {
if (this.discarded || input.text === this.lastSavedText) {
return;
}
this.saveDraft(input);
});
}
private async saveDraft(input: DraftInput): Promise<void> {
try {
this.lastSavedText = input.text;
await this.draftStorage.save(input);
this.logger.debug('Draft saved', { route: input.route, length: input.text.length });
} catch (error) {
this.lastSavedText = undefined;
this.logger.error('Failed to save draft', error);
}
}
}
|