refactor(editor): enable the noUncheckedIndexedAccess rule for the block-database package (#9691)
close: BS-2269
This commit is contained in:
@@ -10,11 +10,11 @@ import {
|
|||||||
type InlineHtmlAST,
|
type InlineHtmlAST,
|
||||||
TextUtils,
|
TextUtils,
|
||||||
} from '@blocksuite/affine-shared/adapters';
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
import type { DeltaInsert } from '@blocksuite/inline';
|
import { nanoid } from '@blocksuite/store';
|
||||||
import { type BlockSnapshot, nanoid } from '@blocksuite/store';
|
|
||||||
import { format } from 'date-fns/format';
|
|
||||||
import type { Element } from 'hast';
|
import type { Element } from 'hast';
|
||||||
|
|
||||||
|
import { processTable } from './utils';
|
||||||
|
|
||||||
const DATABASE_NODE_TYPES = new Set(['table', 'thead', 'tbody', 'th', 'tr']);
|
const DATABASE_NODE_TYPES = new Set(['table', 'thead', 'tbody', 'th', 'tr']);
|
||||||
|
|
||||||
export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||||
@@ -53,8 +53,12 @@ export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
|||||||
const rowId = nanoid();
|
const rowId = nanoid();
|
||||||
cells[rowId] = Object.create(null);
|
cells[rowId] = Object.create(null);
|
||||||
(row as Element).children.forEach((cell, index) => {
|
(row as Element).children.forEach((cell, index) => {
|
||||||
cells[rowId][viewsColumns[index].id] = {
|
const column = viewsColumns[index];
|
||||||
columnId: viewsColumns[index].id,
|
if (!column) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cells[rowId][column.id] = {
|
||||||
|
columnId: column.id,
|
||||||
value: TextUtils.createText(
|
value: TextUtils.createText(
|
||||||
(cell as Element).children
|
(cell as Element).children
|
||||||
.map(child => ('value' in child ? child.value : ''))
|
.map(child => ('value' in child ? child.value : ''))
|
||||||
@@ -65,14 +69,18 @@ export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Build database columns from table header row
|
// Build database columns from table header row
|
||||||
const columns = tableHeaderRow.children.map((_child, index) => {
|
const columns = tableHeaderRow.children.flatMap((_child, index) => {
|
||||||
|
const column = viewsColumns[index];
|
||||||
|
if (!column) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
type: index === 0 ? 'title' : 'rich-text',
|
type: index === 0 ? 'title' : 'rich-text',
|
||||||
name: (_child as Element).children
|
name: (_child as Element).children
|
||||||
.map(child => ('value' in child ? child.value : ''))
|
.map(child => ('value' in child ? child.value : ''))
|
||||||
.join(''),
|
.join(''),
|
||||||
data: {},
|
data: {},
|
||||||
id: viewsColumns[index].id,
|
id: column.id,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,6 +125,10 @@ export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
|||||||
// The first child of each table body row is the database title cell
|
// The first child of each table body row is the database title cell
|
||||||
if (o.node.tagName === 'tr') {
|
if (o.node.tagName === 'tr') {
|
||||||
const { deltaConverter } = context;
|
const { deltaConverter } = context;
|
||||||
|
const firstChild = o.node.children[0];
|
||||||
|
if (!firstChild) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
walkerContext
|
walkerContext
|
||||||
.openNode({
|
.openNode({
|
||||||
type: 'block',
|
type: 'block',
|
||||||
@@ -130,7 +142,7 @@ export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
|||||||
props: {
|
props: {
|
||||||
text: {
|
text: {
|
||||||
'$blocksuite:internal:text$': true,
|
'$blocksuite:internal:text$': true,
|
||||||
delta: deltaConverter.astToDelta(o.node.children[0]),
|
delta: deltaConverter.astToDelta(firstChild),
|
||||||
},
|
},
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
@@ -156,7 +168,7 @@ export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
|||||||
const columns = o.node.props.columns as Array<Column>;
|
const columns = o.node.props.columns as Array<Column>;
|
||||||
const children = o.node.children;
|
const children = o.node.children;
|
||||||
const cells = o.node.props.cells as SerializedCells;
|
const cells = o.node.props.cells as SerializedCells;
|
||||||
|
const table = processTable(columns, children, cells);
|
||||||
const createAstTableCell = (
|
const createAstTableCell = (
|
||||||
children: InlineHtmlAST[]
|
children: InlineHtmlAST[]
|
||||||
): InlineHtmlAST => ({
|
): InlineHtmlAST => ({
|
||||||
@@ -183,94 +195,40 @@ export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { deltaConverter } = context;
|
const { deltaConverter } = context;
|
||||||
const htmlAstRows = Array.prototype.map.call(
|
|
||||||
children,
|
|
||||||
(v: BlockSnapshot) => {
|
|
||||||
const rowCells = Array.prototype.map.call(columns, col => {
|
|
||||||
const cell = cells[v.id]?.[col.id];
|
|
||||||
if (!cell && col.type !== 'title') {
|
|
||||||
return createAstTableCell([{ type: 'text', value: '' }]);
|
|
||||||
}
|
|
||||||
switch (col.type) {
|
|
||||||
case 'rich-text':
|
|
||||||
return createAstTableCell(
|
|
||||||
deltaConverter.deltaToAST(
|
|
||||||
(cell.value as { delta: DeltaInsert[] }).delta
|
|
||||||
)
|
|
||||||
);
|
|
||||||
case 'title':
|
|
||||||
return createAstTableCell(
|
|
||||||
deltaConverter.deltaToAST(
|
|
||||||
(v.props.text as { delta: DeltaInsert[] }).delta
|
|
||||||
)
|
|
||||||
);
|
|
||||||
case 'date':
|
|
||||||
return createAstTableCell([
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
value: format(new Date(cell.value as number), 'yyyy-MM-dd'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
case 'select': {
|
|
||||||
const value =
|
|
||||||
(col.data.options.find(
|
|
||||||
(opt: Record<string, string>) => opt.id === cell.value
|
|
||||||
)?.value as string) ?? '';
|
|
||||||
return createAstTableCell([{ type: 'text', value }]);
|
|
||||||
}
|
|
||||||
case 'multi-select': {
|
|
||||||
const value = Array.prototype.map
|
|
||||||
.call(
|
|
||||||
cell.value,
|
|
||||||
val =>
|
|
||||||
col.data.options.find(
|
|
||||||
(opt: Record<string, string>) => val === opt.id
|
|
||||||
).value ?? ''
|
|
||||||
)
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(',');
|
|
||||||
return createAstTableCell([{ type: 'text', value }]);
|
|
||||||
}
|
|
||||||
case 'checkbox': {
|
|
||||||
return createAstTableCell([
|
|
||||||
{ type: 'text', value: String(cell.value) },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
|
||||||
default:
|
|
||||||
return createAstTableCell([
|
|
||||||
{ type: 'text', value: String(cell.value) },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}) as InlineHtmlAST[];
|
|
||||||
return createAstTableRow(rowCells);
|
|
||||||
}
|
|
||||||
) as Element[];
|
|
||||||
|
|
||||||
// Handle first row (header).
|
|
||||||
const headerRow = createAstTableRow(
|
|
||||||
Array.prototype.map.call(columns, v =>
|
|
||||||
createAstTableHeaderCell([
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
value: v.name ?? '',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
) as Element[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const tableHeaderAst: Element = {
|
const tableHeaderAst: Element = {
|
||||||
type: 'element',
|
type: 'element',
|
||||||
tagName: 'thead',
|
tagName: 'thead',
|
||||||
properties: Object.create(null),
|
properties: Object.create(null),
|
||||||
children: [headerRow],
|
children: [
|
||||||
|
createAstTableRow(
|
||||||
|
table.headers.map(v =>
|
||||||
|
createAstTableHeaderCell([
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
value: v.name ?? '',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const tableBodyAst: Element = {
|
const tableBodyAst: Element = {
|
||||||
type: 'element',
|
type: 'element',
|
||||||
tagName: 'tbody',
|
tagName: 'tbody',
|
||||||
properties: Object.create(null),
|
properties: Object.create(null),
|
||||||
children: [...htmlAstRows],
|
children: table.rows.map(v => {
|
||||||
|
return createAstTableRow(
|
||||||
|
v.cells.map(cell => {
|
||||||
|
return createAstTableCell(
|
||||||
|
typeof cell.value === 'string'
|
||||||
|
? [{ type: 'text', value: cell.value }]
|
||||||
|
: deltaConverter.deltaToAST(cell.value.delta)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
walkerContext
|
walkerContext
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import {
|
|||||||
type MarkdownAST,
|
type MarkdownAST,
|
||||||
TextUtils,
|
TextUtils,
|
||||||
} from '@blocksuite/affine-shared/adapters';
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
import type { DeltaInsert } from '@blocksuite/inline';
|
import { nanoid } from '@blocksuite/store';
|
||||||
import { type BlockSnapshot, nanoid } from '@blocksuite/store';
|
|
||||||
import { format } from 'date-fns/format';
|
|
||||||
import type { TableRow } from 'mdast';
|
import type { TableRow } from 'mdast';
|
||||||
|
|
||||||
|
import { processTable } from './utils';
|
||||||
|
|
||||||
const DATABASE_NODE_TYPES = new Set(['table', 'tableRow']);
|
const DATABASE_NODE_TYPES = new Set(['table', 'tableRow']);
|
||||||
|
|
||||||
const isDatabaseNode = (node: MarkdownAST) =>
|
const isDatabaseNode = (node: MarkdownAST) =>
|
||||||
@@ -28,7 +28,7 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
enter: (o, context) => {
|
enter: (o, context) => {
|
||||||
const { walkerContext } = context;
|
const { walkerContext } = context;
|
||||||
if (o.node.type === 'table') {
|
if (o.node.type === 'table') {
|
||||||
const viewsColumns = o.node.children[0].children.map(() => {
|
const viewsColumns = o.node.children[0]?.children.map(() => {
|
||||||
return {
|
return {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
hide: false,
|
hide: false,
|
||||||
@@ -40,8 +40,12 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
const rowId = nanoid();
|
const rowId = nanoid();
|
||||||
cells[rowId] = Object.create(null);
|
cells[rowId] = Object.create(null);
|
||||||
row.children.slice(1).forEach((cell, index) => {
|
row.children.slice(1).forEach((cell, index) => {
|
||||||
cells[rowId][viewsColumns[index + 1].id] = {
|
const column = viewsColumns?.[index + 1];
|
||||||
columnId: viewsColumns[index + 1].id,
|
if (!column) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cells[rowId][column.id] = {
|
||||||
|
columnId: column.id,
|
||||||
value: TextUtils.createText(
|
value: TextUtils.createText(
|
||||||
cell.children
|
cell.children
|
||||||
.map(child => ('value' in child ? child.value : ''))
|
.map(child => ('value' in child ? child.value : ''))
|
||||||
@@ -50,14 +54,14 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const columns = o.node.children[0].children.map((_child, index) => {
|
const columns = o.node.children[0]?.children.map((_child, index) => {
|
||||||
return {
|
return {
|
||||||
type: index === 0 ? 'title' : 'rich-text',
|
type: index === 0 ? 'title' : 'rich-text',
|
||||||
name: _child.children
|
name: _child.children
|
||||||
.map(child => ('value' in child ? child.value : ''))
|
.map(child => ('value' in child ? child.value : ''))
|
||||||
.join(''),
|
.join(''),
|
||||||
data: {},
|
data: {},
|
||||||
id: viewsColumns[index].id,
|
id: viewsColumns?.[index]?.id,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
walkerContext.openNode(
|
walkerContext.openNode(
|
||||||
@@ -78,7 +82,7 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
conditions: [],
|
conditions: [],
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
titleColumn: viewsColumns[0]?.id,
|
titleColumn: viewsColumns?.[0]?.id,
|
||||||
iconColumn: 'type',
|
iconColumn: 'type',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -103,6 +107,10 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
|
|
||||||
if (o.node.type === 'tableRow') {
|
if (o.node.type === 'tableRow') {
|
||||||
const { deltaConverter } = context;
|
const { deltaConverter } = context;
|
||||||
|
const firstChild = o.node.children[0];
|
||||||
|
if (!firstChild) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
walkerContext
|
walkerContext
|
||||||
.openNode({
|
.openNode({
|
||||||
type: 'block',
|
type: 'block',
|
||||||
@@ -116,7 +124,7 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
props: {
|
props: {
|
||||||
text: {
|
text: {
|
||||||
'$blocksuite:internal:text$': true,
|
'$blocksuite:internal:text$': true,
|
||||||
delta: deltaConverter.astToDelta(o.node.children[0]),
|
delta: deltaConverter.astToDelta(firstChild),
|
||||||
},
|
},
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
@@ -140,101 +148,25 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
const columns = o.node.props.columns as Array<Column>;
|
const columns = o.node.props.columns as Array<Column>;
|
||||||
const children = o.node.children;
|
const children = o.node.children;
|
||||||
const cells = o.node.props.cells as SerializedCells;
|
const cells = o.node.props.cells as SerializedCells;
|
||||||
const createAstCell = (children: MarkdownAST[]) => ({
|
const table = processTable(columns, children, cells);
|
||||||
type: 'tableCell',
|
rows.push({
|
||||||
children,
|
type: 'tableRow',
|
||||||
|
children: table.headers.map(v => ({
|
||||||
|
type: 'tableCell',
|
||||||
|
children: [{ type: 'text', value: v.name }],
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
const mdAstCells = Array.prototype.map.call(
|
table.rows.forEach(v => {
|
||||||
children,
|
|
||||||
(v: BlockSnapshot) =>
|
|
||||||
Array.prototype.map.call(columns, col => {
|
|
||||||
const cell = cells[v.id]?.[col.id];
|
|
||||||
if (!cell && col.type !== 'title') {
|
|
||||||
return createAstCell([{ type: 'text', value: '' }]);
|
|
||||||
}
|
|
||||||
switch (col.type) {
|
|
||||||
case 'link':
|
|
||||||
case 'progress':
|
|
||||||
case 'number':
|
|
||||||
return createAstCell([
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
value: cell.value as string,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
case 'rich-text':
|
|
||||||
return createAstCell(
|
|
||||||
deltaConverter.deltaToAST(
|
|
||||||
(cell.value as { delta: DeltaInsert[] }).delta
|
|
||||||
)
|
|
||||||
);
|
|
||||||
case 'title':
|
|
||||||
return createAstCell(
|
|
||||||
deltaConverter.deltaToAST(
|
|
||||||
(v.props.text as { delta: DeltaInsert[] }).delta
|
|
||||||
)
|
|
||||||
);
|
|
||||||
case 'date':
|
|
||||||
return createAstCell([
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
value: format(
|
|
||||||
new Date(cell.value as number),
|
|
||||||
'yyyy-MM-dd'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
case 'select': {
|
|
||||||
const value = col.data.options.find(
|
|
||||||
(opt: Record<string, string>) => opt.id === cell.value
|
|
||||||
)?.value;
|
|
||||||
return createAstCell([{ type: 'text', value }]);
|
|
||||||
}
|
|
||||||
case 'multi-select': {
|
|
||||||
const value = Array.prototype.map
|
|
||||||
.call(
|
|
||||||
cell.value,
|
|
||||||
val =>
|
|
||||||
col.data.options.find(
|
|
||||||
(opt: Record<string, string>) => val === opt.id
|
|
||||||
).value
|
|
||||||
)
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(',');
|
|
||||||
return createAstCell([{ type: 'text', value }]);
|
|
||||||
}
|
|
||||||
case 'checkbox': {
|
|
||||||
return createAstCell([
|
|
||||||
{ type: 'text', value: cell.value as string },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
|
||||||
default:
|
|
||||||
return createAstCell([
|
|
||||||
{ type: 'text', value: cell.value as string },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle first row.
|
|
||||||
if (Array.isArray(columns)) {
|
|
||||||
rows.push({
|
rows.push({
|
||||||
type: 'tableRow',
|
type: 'tableRow',
|
||||||
children: Array.prototype.map.call(columns, v =>
|
children: v.cells.map(v => ({
|
||||||
createAstCell([
|
type: 'tableCell',
|
||||||
{
|
children:
|
||||||
type: 'text',
|
typeof v.value === 'string'
|
||||||
value: v.name,
|
? [{ type: 'text', value: v.value }]
|
||||||
},
|
: deltaConverter.deltaToAST(v.value.delta),
|
||||||
])
|
})),
|
||||||
) as [],
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Handle 2-... rows
|
|
||||||
Array.prototype.forEach.call(mdAstCells, children => {
|
|
||||||
rows.push({ type: 'tableRow', children });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
walkerContext
|
walkerContext
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
|||||||
const columnTypeClass = HastUtils.querySelector(o.node, 'svg')
|
const columnTypeClass = HastUtils.querySelector(o.node, 'svg')
|
||||||
?.properties?.className;
|
?.properties?.className;
|
||||||
const columnType = Array.isArray(columnTypeClass)
|
const columnType = Array.isArray(columnTypeClass)
|
||||||
? (ColumnClassMap[columnTypeClass[0]] ?? 'rich-text')
|
? (ColumnClassMap[columnTypeClass[0] ?? ''] ?? 'rich-text')
|
||||||
: 'rich-text';
|
: 'rich-text';
|
||||||
walkerContext.pushGlobalContextStack<BlocksuiteTableColumn>(
|
walkerContext.pushGlobalContextStack<BlocksuiteTableColumn>(
|
||||||
'hast:table:column',
|
'hast:table:column',
|
||||||
@@ -132,8 +132,12 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
|||||||
children: [],
|
children: [],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
row[columns[index].id] = {
|
const column = columns[index];
|
||||||
columnId: columns[index].id,
|
if (!column) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
row[column.id] = {
|
||||||
|
columnId: column.id,
|
||||||
value: HastUtils.getTextContent(child),
|
value: HastUtils.getTextContent(child),
|
||||||
};
|
};
|
||||||
} else if (HastUtils.querySelector(child, '.cell-title')) {
|
} else if (HastUtils.querySelector(child, '.cell-title')) {
|
||||||
@@ -157,33 +161,35 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const optionIds: string[] = [];
|
const optionIds: string[] = [];
|
||||||
|
const column = columns[index];
|
||||||
|
if (!column) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (HastUtils.querySelector(child, '.selected-value')) {
|
if (HastUtils.querySelector(child, '.selected-value')) {
|
||||||
if (!('options' in columns[index].data)) {
|
if (!('options' in column.data)) {
|
||||||
columns[index].data.options = [];
|
column.data.options = [];
|
||||||
|
}
|
||||||
|
if (!['multi-select', 'select'].includes(column.type)) {
|
||||||
|
column.type = 'select';
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!['multi-select', 'select'].includes(columns[index].type)
|
column.type === 'select' &&
|
||||||
) {
|
|
||||||
columns[index].type = 'select';
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
columns[index].type === 'select' &&
|
|
||||||
child.type === 'element' &&
|
child.type === 'element' &&
|
||||||
child.children.length > 1
|
child.children.length > 1
|
||||||
) {
|
) {
|
||||||
columns[index].type = 'multi-select';
|
column.type = 'multi-select';
|
||||||
}
|
}
|
||||||
child.type === 'element' &&
|
child.type === 'element' &&
|
||||||
child.children.forEach(span => {
|
child.children.forEach(span => {
|
||||||
const filteredArray = columns[index].data.options?.filter(
|
const filteredArray = column.data.options?.filter(
|
||||||
option =>
|
option =>
|
||||||
option.value === HastUtils.getTextContent(span)
|
option.value === HastUtils.getTextContent(span)
|
||||||
);
|
);
|
||||||
const id = filteredArray?.length
|
const id = filteredArray?.length
|
||||||
? filteredArray[0].id
|
? (filteredArray[0]?.id ?? nanoid())
|
||||||
: nanoid();
|
: nanoid();
|
||||||
if (!filteredArray?.length) {
|
if (!filteredArray?.length) {
|
||||||
columns[index].data.options?.push({
|
column.data.options?.push({
|
||||||
id,
|
id,
|
||||||
value: HastUtils.getTextContent(span),
|
value: HastUtils.getTextContent(span),
|
||||||
color: getTagColor(),
|
color: getTagColor(),
|
||||||
@@ -192,48 +198,48 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
|||||||
optionIds.push(id);
|
optionIds.push(id);
|
||||||
});
|
});
|
||||||
// Expand will be done when leaving the table
|
// Expand will be done when leaving the table
|
||||||
row[columns[index].id] = {
|
row[column.id] = {
|
||||||
columnId: columns[index].id,
|
columnId: column.id,
|
||||||
value: optionIds,
|
value: optionIds,
|
||||||
};
|
};
|
||||||
} else if (HastUtils.querySelector(child, '.checkbox')) {
|
} else if (HastUtils.querySelector(child, '.checkbox')) {
|
||||||
if (columns[index].type !== 'checkbox') {
|
if (column.type !== 'checkbox') {
|
||||||
columns[index].type = 'checkbox';
|
column.type = 'checkbox';
|
||||||
}
|
}
|
||||||
row[columns[index].id] = {
|
row[column.id] = {
|
||||||
columnId: columns[index].id,
|
columnId: column.id,
|
||||||
value: HastUtils.querySelector(child, '.checkbox-on')
|
value: HastUtils.querySelector(child, '.checkbox-on')
|
||||||
? true
|
? true
|
||||||
: false,
|
: false,
|
||||||
};
|
};
|
||||||
} else if (columns[index].type === 'number') {
|
} else if (column.type === 'number') {
|
||||||
const text = HastUtils.getTextContent(child);
|
const text = HastUtils.getTextContent(child);
|
||||||
const number = Number(text);
|
const number = Number(text);
|
||||||
if (Number.isNaN(number)) {
|
if (Number.isNaN(number)) {
|
||||||
columns[index].type = 'rich-text';
|
column.type = 'rich-text';
|
||||||
row[columns[index].id] = {
|
row[column.id] = {
|
||||||
columnId: columns[index].id,
|
columnId: column.id,
|
||||||
value: TextUtils.createText(text),
|
value: TextUtils.createText(text),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
row[columns[index].id] = {
|
row[column.id] = {
|
||||||
columnId: columns[index].id,
|
columnId: column.id,
|
||||||
value: number,
|
value: number,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
row[columns[index].id] = {
|
row[column.id] = {
|
||||||
columnId: columns[index].id,
|
columnId: column.id,
|
||||||
value: HastUtils.getTextContent(child),
|
value: HastUtils.getTextContent(child),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
columns[index].type === 'rich-text' &&
|
column.type === 'rich-text' &&
|
||||||
!TextUtils.isText(row[columns[index].id].value)
|
!TextUtils.isText(row[column.id].value)
|
||||||
) {
|
) {
|
||||||
row[columns[index].id] = {
|
row[column.id] = {
|
||||||
columnId: columns[index].id,
|
columnId: column.id,
|
||||||
value: TextUtils.createText(row[columns[index].id].value),
|
value: TextUtils.createText(row[column.id].value),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -264,11 +270,15 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
|||||||
.getGlobalContextStack<BlocksuiteTableRow>('hast:table:rows')
|
.getGlobalContextStack<BlocksuiteTableRow>('hast:table:rows')
|
||||||
.forEach((row, i) => {
|
.forEach((row, i) => {
|
||||||
Object.keys(row).forEach(columnId => {
|
Object.keys(row).forEach(columnId => {
|
||||||
|
const cell = row[columnId];
|
||||||
|
if (!cell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
columns.find(column => column.id === columnId)?.type ===
|
columns.find(column => column.id === columnId)?.type ===
|
||||||
'select'
|
'select'
|
||||||
) {
|
) {
|
||||||
row[columnId].value = (row[columnId].value as string[])[0];
|
cell.value = (cell.value as string[])[0];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cells[children.at(i)?.id ?? nanoid()] = row;
|
cells[children.at(i)?.id ?? nanoid()] = row;
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ import {
|
|||||||
BlockPlainTextAdapterExtension,
|
BlockPlainTextAdapterExtension,
|
||||||
type BlockPlainTextAdapterMatcher,
|
type BlockPlainTextAdapterMatcher,
|
||||||
} from '@blocksuite/affine-shared/adapters';
|
} 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';
|
import { formatTable, processTable } from './utils.js';
|
||||||
|
|
||||||
export const databaseBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
|
export const databaseBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
|
||||||
{
|
{
|
||||||
@@ -26,63 +23,22 @@ export const databaseBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher
|
|||||||
const columns = o.node.props.columns as Array<Column>;
|
const columns = o.node.props.columns as Array<Column>;
|
||||||
const children = o.node.children;
|
const children = o.node.children;
|
||||||
const cells = o.node.props.cells as SerializedCells;
|
const cells = o.node.props.cells as SerializedCells;
|
||||||
const tableCells = children.map((v: BlockSnapshot) =>
|
const table = processTable(columns, children, cells);
|
||||||
columns.map(col => {
|
rows.push(table.headers.map(v => v.name));
|
||||||
const cell = cells[v.id]?.[col.id];
|
table.rows.forEach(v => {
|
||||||
if (!cell && col.type !== 'title') {
|
rows.push(
|
||||||
return '';
|
v.cells.map(v =>
|
||||||
}
|
typeof v.value === 'string'
|
||||||
switch (col.type) {
|
? v.value
|
||||||
case 'rich-text':
|
: deltaConverter.deltaToAST(v.value.delta).join('')
|
||||||
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);
|
const tableString = formatTable(rows);
|
||||||
|
|
||||||
context.textBuffer.content += tableString;
|
context.textBuffer.content += tableString;
|
||||||
context.textBuffer.content += '\n';
|
context.textBuffer.content += '\n';
|
||||||
|
|
||||||
walkerContext.skipAllChildren();
|
walkerContext.skipAllChildren();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
|
import type { Column, SerializedCells } from '@blocksuite/affine-model';
|
||||||
|
import type { DeltaInsert } from '@blocksuite/inline';
|
||||||
|
import type { BlockSnapshot } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { databaseBlockModels } from '../properties/model';
|
||||||
|
|
||||||
function calculateColumnWidths(rows: string[][]): number[] {
|
function calculateColumnWidths(rows: string[][]): number[] {
|
||||||
return rows[0].map((_, colIndex) =>
|
return (
|
||||||
Math.max(...rows.map(row => (row[colIndex] || '').length))
|
rows[0]?.map((_, colIndex) =>
|
||||||
|
Math.max(...rows.map(row => (row[colIndex] || '').length))
|
||||||
|
) ?? []
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,7 +18,7 @@ function formatRow(
|
|||||||
isHeader: boolean
|
isHeader: boolean
|
||||||
): string {
|
): string {
|
||||||
const cells = row.map((cell, colIndex) =>
|
const cells = row.map((cell, colIndex) =>
|
||||||
cell.padEnd(columnWidths[colIndex], ' ')
|
cell?.padEnd(columnWidths[colIndex] ?? 0, ' ')
|
||||||
);
|
);
|
||||||
const rowString = `| ${cells.join(' | ')} |`;
|
const rowString = `| ${cells.join(' | ')} |`;
|
||||||
return isHeader
|
return isHeader
|
||||||
@@ -30,3 +38,73 @@ export function formatTable(rows: string[][]): string {
|
|||||||
);
|
);
|
||||||
return formattedRows.join('\n');
|
return formattedRows.join('\n');
|
||||||
}
|
}
|
||||||
|
export const isDelta = (value: unknown): value is { delta: DeltaInsert[] } => {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
return '$blocksuite:internal:text$' in value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
type Table = {
|
||||||
|
headers: Column[];
|
||||||
|
rows: Row[];
|
||||||
|
};
|
||||||
|
type Row = {
|
||||||
|
cells: Cell[];
|
||||||
|
};
|
||||||
|
type Cell = {
|
||||||
|
value: string | { delta: DeltaInsert[] };
|
||||||
|
};
|
||||||
|
export const processTable = (
|
||||||
|
columns: Column[],
|
||||||
|
children: BlockSnapshot[],
|
||||||
|
cells: SerializedCells
|
||||||
|
): Table => {
|
||||||
|
const table: Table = {
|
||||||
|
headers: columns,
|
||||||
|
rows: [],
|
||||||
|
};
|
||||||
|
children.forEach(v => {
|
||||||
|
const row: Row = {
|
||||||
|
cells: [],
|
||||||
|
};
|
||||||
|
const title = v.props.text;
|
||||||
|
if (isDelta(title)) {
|
||||||
|
row.cells.push({
|
||||||
|
value: title,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
row.cells.push({
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.forEach(col => {
|
||||||
|
const property = databaseBlockModels[col.type];
|
||||||
|
const cell = cells[v.id]?.[col.id];
|
||||||
|
if (col.type === 'title') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!cell || !property) {
|
||||||
|
row.cells.push({
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value: string | { delta: DeltaInsert[] };
|
||||||
|
if (isDelta(cell.value)) {
|
||||||
|
value = cell.value;
|
||||||
|
} else {
|
||||||
|
value = property.config.cellToString({
|
||||||
|
value: cell.value,
|
||||||
|
data: col.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
row.cells.push({
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
table.rows.push(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
return table;
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,22 +18,24 @@ export const insertDatabaseBlockCommand: Command<
|
|||||||
: selectedModels[selectedModels.length - 1];
|
: selectedModels[selectedModels.length - 1];
|
||||||
|
|
||||||
const service = std.getService('affine:database');
|
const service = std.getService('affine:database');
|
||||||
if (!service) return;
|
if (!service || !targetModel) return;
|
||||||
|
|
||||||
const result = std.store.addSiblingBlocks(
|
const result = std.store.addSiblingBlocks(
|
||||||
targetModel,
|
targetModel,
|
||||||
[{ flavour: 'affine:database' }],
|
[{ flavour: 'affine:database' }],
|
||||||
place
|
place
|
||||||
);
|
);
|
||||||
if (result.length === 0) return;
|
const string = result[0];
|
||||||
|
|
||||||
service.initDatabaseBlock(std.store, targetModel, result[0], viewType, false);
|
if (string == null) return;
|
||||||
|
|
||||||
|
service.initDatabaseBlock(std.store, targetModel, string, viewType, false);
|
||||||
|
|
||||||
if (removeEmptyLine && targetModel.text?.length === 0) {
|
if (removeEmptyLine && targetModel.text?.length === 0) {
|
||||||
std.store.deleteBlock(targetModel);
|
std.store.deleteBlock(targetModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
next({ insertedDatabaseBlockId: result[0] });
|
next({ insertedDatabaseBlockId: string });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const commands: BlockCommands = {
|
export const commands: BlockCommands = {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
} from '@blocksuite/data-view';
|
} from '@blocksuite/data-view';
|
||||||
import { propertyPresets } from '@blocksuite/data-view/property-presets';
|
import { propertyPresets } from '@blocksuite/data-view/property-presets';
|
||||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||||
|
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { type BlockModel, nanoid, Text } from '@blocksuite/store';
|
import { type BlockModel, nanoid, Text } from '@blocksuite/store';
|
||||||
import { computed, type ReadonlySignal } from '@preact/signals-core';
|
import { computed, type ReadonlySignal } from '@preact/signals-core';
|
||||||
@@ -28,7 +29,7 @@ import {
|
|||||||
databaseBlockPropertyList,
|
databaseBlockPropertyList,
|
||||||
databasePropertyConverts,
|
databasePropertyConverts,
|
||||||
} from './properties/index.js';
|
} from './properties/index.js';
|
||||||
import { titlePurePropertyConfig } from './properties/title/define.js';
|
import { titlePropertyModelConfig } from './properties/title/define.js';
|
||||||
import {
|
import {
|
||||||
addProperty,
|
addProperty,
|
||||||
applyCellsUpdate,
|
applyCellsUpdate,
|
||||||
@@ -180,12 +181,13 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
|||||||
|
|
||||||
propertyAdd(insertToPosition: InsertToPosition, type?: string): string {
|
propertyAdd(insertToPosition: InsertToPosition, type?: string): string {
|
||||||
this.doc.captureSync();
|
this.doc.captureSync();
|
||||||
|
const property = this.propertyMetaGet(
|
||||||
|
type ?? propertyPresets.multiSelectPropertyConfig.type
|
||||||
|
);
|
||||||
const result = addProperty(
|
const result = addProperty(
|
||||||
this._model,
|
this._model,
|
||||||
insertToPosition,
|
insertToPosition,
|
||||||
databaseBlockAllPropertyMap[
|
property.create(this.newPropertyName())
|
||||||
type ?? propertyPresets.multiSelectPropertyConfig.type
|
|
||||||
].create(this.newPropertyName())
|
|
||||||
);
|
);
|
||||||
applyPropertyUpdate(this._model);
|
applyPropertyUpdate(this._model);
|
||||||
return result;
|
return result;
|
||||||
@@ -251,7 +253,14 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
propertyMetaGet(type: string): PropertyMetaConfig {
|
propertyMetaGet(type: string): PropertyMetaConfig {
|
||||||
return databaseBlockAllPropertyMap[type];
|
const property = databaseBlockAllPropertyMap[type];
|
||||||
|
if (!property) {
|
||||||
|
throw new BlockSuiteError(
|
||||||
|
ErrorCode.DatabaseBlockError,
|
||||||
|
`Unknown property type: ${type}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return property;
|
||||||
}
|
}
|
||||||
|
|
||||||
propertyNameGet(propertyId: string): string {
|
propertyNameGet(propertyId: string): string {
|
||||||
@@ -298,7 +307,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
|||||||
|
|
||||||
currentCells as any
|
currentCells as any
|
||||||
) ?? {
|
) ?? {
|
||||||
property: databaseBlockAllPropertyMap[toType].config.defaultData(),
|
property: this.propertyMetaGet(toType).config.defaultData(),
|
||||||
cells: currentCells.map(() => undefined),
|
cells: currentCells.map(() => undefined),
|
||||||
};
|
};
|
||||||
this.doc.captureSync();
|
this.doc.captureSync();
|
||||||
@@ -309,7 +318,10 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
|||||||
const cells: Record<string, unknown> = {};
|
const cells: Record<string, unknown> = {};
|
||||||
currentCells.forEach((value, i) => {
|
currentCells.forEach((value, i) => {
|
||||||
if (value != null || result.cells[i] != null) {
|
if (value != null || result.cells[i] != null) {
|
||||||
cells[rows[i]] = result.cells[i];
|
const rowId = rows[i];
|
||||||
|
if (rowId) {
|
||||||
|
cells[rowId] = result.cells[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateCells(this._model, propertyId, cells);
|
updateCells(this._model, propertyId, cells);
|
||||||
@@ -381,7 +393,14 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewMetaGet(type: string): ViewMeta {
|
viewMetaGet(type: string): ViewMeta {
|
||||||
return databaseBlockViewMap[type];
|
const view = databaseBlockViewMap[type];
|
||||||
|
if (!view) {
|
||||||
|
throw new BlockSuiteError(
|
||||||
|
ErrorCode.DatabaseBlockError,
|
||||||
|
`Unknown view type: ${type}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewMetaGetById(viewId: string): ViewMeta {
|
viewMetaGetById(viewId: string): ViewMeta {
|
||||||
@@ -404,7 +423,7 @@ export const databaseViewInitEmpty = (
|
|||||||
addProperty(
|
addProperty(
|
||||||
model,
|
model,
|
||||||
'start',
|
'start',
|
||||||
titlePurePropertyConfig.create(titlePurePropertyConfig.config.name)
|
titlePropertyModelConfig.create(titlePropertyModelConfig.config.name)
|
||||||
);
|
);
|
||||||
databaseViewAddView(model, viewType);
|
databaseViewAddView(model, viewType);
|
||||||
};
|
};
|
||||||
@@ -423,7 +442,7 @@ export const databaseViewInitTemplate = (
|
|||||||
model: DatabaseBlockModel,
|
model: DatabaseBlockModel,
|
||||||
viewType: string
|
viewType: string
|
||||||
) => {
|
) => {
|
||||||
const ids = [nanoid(), nanoid(), nanoid()];
|
const ids = [nanoid(), nanoid(), nanoid()] as const;
|
||||||
const statusId = addProperty(
|
const statusId = addProperty(
|
||||||
model,
|
model,
|
||||||
'end',
|
'end',
|
||||||
@@ -470,11 +489,12 @@ export const convertToDatabase = (host: EditorHost, viewType: string) => {
|
|||||||
})
|
})
|
||||||
.run();
|
.run();
|
||||||
const { selectedModels } = ctx;
|
const { selectedModels } = ctx;
|
||||||
if (!selectedModels || selectedModels.length === 0) return;
|
const firstModel = selectedModels?.[0];
|
||||||
|
if (!firstModel) return;
|
||||||
|
|
||||||
host.doc.captureSync();
|
host.doc.captureSync();
|
||||||
|
|
||||||
const parentModel = host.doc.getParent(selectedModels[0]);
|
const parentModel = host.doc.getParent(firstModel);
|
||||||
if (!parentModel) {
|
if (!parentModel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -483,7 +503,7 @@ export const convertToDatabase = (host: EditorHost, viewType: string) => {
|
|||||||
'affine:database',
|
'affine:database',
|
||||||
{},
|
{},
|
||||||
parentModel,
|
parentModel,
|
||||||
parentModel.children.indexOf(selectedModels[0])
|
parentModel.children.indexOf(firstModel)
|
||||||
);
|
);
|
||||||
const databaseModel = host.doc.getBlock(id)?.model as
|
const databaseModel = host.doc.getBlock(id)?.model as
|
||||||
| DatabaseBlockModel
|
| DatabaseBlockModel
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import { presetPropertyConverts } from '@blocksuite/data-view/property-presets';
|
|||||||
import { propertyModelPresets } from '@blocksuite/data-view/property-pure-presets';
|
import { propertyModelPresets } from '@blocksuite/data-view/property-pure-presets';
|
||||||
import { nanoid, Text } from '@blocksuite/store';
|
import { nanoid, Text } from '@blocksuite/store';
|
||||||
|
|
||||||
import { richTextColumnModelConfig } from './rich-text/define.js';
|
import { richTextPropertyModelConfig } from './rich-text/define.js';
|
||||||
|
|
||||||
export const databasePropertyConverts = [
|
export const databasePropertyConverts = [
|
||||||
...presetPropertyConverts,
|
...presetPropertyConverts,
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
propertyModelPresets.selectPropertyModelConfig,
|
propertyModelPresets.selectPropertyModelConfig,
|
||||||
(_property, cells) => {
|
(_property, cells) => {
|
||||||
const options: Record<string, SelectTag> = {};
|
const options: Record<string, SelectTag> = {};
|
||||||
@@ -43,7 +43,7 @@ export const databasePropertyConverts = [
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
propertyModelPresets.multiSelectPropertyModelConfig,
|
propertyModelPresets.multiSelectPropertyModelConfig,
|
||||||
(_property, cells) => {
|
(_property, cells) => {
|
||||||
const options: Record<string, SelectTag> = {};
|
const options: Record<string, SelectTag> = {};
|
||||||
@@ -77,7 +77,7 @@ export const databasePropertyConverts = [
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
propertyModelPresets.numberPropertyModelConfig,
|
propertyModelPresets.numberPropertyModelConfig,
|
||||||
(_property, cells) => {
|
(_property, cells) => {
|
||||||
return {
|
return {
|
||||||
@@ -93,7 +93,7 @@ export const databasePropertyConverts = [
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
propertyModelPresets.progressPropertyModelConfig,
|
propertyModelPresets.progressPropertyModelConfig,
|
||||||
(_property, cells) => {
|
(_property, cells) => {
|
||||||
return {
|
return {
|
||||||
@@ -106,7 +106,7 @@ export const databasePropertyConverts = [
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
propertyModelPresets.checkboxPropertyModelConfig,
|
propertyModelPresets.checkboxPropertyModelConfig,
|
||||||
(_property, cells) => {
|
(_property, cells) => {
|
||||||
const truthyValues = new Set(['yes', 'true']);
|
const truthyValues = new Set(['yes', 'true']);
|
||||||
@@ -120,7 +120,7 @@ export const databasePropertyConverts = [
|
|||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
propertyModelPresets.checkboxPropertyModelConfig,
|
propertyModelPresets.checkboxPropertyModelConfig,
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
(_property, cells) => {
|
(_property, cells) => {
|
||||||
return {
|
return {
|
||||||
property: {},
|
property: {},
|
||||||
@@ -130,7 +130,7 @@ export const databasePropertyConverts = [
|
|||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
propertyModelPresets.multiSelectPropertyModelConfig,
|
propertyModelPresets.multiSelectPropertyModelConfig,
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
(property, cells) => {
|
(property, cells) => {
|
||||||
const optionMap = Object.fromEntries(
|
const optionMap = Object.fromEntries(
|
||||||
property.options.map(v => [v.id, v])
|
property.options.map(v => [v.id, v])
|
||||||
@@ -146,7 +146,7 @@ export const databasePropertyConverts = [
|
|||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
propertyModelPresets.numberPropertyModelConfig,
|
propertyModelPresets.numberPropertyModelConfig,
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
(_property, cells) => ({
|
(_property, cells) => ({
|
||||||
property: {},
|
property: {},
|
||||||
cells: cells.map(v => new Text(v?.toString()).yText),
|
cells: cells.map(v => new Text(v?.toString()).yText),
|
||||||
@@ -154,7 +154,7 @@ export const databasePropertyConverts = [
|
|||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
propertyModelPresets.progressPropertyModelConfig,
|
propertyModelPresets.progressPropertyModelConfig,
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
(_property, cells) => ({
|
(_property, cells) => ({
|
||||||
property: {},
|
property: {},
|
||||||
cells: cells.map(v => new Text(v?.toString()).yText),
|
cells: cells.map(v => new Text(v?.toString()).yText),
|
||||||
@@ -162,7 +162,7 @@ export const databasePropertyConverts = [
|
|||||||
),
|
),
|
||||||
createPropertyConvert(
|
createPropertyConvert(
|
||||||
propertyModelPresets.selectPropertyModelConfig,
|
propertyModelPresets.selectPropertyModelConfig,
|
||||||
richTextColumnModelConfig,
|
richTextPropertyModelConfig,
|
||||||
(property, cells) => {
|
(property, cells) => {
|
||||||
const optionMap = Object.fromEntries(
|
const optionMap = Object.fromEntries(
|
||||||
property.options.map(v => [v.id, v])
|
property.options.map(v => [v.id, v])
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { query, state } from 'lit/decorators.js';
|
|||||||
import { html } from 'lit/static-html.js';
|
import { html } from 'lit/static-html.js';
|
||||||
|
|
||||||
import { HostContextKey } from '../../context/host-context.js';
|
import { HostContextKey } from '../../context/host-context.js';
|
||||||
import { linkColumnModelConfig } from './define.js';
|
import { linkPropertyModelConfig } from './define.js';
|
||||||
|
|
||||||
export class LinkCell extends BaseCellRenderer<string> {
|
export class LinkCell extends BaseCellRenderer<string> {
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
@@ -247,7 +247,7 @@ export class LinkCellEditing extends BaseCellRenderer<string> {
|
|||||||
private accessor _container!: HTMLInputElement;
|
private accessor _container!: HTMLInputElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const linkColumnConfig = linkColumnModelConfig.createPropertyMeta({
|
export const linkColumnConfig = linkPropertyModelConfig.createPropertyMeta({
|
||||||
icon: createIcon('LinkIcon'),
|
icon: createIcon('LinkIcon'),
|
||||||
cellRenderer: {
|
cellRenderer: {
|
||||||
view: createFromBaseCellRenderer(LinkCell),
|
view: createFromBaseCellRenderer(LinkCell),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { propertyType, t } from '@blocksuite/data-view';
|
import { propertyType, t } from '@blocksuite/data-view';
|
||||||
|
|
||||||
export const linkColumnType = propertyType('link');
|
export const linkColumnType = propertyType('link');
|
||||||
export const linkColumnModelConfig = linkColumnType.modelConfig<string>({
|
export const linkPropertyModelConfig = linkColumnType.modelConfig<string>({
|
||||||
name: 'Link',
|
name: 'Link',
|
||||||
type: () => t.string.instance(),
|
type: () => t.string.instance(),
|
||||||
defaultData: () => ({}),
|
defaultData: () => ({}),
|
||||||
|
|||||||
20
blocksuite/affine/block-database/src/properties/model.ts
Normal file
20
blocksuite/affine/block-database/src/properties/model.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { PropertyModel } from '@blocksuite/data-view';
|
||||||
|
import { propertyModelPresets } from '@blocksuite/data-view/property-pure-presets';
|
||||||
|
|
||||||
|
import { linkPropertyModelConfig } from './link/define';
|
||||||
|
import { richTextPropertyModelConfig } from './rich-text/define';
|
||||||
|
import { titlePropertyModelConfig } from './title/define';
|
||||||
|
|
||||||
|
export const databaseBlockModels = Object.fromEntries(
|
||||||
|
[
|
||||||
|
propertyModelPresets.checkboxPropertyModelConfig,
|
||||||
|
propertyModelPresets.datePropertyModelConfig,
|
||||||
|
propertyModelPresets.numberPropertyModelConfig,
|
||||||
|
propertyModelPresets.progressPropertyModelConfig,
|
||||||
|
propertyModelPresets.selectPropertyModelConfig,
|
||||||
|
propertyModelPresets.multiSelectPropertyModelConfig,
|
||||||
|
linkPropertyModelConfig,
|
||||||
|
richTextPropertyModelConfig,
|
||||||
|
titlePropertyModelConfig,
|
||||||
|
].map(v => [v.type, v as PropertyModel])
|
||||||
|
);
|
||||||
@@ -31,7 +31,7 @@ import { html } from 'lit/static-html.js';
|
|||||||
|
|
||||||
import { HostContextKey } from '../../context/host-context.js';
|
import { HostContextKey } from '../../context/host-context.js';
|
||||||
import type { DatabaseBlockComponent } from '../../database-block.js';
|
import type { DatabaseBlockComponent } from '../../database-block.js';
|
||||||
import { richTextColumnModelConfig } from './define.js';
|
import { richTextPropertyModelConfig } from './define.js';
|
||||||
|
|
||||||
function toggleStyle(
|
function toggleStyle(
|
||||||
inlineEditor: AffineInlineEditor | null,
|
inlineEditor: AffineInlineEditor | null,
|
||||||
@@ -581,7 +581,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const richTextColumnConfig =
|
export const richTextColumnConfig =
|
||||||
richTextColumnModelConfig.createPropertyMeta({
|
richTextPropertyModelConfig.createPropertyMeta({
|
||||||
icon: createIcon('TextIcon'),
|
icon: createIcon('TextIcon'),
|
||||||
|
|
||||||
cellRenderer: {
|
cellRenderer: {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { type RichTextCellType, toYText } from '../utils.js';
|
|||||||
|
|
||||||
export const richTextColumnType = propertyType('rich-text');
|
export const richTextColumnType = propertyType('rich-text');
|
||||||
|
|
||||||
export const richTextColumnModelConfig =
|
export const richTextPropertyModelConfig =
|
||||||
richTextColumnType.modelConfig<RichTextCellType>({
|
richTextColumnType.modelConfig<RichTextCellType>({
|
||||||
name: 'Text',
|
name: 'Text',
|
||||||
type: () => t.richText.instance(),
|
type: () => t.richText.instance(),
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import {
|
|||||||
} from '@blocksuite/data-view';
|
} from '@blocksuite/data-view';
|
||||||
import { TableSingleView } from '@blocksuite/data-view/view-presets';
|
import { TableSingleView } from '@blocksuite/data-view/view-presets';
|
||||||
|
|
||||||
import { titlePurePropertyConfig } from './define.js';
|
import { titlePropertyModelConfig } from './define.js';
|
||||||
import { HeaderAreaTextCell, HeaderAreaTextCellEditing } from './text.js';
|
import { HeaderAreaTextCell, HeaderAreaTextCellEditing } from './text.js';
|
||||||
|
|
||||||
export const titleColumnConfig = titlePurePropertyConfig.createPropertyMeta({
|
export const titleColumnConfig = titlePropertyModelConfig.createPropertyMeta({
|
||||||
icon: createIcon('TitleIcon'),
|
icon: createIcon('TitleIcon'),
|
||||||
cellRenderer: {
|
cellRenderer: {
|
||||||
view: uniMap(
|
view: uniMap(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { isLinkedDoc } from '../../utils/title-doc.js';
|
|||||||
|
|
||||||
export const titleColumnType = propertyType('title');
|
export const titleColumnType = propertyType('title');
|
||||||
|
|
||||||
export const titlePurePropertyConfig = titleColumnType.modelConfig<Text>({
|
export const titlePropertyModelConfig = titleColumnType.modelConfig<Text>({
|
||||||
name: 'Title',
|
name: 'Title',
|
||||||
type: () => t.richText.instance(),
|
type: () => t.richText.instance(),
|
||||||
defaultData: () => ({}),
|
defaultData: () => ({}),
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ export function copyCellsByProperty(
|
|||||||
) {
|
) {
|
||||||
model.doc.transact(() => {
|
model.doc.transact(() => {
|
||||||
Object.keys(model.cells).forEach(rowId => {
|
Object.keys(model.cells).forEach(rowId => {
|
||||||
const cell = model.cells[rowId][fromId];
|
const cell = model.cells[rowId]?.[fromId];
|
||||||
if (cell) {
|
if (cell && model.cells[rowId]) {
|
||||||
model.cells[rowId][toId] = {
|
model.cells[rowId][toId] = {
|
||||||
...cell,
|
...cell,
|
||||||
columnId: toId,
|
columnId: toId,
|
||||||
@@ -186,14 +186,15 @@ export function updateCell(
|
|||||||
console.error('Invalid columnId');
|
console.error('Invalid columnId');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const hasRow = rowId in model.cells;
|
if (!model.cells[rowId]) {
|
||||||
if (!hasRow) {
|
|
||||||
model.cells[rowId] = Object.create(null);
|
model.cells[rowId] = Object.create(null);
|
||||||
}
|
}
|
||||||
model.cells[rowId][columnId] = {
|
if (model.cells[rowId]) {
|
||||||
columnId: columnId,
|
model.cells[rowId][columnId] = {
|
||||||
value: cell.value,
|
columnId: columnId,
|
||||||
};
|
value: cell.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,10 +215,12 @@ export function updateCells(
|
|||||||
if (!model.cells[rowId]) {
|
if (!model.cells[rowId]) {
|
||||||
model.cells[rowId] = Object.create(null);
|
model.cells[rowId] = Object.create(null);
|
||||||
}
|
}
|
||||||
model.cells[rowId][columnId] = {
|
if (model.cells[rowId]) {
|
||||||
columnId,
|
model.cells[rowId][columnId] = {
|
||||||
value,
|
columnId,
|
||||||
};
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -233,6 +236,9 @@ export function updateProperty(
|
|||||||
}
|
}
|
||||||
model.doc.transact(() => {
|
model.doc.transact(() => {
|
||||||
const column = model.columns[index];
|
const column = model.columns[index];
|
||||||
|
if (!column) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const result = updater(column);
|
const result = updater(column);
|
||||||
model.columns[index] = { ...column, ...result };
|
model.columns[index] = { ...column, ...result };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo",
|
||||||
|
"noUncheckedIndexedAccess": true
|
||||||
},
|
},
|
||||||
"include": ["./src"],
|
"include": ["./src"],
|
||||||
"references": [
|
"references": [
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ type Create<
|
|||||||
};
|
};
|
||||||
export type PropertyModel<
|
export type PropertyModel<
|
||||||
Type extends string = string,
|
Type extends string = string,
|
||||||
PropertyData extends Record<string, unknown> = Record<string, never>,
|
PropertyData extends Record<string, unknown> = Record<string, unknown>,
|
||||||
CellData = unknown,
|
CellData = unknown,
|
||||||
> = {
|
> = {
|
||||||
type: Type;
|
type: Type;
|
||||||
|
|||||||
@@ -38,12 +38,7 @@ export type PropertyConfig<
|
|||||||
value?: Value;
|
value?: Value;
|
||||||
}>
|
}>
|
||||||
) => unknown[];
|
) => unknown[];
|
||||||
cellToString: (
|
cellToString: (config: { value: Value; data: Data }) => string;
|
||||||
config: WithCommonPropertyConfig<{
|
|
||||||
value: Value;
|
|
||||||
data: Data;
|
|
||||||
}>
|
|
||||||
) => string;
|
|
||||||
cellFromString: (
|
cellFromString: (
|
||||||
config: WithCommonPropertyConfig<{
|
config: WithCommonPropertyConfig<{
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@@ -286,7 +286,6 @@ export abstract class SingleViewBase<
|
|||||||
this.dataSource.propertyMetaGet(type).config.cellToString({
|
this.dataSource.propertyMetaGet(type).config.cellToString({
|
||||||
value: this.dataSource.cellValueGet(rowId, propertyId),
|
value: this.dataSource.cellValueGet(rowId, propertyId),
|
||||||
data: this.propertyDataGet(propertyId),
|
data: this.propertyDataGet(propertyId),
|
||||||
dataSource: this.dataSource,
|
|
||||||
}) ?? ''
|
}) ?? ''
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { format } from 'date-fns/format';
|
||||||
|
import { parse } from 'date-fns/parse';
|
||||||
|
|
||||||
import { t } from '../../core/logical/type-presets.js';
|
import { t } from '../../core/logical/type-presets.js';
|
||||||
import { propertyType } from '../../core/property/property-config.js';
|
import { propertyType } from '../../core/property/property-config.js';
|
||||||
|
|
||||||
@@ -6,12 +9,12 @@ export const datePropertyModelConfig = datePropertyType.modelConfig<number>({
|
|||||||
name: 'Date',
|
name: 'Date',
|
||||||
type: () => t.date.instance(),
|
type: () => t.date.instance(),
|
||||||
defaultData: () => ({}),
|
defaultData: () => ({}),
|
||||||
cellToString: ({ value }) => value?.toString() ?? '',
|
cellToString: ({ value }) => format(value, 'yyyy-MM-dd'),
|
||||||
cellFromString: ({ value }) => {
|
cellFromString: ({ value }) => {
|
||||||
const isDateFormat = !isNaN(Date.parse(value));
|
const date = parse(value, 'yyyy-MM-dd', new Date());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: isDateFormat ? +new Date(value) : null,
|
value: +date,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
cellToJson: ({ value }) => value ?? null,
|
cellToJson: ({ value }) => value ?? null,
|
||||||
|
|||||||
@@ -1441,7 +1441,7 @@ describe('snapshot to html', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
const html = template(
|
const html = template(
|
||||||
'<table><thead><tr><th>Title</th><th>Status</th><th>Date</th><th>Number</th><th>Progress</th><th>MultiSelect</th><th>RichText</th><th>Link</th><th>Checkbox</th></tr></thead><tbody><tr><td>Task 1</td><td>TODO</td><td>2023-12-15</td><td>1</td><td>65</td><td>test1,test2</td><td><a href="https://google.com">test2</a></td><td>https://google.com</td><td>true</td></tr><tr><td>Task 2</td><td>In Progress</td><td>2023-12-20</td><td></td><td></td><td></td><td>test1</td><td></td><td></td></tr></tbody></table>'
|
'<table><thead><tr><th>Title</th><th>Status</th><th>Date</th><th>Number</th><th>Progress</th><th>MultiSelect</th><th>RichText</th><th>Link</th><th>Checkbox</th></tr></thead><tbody><tr><td>Task 1</td><td>TODO</td><td>2023-12-15</td><td>1</td><td>65</td><td>test1,test2</td><td><a href="https://google.com">test2</a></td><td>https://google.com</td><td>True</td></tr><tr><td>Task 2</td><td>In Progress</td><td>2023-12-20</td><td></td><td></td><td></td><td>test1</td><td></td><td></td></tr></tbody></table>'
|
||||||
);
|
);
|
||||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||||
const target = await htmlAdapter.fromBlockSnapshot({
|
const target = await htmlAdapter.fromBlockSnapshot({
|
||||||
|
|||||||
@@ -1684,7 +1684,7 @@ hhh
|
|||||||
const md = `\
|
const md = `\
|
||||||
| Title | Status | Date | Number | Progress | MultiSelect | RichText | Link | Checkbox |
|
| 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 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 | | |
|
| Task 2 | In Progress | 2023-12-20 | | | | test1 | | |
|
||||||
`;
|
`;
|
||||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
|
|||||||
@@ -1417,7 +1417,7 @@ describe('snapshot to plain text', () => {
|
|||||||
const plainText = `\
|
const plainText = `\
|
||||||
| Title | Status | Date | Number | Progress | MultiSelect | RichText | Link | Checkbox |
|
| 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 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 | | |
|
| Task 2 | In Progress | 2023-12-20 | | | | test1 | | |
|
||||||
`;
|
`;
|
||||||
const plainTextAdapter = new PlainTextAdapter(createJob(), provider);
|
const plainTextAdapter = new PlainTextAdapter(createJob(), provider);
|
||||||
|
|||||||
Reference in New Issue
Block a user