feat(editor): support file column and member column for database block (#10932)

close: BS-2630, BS-2631, BS-2629, BS-2632, BS-2635
This commit is contained in:
zzj3720
2025-03-18 14:51:45 +00:00
parent 321e3449ec
commit 3939cc1c52
34 changed files with 1796 additions and 77 deletions

View File

@@ -1,15 +1,15 @@
import {
addProperty,
copyCellsByProperty,
databaseBlockColumns,
databaseBlockProperties,
deleteColumn,
getCell,
getProperty,
updateCell,
} from '@blocksuite/affine-block-database';
import {
type Cell,
type Column,
type CellDataType,
type ColumnDataType,
type DatabaseBlockModel,
DatabaseBlockSchemaExtension,
NoteBlockSchemaExtension,
@@ -55,9 +55,9 @@ describe('DatabaseManager', () => {
let databaseBlockId: BlockModel['id'];
let p1: BlockModel['id'];
let p2: BlockModel['id'];
let col1: Column['id'];
let col2: Column['id'];
let col3: Column['id'];
let col1: ColumnDataType['id'];
let col2: ColumnDataType['id'];
let col3: ColumnDataType['id'];
const selection = [
{ id: '1', value: 'Done', color: 'var(--affine-tag-white)' },
@@ -90,7 +90,7 @@ describe('DatabaseManager', () => {
col1 = addProperty(
db,
'end',
databaseBlockColumns.numberColumnConfig.create('Number')
databaseBlockProperties.numberColumnConfig.create('Number')
);
col2 = addProperty(
db,
@@ -102,7 +102,7 @@ describe('DatabaseManager', () => {
col3 = addProperty(
db,
'end',
databaseBlockColumns.richTextColumnConfig.create('Rich Text')
databaseBlockProperties.richTextColumnConfig.create('Rich Text')
);
doc.updateBlock(databaseModel, {
@@ -136,7 +136,7 @@ describe('DatabaseManager', () => {
test('getColumn', () => {
const column = {
...databaseBlockColumns.numberColumnConfig.create('testColumnId'),
...databaseBlockProperties.numberColumnConfig.create('testColumnId'),
id: 'testColumnId',
};
addProperty(db, 'end', column);
@@ -147,7 +147,7 @@ describe('DatabaseManager', () => {
test('addColumn', () => {
const column =
databaseBlockColumns.numberColumnConfig.create('Test Column');
databaseBlockProperties.numberColumnConfig.create('Test Column');
const id = addProperty(db, 'end', column);
const result = getProperty(db, id);
@@ -157,7 +157,7 @@ describe('DatabaseManager', () => {
test('deleteColumn', () => {
const column = {
...databaseBlockColumns.numberColumnConfig.create('Test Column'),
...databaseBlockProperties.numberColumnConfig.create('Test Column'),
id: 'testColumnId',
};
addProperty(db, 'end', column);
@@ -176,10 +176,10 @@ describe('DatabaseManager', () => {
noteBlockId
);
const column = {
...databaseBlockColumns.numberColumnConfig.create('Test Column'),
...databaseBlockProperties.numberColumnConfig.create('Test Column'),
id: 'testColumnId',
};
const cell: Cell = {
const cell: CellDataType = {
columnId: column.id,
value: 42,
};

View File

@@ -1,8 +1,8 @@
import {
databaseBlockAllPropertyMap,
DatabaseBlockDataSource,
databasePropertyConverts,
} from '@blocksuite/affine-block-database';
import type { Column } from '@blocksuite/affine-model';
import type { ColumnDataType } from '@blocksuite/affine-model';
import {
insertPositionToIndex,
type InsertToPosition,
@@ -171,7 +171,7 @@ export class BlockQueryDataSource extends DataSourceBase {
): string {
const doc = this.block.doc;
doc.captureSync();
const column = databaseBlockAllPropertyMap[
const column = DatabaseBlockDataSource.propertiesMap.value[
type ?? propertyPresets.multiSelectPropertyConfig.type
].create(this.newColumnName());
@@ -180,7 +180,7 @@ export class BlockQueryDataSource extends DataSourceBase {
return id;
}
doc.transact(() => {
const col: Column = {
const col: ColumnDataType = {
...column,
id,
};
@@ -287,7 +287,9 @@ export class BlockQueryDataSource extends DataSourceBase {
currentCells as any
) ?? {
property:
databaseBlockAllPropertyMap[toType].config.propertyData.default(),
DatabaseBlockDataSource.propertiesMap.value[
toType
].config.propertyData.default(),
cells: currentCells.map(() => undefined),
};
this.block.doc.captureSync();

View File

@@ -1,4 +1,4 @@
import type { Column } from '@blocksuite/affine-model';
import type { ColumnDataType } from '@blocksuite/affine-model';
import {
arrayMove,
insertPositionToIndex,
@@ -14,7 +14,7 @@ import {
type Props = {
title: string;
views: DataViewDataType[];
columns: Column[];
columns: ColumnDataType[];
cells: Record<string, Record<string, unknown>>;
};

View File

@@ -1,5 +1,5 @@
import {
type Column,
type ColumnDataType,
DatabaseBlockSchema,
type SerializedCells,
} from '@blocksuite/affine-model';
@@ -20,7 +20,7 @@ export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
fromBlockSnapshot: {
enter: (o, context) => {
const { walkerContext } = context;
const columns = o.node.props.columns as Array<Column>;
const columns = o.node.props.columns as Array<ColumnDataType>;
const children = o.node.children;
const cells = o.node.props.cells as SerializedCells;
const table = processTable(columns, children, cells);

View File

@@ -1,5 +1,5 @@
import {
type Column,
type ColumnDataType,
DatabaseBlockSchema,
type SerializedCells,
} from '@blocksuite/affine-model';
@@ -27,7 +27,7 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
enter: (o, context) => {
const { walkerContext, deltaConverter } = context;
const rows: TableRow[] = [];
const columns = o.node.props.columns as Array<Column>;
const columns = o.node.props.columns as Array<ColumnDataType>;
const children = o.node.children;
const cells = o.node.props.cells as SerializedCells;
const table = processTable(columns, children, cells);

View File

@@ -1,5 +1,5 @@
import {
type Column,
type ColumnDataType,
DatabaseBlockSchema,
type SerializedCells,
} from '@blocksuite/affine-model';
@@ -20,7 +20,7 @@ export const databaseBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher
enter: (o, context) => {
const { walkerContext, deltaConverter } = context;
const rows: string[][] = [];
const columns = o.node.props.columns as Array<Column>;
const columns = o.node.props.columns as Array<ColumnDataType>;
const children = o.node.children;
const cells = o.node.props.cells as SerializedCells;
const table = processTable(columns, children, cells);

View File

@@ -1,4 +1,4 @@
import type { Column, SerializedCells } from '@blocksuite/affine-model';
import type { ColumnDataType, SerializedCells } from '@blocksuite/affine-model';
import type { DeltaInsert } from '@blocksuite/inline';
import type { BlockSnapshot } from '@blocksuite/store';
@@ -45,7 +45,7 @@ export const isDelta = (value: unknown): value is { delta: DeltaInsert[] } => {
return false;
};
type Table = {
headers: Column[];
headers: ColumnDataType[];
rows: Row[];
};
type Row = {
@@ -55,7 +55,7 @@ type Cell = {
value: string | { delta: DeltaInsert[] };
};
export const processTable = (
columns: Column[],
columns: ColumnDataType[],
children: BlockSnapshot[],
cells: SerializedCells
): Table => {

View File

@@ -0,0 +1 @@
export * from './host-context';

View File

@@ -1,5 +1,5 @@
import type {
Column,
ColumnDataType,
ColumnUpdater,
DatabaseBlockModel,
} from '@blocksuite/affine-model';
@@ -24,12 +24,11 @@ import { propertyPresets } from '@blocksuite/data-view/property-presets';
import { IS_MOBILE } from '@blocksuite/global/env';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { type BlockModel } from '@blocksuite/store';
import { computed, type ReadonlySignal } from '@preact/signals-core';
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
import { getIcon } from './block-icons.js';
import {
databaseBlockAllPropertyMap,
databaseBlockPropertyList,
databaseBlockProperties,
databasePropertyConverts,
} from './properties/index.js';
import {
@@ -53,6 +52,19 @@ import {
} from './views/index.js';
export class DatabaseBlockDataSource extends DataSourceBase {
static externalProperties = signal<PropertyMetaConfig[]>([]);
static propertiesList = computed(() => {
return [
...Object.values(databaseBlockProperties),
...this.externalProperties.value,
];
});
static propertiesMap = computed(() => {
return Object.fromEntries(
this.propertiesList.value.map(v => [v.type, v as PropertyMetaConfig])
);
});
private _batch = 0;
private readonly _model: DatabaseBlockModel;
@@ -108,7 +120,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
}
allPropertyMetas$ = computed<PropertyMetaConfig<any, any, any, any>[]>(() => {
return databaseBlockPropertyList;
return DatabaseBlockDataSource.propertiesList.value;
});
propertyMetas$ = computed<PropertyMetaConfig[]>(() => {
@@ -153,6 +165,9 @@ export class DatabaseBlockDataSource extends DataSourceBase {
this._runCapture();
const type = this.propertyTypeGet(propertyId);
if (type == null) {
return;
}
const update = this.propertyMetaGet(type)?.config.rawValue.setValue;
const old = this.cellValueGet(rowId, propertyId);
const updateFn =
@@ -185,6 +200,9 @@ export class DatabaseBlockDataSource extends DataSourceBase {
return getIcon(model);
}
const type = this.propertyTypeGet(propertyId);
if (!type) {
return;
}
if (type === 'title') {
const model = this.getModelById(rowId);
return model?.text;
@@ -225,7 +243,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
protected override getNormalPropertyAndIndex(propertyId: string):
| {
column: Column<Record<string, unknown>>;
column: ColumnDataType<Record<string, unknown>>;
index: number;
}
| undefined {
@@ -247,7 +265,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
private getPropertyAndIndex(propertyId: string):
| {
column: Column<Record<string, unknown>>;
column: ColumnDataType<Record<string, unknown>>;
index: number;
}
| undefined {
@@ -368,7 +386,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
}
propertyMetaGet(type: string): PropertyMetaConfig | undefined {
return databaseBlockAllPropertyMap[type];
return DatabaseBlockDataSource.propertiesMap.value[type];
}
propertyNameGet(propertyId: string): string {
@@ -392,13 +410,13 @@ export class DatabaseBlockDataSource extends DataSourceBase {
return false;
}
propertyTypeGet(propertyId: string): string {
propertyTypeGet(propertyId: string): string | undefined {
if (propertyId === 'type') {
return 'image';
}
const result = this.getPropertyAndIndex(propertyId);
if (!result) {
return '';
return;
}
return result.column.type;
}

View File

@@ -1,6 +1,7 @@
export * from './adapters';
export * from './commands';
export * from './config';
export * from './context';
export * from './data-source';
export * from './database-block';
export * from './database-spec';
@@ -9,4 +10,6 @@ export * from './detail-panel/note-renderer';
export * from './properties';
export * from './properties/rich-text/cell-renderer';
export * from './selection.js';
export * from './service';
export * from './utils/block-utils';
export * from '@blocksuite/data-view';

View File

@@ -1,4 +1,3 @@
import type { PropertyMetaConfig } from '@blocksuite/data-view';
import { propertyPresets } from '@blocksuite/data-view/property-presets';
import { linkColumnConfig } from './link/cell-renderer.js';
@@ -14,7 +13,7 @@ const {
progressPropertyConfig,
selectPropertyConfig,
} = propertyPresets;
export const databaseBlockColumns = {
export const databaseBlockProperties = {
checkboxColumnConfig: checkboxPropertyConfig,
dateColumnConfig: datePropertyConfig,
multiSelectColumnConfig: multiSelectPropertyConfig,
@@ -26,7 +25,3 @@ export const databaseBlockColumns = {
richTextColumnConfig,
titleColumnConfig,
};
export const databaseBlockPropertyList = Object.values(databaseBlockColumns);
export const databaseBlockAllPropertyMap = Object.fromEntries(
databaseBlockPropertyList.map(v => [v.type, v as PropertyMetaConfig])
);

View File

@@ -0,0 +1,11 @@
import type { PropertyMetaConfig } from '@blocksuite/data-view';
import { createIdentifier } from '@blocksuite/global/di';
export interface DatabaseBlockConfigService {
propertiesPresets: PropertyMetaConfig[];
}
export const DatabaseBlockConfigService =
createIdentifier<DatabaseBlockConfigService>(
'AffineDatabaseBlockConfigService'
);

View File

@@ -1,6 +1,6 @@
import type {
Cell,
Column,
CellDataType,
ColumnDataType,
ColumnUpdater,
DatabaseBlockModel,
ViewBasicDataType,
@@ -15,7 +15,7 @@ import type { BlockModel } from '@blocksuite/store';
export function addProperty(
model: DatabaseBlockModel,
position: InsertToPosition,
column: Omit<Column, 'id'> & {
column: Omit<ColumnDataType, 'id'> & {
id?: string;
}
): string {
@@ -24,7 +24,7 @@ export function addProperty(
return id;
}
model.doc.transact(() => {
const col: Column = {
const col: ColumnDataType = {
...column,
id,
};
@@ -39,8 +39,8 @@ export function addProperty(
export function copyCellsByProperty(
model: DatabaseBlockModel,
fromId: Column['id'],
toId: Column['id']
fromId: ColumnDataType['id'],
toId: ColumnDataType['id']
) {
model.doc.transact(() => {
Object.keys(model.props.cells).forEach(rowId => {
@@ -57,7 +57,7 @@ export function copyCellsByProperty(
export function deleteColumn(
model: DatabaseBlockModel,
columnId: Column['id']
columnId: ColumnDataType['id']
) {
const index = model.props.columns.findIndex(v => v.id === columnId);
if (index < 0) return;
@@ -101,8 +101,8 @@ export function duplicateView(model: DatabaseBlockModel, id: string): string {
export function getCell(
model: DatabaseBlockModel,
rowId: BlockModel['id'],
columnId: Column['id']
): Cell | null {
columnId: ColumnDataType['id']
): CellDataType | null {
if (columnId === 'title') {
return {
columnId: 'title',
@@ -121,8 +121,8 @@ export function getCell(
export function getProperty(
model: DatabaseBlockModel,
id: Column['id']
): Column | undefined {
id: ColumnDataType['id']
): ColumnDataType | undefined {
return model.props.columns.find(v => v.id === id);
}
@@ -143,7 +143,7 @@ export function moveViewTo(
export function updateCell(
model: DatabaseBlockModel,
rowId: string,
cell: Cell
cell: CellDataType
) {
model.doc.transact(() => {
const columnId = cell.columnId;

View File

@@ -1,4 +1,4 @@
import type { Column } from '@blocksuite/affine-model';
import type { ColumnDataType } from '@blocksuite/affine-model';
import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
import { computed, type ReadonlySignal } from '@preact/signals-core';
@@ -256,7 +256,7 @@ export abstract class DataSourceBase implements DataSource {
protected abstract getNormalPropertyAndIndex(propertyId: string):
| {
column: Column<Record<string, unknown>>;
column: ColumnDataType<Record<string, unknown>>;
index: number;
}
| undefined;

View File

@@ -39,7 +39,7 @@ export type PropertyConfig<Data, RawValue = unknown, JsonValue = unknown> = {
data: Data;
}>
) => JsonValue;
fromJson: (
fromJson?: (
config: WithCommonPropertyConfig<{
value: JsonValue;
data: Data;

View File

@@ -1,2 +1,5 @@
export * from './cell';
export * from './property';
export * from './row';
export * from './single-view.js';
export * from './view-manager.js';

View File

@@ -5,13 +5,17 @@ import {
defineBlockSchema,
} from '@blocksuite/store';
import type { Column, SerializedCells, ViewBasicDataType } from './types.js';
import type {
ColumnDataType,
SerializedCells,
ViewBasicDataType,
} from './types.js';
export type DatabaseBlockProps = {
views: ViewBasicDataType[];
title: Text;
cells: SerializedCells;
columns: Array<Column>;
columns: Array<ColumnDataType>;
};
export class DatabaseBlockModel extends BlockModel<DatabaseBlockProps> {}

View File

@@ -1,4 +1,4 @@
export interface Column<
export interface ColumnDataType<
Data extends Record<string, unknown> = Record<string, unknown>,
> {
id: string;
@@ -7,13 +7,15 @@ export interface Column<
data: Data;
}
export type ColumnUpdater<T extends Column = Column> = (data: T) => Partial<T>;
export type Cell<ValueType = unknown> = {
columnId: Column['id'];
export type ColumnUpdater<T extends ColumnDataType = ColumnDataType> = (
data: T
) => Partial<T>;
export type CellDataType<ValueType = unknown> = {
columnId: ColumnDataType['id'];
value: ValueType;
};
export type SerializedCells = Record<string, Record<string, Cell>>;
export type SerializedCells = Record<string, Record<string, CellDataType>>;
export type ViewBasicDataType = {
id: string;
name: string;

View File

@@ -1,12 +1,14 @@
import { createIdentifier } from '@blocksuite/global/di';
import type { ExtensionType } from '@blocksuite/store';
import type { Signal } from '@preact/signals-core';
import type { ReadonlySignal } from '@preact/signals-core';
import type { AffineUserInfo } from './types';
export interface UserListService {
users$: Signal<AffineUserInfo[]>;
hasMore$: Signal<boolean>;
users$: ReadonlySignal<AffineUserInfo[]>;
isLoading$: ReadonlySignal<boolean>;
searchText$: ReadonlySignal<string>;
hasMore$: ReadonlySignal<boolean>;
loadMore(): void;
search(keyword: string): void;
}

View File

@@ -1,6 +1,6 @@
import {
databaseBlockColumns,
DatabaseBlockDataSource,
databaseBlockProperties,
} from '@blocksuite/affine/blocks/database';
import {
type DatabaseBlockModel,
@@ -46,13 +46,13 @@ export const database: InitFn = (collection: Workspace, id: string) => {
database.props.title = new Text(title);
const richTextId = datasource.propertyAdd(
'end',
databaseBlockColumns.richTextColumnConfig.type
databaseBlockProperties.richTextColumnConfig.type
);
Object.values([
propertyPresets.multiSelectPropertyConfig,
propertyPresets.datePropertyConfig,
propertyPresets.numberPropertyConfig,
databaseBlockColumns.linkColumnConfig,
databaseBlockProperties.linkColumnConfig,
propertyPresets.checkboxPropertyConfig,
propertyPresets.progressPropertyConfig,
]).forEach(column => {