mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-03 02:20:19 +08:00
feat(editor): add created-time and created-by property for database block (#12156)
close: BS-3431 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added "Created By" property type and cell renderer, displaying creator's avatar and name in database blocks. - Introduced "Created Time" property type and cell renderer, showing formatted creation timestamps. - **Improvements** - Enhanced table and Kanban views with improved column and row movement, hiding, and statistics capabilities. - Streamlined property and row management with unified object handling and reactive signals for better performance and reliability. - Improved avatar display logic to handle removed or unnamed users gracefully. - Refactored property and row APIs to consolidate access patterns and support reactive updates. - Updated icon retrieval and reactive value access for improved UI responsiveness in database and Kanban cells. - Consolidated property and cell access methods to use "get or create" patterns ensuring consistent data availability. - Added locking mechanism to stabilize computed signals during locked states. - Modularized table and Kanban column and row abstractions for better encapsulation and maintainability. - **Bug Fixes** - Corrected row and column deletion, movement, and selection behaviors across table and Kanban views. - Fixed issues with property and row referencing, ensuring consistent handling of identifiers and objects. - Removed debugging logs and fixed method calls to align with updated APIs. - **Style** - Updated and simplified table column header styles for a cleaner appearance. - **Chores** - Added `@emotion/css` dependency for styling support. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -2,6 +2,7 @@ import type {
|
||||
ColumnDataType,
|
||||
ColumnUpdater,
|
||||
DatabaseBlockModel,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { getSelectedModelsCommand } from '@blocksuite/affine-shared/commands';
|
||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
@@ -51,7 +52,59 @@ import {
|
||||
databaseBlockViews,
|
||||
} from './views/index.js';
|
||||
|
||||
type SpacialProperty = {
|
||||
valueSet: (rowId: string, propertyId: string, value: unknown) => void;
|
||||
valueGet: (rowId: string, propertyId: string) => unknown;
|
||||
};
|
||||
export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
spacialProperties: Record<string, SpacialProperty> = {
|
||||
'created-time': {
|
||||
valueSet: () => {},
|
||||
valueGet: (rowId: string) => {
|
||||
const model = this.getModelById(rowId) as ParagraphBlockModel;
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
return model.props['meta:createdAt'];
|
||||
},
|
||||
},
|
||||
'created-by': {
|
||||
valueSet: () => {},
|
||||
valueGet: (rowId: string) => {
|
||||
const model = this.getModelById(rowId) as
|
||||
| ParagraphBlockModel
|
||||
| undefined;
|
||||
return model ? model.props['meta:createdBy'] : null;
|
||||
},
|
||||
},
|
||||
type: {
|
||||
valueSet: () => {},
|
||||
valueGet: (rowId: string) => {
|
||||
const model = this.getModelById(rowId);
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
return getIcon(model);
|
||||
},
|
||||
},
|
||||
title: {
|
||||
valueSet: () => {},
|
||||
valueGet: (rowId: string) => {
|
||||
const model = this.getModelById(rowId);
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
return model.text;
|
||||
},
|
||||
},
|
||||
};
|
||||
isSpacialProperty(propertyId: string): boolean {
|
||||
return this.spacialProperties[propertyId] !== undefined;
|
||||
}
|
||||
spacialValueGet(rowId: string, propertyId: string): unknown {
|
||||
return this.spacialProperties[propertyId]?.valueGet(rowId, propertyId);
|
||||
}
|
||||
|
||||
static externalProperties = signal<PropertyMetaConfig[]>([]);
|
||||
static propertiesList = computed(() => {
|
||||
return [
|
||||
@@ -196,20 +249,15 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
}
|
||||
|
||||
cellValueGet(rowId: string, propertyId: string): unknown {
|
||||
if (propertyId === 'type') {
|
||||
const model = this.getModelById(rowId);
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
return getIcon(model);
|
||||
if (this.isSpacialProperty(propertyId)) {
|
||||
return this.spacialValueGet(rowId, propertyId);
|
||||
}
|
||||
const type = this.propertyTypeGet(propertyId);
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
if (type === 'title') {
|
||||
const model = this.getModelById(rowId);
|
||||
return model?.text;
|
||||
return this.spacialValueGet(rowId, 'title');
|
||||
}
|
||||
const meta = this.propertyMetaGet(type);
|
||||
if (!meta) {
|
||||
|
||||
@@ -140,7 +140,7 @@ export class BlockRenderer
|
||||
return;
|
||||
}
|
||||
return html` <div class="database-block-detail-header-icon">
|
||||
${this.view.cellValueGet(this.rowId, iconColumn)}
|
||||
${this.view.cellGetOrCreate(this.rowId, iconColumn).value$.value}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DatabaseBlockComponent } from './database-block';
|
||||
import { DatabaseDndPreviewBlockComponent } from './database-dnd-preview-block';
|
||||
import { BlockRenderer } from './detail-panel/block-renderer';
|
||||
import { NoteRenderer } from './detail-panel/note-renderer';
|
||||
import { CreatedTimeCell } from './properties/created-time/cell-renderer';
|
||||
import { LinkCell } from './properties/link/cell-renderer';
|
||||
import { RichTextCell } from './properties/rich-text/cell-renderer';
|
||||
import { IconCell } from './properties/title/icon';
|
||||
@@ -15,6 +16,7 @@ export function effects() {
|
||||
customElements.define('affine-database-link-cell', LinkCell);
|
||||
customElements.define('data-view-header-area-text', HeaderAreaTextCell);
|
||||
customElements.define('affine-database-rich-text-cell', RichTextCell);
|
||||
customElements.define('affine-database-created-time-cell', CreatedTimeCell);
|
||||
customElements.define('center-peek', CenterPeek);
|
||||
customElements.define('database-datasource-note-renderer', NoteRenderer);
|
||||
customElements.define('database-datasource-block-renderer', BlockRenderer);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
BaseCellRenderer,
|
||||
createFromBaseCellRenderer,
|
||||
createIcon,
|
||||
} from '@blocksuite/data-view';
|
||||
import { css } from '@emotion/css';
|
||||
import { format } from 'date-fns/format';
|
||||
import { html } from 'lit';
|
||||
|
||||
import { createdTimePropertyModelConfig } from './define.js';
|
||||
const createdTimeCellStyle = css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
const textStyle = css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
export class CreatedTimeCell extends BaseCellRenderer<number, number> {
|
||||
renderContent() {
|
||||
const formattedDate = this.value
|
||||
? format(this.value, 'yyyy-MM-dd HH:mm:ss')
|
||||
: '';
|
||||
return html`<div class="${textStyle}">${formattedDate}</div>`;
|
||||
}
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.classList.add(createdTimeCellStyle);
|
||||
}
|
||||
|
||||
override beforeEnterEditMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<div class="date-container">${this.renderContent()}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export const createdTimeColumnConfig =
|
||||
createdTimePropertyModelConfig.createPropertyMeta({
|
||||
icon: createIcon('DateTimeIcon'),
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(CreatedTimeCell),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { propertyType, t } from '@blocksuite/data-view';
|
||||
import { format } from 'date-fns/format';
|
||||
import zod from 'zod';
|
||||
|
||||
export const createdTimeColumnType = propertyType('created-time');
|
||||
export const createdTimePropertyModelConfig = createdTimeColumnType.modelConfig(
|
||||
{
|
||||
name: 'Created Time',
|
||||
propertyData: {
|
||||
schema: zod.object({}),
|
||||
default: () => ({}),
|
||||
},
|
||||
jsonValue: {
|
||||
schema: zod.number().nullable(),
|
||||
isEmpty: () => false,
|
||||
type: () => t.date.instance(),
|
||||
},
|
||||
rawValue: {
|
||||
schema: zod.number().nullable(),
|
||||
default: () => null,
|
||||
toString: ({ value }) =>
|
||||
value != null ? format(value, 'yyyy-MM-dd HH:mm:ss') : '',
|
||||
fromString: () => {
|
||||
return { value: null };
|
||||
},
|
||||
toJson: ({ value }) => value,
|
||||
fromJson: ({ value }) => value,
|
||||
},
|
||||
fixed: {
|
||||
defaultData: {},
|
||||
defaultShow: false,
|
||||
defaultOrder: 'end',
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -1,5 +1,6 @@
|
||||
import { propertyPresets } from '@blocksuite/data-view/property-presets';
|
||||
|
||||
import { createdTimeColumnConfig } from './created-time/cell-renderer.js';
|
||||
import { linkColumnConfig } from './link/cell-renderer.js';
|
||||
import { richTextColumnConfig } from './rich-text/cell-renderer.js';
|
||||
import { titleColumnConfig } from './title/cell-renderer.js';
|
||||
@@ -24,4 +25,5 @@ export const databaseBlockProperties = {
|
||||
linkColumnConfig,
|
||||
richTextColumnConfig,
|
||||
titleColumnConfig,
|
||||
createdTimeColumnConfig,
|
||||
};
|
||||
|
||||
@@ -310,7 +310,6 @@ export class RichTextCell extends BaseCellRenderer<Text, string> {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(text);
|
||||
inlineEditor.insertText(inlineRange, text);
|
||||
inlineEditor.setInlineRange({
|
||||
index: inlineRange.index + text.length,
|
||||
|
||||
@@ -271,7 +271,8 @@ export class HeaderAreaTextCell extends BaseCellRenderer<Text, string> {
|
||||
const iconColumn = this.view.mainProperties$.value.iconColumn;
|
||||
if (!iconColumn) return;
|
||||
|
||||
const icon = this.view.cellValueGet(this.cell.rowId, iconColumn) as string;
|
||||
const icon = this.view.cellGetOrCreate(this.cell.rowId, iconColumn).value$
|
||||
.value;
|
||||
if (!icon) return;
|
||||
return icon;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user