411 lines
10 KiB
Vue
411 lines
10 KiB
Vue
<!--
|
|
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
|
|
-
|
|
- @author Georg Ehrke <oc.list@georgehrke.com>
|
|
-
|
|
- @license GNU AGPL version 3 or any later version
|
|
-
|
|
- This program is free software: you can redistribute it and/or modify
|
|
- it under the terms of the GNU Affero General Public License as
|
|
- published by the Free Software Foundation, either version 3 of the
|
|
- License, or (at your option) any later version.
|
|
-
|
|
- This program is distributed in the hope that it will be useful,
|
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
- GNU Affero General Public License for more details.
|
|
-
|
|
- You should have received a copy of the GNU Affero General Public License
|
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
-
|
|
-->
|
|
|
|
<template>
|
|
<DatetimePicker
|
|
:lang="lang"
|
|
:first-day-of-week="firstDay"
|
|
:format="'YYYY-MM-DD HH:mm'"
|
|
:formatter="formatter"
|
|
:value="date"
|
|
:type="type"
|
|
:clearable="false"
|
|
:minute-step="5"
|
|
:disabled-date="disabledDate"
|
|
:show-second="false"
|
|
:show-time-panel="showTimePanel"
|
|
:show-week-number="showWeekNumbers"
|
|
:use12h="showAmPm"
|
|
v-bind="$attrs"
|
|
v-on="$listeners"
|
|
@close="close"
|
|
@change="change"
|
|
@pick="pickDate">
|
|
<template #icon-calendar>
|
|
<button
|
|
class="datetime-picker-inline-icon icon"
|
|
:class="{'icon-timezone': !isAllDay, 'icon-new-calendar': isAllDay, 'datetime-picker-inline-icon--highlighted': highlightTimezone}"
|
|
@click.stop.prevent="toggleTimezonePopover"
|
|
@mousedown.stop.prevent="() => {}" />
|
|
<Popover
|
|
:open.sync="showTimezonePopover"
|
|
open-class="timezone-popover-wrapper">
|
|
<div class="timezone-popover-wrapper__title">
|
|
<strong>
|
|
{{ $t('calendar', 'Please select a timezone:') }}
|
|
</strong>
|
|
</div>
|
|
<TimezoneSelect
|
|
class="timezone-popover-wrapper__timezone-select"
|
|
:value="timezoneId"
|
|
@change="changeTimezone" />
|
|
</Popover>
|
|
</template>
|
|
<template
|
|
v-if="!isAllDay"
|
|
#footer>
|
|
<button
|
|
v-if="!showTimePanel"
|
|
class="mx-btn mx-btn-text"
|
|
@click="toggleTimePanel">
|
|
{{ $t('calendar', 'Pick a time') }}
|
|
</button>
|
|
<button
|
|
v-else
|
|
class="mx-btn mx-btn-text"
|
|
@click="toggleTimePanel">
|
|
{{ $t('calendar', 'Pick a date') }}
|
|
</button>
|
|
</template>
|
|
</DatetimePicker>
|
|
</template>
|
|
|
|
<script>
|
|
import DatetimePicker from '@nextcloud/vue/dist/Components/DatetimePicker'
|
|
import Popover from '@nextcloud/vue/dist/Components/Popover'
|
|
import {
|
|
getFirstDay,
|
|
} from '@nextcloud/l10n'
|
|
import moment from '@nextcloud/moment'
|
|
import { mapState } from 'vuex'
|
|
import {
|
|
showError,
|
|
} from '@nextcloud/dialogs'
|
|
|
|
import TimezoneSelect from './TimezoneSelect'
|
|
import { getLangConfigForVue2DatePicker } from '../../utils/localization.js'
|
|
|
|
export default {
|
|
name: 'DatePicker',
|
|
components: {
|
|
DatetimePicker,
|
|
Popover,
|
|
TimezoneSelect,
|
|
},
|
|
props: {
|
|
date: {
|
|
type: Date,
|
|
required: true,
|
|
},
|
|
hasTimezone: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
timezoneId: {
|
|
type: String,
|
|
default: 'floating',
|
|
},
|
|
prefix: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
isAllDay: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
userTimezoneId: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
min: {
|
|
type: Date,
|
|
default: null,
|
|
},
|
|
max: {
|
|
type: Date,
|
|
default: null,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
firstDay: getFirstDay() === 0 ? 7 : getFirstDay(),
|
|
showTimezonePopover: false,
|
|
formatter: {
|
|
stringify: this.stringify,
|
|
parse: this.parse,
|
|
},
|
|
showTimePanel: false,
|
|
}
|
|
},
|
|
computed: {
|
|
...mapState({
|
|
locale: (state) => state.settings.momentLocale,
|
|
showWeekNumbers: (state) => state.settings.showWeekNumbers,
|
|
}),
|
|
/**
|
|
* Returns the lang config for vue2-datepicker
|
|
*
|
|
* @returns {Object}
|
|
*/
|
|
lang() {
|
|
return getLangConfigForVue2DatePicker(this.locale)
|
|
},
|
|
/**
|
|
* Whether or not to highlight the timezone-icon.
|
|
* The icon is highlighted when the selected timezone
|
|
* does not equal the current user's timezone
|
|
*
|
|
* @returns {Boolean}
|
|
*/
|
|
highlightTimezone() {
|
|
if (this.isAllDay) {
|
|
return true
|
|
}
|
|
|
|
return this.timezoneId !== this.userTimezoneId
|
|
},
|
|
/**
|
|
* Type of the DatePicker.
|
|
* Ether date if allDay or datetime
|
|
*
|
|
* @returns {String}
|
|
*/
|
|
type() {
|
|
if (this.isAllDay) {
|
|
return 'date'
|
|
}
|
|
|
|
return 'datetime'
|
|
},
|
|
/**
|
|
* The earliest date a user is allowed to pick in the timezone
|
|
*
|
|
* @returns {Date}
|
|
*/
|
|
minimumDate() {
|
|
return this.min || new Date(this.$store.state.davRestrictions.minimumDate)
|
|
},
|
|
/**
|
|
* The latest date a user is allowed to pick in the timezone
|
|
*
|
|
* @returns {Date}
|
|
*/
|
|
maximumDate() {
|
|
return this.max || new Date(this.$store.state.davRestrictions.maximumDate)
|
|
},
|
|
/**
|
|
* Whether or not to offer am/pm in the timepicker
|
|
*
|
|
* @returns {Boolean}
|
|
*/
|
|
showAmPm() {
|
|
const localeData = moment().locale(this.locale).localeData()
|
|
const timeFormat = localeData.longDateFormat('LT').toLowerCase()
|
|
|
|
return timeFormat.indexOf('a') !== -1
|
|
},
|
|
},
|
|
methods: {
|
|
/**
|
|
* Emits a change event for the Date
|
|
*
|
|
* @param {Date} date The new Date object
|
|
*/
|
|
change(date) {
|
|
this.$emit('change', date)
|
|
},
|
|
/**
|
|
* Changes the view to time-picker,
|
|
* when user picked a date and date-time-picker is not all-day
|
|
*
|
|
* @param {Date} date The selected Date object
|
|
* @param {String} type The type of selected date (Date, Time, ...)
|
|
*/
|
|
pickDate(date, type) {
|
|
if (!this.isAllDay && type === 'date') {
|
|
this.showTimePanel = true
|
|
}
|
|
},
|
|
/**
|
|
* Emits a change event for the Timezone
|
|
*
|
|
* @param {String} timezoneId The new timezoneId
|
|
*/
|
|
changeTimezone(timezoneId) {
|
|
this.$emit('changeTimezone', timezoneId)
|
|
},
|
|
/**
|
|
* Toggles the visibility of the timezone popover
|
|
*/
|
|
toggleTimezonePopover() {
|
|
if (this.isAllDay) {
|
|
return
|
|
}
|
|
|
|
this.showTimezonePopover = !this.showTimezonePopover
|
|
},
|
|
/**
|
|
* Reset to date-panel on close of datepicker
|
|
*/
|
|
close() {
|
|
this.showTimePanel = false
|
|
this.$emit('close')
|
|
},
|
|
/**
|
|
* Toggles the time-picker
|
|
*/
|
|
toggleTimePanel() {
|
|
this.showTimePanel = !this.showTimePanel
|
|
},
|
|
/**
|
|
* Formats the date string
|
|
*
|
|
* @param {Date} date The date for format
|
|
* @returns {String}
|
|
*/
|
|
stringify(date) {
|
|
const formattedDate = moment(date).locale(this.locale).format('L')
|
|
const formattedTime = moment(date).locale(this.locale).format('LT')
|
|
|
|
if (this.isAllDay) {
|
|
switch (this.prefix) {
|
|
case 'from':
|
|
return this.$t('calendar', 'from {formattedDate}', { formattedDate })
|
|
|
|
case 'to':
|
|
return this.$t('calendar', 'to {formattedDate}', { formattedDate })
|
|
|
|
case 'on':
|
|
return this.$t('calendar', 'on {formattedDate}', { formattedDate })
|
|
|
|
default:
|
|
return formattedDate
|
|
}
|
|
} else {
|
|
switch (this.prefix) {
|
|
case 'from':
|
|
return this.$t('calendar', 'from {formattedDate} at {formattedTime}', { formattedDate, formattedTime })
|
|
|
|
case 'to':
|
|
return this.$t('calendar', 'to {formattedDate} at {formattedTime}', { formattedDate, formattedTime })
|
|
|
|
case 'on':
|
|
return this.$t('calendar', 'on {formattedDate} at {formattedTime}', { formattedDate, formattedTime })
|
|
|
|
default:
|
|
return this.$t('calendar', '{formattedDate} at {formattedTime}', { formattedDate, formattedTime })
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* Parses the user input from the input field
|
|
*
|
|
* @param {String} value The user-input to be parsed
|
|
* @returns {Date}
|
|
*/
|
|
parse(value) {
|
|
if (this.isAllDay) {
|
|
let format
|
|
|
|
switch (this.prefix) {
|
|
case 'from':
|
|
format = this.$t('calendar', 'from {formattedDate}')
|
|
break
|
|
|
|
case 'to':
|
|
format = this.$t('calendar', 'to {formattedDate}')
|
|
break
|
|
|
|
case 'on':
|
|
format = this.$t('calendar', 'on {formattedDate}')
|
|
break
|
|
|
|
default:
|
|
format = '{formattedDate}'
|
|
break
|
|
}
|
|
|
|
const regexString = format
|
|
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
|
|
.replace(/(?:^|\\})([^{}]+)(?:$|\\{)/g, (fullMatch, groupMatch) => {
|
|
return fullMatch.replace(groupMatch, '(?:' + groupMatch + ')?')
|
|
})
|
|
.replace('\\{formattedDate\\}', '(.*)')
|
|
const regex = new RegExp(regexString)
|
|
const matches = value.match(regex)
|
|
|
|
if (!matches) {
|
|
showError(this.$t('calendar', 'Please enter a valid date'))
|
|
// Just return the previous date
|
|
return this.date
|
|
}
|
|
|
|
return moment(matches[1], 'L', this.locale).toDate()
|
|
} else {
|
|
let format
|
|
|
|
switch (this.prefix) {
|
|
case 'from':
|
|
format = this.$t('calendar', 'from {formattedDate} at {formattedTime}')
|
|
break
|
|
|
|
case 'to':
|
|
format = this.$t('calendar', 'to {formattedDate} at {formattedTime}')
|
|
break
|
|
|
|
case 'on':
|
|
format = this.$t('calendar', 'on {formattedDate} at {formattedTime}')
|
|
break
|
|
|
|
default:
|
|
format = this.$t('calendar', '{formattedDate} at {formattedTime}')
|
|
break
|
|
}
|
|
|
|
const escapedFormat = format
|
|
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
|
|
.replace(/(?:^|\\})([^{}]+)(?:$|\\{)/g, (fullMatch, groupMatch) => {
|
|
return fullMatch.replace(groupMatch, '(?:' + groupMatch + ')?')
|
|
})
|
|
const dateRegexString = escapedFormat
|
|
.replace('\\{formattedDate\\}', '(.*)')
|
|
.replace('\\{formattedTime\\}', '.*')
|
|
const dateRegex = new RegExp(dateRegexString)
|
|
const timeRegexString = escapedFormat
|
|
.replace('\\{formattedDate\\}', '.*')
|
|
.replace('\\{formattedTime\\}', '(.*)')
|
|
const timeRegex = new RegExp(timeRegexString)
|
|
const dateMatches = value.match(dateRegex)
|
|
const timeMatches = value.match(timeRegex)
|
|
|
|
if (!dateMatches || !timeMatches) {
|
|
showError(this.$t('calendar', 'Please enter a valid date and time'))
|
|
// Just return the previous date
|
|
return this.date
|
|
}
|
|
|
|
return moment(dateMatches[1] + ' ' + timeMatches[1], 'L LT', this.locale).toDate()
|
|
}
|
|
},
|
|
/**
|
|
* Whether or not the date is acceptable
|
|
*
|
|
* @param {Date} date The date to compare to
|
|
* @returns {Boolean}
|
|
*/
|
|
disabledDate(date) {
|
|
return date < this.minimumDate || date > this.maximumDate
|
|
},
|
|
},
|
|
}
|
|
</script>
|