Skip to content

Commit

Permalink
feat(blocksuite): add plain text adapter for database block
Browse files Browse the repository at this point in the history
  • Loading branch information
donteatfriedrice committed Dec 20, 2024
1 parent 03dd354 commit 185ba27
Show file tree
Hide file tree
Showing 5 changed files with 353 additions and 0 deletions.
225 changes: 225 additions & 0 deletions blocksuite/blocks/src/__tests__/adapters/plain-text.unit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1186,4 +1186,229 @@ describe('snapshot to plain text', () => {
});
expect(target.file).toBe(plainText);
});

test('table', async () => {
const blockSnapshot: BlockSnapshot = {
type: 'block',
id: 'block:8Wb7CSJ9Qe',
flavour: 'affine:database',
props: {
cells: {
'block:P_-Wg7Rg9O': {
'block:qyo8q9VPWU': {
columnId: 'block:qyo8q9VPWU',
value: 'TKip9uc7Yx',
},
'block:5cglrBmAr3': {
columnId: 'block:5cglrBmAr3',
value: 1702598400000,
},
'block:8Fa0JQe7WY': {
columnId: 'block:8Fa0JQe7WY',
value: 1,
},
'block:5ej6StPuF_': {
columnId: 'block:5ej6StPuF_',
value: 65,
},
'block:DPhZ6JBziD': {
columnId: 'block:DPhZ6JBziD',
value: ['-2_QD3GZT1', '73UrEZWaKk'],
},
'block:O8dpIDiP7-': {
columnId: 'block:O8dpIDiP7-',
value: {
'$blocksuite:internal:text$': true,
delta: [
{
insert: 'test2',
attributes: {
link: 'https://google.com',
},
},
],
},
},
'block:U8lPD59MkF': {
columnId: 'block:U8lPD59MkF',
value: 'https://google.com',
},
'block:-DT7B0TafG': {
columnId: 'block:-DT7B0TafG',
value: true,
},
},
'block:0vhfgcHtPF': {
'block:qyo8q9VPWU': {
columnId: 'block:qyo8q9VPWU',
value: 'F2bgsaE3X2',
},
'block:O8dpIDiP7-': {
columnId: 'block:O8dpIDiP7-',
value: {
'$blocksuite:internal:text$': true,
delta: [
{
insert: 'test1',
},
],
},
},
'block:5cglrBmAr3': {
columnId: 'block:5cglrBmAr3',
value: 1703030400000,
},
},
'block:b4_02QXMAM': {
'block:qyo8q9VPWU': {
columnId: 'block:qyo8q9VPWU',
value: 'y3O1A2IHHu',
},
},
'block:W_eirvg7EJ': {
'block:qyo8q9VPWU': {
columnId: 'block:qyo8q9VPWU',
},
},
},
columns: [
{
type: 'title',
name: 'Title',
data: {},
id: 'block:2VfUaitjf9',
},
{
type: 'select',
name: 'Status',
data: {
options: [
{
id: 'TKip9uc7Yx',
color: 'var(--affine-tag-white)',
value: 'TODO',
},
{
id: 'F2bgsaE3X2',
color: 'var(--affine-tag-green)',
value: 'In Progress',
},
{
id: 'y3O1A2IHHu',
color: 'var(--affine-tag-gray)',
value: 'Done',
},
],
},
id: 'block:qyo8q9VPWU',
},
{
type: 'date',
name: 'Date',
data: {},
id: 'block:5cglrBmAr3',
},
{
type: 'number',
name: 'Number',
data: {
decimal: 0,
},
id: 'block:8Fa0JQe7WY',
},
{
type: 'progress',
name: 'Progress',
data: {},
id: 'block:5ej6StPuF_',
},
{
type: 'multi-select',
name: 'MultiSelect',
data: {
options: [
{
id: '73UrEZWaKk',
value: 'test2',
color: 'var(--affine-tag-purple)',
},
{
id: '-2_QD3GZT1',
value: 'test1',
color: 'var(--affine-tag-teal)',
},
],
},
id: 'block:DPhZ6JBziD',
},
{
type: 'rich-text',
name: 'RichText',
data: {},
id: 'block:O8dpIDiP7-',
},
{
type: 'link',
name: 'Link',
data: {},
id: 'block:U8lPD59MkF',
},
{
type: 'checkbox',
name: 'Checkbox',
data: {},
id: 'block:-DT7B0TafG',
},
],
},
children: [
{
type: 'block',
id: 'block:P_-Wg7Rg9O',
flavour: 'affine:paragraph',
props: {
type: 'text',
text: {
'$blocksuite:internal:text$': true,
delta: [
{
insert: 'Task 1',
},
],
},
},
children: [],
},
{
type: 'block',
id: 'block:0vhfgcHtPF',
flavour: 'affine:paragraph',
props: {
type: 'text',
text: {
'$blocksuite:internal:text$': true,
delta: [
{
insert: 'Task 2',
},
],
},
},
children: [],
},
],
};

const plainText = `\
| Title | Status | Date | Number | Progress | MultiSelect | RichText | Link | Checkbox |
| ------ | ----------- | ---------- | ------ | -------- | ----------- | ------------------------- | ------------------ | -------- |
| Task 1 | TODO | 2023-12-15 | 1 | 65 | test1,test2 | test2: https://google.com | https://google.com | true |
| Task 2 | In Progress | 2023-12-20 | | | | test1 | | |
`;
const plainTextAdapter = new PlainTextAdapter(createJob());
const target = await plainTextAdapter.fromBlockSnapshot({
snapshot: blockSnapshot,
});
expect(target.file).toBe(plainText);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { BlockPlainTextAdapterMatcher } from '@blocksuite/affine-shared/ada

import { bookmarkBlockPlainTextAdapterMatcher } from '../../../bookmark-block/adapters/plain-text.js';
import { codeBlockPlainTextAdapterMatcher } from '../../../code-block/adapters/plain-text.js';
import { databaseBlockPlainTextAdapterMatcher } from '../../../database-block/adapters/plain-text.js';
import { dividerBlockPlainTextAdapterMatcher } from '../../../divider-block/adapters/plain-text.js';
import { latexBlockPlainTextAdapterMatcher } from '../../../latex-block/adapters/plain-text.js';

Expand All @@ -29,4 +30,5 @@ export const defaultBlockPlainTextAdapterMatchers: BlockPlainTextAdapterMatcher[
embedLinkedDocBlockPlainTextAdapterMatcher,
embedSyncedDocBlockPlainTextAdapterMatcher,
latexBlockPlainTextAdapterMatcher,
databaseBlockPlainTextAdapterMatcher,
];
2 changes: 2 additions & 0 deletions blocksuite/blocks/src/database-block/adapters/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
];
92 changes: 92 additions & 0 deletions blocksuite/blocks/src/database-block/adapters/plain-text.ts
Original file line number Diff line number Diff line change
@@ -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);
32 changes: 32 additions & 0 deletions blocksuite/blocks/src/database-block/adapters/utils.ts
Original file line number Diff line number Diff line change
@@ -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');
}

0 comments on commit 185ba27

Please sign in to comment.