mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-25 14:28:51 +08:00
feat(editor): simple table block (#9740)
close: BS-2122, BS-2125, BS-2124, BS-2420, PD-2073, BS-2126, BS-2469, BS-2470, BS-2478, BS-2471
This commit is contained in:
@@ -2,7 +2,11 @@ import {
|
||||
InlineDeltaToMarkdownAdapterExtensions,
|
||||
MarkdownInlineToDeltaAdapterExtensions,
|
||||
} from '@blocksuite/affine-components/rich-text';
|
||||
import { DefaultTheme, NoteDisplayMode } from '@blocksuite/affine-model';
|
||||
import {
|
||||
DefaultTheme,
|
||||
NoteDisplayMode,
|
||||
TableModelFlavour,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { MarkdownAdapter } from '@blocksuite/affine-shared/adapters';
|
||||
import { Container } from '@blocksuite/global/di';
|
||||
import type {
|
||||
@@ -3529,91 +3533,76 @@ bbb
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
background: {
|
||||
dark: '#000000',
|
||||
light: '#ffffff',
|
||||
},
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
displayMode: 'both',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:database',
|
||||
flavour: TableModelFlavour,
|
||||
props: {
|
||||
views: [
|
||||
{
|
||||
id: 'matchesReplaceMap[2]',
|
||||
name: 'Table View',
|
||||
mode: 'table',
|
||||
columns: [],
|
||||
filter: {
|
||||
type: 'group',
|
||||
op: 'and',
|
||||
conditions: [],
|
||||
},
|
||||
header: {
|
||||
titleColumn: 'matchesReplaceMap[9]',
|
||||
iconColumn: 'type',
|
||||
},
|
||||
columns: {
|
||||
'matchesReplaceMap[3]': {
|
||||
columnId: 'matchesReplaceMap[3]',
|
||||
order: 'matchesReplaceMap[4]',
|
||||
},
|
||||
'matchesReplaceMap[6]': {
|
||||
columnId: 'matchesReplaceMap[6]',
|
||||
order: 'matchesReplaceMap[7]',
|
||||
},
|
||||
'matchesReplaceMap[9]': {
|
||||
columnId: 'matchesReplaceMap[9]',
|
||||
order: 'matchesReplaceMap[10]',
|
||||
},
|
||||
},
|
||||
rows: {
|
||||
'matchesReplaceMap[12]': {
|
||||
rowId: 'matchesReplaceMap[12]',
|
||||
order: 'matchesReplaceMap[13]',
|
||||
},
|
||||
'matchesReplaceMap[15]': {
|
||||
rowId: 'matchesReplaceMap[15]',
|
||||
order: 'matchesReplaceMap[16]',
|
||||
},
|
||||
],
|
||||
title: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [],
|
||||
},
|
||||
cells: {
|
||||
'matchesReplaceMap[12]': {
|
||||
'matchesReplaceMap[10]': {
|
||||
columnId: 'matchesReplaceMap[10]',
|
||||
value: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'eee',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
'matchesReplaceMap[11]': {
|
||||
columnId: 'matchesReplaceMap[11]',
|
||||
value: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'fff',
|
||||
},
|
||||
],
|
||||
},
|
||||
'matchesReplaceMap[17]': {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
type: 'title',
|
||||
name: 'aaa',
|
||||
data: {},
|
||||
id: 'matchesReplaceMap[9]',
|
||||
'matchesReplaceMap[18]': {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'bbb',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'bbb',
|
||||
data: {},
|
||||
id: 'matchesReplaceMap[10]',
|
||||
'matchesReplaceMap[19]': {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'ccc',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'ccc',
|
||||
data: {},
|
||||
id: 'matchesReplaceMap[11]',
|
||||
},
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[12]',
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
'matchesReplaceMap[20]': {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
@@ -3622,11 +3611,30 @@ bbb
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
children: [],
|
||||
'matchesReplaceMap[21]': {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'eee',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
'matchesReplaceMap[22]': {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'fff',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { ImageBlockHtmlAdapterExtension } from '@blocksuite/affine-block-image';
|
||||
import { ListBlockHtmlAdapterExtension } from '@blocksuite/affine-block-list';
|
||||
import { ParagraphBlockHtmlAdapterExtension } from '@blocksuite/affine-block-paragraph';
|
||||
import { TableBlockHtmlAdapterExtension } from '@blocksuite/affine-block-table';
|
||||
|
||||
import { RootBlockHtmlAdapterExtension } from '../../../root-block/adapters/html.js';
|
||||
|
||||
@@ -29,6 +30,7 @@ export const defaultBlockHtmlAdapterMatchers = [
|
||||
EmbedGithubBlockHtmlAdapterExtension,
|
||||
BookmarkBlockHtmlAdapterExtension,
|
||||
DatabaseBlockHtmlAdapterExtension,
|
||||
TableBlockHtmlAdapterExtension,
|
||||
EmbedLinkedDocHtmlAdapterExtension,
|
||||
EmbedSyncedDocBlockHtmlAdapterExtension,
|
||||
];
|
||||
|
||||
@@ -15,6 +15,7 @@ import { LatexBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-lat
|
||||
import { ListBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-list';
|
||||
import { DocNoteBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-note';
|
||||
import { ParagraphBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-paragraph';
|
||||
import { TableBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-table';
|
||||
|
||||
import { RootBlockMarkdownAdapterExtension } from '../../../root-block/adapters/markdown.js';
|
||||
|
||||
@@ -32,6 +33,7 @@ export const defaultBlockMarkdownAdapterMatchers = [
|
||||
BookmarkBlockMarkdownAdapterExtension,
|
||||
CodeBlockMarkdownAdapterExtension,
|
||||
DatabaseBlockMarkdownAdapterExtension,
|
||||
TableBlockMarkdownAdapterExtension,
|
||||
DividerBlockMarkdownAdapterExtension,
|
||||
ImageBlockMarkdownAdapterExtension,
|
||||
LatexBlockMarkdownAdapterExtension,
|
||||
|
||||
@@ -18,7 +18,6 @@ export class TestUtils {
|
||||
export function nanoidReplacement(snapshot: BlockSnapshot | SliceSnapshot) {
|
||||
return JSON.parse(nanoidReplacementString(JSON.stringify(snapshot)));
|
||||
}
|
||||
|
||||
const escapedSnapshotAttributes = new Set([
|
||||
'"attributes"',
|
||||
'"conditions"',
|
||||
@@ -31,7 +30,7 @@ const escapedSnapshotAttributes = new Set([
|
||||
|
||||
function nanoidReplacementString(snapshotString: string) {
|
||||
const re =
|
||||
/("block:[A-Za-z0-9-_]{10}")|("[A-Za-z0-9-_]{10}")|("var\(--affine-v2-chip-label-[a-z]{3,10}\)")|("[A-Za-z0-9-_=]{44}")/g;
|
||||
/("block:[A-Za-z0-9-_]{10}")|("[A-Za-z0-9-_]{10}")|("[A-Za-z0-9-_]{35}")|("[A-Za-z0-9-_]{10}:[A-Za-z0-9-_]{10}")|("var\(--affine-v2-chip-label-[a-z]{3,10}\)")|("[A-Za-z0-9-_=]{44}")/g;
|
||||
const matches = snapshotString.matchAll(re);
|
||||
const matchesReplaceMap = new Map();
|
||||
let escapedNumber = 0;
|
||||
|
||||
@@ -26,6 +26,10 @@ import {
|
||||
EdgelessSurfaceRefBlockSpec,
|
||||
PageSurfaceRefBlockSpec,
|
||||
} from '@blocksuite/affine-block-surface-ref';
|
||||
import {
|
||||
TableBlockSpec,
|
||||
TableSelectionExtension,
|
||||
} from '@blocksuite/affine-block-table';
|
||||
import {
|
||||
RefNodeSlotsExtension,
|
||||
RichTextExtensions,
|
||||
@@ -60,6 +64,7 @@ export const CommonBlockSpecs: ExtensionType[] = [
|
||||
LatexBlockSpec,
|
||||
ListBlockSpec,
|
||||
DatabaseBlockSpec,
|
||||
TableBlockSpec,
|
||||
DataViewBlockSpec,
|
||||
DividerBlockSpec,
|
||||
BookmarkBlockSpec,
|
||||
@@ -100,5 +105,6 @@ export const StoreExtensions: ExtensionType[] = [
|
||||
HighlightSelectionExtension,
|
||||
ImageSelectionExtension,
|
||||
DatabaseSelectionExtension,
|
||||
TableSelectionExtension,
|
||||
LinkPreviewerService,
|
||||
];
|
||||
|
||||
@@ -14,6 +14,7 @@ import { effects as blockNoteEffects } from '@blocksuite/affine-block-note/effec
|
||||
import { effects as blockParagraphEffects } from '@blocksuite/affine-block-paragraph/effects';
|
||||
import { effects as blockSurfaceEffects } from '@blocksuite/affine-block-surface/effects';
|
||||
import { effects as blockSurfaceRefEffects } from '@blocksuite/affine-block-surface-ref/effects';
|
||||
import { effects as blockTableEffects } from '@blocksuite/affine-block-table/effects';
|
||||
import { effects as componentAiItemEffects } from '@blocksuite/affine-components/ai-item';
|
||||
import { BlockSelection } from '@blocksuite/affine-components/block-selection';
|
||||
import { BlockZeroWidth } from '@blocksuite/affine-components/block-zero-width';
|
||||
@@ -204,6 +205,7 @@ export function effects() {
|
||||
blockDividerEffects();
|
||||
blockDataViewEffects();
|
||||
blockCodeEffects();
|
||||
blockTableEffects();
|
||||
|
||||
componentCaptionEffects();
|
||||
componentContextMenuEffects();
|
||||
|
||||
@@ -48,6 +48,7 @@ export * from '@blocksuite/affine-block-note';
|
||||
export * from '@blocksuite/affine-block-paragraph';
|
||||
export * from '@blocksuite/affine-block-surface';
|
||||
export * from '@blocksuite/affine-block-surface-ref';
|
||||
export * from '@blocksuite/affine-block-table';
|
||||
export {
|
||||
type AIError,
|
||||
type AIItemConfig,
|
||||
|
||||
@@ -439,6 +439,8 @@ export const BUILT_IN_GROUPS: MenuItemGroup<FormatBarContext>[] = [
|
||||
];
|
||||
|
||||
export function toolbarMoreButton(toolbar: AffineFormatBarWidget) {
|
||||
const richText = getRichText();
|
||||
if (richText?.dataset.disableAskAi !== undefined) return null;
|
||||
const context = new FormatBarContext(toolbar);
|
||||
const actions = renderGroups(toolbar.moreGroups, context);
|
||||
|
||||
@@ -455,3 +457,15 @@ export function toolbarMoreButton(toolbar: AffineFormatBarWidget) {
|
||||
</editor-menu-button>
|
||||
`;
|
||||
}
|
||||
const getRichText = () => {
|
||||
const selection = getSelection();
|
||||
if (!selection) return null;
|
||||
if (selection.rangeCount === 0) return null;
|
||||
const range = selection.getRangeAt(0);
|
||||
const commonAncestorContainer =
|
||||
range.commonAncestorContainer instanceof Element
|
||||
? range.commonAncestorContainer
|
||||
: range.commonAncestorContainer.parentElement;
|
||||
if (!commonAncestorContainer) return null;
|
||||
return commonAncestorContainer.closest('rich-text');
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DatabaseSelection } from '@blocksuite/affine-block-database';
|
||||
import { HoverController } from '@blocksuite/affine-components/hover';
|
||||
import type { RichText } from '@blocksuite/affine-components/rich-text';
|
||||
import { isFormatSupported } from '@blocksuite/affine-components/rich-text';
|
||||
import {
|
||||
isFormatSupported,
|
||||
type RichText,
|
||||
} from '@blocksuite/affine-components/rich-text';
|
||||
import {
|
||||
cloneGroups,
|
||||
getMoreMenuConfig,
|
||||
@@ -205,34 +206,20 @@ export class AffineFormatBarWidget extends WidgetComponent {
|
||||
);
|
||||
this.disposables.addFromEvent(document, 'selectionchange', () => {
|
||||
if (!this.host.event.active) return;
|
||||
|
||||
const databaseSelection = this.host.selection.find(DatabaseSelection);
|
||||
if (!databaseSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
this.reset();
|
||||
this.requestUpdate();
|
||||
};
|
||||
const viewSelection = databaseSelection.viewSelection;
|
||||
// check table selection
|
||||
if (
|
||||
viewSelection.type === 'table' &&
|
||||
(viewSelection.selectionType !== 'area' || !viewSelection.isEditing)
|
||||
)
|
||||
return reset();
|
||||
// check kanban selection
|
||||
if (
|
||||
(viewSelection.type === 'kanban' &&
|
||||
viewSelection.selectionType !== 'cell') ||
|
||||
!viewSelection.isEditing
|
||||
)
|
||||
return reset();
|
||||
|
||||
const range = this.nativeRange;
|
||||
|
||||
if (!range || range.collapsed) return reset();
|
||||
if (!range) return;
|
||||
const container =
|
||||
range.commonAncestorContainer instanceof Element
|
||||
? range.commonAncestorContainer
|
||||
: range.commonAncestorContainer.parentElement;
|
||||
if (!container) return;
|
||||
const notBlockText = container.closest('rich-text')?.dataset.notBlockText;
|
||||
if (notBlockText == null) return;
|
||||
if (range.collapsed) return reset();
|
||||
this._displayType = 'native';
|
||||
this.requestUpdate();
|
||||
});
|
||||
@@ -551,12 +538,16 @@ export class AffineFormatBarWidget extends WidgetComponent {
|
||||
}
|
||||
|
||||
const items = ConfigRenderer(this);
|
||||
|
||||
const moreButton = toolbarMoreButton(this);
|
||||
return html`
|
||||
<editor-toolbar class="${AFFINE_FORMAT_BAR_WIDGET}">
|
||||
${items}
|
||||
<editor-toolbar-separator></editor-toolbar-separator>
|
||||
${toolbarMoreButton(this)}
|
||||
${moreButton
|
||||
? html`
|
||||
<editor-toolbar-separator></editor-toolbar-separator>
|
||||
${moreButton}
|
||||
`
|
||||
: nothing}
|
||||
</editor-toolbar>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -246,6 +246,23 @@ export const defaultSlashMenuConfig: SlashMenuConfig = {
|
||||
|
||||
// ---------------------------------------------------------
|
||||
{ groupName: 'Content & Media' },
|
||||
{
|
||||
name: 'Table',
|
||||
description: 'Create a table block.',
|
||||
icon: DatabaseTableViewIcon20,
|
||||
tooltip: slashMenuToolTips['Table View'],
|
||||
showWhen: ({ model }) => !insideEdgelessText(model),
|
||||
action: ({ rootComponent }) => {
|
||||
rootComponent.std.command
|
||||
.chain()
|
||||
.getSelectedModels()
|
||||
.insertTableBlock({
|
||||
place: 'after',
|
||||
removeEmptyLine: true,
|
||||
})
|
||||
.run();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Image',
|
||||
description: 'Insert an image.',
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
ParagraphBlockSchema,
|
||||
RootBlockSchema,
|
||||
SurfaceRefBlockSchema,
|
||||
TableBlockSchema,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { BlockSchema } from '@blocksuite/store';
|
||||
import type { z } from 'zod';
|
||||
@@ -52,4 +53,5 @@ export const AffineSchemas: z.infer<typeof BlockSchema>[] = [
|
||||
EmbedLoomBlockSchema,
|
||||
EdgelessTextBlockSchema,
|
||||
LatexBlockSchema,
|
||||
TableBlockSchema,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user