mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-11 14:08:55 +08:00
feat(editor): calendar view for database block (#14984)
fix #13663 #### PR Dependency Tree * **PR #14984** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Calendar view for database blocks (month layout, entry cards, external-source support) * Workspace calendar integration and new slash-menu "Calendar View" * **Improvements** * Create/manage database rows from calendar UI; preserve durations when moving/resizing ranges * Drag-and-drop, drop-preview, and hit-testing support for calendar and docs * Redesigned in-menu View settings with multi-page navigation * Context-menu input autofocus toggle and conditional back-navigation * **Tests** * New unit and E2E suites covering calendar layout, interactions, sources, and slash-menu integration <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+319
@@ -0,0 +1,319 @@
|
||||
/** @vitest-environment happy-dom */
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
calendarEventToExternalEntry,
|
||||
selectWorkspaceCalendarSubscriptionIds,
|
||||
WorkspaceCalendarExternalSource,
|
||||
} from './workspace-calendar-source';
|
||||
|
||||
const accountCalendarsState = (value: unknown) => ({
|
||||
['accountCalendars$']: { value },
|
||||
});
|
||||
|
||||
const workspaceCalendarsState = (value: unknown) => ({
|
||||
['workspaceCalendars$']: { value },
|
||||
});
|
||||
|
||||
describe('workspace calendar source', () => {
|
||||
it('intersects workspace enabled items with view subscription ids', () => {
|
||||
const workspaceItems = [
|
||||
{ subscriptionId: 'a', enabled: true },
|
||||
{ subscriptionId: 'b', enabled: false },
|
||||
{ subscriptionId: 'c', enabled: true },
|
||||
];
|
||||
const cases = [
|
||||
{
|
||||
viewConfig: {
|
||||
enabled: true,
|
||||
subscriptionIds: ['b', 'c'],
|
||||
},
|
||||
expected: ['c'],
|
||||
},
|
||||
{
|
||||
viewConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
expected: [],
|
||||
},
|
||||
];
|
||||
|
||||
for (const { viewConfig, expected } of cases) {
|
||||
const ids = selectWorkspaceCalendarSubscriptionIds(
|
||||
workspaceItems,
|
||||
viewConfig
|
||||
);
|
||||
|
||||
expect([...ids]).toEqual(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('maps event range to an external entry', () => {
|
||||
const entry = calendarEventToExternalEntry(
|
||||
{
|
||||
id: 'event-1',
|
||||
subscriptionId: 'sub-1',
|
||||
externalEventId: 'external-1',
|
||||
title: 'Planning',
|
||||
description: 'Discuss roadmap',
|
||||
location: 'Room A',
|
||||
startAtUtc: '2026-05-15T01:00:00.000Z',
|
||||
endAtUtc: '2026-05-16T01:00:00.000Z',
|
||||
allDay: false,
|
||||
} as any,
|
||||
{
|
||||
color: '#2f7d32',
|
||||
calendarName: 'Work',
|
||||
}
|
||||
);
|
||||
|
||||
expect(entry).toMatchObject({
|
||||
kind: 'external',
|
||||
id: 'workspace-calendar:event-1',
|
||||
externalId: 'external-1',
|
||||
title: 'Planning',
|
||||
color: '#2f7d32',
|
||||
calendarName: 'Work',
|
||||
description: 'Discuss roadmap',
|
||||
location: 'Room A',
|
||||
});
|
||||
expect(entry.endAt).toBeGreaterThan(entry.startAt);
|
||||
});
|
||||
|
||||
it('falls back to stable visible colors for muted calendar colors', () => {
|
||||
const event = {
|
||||
id: 'event-1',
|
||||
subscriptionId: 'sub-1',
|
||||
title: 'Planning',
|
||||
startAtUtc: '2026-05-15T01:00:00.000Z',
|
||||
endAtUtc: '2026-05-16T01:00:00.000Z',
|
||||
allDay: false,
|
||||
} as any;
|
||||
|
||||
expect(calendarEventToExternalEntry(event, { color: '#00f' }).color).toBe(
|
||||
'#6f6b2f'
|
||||
);
|
||||
expect(calendarEventToExternalEntry(event, { color: '#eee' }).color).toBe(
|
||||
'#6f6b2f'
|
||||
);
|
||||
expect(calendarEventToExternalEntry(event).color).toBe('#6f6b2f');
|
||||
});
|
||||
|
||||
it('uses workspace color override before account calendar color', async () => {
|
||||
const source = new WorkspaceCalendarExternalSource(
|
||||
{
|
||||
...accountCalendarsState(
|
||||
new Map([['account-1', [{ id: 'sub-1', color: '#111' }]]])
|
||||
),
|
||||
...workspaceCalendarsState([
|
||||
{
|
||||
items: [
|
||||
{
|
||||
subscriptionId: 'sub-1',
|
||||
enabled: true,
|
||||
colorOverride: '#ad3b69',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
revalidateEventsRange: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: 'event-1',
|
||||
subscriptionId: 'sub-1',
|
||||
title: 'Planning',
|
||||
startAtUtc: '2026-05-15T01:00:00.000Z',
|
||||
endAtUtc: '2026-05-15T02:00:00.000Z',
|
||||
allDay: false,
|
||||
},
|
||||
]),
|
||||
} as any,
|
||||
() => true,
|
||||
{
|
||||
sources: {
|
||||
workspaceCalendar: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
|
||||
await expect(
|
||||
source.getEntries({ from: Date.now(), to: Date.now() })
|
||||
).resolves.toMatchObject([{ color: '#ad3b69' }]);
|
||||
expect(source.getSubscriptionOptions()).toEqual([
|
||||
{
|
||||
id: 'sub-1',
|
||||
name: 'sub-1',
|
||||
color: '#ad3b69',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns empty entries without server', async () => {
|
||||
const revalidateEventsRange = vi.fn();
|
||||
const source = new WorkspaceCalendarExternalSource(
|
||||
{
|
||||
...accountCalendarsState(new Map()),
|
||||
...workspaceCalendarsState([
|
||||
{
|
||||
items: [{ subscriptionId: 'sub-1', enabled: true }],
|
||||
},
|
||||
]),
|
||||
revalidateEventsRange,
|
||||
} as any,
|
||||
() => false,
|
||||
{
|
||||
sources: {
|
||||
workspaceCalendar: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
|
||||
await expect(
|
||||
source.getEntries({ from: Date.now(), to: Date.now() })
|
||||
).resolves.toEqual([]);
|
||||
expect(revalidateEventsRange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('opens workspace integration settings from connect entry', () => {
|
||||
const openSettings = vi.fn();
|
||||
const source = new WorkspaceCalendarExternalSource(
|
||||
undefined,
|
||||
() => false,
|
||||
{
|
||||
sources: {
|
||||
workspaceCalendar: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
openSettings
|
||||
);
|
||||
|
||||
source.openConnectSettings();
|
||||
|
||||
expect(openSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('loads empty calendar caches before fetching entries', async () => {
|
||||
const loadAccountCalendars = vi.fn().mockResolvedValue(
|
||||
new Map([
|
||||
[
|
||||
'account-1',
|
||||
[
|
||||
{
|
||||
id: 'sub-1',
|
||||
displayName: 'Work',
|
||||
color: '#111',
|
||||
},
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
const revalidateWorkspaceCalendars = vi.fn().mockResolvedValue([
|
||||
{
|
||||
items: [{ subscriptionId: 'sub-1', enabled: true }],
|
||||
},
|
||||
]);
|
||||
const source = new WorkspaceCalendarExternalSource(
|
||||
{
|
||||
...accountCalendarsState(new Map()),
|
||||
...workspaceCalendarsState([]),
|
||||
loadAccountCalendars,
|
||||
revalidateWorkspaceCalendars,
|
||||
revalidateEventsRange: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: 'event-1',
|
||||
subscriptionId: 'sub-1',
|
||||
title: 'Planning',
|
||||
startAtUtc: '2026-05-15T01:00:00.000Z',
|
||||
endAtUtc: '2026-05-15T02:00:00.000Z',
|
||||
allDay: false,
|
||||
},
|
||||
]),
|
||||
} as any,
|
||||
() => true,
|
||||
{
|
||||
sources: {
|
||||
workspaceCalendar: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
|
||||
await expect(
|
||||
source.getEntries({ from: Date.now(), to: Date.now() })
|
||||
).resolves.toMatchObject([{ title: 'Planning' }]);
|
||||
expect(loadAccountCalendars).toHaveBeenCalled();
|
||||
expect(revalidateWorkspaceCalendars).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns empty entries when calendar requests fail', async () => {
|
||||
const source = new WorkspaceCalendarExternalSource(
|
||||
{
|
||||
...accountCalendarsState(new Map()),
|
||||
...workspaceCalendarsState([
|
||||
{
|
||||
items: [{ subscriptionId: 'sub-1', enabled: true }],
|
||||
},
|
||||
]),
|
||||
loadAccountCalendars: vi.fn().mockResolvedValue(new Map()),
|
||||
revalidateEventsRange: vi.fn().mockRejectedValue(new Error('denied')),
|
||||
} as any,
|
||||
() => true,
|
||||
{
|
||||
sources: {
|
||||
workspaceCalendar: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
|
||||
await expect(
|
||||
source.getEntries({ from: Date.now(), to: Date.now() })
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty entries when account calendar loading fails', async () => {
|
||||
const revalidateEventsRange = vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: 'event-1',
|
||||
subscriptionId: 'sub-1',
|
||||
title: 'Planning',
|
||||
startAtUtc: '2026-05-15T01:00:00.000Z',
|
||||
endAtUtc: '2026-05-15T02:00:00.000Z',
|
||||
allDay: false,
|
||||
},
|
||||
]);
|
||||
const source = new WorkspaceCalendarExternalSource(
|
||||
{
|
||||
...accountCalendarsState(new Map()),
|
||||
...workspaceCalendarsState([
|
||||
{
|
||||
items: [{ subscriptionId: 'sub-1', enabled: true }],
|
||||
},
|
||||
]),
|
||||
loadAccountCalendars: vi.fn().mockRejectedValue(new Error('denied')),
|
||||
revalidateEventsRange,
|
||||
} as any,
|
||||
() => true,
|
||||
{
|
||||
sources: {
|
||||
workspaceCalendar: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
|
||||
await expect(
|
||||
source.getEntries({ from: Date.now(), to: Date.now() })
|
||||
).resolves.toEqual([]);
|
||||
expect(revalidateEventsRange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
import { CALENDAR_INTEGRATION_SCROLL_ANCHOR } from '@affine/core/desktop/dialogs/setting/navigation-constants';
|
||||
import { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { IntegrationService } from '@affine/core/modules/integration';
|
||||
import type {
|
||||
CalendarEntryRange,
|
||||
CalendarExternalEntry,
|
||||
CalendarViewData,
|
||||
} from '@blocksuite/data-view/view-presets';
|
||||
import type { FrameworkProvider } from '@toeverything/infra';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
type CalendarIntegrationLike = IntegrationService['calendar'];
|
||||
|
||||
type CalendarEventPayload = Awaited<
|
||||
ReturnType<CalendarIntegrationLike['revalidateEventsRange']>
|
||||
>[number];
|
||||
|
||||
const calendarColorPalette = [
|
||||
'#2f7d32',
|
||||
'#b45309',
|
||||
'#ad3b69',
|
||||
'#8f6a00',
|
||||
'#6f6b2f',
|
||||
'#9f4f1a',
|
||||
] as const;
|
||||
|
||||
const hashString = (value: string) => {
|
||||
let hash = 0;
|
||||
for (let index = 0; index < value.length; index++) {
|
||||
hash = (hash * 31 + value.charCodeAt(index)) >>> 0;
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
const parseHexColor = (color: string) => {
|
||||
const hex = color.trim();
|
||||
const match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
const value =
|
||||
match[1].length === 3
|
||||
? match[1]
|
||||
.split('')
|
||||
.map(char => char + char)
|
||||
.join('')
|
||||
: match[1];
|
||||
return {
|
||||
r: Number.parseInt(value.slice(0, 2), 16),
|
||||
g: Number.parseInt(value.slice(2, 4), 16),
|
||||
b: Number.parseInt(value.slice(4, 6), 16),
|
||||
};
|
||||
};
|
||||
|
||||
const getColorHue = ({ r, g, b }: { r: number; g: number; b: number }) => {
|
||||
const red = r / 255;
|
||||
const green = g / 255;
|
||||
const blue = b / 255;
|
||||
const max = Math.max(red, green, blue);
|
||||
const min = Math.min(red, green, blue);
|
||||
const delta = max - min;
|
||||
if (delta === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (max === red) {
|
||||
return ((green - blue) / delta + (green < blue ? 6 : 0)) * 60;
|
||||
}
|
||||
if (max === green) {
|
||||
return ((blue - red) / delta + 2) * 60;
|
||||
}
|
||||
return ((red - green) / delta + 4) * 60;
|
||||
};
|
||||
|
||||
const isMutedCalendarColor = (color: string) => {
|
||||
const rgb = parseHexColor(color);
|
||||
if (!rgb) {
|
||||
return true;
|
||||
}
|
||||
const { r, g, b } = rgb;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const lightness = (max + min) / 510;
|
||||
const saturation =
|
||||
max === min ? 0 : (max - min) / (255 - Math.abs(max + min - 255));
|
||||
const hue = getColorHue(rgb);
|
||||
return (
|
||||
lightness > 0.9 ||
|
||||
saturation < 0.18 ||
|
||||
(hue >= 190 && hue <= 260 && saturation > 0.18)
|
||||
);
|
||||
};
|
||||
|
||||
const getCalendarColor = (subscriptionId: string, color?: string | null) => {
|
||||
if (color && !isMutedCalendarColor(color)) {
|
||||
return color;
|
||||
}
|
||||
return calendarColorPalette[
|
||||
hashString(subscriptionId) % calendarColorPalette.length
|
||||
];
|
||||
};
|
||||
|
||||
export const selectWorkspaceCalendarSubscriptionIds = (
|
||||
workspaceItems: Array<{
|
||||
subscriptionId: string;
|
||||
enabled: boolean;
|
||||
}>,
|
||||
viewConfig?: CalendarViewData['sources']['workspaceCalendar']
|
||||
) => {
|
||||
if (!viewConfig?.enabled) {
|
||||
return new Set<string>();
|
||||
}
|
||||
const viewIds = viewConfig.subscriptionIds
|
||||
? new Set(viewConfig.subscriptionIds)
|
||||
: undefined;
|
||||
return new Set(
|
||||
workspaceItems
|
||||
.filter(item => item.enabled)
|
||||
.filter(item => !viewIds || viewIds.has(item.subscriptionId))
|
||||
.map(item => item.subscriptionId)
|
||||
);
|
||||
};
|
||||
|
||||
export const calendarEventToExternalEntry = (
|
||||
event: CalendarEventPayload,
|
||||
options?: {
|
||||
color?: string | null;
|
||||
calendarName?: string;
|
||||
}
|
||||
) =>
|
||||
({
|
||||
kind: 'external',
|
||||
id: `workspace-calendar:${event.id}`,
|
||||
sourceId: 'workspace-calendar',
|
||||
externalId: event.externalEventId ?? event.id,
|
||||
title: event.title ?? '',
|
||||
color:
|
||||
options?.color !== undefined
|
||||
? getCalendarColor(event.subscriptionId, options.color)
|
||||
: getCalendarColor(event.subscriptionId),
|
||||
calendarName: options?.calendarName,
|
||||
location: event.location ?? undefined,
|
||||
description: event.description ?? undefined,
|
||||
startAt: dayjs(event.startAtUtc).valueOf(),
|
||||
endAt: dayjs(event.endAtUtc).valueOf(),
|
||||
allDay: event.allDay,
|
||||
canResizeRange: false,
|
||||
}) as CalendarExternalEntry;
|
||||
|
||||
export class WorkspaceCalendarExternalSource {
|
||||
id = 'workspace-calendar';
|
||||
|
||||
constructor(
|
||||
private readonly calendar: CalendarIntegrationLike | undefined,
|
||||
private readonly hasServer: () => boolean,
|
||||
private readonly viewData: CalendarViewData,
|
||||
private readonly openSettings?: () => void
|
||||
) {}
|
||||
|
||||
openConnectSettings() {
|
||||
this.openSettings?.();
|
||||
}
|
||||
|
||||
async getEntries(range: CalendarEntryRange) {
|
||||
const calendar = this.calendar;
|
||||
if (!calendar || !this.hasServer()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const workspaceCalendars =
|
||||
calendar.workspaceCalendars$.value.length > 0
|
||||
? calendar.workspaceCalendars$.value
|
||||
: await calendar.revalidateWorkspaceCalendars().catch(() => []);
|
||||
if (calendar.accountCalendars$.value.size === 0) {
|
||||
const accountCalendars = await calendar
|
||||
.loadAccountCalendars()
|
||||
.catch(() => undefined);
|
||||
if (!accountCalendars) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceCalendar = workspaceCalendars[0];
|
||||
const workspaceItems = workspaceCalendar?.items ?? [];
|
||||
const subscriptionIds = selectWorkspaceCalendarSubscriptionIds(
|
||||
workspaceItems,
|
||||
this.viewData.sources?.workspaceCalendar
|
||||
);
|
||||
if (subscriptionIds.size === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const events = await calendar
|
||||
.revalidateEventsRange(dayjs(range.from), dayjs(range.to))
|
||||
.catch(() => []);
|
||||
const infoBySubscriptionId = this.getSubscriptionInfo();
|
||||
const colorBySubscriptionId = new Map<string, string | null | undefined>();
|
||||
for (const calendars of calendar.accountCalendars$.value.values()) {
|
||||
for (const subscription of calendars) {
|
||||
colorBySubscriptionId.set(subscription.id, subscription.color);
|
||||
}
|
||||
}
|
||||
for (const item of workspaceItems) {
|
||||
if (item.colorOverride) {
|
||||
colorBySubscriptionId.set(item.subscriptionId, item.colorOverride);
|
||||
}
|
||||
}
|
||||
|
||||
return events
|
||||
.filter(event => subscriptionIds.has(event.subscriptionId))
|
||||
.map(event =>
|
||||
calendarEventToExternalEntry(event, {
|
||||
color: getCalendarColor(
|
||||
event.subscriptionId,
|
||||
colorBySubscriptionId.get(event.subscriptionId)
|
||||
),
|
||||
calendarName: infoBySubscriptionId.get(event.subscriptionId)?.name,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getSubscriptionOptions() {
|
||||
const workspaceItems =
|
||||
this.calendar?.workspaceCalendars$.value[0]?.items ?? [];
|
||||
const enabledIds = new Set(
|
||||
workspaceItems
|
||||
.filter(item => item.enabled)
|
||||
.map(item => item.subscriptionId)
|
||||
);
|
||||
return [...this.getSubscriptionInfo()]
|
||||
.filter(([id]) => enabledIds.has(id))
|
||||
.map(([id, info]) => ({
|
||||
id,
|
||||
name: info.name,
|
||||
color: getCalendarColor(
|
||||
id,
|
||||
workspaceItems.find(item => item.subscriptionId === id)
|
||||
?.colorOverride ?? info.color
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
private getSubscriptionInfo() {
|
||||
const infoBySubscriptionId = new Map<
|
||||
string,
|
||||
{
|
||||
name: string;
|
||||
color?: string | null;
|
||||
}
|
||||
>();
|
||||
for (const calendars of this.calendar?.accountCalendars$.value.values() ??
|
||||
[]) {
|
||||
for (const subscription of calendars) {
|
||||
infoBySubscriptionId.set(subscription.id, {
|
||||
name:
|
||||
subscription.displayName ??
|
||||
subscription.externalCalendarId ??
|
||||
subscription.id,
|
||||
color: subscription.color,
|
||||
});
|
||||
}
|
||||
}
|
||||
return infoBySubscriptionId;
|
||||
}
|
||||
}
|
||||
|
||||
export const createWorkspaceCalendarExternalSource = (
|
||||
framework?: FrameworkProvider
|
||||
) => {
|
||||
if (!framework) {
|
||||
return {
|
||||
id: 'workspace-calendar',
|
||||
create: (viewData: CalendarViewData) =>
|
||||
new WorkspaceCalendarExternalSource(undefined, () => false, viewData),
|
||||
};
|
||||
}
|
||||
const integration = framework.get(IntegrationService);
|
||||
const server = framework.get(WorkspaceServerService);
|
||||
const dialog = framework.get(WorkspaceDialogService);
|
||||
return {
|
||||
id: 'workspace-calendar',
|
||||
create: (viewData: CalendarViewData) =>
|
||||
new WorkspaceCalendarExternalSource(
|
||||
integration.calendar,
|
||||
() => !!server.server,
|
||||
viewData,
|
||||
() =>
|
||||
dialog.open('setting', {
|
||||
activeTab: 'workspace:integrations',
|
||||
scrollAnchor: CALENDAR_INTEGRATION_SCROLL_ANCHOR,
|
||||
})
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -224,6 +224,7 @@ class ViewProvider {
|
||||
};
|
||||
|
||||
private readonly _configureDatabase = (framework?: FrameworkProvider) => {
|
||||
this._manager.configure(AffineDatabaseViewExtension, { framework });
|
||||
if (framework) {
|
||||
this._manager.configure(
|
||||
DatabaseViewExtension,
|
||||
|
||||
+8
-1
@@ -3,11 +3,16 @@ import {
|
||||
ExternalGroupByConfigProvider,
|
||||
} from '@blocksuite/affine/blocks/database';
|
||||
import type { ExtensionType } from '@blocksuite/affine/store';
|
||||
import { CalendarExternalSourceProvider } from '@blocksuite/data-view/view-presets';
|
||||
import type { FrameworkProvider } from '@toeverything/infra';
|
||||
|
||||
import { createWorkspaceCalendarExternalSource } from '../../database-block/calendar/workspace-calendar-source';
|
||||
import { groupByConfigList } from '../../database-block/group-by';
|
||||
import { propertiesPresets } from '../../database-block/properties';
|
||||
|
||||
export function patchDatabaseBlockConfigService(): ExtensionType {
|
||||
export function patchDatabaseBlockConfigService(
|
||||
framework?: FrameworkProvider
|
||||
): ExtensionType {
|
||||
//TODO use service
|
||||
DatabaseBlockDataSource.externalProperties.value = propertiesPresets;
|
||||
return {
|
||||
@@ -15,6 +20,8 @@ export function patchDatabaseBlockConfigService(): ExtensionType {
|
||||
groupByConfigList.forEach(config => {
|
||||
di.addValue(ExternalGroupByConfigProvider(config.name), config);
|
||||
});
|
||||
const source = createWorkspaceCalendarExternalSource(framework);
|
||||
di.addValue(CalendarExternalSourceProvider(source.id), source);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ import {
|
||||
type ViewExtensionContext,
|
||||
ViewExtensionProvider,
|
||||
} from '@blocksuite/affine/ext-loader';
|
||||
import { FrameworkProvider } from '@toeverything/infra';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { patchDatabaseBlockConfigService } from './database-block-config-service';
|
||||
|
||||
const optionsSchema = z.object({});
|
||||
const optionsSchema = z.object({
|
||||
framework: z.instanceof(FrameworkProvider).optional(),
|
||||
});
|
||||
|
||||
export type AffineDatabaseViewOptions = z.infer<typeof optionsSchema>;
|
||||
|
||||
@@ -21,6 +24,6 @@ export class AffineDatabaseViewExtension extends ViewExtensionProvider<AffineDat
|
||||
) {
|
||||
super.setup(context, options);
|
||||
|
||||
context.register(patchDatabaseBlockConfigService());
|
||||
context.register(patchDatabaseBlockConfigService(options?.framework));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user