@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/dist/components/AddToCalendar.vue.d.ts +16 -0
- package/dist/components/AddToCalendar.vue.d.ts.map +1 -0
- package/dist/components/calendar/Index.vue.d.ts +1 -1
- package/dist/components/calendar/views/WeekView.vue.d.ts +1 -1
- package/dist/components/calendar/views/WeekView.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useAddToCalendar.d.ts +19 -0
- package/dist/composables/useAddToCalendar.d.ts.map +1 -0
- package/dist/index.cjs +22 -21
- package/dist/index.mjs +21 -20
- package/package.json +1 -1
- package/src/components/AddToCalendar.vue +66 -0
- package/src/components/index.ts +1 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/useAddToCalendar.ts +237 -0
package/package.json
CHANGED
|
@@ -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>
|
package/src/components/index.ts
CHANGED
|
@@ -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'
|
package/src/composables/index.ts
CHANGED
|
@@ -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
|
+
}
|