diff options
author | suntala <33031346+suntala@users.noreply.github.com> | 2018-09-17 18:33:51 +0200 |
---|---|---|
committer | suntala <33031346+suntala@users.noreply.github.com> | 2018-09-17 18:33:51 +0200 |
commit | 16bfac4a2dd1957b326f5278d5047784c3444fce (patch) | |
tree | d7d9b1ff48ccd92e2c2d00b1dc9e58896f6fcf13 /src | |
parent | 605f462be165c5526d0599d8d5366341ffee051c (diff) | |
parent | 340c8c4d05bc797f037f83d544169dc304d9e69c (diff) |
Merge branch 'vue' into vue-new-addressbook
Diffstat (limited to 'src')
-rw-r--r-- | src/components/ContactDetails.vue | 15 | ||||
-rw-r--r-- | src/components/ContactDetails/ContactDetailsProperty.vue | 2 | ||||
-rw-r--r-- | src/components/ContentList/ContentListItem.vue | 2 | ||||
-rw-r--r-- | src/components/Properties/PropertyDateTime.vue | 138 | ||||
-rw-r--r-- | src/components/Settings/SettingsAddressbook.vue | 62 | ||||
-rw-r--r-- | src/components/core/appNavigation.vue | 96 | ||||
-rw-r--r-- | src/components/core/appNavigation/navigationItem.vue | 162 | ||||
-rw-r--r-- | src/components/core/popoverMenu.vue | 51 | ||||
-rw-r--r-- | src/components/core/popoverMenu/popoverItem.vue | 78 | ||||
-rw-r--r-- | src/main.js | 10 | ||||
-rw-r--r-- | src/router/index.js | 2 | ||||
-rw-r--r-- | src/store/FakeName.vcf | 242 | ||||
-rw-r--r-- | src/store/addressbooks.js | 9 | ||||
-rw-r--r-- | src/views/Contacts.vue | 23 |
14 files changed, 324 insertions, 568 deletions
diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue index 0b506716..41540e38 100644 --- a/src/components/ContactDetails.vue +++ b/src/components/ContactDetails.vue @@ -52,17 +52,17 @@ <!-- fullname, org, title --> <div id="contact-header-infos"> <h2> - <input id="contact-fullname" v-model="contact.fullName" :disabled="!contact.addressbook.enabled" + <input id="contact-fullname" v-model="contact.fullName" :disabled="!contact.addressbook.readOnly" :placeholder="t('contacts', 'Name')" type="text" autocomplete="off" autocorrect="off" spellcheck="false" name="fullname" value="" @input="debounceUpdateContact"> </h2> <div id="details-org-container"> - <input id="contact-org" v-model="contact.org" :disabled="!contact.addressbook.enabled" + <input id="contact-org" v-model="contact.org" :disabled="!contact.addressbook.readOnly" :placeholder="t('contacts', 'Company')" type="text" autocomplete="off" autocorrect="off" spellcheck="false" name="org" value="" @input="debounceUpdateContact"> - <input id="contact-title" v-model="contact.title" :disabled="!contact.addressbook.enabled" + <input id="contact-title" v-model="contact.title" :disabled="!contact.addressbook.readOnly" :placeholder="t('contacts', 'Title')" type="text" autocomplete="off" autocorrect="off" spellcheck="false" name="title" value="" @input="debounceUpdateContact"> @@ -99,6 +99,7 @@ </template> <script> +import { PopoverMenu } from 'nextcloud-vue' import ClickOutside from 'vue-click-outside' import Vue from 'vue' import VTooltip from 'v-tooltip' @@ -107,7 +108,6 @@ import debounce from 'debounce' import Contact from '../models/contact' import rfcProps from '../models/rfcProps.js' -import PopoverMenu from './core/popoverMenu' import ContactProperty from './ContactDetails/ContactDetailsProperty' import AddNewProp from './ContactDetails/ContactDetailsAddNewProp' import PropertySelect from './Properties/PropertySelect' @@ -175,7 +175,7 @@ export default { href: this.contact.url } ] - if (this.contact.addressbook.enabled) { + if (this.contact.addressbook.readOnly) { actions.push({ icon: 'icon-delete', text: t('contacts', 'Delete'), @@ -192,8 +192,7 @@ export default { sortedProperties() { return this.contact.properties.slice(0).sort((a, b) => { return ( - rfcProps.fieldOrder.indexOf(a.name) - - rfcProps.fieldOrder.indexOf(b.name) + rfcProps.fieldOrder.indexOf(a.name) - rfcProps.fieldOrder.indexOf(b.name) ) }) }, @@ -214,7 +213,7 @@ export default { // store getters filtered and mapped to usable object addressbooksOptions() { return this.addressbooks - .filter(addressbook => addressbook.enabled) + .filter(addressbook => addressbook.readOnly) .map(addressbook => { return { id: addressbook.id, diff --git a/src/components/ContactDetails/ContactDetailsProperty.vue b/src/components/ContactDetails/ContactDetailsProperty.vue index dd352517..4b0428fb 100644 --- a/src/components/ContactDetails/ContactDetailsProperty.vue +++ b/src/components/ContactDetails/ContactDetailsProperty.vue @@ -111,7 +111,7 @@ export default { return this.property.name }, propType() { - return this.property.type + return this.property.getDefaultType() }, // template to use diff --git a/src/components/ContentList/ContentListItem.vue b/src/components/ContentList/ContentListItem.vue index 6c2ccdbf..ee3dfe75 100644 --- a/src/components/ContentList/ContentListItem.vue +++ b/src/components/ContentList/ContentListItem.vue @@ -10,7 +10,7 @@ <div :style="{ 'backgroundColor': colorAvatar }" class="app-content-list-item-icon">{{ contact.displayName | firstLetter }}</div> <div class="app-content-list-item-line-one">{{ contact.displayName }}</div> <div v-if="contact.email" class="app-content-list-item-line-two">{{ contact.email }}</div> - <div v-if="contact.addressbook.enabled" class="icon-delete" tabindex="0" + <div v-if="contact.addressbook.readOnly" class="icon-delete" tabindex="0" @click.prevent.stop="deleteContact" @keypress.enter.prevent.stop="deleteContact" /> </div> </template> diff --git a/src/components/Properties/PropertyDateTime.vue b/src/components/Properties/PropertyDateTime.vue index 74f6ed72..264417da 100644 --- a/src/components/Properties/PropertyDateTime.vue +++ b/src/components/Properties/PropertyDateTime.vue @@ -41,24 +41,88 @@ <!-- delete the prop --> <button :title="t('contacts', 'Delete')" class="property__delete icon-delete" @click="deleteProperty" /> - <input v-model.trim="localValue" class="property__value" type="text" - @input="updateValue"> + <!-- Real input where the picker shows --> + <datetime-picker :value="localValue.toJSDate()" :minute-step="10" :lang="lang" + :clearable="false" :first-day-of-week="firstDay" :type="inputType" + confirm @confirm="updateValue" /> </div> </div> </template> <script> import Multiselect from 'vue-multiselect' -import propertyTitle from './PropertyTitle' +import { DatetimePicker } from 'nextcloud-vue' import debounce from 'debounce' +import moment from 'moment' import { VCardTime } from 'ical.js' +import propertyTitle from './PropertyTitle' + +/** + * Format time with locale to display only + * Using the Object as hared data since it's the only way + * for us to forcefully omit some data (no year, or no time... etc) + * and ths only common syntax between js Date, moment and VCardTime + */ +let formatDateTime = function(vcardTime, type, locale) { + // this is the only possibility for us to ensure + // no data is lost. e.g. if no second are set + // the second will be null and not 0 + let datetimeData = vcardTime.toJSON() + let datetime = '' + /** + * Make sure to display the most interesting data. + * If the Object does not have any time, do not display + * the time and vice-versa. + */ + // No hour, no minute and no second = date only + if (datetimeData.hour === null && datetimeData.minute === null && datetimeData.second === null) { + datetime = moment(datetimeData) + .locale(locale) + .format('LL') + + // No year, no month and no day = time only + } else if (datetimeData.year === null && datetimeData.month === null && datetimeData.day === null) { + datetime = moment(datetimeData) + .locale(locale) + .format('LTS') + } + + // Fallback to the data ical.js provide us + if (datetime === '') { + datetime = moment(datetimeData) + .locale(locale) + .format( + type === 'datetime' + ? 'LLLL' // date & time display + : type === 'date' + ? 'LL' // only date + : 'LTS' // only time + ) + } + return datetimeData.year === null + // replace year and remove double spaces + ? datetime.replace(moment(vcardTime).year(), '').replace(/\s\s+/g, ' ') + : datetime +} + +/** + * Override format function and use this since this + * inside a function declaration will represent the + * location of the call. So this = DatetimePicker. + * Therefore we can use any props we pass through datetime-picker + */ +DatetimePicker.methods.stringify = function() { + return formatDateTime(this.$parent.localValue, this.type, this.$parent.locale) +} + export default { name: 'PropertyDateTime', components: { Multiselect, - propertyTitle + propertyTitle, + DatetimePicker }, props: { @@ -76,6 +140,11 @@ export default { default: '', required: true }, + property: { + type: Object, + default: () => {}, + required: true + }, isFirstProperty: { type: Boolean, default: true @@ -85,11 +154,28 @@ export default { default: true } }, - data() { return { localValue: this.value, - localType: this.selectType + localType: this.selectType, + + // input type following DatePicker docs + inputType: this.property.getDefaultType() === 'date-time' || this.property.getDefaultType() === 'date-and-or-time' + ? 'datetime' + : this.property.getDefaultType() === 'date' + ? 'date' + : 'time', + + // locale and lang data + locale: 'en', // temporary value, see mounted + firstDay: window.firstDay, // provided by nextcloud + lang: { + days: window.dayNamesShort, // provided by nextcloud + months: window.monthNamesShort, // provided by nextcloud + placeholder: { + date: t('contacts', 'Select Date') + } + } } }, @@ -116,6 +202,31 @@ export default { } }, + mounted() { + // Load the locale + // convert format like en_GB to en-gb for `moment.js` + let locale = OC.getLocale().replace('_', '-').toLowerCase() + + // default load e.g. fr-fr + import('moment/locale/' + this.locale) + .then(e => { + // force locale change to update + // the component once done loading + this.locale = locale + }) + .catch(e => { + // failure: fallback to fr + import('moment/locale/' + locale.split('-')[0]) + .then(e => { + this.locale = locale.split('-')[0] + }) + .catch(e => { + // failure, fallback to english + this.locale = 'en' + }) + }) + }, + methods: { /** @@ -129,7 +240,22 @@ export default { * Debounce and send update event to parent */ updateValue: debounce(function(e) { + let rawData = moment(e).toArray() + + /** + * Use the current year to ensure we do not lose + * the year data on v4.0 since we currently have + * no options to remove the year selection. + */ + if (this.value.year === null) { + rawData[0] = null + } + + // reset the VCardTime component to the selected date/time + this.localValue.resetTo(...rawData) + // https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier + // Use moment to convert the JsDate to Object this.$emit('update:value', this.localValue) }, 500), diff --git a/src/components/Settings/SettingsAddressbook.vue b/src/components/Settings/SettingsAddressbook.vue index beabae89..864643e5 100644 --- a/src/components/Settings/SettingsAddressbook.vue +++ b/src/components/Settings/SettingsAddressbook.vue @@ -22,17 +22,7 @@ --> <template> <div> - <li v-if="editingName" class="new-addressbook"> - <form id="rename-addressbook__form" name="rename-addressbook__form" class="rename-addressbook__form" - @submit.prevent="updateAddressbookName"> - <!-- rename addressbook input --> - <input :placeholder="addressbook.displayName" - v-model="newName" type="text"> - <input type="submit" value="" - class="rename-addressbook__submit icon-confirm"> - </form> - </li> - <li v-else :class="{disabled: !addressbook.enabled}" class="addressbook"> + <li :class="{disabled: !addressbook.enabled}" class="addressbook"> <!-- addressbook name --> <span class="addressbook__name">{{ addressbook.displayName }}</span> <!-- sharing button --> @@ -53,23 +43,24 @@ <script> import Vue from 'vue' -import popoverMenu from '../core/popoverMenu' -import shareAddressBook from './SettingsAddressbookShare' -import renameAddressBookField from './SettingsRenameAddressbookField' -import clickOutside from 'vue-click-outside' +import { PopoverMenu } from 'nextcloud-vue' +import ClickOutside from 'vue-click-outside' import VueClipboard from 'vue-clipboard2' +import ShareAddressBook from './SettingsAddressbookShare' +import RenameAddressBookField from './SettingsRenameAddressbookField' + Vue.use(VueClipboard) export default { name: 'SettingsAddressbook', components: { - popoverMenu, - shareAddressBook, - renameAddressBookField + PopoverMenu, + ShareAddressBook, + RenameAddressBookField }, directives: { - clickOutside + ClickOutside }, props: { addressbook: { @@ -86,7 +77,7 @@ export default { editingName: false, copied: false, copySuccess: true, - newName: this.addressbook.displayName // new name for addressbook + readOnly: this.addressbook.readOnly } }, computed: { @@ -95,8 +86,8 @@ export default { }, // building the popover menu menu() { - let menu = - [{ + let menu = [ + { href: this.addressbook.url, icon: 'icon-public', text: !this.copied @@ -111,20 +102,29 @@ export default { icon: 'icon-download', text: t('contacts', 'Download'), action: null - }, - { + } + ] + + // check if addressbook is readonly + if (!this.readOnly) { + menu.push({ icon: 'icon-rename', - text: t('contacts', 'Rename'), - action: this.renameAddressbook + // check if editing name + input: this.editingName ? 'text' : null, + text: !this.editingName ? t('contacts', 'Rename') : '', + action: !this.editingName ? this.renameAddressbook : this.updateAddressbookName, + value: this.addressbook.displayName, + placeholder: this.addressbook.displayName }, { - icon: 'checkbox', text: this.enabled ? t('contacts', 'Enabled') : t('contacts', 'Disabled'), input: 'checkbox', key: 'enableAddressbook', model: this.enabled, action: this.toggleAddressbookEnabled - }] + }) + + } // check to ensure last addressbook is not deleted. if (this.$store.getters.getAddressbooks.length > 1) { menu.push({ @@ -159,9 +159,10 @@ export default { renameAddressbook() { this.editingName = true }, - updateAddressbookName() { + updateAddressbookName(e) { let addressbook = this.addressbook - let newName = this.newName + // New name for addressbook - inputed value from form + let newName = e.target[0].value this.$store.dispatch('renameAddressbook', { addressbook, newName }).then(this.editingName = false) }, copyLink() { @@ -174,6 +175,7 @@ export default { this.copied = true }) + // timeout sets the text back to copy to show text was copied setTimeout(() => { this.copied = false }, 1500) } } diff --git a/src/components/core/appNavigation.vue b/src/components/core/appNavigation.vue deleted file mode 100644 index 8793e9eb..00000000 --- a/src/components/core/appNavigation.vue +++ /dev/null @@ -1,96 +0,0 @@ -<!-- - - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - - - - @author John Molakvoæ <skjnldsv@protonmail.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> - <div id="app-navigation" :class="{'icon-loading': menu.loading}"> - <div v-if="menu.new" class="app-navigation-new"> - <button :id="menu.new.id" :class="menu.new.icon" type="button" - @click="menu.new.action">{{ menu.new.text }}</button> - </div> - <ul :id="menu.id"> - <navigation-item v-for="item in menu.items" :item="item" :key="item.key" /> - </ul> - <div v-click-outside="closeMenu" v-if="!!$slots['settings-content']" id="app-settings" - :class="{open: opened}"> - <div id="app-settings-header"> - <button class="settings-button" - data-apps-slide-toggle="#app-settings-content" - @click="toggleMenu" - >{{ t('contacts', 'Settings') }}</button> - </div> - <div id="app-settings-content"> - <slot name="settings-content" /> - </div> - </div> - </div> -</template> - -<script> -import navigationItem from './appNavigation/navigationItem' -import clickOutside from 'vue-click-outside' - -export default { - name: 'AppNavigation', - components: { - navigationItem, - clickOutside - }, - directives: { - clickOutside - }, - props: { - menu: { - type: Object, - required: true, - default: () => { - return { - new: { - id: 'new-item', - action: () => alert('Success!'), - icon: 'icon-add', - text: 'New item' - }, - menu: { - id: 'navigation', - items: [ - - ] - } - } - } - } - }, - data() { - return { - opened: false - } - }, - methods: { - toggleMenu() { - this.opened = !this.opened - }, - closeMenu() { - this.opened = false - } - } -} -</script> diff --git a/src/components/core/appNavigation/navigationItem.vue b/src/components/core/appNavigation/navigationItem.vue deleted file mode 100644 index 925ed4c2..00000000 --- a/src/components/core/appNavigation/navigationItem.vue +++ /dev/null @@ -1,162 +0,0 @@ -<!-- - - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - - - - @author John Molakvoæ <skjnldsv@protonmail.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> - - <!-- Is this a caption ? --> - <li v-if="item.caption" class="app-navigation-caption">{{ item.text }}</li> - - <!-- Navigation item --> - <nav-element v-else :id="item.id" v-bind="navElement(item)" - :class="[{'icon-loading-small': item.loading, 'open': item.opened, 'collapsible': item.collapsible&&item.children&&item.children.length>0 }, item.classes]"> - - <!-- Bullet --> - <div v-if="item.bullet" :style="{ backgroundColor: item.bullet }" class="app-navigation-entry-bullet" /> - - <!-- Main link --> - <a :href="(item.href) ? item.href : '#' " :class="item.icon" @click="toggleCollapse"> - <img v-if="item.iconUrl" :alt="item.text" :src="item.iconUrl"> - {{ item.text }} - </a> - - <!-- Popover, counter and button(s) --> - <div v-if="item.utils" class="app-navigation-entry-utils"> - <ul> - <!-- counter --> - <li v-if="Number.isInteger(item.utils.counter)" - class="app-navigation-entry-utils-counter">{{ item.utils.counter }}</li> - - <!-- first action if only one action and counter --> - <li v-if="item.utils.actions && item.utils.actions.length === 1 && Number.isInteger(item.utils.counter)" - class="app-navigation-entry-utils-menu-button"> - <button :class="item.utils.actions[0].icon" :title="item.utils.actions[0].text" @click="item.utils.actions[0].action" /> - </li> - - <!-- second action only two actions and no counter --> - <li v-for="action in item.utils.actions" - v-else-if="item.utils.actions && item.utils.actions.length === 2 && !Number.isInteger(item.utils.counter)" :key="action.action" - class="app-navigation-entry-utils-menu-button"> - <button :class="action.icon" :title="action.text" @click="action.action" /> - </li> - - <!-- menu if only at least one action and counter OR two actions and no counter--> - <li v-else-if="item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)" - class="app-navigation-entry-utils-menu-button"> - <button v-click-outside="hideMenu" @click="showMenu" /> - </li> - </ul> - </div> - - <!-- if more than 2 actions or more than 1 actions with counter --> - <div v-if="item.utils && item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)" - :class="{ 'open': openedMenu }" class="app-navigation-entry-menu"> - <popover-menu :menu="item.utils.actions" /> - </div> - - <!-- undo entry --> - <div v-if="item.undo" class="app-navigation-entry-deleted"> - <div class="app-navigation-entry-deleted-description">{{ item.undo.text }}</div> - <button :title="t('settings', 'Undo')" class="app-navigation-entry-deleted-button icon-history" /> - </div> - - <!-- edit entry --> - <div v-if="item.edit" class="app-navigation-entry-edit"> - <form> - <input v-model="item.text" type="text"> - <input type="submit" value="" class="icon-confirm"> - <input type="submit" value="" class="icon-close" - @click.stop.prevent="cancelEdit"> - </form> - </div> - - <!-- if the item has children, inject the component with proper data --> - <ul v-if="item.children"> - <navigation-item v-for="(item, key) in item.children" :item="item" :key="key" /> - </ul> - </nav-element> -</template> - -<script> -import popoverMenu from '../popoverMenu' -import ClickOutside from 'vue-click-outside' -import Vue from 'vue' - -export default { - name: 'NavigationItem', - components: { - popoverMenu - }, - directives: { - ClickOutside - }, - props: { - item: { - type: Object, - required: true - } - }, - data() { - return { - openedMenu: false - } - }, - mounted() { - // prevent click outside event with popupItem. - this.popupItem = this.$el - }, - methods: { - showMenu() { - this.openedMenu = true - }, - hideMenu() { - this.openedMenu = false - }, - toggleCollapse() { - // if item.opened isn't set, Vue won't trigger view updates https://vuejs.org/v2/api/#Vue-set - // ternary is here to detect the undefined state of item.opened - Vue.set(this.item, 'opened', this.item.opened ? !this.item.opened : true) - }, - cancelEdit() { - // remove the editing class - if (Array.isArray(this.item.classes)) { - this.item.classes = this.item.classes.filter( - item => item !== 'editing' - ) - } - }, - // This is used to decide which outter element type to use - // li or router-link - navElement(item) { - if (item.href) { - return { - is: 'li' - } - } - return { - is: 'router-link', - tag: 'li', - to: item.router, - exact: true - } - } - } -} -</script> diff --git a/src/components/core/popoverMenu.vue b/src/components/core/popoverMenu.vue deleted file mode 100644 index 6c1a0541..00000000 --- a/src/components/core/popoverMenu.vue +++ /dev/null @@ -1,51 +0,0 @@ -<!-- - - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - - - - @author John Molakvoæ <skjnldsv@protonmail.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> - <ul> - <popover-item v-for="(item, key) in menu" :item="item" :key="key" /> - </ul> -</template> - -<script> -import popoverItem from './popoverMenu/popoverItem' - -export default { - name: 'PopoverMenu', - components: { - popoverItem - }, - props: { - menu: { - type: Array, - default: () => { - return [{ - href: 'https://nextcloud.com', - icon: 'icon-links', - text: 'Nextcloud' - }] - }, - required: true - } - } -} -</script> diff --git a/src/components/core/popoverMenu/popoverItem.vue b/src/components/core/popoverMenu/popoverItem.vue deleted file mode 100644 index b9475812..00000000 --- a/src/components/core/popoverMenu/popoverItem.vue +++ /dev/null @@ -1,78 +0,0 @@ -<!-- - - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - - - - @author John Molakvoæ <skjnldsv@protonmail.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> - <li> - <!-- If item.href is set, a link will be directly used --> - <a v-if="item.href" :href="(item.href) ? item.href : '#' " |