mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-29 16:19:43 +08:00
feat(core): add plain text adapter for database block (#9225)
[BS-2059](https://linear.app/affine-design/issue/BS-2059/补齐-database-plain-text-adapter)
This commit is contained in:
@@ -3,9 +3,11 @@ import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import { DatabaseBlockHtmlAdapterExtension } from './html.js';
|
||||
import { DatabaseBlockMarkdownAdapterExtension } from './markdown.js';
|
||||
import { DatabaseBlockNotionHtmlAdapterExtension } from './notion-html.js';
|
||||
import { DatabaseBlockPlainTextAdapterExtension } from './plain-text.js';
|
||||
|
||||
export const DatabaseBlockAdapterExtensions: ExtensionType[] = [
|
||||
DatabaseBlockHtmlAdapterExtension,
|
||||
DatabaseBlockMarkdownAdapterExtension,
|
||||
DatabaseBlockNotionHtmlAdapterExtension,
|
||||
DatabaseBlockPlainTextAdapterExtension,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
type Column,
|
||||
DatabaseBlockSchema,
|
||||
type SerializedCells,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockPlainTextAdapterExtension,
|
||||
type BlockPlainTextAdapterMatcher,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
import type { BlockSnapshot } from '@blocksuite/store';
|
||||
import { format } from 'date-fns/format';
|
||||
|
||||
import { formatTable } from './utils.js';
|
||||
|
||||
export const databaseBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
|
||||
{
|
||||
flavour: DatabaseBlockSchema.model.flavour,
|
||||
toMatch: () => false,
|
||||
fromMatch: o => o.node.flavour === DatabaseBlockSchema.model.flavour,
|
||||
toBlockSnapshot: {},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const { walkerContext, deltaConverter } = context;
|
||||
const rows: string[][] = [];
|
||||
const columns = o.node.props.columns as Array<Column>;
|
||||
const children = o.node.children;
|
||||
const cells = o.node.props.cells as SerializedCells;
|
||||
const tableCells = children.map((v: BlockSnapshot) =>
|
||||
columns.map(col => {
|
||||
const cell = cells[v.id]?.[col.id];
|
||||
if (!cell && col.type !== 'title') {
|
||||
return '';
|
||||
}
|
||||
switch (col.type) {
|
||||
case 'rich-text':
|
||||
return deltaConverter
|
||||
.deltaToAST((cell.value as { delta: DeltaInsert[] }).delta)
|
||||
.join('');
|
||||
case 'title':
|
||||
return deltaConverter
|
||||
.deltaToAST((v.props.text as { delta: DeltaInsert[] }).delta)
|
||||
.join('');
|
||||
case 'date':
|
||||
return format(new Date(cell.value as number), 'yyyy-MM-dd');
|
||||
case 'select': {
|
||||
const value = (
|
||||
col.data as { options: Array<Record<string, string>> }
|
||||
).options.find(opt => opt.id === cell.value)?.value;
|
||||
return value || '';
|
||||
}
|
||||
case 'multi-select': {
|
||||
const value = (cell.value as string[])
|
||||
.map(
|
||||
val =>
|
||||
(
|
||||
col.data as { options: Array<Record<string, string>> }
|
||||
).options.find(opt => val === opt.id)?.value
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join(',');
|
||||
return value || '';
|
||||
}
|
||||
default:
|
||||
return String(cell.value);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Handle first row.
|
||||
if (Array.isArray(columns)) {
|
||||
rows.push(columns.map(col => col.name));
|
||||
}
|
||||
|
||||
// Handle 2-... rows
|
||||
tableCells.forEach(children => {
|
||||
rows.push(children);
|
||||
});
|
||||
|
||||
// Convert rows to table string
|
||||
const tableString = formatTable(rows);
|
||||
|
||||
context.textBuffer.content += tableString;
|
||||
context.textBuffer.content += '\n';
|
||||
|
||||
walkerContext.skipAllChildren();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DatabaseBlockPlainTextAdapterExtension =
|
||||
BlockPlainTextAdapterExtension(databaseBlockPlainTextAdapterMatcher);
|
||||
@@ -0,0 +1,32 @@
|
||||
function calculateColumnWidths(rows: string[][]): number[] {
|
||||
return rows[0].map((_, colIndex) =>
|
||||
Math.max(...rows.map(row => (row[colIndex] || '').length))
|
||||
);
|
||||
}
|
||||
|
||||
function formatRow(
|
||||
row: string[],
|
||||
columnWidths: number[],
|
||||
isHeader: boolean
|
||||
): string {
|
||||
const cells = row.map((cell, colIndex) =>
|
||||
cell.padEnd(columnWidths[colIndex], ' ')
|
||||
);
|
||||
const rowString = `| ${cells.join(' | ')} |`;
|
||||
return isHeader
|
||||
? `${rowString}\n${formatSeparator(columnWidths)}`
|
||||
: rowString;
|
||||
}
|
||||
|
||||
function formatSeparator(columnWidths: number[]): string {
|
||||
const separator = columnWidths.map(width => '-'.repeat(width)).join(' | ');
|
||||
return `| ${separator} |`;
|
||||
}
|
||||
|
||||
export function formatTable(rows: string[][]): string {
|
||||
const columnWidths = calculateColumnWidths(rows);
|
||||
const formattedRows = rows.map((row, index) =>
|
||||
formatRow(row, columnWidths, index === 0)
|
||||
);
|
||||
return formattedRows.join('\n');
|
||||
}
|
||||
Reference in New Issue
Block a user