mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 19:53:48 +08:00
feat(core): events list in journal calendar (#11873)
close AF-2505 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced calendar event integration in the journal detail page, allowing users to view and interact with calendar events for a selected date. - Added the ability to create new linked documents from calendar events directly within the journal interface. - **Improvements** - Enhanced calendar integration with support for all-day and timed events, improved event grouping by date, and real-time updates. - Added new English localization entries for calendar event labels. - **Bug Fixes** - Improved URL handling for calendar subscriptions, ensuring better compatibility and error feedback. - **Style** - Added new styles for calendar event components to ensure a consistent and user-friendly appearance. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -19,7 +19,7 @@
|
|||||||
"@affine/templates": "workspace:*",
|
"@affine/templates": "workspace:*",
|
||||||
"@affine/track": "workspace:*",
|
"@affine/track": "workspace:*",
|
||||||
"@blocksuite/affine": "workspace:*",
|
"@blocksuite/affine": "workspace:*",
|
||||||
"@blocksuite/icons": "^2.2.12",
|
"@blocksuite/icons": "^2.2.13",
|
||||||
"@dotlottie/player-component": "^2.7.12",
|
"@dotlottie/player-component": "^2.7.12",
|
||||||
"@emotion/cache": "^11.14.0",
|
"@emotion/cache": "^11.14.0",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
|
|||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { createVar, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const primaryColor = createVar('calendar-event-primary');
|
||||||
|
|
||||||
|
export const list = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
padding: '0px 16px 10px 16px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const event = style({
|
||||||
|
display: 'flex',
|
||||||
|
gap: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '5px 4px',
|
||||||
|
borderRadius: 4,
|
||||||
|
cursor: 'pointer',
|
||||||
|
selectors: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: cssVarV2.layer.background.hoverOverlay,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const eventIcon = style({
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
color: primaryColor,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const eventTitle = style({
|
||||||
|
width: 0,
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: '22px',
|
||||||
|
color: cssVarV2.text.primary,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const eventCaption = style({
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: cssVarV2.text.secondary,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const eventTime = style({
|
||||||
|
display: 'flex',
|
||||||
|
selectors: {
|
||||||
|
[`${event}:hover &`]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const eventNewDoc = style({
|
||||||
|
display: 'none',
|
||||||
|
gap: 4,
|
||||||
|
selectors: {
|
||||||
|
[`${event}:hover &`]: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
import { Loading, toast } from '@affine/component';
|
||||||
|
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
|
||||||
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
|
import { DocsService } from '@affine/core/modules/doc';
|
||||||
|
import {
|
||||||
|
type CalendarEvent,
|
||||||
|
IntegrationService,
|
||||||
|
} from '@affine/core/modules/integration';
|
||||||
|
import { JournalService } from '@affine/core/modules/journal';
|
||||||
|
import { GuardService } from '@affine/core/modules/permissions';
|
||||||
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import { FullDayIcon, PeriodIcon, PlusIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
import type ICAL from 'ical.js';
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import * as styles from './calendar-events.css';
|
||||||
|
|
||||||
|
const pad = (val?: number) => (val ?? 0).toString().padStart(2, '0');
|
||||||
|
|
||||||
|
function formatTime(start?: ICAL.Time, end?: ICAL.Time) {
|
||||||
|
const from = `${pad(start?.hour)}:${pad(start?.minute)}`;
|
||||||
|
const to = `${pad(end?.hour)}:${pad(end?.minute)}`;
|
||||||
|
return from === to ? from : `${from} - ${to}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CalendarEvents = ({ date }: { date: Dayjs }) => {
|
||||||
|
const calendar = useService(IntegrationService).calendar;
|
||||||
|
const events = useLiveData(
|
||||||
|
useMemo(() => calendar.eventsByDate$(date), [calendar, date])
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const update = () => {
|
||||||
|
calendar.subscriptions$.value.forEach(sub => sub.update());
|
||||||
|
};
|
||||||
|
update();
|
||||||
|
const interval = setInterval(update, 5 * 60 * 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [calendar]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className={styles.list}>
|
||||||
|
{events.map(event => (
|
||||||
|
<CalendarEventRenderer key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CalendarEventRenderer = ({ event }: { event: CalendarEvent }) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const { url, title, startAt, endAt, allDay, date } = event;
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const calendar = useService(IntegrationService).calendar;
|
||||||
|
const docsService = useService(DocsService);
|
||||||
|
const guardService = useService(GuardService);
|
||||||
|
const journalService = useService(JournalService);
|
||||||
|
const workspaceService = useService(WorkspaceService);
|
||||||
|
const { createPage } = usePageHelper(
|
||||||
|
workspaceService.workspace.docCollection
|
||||||
|
);
|
||||||
|
const subscription = useLiveData(
|
||||||
|
useMemo(() => calendar.subscription$(url), [calendar, url])
|
||||||
|
);
|
||||||
|
const config = useLiveData(
|
||||||
|
useMemo(() => subscription?.config$, [subscription?.config$])
|
||||||
|
);
|
||||||
|
const color = config?.color || cssVarV2.button.primary;
|
||||||
|
|
||||||
|
const handleClick = useAsyncCallback(async () => {
|
||||||
|
if (!date || loading) return;
|
||||||
|
const docs = journalService.journalsByDate$(
|
||||||
|
date.format('YYYY-MM-DD')
|
||||||
|
).value;
|
||||||
|
if (docs.length === 0) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const doc of docs) {
|
||||||
|
const canEdit = await guardService.can('Doc_Update', doc.id);
|
||||||
|
if (!canEdit) {
|
||||||
|
toast(t['com.affine.no-permission']());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDoc = createPage();
|
||||||
|
await docsService.changeDocTitle(newDoc.id, title);
|
||||||
|
await docsService.addLinkedDoc(doc.id, newDoc.id);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
createPage,
|
||||||
|
date,
|
||||||
|
docsService,
|
||||||
|
guardService,
|
||||||
|
journalService,
|
||||||
|
loading,
|
||||||
|
t,
|
||||||
|
title,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
style={assignInlineVars({
|
||||||
|
[styles.primaryColor]: color,
|
||||||
|
})}
|
||||||
|
className={styles.event}
|
||||||
|
data-all-day={allDay}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div className={styles.eventIcon}>
|
||||||
|
{allDay ? <FullDayIcon /> : <PeriodIcon />}
|
||||||
|
</div>
|
||||||
|
<div className={styles.eventTitle}>{title}</div>
|
||||||
|
{loading ? (
|
||||||
|
<Loading />
|
||||||
|
) : (
|
||||||
|
<div className={styles.eventCaption}>
|
||||||
|
<span className={styles.eventTime}>
|
||||||
|
{allDay
|
||||||
|
? t['com.affine.integration.calendar.all-day']()
|
||||||
|
: formatTime(startAt, endAt)}
|
||||||
|
</span>
|
||||||
|
<span className={styles.eventNewDoc}>
|
||||||
|
<PlusIcon style={{ fontSize: 18 }} />
|
||||||
|
{t['com.affine.integration.calendar.new-doc']()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -32,6 +32,7 @@ import dayjs from 'dayjs';
|
|||||||
import type { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
import type { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { CalendarEvents } from './calendar-events';
|
||||||
import * as styles from './journal.css';
|
import * as styles from './journal.css';
|
||||||
import { JournalTemplateOnboarding } from './template-onboarding';
|
import { JournalTemplateOnboarding } from './template-onboarding';
|
||||||
import { JournalTemplateSetting } from './template-setting';
|
import { JournalTemplateSetting } from './template-setting';
|
||||||
@@ -166,6 +167,7 @@ export const EditorJournalPanel = () => {
|
|||||||
{journalDate ? (
|
{journalDate ? (
|
||||||
<>
|
<>
|
||||||
<JournalConflictBlock date={journalDate} />
|
<JournalConflictBlock date={journalDate} />
|
||||||
|
<CalendarEvents date={journalDate} />
|
||||||
<JournalDailyCountBlock date={journalDate} />
|
<JournalDailyCountBlock date={journalDate} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import {
|
|||||||
onStart,
|
onStart,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import ICAL from 'ical.js';
|
import ICAL from 'ical.js';
|
||||||
import { switchMap } from 'rxjs';
|
import { EMPTY, mergeMap, switchMap, throttleTime } from 'rxjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
CalendarStore,
|
CalendarStore,
|
||||||
CalendarSubscriptionConfig,
|
CalendarSubscriptionConfig,
|
||||||
} from '../store/calendar';
|
} from '../store/calendar';
|
||||||
|
import { parseCalendarUrl } from '../utils/calendar-url-parser';
|
||||||
|
|
||||||
export class CalendarSubscription extends Entity<{ url: string }> {
|
export class CalendarSubscription extends Entity<{ url: string }> {
|
||||||
constructor(private readonly store: CalendarStore) {
|
constructor(private readonly store: CalendarStore) {
|
||||||
@@ -44,16 +45,26 @@ export class CalendarSubscription extends Entity<{ url: string }> {
|
|||||||
error$ = new LiveData<any>(null);
|
error$ = new LiveData<any>(null);
|
||||||
|
|
||||||
update = effect(
|
update = effect(
|
||||||
|
throttleTime(30 * 1000),
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
fromPromise(async () => {
|
fromPromise(async () => {
|
||||||
const response = await fetch(this.url);
|
const url = parseCalendarUrl(this.url);
|
||||||
const cache = await response.text();
|
const response = await fetch(url);
|
||||||
this.store.setSubscriptionCache(this.url, cache).catch(console.error);
|
return await response.text();
|
||||||
}).pipe(
|
}).pipe(
|
||||||
|
mergeMap(value => {
|
||||||
|
this.store.setSubscriptionCache(this.url, value).catch(console.error);
|
||||||
|
return EMPTY;
|
||||||
|
}),
|
||||||
catchErrorInto(this.error$),
|
catchErrorInto(this.error$),
|
||||||
onStart(() => this.loading$.setValue(true)),
|
onStart(() => this.loading$.setValue(true)),
|
||||||
onComplete(() => this.loading$.setValue(false))
|
onComplete(() => this.loading$.setValue(false))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
override dispose() {
|
||||||
|
super.dispose();
|
||||||
|
this.update.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Entity, LiveData, ObjectPool } from '@toeverything/infra';
|
import { Entity, LiveData, ObjectPool } from '@toeverything/infra';
|
||||||
|
import dayjs, { type Dayjs } from 'dayjs';
|
||||||
import ICAL from 'ical.js';
|
import ICAL from 'ical.js';
|
||||||
import { Observable, switchMap } from 'rxjs';
|
import { Observable, switchMap } from 'rxjs';
|
||||||
|
|
||||||
@@ -6,8 +7,33 @@ import type {
|
|||||||
CalendarStore,
|
CalendarStore,
|
||||||
CalendarSubscriptionConfig,
|
CalendarSubscriptionConfig,
|
||||||
} from '../store/calendar';
|
} from '../store/calendar';
|
||||||
|
import { parseCalendarUrl } from '../utils/calendar-url-parser';
|
||||||
import { CalendarSubscription } from './calendar-subscription';
|
import { CalendarSubscription } from './calendar-subscription';
|
||||||
|
|
||||||
|
export type CalendarEvent = {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
title: string;
|
||||||
|
startAt?: ICAL.Time;
|
||||||
|
endAt?: ICAL.Time;
|
||||||
|
allDay?: boolean;
|
||||||
|
date?: Dayjs;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EventsByDateMap = Map<string, CalendarEvent[]>;
|
||||||
|
|
||||||
|
const isAllDay = (current: Dayjs, start: Dayjs, end: Dayjs): boolean => {
|
||||||
|
if (current.isSame(start, 'day')) {
|
||||||
|
return (
|
||||||
|
start.hour() === 0 && start.minute() === 0 && !current.isSame(end, 'day')
|
||||||
|
);
|
||||||
|
} else if (current.isSame(end, 'day')) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export class CalendarIntegration extends Entity {
|
export class CalendarIntegration extends Entity {
|
||||||
constructor(private readonly store: CalendarStore) {
|
constructor(private readonly store: CalendarStore) {
|
||||||
super();
|
super();
|
||||||
@@ -45,19 +71,87 @@ export class CalendarIntegration extends Entity {
|
|||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
subscription$(url: string) {
|
||||||
|
return this.subscriptions$.map(subscriptions =>
|
||||||
|
subscriptions.find(sub => sub.url === url)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
contents$ = LiveData.computed(get => {
|
||||||
|
const subscriptions = get(this.subscriptions$);
|
||||||
|
return subscriptions.map(sub => ({
|
||||||
|
url: sub.url,
|
||||||
|
content: get(sub.content$),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
eventsByDateMap$ = LiveData.computed(get => {
|
||||||
|
const contents = get(this.contents$);
|
||||||
|
const eventsByDate: EventsByDateMap = new Map();
|
||||||
|
|
||||||
|
for (const { content, url } of contents) {
|
||||||
|
if (!content) continue;
|
||||||
|
const jCal = ICAL.parse(content);
|
||||||
|
const vCalendar = new ICAL.Component(jCal);
|
||||||
|
const vEvents = vCalendar.getAllSubcomponents('vevent');
|
||||||
|
|
||||||
|
for (const vEvent of vEvents) {
|
||||||
|
const event = new ICAL.Event(vEvent);
|
||||||
|
const calendarEvent: CalendarEvent = {
|
||||||
|
id: event.uid,
|
||||||
|
url,
|
||||||
|
title: event.summary,
|
||||||
|
startAt: event.startDate,
|
||||||
|
endAt: event.endDate,
|
||||||
|
};
|
||||||
|
|
||||||
|
// create index for each day of the event
|
||||||
|
if (event.startDate && event.endDate) {
|
||||||
|
const start = dayjs(event.startDate.toJSDate());
|
||||||
|
const end = dayjs(event.endDate.toJSDate());
|
||||||
|
|
||||||
|
let current = start;
|
||||||
|
while (current.isBefore(end) || current.isSame(end, 'day')) {
|
||||||
|
if (
|
||||||
|
current.isSame(end, 'day') &&
|
||||||
|
end.hour() === 0 &&
|
||||||
|
end.minute() === 0
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const todayEvent: CalendarEvent = { ...calendarEvent };
|
||||||
|
const dateKey = current.format('YYYY-MM-DD');
|
||||||
|
if (!eventsByDate.has(dateKey)) {
|
||||||
|
eventsByDate.set(dateKey, []);
|
||||||
|
}
|
||||||
|
todayEvent.allDay = isAllDay(current, start, end);
|
||||||
|
todayEvent.date = current;
|
||||||
|
todayEvent.id = `${event.uid}-${dateKey}`;
|
||||||
|
eventsByDate.get(dateKey)?.push(todayEvent);
|
||||||
|
current = current.add(1, 'day');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("event's start or end date is missing", event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventsByDate;
|
||||||
|
});
|
||||||
|
eventsByDate$(date: Dayjs) {
|
||||||
|
return this.eventsByDateMap$.map(eventsByDateMap => {
|
||||||
|
const dateKey = date.format('YYYY-MM-DD');
|
||||||
|
const events = [...(eventsByDateMap.get(dateKey) || [])];
|
||||||
|
|
||||||
|
// sort events by start time
|
||||||
|
return events.sort((a, b) => {
|
||||||
|
return (
|
||||||
|
(a.startAt?.toJSDate().getTime() ?? 0) -
|
||||||
|
(b.startAt?.toJSDate().getTime() ?? 0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async verifyUrl(_url: string) {
|
async verifyUrl(_url: string) {
|
||||||
let url = _url;
|
const url = parseCalendarUrl(_url);
|
||||||
try {
|
|
||||||
const urlObj = new URL(url);
|
|
||||||
if (urlObj.protocol === 'webcal:') {
|
|
||||||
urlObj.protocol = 'https';
|
|
||||||
}
|
|
||||||
url = urlObj.toString();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
throw new Error('Invalid URL');
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const content = await response.text();
|
const content = await response.text();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { IntegrationRefStore } from './store/integration-ref';
|
|||||||
import { ReadwiseStore } from './store/readwise';
|
import { ReadwiseStore } from './store/readwise';
|
||||||
|
|
||||||
export { IntegrationService };
|
export { IntegrationService };
|
||||||
|
export type { CalendarEvent } from './entities/calendar';
|
||||||
export { CalendarIntegration } from './entities/calendar';
|
export { CalendarIntegration } from './entities/calendar';
|
||||||
export { CalendarSubscription } from './entities/calendar-subscription';
|
export { CalendarSubscription } from './entities/calendar-subscription';
|
||||||
export { IntegrationTypeIcon } from './views/icon';
|
export { IntegrationTypeIcon } from './views/icon';
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export const parseCalendarUrl = (_url: string) => {
|
||||||
|
let url = _url;
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
if (urlObj.protocol === 'webcal:') {
|
||||||
|
urlObj.protocol = 'https';
|
||||||
|
}
|
||||||
|
url = urlObj.toString();
|
||||||
|
return url;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw new Error(`Invalid URL: "${url}"`);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -7653,6 +7653,14 @@ export function useAFFiNEI18N(): {
|
|||||||
* `An error occurred while adding the calendar`
|
* `An error occurred while adding the calendar`
|
||||||
*/
|
*/
|
||||||
["com.affine.integration.calendar.new-error"](): string;
|
["com.affine.integration.calendar.new-error"](): string;
|
||||||
|
/**
|
||||||
|
* `All day`
|
||||||
|
*/
|
||||||
|
["com.affine.integration.calendar.all-day"](): string;
|
||||||
|
/**
|
||||||
|
* `New doc`
|
||||||
|
*/
|
||||||
|
["com.affine.integration.calendar.new-doc"](): string;
|
||||||
/**
|
/**
|
||||||
* `Notes`
|
* `Notes`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1915,6 +1915,8 @@
|
|||||||
"com.affine.integration.calendar.new-title": "Add a calendar by URL",
|
"com.affine.integration.calendar.new-title": "Add a calendar by URL",
|
||||||
"com.affine.integration.calendar.new-url-label": "Calendar URL",
|
"com.affine.integration.calendar.new-url-label": "Calendar URL",
|
||||||
"com.affine.integration.calendar.new-error": "An error occurred while adding the calendar",
|
"com.affine.integration.calendar.new-error": "An error occurred while adding the calendar",
|
||||||
|
"com.affine.integration.calendar.all-day": "All day",
|
||||||
|
"com.affine.integration.calendar.new-doc": "New doc",
|
||||||
"com.affine.audio.notes": "Notes",
|
"com.affine.audio.notes": "Notes",
|
||||||
"com.affine.audio.transcribing": "Transcribing",
|
"com.affine.audio.transcribing": "Transcribing",
|
||||||
"com.affine.audio.transcribe.non-owner.confirm.title": "Unable to retrieve AI results for others",
|
"com.affine.audio.transcribe.non-owner.confirm.title": "Unable to retrieve AI results for others",
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ __metadata:
|
|||||||
"@affine/templates": "workspace:*"
|
"@affine/templates": "workspace:*"
|
||||||
"@affine/track": "workspace:*"
|
"@affine/track": "workspace:*"
|
||||||
"@blocksuite/affine": "workspace:*"
|
"@blocksuite/affine": "workspace:*"
|
||||||
"@blocksuite/icons": "npm:^2.2.12"
|
"@blocksuite/icons": "npm:^2.2.13"
|
||||||
"@dotlottie/player-component": "npm:^2.7.12"
|
"@dotlottie/player-component": "npm:^2.7.12"
|
||||||
"@emotion/cache": "npm:^11.14.0"
|
"@emotion/cache": "npm:^11.14.0"
|
||||||
"@emotion/react": "npm:^11.14.0"
|
"@emotion/react": "npm:^11.14.0"
|
||||||
@@ -3986,7 +3986,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@blocksuite/icons@npm:^2.2.12":
|
"@blocksuite/icons@npm:^2.2.12, @blocksuite/icons@npm:^2.2.13":
|
||||||
version: 2.2.13
|
version: 2.2.13
|
||||||
resolution: "@blocksuite/icons@npm:2.2.13"
|
resolution: "@blocksuite/icons@npm:2.2.13"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user