mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
refactor(editor): add runtime type checks to database cell values (#10770)
This commit is contained in:
@@ -94,7 +94,7 @@ export const processTable = (
|
||||
if (isDelta(cell.value)) {
|
||||
value = cell.value;
|
||||
} else {
|
||||
value = property.config.cellToString({
|
||||
value = property.config.rawValue.toString({
|
||||
value: cell.value,
|
||||
data: col.data,
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
return this._model.doc;
|
||||
}
|
||||
|
||||
allPropertyMetas$ = computed<PropertyMetaConfig<any, any, any>[]>(() => {
|
||||
allPropertyMetas$ = computed<PropertyMetaConfig<any, any, any, any>[]>(() => {
|
||||
return databaseBlockPropertyList;
|
||||
});
|
||||
|
||||
@@ -151,23 +151,27 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
this._runCapture();
|
||||
|
||||
const type = this.propertyTypeGet(propertyId);
|
||||
const update = this.propertyMetaGet(type).config.valueUpdate;
|
||||
let newValue = value;
|
||||
if (update) {
|
||||
const old = this.cellValueGet(rowId, propertyId);
|
||||
newValue = update({
|
||||
value: old,
|
||||
data: this.propertyDataGet(propertyId),
|
||||
dataSource: this,
|
||||
newValue: value,
|
||||
const update = this.propertyMetaGet(type)?.config.rawValue.setValue;
|
||||
const old = this.cellValueGet(rowId, propertyId);
|
||||
const updateFn =
|
||||
update ??
|
||||
(({ setValue, newValue }) => {
|
||||
setValue(newValue);
|
||||
});
|
||||
}
|
||||
if (this._model.columns$.value.some(v => v.id === propertyId)) {
|
||||
updateCell(this._model, rowId, {
|
||||
columnId: propertyId,
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
updateFn({
|
||||
value: old,
|
||||
data: this.propertyDataGet(propertyId),
|
||||
dataSource: this,
|
||||
newValue: value,
|
||||
setValue: newValue => {
|
||||
if (this._model.columns$.value.some(v => v.id === propertyId)) {
|
||||
updateCell(this._model, rowId, {
|
||||
columnId: propertyId,
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
cellValueGet(rowId: string, propertyId: string): unknown {
|
||||
@@ -183,14 +187,32 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
const model = this.getModelById(rowId);
|
||||
return model?.text;
|
||||
}
|
||||
return getCell(this._model, rowId, propertyId)?.value;
|
||||
const meta = this.propertyMetaGet(type);
|
||||
if (!meta) {
|
||||
return;
|
||||
}
|
||||
const rawValue =
|
||||
getCell(this._model, rowId, propertyId)?.value ??
|
||||
meta.config.rawValue.default();
|
||||
const schema = meta.config.rawValue.schema;
|
||||
const result = schema.safeParse(rawValue);
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
propertyAdd(insertToPosition: InsertToPosition, type?: string): string {
|
||||
propertyAdd(
|
||||
insertToPosition: InsertToPosition,
|
||||
type?: string
|
||||
): string | undefined {
|
||||
this.doc.captureSync();
|
||||
const property = this.propertyMetaGet(
|
||||
type ?? propertyPresets.multiSelectPropertyConfig.type
|
||||
);
|
||||
if (!property) {
|
||||
return;
|
||||
}
|
||||
const result = addProperty(
|
||||
this._model,
|
||||
insertToPosition,
|
||||
@@ -233,6 +255,9 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
}
|
||||
if (this.isFixedProperty(propertyId)) {
|
||||
const meta = this.propertyMetaGet(propertyId);
|
||||
if (!meta) {
|
||||
return;
|
||||
}
|
||||
const defaultData = meta.config.fixed?.defaultData ?? {};
|
||||
return {
|
||||
column: {
|
||||
@@ -288,7 +313,10 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
}
|
||||
const { column } = result;
|
||||
const meta = this.propertyMetaGet(column.type);
|
||||
return meta.config.type({
|
||||
if (!meta) {
|
||||
return;
|
||||
}
|
||||
return meta.config?.jsonValue.type({
|
||||
data: column.data,
|
||||
dataSource: this,
|
||||
});
|
||||
@@ -335,15 +363,8 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
return id;
|
||||
}
|
||||
|
||||
propertyMetaGet(type: string): PropertyMetaConfig {
|
||||
const property = databaseBlockAllPropertyMap[type];
|
||||
if (!property) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.DatabaseBlockError,
|
||||
`Unknown property type: ${type}`
|
||||
);
|
||||
}
|
||||
return property;
|
||||
propertyMetaGet(type: string): PropertyMetaConfig | undefined {
|
||||
return databaseBlockAllPropertyMap[type];
|
||||
}
|
||||
|
||||
propertyNameGet(propertyId: string): string {
|
||||
@@ -382,6 +403,10 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
if (this.isFixedProperty(propertyId)) {
|
||||
return;
|
||||
}
|
||||
const meta = this.propertyMetaGet(toType);
|
||||
if (!meta) {
|
||||
return;
|
||||
}
|
||||
const currentType = this.propertyTypeGet(propertyId);
|
||||
const currentData = this.propertyDataGet(propertyId);
|
||||
const rows = this.rows$.value;
|
||||
@@ -396,7 +421,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
|
||||
currentCells as any
|
||||
) ?? {
|
||||
property: this.propertyMetaGet(toType).config.defaultData(),
|
||||
property: meta.config.propertyData.default(),
|
||||
cells: currentCells.map(() => undefined),
|
||||
};
|
||||
this.doc.captureSync();
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from './cell-renderer.css.js';
|
||||
import { linkPropertyModelConfig } from './define.js';
|
||||
|
||||
export class LinkCell extends BaseCellRenderer<string> {
|
||||
export class LinkCell extends BaseCellRenderer<string, string> {
|
||||
protected override firstUpdated(_changedProperties: PropertyValues) {
|
||||
super.firstUpdated(_changedProperties);
|
||||
this.classList.add(linkCellStyle);
|
||||
|
||||
@@ -3,17 +3,23 @@ import zod from 'zod';
|
||||
export const linkColumnType = propertyType('link');
|
||||
export const linkPropertyModelConfig = linkColumnType.modelConfig({
|
||||
name: 'Link',
|
||||
valueSchema: zod.string().optional(),
|
||||
type: () => t.string.instance(),
|
||||
defaultData: () => ({}),
|
||||
cellToString: ({ value }) => value?.toString() ?? '',
|
||||
cellFromString: ({ value }) => {
|
||||
return {
|
||||
value: value,
|
||||
};
|
||||
propertyData: {
|
||||
schema: zod.object({}),
|
||||
default: () => ({}),
|
||||
},
|
||||
jsonValue: {
|
||||
schema: zod.string(),
|
||||
type: () => t.string.instance(),
|
||||
isEmpty: ({ value }) => !value,
|
||||
},
|
||||
rawValue: {
|
||||
schema: zod.string(),
|
||||
default: () => '',
|
||||
toString: ({ value }) => value,
|
||||
fromString: ({ value }) => {
|
||||
return { value: value };
|
||||
},
|
||||
toJson: ({ value }) => value,
|
||||
fromJson: ({ value }) => value,
|
||||
},
|
||||
cellToJson: ({ value }) => value ?? null,
|
||||
cellFromJson: ({ value }) => (typeof value !== 'string' ? undefined : value),
|
||||
|
||||
isEmpty: ({ value }) => value == null || value.length == 0,
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ function toggleStyle(
|
||||
inlineEditor.syncInlineRange();
|
||||
}
|
||||
|
||||
export class RichTextCell extends BaseCellRenderer<Text> {
|
||||
export class RichTextCell extends BaseCellRenderer<Text, string> {
|
||||
inlineEditor$ = computed(() => {
|
||||
return this.richText$.value?.inlineEditor;
|
||||
});
|
||||
|
||||
@@ -19,51 +19,59 @@ export const toYText = (text?: RichTextCellType): undefined | Text['yText'] => {
|
||||
|
||||
export const richTextPropertyModelConfig = richTextColumnType.modelConfig({
|
||||
name: 'Text',
|
||||
valueSchema: zod
|
||||
.custom<RichTextCellType>(
|
||||
data => data instanceof Text || data instanceof Y.Text
|
||||
)
|
||||
.optional(),
|
||||
type: () => t.richText.instance(),
|
||||
defaultData: () => ({}),
|
||||
cellToString: ({ value }) => value?.toString() ?? '',
|
||||
cellFromString: ({ value }) => {
|
||||
return {
|
||||
value: new Text(value),
|
||||
};
|
||||
propertyData: {
|
||||
schema: zod.object({}),
|
||||
default: () => ({}),
|
||||
},
|
||||
cellToJson: ({ value, dataSource }) => {
|
||||
if (!value) return null;
|
||||
const host = dataSource.contextGet(HostContextKey);
|
||||
if (host) {
|
||||
const collection = host.std.workspace;
|
||||
jsonValue: {
|
||||
schema: zod.string(),
|
||||
type: () => t.richText.instance(),
|
||||
isEmpty: ({ value }) => !value,
|
||||
},
|
||||
rawValue: {
|
||||
schema: zod
|
||||
.custom<RichTextCellType>(
|
||||
data => data instanceof Text || data instanceof Y.Text
|
||||
)
|
||||
.optional(),
|
||||
default: () => undefined,
|
||||
toString: ({ value }) => value?.toString() ?? '',
|
||||
fromString: ({ value }) => {
|
||||
return {
|
||||
value: new Text(value),
|
||||
};
|
||||
},
|
||||
toJson: ({ value, dataSource }) => {
|
||||
if (!value) return null;
|
||||
const host = dataSource.contextGet(HostContextKey);
|
||||
if (host) {
|
||||
const collection = host.std.workspace;
|
||||
const yText = toYText(value);
|
||||
const deltas = yText?.toDelta();
|
||||
const text = deltas
|
||||
.map((delta: DeltaInsert<AffineTextAttributes>) => {
|
||||
if (isLinkedDoc(delta)) {
|
||||
const linkedDocId = delta.attributes?.reference?.pageId as string;
|
||||
return collection.getDoc(linkedDocId)?.meta?.title;
|
||||
}
|
||||
return delta.insert;
|
||||
})
|
||||
.join('');
|
||||
return text;
|
||||
}
|
||||
return value?.toString() ?? null;
|
||||
},
|
||||
fromJson: ({ value }) =>
|
||||
typeof value !== 'string' ? undefined : new Text(value),
|
||||
onUpdate: ({ value, callback }) => {
|
||||
const yText = toYText(value);
|
||||
const deltas = yText?.toDelta();
|
||||
const text = deltas
|
||||
.map((delta: DeltaInsert<AffineTextAttributes>) => {
|
||||
if (isLinkedDoc(delta)) {
|
||||
const linkedDocId = delta.attributes?.reference?.pageId as string;
|
||||
return collection.getDoc(linkedDocId)?.meta?.title;
|
||||
}
|
||||
return delta.insert;
|
||||
})
|
||||
.join('');
|
||||
return text;
|
||||
}
|
||||
return value?.toString() ?? null;
|
||||
yText?.observe(callback);
|
||||
callback();
|
||||
return {
|
||||
dispose: () => {
|
||||
yText?.unobserve(callback);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
cellFromJson: ({ value }) =>
|
||||
typeof value !== 'string' ? undefined : new Text(value),
|
||||
onUpdate: ({ value, callback }) => {
|
||||
const yText = toYText(value);
|
||||
yText?.observe(callback);
|
||||
callback();
|
||||
return {
|
||||
dispose: () => {
|
||||
yText?.unobserve(callback);
|
||||
},
|
||||
};
|
||||
},
|
||||
isEmpty: ({ value }) => value == null || value.length === 0,
|
||||
values: ({ value }) => (value?.toString() ? [value.toString()] : []),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { propertyType, t } from '@blocksuite/data-view';
|
||||
import { Text } from '@blocksuite/store';
|
||||
import { Doc } from 'yjs';
|
||||
import zod from 'zod';
|
||||
|
||||
import { HostContextKey } from '../../context/host-context.js';
|
||||
@@ -9,61 +10,74 @@ export const titleColumnType = propertyType('title');
|
||||
|
||||
export const titlePropertyModelConfig = titleColumnType.modelConfig({
|
||||
name: 'Title',
|
||||
valueSchema: zod.custom<Text>(data => data instanceof Text).optional(),
|
||||
propertyData: {
|
||||
schema: zod.object({}),
|
||||
default: () => ({}),
|
||||
},
|
||||
jsonValue: {
|
||||
schema: zod.string(),
|
||||
type: () => t.richText.instance(),
|
||||
isEmpty: ({ value }) => !value,
|
||||
},
|
||||
rawValue: {
|
||||
schema: zod.custom<Text>(data => data instanceof Text).optional(),
|
||||
default: () => undefined,
|
||||
toString: ({ value }) => value?.toString() ?? '',
|
||||
fromString: ({ value }) => {
|
||||
return { value: new Text(value) };
|
||||
},
|
||||
toJson: ({ value, dataSource }) => {
|
||||
if (!value) return '';
|
||||
const host = dataSource.contextGet(HostContextKey);
|
||||
if (host) {
|
||||
const collection = host.std.workspace;
|
||||
const deltas = value.deltas$.value;
|
||||
const text = deltas
|
||||
.map(delta => {
|
||||
if (isLinkedDoc(delta)) {
|
||||
const linkedDocId = delta.attributes?.reference?.pageId as string;
|
||||
return collection.getDoc(linkedDocId)?.meta?.title;
|
||||
}
|
||||
return delta.insert;
|
||||
})
|
||||
.join('');
|
||||
return text;
|
||||
}
|
||||
return value?.toString() ?? '';
|
||||
},
|
||||
fromJson: ({ value }) => new Text(value),
|
||||
onUpdate: ({ value, callback }) => {
|
||||
value?.yText.observe(callback);
|
||||
callback();
|
||||
return {
|
||||
dispose: () => {
|
||||
value?.yText.unobserve(callback);
|
||||
},
|
||||
};
|
||||
},
|
||||
setValue: ({ value, newValue }) => {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
const v = newValue as unknown;
|
||||
if (v == null) {
|
||||
value.replace(0, value.length, '');
|
||||
return;
|
||||
}
|
||||
if (typeof v === 'string') {
|
||||
value.replace(0, value.length, v);
|
||||
return;
|
||||
}
|
||||
if (newValue instanceof Text) {
|
||||
new Doc().getMap('root').set('text', newValue.yText);
|
||||
value.clear();
|
||||
value.applyDelta(newValue.toDelta());
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
fixed: {
|
||||
defaultData: {},
|
||||
defaultShow: true,
|
||||
},
|
||||
type: () => t.richText.instance(),
|
||||
defaultData: () => ({}),
|
||||
cellToString: ({ value }) => value?.toString() ?? '',
|
||||
cellFromString: ({ value }) => {
|
||||
return {
|
||||
value: value,
|
||||
};
|
||||
},
|
||||
cellToJson: ({ value, dataSource }) => {
|
||||
if (!value) return null;
|
||||
const host = dataSource.contextGet(HostContextKey);
|
||||
if (host) {
|
||||
const collection = host.std.workspace;
|
||||
const deltas = value.deltas$.value;
|
||||
const text = deltas
|
||||
.map(delta => {
|
||||
if (isLinkedDoc(delta)) {
|
||||
const linkedDocId = delta.attributes?.reference?.pageId as string;
|
||||
return collection.getDoc(linkedDocId)?.meta?.title;
|
||||
}
|
||||
return delta.insert;
|
||||
})
|
||||
.join('');
|
||||
return text;
|
||||
}
|
||||
return value?.toString() ?? null;
|
||||
},
|
||||
cellFromJson: ({ value }) =>
|
||||
typeof value !== 'string' ? undefined : new Text(value),
|
||||
onUpdate: ({ value, callback }) => {
|
||||
value?.yText.observe(callback);
|
||||
callback();
|
||||
return {
|
||||
dispose: () => {
|
||||
value?.yText.unobserve(callback);
|
||||
},
|
||||
};
|
||||
},
|
||||
valueUpdate: ({ value, newValue }) => {
|
||||
const v = newValue as unknown;
|
||||
if (typeof v === 'string') {
|
||||
value?.replace(0, value.length, v);
|
||||
return value;
|
||||
}
|
||||
if (v == null) {
|
||||
value?.replace(0, value.length, '');
|
||||
return value;
|
||||
}
|
||||
return newValue;
|
||||
},
|
||||
isEmpty: ({ value }) => value == null || value.length === 0,
|
||||
values: ({ value }) => (value?.toString() ? [value.toString()] : []),
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
titleRichTextStyle,
|
||||
} from './cell-renderer.css.js';
|
||||
|
||||
export class HeaderAreaTextCell extends BaseCellRenderer<Text> {
|
||||
export class HeaderAreaTextCell extends BaseCellRenderer<Text, string> {
|
||||
activity = true;
|
||||
|
||||
docId$ = signal<string>();
|
||||
|
||||
Reference in New Issue
Block a user