@dayflow/plugin-sidebar 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ICalendarApp, CalendarType, TNode, CreateCalendarDialogProps, CalendarPlugin } from '@dayflow/core';
|
|
1
|
+
import { ICalendarApp, CalendarType, TNode, SidebarHeaderSlotArgs, CreateCalendarDialogProps, CalendarPlugin } from '@dayflow/core';
|
|
2
|
+
export { SidebarHeaderSlotArgs } from '@dayflow/core';
|
|
2
3
|
|
|
3
4
|
interface CalendarSidebarRenderProps {
|
|
4
5
|
app: ICalendarApp;
|
|
@@ -8,6 +9,7 @@ interface CalendarSidebarRenderProps {
|
|
|
8
9
|
isCollapsed: boolean;
|
|
9
10
|
setCollapsed: (collapsed: boolean) => void;
|
|
10
11
|
renderCalendarContextMenu?: (calendar: CalendarType, onClose: () => void) => TNode;
|
|
12
|
+
renderSidebarHeader?: (args: SidebarHeaderSlotArgs) => TNode;
|
|
11
13
|
createCalendarMode?: 'inline' | 'modal';
|
|
12
14
|
renderCreateCalendarDialog?: (props: CreateCalendarDialogProps) => TNode;
|
|
13
15
|
editingCalendarId?: string | null;
|
|
@@ -21,6 +23,7 @@ interface SidebarPluginConfig {
|
|
|
21
23
|
createCalendarMode?: 'inline' | 'modal';
|
|
22
24
|
render?: (props: CalendarSidebarRenderProps) => TNode;
|
|
23
25
|
renderCalendarContextMenu?: (calendar: CalendarType, onClose: () => void) => TNode;
|
|
26
|
+
renderSidebarHeader?: (args: SidebarHeaderSlotArgs) => TNode;
|
|
24
27
|
renderCreateCalendarDialog?: (props: CreateCalendarDialogProps) => TNode;
|
|
25
28
|
[key: string]: unknown;
|
|
26
29
|
}
|
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useLocale, createPortal, cancelButton, ChevronsUpDown, Check, ChevronRight, sidebarHeader, sidebarHeaderToggle, PanelRightClose, PanelRightOpen, sidebarHeaderTitle, getCalendarColorsForHex, generateUniKey, downloadICS, MiniCalendar, ContextMenu,
|
|
1
|
+
import { AudioLines, useLocale, createPortal, cancelButton, ChevronsUpDown, Check, ChevronRight, sidebarHeader, sidebarHeaderToggle, PanelRightClose, PanelRightOpen, sidebarHeaderTitle, getCalendarColorsForHex, generateUniKey, downloadICS, ContentSlot, MiniCalendar, ContextMenu, ContextMenuLabel, ContextMenuItem, ContextMenuSeparator, ContextMenuColorPicker, DefaultColorPicker, BlossomColorPicker, importICSFile, parseICS, sidebarContainer, normalizeCssWidth, registerSidebarImplementation, CreateCalendarDialog } from '@dayflow/core';
|
|
2
2
|
import { options, Fragment, h } from 'preact';
|
|
3
3
|
import { useState, useRef, useCallback, useEffect, useMemo } from 'preact/hooks';
|
|
4
4
|
import { hexToHsl, lightnessToSliderValue } from '@dayflow/blossom-color-picker';
|
|
@@ -177,16 +177,16 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
|
|
|
177
177
|
const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === calendar.id;
|
|
178
178
|
const isActive = activeContextMenuCalendarId === calendar.id ||
|
|
179
179
|
editingId === calendar.id;
|
|
180
|
-
return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-primary' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'calendar-checkbox shrink-0 cursor-pointer', style: {
|
|
180
|
+
return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'df-calendar-checkbox shrink-0 cursor-pointer', style: {
|
|
181
181
|
'--checkbox-color': calendarColor,
|
|
182
|
-
}, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-primary' }))] }, calendar.id));
|
|
182
|
+
}, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u(Fragment, { children: [u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }), calendar.subscribed && (u(AudioLines, { width: 13, height: 13, className: 'ml-1 shrink-0 text-gray-400 dark:text-gray-500' }))] }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' }))] }, calendar.id));
|
|
183
183
|
}) }) }));
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
const DeleteCalendarDialog = ({ calendarId, calendarName, calendars, step, onStepChange, onConfirmDelete, onCancel, onMergeSelect, }) => {
|
|
187
187
|
const [showMergeDropdown, setShowMergeDropdown] = useState(false);
|
|
188
188
|
const { t } = useLocale();
|
|
189
|
-
return createPortal(u("div", { className: 'fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-
|
|
189
|
+
return createPortal(u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: step === 'initial' ? (u(Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('deleteCalendarMessage', { calendarName }) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-white shadow-lg dark:border-slate-700 dark:bg-gray-800', children: calendars
|
|
190
190
|
.filter(c => c.id !== calendarId)
|
|
191
191
|
.map(calendar => (u("div", { className: 'flex cursor-pointer items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-slate-700', onClick: () => {
|
|
192
192
|
onMergeSelect(calendar.id);
|
|
@@ -243,9 +243,10 @@ const ImportCalendarDialog = ({ calendars, filename, onConfirm, onCancel, }) =>
|
|
|
243
243
|
top: rect.bottom,
|
|
244
244
|
left: rect.left,
|
|
245
245
|
width: rect.width,
|
|
246
|
+
overscrollBehavior: 'none',
|
|
246
247
|
}, children: u("div", { className: 'py-1', children: [calendars.map(calendar => (u("div", { className: `flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 ${selectedCalendarId === calendar.id ? 'bg-primary/10' : ''}`, onClick: () => handleSelect(calendar.id), children: [u("div", { className: 'mr-3 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: calendar.colors.lineColor } }), u("span", { className: `flex-1 truncate text-sm ${selectedCalendarId === calendar.id ? 'font-medium text-primary' : 'text-gray-700 dark:text-gray-200'}`, children: calendar.name || calendar.id }), selectedCalendarId === calendar.id && (u(Check, { className: 'ml-2 h-4 w-4 shrink-0 text-primary' }))] }, calendar.id))), u("div", { className: 'my-1 border-t border-gray-100 dark:border-slate-700' }), u("div", { className: `flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 ${isNewSelected ? 'bg-primary/10' : ''}`, onClick: () => handleSelect(NEW_CALENDAR_ID), children: [u("span", { className: `flex-1 truncate text-sm ${isNewSelected ? 'font-medium text-primary' : 'pl-6 text-gray-700 dark:text-gray-200'}`, children: [t('newCalendar') || 'New Calendar', ": ", filename] }), isNewSelected && (u(Check, { className: 'ml-2 h-4 w-4 shrink-0 text-primary' }))] })] }) }), document.body);
|
|
247
248
|
};
|
|
248
|
-
return (u("div", { className: 'fixed inset-0 z-100 flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-
|
|
249
|
+
return (u("div", { className: 'df-portal fixed inset-0 z-100 flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-900', children: [u("h2", { className: 'mb-4 text-lg font-semibold text-gray-900 dark:text-white', children: t('addSchedule') || 'Add Schedule' }), u("p", { className: 'mb-4 text-sm text-gray-600 dark:text-gray-300', children: t('importCalendarMessage') ||
|
|
249
250
|
'This calendar contains new events. Please select a target calendar.' }), u("div", { className: 'relative', children: [u("button", { ref: triggerRef, type: 'button', className: 'flex w-full items-center rounded-md border border-gray-300 px-3 py-2 shadow-sm transition-colors hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800', onClick: () => setIsOpen(!isOpen), children: [!isNewSelected && selectedCalendar && (u("div", { className: 'mr-3 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: selectedCalendar.colors.lineColor } })), u("span", { className: `flex-1 truncate text-left text-sm font-medium text-gray-700 dark:text-gray-200 ${isNewSelected ? 'pl-0' : ''}`, children: isNewSelected
|
|
250
251
|
? `${t('newCalendar')}: ${filename}`
|
|
251
252
|
: (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.name) || (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.id) }), u(ChevronsUpDown, { className: 'ml-2 h-4 w-4 shrink-0 text-gray-400' })] }), renderDropdown()] }), u("div", { className: 'mt-8 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: cancelButton, children: t('cancel') || 'Cancel' }), u("button", { type: 'button', onClick: () => onConfirm(selectedCalendarId), className: 'rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90', children: t('ok') || 'OK' })] })] }) }));
|
|
@@ -253,7 +254,7 @@ const ImportCalendarDialog = ({ calendars, filename, onConfirm, onCancel, }) =>
|
|
|
253
254
|
|
|
254
255
|
const MergeCalendarDialog = ({ sourceName, targetName, onConfirm, onCancel, }) => {
|
|
255
256
|
const { t } = useLocale();
|
|
256
|
-
return (u("div", { className: 'fixed inset-0 z-
|
|
257
|
+
return (u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('mergeConfirmTitle', { sourceName, targetName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('mergeConfirmMessage', { sourceName, targetName }) }), u("div", { className: 'mt-6 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: cancelButton, children: t('cancel') }), u("button", { type: 'button', onClick: onConfirm, className: 'rounded-md bg-destructive px-3 py-2 text-xs font-medium text-destructive-foreground hover:bg-destructive/90', children: t('merge') })] })] }) }));
|
|
257
258
|
};
|
|
258
259
|
|
|
259
260
|
const stopPropagation = (e) => e.stopPropagation();
|
|
@@ -290,8 +291,8 @@ const MergeMenuItem = ({ calendars, currentCalendarId, onMergeSelect, }) => {
|
|
|
290
291
|
const availableCalendars = calendars.filter(c => c.id !== currentCalendarId);
|
|
291
292
|
if (availableCalendars.length === 0)
|
|
292
293
|
return null;
|
|
293
|
-
return (u(Fragment, { children: [u("div", { ref: itemRef, className: 'relative flex cursor-default items-center justify-between rounded-sm px-3 py-0.5 text-[12px] transition-colors outline-none select-none hover:bg-primary hover:text-
|
|
294
|
-
createPortal(u("div", { ref: submenuRef, "data-submenu-content": 'true', className: 'animate-in fade-in-
|
|
294
|
+
return (u(Fragment, { children: [u("div", { ref: itemRef, className: 'relative flex cursor-default items-center justify-between rounded-sm px-3 py-0.5 text-[12px] text-[var(--df-color-foreground)] transition-colors outline-none select-none hover:bg-[var(--df-color-primary)] hover:text-[var(--df-color-primary-foreground)]', onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [u("span", { children: t('merge') }), u(ChevronRight, { className: 'h-4 w-4' })] }), isHovered &&
|
|
295
|
+
createPortal(u("div", { ref: submenuRef, "data-submenu-content": 'true', className: 'df-portal df-animate-in df-fade-in df-zoom-in-95 fixed z-60 min-w-48 overflow-hidden rounded-md border border-slate-200 bg-white p-1 shadow-md duration-100 dark:border-slate-800 dark:bg-slate-950', style: { top: position.y, left: position.x }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: e => e.stopPropagation(), children: availableCalendars.map(calendar => (u("div", { className: 'flex cursor-pointer items-center rounded-sm px-3 py-1 text-[12px] text-[var(--df-color-foreground)] transition-colors hover:bg-[var(--df-color-primary)] hover:text-[var(--df-color-primary-foreground)]', onClick: e => {
|
|
295
296
|
e.stopPropagation();
|
|
296
297
|
onMergeSelect(calendar.id);
|
|
297
298
|
}, children: [u("div", { className: 'mr-2 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: calendar.colors.lineColor } }), u("span", { className: 'truncate', children: calendar.name || calendar.id })] }, calendar.id))) }), document.body)] }));
|
|
@@ -302,7 +303,37 @@ const SidebarHeader = ({ isCollapsed, onCollapseToggle, }) => {
|
|
|
302
303
|
return (u("div", { className: sidebarHeader, children: [u("button", { type: 'button', "aria-label": isCollapsed ? t('expandSidebar') : t('collapseSidebar'), className: sidebarHeaderToggle, onClick: onCollapseToggle, children: isCollapsed ? (u(PanelRightClose, { className: 'h-4 w-4 text-gray-500 dark:text-gray-400' })) : (u(PanelRightOpen, { className: 'h-4 w-4 text-gray-500 dark:text-gray-400' })) }), !isCollapsed && (u("div", { className: 'ml-3 flex flex-1 items-center justify-between', children: u("span", { className: sidebarHeaderTitle, children: t('calendars') }) }))] }));
|
|
303
304
|
};
|
|
304
305
|
|
|
305
|
-
const
|
|
306
|
+
const SubscribeCalendarDialog = ({ onSubscribe, onCancel, }) => {
|
|
307
|
+
const { t } = useLocale();
|
|
308
|
+
const [url, setUrl] = useState('');
|
|
309
|
+
const [loading, setLoading] = useState(false);
|
|
310
|
+
const [error, setError] = useState(null);
|
|
311
|
+
const handleSubmit = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
312
|
+
const trimmed = url.trim();
|
|
313
|
+
if (!trimmed)
|
|
314
|
+
return;
|
|
315
|
+
setError(null);
|
|
316
|
+
setLoading(true);
|
|
317
|
+
try {
|
|
318
|
+
yield onSubscribe(trimmed);
|
|
319
|
+
}
|
|
320
|
+
catch (_a) {
|
|
321
|
+
setError(t('subscribeError'));
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
setLoading(false);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
const handleKeyDown = (e) => {
|
|
328
|
+
if (e.key === 'Enter')
|
|
329
|
+
handleSubmit();
|
|
330
|
+
if (e.key === 'Escape')
|
|
331
|
+
onCancel();
|
|
332
|
+
};
|
|
333
|
+
return (u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-xl rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('subscribeCalendarTitle') }), u("div", { className: 'mt-4', children: [u("div", { className: 'flex items-center gap-3', children: [u("label", { className: 'shrink-0 text-sm font-medium text-gray-700 dark:text-gray-300', children: t('calendarUrl') }), u("input", { type: 'url', value: url, onInput: e => setUrl(e.target.value), onKeyDown: handleKeyDown, placeholder: t('calendarUrlPlaceholder'), disabled: loading, autoFocus: true, className: 'flex-1 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400' })] }), error && (u("p", { className: 'mt-2 text-xs text-red-500 dark:text-red-400', children: error }))] }), u("div", { className: 'mt-6 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, disabled: loading, className: cancelButton, children: t('cancel') }), u("button", { type: 'button', onClick: handleSubmit, disabled: loading || !url.trim(), className: 'rounded-md bg-primary px-4 py-2 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50', children: loading ? t('fetchingCalendar') : t('subscribe') })] })] }) }));
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, renderSidebarHeader, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }) => {
|
|
306
337
|
var _a, _b, _c, _d;
|
|
307
338
|
const { t } = useLocale();
|
|
308
339
|
// Detect if custom color picker slot is provided
|
|
@@ -331,6 +362,8 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
331
362
|
const [deleteState, setDeleteState] = useState(null);
|
|
332
363
|
// Import Calendar State
|
|
333
364
|
const [importState, setImportState] = useState(null);
|
|
365
|
+
// Subscribe Calendar State
|
|
366
|
+
const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
|
|
334
367
|
const handleContextMenu = useCallback((e, calendarId) => {
|
|
335
368
|
e.preventDefault();
|
|
336
369
|
e.stopPropagation(); // Stop propagation to prevent sidebar context menu
|
|
@@ -445,6 +478,51 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
445
478
|
(_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
446
479
|
handleCloseSidebarContextMenu();
|
|
447
480
|
}, [handleCloseSidebarContextMenu]);
|
|
481
|
+
// Subscribe Calendar handler
|
|
482
|
+
const handleSubscribeClick = useCallback(() => {
|
|
483
|
+
setSubscribeDialogOpen(true);
|
|
484
|
+
handleCloseSidebarContextMenu();
|
|
485
|
+
}, [handleCloseSidebarContextMenu]);
|
|
486
|
+
const handleSubscribeConfirm = useCallback((url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
487
|
+
const response = yield fetch(url);
|
|
488
|
+
if (!response.ok)
|
|
489
|
+
throw new Error(`HTTP ${response.status}`);
|
|
490
|
+
const icsContent = yield response.text();
|
|
491
|
+
const result = parseICS(icsContent);
|
|
492
|
+
// Extract calendar name from X-WR-CALNAME if present
|
|
493
|
+
const nameMatch = icsContent.match(/X-WR-CALNAME[^:]*:([^\r\n]+)/);
|
|
494
|
+
const calendarName = nameMatch
|
|
495
|
+
? nameMatch[1].trim()
|
|
496
|
+
: new URL(url).hostname;
|
|
497
|
+
const presetColors = [
|
|
498
|
+
'#3b82f6',
|
|
499
|
+
'#10b981',
|
|
500
|
+
'#8b5cf6',
|
|
501
|
+
'#f59e0b',
|
|
502
|
+
'#ef4444',
|
|
503
|
+
'#f97316',
|
|
504
|
+
'#ec4899',
|
|
505
|
+
'#14b8a6',
|
|
506
|
+
'#6366f1',
|
|
507
|
+
'#6b7280',
|
|
508
|
+
];
|
|
509
|
+
const randomColor = presetColors[Math.floor(Math.random() * presetColors.length)];
|
|
510
|
+
const { colors: calendarColors, darkColors } = getCalendarColorsForHex(randomColor);
|
|
511
|
+
const calendarId = generateUniKey();
|
|
512
|
+
app.createCalendar({
|
|
513
|
+
id: calendarId,
|
|
514
|
+
name: calendarName,
|
|
515
|
+
isDefault: false,
|
|
516
|
+
colors: calendarColors,
|
|
517
|
+
darkColors,
|
|
518
|
+
isVisible: true,
|
|
519
|
+
subscribed: true,
|
|
520
|
+
});
|
|
521
|
+
result.events.forEach(event => {
|
|
522
|
+
app.addEvent(Object.assign(Object.assign({}, event), { calendarId }));
|
|
523
|
+
});
|
|
524
|
+
setSubscribeDialogOpen(false);
|
|
525
|
+
}), [app]);
|
|
448
526
|
const handleFileChange = useCallback((e) => __awaiter(void 0, void 0, void 0, function* () {
|
|
449
527
|
var _a;
|
|
450
528
|
const file = (_a = e.currentTarget.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
@@ -526,7 +604,13 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
526
604
|
const readOnlyConfig = app.getReadOnlyConfig();
|
|
527
605
|
const isEditable = !app.state.readOnly;
|
|
528
606
|
const isDraggable = readOnlyConfig.draggable !== false;
|
|
529
|
-
return (u("div", { className: sidebarContainer, onContextMenu: isEditable ? handleSidebarContextMenu : undefined, children: [u(
|
|
607
|
+
return (u("div", { className: sidebarContainer, onContextMenu: isEditable ? handleSidebarContextMenu : undefined, children: [u(ContentSlot, { generatorName: 'sidebarHeader', generatorArgs: {
|
|
608
|
+
isCollapsed,
|
|
609
|
+
onCollapseToggle: () => setCollapsed(!isCollapsed),
|
|
610
|
+
}, defaultContent: renderSidebarHeader ? (renderSidebarHeader({
|
|
611
|
+
isCollapsed,
|
|
612
|
+
onCollapseToggle: () => setCollapsed(!isCollapsed),
|
|
613
|
+
})) : (u(SidebarHeader, { isCollapsed: isCollapsed, onCollapseToggle: () => setCollapsed(!isCollapsed) })) }), isCollapsed ? (u(CalendarList, { calendars: calendars, onToggleVisibility: toggleCalendarVisibility, onReorder: isDraggable
|
|
530
614
|
? app.reorderCalendars
|
|
531
615
|
: () => {
|
|
532
616
|
/* noop */
|
|
@@ -557,11 +641,12 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
557
641
|
createPortal(u(ContextMenu, { x: sidebarContextMenu.x, y: sidebarContextMenu.y, onClose: handleCloseSidebarContextMenu, className: 'w-max p-2', children: [u(ContextMenuItem, { onClick: () => {
|
|
558
642
|
onCreateCalendar === null || onCreateCalendar === void 0 ? void 0 : onCreateCalendar();
|
|
559
643
|
handleCloseSidebarContextMenu();
|
|
560
|
-
}, children: t('newCalendar') || 'New Calendar' }), u(ContextMenuItem, { onClick: handleImportClick, children: t('importCalendar') || 'Import Calendar' }), u(ContextMenuItem, { onClick: () => {
|
|
644
|
+
}, children: t('newCalendar') || 'New Calendar' }), u(ContextMenuItem, { onClick: handleImportClick, children: t('importCalendar') || 'Import Calendar' }), u(ContextMenuItem, { onClick: handleSubscribeClick, children: t('subscribeCalendar') || 'Subscribe to Calendar' }), u(ContextMenuItem, { onClick: () => {
|
|
561
645
|
app.triggerRender();
|
|
562
646
|
handleCloseSidebarContextMenu();
|
|
563
647
|
}, children: t('refreshAll') || 'Refresh All' })] }), document.body), u("input", { ref: fileInputRef, type: 'file', accept: '.ics', style: { display: 'none' }, onChange: handleFileChange }), importState &&
|
|
564
|
-
createPortal(u(ImportCalendarDialog, { calendars: calendars, filename: importState.filename, onConfirm: handleImportConfirm, onCancel: () => setImportState(null) }), document.body),
|
|
648
|
+
createPortal(u(ImportCalendarDialog, { calendars: calendars, filename: importState.filename, onConfirm: handleImportConfirm, onCancel: () => setImportState(null) }), document.body), subscribeDialogOpen &&
|
|
649
|
+
createPortal(u(SubscribeCalendarDialog, { onSubscribe: handleSubscribeConfirm, onCancel: () => setSubscribeDialogOpen(false) }), document.body), mergeState &&
|
|
565
650
|
createPortal(u(MergeCalendarDialog, { sourceName: sourceCalendarName, targetName: targetCalendarName, onConfirm: handleMergeConfirm, onCancel: () => setMergeState(null) }), document.body), deleteState &&
|
|
566
651
|
createPortal(u(DeleteCalendarDialog, { calendarId: deleteState.calendarId, calendarName: deleteCalendarName, calendars: calendars, step: deleteState.step, onStepChange: step => setDeleteState(prev => (prev ? Object.assign(Object.assign({}, prev), { step }) : null)), onConfirmDelete: handleConfirmDelete, onCancel: () => setDeleteState(null), onMergeSelect: handleDeleteMergeSelect }), document.body), customColorPicker &&
|
|
567
652
|
createPortal(u("div", { className: 'fixed inset-0 z-50', onMouseDown: () => {
|
|
@@ -589,7 +674,7 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
589
674
|
darkColors,
|
|
590
675
|
});
|
|
591
676
|
},
|
|
592
|
-
}, defaultContent: u("div", { className: 'rounded-lg border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-
|
|
677
|
+
}, defaultContent: u("div", { className: 'rounded-lg border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-gray-900', children: u(DefaultColorPicker, { color: customColorPicker.currentColor, onChange: (color, isPending) => {
|
|
593
678
|
setCustomColorPicker(prev => prev ? Object.assign(Object.assign({}, prev), { currentColor: color.hex }) : null);
|
|
594
679
|
const { colors, darkColors } = getCalendarColorsForHex(color.hex);
|
|
595
680
|
app.updateCalendar(customColorPicker.calendarId, {
|
|
@@ -678,6 +763,7 @@ function createSidebarPlugin(config = {}) {
|
|
|
678
763
|
isCollapsed,
|
|
679
764
|
setCollapsed: setIsCollapsed,
|
|
680
765
|
renderCalendarContextMenu: config.renderCalendarContextMenu,
|
|
766
|
+
renderSidebarHeader: config.renderSidebarHeader,
|
|
681
767
|
createCalendarMode: config.createCalendarMode,
|
|
682
768
|
renderCreateCalendarDialog: config.renderCreateCalendarDialog,
|
|
683
769
|
editingCalendarId,
|
package/dist/index.js
CHANGED
|
@@ -179,16 +179,16 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
|
|
|
179
179
|
const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === calendar.id;
|
|
180
180
|
const isActive = activeContextMenuCalendarId === calendar.id ||
|
|
181
181
|
editingId === calendar.id;
|
|
182
|
-
return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-primary' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'calendar-checkbox shrink-0 cursor-pointer', style: {
|
|
182
|
+
return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'df-calendar-checkbox shrink-0 cursor-pointer', style: {
|
|
183
183
|
'--checkbox-color': calendarColor,
|
|
184
|
-
}, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-primary' }))] }, calendar.id));
|
|
184
|
+
}, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u(preact.Fragment, { children: [u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }), calendar.subscribed && (u(core.AudioLines, { width: 13, height: 13, className: 'ml-1 shrink-0 text-gray-400 dark:text-gray-500' }))] }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' }))] }, calendar.id));
|
|
185
185
|
}) }) }));
|
|
186
186
|
};
|
|
187
187
|
|
|
188
188
|
const DeleteCalendarDialog = ({ calendarId, calendarName, calendars, step, onStepChange, onConfirmDelete, onCancel, onMergeSelect, }) => {
|
|
189
189
|
const [showMergeDropdown, setShowMergeDropdown] = hooks.useState(false);
|
|
190
190
|
const { t } = core.useLocale();
|
|
191
|
-
return core.createPortal(u("div", { className: 'fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-
|
|
191
|
+
return core.createPortal(u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: step === 'initial' ? (u(preact.Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('deleteCalendarMessage', { calendarName }) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-white shadow-lg dark:border-slate-700 dark:bg-gray-800', children: calendars
|
|
192
192
|
.filter(c => c.id !== calendarId)
|
|
193
193
|
.map(calendar => (u("div", { className: 'flex cursor-pointer items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-slate-700', onClick: () => {
|
|
194
194
|
onMergeSelect(calendar.id);
|
|
@@ -245,9 +245,10 @@ const ImportCalendarDialog = ({ calendars, filename, onConfirm, onCancel, }) =>
|
|
|
245
245
|
top: rect.bottom,
|
|
246
246
|
left: rect.left,
|
|
247
247
|
width: rect.width,
|
|
248
|
+
overscrollBehavior: 'none',
|
|
248
249
|
}, children: u("div", { className: 'py-1', children: [calendars.map(calendar => (u("div", { className: `flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 ${selectedCalendarId === calendar.id ? 'bg-primary/10' : ''}`, onClick: () => handleSelect(calendar.id), children: [u("div", { className: 'mr-3 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: calendar.colors.lineColor } }), u("span", { className: `flex-1 truncate text-sm ${selectedCalendarId === calendar.id ? 'font-medium text-primary' : 'text-gray-700 dark:text-gray-200'}`, children: calendar.name || calendar.id }), selectedCalendarId === calendar.id && (u(core.Check, { className: 'ml-2 h-4 w-4 shrink-0 text-primary' }))] }, calendar.id))), u("div", { className: 'my-1 border-t border-gray-100 dark:border-slate-700' }), u("div", { className: `flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 ${isNewSelected ? 'bg-primary/10' : ''}`, onClick: () => handleSelect(NEW_CALENDAR_ID), children: [u("span", { className: `flex-1 truncate text-sm ${isNewSelected ? 'font-medium text-primary' : 'pl-6 text-gray-700 dark:text-gray-200'}`, children: [t('newCalendar') || 'New Calendar', ": ", filename] }), isNewSelected && (u(core.Check, { className: 'ml-2 h-4 w-4 shrink-0 text-primary' }))] })] }) }), document.body);
|
|
249
250
|
};
|
|
250
|
-
return (u("div", { className: 'fixed inset-0 z-100 flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-
|
|
251
|
+
return (u("div", { className: 'df-portal fixed inset-0 z-100 flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-900', children: [u("h2", { className: 'mb-4 text-lg font-semibold text-gray-900 dark:text-white', children: t('addSchedule') || 'Add Schedule' }), u("p", { className: 'mb-4 text-sm text-gray-600 dark:text-gray-300', children: t('importCalendarMessage') ||
|
|
251
252
|
'This calendar contains new events. Please select a target calendar.' }), u("div", { className: 'relative', children: [u("button", { ref: triggerRef, type: 'button', className: 'flex w-full items-center rounded-md border border-gray-300 px-3 py-2 shadow-sm transition-colors hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800', onClick: () => setIsOpen(!isOpen), children: [!isNewSelected && selectedCalendar && (u("div", { className: 'mr-3 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: selectedCalendar.colors.lineColor } })), u("span", { className: `flex-1 truncate text-left text-sm font-medium text-gray-700 dark:text-gray-200 ${isNewSelected ? 'pl-0' : ''}`, children: isNewSelected
|
|
252
253
|
? `${t('newCalendar')}: ${filename}`
|
|
253
254
|
: (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.name) || (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.id) }), u(core.ChevronsUpDown, { className: 'ml-2 h-4 w-4 shrink-0 text-gray-400' })] }), renderDropdown()] }), u("div", { className: 'mt-8 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: core.cancelButton, children: t('cancel') || 'Cancel' }), u("button", { type: 'button', onClick: () => onConfirm(selectedCalendarId), className: 'rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90', children: t('ok') || 'OK' })] })] }) }));
|
|
@@ -255,7 +256,7 @@ const ImportCalendarDialog = ({ calendars, filename, onConfirm, onCancel, }) =>
|
|
|
255
256
|
|
|
256
257
|
const MergeCalendarDialog = ({ sourceName, targetName, onConfirm, onCancel, }) => {
|
|
257
258
|
const { t } = core.useLocale();
|
|
258
|
-
return (u("div", { className: 'fixed inset-0 z-
|
|
259
|
+
return (u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('mergeConfirmTitle', { sourceName, targetName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('mergeConfirmMessage', { sourceName, targetName }) }), u("div", { className: 'mt-6 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: core.cancelButton, children: t('cancel') }), u("button", { type: 'button', onClick: onConfirm, className: 'rounded-md bg-destructive px-3 py-2 text-xs font-medium text-destructive-foreground hover:bg-destructive/90', children: t('merge') })] })] }) }));
|
|
259
260
|
};
|
|
260
261
|
|
|
261
262
|
const stopPropagation = (e) => e.stopPropagation();
|
|
@@ -292,8 +293,8 @@ const MergeMenuItem = ({ calendars, currentCalendarId, onMergeSelect, }) => {
|
|
|
292
293
|
const availableCalendars = calendars.filter(c => c.id !== currentCalendarId);
|
|
293
294
|
if (availableCalendars.length === 0)
|
|
294
295
|
return null;
|
|
295
|
-
return (u(preact.Fragment, { children: [u("div", { ref: itemRef, className: 'relative flex cursor-default items-center justify-between rounded-sm px-3 py-0.5 text-[12px] transition-colors outline-none select-none hover:bg-primary hover:text-
|
|
296
|
-
core.createPortal(u("div", { ref: submenuRef, "data-submenu-content": 'true', className: 'animate-in fade-in-
|
|
296
|
+
return (u(preact.Fragment, { children: [u("div", { ref: itemRef, className: 'relative flex cursor-default items-center justify-between rounded-sm px-3 py-0.5 text-[12px] text-[var(--df-color-foreground)] transition-colors outline-none select-none hover:bg-[var(--df-color-primary)] hover:text-[var(--df-color-primary-foreground)]', onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [u("span", { children: t('merge') }), u(core.ChevronRight, { className: 'h-4 w-4' })] }), isHovered &&
|
|
297
|
+
core.createPortal(u("div", { ref: submenuRef, "data-submenu-content": 'true', className: 'df-portal df-animate-in df-fade-in df-zoom-in-95 fixed z-60 min-w-48 overflow-hidden rounded-md border border-slate-200 bg-white p-1 shadow-md duration-100 dark:border-slate-800 dark:bg-slate-950', style: { top: position.y, left: position.x }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: e => e.stopPropagation(), children: availableCalendars.map(calendar => (u("div", { className: 'flex cursor-pointer items-center rounded-sm px-3 py-1 text-[12px] text-[var(--df-color-foreground)] transition-colors hover:bg-[var(--df-color-primary)] hover:text-[var(--df-color-primary-foreground)]', onClick: e => {
|
|
297
298
|
e.stopPropagation();
|
|
298
299
|
onMergeSelect(calendar.id);
|
|
299
300
|
}, children: [u("div", { className: 'mr-2 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: calendar.colors.lineColor } }), u("span", { className: 'truncate', children: calendar.name || calendar.id })] }, calendar.id))) }), document.body)] }));
|
|
@@ -304,7 +305,37 @@ const SidebarHeader = ({ isCollapsed, onCollapseToggle, }) => {
|
|
|
304
305
|
return (u("div", { className: core.sidebarHeader, children: [u("button", { type: 'button', "aria-label": isCollapsed ? t('expandSidebar') : t('collapseSidebar'), className: core.sidebarHeaderToggle, onClick: onCollapseToggle, children: isCollapsed ? (u(core.PanelRightClose, { className: 'h-4 w-4 text-gray-500 dark:text-gray-400' })) : (u(core.PanelRightOpen, { className: 'h-4 w-4 text-gray-500 dark:text-gray-400' })) }), !isCollapsed && (u("div", { className: 'ml-3 flex flex-1 items-center justify-between', children: u("span", { className: core.sidebarHeaderTitle, children: t('calendars') }) }))] }));
|
|
305
306
|
};
|
|
306
307
|
|
|
307
|
-
const
|
|
308
|
+
const SubscribeCalendarDialog = ({ onSubscribe, onCancel, }) => {
|
|
309
|
+
const { t } = core.useLocale();
|
|
310
|
+
const [url, setUrl] = hooks.useState('');
|
|
311
|
+
const [loading, setLoading] = hooks.useState(false);
|
|
312
|
+
const [error, setError] = hooks.useState(null);
|
|
313
|
+
const handleSubmit = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
314
|
+
const trimmed = url.trim();
|
|
315
|
+
if (!trimmed)
|
|
316
|
+
return;
|
|
317
|
+
setError(null);
|
|
318
|
+
setLoading(true);
|
|
319
|
+
try {
|
|
320
|
+
yield onSubscribe(trimmed);
|
|
321
|
+
}
|
|
322
|
+
catch (_a) {
|
|
323
|
+
setError(t('subscribeError'));
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
setLoading(false);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
const handleKeyDown = (e) => {
|
|
330
|
+
if (e.key === 'Enter')
|
|
331
|
+
handleSubmit();
|
|
332
|
+
if (e.key === 'Escape')
|
|
333
|
+
onCancel();
|
|
334
|
+
};
|
|
335
|
+
return (u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-xl rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800', children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('subscribeCalendarTitle') }), u("div", { className: 'mt-4', children: [u("div", { className: 'flex items-center gap-3', children: [u("label", { className: 'shrink-0 text-sm font-medium text-gray-700 dark:text-gray-300', children: t('calendarUrl') }), u("input", { type: 'url', value: url, onInput: e => setUrl(e.target.value), onKeyDown: handleKeyDown, placeholder: t('calendarUrlPlaceholder'), disabled: loading, autoFocus: true, className: 'flex-1 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400' })] }), error && (u("p", { className: 'mt-2 text-xs text-red-500 dark:text-red-400', children: error }))] }), u("div", { className: 'mt-6 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, disabled: loading, className: core.cancelButton, children: t('cancel') }), u("button", { type: 'button', onClick: handleSubmit, disabled: loading || !url.trim(), className: 'rounded-md bg-primary px-4 py-2 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50', children: loading ? t('fetchingCalendar') : t('subscribe') })] })] }) }));
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, renderSidebarHeader, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }) => {
|
|
308
339
|
var _a, _b, _c, _d;
|
|
309
340
|
const { t } = core.useLocale();
|
|
310
341
|
// Detect if custom color picker slot is provided
|
|
@@ -333,6 +364,8 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
333
364
|
const [deleteState, setDeleteState] = hooks.useState(null);
|
|
334
365
|
// Import Calendar State
|
|
335
366
|
const [importState, setImportState] = hooks.useState(null);
|
|
367
|
+
// Subscribe Calendar State
|
|
368
|
+
const [subscribeDialogOpen, setSubscribeDialogOpen] = hooks.useState(false);
|
|
336
369
|
const handleContextMenu = hooks.useCallback((e, calendarId) => {
|
|
337
370
|
e.preventDefault();
|
|
338
371
|
e.stopPropagation(); // Stop propagation to prevent sidebar context menu
|
|
@@ -447,6 +480,51 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
447
480
|
(_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
448
481
|
handleCloseSidebarContextMenu();
|
|
449
482
|
}, [handleCloseSidebarContextMenu]);
|
|
483
|
+
// Subscribe Calendar handler
|
|
484
|
+
const handleSubscribeClick = hooks.useCallback(() => {
|
|
485
|
+
setSubscribeDialogOpen(true);
|
|
486
|
+
handleCloseSidebarContextMenu();
|
|
487
|
+
}, [handleCloseSidebarContextMenu]);
|
|
488
|
+
const handleSubscribeConfirm = hooks.useCallback((url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
489
|
+
const response = yield fetch(url);
|
|
490
|
+
if (!response.ok)
|
|
491
|
+
throw new Error(`HTTP ${response.status}`);
|
|
492
|
+
const icsContent = yield response.text();
|
|
493
|
+
const result = core.parseICS(icsContent);
|
|
494
|
+
// Extract calendar name from X-WR-CALNAME if present
|
|
495
|
+
const nameMatch = icsContent.match(/X-WR-CALNAME[^:]*:([^\r\n]+)/);
|
|
496
|
+
const calendarName = nameMatch
|
|
497
|
+
? nameMatch[1].trim()
|
|
498
|
+
: new URL(url).hostname;
|
|
499
|
+
const presetColors = [
|
|
500
|
+
'#3b82f6',
|
|
501
|
+
'#10b981',
|
|
502
|
+
'#8b5cf6',
|
|
503
|
+
'#f59e0b',
|
|
504
|
+
'#ef4444',
|
|
505
|
+
'#f97316',
|
|
506
|
+
'#ec4899',
|
|
507
|
+
'#14b8a6',
|
|
508
|
+
'#6366f1',
|
|
509
|
+
'#6b7280',
|
|
510
|
+
];
|
|
511
|
+
const randomColor = presetColors[Math.floor(Math.random() * presetColors.length)];
|
|
512
|
+
const { colors: calendarColors, darkColors } = core.getCalendarColorsForHex(randomColor);
|
|
513
|
+
const calendarId = core.generateUniKey();
|
|
514
|
+
app.createCalendar({
|
|
515
|
+
id: calendarId,
|
|
516
|
+
name: calendarName,
|
|
517
|
+
isDefault: false,
|
|
518
|
+
colors: calendarColors,
|
|
519
|
+
darkColors,
|
|
520
|
+
isVisible: true,
|
|
521
|
+
subscribed: true,
|
|
522
|
+
});
|
|
523
|
+
result.events.forEach(event => {
|
|
524
|
+
app.addEvent(Object.assign(Object.assign({}, event), { calendarId }));
|
|
525
|
+
});
|
|
526
|
+
setSubscribeDialogOpen(false);
|
|
527
|
+
}), [app]);
|
|
450
528
|
const handleFileChange = hooks.useCallback((e) => __awaiter(void 0, void 0, void 0, function* () {
|
|
451
529
|
var _a;
|
|
452
530
|
const file = (_a = e.currentTarget.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
@@ -528,7 +606,13 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
528
606
|
const readOnlyConfig = app.getReadOnlyConfig();
|
|
529
607
|
const isEditable = !app.state.readOnly;
|
|
530
608
|
const isDraggable = readOnlyConfig.draggable !== false;
|
|
531
|
-
return (u("div", { className: core.sidebarContainer, onContextMenu: isEditable ? handleSidebarContextMenu : undefined, children: [u(
|
|
609
|
+
return (u("div", { className: core.sidebarContainer, onContextMenu: isEditable ? handleSidebarContextMenu : undefined, children: [u(core.ContentSlot, { generatorName: 'sidebarHeader', generatorArgs: {
|
|
610
|
+
isCollapsed,
|
|
611
|
+
onCollapseToggle: () => setCollapsed(!isCollapsed),
|
|
612
|
+
}, defaultContent: renderSidebarHeader ? (renderSidebarHeader({
|
|
613
|
+
isCollapsed,
|
|
614
|
+
onCollapseToggle: () => setCollapsed(!isCollapsed),
|
|
615
|
+
})) : (u(SidebarHeader, { isCollapsed: isCollapsed, onCollapseToggle: () => setCollapsed(!isCollapsed) })) }), isCollapsed ? (u(CalendarList, { calendars: calendars, onToggleVisibility: toggleCalendarVisibility, onReorder: isDraggable
|
|
532
616
|
? app.reorderCalendars
|
|
533
617
|
: () => {
|
|
534
618
|
/* noop */
|
|
@@ -559,11 +643,12 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
559
643
|
core.createPortal(u(core.ContextMenu, { x: sidebarContextMenu.x, y: sidebarContextMenu.y, onClose: handleCloseSidebarContextMenu, className: 'w-max p-2', children: [u(core.ContextMenuItem, { onClick: () => {
|
|
560
644
|
onCreateCalendar === null || onCreateCalendar === void 0 ? void 0 : onCreateCalendar();
|
|
561
645
|
handleCloseSidebarContextMenu();
|
|
562
|
-
}, children: t('newCalendar') || 'New Calendar' }), u(core.ContextMenuItem, { onClick: handleImportClick, children: t('importCalendar') || 'Import Calendar' }), u(core.ContextMenuItem, { onClick: () => {
|
|
646
|
+
}, children: t('newCalendar') || 'New Calendar' }), u(core.ContextMenuItem, { onClick: handleImportClick, children: t('importCalendar') || 'Import Calendar' }), u(core.ContextMenuItem, { onClick: handleSubscribeClick, children: t('subscribeCalendar') || 'Subscribe to Calendar' }), u(core.ContextMenuItem, { onClick: () => {
|
|
563
647
|
app.triggerRender();
|
|
564
648
|
handleCloseSidebarContextMenu();
|
|
565
649
|
}, children: t('refreshAll') || 'Refresh All' })] }), document.body), u("input", { ref: fileInputRef, type: 'file', accept: '.ics', style: { display: 'none' }, onChange: handleFileChange }), importState &&
|
|
566
|
-
core.createPortal(u(ImportCalendarDialog, { calendars: calendars, filename: importState.filename, onConfirm: handleImportConfirm, onCancel: () => setImportState(null) }), document.body),
|
|
650
|
+
core.createPortal(u(ImportCalendarDialog, { calendars: calendars, filename: importState.filename, onConfirm: handleImportConfirm, onCancel: () => setImportState(null) }), document.body), subscribeDialogOpen &&
|
|
651
|
+
core.createPortal(u(SubscribeCalendarDialog, { onSubscribe: handleSubscribeConfirm, onCancel: () => setSubscribeDialogOpen(false) }), document.body), mergeState &&
|
|
567
652
|
core.createPortal(u(MergeCalendarDialog, { sourceName: sourceCalendarName, targetName: targetCalendarName, onConfirm: handleMergeConfirm, onCancel: () => setMergeState(null) }), document.body), deleteState &&
|
|
568
653
|
core.createPortal(u(DeleteCalendarDialog, { calendarId: deleteState.calendarId, calendarName: deleteCalendarName, calendars: calendars, step: deleteState.step, onStepChange: step => setDeleteState(prev => (prev ? Object.assign(Object.assign({}, prev), { step }) : null)), onConfirmDelete: handleConfirmDelete, onCancel: () => setDeleteState(null), onMergeSelect: handleDeleteMergeSelect }), document.body), customColorPicker &&
|
|
569
654
|
core.createPortal(u("div", { className: 'fixed inset-0 z-50', onMouseDown: () => {
|
|
@@ -591,7 +676,7 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
591
676
|
darkColors,
|
|
592
677
|
});
|
|
593
678
|
},
|
|
594
|
-
}, defaultContent: u("div", { className: 'rounded-lg border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-
|
|
679
|
+
}, defaultContent: u("div", { className: 'rounded-lg border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-gray-900', children: u(core.DefaultColorPicker, { color: customColorPicker.currentColor, onChange: (color, isPending) => {
|
|
595
680
|
setCustomColorPicker(prev => prev ? Object.assign(Object.assign({}, prev), { currentColor: color.hex }) : null);
|
|
596
681
|
const { colors, darkColors } = core.getCalendarColorsForHex(color.hex);
|
|
597
682
|
app.updateCalendar(customColorPicker.calendarId, {
|
|
@@ -680,6 +765,7 @@ function createSidebarPlugin(config = {}) {
|
|
|
680
765
|
isCollapsed,
|
|
681
766
|
setCollapsed: setIsCollapsed,
|
|
682
767
|
renderCalendarContextMenu: config.renderCalendarContextMenu,
|
|
768
|
+
renderSidebarHeader: config.renderSidebarHeader,
|
|
683
769
|
createCalendarMode: config.createCalendarMode,
|
|
684
770
|
renderCreateCalendarDialog: config.renderCreateCalendarDialog,
|
|
685
771
|
editingCalendarId,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { JSX } from 'preact';
|
|
2
2
|
import type { CalendarSidebarRenderProps } from './plugin';
|
|
3
|
-
declare const DefaultCalendarSidebar: ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }: CalendarSidebarRenderProps) => JSX.Element;
|
|
3
|
+
declare const DefaultCalendarSidebar: ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, renderSidebarHeader, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }: CalendarSidebarRenderProps) => JSX.Element;
|
|
4
4
|
export default DefaultCalendarSidebar;
|
package/dist/types/plugin.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { CalendarPlugin, ICalendarApp, CalendarType, TNode, CreateCalendarDialogProps } from '@dayflow/core';
|
|
1
|
+
import { CalendarPlugin, ICalendarApp, CalendarType, TNode, CreateCalendarDialogProps, SidebarHeaderSlotArgs } from '@dayflow/core';
|
|
2
|
+
export type { SidebarHeaderSlotArgs };
|
|
2
3
|
export interface CalendarSidebarRenderProps {
|
|
3
4
|
app: ICalendarApp;
|
|
4
5
|
calendars: CalendarType[];
|
|
@@ -7,6 +8,7 @@ export interface CalendarSidebarRenderProps {
|
|
|
7
8
|
isCollapsed: boolean;
|
|
8
9
|
setCollapsed: (collapsed: boolean) => void;
|
|
9
10
|
renderCalendarContextMenu?: (calendar: CalendarType, onClose: () => void) => TNode;
|
|
11
|
+
renderSidebarHeader?: (args: SidebarHeaderSlotArgs) => TNode;
|
|
10
12
|
createCalendarMode?: 'inline' | 'modal';
|
|
11
13
|
renderCreateCalendarDialog?: (props: CreateCalendarDialogProps) => TNode;
|
|
12
14
|
editingCalendarId?: string | null;
|
|
@@ -20,6 +22,7 @@ export interface SidebarPluginConfig {
|
|
|
20
22
|
createCalendarMode?: 'inline' | 'modal';
|
|
21
23
|
render?: (props: CalendarSidebarRenderProps) => TNode;
|
|
22
24
|
renderCalendarContextMenu?: (calendar: CalendarType, onClose: () => void) => TNode;
|
|
25
|
+
renderSidebarHeader?: (args: SidebarHeaderSlotArgs) => TNode;
|
|
23
26
|
renderCreateCalendarDialog?: (props: CreateCalendarDialogProps) => TNode;
|
|
24
27
|
[key: string]: unknown;
|
|
25
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dayflow/plugin-sidebar",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Sidebar plugin for DayFlow calendar",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calendar",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"rollup-plugin-dts": "^6.3.0",
|
|
38
38
|
"temporal-polyfill": "^0.3.0",
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
|
-
"@dayflow/core": "3.
|
|
40
|
+
"@dayflow/core": "3.3.1"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@dayflow/core": "3.
|
|
43
|
+
"@dayflow/core": "3.3.1"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && rollup -c",
|