fix(core): do not ensure properties on read (#6263)
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import type { WorkspacePropertiesAdapter } from '@affine/core/modules/workspace';
|
import type { WorkspacePropertiesAdapter } from '@affine/core/modules/workspace';
|
||||||
import type {
|
import type {
|
||||||
PageInfoCustomProperty,
|
PageInfoCustomProperty,
|
||||||
PageInfoCustomPropertyMeta,
|
PageInfoCustomPropertyMeta,
|
||||||
TagOption,
|
|
||||||
} from '@affine/core/modules/workspace/properties/schema';
|
} from '@affine/core/modules/workspace/properties/schema';
|
||||||
import { PagePropertyType } from '@affine/core/modules/workspace/properties/schema';
|
import { PagePropertyType } from '@affine/core/modules/workspace/properties/schema';
|
||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
@@ -41,20 +41,16 @@ export const newPropertyTypes: PagePropertyType[] = [
|
|||||||
export class PagePropertiesMetaManager {
|
export class PagePropertiesMetaManager {
|
||||||
constructor(private readonly adapter: WorkspacePropertiesAdapter) {}
|
constructor(private readonly adapter: WorkspacePropertiesAdapter) {}
|
||||||
|
|
||||||
get tagOptions() {
|
|
||||||
return this.adapter.tagOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
get propertiesSchema() {
|
get propertiesSchema() {
|
||||||
return this.adapter.schema.pageProperties;
|
return this.adapter.schema?.pageProperties ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
get systemPropertiesSchema() {
|
get systemPropertiesSchema() {
|
||||||
return this.adapter.schema.pageProperties.system;
|
return this.adapter.schema?.pageProperties.system ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
get customPropertiesSchema() {
|
get customPropertiesSchema() {
|
||||||
return this.adapter.schema.pageProperties.custom;
|
return this.adapter.schema?.pageProperties.custom ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderedPropertiesSchema() {
|
getOrderedPropertiesSchema() {
|
||||||
@@ -126,9 +122,11 @@ export class PagePropertiesMetaManager {
|
|||||||
const mapping = new Map<string, Set<string>>();
|
const mapping = new Map<string, Set<string>>();
|
||||||
for (const page of this.adapter.workspace.docCollection.docs.values()) {
|
for (const page of this.adapter.workspace.docCollection.docs.values()) {
|
||||||
const properties = this.adapter.getPageProperties(page.id);
|
const properties = this.adapter.getPageProperties(page.id);
|
||||||
for (const id of Object.keys(properties.custom)) {
|
if (properties) {
|
||||||
if (!mapping.has(id)) mapping.set(id, new Set());
|
for (const id of Object.keys(properties.custom)) {
|
||||||
mapping.get(id)?.add(page.id);
|
if (!mapping.has(id)) mapping.set(id, new Set());
|
||||||
|
mapping.get(id)?.add(page.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mapping;
|
return mapping;
|
||||||
@@ -145,13 +143,14 @@ export class PagePropertiesManager {
|
|||||||
private readonly adapter: WorkspacePropertiesAdapter,
|
private readonly adapter: WorkspacePropertiesAdapter,
|
||||||
public readonly pageId: string
|
public readonly pageId: string
|
||||||
) {
|
) {
|
||||||
this.adapter.ensurePageProperties(this.pageId);
|
|
||||||
this.metaManager = new PagePropertiesMetaManager(this.adapter);
|
this.metaManager = new PagePropertiesMetaManager(this.adapter);
|
||||||
|
this.ensureRequiredProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent infinite loop
|
// prevent infinite loop
|
||||||
private ensuring = false;
|
private ensuring = false;
|
||||||
ensureRequiredProperties() {
|
ensureRequiredProperties() {
|
||||||
|
this.adapter.ensurePageProperties(this.pageId);
|
||||||
if (this.ensuring) return;
|
if (this.ensuring) return;
|
||||||
this.ensuring = true;
|
this.ensuring = true;
|
||||||
this.transact(() => {
|
this.transact(() => {
|
||||||
@@ -184,12 +183,7 @@ export class PagePropertiesManager {
|
|||||||
return this.intrinsicMeta?.createDate;
|
return this.intrinsicMeta?.createDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
get pageTags() {
|
|
||||||
return this.adapter.getPageTags(this.pageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
get properties() {
|
get properties() {
|
||||||
this.ensureRequiredProperties();
|
|
||||||
return this.adapter.getPageProperties(this.pageId);
|
return this.adapter.getPageProperties(this.pageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,23 +191,17 @@ export class PagePropertiesManager {
|
|||||||
return !!this.page?.readonly;
|
return !!this.page?.readonly;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPageTag(pageId: string, tag: TagOption | string) {
|
|
||||||
this.adapter.addPageTag(pageId, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
removePageTag(pageId: string, tag: TagOption | string) {
|
|
||||||
this.adapter.removePageTag(pageId, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get custom properties (filter out properties that are not in schema)
|
* get custom properties (filter out properties that are not in schema)
|
||||||
*/
|
*/
|
||||||
getCustomProperties(): Record<string, PageInfoCustomProperty> {
|
getCustomProperties(): Record<string, PageInfoCustomProperty> {
|
||||||
return Object.fromEntries(
|
return this.properties
|
||||||
Object.entries(this.properties.custom).filter(([id]) =>
|
? Object.fromEntries(
|
||||||
this.metaManager.checkPropertyExists(id)
|
Object.entries(this.properties.custom).filter(([id]) =>
|
||||||
)
|
this.metaManager.checkPropertyExists(id)
|
||||||
);
|
)
|
||||||
|
)
|
||||||
|
: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderedCustomProperties() {
|
getOrderedCustomProperties() {
|
||||||
@@ -231,10 +219,11 @@ export class PagePropertiesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getCustomProperty(id: string) {
|
getCustomProperty(id: string) {
|
||||||
return this.properties.custom[id];
|
return this.properties?.custom[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
addCustomProperty(id: string, value?: any) {
|
addCustomProperty(id: string, value?: any) {
|
||||||
|
this.ensureRequiredProperties();
|
||||||
if (!this.metaManager.checkPropertyExists(id)) {
|
if (!this.metaManager.checkPropertyExists(id)) {
|
||||||
logger.warn(`property ${id} not found`);
|
logger.warn(`property ${id} not found`);
|
||||||
return;
|
return;
|
||||||
@@ -246,11 +235,11 @@ export class PagePropertiesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newOrder = generateKeyBetween(this.largestOrder(), null);
|
const newOrder = generateKeyBetween(this.largestOrder(), null);
|
||||||
if (this.properties.custom[id]) {
|
if (this.properties!.custom[id]) {
|
||||||
logger.warn(`custom property ${id} already exists`);
|
logger.warn(`custom property ${id} already exists`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.properties.custom[id] = {
|
this.properties!.custom[id] = {
|
||||||
id,
|
id,
|
||||||
value,
|
value,
|
||||||
order: newOrder,
|
order: newOrder,
|
||||||
@@ -259,6 +248,7 @@ export class PagePropertiesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
moveCustomProperty(from: number, to: number) {
|
moveCustomProperty(from: number, to: number) {
|
||||||
|
this.ensureRequiredProperties();
|
||||||
// move from -> to means change from's order to a new order between to and to -1/+1
|
// move from -> to means change from's order to a new order between to and to -1/+1
|
||||||
const properties = this.getOrderedCustomProperties();
|
const properties = this.getOrderedCustomProperties();
|
||||||
const fromProperty = properties[from];
|
const fromProperty = properties[from];
|
||||||
@@ -269,19 +259,21 @@ export class PagePropertiesManager {
|
|||||||
? [toProperty.order, toNextProperty?.order ?? null]
|
? [toProperty.order, toNextProperty?.order ?? null]
|
||||||
: [toNextProperty?.order ?? null, toProperty.order];
|
: [toNextProperty?.order ?? null, toProperty.order];
|
||||||
const newOrder = generateKeyBetween(...args);
|
const newOrder = generateKeyBetween(...args);
|
||||||
this.properties.custom[fromProperty.id].order = newOrder;
|
this.properties!.custom[fromProperty.id].order = newOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCustomProperty(id: string) {
|
hasCustomProperty(id: string) {
|
||||||
return !!this.properties.custom[id];
|
return !!this.properties?.custom[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCustomProperty(id: string) {
|
removeCustomProperty(id: string) {
|
||||||
delete this.properties.custom[id];
|
this.ensureRequiredProperties();
|
||||||
|
delete this.properties!.custom[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCustomProperty(id: string, opt: Partial<PageInfoCustomProperty>) {
|
updateCustomProperty(id: string, opt: Partial<PageInfoCustomProperty>) {
|
||||||
if (!this.properties.custom[id]) {
|
this.ensureRequiredProperties();
|
||||||
|
if (!this.properties?.custom[id]) {
|
||||||
logger.warn(`custom property ${id} not found`);
|
logger.warn(`custom property ${id} not found`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
// the adapter is to bridge the workspace rootdoc & native js bindings
|
// the adapter is to bridge the workspace rootdoc & native js bindings
|
||||||
|
|
||||||
import { createYProxy, type Y } from '@blocksuite/store';
|
import { createYProxy, type Y } from '@blocksuite/store';
|
||||||
@@ -7,7 +8,6 @@ import { defaultsDeep } from 'lodash-es';
|
|||||||
import {
|
import {
|
||||||
PagePropertyType,
|
PagePropertyType,
|
||||||
PageSystemPropertyId,
|
PageSystemPropertyId,
|
||||||
type TagOption,
|
|
||||||
type WorkspaceAffineProperties,
|
type WorkspaceAffineProperties,
|
||||||
type WorkspaceFavoriteItem,
|
type WorkspaceFavoriteItem,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
@@ -28,17 +28,21 @@ export class WorkspacePropertiesAdapter {
|
|||||||
public readonly proxy: WorkspaceAffineProperties;
|
public readonly proxy: WorkspaceAffineProperties;
|
||||||
public readonly properties: Y.Map<any>;
|
public readonly properties: Y.Map<any>;
|
||||||
|
|
||||||
|
private ensuredRoot = false;
|
||||||
|
private ensuredPages = {} as Record<string, boolean>;
|
||||||
|
|
||||||
constructor(public readonly workspace: Workspace) {
|
constructor(public readonly workspace: Workspace) {
|
||||||
// check if properties exists, if not, create one
|
// check if properties exists, if not, create one
|
||||||
const rootDoc = workspace.docCollection.doc;
|
const rootDoc = workspace.docCollection.doc;
|
||||||
this.properties = rootDoc.getMap(AFFINE_PROPERTIES_ID);
|
this.properties = rootDoc.getMap(AFFINE_PROPERTIES_ID);
|
||||||
this.proxy = createYProxy(this.properties);
|
this.proxy = createYProxy(this.properties);
|
||||||
|
|
||||||
// fixme: deal with migration issue?
|
|
||||||
this.ensureRootProperties();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureRootProperties() {
|
private ensureRootProperties() {
|
||||||
|
if (this.ensuredRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.ensuredRoot = true;
|
||||||
// todo: deal with schema change issue
|
// todo: deal with schema change issue
|
||||||
// fixme: may not to be called every time
|
// fixme: may not to be called every time
|
||||||
defaultsDeep(this.proxy, {
|
defaultsDeep(this.proxy, {
|
||||||
@@ -70,6 +74,11 @@ export class WorkspacePropertiesAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensurePageProperties(pageId: string) {
|
ensurePageProperties(pageId: string) {
|
||||||
|
this.ensureRootProperties();
|
||||||
|
if (this.ensuredPages[pageId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.ensuredPages[pageId] = true;
|
||||||
// fixme: may not to be called every time
|
// fixme: may not to be called every time
|
||||||
defaultsDeep(this.proxy.pageProperties, {
|
defaultsDeep(this.proxy.pageProperties, {
|
||||||
[pageId]: {
|
[pageId]: {
|
||||||
@@ -108,65 +117,21 @@ export class WorkspacePropertiesAdapter {
|
|||||||
// ====== utilities ======
|
// ====== utilities ======
|
||||||
|
|
||||||
getPageProperties(pageId: string) {
|
getPageProperties(pageId: string) {
|
||||||
this.ensurePageProperties(pageId);
|
return this.pageProperties?.[pageId] ?? null;
|
||||||
return this.pageProperties[pageId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isFavorite(id: string, type: WorkspaceFavoriteItem['type']) {
|
isFavorite(id: string, type: WorkspaceFavoriteItem['type']) {
|
||||||
return this.favorites[id]?.type === type;
|
return this.favorites?.[id]?.type === type;
|
||||||
}
|
}
|
||||||
|
|
||||||
getJournalPageDateString(id: string) {
|
getJournalPageDateString(id: string) {
|
||||||
return this.pageProperties[id]?.system[PageSystemPropertyId.Journal]?.value;
|
return this.pageProperties?.[id]?.system[PageSystemPropertyId.Journal]
|
||||||
|
?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
setJournalPageDateString(id: string, date: string) {
|
setJournalPageDateString(id: string, date: string) {
|
||||||
this.ensurePageProperties(id);
|
this.ensurePageProperties(id);
|
||||||
const pageProperties = this.pageProperties[id];
|
const pageProperties = this.pageProperties?.[id];
|
||||||
pageProperties.system[PageSystemPropertyId.Journal].value = date;
|
pageProperties!.system[PageSystemPropertyId.Journal].value = date;
|
||||||
}
|
|
||||||
|
|
||||||
get tagOptions() {
|
|
||||||
return this.schema.pageProperties.system[PageSystemPropertyId.Tags].options;
|
|
||||||
}
|
|
||||||
|
|
||||||
// page tags could be reactive
|
|
||||||
getPageTags(pageId: string) {
|
|
||||||
const tags =
|
|
||||||
this.getPageProperties(pageId)?.system[PageSystemPropertyId.Tags].value ??
|
|
||||||
[];
|
|
||||||
const optionsMap = Object.fromEntries(this.tagOptions.map(o => [o.id, o]));
|
|
||||||
return tags.map(tag => optionsMap[tag]).filter((t): t is TagOption => !!t);
|
|
||||||
}
|
|
||||||
|
|
||||||
addPageTag(pageId: string, tag: TagOption | string) {
|
|
||||||
this.ensurePageProperties(pageId);
|
|
||||||
const tags = this.getPageTags(pageId);
|
|
||||||
const tagId = typeof tag === 'string' ? tag : tag.id;
|
|
||||||
if (tags.some(t => t.id === tagId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// add tag option if not exist
|
|
||||||
if (!this.tagOptions.some(t => t.id === tagId)) {
|
|
||||||
if (typeof tag === 'string') {
|
|
||||||
throw new Error(`Tag ${tag} does not exist`);
|
|
||||||
} else {
|
|
||||||
this.tagOptions.push(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const pageProperties = this.pageProperties[pageId];
|
|
||||||
pageProperties.system[PageSystemPropertyId.Tags].value.push(tagId);
|
|
||||||
}
|
|
||||||
|
|
||||||
removePageTag(pageId: string, tag: TagOption | string) {
|
|
||||||
this.ensurePageProperties(pageId);
|
|
||||||
const tags = this.getPageTags(pageId);
|
|
||||||
const tagId = typeof tag === 'string' ? tag : tag.id;
|
|
||||||
const index = tags.findIndex(t => t.id === tagId);
|
|
||||||
if (index === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pageProperties = this.pageProperties[pageId];
|
|
||||||
pageProperties.system[PageSystemPropertyId.Tags].value.splice(index, 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,9 +100,9 @@ const WorkspacePagePropertiesSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const WorkspaceAffinePropertiesSchema = z.object({
|
export const WorkspaceAffinePropertiesSchema = z.object({
|
||||||
schema: WorkspaceAffinePropertiesSchemaSchema,
|
schema: WorkspaceAffinePropertiesSchemaSchema.optional(),
|
||||||
favorites: z.record(WorkspaceFavoriteItemSchema),
|
favorites: z.record(WorkspaceFavoriteItemSchema).optional(),
|
||||||
pageProperties: z.record(WorkspacePagePropertiesSchema),
|
pageProperties: z.record(WorkspacePagePropertiesSchema).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type PageInfoCustomPropertyMeta = z.infer<
|
export type PageInfoCustomPropertyMeta = z.infer<
|
||||||
|
|||||||
Reference in New Issue
Block a user