mirror of https://github.com/nextcloud/server
Replace CalDAV availability component with component lib
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
parent
5ee0fb3acb
commit
daf1b5f6a3
|
@ -19,11 +19,13 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { getClient } from '../dav/client'
|
||||
import ICAL from 'ical.js'
|
||||
import logger from './logger'
|
||||
import { parseXML } from 'webdav/dist/node/tools/dav'
|
||||
import { getZoneString } from 'icalzone'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import {
|
||||
slotsToVavailability,
|
||||
vavailabilityToSlots,
|
||||
} from '@nextcloud/calendar-availability-vue'
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -67,44 +69,7 @@ export async function findScheduleInboxAvailability() {
|
|||
return undefined
|
||||
}
|
||||
|
||||
const parsedIcal = ICAL.parse(availability)
|
||||
|
||||
const vcalendarComp = new ICAL.Component(parsedIcal)
|
||||
const vavailabilityComp = vcalendarComp.getFirstSubcomponent('vavailability')
|
||||
|
||||
let timezoneId
|
||||
const timezoneComp = vcalendarComp.getFirstSubcomponent('vtimezone')
|
||||
if (timezoneComp) {
|
||||
timezoneId = timezoneComp.getFirstProperty('tzid').getFirstValue()
|
||||
}
|
||||
|
||||
const availableComps = vavailabilityComp.getAllSubcomponents('available')
|
||||
// Combine all AVAILABLE blocks into a week of slots
|
||||
const slots = getEmptySlots()
|
||||
availableComps.forEach((availableComp) => {
|
||||
const start = availableComp.getFirstProperty('dtstart').getFirstValue().toJSDate()
|
||||
const end = availableComp.getFirstProperty('dtend').getFirstValue().toJSDate()
|
||||
const rrule = availableComp.getFirstProperty('rrule')
|
||||
|
||||
if (rrule.getFirstValue().freq !== 'WEEKLY') {
|
||||
logger.warn('rrule not supported', {
|
||||
rrule: rrule.toICALString(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rrule.getFirstValue().getComponent('BYDAY').forEach(day => {
|
||||
slots[day].push({
|
||||
start,
|
||||
end,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
slots,
|
||||
timezoneId,
|
||||
}
|
||||
return vavailabilityToSlots(availability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,74 +82,10 @@ export async function saveScheduleInboxAvailability(slots, timezoneId) {
|
|||
day: dayId,
|
||||
})))]
|
||||
|
||||
const vcalendarComp = new ICAL.Component('vcalendar')
|
||||
vcalendarComp.addPropertyWithValue('prodid', 'Nextcloud DAV app')
|
||||
const vavailability = slotsToVavailability(all, timezoneId)
|
||||
|
||||
// Store time zone info
|
||||
// If possible we use the info from a time zone database
|
||||
const predefinedTimezoneIcal = getZoneString(timezoneId)
|
||||
if (predefinedTimezoneIcal) {
|
||||
const timezoneComp = new ICAL.Component(ICAL.parse(predefinedTimezoneIcal))
|
||||
vcalendarComp.addSubcomponent(timezoneComp)
|
||||
} else {
|
||||
// Fall back to a simple markup
|
||||
const timezoneComp = new ICAL.Component('vtimezone')
|
||||
timezoneComp.addPropertyWithValue('tzid', timezoneId)
|
||||
vcalendarComp.addSubcomponent(timezoneComp)
|
||||
}
|
||||
|
||||
// Store availability info
|
||||
const vavailabilityComp = new ICAL.Component('vavailability')
|
||||
|
||||
// Deduplicate by start and end time
|
||||
const deduplicated = all.reduce((acc, slot) => {
|
||||
const key = [
|
||||
slot.start.getHours(),
|
||||
slot.start.getMinutes(),
|
||||
slot.end.getHours(),
|
||||
slot.end.getMinutes(),
|
||||
].join('-')
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[key]: [...(acc[key] ?? []), slot],
|
||||
}
|
||||
}, {})
|
||||
|
||||
// Create an AVAILABILITY component for every recurring slot
|
||||
Object.keys(deduplicated).map(key => {
|
||||
const slots = deduplicated[key]
|
||||
const start = slots[0].start
|
||||
const end = slots[0].end
|
||||
// Combine days but make them also unique
|
||||
const days = slots.map(slot => slot.day).filter((day, index, self) => self.indexOf(day) === index)
|
||||
|
||||
const availableComp = new ICAL.Component('available')
|
||||
|
||||
// Define DTSTART and DTEND
|
||||
const startTimeProp = availableComp.addPropertyWithValue('dtstart', ICAL.Time.fromJSDate(start, false))
|
||||
startTimeProp.setParameter('tzid', timezoneId)
|
||||
const endTimeProp = availableComp.addPropertyWithValue('dtend', ICAL.Time.fromJSDate(end, false))
|
||||
endTimeProp.setParameter('tzid', timezoneId)
|
||||
|
||||
// Add mandatory UID
|
||||
availableComp.addPropertyWithValue('uid', uuidv4())
|
||||
|
||||
// TODO: add optional summary
|
||||
|
||||
// Define RRULE
|
||||
availableComp.addPropertyWithValue('rrule', {
|
||||
freq: 'WEEKLY',
|
||||
byday: days,
|
||||
})
|
||||
|
||||
return availableComp
|
||||
}).map(vavailabilityComp.addSubcomponent.bind(vavailabilityComp))
|
||||
|
||||
vcalendarComp.addSubcomponent(vavailabilityComp)
|
||||
logger.debug('New availability ical created', {
|
||||
asObject: vcalendarComp,
|
||||
asString: vcalendarComp.toString(),
|
||||
vavailability,
|
||||
})
|
||||
|
||||
const client = getClient('calendars')
|
||||
|
@ -194,7 +95,7 @@ export async function saveScheduleInboxAvailability(slots, timezoneId) {
|
|||
<x0:propertyupdate xmlns:x0="DAV:">
|
||||
<x0:set>
|
||||
<x0:prop>
|
||||
<x1:calendar-availability xmlns:x1="urn:ietf:params:xml:ns:caldav">${vcalendarComp.toString()}</x1:calendar-availability>
|
||||
<x1:calendar-availability xmlns:x1="urn:ietf:params:xml:ns:caldav">${vavailability}</x1:calendar-availability>
|
||||
</x0:prop>
|
||||
</x0:set>
|
||||
</x0:propertyupdate>`,
|
||||
|
|
|
@ -12,45 +12,19 @@
|
|||
<TimezonePicker v-model="timezone" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid-table">
|
||||
<template v-for="day in daysOfTheWeek">
|
||||
<div :key="`day-label-${day.id}`" class="label-weekday">
|
||||
{{ day.displayName }}
|
||||
</div>
|
||||
<div :key="`day-slots-${day.id}`" class="availability-slots">
|
||||
<div class="availability-slot-group">
|
||||
<template v-for="(slot, idx) in day.slots">
|
||||
<div :key="`slot-${day.id}-${idx}`" class="availability-slot">
|
||||
<DatetimePicker v-model="slot.start"
|
||||
type="time"
|
||||
class="start-date"
|
||||
format="H:mm" />
|
||||
<span class="to-text">
|
||||
{{ $t('dav', 'to') }}
|
||||
</span>
|
||||
<DatetimePicker v-model="slot.end"
|
||||
type="time"
|
||||
class="end-date"
|
||||
format="H:mm" />
|
||||
<button :key="`slot-${day.id}-${idx}-btn`"
|
||||
class="icon-delete delete-slot button"
|
||||
:title="$t('dav', 'Delete slot')"
|
||||
@click="deleteSlot(day, idx)" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<span v-if="day.slots.length === 0"
|
||||
class="empty-content">
|
||||
{{ $t('dav', 'No working hours set') }}
|
||||
</span>
|
||||
</div>
|
||||
<button :key="`add-slot-${day.id}`"
|
||||
:disabled="loading"
|
||||
class="icon-add add-another button"
|
||||
:title="$t('dav', 'Add slot')"
|
||||
@click="addSlot(day)" />
|
||||
</template>
|
||||
</div>
|
||||
<CalendarAvailability :slots.sync="slots"
|
||||
:loading="loading"
|
||||
:l10n-to="$t('dav', 'to')"
|
||||
:l10n-delete-slot="$t('dav', 'Delete slot')"
|
||||
:l10n-empty-day="$t('dav', 'No working hours set')"
|
||||
:l10n-add-slot="$t('dav', 'Add slot')"
|
||||
:l10n-monday="$t('dav', 'Monday')"
|
||||
:l10n-tuesday="$t('dav', 'Tuesday')"
|
||||
:l10n-wednesday="$t('dav', 'Wednesday')"
|
||||
:l10n-thursday="$t('dav', 'Thursday')"
|
||||
:l10n-friday="$t('dav', 'Friday')"
|
||||
:l10n-saturday="$t('dav', 'Saturday')"
|
||||
:l10n-sunday="$t('dav', 'Sunday')" />
|
||||
<button :disabled="loading || saving"
|
||||
class="button primary"
|
||||
@click="save">
|
||||
|
@ -60,19 +34,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import DatetimePicker from '@nextcloud/vue/dist/Components/DatetimePicker'
|
||||
import { CalendarAvailability } from '@nextcloud/calendar-availability-vue'
|
||||
import {
|
||||
findScheduleInboxAvailability,
|
||||
getEmptySlots,
|
||||
saveScheduleInboxAvailability,
|
||||
} from '../service/CalendarService'
|
||||
import { getFirstDay } from '@nextcloud/l10n'
|
||||
import jstz from 'jstimezonedetect'
|
||||
import TimezonePicker from '@nextcloud/vue/dist/Components/TimezonePicker'
|
||||
export default {
|
||||
name: 'Availability',
|
||||
components: {
|
||||
DatetimePicker,
|
||||
CalendarAvailability,
|
||||
TimezonePicker,
|
||||
},
|
||||
data() {
|
||||
|
@ -80,63 +53,27 @@ export default {
|
|||
const defaultTimezone = jstz.determine()
|
||||
const defaultTimezoneId = defaultTimezone ? defaultTimezone.name() : 'UTC'
|
||||
|
||||
const moToSa = [
|
||||
{
|
||||
id: 'MO',
|
||||
displayName: this.$t('dav', 'Monday'),
|
||||
slots: [],
|
||||
},
|
||||
{
|
||||
id: 'TU',
|
||||
displayName: this.$t('dav', 'Tuesday'),
|
||||
slots: [],
|
||||
},
|
||||
{
|
||||
id: 'WE',
|
||||
displayName: this.$t('dav', 'Wednesday'),
|
||||
slots: [],
|
||||
},
|
||||
{
|
||||
id: 'TH',
|
||||
displayName: this.$t('dav', 'Thursday'),
|
||||
slots: [],
|
||||
},
|
||||
{
|
||||
id: 'FR',
|
||||
displayName: this.$t('dav', 'Friday'),
|
||||
slots: [],
|
||||
},
|
||||
{
|
||||
id: 'SA',
|
||||
displayName: this.$t('dav', 'Saturday'),
|
||||
slots: [],
|
||||
},
|
||||
]
|
||||
const sunday = {
|
||||
id: 'SU',
|
||||
displayName: this.$t('dav', 'Sunday'),
|
||||
slots: [],
|
||||
}
|
||||
const daysOfTheWeek = getFirstDay() === 1 ? [...moToSa, sunday] : [sunday, ...moToSa]
|
||||
return {
|
||||
loading: true,
|
||||
saving: false,
|
||||
timezone: defaultTimezoneId,
|
||||
daysOfTheWeek,
|
||||
slots: getEmptySlots(),
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
const { slots, timezoneId } = await findScheduleInboxAvailability()
|
||||
if (slots) {
|
||||
this.daysOfTheWeek.forEach(day => {
|
||||
day.slots.push(...slots[day.id])
|
||||
})
|
||||
const slotData = await findScheduleInboxAvailability()
|
||||
if (!slotData) {
|
||||
console.info('no availability is set')
|
||||
this.slots = getEmptySlots()
|
||||
} else {
|
||||
const { slots, timezoneId } = slotData
|
||||
this.slots = slots
|
||||
if (timezoneId) {
|
||||
this.timezone = timezoneId
|
||||
}
|
||||
console.info('availability loaded', this.slots, this.timezoneId)
|
||||
}
|
||||
if (timezoneId) {
|
||||
this.timezone = timezoneId
|
||||
}
|
||||
console.info('availability loaded', this.daysOfTheWeek)
|
||||
} catch (e) {
|
||||
console.error('could not load existing availability', e)
|
||||
|
||||
|
@ -146,32 +83,11 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
addSlot(day) {
|
||||
const start = new Date()
|
||||
start.setHours(9)
|
||||
start.setMinutes(0)
|
||||
start.setSeconds(0)
|
||||
const end = new Date()
|
||||
end.setHours(17)
|
||||
end.setMinutes(0)
|
||||
end.setSeconds(0)
|
||||
day.slots.push({
|
||||
start,
|
||||
end,
|
||||
})
|
||||
},
|
||||
deleteSlot(day, idx) {
|
||||
day.slots.splice(idx, 1)
|
||||
},
|
||||
async save() {
|
||||
try {
|
||||
this.saving = true
|
||||
|
||||
const slots = getEmptySlots()
|
||||
this.daysOfTheWeek.forEach(day => {
|
||||
day.slots.forEach(slot => slots[day.id].push(slot))
|
||||
})
|
||||
await saveScheduleInboxAvailability(slots, this.timezone)
|
||||
await saveScheduleInboxAvailability(this.slots, this.timezone)
|
||||
|
||||
// TODO: show a nice toast
|
||||
} catch (e) {
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -12,6 +12,7 @@
|
|||
"@chenfengyuan/vue-qrcode": "^1.0.2",
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.9.0",
|
||||
"@nextcloud/calendar-availability-vue": "^0.3.0",
|
||||
"@nextcloud/capabilities": "^1.0.4",
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
"@nextcloud/event-bus": "^2.1.1",
|
||||
|
@ -42,7 +43,6 @@
|
|||
"escape-html": "^1.0.3",
|
||||
"handlebars": "^4.7.7",
|
||||
"ical.js": "^1.4.0",
|
||||
"icalzone": "^0.0.1",
|
||||
"jquery": "~3.6",
|
||||
"jquery-migrate": "~3.3",
|
||||
"jquery-ui": "^1.13.1",
|
||||
|
@ -64,7 +64,6 @@
|
|||
"strengthify": "github:nextcloud/strengthify#0.5.9",
|
||||
"underscore": "1.12.0",
|
||||
"url-search-params-polyfill": "^8.1.1",
|
||||
"uuid": "^8.3.2",
|
||||
"v-click-outside": "^3.1.2",
|
||||
"v-tooltip": "^2.1.3",
|
||||
"vue": "^2.6.14",
|
||||
|
@ -2725,6 +2724,21 @@
|
|||
"integrity": "sha512-kC42RQW5rZjZZsRaEjVlIQpp6aW/yxm+zZdETnrRQnUzcPwBgF4wO4makfGT63Ckd+LkgUW+geesPiPRqxFVew==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@nextcloud/calendar-availability-vue": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/calendar-availability-vue/-/calendar-availability-vue-0.3.0.tgz",
|
||||
"integrity": "sha512-O6v3Eq5ofT6jfORqN7BmDoP5jzhLWWHgCDNx+esH+iwhxdVoWti9M6YjMf1ZRvetReanxepLOblEeT9rBBi7bw==",
|
||||
"dependencies": {
|
||||
"ical.js": "^1.4.0",
|
||||
"icalzone": "^0.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nextcloud/l10n": "^1.4",
|
||||
"@nextcloud/vue": "^4.2||^5.0",
|
||||
"vue": "^2.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@nextcloud/calendar-js": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/calendar-js/-/calendar-js-3.0.0.tgz",
|
||||
|
@ -21651,6 +21665,16 @@
|
|||
"integrity": "sha512-kC42RQW5rZjZZsRaEjVlIQpp6aW/yxm+zZdETnrRQnUzcPwBgF4wO4makfGT63Ckd+LkgUW+geesPiPRqxFVew==",
|
||||
"dev": true
|
||||
},
|
||||
"@nextcloud/calendar-availability-vue": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/calendar-availability-vue/-/calendar-availability-vue-0.3.0.tgz",
|
||||
"integrity": "sha512-O6v3Eq5ofT6jfORqN7BmDoP5jzhLWWHgCDNx+esH+iwhxdVoWti9M6YjMf1ZRvetReanxepLOblEeT9rBBi7bw==",
|
||||
"requires": {
|
||||
"ical.js": "^1.4.0",
|
||||
"icalzone": "^0.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"@nextcloud/calendar-js": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/calendar-js/-/calendar-js-3.0.0.tgz",
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"@chenfengyuan/vue-qrcode": "^1.0.2",
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.9.0",
|
||||
"@nextcloud/calendar-availability-vue": "^0.3.0",
|
||||
"@nextcloud/capabilities": "^1.0.4",
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
"@nextcloud/event-bus": "^2.1.1",
|
||||
|
@ -59,7 +60,6 @@
|
|||
"escape-html": "^1.0.3",
|
||||
"handlebars": "^4.7.7",
|
||||
"ical.js": "^1.4.0",
|
||||
"icalzone": "^0.0.1",
|
||||
"jquery": "~3.6",
|
||||
"jquery-migrate": "~3.3",
|
||||
"jquery-ui": "^1.13.1",
|
||||
|
@ -81,7 +81,6 @@
|
|||
"strengthify": "github:nextcloud/strengthify#0.5.9",
|
||||
"underscore": "1.12.0",
|
||||
"url-search-params-polyfill": "^8.1.1",
|
||||
"uuid": "^8.3.2",
|
||||
"v-click-outside": "^3.1.2",
|
||||
"v-tooltip": "^2.1.3",
|
||||
"vue": "^2.6.14",
|
||||
|
|
|
@ -142,6 +142,11 @@ module.exports = {
|
|||
new webpack.ProvidePlugin({
|
||||
// Provide jQuery to jquery plugins as some are loaded before $ is exposed globally.
|
||||
jQuery: 'jquery',
|
||||
// Shim ICAL to prevent using the global object (window.ICAL).
|
||||
// The library ical.js heavily depends on instanceof checks which will
|
||||
// break if two separate versions of the library are used (e.g. bundled one
|
||||
// and global one).
|
||||
ICAL: 'ical.js',
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
|
|
Loading…
Reference in New Issue