@bagelink/vue 1.5.9 → 1.5.13

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.5.9",
4
+ "version": "1.5.13",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Neveh Allon",
@@ -0,0 +1,66 @@
1
+ <script setup lang="ts">
2
+ import type { CalendarEvent, CalendarProvider } from '../composables/useAddToCalendar'
3
+ import { Dropdown, Icon, ListItem, useDevice } from '@bagelink/vue'
4
+ import { computed } from 'vue'
5
+ import { useAddToCalendar } from '../composables/useAddToCalendar'
6
+
7
+ interface Props {
8
+ title: string
9
+ description?: string
10
+ location?: string
11
+ startDate: Date | string
12
+ endDate?: Date | string
13
+ value?: string
14
+ providers?: CalendarProvider[]
15
+ outline?: boolean
16
+ }
17
+
18
+ const props = withDefaults(defineProps<Props>(), {
19
+ value: 'Add to Calendar',
20
+ })
21
+
22
+ const event: CalendarEvent = {
23
+ title: props.title,
24
+ description: props.description,
25
+ location: props.location,
26
+ startDate: props.startDate,
27
+ endDate: props.endDate,
28
+ }
29
+
30
+ const { addToCalendar } = useAddToCalendar(event)
31
+
32
+ const calendarProviders = [
33
+ { id: 'google' as CalendarProvider, name: 'Google Calendar', icon: 'google', color: 'red' },
34
+ { id: 'apple' as CalendarProvider, name: 'Apple Calendar', icon: 'apple', color: 'black' },
35
+ { id: 'outlook' as CalendarProvider, name: 'Outlook', icon: 'microsoft', color: 'blue' },
36
+ { id: 'outlookcom' as CalendarProvider, name: 'Outlook.com', icon: 'microsoft', color: 'blue' },
37
+ { id: 'yahoo' as CalendarProvider, name: 'Yahoo Calendar', icon: 'yahoo', color: 'purple' },
38
+ { id: 'ics' as CalendarProvider, name: 'Other (ICS file)', icon: 'calendar', color: 'gray' },
39
+ ]
40
+
41
+ const includedProviders = computed(() => {
42
+ if (!props.providers || !props.providers.length) return calendarProviders
43
+ return calendarProviders.filter(provider => props.providers?.includes(provider.id))
44
+ })
45
+
46
+ function handleProviderClick(provider: CalendarProvider, callback: () => void) {
47
+ addToCalendar(provider)
48
+ callback()
49
+ }
50
+
51
+ const { isMobile } = useDevice()
52
+ </script>
53
+
54
+ <template>
55
+ <Dropdown :outline="outline" icon="calendar" :disablePlacement="isMobile" :value="value" color="primary">
56
+ <template #default="{ hide }">
57
+ <ListItem
58
+ v-for="provider in includedProviders" :key="provider.id" type="button"
59
+ @click="handleProviderClick(provider.id, hide)"
60
+ >
61
+ <Icon :icon="provider.icon" :color="provider.color" :size="1.2" class="me-05" />
62
+ {{ provider.name }}
63
+ </ListItem>
64
+ </template>
65
+ </Dropdown>
66
+ </template>
@@ -1,6 +1,7 @@
1
1
  export { default as Accordion } from './Accordion.vue'
2
2
  export { default as AccordionItem } from './AccordionItem.vue'
3
3
  export { default as AddressSearch } from './AddressSearch.vue'
4
+ export { default as AddToCalendar } from './AddToCalendar.vue'
4
5
  export { default as Alert } from './Alert.vue'
5
6
  export * from './analytics'
6
7
  export { default as Avatar } from './Avatar.vue'
@@ -4,6 +4,7 @@ import type { MaybeRefOrGetter, Ref, UnwrapRef } from 'vue'
4
4
  import { getFallbackSchema } from '@bagelink/vue'
5
5
  import { ref, toValue, watch } from 'vue'
6
6
 
7
+ export { useAddToCalendar } from './useAddToCalendar'
7
8
  export { getBagelInstance, setBagelInstance, useBagel } from './useBagel'
8
9
  export { useDevice } from './useDevice'
9
10
  export { useExcel } from './useExcel'
