mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 22:09:08 +08:00
2d1600fa00
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Documents now automatically track and display "created by" and "updated by" user information. - Document creation and update timestamps are now managed and shown more accurately. - Workspace and document metadata (name, avatar) updates are more responsive and reliable. - Document creation supports middleware for customizing properties and behavior. - **Improvements** - Simplified and unified event handling for document list updates, reducing redundant event subscriptions. - Enhanced integration of editor and theme settings into the document creation process. - Explicit Yjs document initialization for improved workspace stability and reliability. - Consolidated journal-related metadata display in document icons and titles for clarity. - **Bug Fixes** - Fixed inconsistencies in how workspace and document names are set and updated. - Improved accuracy of "last updated" indicators by handling timestamps automatically. - **Refactor** - Removed deprecated event subjects and direct metadata manipulation in favor of more robust, reactive patterns. - Streamlined document creation logic across various features (quick search, journal, recording, etc.). - Simplified user avatar display components and removed cloud metadata dependencies. - Removed legacy editor setting and theme service dependencies from multiple modules. - **Chores** - Updated internal APIs and interfaces to support new metadata and event handling mechanisms. - Cleaned up unused code and dependencies related to editor settings and theme services. - Skipped flaky end-to-end test to improve test suite stability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
198 lines
4.9 KiB
TypeScript
198 lines
4.9 KiB
TypeScript
import type { AliasInfo, ReferenceParams } from '@blocksuite/affine-model';
|
|
import { type Container, createIdentifier } from '@blocksuite/global/di';
|
|
import {
|
|
type DisposableMember,
|
|
disposeMember,
|
|
} from '@blocksuite/global/disposable';
|
|
import {
|
|
AliasIcon,
|
|
BlockLinkIcon,
|
|
DeleteIcon,
|
|
EdgelessIcon,
|
|
LinkedEdgelessIcon,
|
|
LinkedPageIcon,
|
|
PageIcon,
|
|
} from '@blocksuite/icons/lit';
|
|
import { LifeCycleWatcher, StdIdentifier } from '@blocksuite/std';
|
|
import type { Store } from '@blocksuite/store';
|
|
import {
|
|
computed,
|
|
type ReadonlySignal,
|
|
type Signal,
|
|
signal,
|
|
} from '@preact/signals-core';
|
|
import type { TemplateResult } from 'lit';
|
|
|
|
import { referenceToNode } from '../utils/reference.js';
|
|
import { DocModeProvider } from './doc-mode-service.js';
|
|
|
|
export type DocDisplayMetaParams = {
|
|
referenced?: boolean;
|
|
params?: ReferenceParams;
|
|
} & AliasInfo;
|
|
|
|
/**
|
|
* Customize document display title and icon.
|
|
*
|
|
* Supports the following blocks:
|
|
*
|
|
* * Inline View:
|
|
* `AffineReference`
|
|
* * Card View:
|
|
* `EmbedLinkedDocBlockComponent`
|
|
* `EmbedEdgelessLinkedDocBlockComponent`
|
|
* * Embed View:
|
|
* `EmbedSyncedDocBlockComponent`
|
|
* `EmbedEdgelessSyncedDocBlockComponent`
|
|
*/
|
|
export interface DocDisplayMetaExtension {
|
|
icon: (
|
|
docId: string,
|
|
referenceInfo?: DocDisplayMetaParams
|
|
) => ReadonlySignal<TemplateResult>;
|
|
title: (
|
|
docId: string,
|
|
referenceInfo?: DocDisplayMetaParams
|
|
) => ReadonlySignal<string>;
|
|
}
|
|
|
|
export const DocDisplayMetaProvider = createIdentifier<DocDisplayMetaExtension>(
|
|
'DocDisplayMetaService'
|
|
);
|
|
|
|
export class DocDisplayMetaService
|
|
extends LifeCycleWatcher
|
|
implements DocDisplayMetaExtension
|
|
{
|
|
static icons = {
|
|
deleted: iconBuilder(DeleteIcon),
|
|
aliased: iconBuilder(AliasIcon),
|
|
page: iconBuilder(PageIcon),
|
|
edgeless: iconBuilder(EdgelessIcon),
|
|
linkedBlock: iconBuilder(BlockLinkIcon),
|
|
linkedPage: iconBuilder(LinkedPageIcon),
|
|
linkedEdgeless: iconBuilder(LinkedEdgelessIcon),
|
|
} as const;
|
|
|
|
static override key = 'doc-display-meta';
|
|
|
|
readonly disposables: DisposableMember[] = [];
|
|
|
|
readonly iconMap = new WeakMap<Store, Signal<TemplateResult>>();
|
|
|
|
readonly titleMap = new WeakMap<Store, Signal<string>>();
|
|
|
|
static override setup(di: Container) {
|
|
di.addImpl(DocDisplayMetaProvider, this, [StdIdentifier]);
|
|
}
|
|
|
|
dispose() {
|
|
while (this.disposables.length > 0) {
|
|
const disposable = this.disposables.pop();
|
|
if (disposable) {
|
|
disposeMember(disposable);
|
|
}
|
|
}
|
|
}
|
|
|
|
icon(
|
|
pageId: string,
|
|
{ params, title, referenced }: DocDisplayMetaParams = {}
|
|
): ReadonlySignal<TemplateResult> {
|
|
const doc = this.std.workspace.getDoc(pageId);
|
|
|
|
if (!doc) {
|
|
return computed(() => DocDisplayMetaService.icons.deleted);
|
|
}
|
|
|
|
const store = doc.getStore();
|
|
|
|
let icon$ = this.iconMap.get(store);
|
|
|
|
if (!icon$) {
|
|
icon$ = signal(
|
|
this.std.get(DocModeProvider).getPrimaryMode(pageId) === 'edgeless'
|
|
? DocDisplayMetaService.icons.edgeless
|
|
: DocDisplayMetaService.icons.page
|
|
);
|
|
|
|
const disposable = this.std
|
|
.get(DocModeProvider)
|
|
.onPrimaryModeChange(mode => {
|
|
icon$!.value =
|
|
mode === 'edgeless'
|
|
? DocDisplayMetaService.icons.edgeless
|
|
: DocDisplayMetaService.icons.page;
|
|
}, pageId);
|
|
|
|
this.disposables.push(disposable);
|
|
this.iconMap.set(store, icon$);
|
|
}
|
|
|
|
return computed(() => {
|
|
if (title) {
|
|
return DocDisplayMetaService.icons.aliased;
|
|
}
|
|
|
|
if (referenceToNode({ pageId, params })) {
|
|
return DocDisplayMetaService.icons.linkedBlock;
|
|
}
|
|
|
|
if (referenced) {
|
|
const mode =
|
|
params?.mode ??
|
|
this.std.get(DocModeProvider).getPrimaryMode(pageId) ??
|
|
'page';
|
|
return mode === 'edgeless'
|
|
? DocDisplayMetaService.icons.linkedEdgeless
|
|
: DocDisplayMetaService.icons.linkedPage;
|
|
}
|
|
|
|
return icon$.value;
|
|
});
|
|
}
|
|
|
|
title(
|
|
pageId: string,
|
|
{ title }: DocDisplayMetaParams = {}
|
|
): ReadonlySignal<string> {
|
|
const doc = this.std.workspace.getDoc(pageId);
|
|
|
|
if (!doc) {
|
|
return computed(() => title || 'Deleted doc');
|
|
}
|
|
|
|
const store = doc.getStore();
|
|
|
|
let title$ = this.titleMap.get(store);
|
|
if (!title$) {
|
|
title$ = signal(doc.meta?.title || 'Untitled');
|
|
|
|
const disposable = this.std.workspace.slots.docListUpdated.subscribe(
|
|
() => {
|
|
title$!.value = doc.meta?.title || 'Untitled';
|
|
}
|
|
);
|
|
|
|
this.disposables.push(disposable);
|
|
this.titleMap.set(store, title$);
|
|
}
|
|
|
|
return computed(() => {
|
|
return title || title$.value;
|
|
});
|
|
}
|
|
|
|
override unmounted() {
|
|
this.dispose();
|
|
}
|
|
}
|
|
|
|
function iconBuilder(
|
|
icon: typeof PageIcon,
|
|
size = '1.25em',
|
|
style = 'user-select:none;flex-shrink:0;vertical-align:middle;font-size:inherit;'
|
|
) {
|
|
return icon({ width: size, height: size, style });
|
|
}
|