@dayflow/plugin-sidebar 1.2.0 → 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.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useLocale, createPortal, cancelButton, ChevronsUpDown, Check, ChevronRight, sidebarHeader, sidebarHeaderToggle, PanelRightClose, PanelRightOpen, sidebarHeaderTitle, getCalendarColorsForHex, generateUniKey, downloadICS, ContentSlot, MiniCalendar, ContextMenu, ContextMenuLabel, ContextMenuItem, ContextMenuSeparator, ContextMenuColorPicker, DefaultColorPicker, BlossomColorPicker, importICSFile, sidebarContainer, normalizeCssWidth, registerSidebarImplementation, CreateCalendarDialog } from '@dayflow/core';
|
|
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';
|
|
@@ -179,14 +179,14 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
|
|
|
179
179
|
editingId === calendar.id;
|
|
180
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-[var(--df-color-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: '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-
|
|
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,6 +243,7 @@ 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
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') ||
|
|
@@ -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();
|
|
@@ -302,6 +303,36 @@ 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
|
|
|
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
|
+
|
|
305
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();
|
|
@@ -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];
|
|
@@ -563,11 +641,12 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
563
641
|
createPortal(u(ContextMenu, { x: sidebarContextMenu.x, y: sidebarContextMenu.y, onClose: handleCloseSidebarContextMenu, className: 'w-max p-2', children: [u(ContextMenuItem, { onClick: () => {
|
|
564
642
|
onCreateCalendar === null || onCreateCalendar === void 0 ? void 0 : onCreateCalendar();
|
|
565
643
|
handleCloseSidebarContextMenu();
|
|
566
|
-
}, 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: () => {
|
|
567
645
|
app.triggerRender();
|
|
568
646
|
handleCloseSidebarContextMenu();
|
|
569
647
|
}, children: t('refreshAll') || 'Refresh All' })] }), document.body), u("input", { ref: fileInputRef, type: 'file', accept: '.ics', style: { display: 'none' }, onChange: handleFileChange }), importState &&
|
|
570
|
-
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 &&
|
|
571
650
|
createPortal(u(MergeCalendarDialog, { sourceName: sourceCalendarName, targetName: targetCalendarName, onConfirm: handleMergeConfirm, onCancel: () => setMergeState(null) }), document.body), deleteState &&
|
|
572
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 &&
|
|
573
652
|
createPortal(u("div", { className: 'fixed inset-0 z-50', onMouseDown: () => {
|
package/dist/index.js
CHANGED
|
@@ -181,14 +181,14 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
|
|
|
181
181
|
editingId === calendar.id;
|
|
182
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-[var(--df-color-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: '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-
|
|
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,6 +245,7 @@ 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
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') ||
|
|
@@ -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();
|
|
@@ -304,6 +305,36 @@ 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
|
|
|
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
|
+
|
|
307
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();
|
|
@@ -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];
|
|
@@ -565,11 +643,12 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
|
|
|
565
643
|
core.createPortal(u(core.ContextMenu, { x: sidebarContextMenu.x, y: sidebarContextMenu.y, onClose: handleCloseSidebarContextMenu, className: 'w-max p-2', children: [u(core.ContextMenuItem, { onClick: () => {
|
|
566
644
|
onCreateCalendar === null || onCreateCalendar === void 0 ? void 0 : onCreateCalendar();
|
|
567
645
|
handleCloseSidebarContextMenu();
|
|
568
|
-
}, 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: () => {
|
|
569
647
|
app.triggerRender();
|
|
570
648
|
handleCloseSidebarContextMenu();
|
|
571
649
|
}, children: t('refreshAll') || 'Refresh All' })] }), document.body), u("input", { ref: fileInputRef, type: 'file', accept: '.ics', style: { display: 'none' }, onChange: handleFileChange }), importState &&
|
|
572
|
-
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 &&
|
|
573
652
|
core.createPortal(u(MergeCalendarDialog, { sourceName: sourceCalendarName, targetName: targetCalendarName, onConfirm: handleMergeConfirm, onCancel: () => setMergeState(null) }), document.body), deleteState &&
|
|
574
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 &&
|
|
575
654
|
core.createPortal(u("div", { className: 'fixed inset-0 z-50', onMouseDown: () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dayflow/plugin-sidebar",
|
|
3
|
-
"version": "1.2.
|
|
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.3.
|
|
40
|
+
"@dayflow/core": "3.3.1"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@dayflow/core": "3.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",
|