@@ -0,0 +1,237 @@
1
+ /* eslint-disable ts/strict-boolean-expressions */
2
+ import { computed } from 'vue'
3
+
4
+ export interface CalendarEvent {
5
+ title: string
6
+ description?: string
7
+ location?: string
8
+ startDate: Date | string
9
+ endDate?: Date | string
10
+ }
11
+
12
+ export type CalendarProvider = 'google' | 'apple' | 'outlook' | 'outlookcom' | 'yahoo' | 'ics'
13
+
14
+ export function useAddToCalendar(event: CalendarEvent) {
15
+ // Parse dates if they're strings
16
+ const parseDate = (date: Date | string): Date => {
17
+ return typeof date === 'string' ? new Date(date) : date
18
+ }
19
+
20
+ const eventStartDate = parseDate(event.startDate)
21
+
22
+ // Calculate end date if not provided (45 minutes after start)
23
+ const calculateEndDate = (): Date => {
24
+ if (event.endDate !== undefined) {
25
+ return parseDate(event.endDate)
26
+ }
27
+ const endDate = new Date(eventStartDate)
28
+ endDate.setMinutes(endDate.getMinutes() + 45)
29
+ return endDate
30
+ }
31
+
32
+ const eventEndDate = calculateEndDate()
33
+
34
+ // Check if location is a URL
35
+ const isUrl = (text?: string): boolean => {
36
+ if (!text || text.length === 0) {
37
+ return false
38
+ }
39
+ return /^https?:\/\/.+/.test(text.trim())
40
+ }
41
+
42
+ const locationIsUrl = isUrl(event.location)
43
+
44
+ // Format date to YYYYMMDDTHHMMSS format for calendar URLs
45
+ const formatDate = (date: Date): string => {
46
+ const year = date.getFullYear()
47
+ const month = String(date.getMonth() + 1).padStart(2, '0')
48
+ const day = String(date.getDate()).padStart(2, '0')
49
+ const hours = String(date.getHours()).padStart(2, '0')
50
+ const minutes = String(date.getMinutes()).padStart(2, '0')
51
+ const seconds = String(date.getSeconds()).padStart(2, '0')
52
+ return `${year}${month}${day}T${hours}${minutes}${seconds}`
53
+ }
54
+
55
+ // Format date to ISO format for Outlook
56
+ const formatDateISO = (date: Date): string => {
57
+ return `${date.toISOString().replace(/[-:]/g, '').split('.')[0]}Z`
58
+ }
59
+
60
+ // Generate ICS file content
61
+ const generateICS = (): string => {
62
+ const startDate = formatDate(eventStartDate)
63
+ const endDate = formatDate(eventEndDate)
64
+ const timestamp = formatDate(new Date())
65
+
66
+ const icsContent = [
67
+ 'BEGIN:VCALENDAR',
68
+ 'VERSION:2.0',
69
+ 'PRODID:-//Add to Calendar//EN',
70
+ 'CALSCALE:GREGORIAN',
71
+ 'METHOD:PUBLISH',
72
+ 'BEGIN:VEVENT',
73
+ `UID:${timestamp}@addtocalendar`,
74
+ `DTSTAMP:${timestamp}`,
75
+ `DTSTART:${startDate}`,
76
+ `DTEND:${endDate}`,
77
+ `SUMMARY:${event.title}`,
78
+ ]
79
+
80
+ if (event.description && event.description.length > 0) {
81
+ // ICS descriptions need special handling of newlines and character limits
82
+ const description = event.description.replace(/\n/g, '\\n')
83
+ icsContent.push(`DESCRIPTION:${description}`)
84
+ }
85
+
86
+ if (event.location && event.location.length > 0) {
87
+ icsContent.push(`LOCATION:${event.location}`)
88
+ // If location is a URL, also add it as the URL field
89
+ if (locationIsUrl) {
90
+ icsContent.push(`URL:${event.location}`)
91
+ }
92
+ }
93
+
94
+ icsContent.push('STATUS:CONFIRMED', 'SEQUENCE:0', 'END:VEVENT', 'END:VCALENDAR')
95
+
96
+ return icsContent.join('\r\n')
97
+ }
98
+
99
+ // Generate Google Calendar URL
100
+ const googleUrl = computed(() => {
101
+ const params = new URLSearchParams({
102
+ action: 'TEMPLATE',
103
+ text: event.title,
104
+ dates: `${formatDateISO(eventStartDate)}/${formatDateISO(eventEndDate)}`,
105
+ })
106
+
107
+ if (event.description && event.description.length > 0) {
108
+ params.append('details', event.description)
109
+ }
110
+
111
+ if (event.location && event.location.length > 0) {
112
+ params.append('location', event.location)
113
+ }
114
+
115
+ return `https://calendar.google.com/calendar/render?${params.toString()}`
116
+ })
117
+
118
+ // Generate Outlook Web URL
119
+ const outlookWebUrl = computed(() => {
120
+ const params = new URLSearchParams({
121
+ path: '/calendar/action/compose',
122
+ rru: 'addevent',
123
+ subject: event.title,
124
+ startdt: eventStartDate.toISOString(),
125
+ enddt: eventEndDate.toISOString(),
126
+ })
127
+
128
+ if (event.description && event.description.length > 0) {
129
+ params.append('body', event.description)
130
+ }
131
+
132
+ if (event.location && event.location.length > 0) {
133
+ params.append('location', event.location)
134
+ }
135
+
136
+ return `https://outlook.live.com/calendar/0/deeplink/compose?${params.toString()}`
137
+ })
138
+
139
+ // Generate Outlook Office 365 URL
140
+ const outlookUrl = computed(() => {
141
+ const params = new URLSearchParams({
142
+ path: '/calendar/action/compose',
143
+ rru: 'addevent',
144
+ subject: event.title,
145
+ startdt: eventStartDate.toISOString(),
146
+ enddt: eventEndDate.toISOString(),
147
+ })
148
+
149
+ if (event.description && event.description.length > 0) {
150
+ params.append('body', event.description)
151
+ }
152
+
153
+ if (event.location && event.location.length > 0) {
154
+ params.append('location', event.location)
155
+ }
156
+
157
+ return `https://outlook.office.com/calendar/0/deeplink/compose?${params.toString()}`
158
+ })
159
+
160
+ // Generate Yahoo Calendar URL
161
+ const yahooUrl = computed(() => {
162
+ const duration = Math.floor((eventEndDate.getTime() - eventStartDate.getTime()) / 1000 / 60) // Duration in minutes
163
+
164
+ const params = new URLSearchParams({
165
+ v: '60',
166
+ title: event.title,
167
+ st: formatDateISO(eventStartDate),
168
+ dur: duration.toString(),
169
+ })
170
+
171
+ if (event.description && event.description.length > 0) {
172
+ params.append('desc', event.description)
173
+ }
174
+
175
+ if (event.location && event.location.length > 0) {
176
+ params.append('in_loc', event.location)
177
+ }
178
+
179
+ return `https://calendar.yahoo.com/?${params.toString()}`
180
+ })
181
+
182
+ // Get calendar URL or file based on provider
183
+ const getCalendarUrl = (provider: CalendarProvider): string => {
184
+ switch (provider) {
185
+ case 'google':
186
+ return googleUrl.value
187
+ case 'apple':
188
+ case 'ics':
189
+ // For Apple and ICS, we'll trigger a download
190
+ return ''
191
+ case 'outlook':
192
+ return outlookUrl.value
193
+ case 'outlookcom':
194
+ return outlookWebUrl.value
195
+ case 'yahoo':
196
+ return yahooUrl.value
197
+ default:
198
+ return ''
199
+ }
200
+ }
201
+
202
+ // Download ICS file
203
+ const downloadICS = (filename = 'event.ics') => {
204
+ const icsContent = generateICS()
205
+ const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' })
206
+ const link = document.createElement('a')
207
+ link.href = URL.createObjectURL(blob)
208
+ link.download = filename
209
+ document.body.appendChild(link)
210
+ link.click()
211
+ document.body.removeChild(link)
212
+ URL.revokeObjectURL(link.href)
213
+ }
214
+
215
+ // Add to calendar action
216
+ const addToCalendar = (provider: CalendarProvider) => {
217
+ if (provider === 'apple' || provider === 'ics') {
218
+ downloadICS()
219
+ } else {
220
+ const url = getCalendarUrl(provider)
221
+ if (url) {
222
+ window.open(url, '_blank', 'noopener,noreferrer')
223
+ }
224
+ }
225
+ }
226
+
227
+ return {
228
+ googleUrl,
229
+ outlookUrl,
230
+ outlookWebUrl,
231
+ yahooUrl,
232
+ generateICS,
233
+ downloadICS,
234
+ getCalendarUrl,
235
+ addToCalendar,
236
+ }
237
+ }