@@ -6,13 +6,11 @@ export const card = style({
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
background: cssVarV2.layer.background.primary,
|
background: cssVarV2.layer.background.primary,
|
||||||
border: `1px solid ${cssVarV2.layer.insideBorder.border}`,
|
border: `1px solid ${cssVarV2.layer.insideBorder.border}`,
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 4,
|
|
||||||
});
|
});
|
||||||
export const divider = style({
|
export const divider = style({
|
||||||
height: 8,
|
height: 8,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
margin: '4px 0',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
':before': {
|
':before': {
|
||||||
@@ -26,6 +24,7 @@ export const header = style({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
|
padding: '7px 0',
|
||||||
});
|
});
|
||||||
export const colorPickerTrigger = style({
|
export const colorPickerTrigger = style({
|
||||||
width: 24,
|
width: 24,
|
||||||
@@ -82,3 +81,21 @@ export const name = style({
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const allDayEventsContainer = style({
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateRows: '1fr',
|
||||||
|
transition:
|
||||||
|
'grid-template-rows 0.4s cubic-bezier(.07,.83,.46,1), opacity 0.4s ease',
|
||||||
|
selectors: {
|
||||||
|
'&[data-collapsed="true"]': {
|
||||||
|
gridTemplateRows: '0fr',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const allDayEventsContent = style({
|
||||||
|
overflow: 'hidden',
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Menu } from '@affine/component';
|
import { Button, Menu, useConfirmModal } from '@affine/component';
|
||||||
import {
|
import {
|
||||||
type CalendarSubscription,
|
type CalendarSubscription,
|
||||||
IntegrationService,
|
IntegrationService,
|
||||||
@@ -7,6 +7,7 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { IntegrationSettingToggle } from '../setting';
|
||||||
import * as styles from './subscription-setting.css';
|
import * as styles from './subscription-setting.css';
|
||||||
|
|
||||||
export const SubscriptionSetting = ({
|
export const SubscriptionSetting = ({
|
||||||
@@ -18,7 +19,7 @@ export const SubscriptionSetting = ({
|
|||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const calendar = useService(IntegrationService).calendar;
|
const calendar = useService(IntegrationService).calendar;
|
||||||
const config = useLiveData(subscription.config$);
|
const config = useLiveData(subscription.config$);
|
||||||
const name = useLiveData(subscription.name$);
|
const name = useLiveData(subscription.name$) || t['Untitled']();
|
||||||
|
|
||||||
const handleColorChange = useCallback(
|
const handleColorChange = useCallback(
|
||||||
(color: string) => {
|
(color: string) => {
|
||||||
@@ -28,9 +29,17 @@ export const SubscriptionSetting = ({
|
|||||||
[calendar, subscription.url]
|
[calendar, subscription.url]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleUnsubscribe = useCallback(() => {
|
const toggleShowEvents = useCallback(() => {
|
||||||
calendar.deleteSubscription(subscription.url);
|
calendar.updateSubscription(subscription.url, {
|
||||||
}, [calendar, subscription.url]);
|
showEvents: !config?.showEvents,
|
||||||
|
});
|
||||||
|
}, [calendar, subscription.url, config?.showEvents]);
|
||||||
|
|
||||||
|
const toggleShowAllDayEvents = useCallback(() => {
|
||||||
|
calendar.updateSubscription(subscription.url, {
|
||||||
|
showAllDayEvents: !config?.showAllDayEvents,
|
||||||
|
});
|
||||||
|
}, [calendar, subscription.url, config?.showAllDayEvents]);
|
||||||
|
|
||||||
if (!config) return null;
|
if (!config) return null;
|
||||||
|
|
||||||
@@ -52,12 +61,58 @@ export const SubscriptionSetting = ({
|
|||||||
style={{ color: config.color }}
|
style={{ color: config.color }}
|
||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
<div className={styles.name}>{name || t['Untitled']()}</div>
|
<div className={styles.name}>{name}</div>
|
||||||
|
<UnsubscribeButton url={subscription.url} name={name} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.divider} />
|
||||||
|
<IntegrationSettingToggle
|
||||||
|
name={t['com.affine.integration.calendar.show-events']()}
|
||||||
|
desc={t['com.affine.integration.calendar.show-events-desc']()}
|
||||||
|
checked={!!config.showEvents}
|
||||||
|
onChange={toggleShowEvents}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
data-collapsed={!config.showEvents}
|
||||||
|
className={styles.allDayEventsContainer}
|
||||||
|
>
|
||||||
|
<div className={styles.allDayEventsContent}>
|
||||||
|
<div className={styles.divider} />
|
||||||
|
<IntegrationSettingToggle
|
||||||
|
name={t['com.affine.integration.calendar.show-all-day-events']()}
|
||||||
|
checked={!!config.showAllDayEvents}
|
||||||
|
onChange={toggleShowAllDayEvents}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UnsubscribeButton = ({ url, name }: { url: string; name: string }) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const calendar = useService(IntegrationService).calendar;
|
||||||
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
|
||||||
|
const handleUnsubscribe = useCallback(() => {
|
||||||
|
openConfirmModal({
|
||||||
|
title: t['com.affine.integration.calendar.unsubscribe'](),
|
||||||
|
children: t.t('com.affine.integration.calendar.unsubscribe-content', {
|
||||||
|
name,
|
||||||
|
}),
|
||||||
|
onConfirm: () => {
|
||||||
|
calendar.deleteSubscription(url);
|
||||||
|
},
|
||||||
|
confirmText: t['com.affine.integration.calendar.unsubscribe'](),
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'error',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [calendar, name, openConfirmModal, t, url]);
|
||||||
|
|
||||||
|
return (
|
||||||
<Button variant="error" onClick={handleUnsubscribe}>
|
<Button variant="error" onClick={handleUnsubscribe}>
|
||||||
{t['com.affine.integration.calendar.unsubscribe']()}
|
{t['com.affine.integration.calendar.unsubscribe']()}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ export const settingItem = style({
|
|||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
|
selectors: {
|
||||||
|
'&[data-has-desc="false"]': {
|
||||||
|
padding: '5px 0',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const settingName = style({
|
export const settingName = style({
|
||||||
@@ -56,6 +61,7 @@ export const settingDesc = style({
|
|||||||
lineHeight: '20px',
|
lineHeight: '20px',
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: cssVarV2.text.secondary,
|
color: cssVarV2.text.secondary,
|
||||||
|
marginTop: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const textRadioGroup = style({
|
export const textRadioGroup = style({
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ export const IntegrationSettingItem = ({
|
|||||||
...props
|
...props
|
||||||
}: IntegrationSettingItemProps) => {
|
}: IntegrationSettingItemProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx(styles.settingItem, className)} {...props}>
|
<div
|
||||||
|
data-has-desc={!!desc}
|
||||||
|
className={clsx(styles.settingItem, className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
{name && <h6 className={styles.settingName}>{name}</h6>}
|
{name && <h6 className={styles.settingName}>{name}</h6>}
|
||||||
{desc && <p className={styles.settingDesc}>{desc}</p>}
|
{desc && <p className={styles.settingDesc}>{desc}</p>}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
onComplete,
|
onComplete,
|
||||||
onStart,
|
onStart,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import ICAL from 'ical.js';
|
import ICAL from 'ical.js';
|
||||||
import { EMPTY, mergeMap, switchMap, throttleTime } from 'rxjs';
|
import { EMPTY, mergeMap, switchMap, throttleTime } from 'rxjs';
|
||||||
|
|
||||||
@@ -14,7 +15,9 @@ import type {
|
|||||||
CalendarStore,
|
CalendarStore,
|
||||||
CalendarSubscriptionConfig,
|
CalendarSubscriptionConfig,
|
||||||
} from '../store/calendar';
|
} from '../store/calendar';
|
||||||
|
import type { CalendarEvent, EventsByDateMap } from '../type';
|
||||||
import { parseCalendarUrl } from '../utils/calendar-url-parser';
|
import { parseCalendarUrl } from '../utils/calendar-url-parser';
|
||||||
|
import { isAllDay } from '../utils/is-all-day';
|
||||||
|
|
||||||
export class CalendarSubscription extends Entity<{ url: string }> {
|
export class CalendarSubscription extends Entity<{ url: string }> {
|
||||||
constructor(private readonly store: CalendarStore) {
|
constructor(private readonly store: CalendarStore) {
|
||||||
@@ -39,6 +42,65 @@ export class CalendarSubscription extends Entity<{ url: string }> {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
eventsByDateMap$ = LiveData.computed(get => {
|
||||||
|
const content = get(this.content$);
|
||||||
|
const config = get(this.config$);
|
||||||
|
|
||||||
|
const map: EventsByDateMap = new Map();
|
||||||
|
|
||||||
|
if (!content || !config?.showEvents) return map;
|
||||||
|
|
||||||
|
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: this.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 (!map.has(dateKey)) {
|
||||||
|
map.set(dateKey, []);
|
||||||
|
}
|
||||||
|
todayEvent.allDay = isAllDay(current, start, end);
|
||||||
|
todayEvent.date = current;
|
||||||
|
todayEvent.id = `${event.uid}-${dateKey}`;
|
||||||
|
if (
|
||||||
|
config.showAllDayEvents ||
|
||||||
|
(!config.showAllDayEvents && !todayEvent.allDay)
|
||||||
|
) {
|
||||||
|
map.get(dateKey)?.push(todayEvent);
|
||||||
|
}
|
||||||
|
current = current.add(1, 'day');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("event's start or end date is missing", event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
url = this.props.url;
|
url = this.props.url;
|
||||||
loading$ = new LiveData(false);
|
loading$ = new LiveData(false);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Entity, LiveData, ObjectPool } from '@toeverything/infra';
|
import { Entity, LiveData, ObjectPool } from '@toeverything/infra';
|
||||||
import dayjs, { type Dayjs } from 'dayjs';
|
import { type Dayjs } from 'dayjs';
|
||||||
import ICAL from 'ical.js';
|
import ICAL from 'ical.js';
|
||||||
import { Observable, switchMap } from 'rxjs';
|
import { Observable, switchMap } from 'rxjs';
|
||||||
|
|
||||||
@@ -7,33 +7,10 @@ import type {
|
|||||||
CalendarStore,
|
CalendarStore,
|
||||||
CalendarSubscriptionConfig,
|
CalendarSubscriptionConfig,
|
||||||
} from '../store/calendar';
|
} from '../store/calendar';
|
||||||
|
import type { CalendarEvent } from '../type';
|
||||||
import { parseCalendarUrl } from '../utils/calendar-url-parser';
|
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();
|
||||||
@@ -76,65 +53,20 @@ export class CalendarIntegration extends Entity {
|
|||||||
subscriptions.find(sub => sub.url === url)
|
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 => {
|
eventsByDateMap$ = LiveData.computed(get => {
|
||||||
const contents = get(this.contents$);
|
return get(this.subscriptions$)
|
||||||
const eventsByDate: EventsByDateMap = new Map();
|
.map(sub => get(sub.eventsByDateMap$))
|
||||||
|
.reduce((acc, map) => {
|
||||||
for (const { content, url } of contents) {
|
for (const [date, events] of map) {
|
||||||
if (!content) continue;
|
acc.set(
|
||||||
const jCal = ICAL.parse(content);
|
date,
|
||||||
const vCalendar = new ICAL.Component(jCal);
|
acc.has(date) ? [...(acc.get(date) ?? []), ...events] : [...events]
|
||||||
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 };
|
return acc;
|
||||||
const dateKey = current.format('YYYY-MM-DD');
|
}, new Map<string, CalendarEvent[]>());
|
||||||
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) {
|
eventsByDate$(date: Dayjs) {
|
||||||
return this.eventsByDateMap$.map(eventsByDateMap => {
|
return this.eventsByDateMap$.map(eventsByDateMap => {
|
||||||
const dateKey = date.format('YYYY-MM-DD');
|
const dateKey = date.format('YYYY-MM-DD');
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ 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 type { CalendarEvent } from './type';
|
||||||
export { IntegrationTypeIcon } from './views/icon';
|
export { IntegrationTypeIcon } from './views/icon';
|
||||||
export { DocIntegrationPropertiesTable } from './views/properties-table';
|
export { DocIntegrationPropertiesTable } from './views/properties-table';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import type { I18nString } from '@affine/i18n';
|
import type { I18nString } from '@affine/i18n';
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
import type ICAL from 'ical.js';
|
||||||
import type { ComponentType, SVGProps } from 'react';
|
import type { ComponentType, SVGProps } from 'react';
|
||||||
|
|
||||||
import type { DocIntegrationRef } from '../db/schema/schema';
|
import type { DocIntegrationRef } from '../db/schema/schema';
|
||||||
@@ -96,3 +98,18 @@ export interface ReadwiseConfig {
|
|||||||
// Zotero
|
// Zotero
|
||||||
// ===============================
|
// ===============================
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
|
// ===============================
|
||||||
|
// Calendar
|
||||||
|
// ===============================
|
||||||
|
export type CalendarEvent = {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
title: string;
|
||||||
|
startAt?: ICAL.Time;
|
||||||
|
endAt?: ICAL.Time;
|
||||||
|
allDay?: boolean;
|
||||||
|
date?: Dayjs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EventsByDateMap = Map<string, CalendarEvent[]>;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -7661,6 +7661,24 @@ export function useAFFiNEI18N(): {
|
|||||||
* `New doc`
|
* `New doc`
|
||||||
*/
|
*/
|
||||||
["com.affine.integration.calendar.new-doc"](): string;
|
["com.affine.integration.calendar.new-doc"](): string;
|
||||||
|
/**
|
||||||
|
* `Show calendar events`
|
||||||
|
*/
|
||||||
|
["com.affine.integration.calendar.show-events"](): string;
|
||||||
|
/**
|
||||||
|
* `Enabling this setting allows you to connect your calendar events to your Journal in AFFiNE`
|
||||||
|
*/
|
||||||
|
["com.affine.integration.calendar.show-events-desc"](): string;
|
||||||
|
/**
|
||||||
|
* `Show all day event`
|
||||||
|
*/
|
||||||
|
["com.affine.integration.calendar.show-all-day-events"](): string;
|
||||||
|
/**
|
||||||
|
* `Are you sure you want to unsubscribe "{{name}}"? Unsubscribing this account will remove its data from Journal.`
|
||||||
|
*/
|
||||||
|
["com.affine.integration.calendar.unsubscribe-content"](options: {
|
||||||
|
readonly name: string;
|
||||||
|
}): string;
|
||||||
/**
|
/**
|
||||||
* `Notes`
|
* `Notes`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1917,6 +1917,10 @@
|
|||||||
"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.all-day": "All day",
|
||||||
"com.affine.integration.calendar.new-doc": "New doc",
|
"com.affine.integration.calendar.new-doc": "New doc",
|
||||||
|
"com.affine.integration.calendar.show-events": "Show calendar events",
|
||||||
|
"com.affine.integration.calendar.show-events-desc": "Enabling this setting allows you to connect your calendar events to your Journal in AFFiNE",
|
||||||
|
"com.affine.integration.calendar.show-all-day-events": "Show all day event",
|
||||||
|
"com.affine.integration.calendar.unsubscribe-content": "Are you sure you want to unsubscribe \"{{name}}\"? Unsubscribing this account will remove its data from Journal.",
|
||||||
"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",
|
||||||
|
|||||||
Reference in New Issue
Block a user