Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/polls.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordartcafe <github@dartcafe.de>2019-12-10 09:32:58 +0300
committerdartcafe <github@dartcafe.de>2019-12-10 09:32:58 +0300
commit41930996829421bbf5e26aeb25c93d9214d35e9c (patch)
tree217e7fe3ed394256a324ed7bcbf81c89f0a0c19e /src/js/components
parent7363c382904c6ea909010a4939e4ecd40dfdb5eb (diff)
- moved src back in right place
Diffstat (limited to 'src/js/components')
-rw-r--r--src/js/components/Base/LoadingOverlay.vue59
-rw-r--r--src/js/components/Base/UserDiv.vue148
-rw-r--r--src/js/components/Create/CreateDateItem.vue71
-rw-r--r--src/js/components/Create/CreateDlg.vue125
-rw-r--r--src/js/components/Create/TextPollItem.vue44
-rw-r--r--src/js/components/Navigation/Navigation.vue191
-rw-r--r--src/js/components/Navigation/store/polls.js75
-rw-r--r--src/js/components/Notification/Notification.vue64
-rw-r--r--src/js/components/PollList/PollListItem.vue490
-rw-r--r--src/js/components/Share/ShareDiv.vue172
-rw-r--r--src/js/components/SideBar/CommentAdd.vue99
-rw-r--r--src/js/components/SideBar/SideBar.vue109
-rw-r--r--src/js/components/SideBar/SideBarTabComments.vue98
-rw-r--r--src/js/components/SideBar/SideBarTabConfiguration.vue344
-rw-r--r--src/js/components/SideBar/SideBarTabDateOptions.vue173
-rw-r--r--src/js/components/SideBar/SideBarTabInformation.vue58
-rw-r--r--src/js/components/SideBar/SideBarTabShare.vue211
-rw-r--r--src/js/components/SideBar/SideBarTabTextOptions.vue164
-rw-r--r--src/js/components/VoteTable/VoteHeader.vue77
-rw-r--r--src/js/components/VoteTable/VoteHeaderPublic.vue221
-rw-r--r--src/js/components/VoteTable/VoteTable.vue210
-rw-r--r--src/js/components/VoteTable/VoteTableHeader.vue291
-rw-r--r--src/js/components/VoteTable/VoteTableItem.vue169
23 files changed, 3663 insertions, 0 deletions
diff --git a/src/js/components/Base/LoadingOverlay.vue b/src/js/components/Base/LoadingOverlay.vue
new file mode 100644
index 00000000..c8148854
--- /dev/null
+++ b/src/js/components/Base/LoadingOverlay.vue
@@ -0,0 +1,59 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 class="loading-overlay">
+ <span class="icon-loading" />
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'LoadingOverlay'
+}
+</script>
+
+<style lang="scss">
+.loading-overlay {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background: #fff;
+ opacity: 0.9;
+ z-index: 1001;
+ .icon-loading {
+ position: fixed;
+ left: 50%;
+ top: 50%;
+ margin-left: -35px;
+ margin-top: -10px;
+ &::after {
+ border: 10px solid var(--color-loading-light);
+ border-top-color: var(--color-primary-element);
+ height: 70px;
+ width: 70px;
+ }
+ }
+}
+</style>
diff --git a/src/js/components/Base/UserDiv.vue b/src/js/components/Base/UserDiv.vue
new file mode 100644
index 00000000..231d9f5c
--- /dev/null
+++ b/src/js/components/Base/UserDiv.vue
@@ -0,0 +1,148 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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/>.
+ -
+ -->
+
+/* global Vue, oc_userconfig */
+<template>
+ <div class="user-row" :class="type">
+ <div v-if="description" class="description">
+ {{ description }}
+ </div>
+ <Avatar :disable-menu="true" :user="userId" :display-name="computedDisplayName"
+ :is-no-user="isNoUser" />
+ <div class="avatar" :class="iconClass" />
+
+ <div v-if="!hideNames" class="user-name">
+ {{ computedDisplayName }}
+ </div>
+ </div>
+</template>
+
+<script>
+import { Avatar } from '@nextcloud/vue'
+
+export default {
+ name: 'UserDiv',
+
+ components: {
+ Avatar
+ },
+
+ props: {
+ hideNames: {
+ type: Boolean,
+ default: false
+ },
+ userId: {
+ type: String,
+ default: undefined
+ },
+ displayName: {
+ type: String,
+ default: ''
+ },
+ type: {
+ type: String,
+ default: 'user'
+ },
+ description: {
+ type: String,
+ default: ''
+ },
+ icon: {
+ type: Boolean,
+ default: false
+ }
+
+ },
+
+ data() {
+ return {
+ nothidden: false
+ }
+ },
+
+ computed: {
+ isNoUser() {
+ return this.type !== 'user'
+ },
+
+ isValidUser() {
+ return (this.userId !== '' && this.userId !== null)
+ },
+
+ iconClass() {
+ if (this.icon) {
+ return 'icon-' + this.type
+ } else {
+ return ''
+ }
+ },
+
+ computedDisplayName() {
+ let value = this.displayName
+
+ if (this.userId === OC.getCurrentUser().uid) {
+ value = OC.getCurrentUser().displayName
+ } else {
+ if (!this.displayName) {
+ value = this.userId
+ }
+ }
+ if (this.type === 'group') {
+ value = value + ' (' + t('polls', 'Group') + ')'
+ }
+ return value
+ }
+
+ }
+}
+</script>
+
+<style lang="scss">
+.user-row {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ margin-left: 0;
+ margin-top: 0;
+
+ > div {
+ margin: 2px 4px;
+ }
+
+ .description {
+ opacity: 0.7;
+ flex: 0;
+ }
+
+ .avatar {
+ height: 32px;
+ width: 32px;
+ flex: 0;
+ }
+
+ .user-name {
+ opacity: 0.5;
+ flex: 1;
+ }
+}
+</style>
diff --git a/src/js/components/Create/CreateDateItem.vue b/src/js/components/Create/CreateDateItem.vue
new file mode 100644
index 00000000..eaebe234
--- /dev/null
+++ b/src/js/components/Create/CreateDateItem.vue
@@ -0,0 +1,71 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <div>{{ option.timestamp | localFullDate }}</div>
+ <div>
+ <a class="icon-delete" @click="$emit('remove')" />
+ </div>
+ </li>
+</template>
+
+<script>
+import moment from 'moment'
+
+export default {
+ name: 'DatePollItem',
+
+ filters: {
+ localFullDate(timestamp) {
+ if (!timestamp) return ''
+ if (timestamp < 999999999999) timestamp = timestamp * 1000
+ if (!moment(timestamp).isValid()) return 'Invalid Date'
+ return moment(timestamp).format('llll')
+ }
+ },
+
+ props: {
+ option: {
+ type: Object,
+ default: undefined
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+ li > div {
+ display: flex;
+ flex-grow: 1;
+ font-size: 1.2em;
+ opacity: 0.7;
+ white-space: normal;
+ padding-right: 4px;
+ }
+
+ li > div:nth-last-child(1) {
+ justify-content: center;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+</style>
diff --git a/src/js/components/Create/CreateDlg.vue b/src/js/components/Create/CreateDlg.vue
new file mode 100644
index 00000000..4330abb9
--- /dev/null
+++ b/src/js/components/Create/CreateDlg.vue
@@ -0,0 +1,125 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 lang="html">
+ <div class="create-dialog">
+ <h2>{{ t('polls', 'Create new poll') }}</h2>
+ <input id="pollTitle" v-model="title" type="text"
+ :placeholder="t('polls', 'Enter Title')">
+
+ <div class="configBox">
+ <label class="title icon-checkmark">
+ {{ t('polls', 'Poll type') }}
+ </label>
+ <input id="datePoll" v-model="type" value="datePoll"
+ :disabled="protect" type="radio" class="radio">
+ <label for="datePoll">
+ {{ t('polls', 'Event schedule') }}
+ </label>
+ <input id="textPoll" v-model="type" value="textPoll"
+ :disabled="protect" type="radio" class="radio">
+ <label for="textPoll">
+ {{ t('polls', 'Text based') }}
+ </label>
+ </div>
+
+ <div class="create-buttons">
+ <button class="button" @click="cancel">
+ {{ t('polls', 'Cancel') }}
+ </button>
+ <button :disabled="titleEmpty" class="button primary" @click="confirm">
+ {{ t('polls', 'Publish') }}
+ </button>
+ </div>
+ </div>
+</template>
+
+<script>
+import { mapState, mapMutations } from 'vuex'
+export default {
+ name: 'CreateDlg',
+
+ data() {
+ return {
+ id: 0,
+ type: 'datePoll',
+ title: ''
+ }
+ },
+
+ computed: {
+ ...mapState({
+ event: state => state.event
+ }),
+
+ titleEmpty() {
+ return this.title === ''
+ }
+ },
+
+ methods: {
+ ...mapMutations([ 'setEventProperty', 'resetEvent', 'reset' ]),
+
+ cancel() {
+ this.title = ''
+ this.type = 'datePoll'
+ this.$emit('closeCreate')
+ },
+
+ confirm() {
+ this.resetEvent()
+ this.reset()
+ this.setEventProperty({ 'id': 0 })
+ this.setEventProperty({ 'title': this.title })
+ this.setEventProperty({ 'type': this.type })
+ this.$store.dispatch('writeEventPromise')
+ .then((response) => {
+ this.cancel()
+ OC.Notification.showTemporary(t('polls', 'Poll "%n" added', 1, this.event.title), { type: 'success' })
+ this.$router.push({ name: 'vote', params: { id: this.event.id } })
+ })
+ .catch(() => {
+ OC.Notification.showTemporary(t('polls', 'Error while creating Poll "%n"', 1, this.event.title), { type: 'error' })
+ })
+ }
+ }
+
+}
+</script>
+
+<style lang="css" scoped>
+.create-dialog {
+ display: flex;
+ flex-direction: column;
+ background-color: var(--color-main-background);
+ padding: 20px;
+}
+
+#pollTitle {
+ width: 100%;
+}
+
+.create-buttons {
+ display: flex;
+ justify-content: space-between;
+}
+</style>
diff --git a/src/js/components/Create/TextPollItem.vue b/src/js/components/Create/TextPollItem.vue
new file mode 100644
index 00000000..aefd8d09
--- /dev/null
+++ b/src/js/components/Create/TextPollItem.vue
@@ -0,0 +1,44 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <div>{{ option.pollOptionText }}</div>
+ <div>
+ <a class="icon icon-delete svg delete-poll" @click="$emit('remove')" />
+ </div>
+ </li>
+</template>
+
+<script>
+export default {
+ name: 'TextPollItem',
+
+ props: {
+ option: {
+ type: Object,
+ default: undefined
+ }
+ }
+}
+
+</script>
diff --git a/src/js/components/Navigation/Navigation.vue b/src/js/components/Navigation/Navigation.vue
new file mode 100644
index 00000000..cafa37d5
--- /dev/null
+++ b/src/js/components/Navigation/Navigation.vue
@@ -0,0 +1,191 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 lang="html">
+ <AppNavigation>
+ <AppNavigationNew :text="t('polls', 'Add new Poll')" @click="toggleCreateDlg" />
+ <CreateDlg v-show="createDlg" @closeCreate="closeCreate()" />
+ <ul>
+ <AppNavigationItem
+ :title="t('polls', 'All polls')"
+ :allow-collapse="true"
+ icon="icon-folder"
+ :to="{ name: 'list', params: {type: 'all'}}"
+ :open="true">
+ <ul>
+ <AppNavigationItem
+ v-for="(poll) in allPolls"
+ :key="poll.id"
+ :title="poll.title"
+ :icon="eventIcon(poll.type)"
+ :to="{name: 'vote', params: {id: poll.id}}" />
+ </ul>
+ </AppNavigationItem>
+ <AppNavigationItem
+ :title="t('polls', 'My polls')"
+ :allow-collapse="true"
+ icon="icon-user"
+ :to="{ name: 'list', params: {type: 'my'}}"
+ :open="false">
+ <ul>
+ <AppNavigationItem
+ v-for="(poll) in myPolls"
+ :key="poll.id"
+ :title="poll.title"
+ :icon="eventIcon(poll.type)"
+ :to="{name: 'vote', params: {id: poll.id}}" />
+ </ul>
+ </AppNavigationItem>
+ <AppNavigationItem
+ :title="t('polls', 'Public polls')"
+ :allow-collapse="true"
+ icon="icon-link"
+ :to="{ name: 'list', params: {type: 'public'}}"
+ :open="false">
+ <ul>
+ <AppNavigationItem
+ v-for="(poll) in publicPolls"
+ :key="poll.id"
+ :title="poll.title"
+ :icon="eventIcon(poll.type)"
+ :to="{name: 'vote', params: {id: poll.id}}" />
+ </ul>
+ </AppNavigationItem>
+ <AppNavigationItem
+ :title="t('polls', 'Hidden polls')"
+ :allow-collapse="true"
+ icon="icon-password"
+ :to="{ name: 'list', params: {type: 'hidden'}}"
+ :open="false">
+ <ul>
+ <AppNavigationItem
+ v-for="(poll) in hiddenPolls"
+ :key="poll.id"
+ :title="poll.title"
+ :icon="eventIcon(poll.type)"
+ :to="{name: 'vote', params: {id: poll.id}}" />
+ </ul>
+ </AppNavigationItem>
+ <AppNavigationItem
+ :title="t('polls', 'Deleted polls')"
+ :allow-collapse="true"
+ icon="icon-delete"
+ :to="{ name: 'list', params: {type: 'deleted'}}"
+ :open="false">
+ <ul>
+ <AppNavigationItem
+ v-for="(poll) in deletedPolls"
+ :key="poll.id"
+ :title="poll.title"
+ :icon="eventIcon(poll.type)"
+ :to="{name: 'vote', params: {id: poll.id}}" />
+ </ul>
+ </AppNavigationItem>
+ </ul>
+
+ <AppNavigationSettings>
+ <router-link :to="{ name: 'list'}">
+ List
+ </router-link>
+ </AppNavigationSettings>
+ </AppNavigation>
+</template>
+
+<script>
+
+import { AppNavigation, AppNavigationNew, AppNavigationItem, AppNavigationSettings } from '@nextcloud/vue'
+import { mapGetters } from 'vuex'
+import CreateDlg from '../Create/CreateDlg'
+import state from './store/polls.js'
+
+export default {
+ name: 'Navigation',
+ components: {
+ AppNavigation,
+ AppNavigationNew,
+ AppNavigationItem,
+ AppNavigationSettings,
+ CreateDlg
+ },
+
+ data() {
+ return {
+ createDlg: false
+ }
+ },
+
+ computed: {
+
+ ...mapGetters([
+ 'allPolls',
+ 'myPolls',
+ 'publicPolls',
+ 'hiddenPolls',
+ 'deletedPolls'
+ ]),
+
+ pollList() {
+ return this.$store.state.polls.list
+ }
+ },
+
+ created() {
+ this.$store.registerModule('polls', state)
+ this.refreshPolls()
+ },
+
+ methods: {
+ closeCreate() {
+ this.createDlg = false
+ },
+
+ toggleCreateDlg() {
+ this.createDlg = !this.createDlg
+ },
+
+ eventIcon(type) {
+ if (type === '0') {
+ return 'icon-calendar'
+ } else {
+ return 'icon-toggle-filelist'
+ }
+ },
+
+ refreshPolls() {
+ if (this.$route.name !== 'publicVote') {
+
+ this.loading = true
+ this.$store
+ .dispatch('loadPolls')
+ .then(response => {
+ this.loading = false
+ })
+ .catch(error => {
+ this.loading = false
+ console.error('refresh poll: ', error.response)
+ OC.Notification.showTemporary(t('polls', 'Error loading polls'), { type: 'error' })
+ })
+ }
+ }
+ }
+}
+</script>
diff --git a/src/js/components/Navigation/store/polls.js b/src/js/components/Navigation/store/polls.js
new file mode 100644
index 00000000..0f528579
--- /dev/null
+++ b/src/js/components/Navigation/store/polls.js
@@ -0,0 +1,75 @@
+/*
+ * @copyright Copyright (c) 2019 Rene Gieling <github@dartcafe.de>
+ *
+ * @author Rene Gieling <github@dartcafe.de>
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @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/>.
+ *
+ */
+
+import axios from '@nextcloud/axios'
+
+const state = {
+ list: []
+}
+
+const mutations = {
+ setPolls(state, { list }) {
+ state.list = list
+ }
+}
+
+const getters = {
+ countPolls: (state) => {
+ return state.list.length
+ },
+ allPolls: (state) => {
+ return state.list.filter(poll => (!poll.deleted))
+ },
+ myPolls: (state) => {
+ return state.list.filter(poll => (poll.owner === OC.getCurrentUser().uid))
+ },
+ publicPolls: (state) => {
+ return state.list.filter(poll => (poll.access === 'public'))
+ },
+ hiddenPolls: (state) => {
+ return state.list.filter(poll => (poll.access === 'hidden'))
+ },
+ deletedPolls: (state) => {
+ return state.list.filter(poll => (poll.deleted))
+ }
+}
+
+const actions = {
+ loadPolls({ commit }) {
+ return axios.get(OC.generateUrl('apps/polls/get/events'))
+ .then((response) => {
+ commit('setPolls', { list: response.data })
+ }, (error) => {
+ console.error(error.response)
+ })
+ },
+
+ deletePollPromise(context, payload) {
+ return axios.post(
+ OC.generateUrl('apps/polls/remove/poll'),
+ payload.event
+ )
+ }
+}
+
+export default { state, mutations, getters, actions }
diff --git a/src/js/components/Notification/Notification.vue b/src/js/components/Notification/Notification.vue
new file mode 100644
index 00000000..930721c6
--- /dev/null
+++ b/src/js/components/Notification/Notification.vue
@@ -0,0 +1,64 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 lang="html">
+ <div class="notification">
+ <input id="subscribe" v-model="subscribe" type="checkbox"
+ class="checkbox">
+ <label for="subscribe">{{ t('polls', 'Receive notification email on activity') }}</label>
+ </div>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+ name: 'Notification',
+
+ computed: {
+ ...mapState({
+ notification: state => state.notification,
+ event: state => state.event
+ }),
+
+ subscribe: {
+ get() {
+ return this.notification.subscribed
+ },
+ set(value) {
+ this.$store.commit('setNotification', value)
+ this.$store.dispatch('writeSubscriptionPromise', { pollId: this.event.id })
+ }
+ }
+ },
+
+ mounted() {
+ this.$store.dispatch('getSubscription', { pollId: this.$route.params.id })
+ }
+
+}
+</script>
+
+<style lang="css" scoped>
+ .notification {
+ padding: 24px;
+ }
+</style>
diff --git a/src/js/components/PollList/PollListItem.vue b/src/js/components/PollList/PollListItem.vue
new file mode 100644
index 00000000..95c0468a
--- /dev/null
+++ b/src/js/components/PollList/PollListItem.vue
@@ -0,0 +1,490 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 v-if="header" class="pollListItem header">
+ <div class="title">
+ {{ t('polls', 'Title') }}
+ </div>
+
+ <div class="access">
+ {{ t('polls', 'Access') }}
+ </div>
+
+ <div class="owner">
+ {{ t('polls', 'Owner') }}
+ </div>
+
+ <div class="dates">
+ <div class="created">
+ {{ t('polls', 'Created') }}
+ </div>
+ <div class="expiry">
+ {{ t('polls', 'Expires') }}
+ </div>
+ </div>
+ </div>
+
+ <div v-else class="pollListItem poll">
+ <div v-tooltip.auto="pollType" class="thumbnail" :class="[pType, {expired : poll.expired}]">
+ {{ pollType }}
+ </div>
+
+ <!-- <div v-if="votedBycurrentUser" class="symbol icon-voted" /> -->
+
+ <router-link :to="{name: 'vote', params: {id: poll.id}}" class="title">
+ <div class="name">
+ {{ poll.title }}
+ </div>
+ <div class="description">
+ {{ poll.description }}
+ </div>
+ </router-link>
+
+ <!-- <div v-if="countComments" v-tooltip.auto="countCommentsHint" class="app-navigation-entry-utils-counter highlighted">
+ <span>{{ countComments }}</span>
+ </div> -->
+
+ <div class="actions">
+ <div class="toggleUserActions">
+ <div v-click-outside="hideMenu" class="icon-more" @click="toggleMenu" />
+ <div class="popovermenu" :class="{ 'open': openedMenu }">
+ <popover-menu :menu="menuItems" />
+ </div>
+ </div>
+ </div>
+
+ <div v-tooltip.auto="accessType" class="thumbnail access" :class="aType">
+ {{ accessType }}
+ </div>
+
+ <div class="owner">
+ <user-div :user-id="poll.owner" :display-name="poll.ownerDisplayName" />
+ </div>
+
+ <div class="dates">
+ <div class="created ">
+ {{ timeSpanCreated }}
+ </div>
+ <div class="expiry" :class="{ expired : poll.expired }">
+ {{ timeSpanExpiration }}
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+import moment from 'moment'
+
+export default {
+ name: 'PollListItem',
+
+ props: {
+ header: {
+ type: Boolean,
+ default: false
+ },
+ poll: {
+ type: Object,
+ default: undefined
+ }
+ },
+
+ data() {
+ return {
+ openedMenu: false,
+ hostName: this.$route.query.page
+ }
+ },
+
+ computed: {
+
+ // TODO: dity hack
+ aType() {
+ if (this.poll.access === 'public') {
+ return this.poll.access
+ } else if (this.poll.access === 'registered') {
+ return this.poll.access
+ } else if (this.poll.access === 'hidden') {
+ return this.poll.access
+ } else {
+ return 'select'
+ }
+ },
+
+ expired() {
+ if (this.poll.expire === null) {
+ return false
+ } else if (Date.parse(this.poll.expire) < Date.now()) {
+ return false
+ } else {
+ return true
+ }
+ },
+
+ accessType() {
+ if (this.aType === 'public') {
+ return t('polls', 'Public access')
+ } else if (this.aType === 'registered') {
+ return t('polls', 'Registered users only')
+ } else if (this.aType === 'hidden') {
+ return t('polls', 'Hidden poll')
+ } else if (this.aType === 'select') {
+ return t('polls', 'Only shared')
+ } else {
+ return ''
+ }
+ },
+
+ // TODO: dity hack
+ pType() {
+ if (this.poll.type === '1') {
+ // TRANSLATORS This means that this is the type of the poll. Another type is a 'date poll'.
+ return t('polls', 'textPoll')
+ } else {
+ // TRANSLATORS This means that this is the type of the poll. Another type is a 'text poll'.
+ return t('polls', 'datePoll')
+ }
+
+ },
+
+ pollType() {
+ if (this.pType === 'textPoll') {
+ // TRANSLATORS This means that this is the type of the poll. Another type is a 'date poll'.
+ return t('polls', 'Text poll')
+ } else {
+ // TRANSLATORS This means that this is the type of the poll. Another type is a 'text poll'.
+ return t('polls', 'Date poll')
+ }
+ },
+
+ timeSpanCreated() {
+ return moment(this.poll.created).fromNow()
+ },
+
+ timeSpanExpiration() {
+ if (this.poll.expire) {
+ return moment(this.poll.expire).fromNow()
+ } else {
+ return t('polls', 'never')
+ }
+ },
+
+ voteUrl() {
+ return OC.generateUrl('apps/polls/poll/') + this.poll.id
+ },
+
+ menuItems() {
+ let items = [
+ {
+ key: 'copyLink',
+ icon: 'icon-clippy',
+ text: t('polls', 'Copy Link'),
+ action: this.copyLink
+ },
+ {
+ key: 'clonePoll',
+ icon: 'icon-confirm',
+ text: t('polls', 'Clone poll'),
+ action: this.clonePoll
+ }
+ ]
+
+ if (this.poll.owner === OC.getCurrentUser().uid) {
+ // items.push({
+ // key: 'editPoll',
+ // icon: 'icon-rename',
+ // text: t('polls', 'Edit poll'),
+ // action: this.editPoll
+ // })
+ items.push({
+ key: 'deletePoll',
+ icon: 'icon-delete',
+ text: t('polls', 'Delete poll'),
+ action: this.deletePoll
+ })
+ } else if (OC.isUserAdmin()) {
+ // items.push({
+ // key: 'editPoll',
+ // icon: 'icon-rename',
+ // text: t('polls', 'Edit poll as admin'),
+ // action: this.editPoll
+ // })
+ items.push({
+ key: 'deletePoll',
+ icon: 'icon-delete',
+ text: t('polls', 'Delete poll as admin'),
+ action: this.deletePoll
+ })
+ }
+
+ return items
+ }
+ },
+
+ methods: {
+ toggleMenu() {
+ this.openedMenu = !this.openedMenu
+ },
+
+ hideMenu() {
+ this.openedMenu = false
+ },
+
+ copyLink() {
+ // this.$emit('copyLink')
+ this.$copyText(window.location.origin + this.voteUrl).then(
+ function(e) {
+ OC.Notification.showTemporary(t('polls', 'Link copied to clipboard'), { type: 'success' })
+ },
+ function(e) {
+ OC.Notification.showTemporary(t('polls', 'Error, while copying link to clipboard'), { type: 'error' })
+ }
+ )
+ this.hideMenu()
+ },
+
+ deletePoll() {
+ this.$emit('deletePoll')
+ this.hideMenu()
+ },
+
+ votePoll() {
+ this.$emit('votePoll')
+ this.hideMenu()
+ },
+
+ editPoll() {
+ this.$emit('editPoll')
+ this.hideMenu()
+ },
+
+ clonePoll() {
+ this.$emit('clonePoll')
+ this.hideMenu()
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.pollListItem {
+ display: flex;
+ flex: 1;
+ padding-left: 8px;
+ &.header {
+ opacity: 0.5;
+ flex: auto;
+ height: 4em;
+ align-items: center;
+ margin-left: 44px;
+ }
+ &> div {
+ padding-right: 8px;
+ }
+}
+
+.thumbnail {
+ flex: 0 0 auto;
+}
+
+.icon-more {
+ right: 14px;
+ opacity: 0.3;
+ cursor: pointer;
+ height: 44px;
+ width: 44px;
+}
+
+.title {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ width: 210px;
+ flex: 1 1 auto;
+ .name,
+ .description {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .description {
+ opacity: 0.5;
+ }
+}
+
+.thumbnail.access, .owner {
+ flex: 0 0 auto;
+}
+
+.thumbnail.access {
+ width: 75px;
+}
+
+.owner {
+ width: 130px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.actions {
+ width: 44px;
+ align-items: center;
+ position: relative;
+}
+
+.dates {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+
+ .created, .expiry {
+ width: 100px;
+ flex: 1 1;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+}
+
+ .thumbnail {
+ width: 44px;
+ height: 44px;
+ padding-right: 4px;
+ font-size: 0;
+ background-color: var(--color-text-light);
+ &.datePoll {
+ mask-image: var(--icon-calendar-000) no-repeat 50% 50%;
+ -webkit-mask: var(--icon-calendar-000) no-repeat 50% 50%;
+ mask-size: 16px;
+ }
+ &.textPoll {
+ mask-image: var(--icon-organization-000) no-repeat 50% 50%;
+ -webkit-mask: var(--icon-organization-000) no-repeat 50% 50%;
+ mask-size: 16px;
+ }
+ &.expired {
+ background-color: var(--color-background-darker);
+ }
+ &.access {
+ display: inherit;
+ &.hidden {
+ mask-image: var(--icon-password-000) no-repeat 50% 50%;
+ -webkit-mask: var(--icon-password-000) no-repeat 50% 50%;
+ mask-size: 16px;
+ }
+ &.public {
+ mask-image: var(--icon-link-000) no-repeat 50% 50%;
+ -webkit-mask: var(--icon-link-000) no-repeat 50% 50%;
+ mask-size: 16px;
+ }
+ &.select {
+ mask-image: var(--icon-share-000) no-repeat 50% 50%;
+ -webkit-mask: var(--icon-share-000) no-repeat 50% 50%;
+ mask-size: 16px;
+ }
+ &.registered {
+ mask-image: var(--icon-group-000) no-repeat 50% 50%;
+ -webkit-mask: var(--icon-group-000) no-repeat 50% 50%;
+ mask-size: 16px;
+ }
+ }
+ }
+
+ .icon-voted {
+ background-image: var(--icon-checkmark-fff);
+ }
+
+ .comment-badge {
+ position: absolute;
+ top: 0;
+ width: 26px;
+ line-height: 26px;
+ text-align: center;
+ font-size: 0.7rem;
+ color: white;
+ background-image: var(--icon-comment-49bc49);
+ background-repeat: no-repeat;
+ background-size: 26px;
+ z-index: 1;
+ }
+
+ .app-navigation-entry-utils-counter {
+ padding-right: 0 !important;
+ overflow: hidden;
+ text-align: right;
+ font-size: 9pt;
+ line-height: 44px;
+ padding: 0 12px;
+ // min-width: 25px;
+ &.highlighted {
+ padding: 0;
+ text-align: center;
+ span {
+ padding: 2px 5px;
+ border-radius: 10px;
+ background-color: var(--color-primary);
+ color: var(--color-primary-text);
+ }
+ }
+ }
+
+ .symbol.icon-voted {
+ position: absolute;
+ left: 11px;
+ top: 16px;
+ background-size: 0;
+ min-width: 8px;
+ min-height: 8px;
+ background-color: var(--color-success);
+ border-radius: 50%;
+ }
+
+ @media all and (max-width: (740px)) {
+ .dates {
+ flex-direction: column;
+ }
+ }
+
+ @media all and (max-width: (620px)) {
+ .owner {
+ display: none;
+ }
+ }
+
+ @media all and (max-width: (490px)) {
+ .dates {
+ display: none;
+ }
+ }
+
+ @media all and (max-width: (380px)) {
+ .thumbnail.access, .access {
+ width: 140px;
+ display: none;
+ }
+ }
+
+</style>
diff --git a/src/js/components/Share/ShareDiv.vue b/src/js/components/Share/ShareDiv.vue
new file mode 100644
index 00000000..f9fa2e1b
--- /dev/null
+++ b/src/js/components/Share/ShareDiv.vue
@@ -0,0 +1,172 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <h2> {{ t('polls', 'Share with') }}</h2>
+
+ <Multiselect id="ajax"
+ v-model="shares"
+ :options="users"
+ :multiple="true"
+ :user-select="true"
+ :tag-width="80"
+ :clear-on-select="false"
+ :preserve-search="true"
+ :options-limit="20"
+ :loading="isLoading"
+ :internal-search="false"
+ :searchable="true"
+ :preselect-first="true"
+ :placeholder="placeholder"
+ label="displayName"
+ track-by="user"
+ @search-change="loadUsersAsync"
+ @close="updateShares">
+ <template slot="selection" slot-scope="{ values, search, isOpen }">
+ <span v-if="values.length &amp;&amp; !isOpen" class="multiselect__single">
+ {{ values.length }} users selected
+ </span>
+ </template>
+ </Multiselect>
+
+ <TransitionGroup :css="false" tag="ul" class="shared-list">
+ <li v-for="(item, index) in sortedShares" :key="item.displayName" :data-index="index">
+ <UserDiv :user-id="item.user" :display-name="item.displayName" :type="item.type"
+ :hide-names="hideNames" />
+ <div class="options">
+ <a class="icon icon-delete svg delete-poll" @click="removeShare(index, item)" />
+ </div>
+ </li>
+ </TransitionGroup>
+ </div>
+</template>
+
+<script>
+import { Multiselect } from '@nextcloud/vue'
+
+export default {
+ name: 'ShareDiv',
+
+ components: {
+ Multiselect
+ },
+
+ props: {
+ placeholder: {
+ type: String,
+ default: ''
+ },
+
+ activeShares: {
+ type: Array,
+ default: function() {
+ return []
+ }
+ },
+
+ hideNames: {
+ type: Boolean,
+ default: false
+ }
+ },
+
+ data() {
+ return {
+ shares: [],
+ users: [],
+ isLoading: false,
+ siteUsersListOptions: {
+ getUsers: true,
+ getGroups: true,
+ query: ''
+ }
+ }
+ },
+
+ computed: {
+ sortedShares() {
+ return this.shares.slice(0).sort(this.sortByDisplayname)
+ }
+ },
+
+ watch: {
+ activeShares(value) {
+ this.shares = value.slice(0)
+ }
+ },
+
+ methods: {
+ removeShare(index, item) {
+ this.$emit('remove-share', item)
+ },
+
+ updateShares() {
+ this.$emit('update-shares', this.shares)
+ },
+
+ loadUsersAsync(query) {
+ this.isLoading = false
+ this.siteUsersListOptions.query = query
+ this.$http.post(OC.generateUrl('apps/polls/get/siteusers'), this.siteUsersListOptions)
+ .then((response) => {
+ this.users = response.data.siteusers
+ this.isLoading = false
+ }, (error) => {
+ console.error(error.response)
+ })
+ },
+
+ sortByDisplayname(a, b) {
+ if (a.displayName.toLowerCase() < b.displayName.toLowerCase()) return -1
+ if (a.displayName.toLowerCase() > b.displayName.toLowerCase()) return 1
+ return 0
+ }
+
+ }
+}
+</script>
+
+<style lang="scss">
+ .shared-list {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ padding-top: 8px;
+
+ > li {
+ display: flex;
+ }
+ }
+
+ .options {
+ display: flex;
+ position: relative;
+ top: -12px;
+ left: -13px;
+ }
+
+ .multiselect {
+ width: 100% !important;
+ max-width: 100% !important;
+ }
+</style>
diff --git a/src/js/components/SideBar/CommentAdd.vue b/src/js/components/SideBar/CommentAdd.vue
new file mode 100644
index 00000000..2d7f054a
--- /dev/null
+++ b/src/js/components/SideBar/CommentAdd.vue
@@ -0,0 +1,99 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 lang="html">
+ <div class="newCommentRow comment new-comment">
+ <user-div :user-id="currentUser" />
+
+ <form class="commentAdd" name="send_comment">
+ <input v-model="comment" class="message" data-placeholder="New Comment ...">
+ <input v-show="!loading" class="submitComment icon-confirm" @click="writeComment">
+ <span v-show="loading" class="icon-loading-small" style="float:right;" />
+ </form>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'AddComment',
+ data() {
+ return {
+ comment: ''
+ }
+ },
+
+ computed: {
+ currentUser() {
+ return OC.getCurrentUser().uid
+ }
+ },
+
+ methods: {
+ writeComment() {
+ this.$store.dispatch('writeCommentPromise', this.comment)
+ .then(response => {
+ OC.Notification.showTemporary(t('polls', 'Your comment was added'), { type: 'success' })
+ })
+ .catch(error => {
+ this.writingVote = false
+ console.error('Error while saving comment - Error: ', error.response)
+ OC.Notification.showTemporary(t('polls', 'Error while saving comment'), { type: 'error' })
+ })
+
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+ .comment {
+ margin-bottom: 30px;
+ }
+
+ .commentAdd {
+ display: flex;
+ }
+
+ .message {
+ margin-left: 40px;
+ flex: 1;
+ &:empty:before {
+ content: attr(data-placeholder);
+ color: grey;
+ }
+ }
+ .submitComment {
+ align-self: last baseline;
+ width: 30px;
+ margin: 0;
+ padding: 7px 9px;
+ background-color: transparent;
+ border: none;
+ opacity: 0.3;
+ cursor: pointer;
+ }
+
+ .icon-loading-small {
+ float: left;
+ margin-top: 10px;
+ }
+</style>
diff --git a/src/js/components/SideBar/SideBar.vue b/src/js/components/SideBar/SideBar.vue
new file mode 100644
index 00000000..30931f01
--- /dev/null
+++ b/src/js/components/SideBar/SideBar.vue
@@ -0,0 +1,109 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <AppSidebar :active="initialTab" :title="t('polls', 'Details')" @close="$emit('closeSideBar')">
+ <UserDiv slot="primary-actions" :user-id="event.owner" :description="t('polls', 'Owner')" />
+
+ <AppSidebarTab :name="t('polls', 'Comments')" icon="icon-comment">
+ <SideBarTabComments />
+ </AppSidebarTab>
+
+ <AppSidebarTab v-if="acl.allowEdit && event.type === 'datePoll'" :name="t('polls', 'Date options')" icon="icon-calendar">
+ <SideBarTabDateOptions />
+ </AppSidebarTab>
+
+ <AppSidebarTab v-if="acl.allowEdit && event.type === 'textPoll'" :name="t('polls', 'Text options')" icon="icon-toggle-filelist">
+ <SideBarTabTextOptions />
+ </AppSidebarTab>
+
+ <AppSidebarTab v-if="acl.allowEdit" :name="t('polls', 'Configuration')" icon="icon-settings">
+ <SideBarTabConfiguration @deletePoll="$emit('deletePoll')" />
+ </AppSidebarTab>
+
+ <AppSidebarTab v-if="acl.allowEdit" :name="t('polls', 'Shares')" icon="icon-share">
+ <SideBarTabShare />
+ </AppSidebarTab>
+ </AppSidebar>
+</template>
+
+<script>
+import { AppSidebar, AppSidebarTab } from '@nextcloud/vue'
+
+import SideBarTabConfiguration from './SideBarTabConfiguration'
+import SideBarTabDateOptions from './SideBarTabDateOptions'
+import SideBarTabTextOptions from './SideBarTabTextOptions'
+import SideBarTabComments from './SideBarTabComments'
+import SideBarTabShare from './SideBarTabShare'
+import { mapState } from 'vuex'
+
+export default {
+ name: 'SideBar',
+ components: {
+ SideBarTabConfiguration,
+ SideBarTabComments,
+ SideBarTabDateOptions,
+ SideBarTabTextOptions,
+ SideBarTabShare,
+ AppSidebar,
+ AppSidebarTab
+ },
+
+ data() {
+ return {
+ initialTab: 'comments'
+ }
+ },
+
+ computed: {
+ ...mapState({
+ event: state => state.event,
+ acl: state => state.acl
+ })
+ }
+
+}
+
+</script>
+
+<style scoped lang="scss">
+
+ ul {
+ & > li {
+ margin-bottom: 30px;
+ & > .comment-item {
+ display: flex;
+ align-items: center;
+
+ & > .date {
+ right: 0;
+ top: 5px;
+ opacity: 0.5;
+ }
+ }
+ & > .message {
+ margin-left: 44px;
+ flex: 1 1;
+ }
+ }
+ }
+</style>
diff --git a/src/js/components/SideBar/SideBarTabComments.vue b/src/js/components/SideBar/SideBarTabComments.vue
new file mode 100644
index 00000000..1d4c9fb4
--- /dev/null
+++ b/src/js/components/SideBar/SideBarTabComments.vue
@@ -0,0 +1,98 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <CommentAdd />
+ <transition-group v-if="countComments" name="fade" class="comments"
+ tag="ul">
+ <li v-for="(comment) in sortedComments" :key="comment.id">
+ <div class="comment-item">
+ <user-div :user-id="comment.userId" />
+ <div class="date">
+ {{ realtiveDate(comment.date) }}
+ </div>
+ </div>
+ <div class="message wordwrap comment-content">
+ {{ comment.comment }}
+ </div>
+ </li>
+ </transition-group>
+ <div v-else class="emptycontent">
+ <div class="icon-comment" />
+ <p> {{ t('polls', 'No comments yet. Be the first.') }}</p>
+ </div>
+ </div>
+</template>
+
+<script>
+import moment from 'moment'
+import CommentAdd from './CommentAdd'
+import { mapState, mapGetters } from 'vuex'
+
+export default {
+ name: 'SideBarTabComments',
+ components: {
+ CommentAdd
+ },
+
+ computed: {
+ ...mapState({
+ comments: state => state.comments
+ }),
+ ...mapGetters([
+ 'countComments',
+ 'sortedComments'
+ ])
+ },
+
+ methods: {
+ realtiveDate(date) {
+ return t('core', moment.utc(date).fromNow())
+ }
+
+ }
+}
+</script>
+
+<style scoped lang="scss">
+
+ ul {
+ & > li {
+ margin-bottom: 30px;
+ & > .comment-item {
+ display: flex;
+ align-items: center;
+
+ & > .date {
+ right: 0;
+ top: 5px;
+ opacity: 0.5;
+ }
+ }
+ & > .message {
+ margin-left: 44px;
+ flex: 1 1;
+ }
+ }
+ }
+</style>
diff --git a/src/js/components/SideBar/SideBarTabConfiguration.vue b/src/js/components/SideBar/SideBarTabConfiguration.vue
new file mode 100644
index 00000000..1e135af1
--- /dev/null
+++ b/src/js/components/SideBar/SideBarTabConfiguration.vue
@@ -0,0 +1,344 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <div class="configBox">
+ <label v-if="writingPoll" class="icon-loading-small title">
+ {{ t('polls', 'Saving') }}
+ </label>
+ <label v-else class="icon-checkmark title">
+ {{ t('polls', 'Saved') }}
+ </label>
+ </div>
+
+ <div v-if="acl.allowEdit" class="configBox">
+ <label class="icon-sound title">
+ {{ t('polls', 'Title') }}
+ </label>
+ <input v-model="eventTitle" :class="{ error: titleEmpty }" type="text">
+ </div>
+
+ <div v-if="acl.allowEdit" class="configBox">
+ <label class="icon-edit title">
+ {{ t('polls', 'Description') }}
+ </label>
+ <textarea v-model="eventDescription" />
+ <!-- <textarea v-if="acl.allowEdit" :value="event.description" @input="updateDescription" /> -->
+ </div>
+
+ <div class="configBox">
+ <label class="title icon-category-customization">
+ {{ t('polls', 'Poll configurations') }}
+ </label>
+
+ <input id="allowMaybe"
+ v-model="eventAllowMaybe"
+ :disabled="!acl.allowEdit"
+ type="checkbox"
+ class="checkbox">
+ <label for="allowMaybe" class="title">
+ {{ t('polls', 'Allow "maybe" vote') }}
+ </label>
+
+ <input id="anonymous" v-model="eventIsAnonymous"
+ :disabled="!acl.allowEdit"
+ type="checkbox"
+ class="checkbox">
+ <label for="anonymous" class="title">
+ {{ t('polls', 'Anonymous poll') }}
+ </label>
+
+ <input v-show="event.isAnonymous"
+ id="trueAnonymous"
+ v-model="eventFullAnonymous"
+ :disabled="!acl.allowEdit"
+ type="checkbox"
+ class="checkbox">
+ <label v-show="event.isAnonymous" class="title" for="trueAnonymous">
+ {{ t('polls', 'Hide user names for admin') }}
+ </label>
+
+ <input id="expiration"
+ v-model="eventExpiration"
+ :disabled="!acl.allowEdit"
+ type="checkbox"
+ class="checkbox">
+ <label class="title" for="expirtion">
+ {{ t('polls', 'Expires') }}
+ </label>
+
+ <date-picker v-show="event.expire"
+ v-model="eventExpiration"
+ v-bind="expirationDatePicker"
+ :disabled="!acl.allowEdit"
+ :time-picker-options="{ start: '00:00', step: '00:05', end: '23:55' }"
+ style="width:170px" />
+ </div>
+
+ <div class="configBox">
+ <label class="title icon-category-auth">
+ {{ t('polls', 'Access') }}
+ </label>
+
+ <input id="hidden"
+ v-model="eventAccess"
+ :disabled="!acl.allowEdit"
+ type="radio"
+ value="hidden"
+ class="radio">
+ <label for="hidden" class="title">
+ <div class="title icon-category-security" />
+ <span>{{ t('polls', 'Hidden to other users') }}</span>
+ </label>
+
+ <input id="public"
+ v-model="eventAccess"
+ :disabled="!acl.allowEdit"
+ type="radio"
+ value="public"
+ class="radio">
+ <label for="public" class="title">
+ <div class="title icon-link" />
+ <span>{{ t('polls', 'Visible to other users') }}</span>
+ </label>
+ </div>
+
+ <button class="button btn primary" @click="$emit('deletePoll')">
+ <span>{{ t('polls', 'Delete this poll') }}</span>
+ </button>
+ </div>
+</template>
+
+<script>
+import debounce from 'lodash/debounce'
+import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
+
+export default {
+ name: 'SideBarTab',
+
+ data() {
+ return {
+ writingPoll: false,
+ sidebar: false,
+ titleEmpty: false
+ }
+ },
+
+ computed: {
+ ...mapState({
+ event: state => state.event,
+ acl: state => state.acl
+ }),
+
+ ...mapGetters([
+ 'languageCodeShort'
+ ]),
+
+ // Add bindings
+ eventDescription: {
+ get() {
+ return this.event.description
+ },
+ set(value) {
+ this.writeValueDebounced({ 'description': value })
+ }
+ },
+
+ eventTitle: {
+ get() {
+ return this.event.title
+ },
+ set(value) {
+ this.writeValueDebounced({ 'title': value })
+ }
+ },
+
+ eventAccess: {
+ get() {
+ return this.event.access
+ },
+ set(value) {
+ this.writeValue({ 'access': value })
+ }
+ },
+
+ eventExpiration: {
+ get() {
+ return this.event.expire
+ },
+ set(value) {
+ this.writeValue({ 'expire': value })
+ }
+ },
+
+ eventFullAnonymous: {
+ get() {
+ return this.event.fullAnonymous
+ },
+ set(value) {
+ this.writeValue({ 'fullAnonymous': value })
+ }
+ },
+
+ eventIsAnonymous: {
+ get() {
+ return this.event.isAnonymous
+ },
+ set(value) {
+ this.writeValue({ 'isAnonymous': value })
+ }
+ },
+
+ eventAllowMaybe: {
+ get() {
+ return this.event.allowMaybe
+ },
+ set(value) {
+ this.writeValue({ 'allowMaybe': value })
+ }
+ },
+
+ // eventExpiration: {
+ // get() {
+ // return this.$store.state.event.expiration
+ // },
+ // set(value) {
+ // this.writeValue({ 'expiration': value })
+ // }
+ // },
+
+ expirationDatePicker() {
+ return {
+ editable: true,
+ minuteStep: 1,
+ type: 'datetime',
+ format: this.dateTimeFormat,
+ lang: this.langShort,
+ placeholder: t('polls', 'Expiration date'),
+ timePickerOptions: {
+ start: '00:00',
+ step: '00:30',
+ end: '23:30'
+ }
+ }
+ },
+
+ optionDatePicker() {
+ return {
+ editable: false,
+ minuteStep: 1,
+ type: 'datetime',
+ format: this.dateTimeFormat,
+ lang: this.languageCodeShort,
+ placeholder: t('polls', 'Click to add a date'),
+ timePickerOptions: {
+ start: '00:00',
+ step: '00:30',
+ end: '23:30'
+ }
+ }
+ },
+
+ protect: function() {
+ return this.poll.mode === 'vote'
+ },
+
+ saveButtonTitle: function() {
+ if (this.writingPoll) {
+ return t('polls', 'Writing poll')
+ } else if (this.acl.allowEdit) {
+ return t('polls', 'Update poll')
+ } else {
+ return t('polls', 'Create new poll')
+ }
+ }
+ },
+ methods: {
+
+ ...mapMutations([ 'setEventProperty' ]),
+ ...mapActions([ 'writeEventPromise' ]),
+
+ writeValueDebounced: debounce(function(e) {
+ this.writeValue(e)
+ }, 1500),
+
+ writeValue(e) {
+ this.$store.commit('setEventProperty', e)
+ this.writingPoll = true
+ this.writePoll()
+ },
+
+ writePoll() {
+ if (this.titleEmpty) {
+ OC.Notification.showTemporary(t('polls', 'Title must not be empty!'), { type: 'success' })
+ } else {
+ this.writeEventPromise()
+ this.writingPoll = false
+ OC.Notification.showTemporary(t('polls', '%n successfully saved', 1, this.event.title), { type: 'success' })
+ }
+ },
+
+ write() {
+ if (this.acl.allowEdit) {
+ this.writePoll()
+ }
+
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+ .configBox {
+ display: flex;
+ flex-direction: column;
+ padding: 8px;
+ & > * {
+ padding-left: 21px;
+ }
+
+ & > input {
+ margin-left: 24px;
+ width: auto;
+
+ }
+
+ & > textarea {
+ margin-left: 24px;
+ width: auto;
+ padding: 7px 6px;
+ }
+
+ & > .title {
+ display: flex;
+ background-position: 0 2px;
+ padding-left: 24px;
+ opacity: 0.7;
+ font-weight: bold;
+ margin-bottom: 4px;
+ & > span {
+ padding-left: 4px;
+ }
+ }
+ }
+</style>
diff --git a/src/js/components/SideBar/SideBarTabDateOptions.vue b/src/js/components/SideBar/SideBarTabDateOptions.vue
new file mode 100644
index 00000000..c99b129b
--- /dev/null
+++ b/src/js/components/SideBar/SideBarTabDateOptions.vue
@@ -0,0 +1,173 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <div class="configBox">
+ <label class="title icon-calendar">
+ {{ t('polls', 'Add a date option') }}
+ </label>
+ <DatePicker v-bind="optionDatePicker" style="width:100%" confirm
+ @change="addOption($event)" />
+ </div>
+
+ <div class="configBox">
+ <label class="title icon-history">
+ {{ t('polls', 'Shift all date options') }}
+ </label>
+ <div>
+ <div class="selectUnit">
+ <input v-model="move.step">
+ <Multiselect v-model="move.unit" :options="move.units" />
+ </div>
+ </div>
+ <div>
+ <button class="button btn primary" @click="shiftDates(move)">
+ <span>{{ t('polls', 'Shift') }}</span>
+ </button>
+ </div>
+ </div>
+
+ <ul class="configBox poll-table">
+ <label class="title icon-calendar">
+ {{ t('polls', 'Available Options') }}
+ </label>
+ <DatePollItem v-for="(option) in sortedOptions"
+ :key="option.id"
+ :option="option"
+ @remove="removeOption(option)" />
+ </ul>
+ </div>
+</template>
+
+<script>
+import { Multiselect } from '@nextcloud/vue'
+import moment from 'moment'
+import { mapGetters, mapState } from 'vuex'
+import DatePollItem from '../Create/CreateDateItem'
+
+export default {
+ name: 'SideBarTabDateOptions',
+
+ components: {
+ Multiselect,
+ DatePollItem
+
+ },
+
+ data() {
+ return {
+ move: {
+ step: 1,
+ unit: 'week',
+ units: ['minute', 'hour', 'day', 'week', 'month', 'year']
+ }
+ }
+ },
+
+ computed: {
+ ...mapState({
+ options: state => state.options
+ }),
+
+ ...mapGetters([ 'languageCodeShort', 'sortedOptions' ]),
+
+ optionDatePicker() {
+ return {
+ editable: false,
+ minuteStep: 1,
+ type: 'datetime',
+ format: this.dateTimeFormat,
+ lang: this.languageCodeShort,
+ placeholder: t('polls', 'Click to add a date'),
+ timePickerOptions: {
+ start: '00:00',
+ step: '00:30',
+ end: '23:30'
+ }
+ }
+ }
+ },
+
+ methods: {
+
+ addOption(pollOptionText) {
+ this.$store.dispatch('addOptionAsync', { pollOptionText: pollOptionText })
+ },
+
+ shiftDates(payload) {
+ let store = this.$store
+ this.options.list.forEach(function(existingOption) {
+ let option = Object.assign({}, existingOption)
+ option.pollOptionText = moment(option.pollOptionText).add(payload.step, payload.unit).format('YYYY-MM-DD HH:mm:ss')
+ option.timestamp = moment.utc(option.pollOptionText).unix()
+ store.dispatch('updateOptionAsync', { option: option })
+ })
+ },
+
+ removeOption(option) {
+ this.$store.dispatch('removeOptionAsync', { option: option })
+ }
+
+ }
+
+}
+</script>
+
+<style lang="scss">
+ .configBox {
+ display: flex;
+ flex-direction: column;
+ padding: 8px;
+ & > * {
+ padding-left: 21px;
+ }
+
+ & > input {
+ margin-left: 24px;
+ width: auto;
+
+ }
+
+ & > textarea {
+ margin-left: 24px;
+ width: auto;
+ padding: 7px 6px;
+ }
+
+ & > .title {
+ display: flex;
+ background-position: 0 2px;
+ padding-left: 24px;
+ opacity: 0.7;
+ font-weight: bold;
+ margin-bottom: 4px;
+ & > span {
+ padding-left: 4px;
+ }
+ }
+ &.poll-table > li {
+ border-bottom-color: rgb(72, 72, 72);
+ margin-left: 18px;
+ }
+ }
+</style>
diff --git a/src/js/components/SideBar/SideBarTabInformation.vue b/src/js/components/SideBar/SideBarTabInformation.vue
new file mode 100644
index 00000000..edffcaaf
--- /dev/null
+++ b/src/js/components/SideBar/SideBarTabInformation.vue
@@ -0,0 +1,58 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <user-div :user-id="event.owner" :description="t('polls', 'Owner')" />
+ <div>{{ accessType }}</div>
+ <h3> {{ t('polls', 'Created') }} </h3>
+ <div>{{ timeSpanCreated }}</div>
+ <h3> {{ t('polls', 'Expires') }} </h3>
+ <div>{{ timeSpanExpiration }}</div>
+ <div>{{ countCommentsHint }}</div>
+ </div>
+</template>
+
+<script>
+import { mapState, mapGetters } from 'vuex'
+
+export default {
+ name: 'SideBarTabInformationTab',
+ computed: {
+ ...mapState({
+ event: state => state.event
+ }),
+
+ ...mapGetters([
+ 'accessType',
+ 'countComments',
+ 'timeSpanCreated',
+ 'timeSpanExpiration'
+ ]),
+
+ countCommentsHint: function() {
+ return n('polls', 'There is %n comment', 'There are %n comments', this.countComments)
+ }
+ }
+}
+
+</script>
diff --git a/src/js/components/SideBar/SideBarTabShare.vue b/src/js/components/SideBar/SideBarTabShare.vue
new file mode 100644
index 00000000..080be7db
--- /dev/null
+++ b/src/js/components/SideBar/SideBarTabShare.vue
@@ -0,0 +1,211 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <h3>{{ t('polls','Invitations') }}</h3>
+ <TransitionGroup :css="false" tag="ul" class="shared-list">
+ <li v-for="(share) in invitationShares" :key="share.id">
+ <UserDiv :user-id="resolveShareUser(share)" :type="share.type" :icon="true" />
+ <div class="options">
+ <a class="icon icon-clippy" @click="copyLink( { url: OC.generateUrl('apps/polls/s/') + share.token } )" />
+ <a class="icon icon-delete svg delete-poll" @click="removeShare(share)" />
+ </div>
+ </li>
+ </TransitionGroup>
+
+ <Multiselect id="ajax"
+ :options="users"
+ :multiple="false"
+ :user-select="true"
+ :tag-width="80"
+ :clear-on-select="false"
+ :preserve-search="true"
+ :options-limit="20"
+ :loading="isLoading"
+ :internal-search="false"
+ :searchable="true"
+ :preselect-first="true"
+ :placeholder="placeholder"
+ label="displayName"
+ track-by="user"
+ @select="addShare"
+ @search-change="loadUsersAsync">
+ <template slot="selection" slot-scope="{ values, search, isOpen }">
+ <span v-if="values.length &amp;&amp; !isOpen" class="multiselect__single">
+ {{ values.length }} users selected
+ </span>
+ </template>
+ </Multiselect>
+
+ <h3>{{ t('polls','Public shares') }}</h3>
+ <TransitionGroup :css="false" tag="ul" class="shared-list">
+ <li v-for="(share) in publicShares" :key="share.id">
+ <div class="user-row user">
+ <div class="avatar icon-public" />
+ <div class="user-name">
+ {{ t('polls', 'Share Link') }}
+ </div>
+ </div>
+ <div class="options">
+ <a class="icon icon-clippy" @click="copyLink( { url: OC.generateUrl('apps/polls/s/') + share.token } )" />
+ <a class="icon icon-delete" @click="removeShare(share)" />
+ </div>
+ </li>
+ </TransitionGroup>
+ <div class="user-row user" @click="addShare({type: 'public', user: ''})">
+ <div class="avatar icon-add" />
+ <div class="user-name">
+ {{ t('polls', 'Add a public link') }}
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+import { Multiselect } from '@nextcloud/vue'
+import { mapGetters } from 'vuex'
+
+export default {
+ name: 'SideBarTabShare',
+
+ components: {
+ Multiselect
+ },
+
+ data() {
+ return {
+ users: [],
+ invitations: [],
+ invitation: {},
+ isLoading: false,
+ siteUsersListOptions: {
+ getUsers: true,
+ getGroups: true,
+ query: ''
+ }
+ }
+ },
+
+ computed: {
+ ...mapGetters([
+ 'countShares',
+ 'sortedShares',
+ 'invitationShares',
+ 'publicShares'
+ ])
+ },
+
+ methods: {
+ loadUsersAsync(query) {
+ this.isLoading = false
+ this.siteUsersListOptions.query = query
+ this.$http.post(OC.generateUrl('apps/polls/get/siteusers'), this.siteUsersListOptions)
+ .then((response) => {
+ this.users = response.data.siteusers
+ this.isLoading = false
+ }, (error) => {
+ console.error(error.response)
+ })
+ },
+
+ copyLink(payload) {
+ this.$copyText(window.location.origin + payload.url).then(
+ function(e) {
+ OC.Notification.showTemporary(t('polls', 'Link copied to clipboard'), { type: 'success' })
+ },
+ function(e) {
+ OC.Notification.showTemporary(t('polls', 'Error while copying link to clipboard'), { type: 'error' })
+ }
+ )
+ },
+
+ resolveShareUser(share) {
+ if (share.userId !== '' && share.userId !== null) {
+ return share.userId
+ } else if (share.type === 'mail') {
+ return share.userEmail
+ } else {
+ return t('polls', 'Unknown user')
+ }
+
+ },
+
+ removeShare(share) {
+ this.$store.dispatch('removeShareAsync', { share: share })
+ },
+
+ addShare(payload) {
+ this.$store.dispatch('writeSharePromise', {
+ 'share': {
+ 'type': payload.type,
+ 'userId': payload.user,
+ 'pollId': '0',
+ 'userEmail': '',
+ 'token': ''
+ }
+ })
+ // .then(response => {
+ // OC.Notification.showTemporary(t('polls', 'You added %n.', 1, payload.user), { type: 'success' })
+ // })
+ .catch(error => {
+ console.error('Error while adding share comment - Error: ', error)
+ OC.Notification.showTemporary(t('polls', 'Error while adding share'), { type: 'error' })
+ })
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+ .shared-list {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: column;
+ justify-content: flex-start;
+ padding-top: 8px;
+
+ > li {
+ display: flex;
+ align-items: stretch;
+ margin: 4px 0;
+ }
+ }
+
+ .options {
+ display: flex;
+
+ .icon:not(.hidden) {
+ padding: 14px;
+ height: 44px;
+ width: 44px;
+ opacity: .5;
+ display: block;
+ cursor: pointer;
+ }
+ }
+
+ .multiselect {
+ width: 100% !important;
+ max-width: 100% !important;
+ }
+</style>
diff --git a/src/js/components/SideBar/SideBarTabTextOptions.vue b/src/js/components/SideBar/SideBarTabTextOptions.vue
new file mode 100644
index 00000000..002d5d62
--- /dev/null
+++ b/src/js/components/SideBar/SideBarTabTextOptions.vue
@@ -0,0 +1,164 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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>
+ <div class="configBox">
+ <label class="title icon-toggle-filelist">
+ {{ t('polls', 'Add a new text option') }}
+ </label>
+ <input v-model="newPollText" :placeholder=" t('polls', 'Enter option text and press Enter') " @keyup.enter="addOption(newPollText)">
+ </div>
+
+ <ul class="configBox poll-table">
+ <label class="title icon-calendar">
+ {{ t('polls', 'Available Options') }}
+ </label>
+ <TextPollItem v-for="(option) in sortedOptions" :key="option.id" :option="option"
+ @remove="removeOption(option)" />
+ </ul>
+ </div>
+</template>
+
+<script>
+import { mapGetters, mapState } from 'vuex'
+import TextPollItem from '../Create/TextPollItem'
+export default {
+ name: 'SideBarTabTextOptions',
+
+ components: {
+ TextPollItem
+ },
+
+ data() {
+ return {
+ newPollText: ''
+ }
+ },
+
+ computed: {
+ ...mapState({
+ options: state => state.options
+ }),
+
+ ...mapGetters(['languageCodeShort', 'sortedOptions'])
+ },
+
+ methods: {
+
+ addOption(pollOptionText) {
+ if (pollOptionText !== '') {
+ this.$store.dispatch('addOptionAsync', {
+ pollOptionText: pollOptionText
+ })
+ this.newPollText = ''
+ }
+ },
+
+ removeOption(option) {
+ this.$store.dispatch('removeOptionAsync', {
+ option: option
+ })
+ }
+
+ }
+
+}
+</script>
+
+<style lang="scss">
+.configBox {
+ display: flex;
+ flex-direction: column;
+ padding: 8px;
+ & > * {
+ padding-left: 21px;
+ }
+
+ & > input {
+ margin-left: 24px;
+ width: auto;
+
+ }
+
+ & > textarea {
+ margin-left: 24px;
+ width: auto;
+ padding: 7px 6px;
+ }
+
+ & > .title {
+ display: flex;
+ background-position: 0 2px;
+ padding-left: 24px;
+ opacity: 0.7;
+ font-weight: bold;
+ margin-bottom: 4px;
+ & > span {
+ padding-left: 4px;
+ }
+ }
+
+ &.poll-table > li {
+ border-bottom-color: rgb(72, 72, 72);
+ margin-left: 18px;
+ }
+
+}
+
+.poll-table {
+ > li {
+ display: flex;
+ align-items: center;
+ padding-left: 8px;
+ padding-right: 8px;
+ line-height: 2em;
+ min-height: 4em;
+ border-bottom: 1px solid var(--color-border);
+ overflow: hidden;
+ white-space: nowrap;
+
+ &:active,
+ &:hover {
+ transition: var(--background-dark) 0.3s ease;
+ background-color: var(--color-background-dark); //$hover-color;
+ }
+
+ > div {
+ display: flex;
+ flex: 1;
+ font-size: 1.2em;
+ opacity: 0.7;
+ white-space: normal;
+ padding-right: 4px;
+ &.avatar {
+ flex: 0;
+ }
+ }
+
+ > div:nth-last-child(1) {
+ justify-content: center;
+ flex: 0 0;
+ }
+ }
+}
+</style>
diff --git a/src/js/components/VoteTable/VoteHeader.vue b/src/js/components/VoteTable/VoteHeader.vue
new file mode 100644
index 00000000..d8eb7fc7
--- /dev/null
+++ b/src/js/components/VoteTable/VoteHeader.vue
@@ -0,0 +1,77 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 class="voteHeader">
+ <h2>
+ {{ event.title }}
+ <span v-if="expired" class="label error">{{ t('polls', 'Expired since %n', 1, timeSpanExpiration) }}</span>
+ <span v-if="!expired && isExpirationSet" class="label success">{{ t('polls', 'Place your votes until %n', 1, event.expire) }}</span>
+ <span v-if="!isExpirationSet" class="label success">{{ t('polls', 'No expiration date set') }}</span>
+ </h2>
+ <h3>
+ {{ event.description }}
+ </h3>
+ </div>
+</template>
+
+<script>
+import { mapState, mapGetters } from 'vuex'
+
+export default {
+ name: 'VoteHeader',
+
+ data() {
+ return {
+ voteSaved: false,
+ delay: 50,
+ newName: ''
+ }
+ },
+
+ computed: {
+ ...mapState({
+ event: state => state.event
+ }),
+
+ ...mapGetters([
+ 'isExpirationSet',
+ 'expired',
+ 'timeSpanExpiration'
+ ])
+
+ },
+
+ methods: {
+ indicateVoteSaved() {
+ this.voteSaved = true
+ window.setTimeout(this.timer, this.delay)
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+ .voteHeader {
+ margin: 8px 24px;
+ }
+</style>
diff --git a/src/js/components/VoteTable/VoteHeaderPublic.vue b/src/js/components/VoteTable/VoteHeaderPublic.vue
new file mode 100644
index 00000000..4142fbbd
--- /dev/null
+++ b/src/js/components/VoteTable/VoteHeaderPublic.vue
@@ -0,0 +1,221 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 class="voteHeader">
+ <div v-if="!isValidUser" class="getUsername">
+ <!-- <label>
+ {{ t('polls', 'Enter a valid username, to participate in this poll.') }}
+ </label> -->
+
+ <form v-if="!redirecting">
+ <input v-model="userName" :class="{ error: (!isValidName && userName.length > 0), success: isValidName }" type="text"
+ :placeholder="t('polls', 'Enter a valid username with at least 3 Characters')">
+ <input v-show="isValidName && !checkingUserName" class="icon-confirm" :class="{ error: !isValidName, success: isValidName }"
+ @click="writeUserName">
+ <span v-show="checkingUserName" class="icon-loading-small" />
+ <!-- <span v-if="!isValidName" class="error"> {{ invalidUserNameMessage }} </span> -->
+ </form>
+ <div v-else>
+ <span>{{ t('polls', 'You will be redirected to your personal share.') }}</span>
+ <span>
+ {{ t('polls', 'If you are not redirected to your poll click this link:') }}
+ <router-link :to="{ name: 'publicVote', params: { token: token }}">
+ Link
+ </router-link>
+ </span>
+ </div>
+ </div>
+ <div v-if="displayLink" class="personal-link">
+ {{ t('polls', 'Your personal link to this poll: %n', 1, personalLink) }}
+ <a class="icon icon-clippy" @click="copyLink( { url: OC.generateUrl($route.path) } )" />
+ </div>
+ </div>
+</template>
+
+<script>
+import debounce from 'lodash/debounce'
+import axios from '@nextcloud/axios'
+import { mapState } from 'vuex'
+
+export default {
+ name: 'VoteHeaderPublic',
+
+ data() {
+ return {
+ userName: '',
+ token: '',
+ checkingUserName: false,
+ redirecting: false,
+ isValidName: false,
+ newName: ''
+ }
+ },
+
+ computed: {
+ ...mapState({
+ event: state => state.event,
+ acl: state => state.acl
+ }),
+
+ personalLink() {
+ return location.protocol.concat('//', window.location.hostname, OC.generateUrl(this.$route.path))
+ },
+
+ displayLink() {
+ return (this.acl.userId !== '' && this.acl.userId !== null && this.acl.foundByToken)
+ },
+
+ isValidUser() {
+ return (this.acl.userId !== '' && this.acl.userId !== null)
+ }
+
+ },
+
+ watch: {
+ userName: function() {
+ if (this.userName.length > 2) {
+ this.isValidName = this.validatePublicUsername()
+ } else {
+ this.invalidUserNameMessage = t('polls', 'Please use at least 3 characters for your user name!')
+ this.isValidName = false
+ }
+ }
+ },
+
+ methods: {
+ copyLink(payload) {
+ this.$copyText(window.location.origin + payload.url).then(
+ function(e) {
+ OC.Notification.showTemporary(t('polls', 'Link copied to clipboard'), { type: 'success' })
+ },
+ function(e) {
+ OC.Notification.showTemporary(t('polls', 'Error while copying link to clipboard'), { type: 'error' })
+ }
+ )
+ },
+ validatePublicUsername: debounce(function() {
+ if (this.userName.length > 2) {
+ this.checkingUserName = true
+ return axios.post(OC.generateUrl('apps/polls/check/username'), { pollId: this.event.id, userName: this.userName, token: this.$route.params.token })
+ .then((response) => {
+ this.checkingUserName = false
+ this.isValidName = true
+ this.invalidUserNameMessage = 'User name is OK.'
+ return true
+ })
+ .catch(() => {
+ this.checkingUserName = false
+ this.isValidName = false
+ this.invalidUserNameMessage = t('polls', 'This user name can not be choosed.')
+ return false
+ })
+ } else {
+ this.checkingUserName = false
+ this.isValidName = false
+ this.invalidUserNameMessage = t('polls', 'Please use at least 3 characters for your user name!')
+ return false
+ }
+ }, 500),
+
+ writeUserName() {
+ if (this.validatePublicUsername()) {
+ this.$store.dispatch('addShareFromUser', { token: this.$route.params.token, userName: this.userName })
+ .then((response) => {
+ this.token = response.token
+ this.redirecting = true
+ this.$router.replace({ name: 'publicVote', params: { 'token': response.token } })
+ })
+ .catch(() => {
+ OC.Notification.showTemporary(t('polls', 'Error saving user name"', 1, event.title), { type: 'error' })
+ })
+ }
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+ .voteHeader {
+ margin: 8px 24px;
+ }
+
+ .personal-link {
+ display: flex;
+ padding: 4px 12px;
+ margin: 0 12px 0 24px;
+ border: 2px solid var(--color-success);
+ background-color: #d6fdda !important;
+ border-radius: var(--border-radius);
+ font-size: 1.2em;
+ opacity: 0.8;
+ .icon {
+ margin: 0 12px;
+ }
+ }
+
+ .getUsername {
+ & > label {
+ margin-right: 12px;
+ }
+
+ margin: 0 12px 12px 24px;
+ border:2px solid var(--color-border-dark);
+ font-size: 1.2em;
+ padding: 0 12px 0 12px;
+ display: flex;
+ align-items: center;
+ border-radius: var(--border-radius);
+ background-color: var(--color-background-dark);
+ flex-wrap: wrap;
+
+ form, div {
+ flex: 1;
+ display: flex;
+
+ }
+ input {
+ flex: 1;
+ }
+
+ .icon-loading-small {
+ position: relative;
+ right: 24px;
+ top: 0px;
+ }
+
+ input[type="text"] + .icon-confirm, input[type="text"] + .icon-loading-small {
+ flex: 0;
+ margin-left: -8px !important;
+ border-left-color: transparent !important;
+ border-radius: 0 var(--border-radius) var(--border-radius) 0 !important;
+ background-clip: padding-box;
+ opacity: 1;
+ height: 34px;
+ width: 34px;
+ padding: 7px 20px;
+ cursor: pointer;
+ margin-right: 0;
+ }
+ }
+
+</style>
diff --git a/src/js/components/VoteTable/VoteTable.vue b/src/js/components/VoteTable/VoteTable.vue
new file mode 100644
index 00000000..c5c8a48b
--- /dev/null
+++ b/src/js/components/VoteTable/VoteTable.vue
@@ -0,0 +1,210 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 lang="html">
+ <div class="vote-table">
+ <div class="header">
+ <div class="sticky" />
+
+ <div v-if="noOptions" class="noOptions">
+ <h2> {{ t('polls', 'there are no vote Options') }} </h2>
+ </div>
+
+ <VoteTableHeader v-for="(option) in sortedOptions"
+ :key="option.id"
+ :option="option"
+ :poll-type="event.type" />
+ </div>
+
+ <div v-for="(participant) in participants" :key="participant" :class="{currentuser: (participant === currentUser) }">
+ <UserDiv :key="participant"
+ class="sticky"
+ :class="{currentuser: (participant === currentUser) }"
+ :user-id="participant" />
+ <VoteTableItem v-for="(option) in sortedOptions"
+ :key="option.id"
+ :user-id="participant"
+ :option="option"
+ @voteClick="setVote(option, participant)" />
+ </div>
+ </div>
+</template>
+
+<script>
+import VoteTableItem from './VoteTableItem'
+import VoteTableHeader from './VoteTableHeader'
+import { mapState, mapGetters } from 'vuex'
+
+export default {
+ name: 'VoteTable',
+ components: {
+ VoteTableHeader,
+ VoteTableItem
+ },
+
+ computed: {
+ ...mapState({
+ event: state => state.event,
+ acl: state => state.acl
+ }),
+
+ ...mapGetters([
+ 'sortedOptions',
+ 'participants'
+ ]),
+
+ currentUser() {
+ return this.acl.userId
+ },
+
+ noOptions() {
+ return (this.sortedOptions.length === 0)
+ }
+ },
+
+ methods: {
+ setVote(option, participant) {
+ let nextAnswer = this.$store.getters.getNextAnswer({
+ option: option,
+ userId: participant
+ })
+ this.$store
+ .dispatch('setVoteAsync', {
+ option: option,
+ userId: participant,
+ setTo: nextAnswer
+ })
+ .then(() => {
+ // this.$emit('voteSaved')
+ })
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+ .user-row.sticky,
+ .header > .sticky {
+ position: sticky;
+ left: 0;
+ background-color: var(--color-main-background);
+ width: 170px;
+ flex: 0 0 auto;
+ }
+ .header {
+ height: 150px;
+ }
+ .user {
+ height: 44px;
+ padding: 0 17px;
+ }
+ .vote-table {
+ display: flex;
+ flex: 0;
+ flex-direction: column;
+ justify-content: flex-start;
+ overflow: scroll;
+
+ & > div {
+ display: flex;
+ flex: 1;
+ border-bottom: 1px solid var(--color-border-dark);
+ order: 3;
+ justify-content: space-between;
+ min-width: max-content;
+
+ & > div {
+ width: 84px;
+ min-width: 84px;
+ flex: 1;
+ margin: 2px;
+ }
+
+ & > .vote-header {
+ flex: 1;
+ }
+
+ &.header {
+ order: 1;
+ }
+
+ &.currentuser {
+ order: 2;
+ }
+ }
+
+ .vote-row {
+ display: flex;
+ justify-content: space-around;
+ flex: 1;
+ align-items: center;
+ }
+ }
+
+ @media (max-width: (480px)) {
+ .vote-table {
+ flex: 1 0;
+ flex-direction: row;
+ min-width: 300px;
+
+ &> div {
+ display: none;
+ &> div {
+ width: unset;
+ margin: 0;
+
+ }
+ // &.currentuser {
+ // display: flex;
+ // > .user-row.currentuser {
+ // display: none;
+ // }
+ // }
+ }
+
+ &> .currentuser {
+ display: flex;
+ flex-direction: column;
+ &> .user-row {
+ display: none;
+ }
+ }
+
+ &> .header, {
+ height: initial;
+ padding-left: initial;
+ display: flex;
+ flex-direction: column;
+ flex: 3 1;
+ justify-content: space-around;
+ align-items: stretch;
+ &> .vote-header {
+ display: flex;
+ flex-direction: row;
+ &> .counter {
+ align-items: baseline;
+ }
+ }
+ }
+ }
+ }
+</style>
diff --git a/src/js/components/VoteTable/VoteTableHeader.vue b/src/js/components/VoteTable/VoteTableHeader.vue
new file mode 100644
index 00000000..28634cb8
--- /dev/null
+++ b/src/js/components/VoteTable/VoteTableHeader.vue
@@ -0,0 +1,291 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 class="vote-header" :class=" { winner: isWinner }">
+ <div v-if="textPoll" class="text-box">
+ {{ option.pollOptionText }}
+ </div>
+
+ <div v-if="datePoll" v-tooltip.auto="localFullDate" class="date-box">
+ <div class="month">
+ {{ monthY }}
+ </div>
+ <div class="day">
+ {{ day }}
+ </div>
+ <div class="dow">
+ {{ dow }}
+ </div>
+ <div class="time">
+ {{ time }}
+ </div>
+ </div>
+
+ <div class="counter">
+ <div class="yes">
+ <span> {{ yesVotes }} </span>
+ </div>
+ <div v-if="event.allowMaybe" class="maybe">
+ <span> {{ maybeVotes }} </span>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+import moment from 'moment'
+import { mapState, mapGetters } from 'vuex'
+
+export default {
+ name: 'VoteTableHeader',
+
+ props: {
+ option: {
+ type: Object,
+ default: undefined
+ },
+ pollType: {
+ type: String,
+ default: undefined
+ }
+ },
+
+ data() {
+ return {
+ openedMenu: false,
+ hostName: this.$route.query.page
+ }
+
+ },
+
+ computed: {
+ ...mapState({
+ event: state => state.event,
+ votes: state => state.votes.list
+ }),
+ ...mapGetters([
+ 'votesRank',
+ 'winnerCombo'
+ ]),
+
+ votesranked() {
+ let pollOptionText = this.option.pollOptionText
+ return this.votesRank.find(rank => {
+ return rank.pollOptionText === pollOptionText
+ })
+ },
+
+ yesVotes() {
+ let pollOptionText = this.option.pollOptionText
+ return this.votesRank.find(rank => {
+ return rank.pollOptionText === pollOptionText
+ }).yes
+ },
+
+ noVotes() {
+ let pollOptionText = this.option.pollOptionText
+ return this.votesRank.find(rank => {
+ return rank.pollOptionText === pollOptionText
+ }).no
+ },
+
+ maybeVotes() {
+ let pollOptionText = this.option.pollOptionText
+ return this.votesRank.find(rank => {
+ return rank.pollOptionText === pollOptionText
+ }).maybe
+ },
+
+ isWinner() {
+ let pollOptionText = this.option.pollOptionText
+ return (
+ this.votesRank.find(rank => {
+ return rank.pollOptionText === pollOptionText
+ }).yes === this.winnerCombo.yes
+
+ && (this.votesRank.find(rank => {
+ return rank.pollOptionText === pollOptionText
+ }).yes + this.votesRank.find(rank => {
+ return rank.pollOptionText === pollOptionText
+ }).maybe > 0)
+
+ && this.winnerCombo.maybe === this.votesRank.find(rank => {
+ return rank.pollOptionText === pollOptionText
+ }).maybe
+ )
+ },
+
+ datePoll() {
+ return (this.event.type === 'datePoll')
+ },
+
+ textPoll() {
+ return (this.event.type === 'textPoll')
+ },
+
+ localFullDate() {
+ return moment(this.option.timestamp * 1000).format('llll')
+ },
+
+ day() {
+ return moment(this.option.timestamp * 1000).format('Do')
+ },
+
+ dow() {
+ return moment(this.option.timestamp * 1000).format('ddd')
+ },
+
+ month() {
+ return moment(this.option.timestamp * 1000).format('MMM')
+ },
+
+ monthY() {
+ return this.month + " '" + moment(this.option.timestamp * 1000).format('YY')
+ },
+
+ year() {
+ return moment(this.option.timestamp * 1000).format('YYYY')
+ },
+
+ time() {
+ return moment(this.option.timestamp * 1000).format('LT')
+ },
+
+ localFullDateT() {
+ return moment(this.option.pollOptionText).format('llll')
+ },
+
+ dayT() {
+ return moment(this.option.pollOptionText).format('Do')
+ },
+
+ dowT() {
+ return moment(this.option.pollOptionText).format('ddd')
+ },
+
+ monthT() {
+ return moment(this.option.pollOptionText).format('MMM')
+ },
+
+ yearT() {
+ return moment(this.option.pollOptionText).format('YYYY')
+ },
+
+ timeT() {
+ return moment(this.option.pollOptionText).format('LT')
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.vote-header {
+ display: flex;
+ flex-direction: column;
+ &.winner {
+ font-weight: bold;
+ color: #49bc49;
+ }
+}
+
+.counter {
+ flex: 0;
+ display: flex;
+ justify-content: center;
+ font-size: 18px;
+ height: 88px;
+ padding: 14px 4px;
+
+ &> * {
+ background-position: 0px 2px;
+ padding-left: 23px;
+ background-repeat: no-repeat;
+ background-size: contain;
+ margin-right: 8px;
+ }
+
+ .yes {
+ color: #49bc49;
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwYXRoIGQ9Im0yLjM1IDcuMyA0IDRsNy4zLTcuMyIgc3Ryb2tlPSIjNDliYzQ5IiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz48L3N2Zz4K);
+ }
+ .no {
+ color: #f45573;
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiIgdmVyc2lvbj0iMS4xIiB2aWV3Ym94PSIwIDAgMTYgMTYiPjxwYXRoIGQ9Im0xNCAxMi4zLTEuNyAxLjctNC4zLTQuMy00LjMgNC4zLTEuNy0xLjcgNC4zLTQuMy00LjMtNC4zIDEuNy0xLjcgNC4zIDQuMyA0LjMtNC4zIDEuNyAxLjctNC4zIDQuM3oiIGZpbGw9IiNmNDU1NzMiLz48L3N2Zz4K);
+ }
+ .maybe {
+ color: #ffc107;
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgd2lkdGg9IjE2IgogICBoZWlnaHQ9IjE2IgogICBzb2RpcG9kaTpkb2NuYW1lPSJtYXliZS12b3RlLXZhcmlhbnQuc3ZnIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjkyLjIgKDVjM2U4MGQsIDIwMTctMDgtMDYpIj4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgb2JqZWN0dG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBndWlkZXRvbGVyYW5jZT0iMTAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjE5MjAiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTAxNyIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iMTQuNzUiCiAgICAgaW5rc2NhcGU6Y3g9IjgiCiAgICAgaW5rc2NhcGU6Y3k9IjE0Ljg2NTIwMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTgiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9Ii04IgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjEiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgaWQ9ImdyaWQ4MzYiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhMTAiPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgICAgPGRjOnRpdGxlPjwvZGM6dGl0bGU+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM4IiAvPgogIDx0ZXh0CiAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICBzdHlsZT0iZm9udC1zdHlsZTpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc2l6ZToxNS4wMDY0OTA3MXB4O2xpbmUtaGVpZ2h0OjEuMjU7Zm9udC1mYW1pbHk6c2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiNmZmMxMDc7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjEuMDIzMTY5NzYiCiAgICAgeD0iLTAuODAzNjg5NDgiCiAgICAgeT0iMTIuNTU5MzEyIgogICAgIGlkPSJ0ZXh0ODE4IgogICAgIHRyYW5zZm9ybT0ic2NhbGUoMS4wOTAxOSwwLjkxNzI3MTMpIj48dHNwYW4KICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICBpZD0idHNwYW44MTYiCiAgICAgICB4PSItMC44MDM2ODk0OCIKICAgICAgIHk9IjEyLjU1OTMxMiIKICAgICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2NjY2OThweDtmaWxsOiNmZmMxMDc7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjEuMDIzMTY5NzYiPig8L3RzcGFuPjwvdGV4dD4KICA8dGV4dAogICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXNpemU6NDAuOTI3MTQzMXB4O2xpbmUtaGVpZ2h0OjEuMjU7Zm9udC1mYW1pbHk6c2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiNmZmMxMDc7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjEuMDIzMTc4NDYiCiAgICAgeD0iOS45NjMwNDQyIgogICAgIHk9IjEyLjQ3ODk0NSIKICAgICBpZD0idGV4dDgyOCIKICAgICB0cmFuc2Zvcm09InNjYWxlKDEuMDkwMTk5MywwLjkxNzI2MzQ4KSI+PHRzcGFuCiAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgaWQ9InRzcGFuODI2IgogICAgICAgeD0iOS45NjMwNDQyIgogICAgICAgeT0iMTIuNDc4OTQ1IgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxNC42NjY2NjY5OHB4O2ZpbGw6I2ZmYzEwNztmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MS4wMjMxNzg0NiI+KTwvdHNwYW4+PC90ZXh0PgogIDxwYXRoCiAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICBkPSJtIDExLjkyNCw0LjA2NTk5OTIgLTQuOTMyMDAwMSw0Ljk3IC0yLjgyOCwtMi44MyBMIDIuNzUsNy42MTc5OTkyIDYuOTkxOTk5OSwxMS44NjEgMTMuMzU3LDUuNDk1OTk5MiBsIC0xLjQzMywtMS40MzIgeiIKICAgICBpZD0icGF0aDgxNiIKICAgICBzdHlsZT0iZmlsbDojZmZjMTA3O2ZpbGwtb3BhY2l0eToxIiAvPgo8L3N2Zz4K);
+ }
+}
+
+.text-box {
+ flex: 1 0;
+ height: 44px;
+ align-self: center;
+ font-size: 1.4em;
+ padding-top: 14px;
+ hyphens: auto;
+}
+
+.date-box {
+ display: flex;
+ flex-direction: column;
+ flex: 1 0;
+ padding: 0 2px;
+ align-items: center;
+ justify-content: center;
+
+ .month, .dow {
+ font-size: 1.2em;
+ color: var(--color-text-lighter);
+ }
+ .day {
+ font-size: 1.8em;
+ margin: 5px 0 5px 0;
+ }
+}
+
+@media (max-width: (480px) ) {
+ .vote-header {
+ padding: 4px 0;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ border-top: 1px solid var(--color-border-dark);
+
+ .date-box {
+ padding: 0 20px 0 4px;
+ align-content: center;
+ }
+ .counter {
+ flex-direction: column;
+ align-items: baseline;
+ & > * {
+ margin: 4px 1px;
+ }
+ }
+ }
+}
+
+</style>
diff --git a/src/js/components/VoteTable/VoteTableItem.vue b/src/js/components/VoteTable/VoteTableItem.vue
new file mode 100644
index 00000000..1e3526c0
--- /dev/null
+++ b/src/js/components/VoteTable/VoteTableItem.vue
@@ -0,0 +1,169 @@
+<!--
+ - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
+ -
+ - @author René Gieling <github@dartcafe.de>
+ -
+ - @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 class="vote-item" :class="[answer, { active: isActive}]">
+ <div class="icon" @click="voteClick()" />
+ </div>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+
+export default {
+ name: 'VoteTableItem',
+
+ props: {
+ option: {
+ type: Object,
+ default: undefined
+ },
+ userId: {
+ type: String,
+ default: ''
+ }
+ },
+
+ computed: {
+ ...mapState({
+ event: state => state.event,
+ acl: state => state.acl
+ }),
+
+ answer() {
+ try {
+ return this.$store.getters.getVote({
+ option: this.option,
+ userId: this.userId
+ }).voteAnswer
+ } catch (e) {
+ return ''
+ }
+ },
+
+ isValidUser() {
+ return (this.userId !== '' && this.userId !== null)
+ },
+
+ isActive() {
+ return (this.isValidUser && this.acl.userId === this.userId && !this.event.expired)
+ }
+
+ },
+
+ methods: {
+ voteClick() {
+ if (this.isActive) {
+ this.$emit('voteClick')
+ }
+ }
+
+ }
+}
+
+</script>
+
+<style lang="scss" scoped>
+ $bg-no: #ffede9;
+ $bg-maybe: #fcf7e1;
+ $bg-unvoted: #fff4c8;
+ $bg-yes: #ebf5d6;
+
+ $fg-no: #f45573;
+ $fg-maybe: #f0db98;
+ $fg-unvoted: #f0db98;
+ $fg-yes: #49bc49;
+
+ .vote-item {
+ height: 43px;
+ display: flex;
+ flex: 1;
+ width: 85px;
+ align-items: center;
+ background-color: var(--color-background-dark);
+ color: var(--color-main-text);
+ > .icon {
+ margin: auto;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 32px;
+ background-image: var(--icon-close-000);
+ min-width: 40px;
+ min-height: 40px;
+ width: 40px;
+ height: 40px;
+ background-size: 90%;
+ flex: 0 0 auto;
+ }
+
+ &.yes {
+ background-color: $bg-yes;
+ color: $fg-yes;
+ // background-image: var(--icon-checkmark-49bc49);
+ > .icon {
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwYXRoIGQ9Im0yLjM1IDcuMyA0IDRsNy4zLTcuMyIgc3Ryb2tlPSIjNDliYzQ5IiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz48L3N2Zz4K);
+ }
+ }
+
+ &.no {
+ background-color: $bg-no;
+ color: $fg-no;
+ // background-image: var(--icon-close-f45573);
+ > .icon {
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiIgdmVyc2lvbj0iMS4xIiB2aWV3Ym94PSIwIDAgMTYgMTYiPjxwYXRoIGQ9Im0xNCAxMi4zLTEuNyAxLjctNC4zLTQuMy00LjMgNC4zLTEuNy0xLjcgNC4zLTQuMy00LjMtNC4zIDEuNy0xLjcgNC4zIDQuMyA0LjMtNC4zIDEuNyAxLjctNC4zIDQuM3oiIGZpbGw9IiNmNDU1NzMiLz48L3N2Zz4K);
+ }
+ }
+
+ &.maybe {
+ background-color: $bg-maybe;
+ color: $fg-maybe;
+ // background-image: var(--icon-polls-maybe-vote-variant-f0db98);
+ > .icon {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgd2lkdGg9IjE2IgogICBoZWlnaHQ9IjE2IgogICBzb2RpcG9kaTpkb2NuYW1lPSJtYXliZS12b3RlLXZhcmlhbnQuc3ZnIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjkyLjIgKDVjM2U4MGQsIDIwMTctMDgtMDYpIj4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgb2JqZWN0dG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBndWlkZXRvbGVyYW5jZT0iMTAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjE5MjAiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTAxNyIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iMTQuNzUiCiAgICAgaW5rc2NhcGU6Y3g9IjgiCiAgICAgaW5rc2NhcGU6Y3k9IjE0Ljg2NTIwMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTgiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9Ii04IgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjEiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgaWQ9ImdyaWQ4MzYiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhMTAiPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgICAgPGRjOnRpdGxlPjwvZGM6dGl0bGU+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM4IiAvPgogIDx0ZXh0CiAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICBzdHlsZT0iZm9udC1zdHlsZTpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc2l6ZToxNS4wMDY0OTA3MXB4O2xpbmUtaGVpZ2h0OjEuMjU7Zm9udC1mYW1pbHk6c2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiNmZmMxMDc7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjEuMDIzMTY5NzYiCiAgICAgeD0iLTAuODAzNjg5NDgiCiAgICAgeT0iMTIuNTU5MzEyIgogICAgIGlkPSJ0ZXh0ODE4IgogICAgIHRyYW5zZm9ybT0ic2NhbGUoMS4wOTAxOSwwLjkxNzI3MTMpIj48dHNwYW4KICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICBpZD0idHNwYW44MTYiCiAgICAgICB4PSItMC44MDM2ODk0OCIKICAgICAgIHk9IjEyLjU1OTMxMiIKICAgICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2NjY2OThweDtmaWxsOiNmZmMxMDc7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjEuMDIzMTY5NzYiPig8L3RzcGFuPjwvdGV4dD4KICA8dGV4dAogICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXNpemU6NDAuOTI3MTQzMXB4O2xpbmUtaGVpZ2h0OjEuMjU7Zm9udC1mYW1pbHk6c2Fucy1zZXJpZjtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiNmZmMxMDc7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjEuMDIzMTc4NDYiCiAgICAgeD0iOS45NjMwNDQyIgogICAgIHk9IjEyLjQ3ODk0NSIKICAgICBpZD0idGV4dDgyOCIKICAgICB0cmFuc2Zvcm09InNjYWxlKDEuMDkwMTk5MywwLjkxNzI2MzQ4KSI+PHRzcGFuCiAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgaWQ9InRzcGFuODI2IgogICAgICAgeD0iOS45NjMwNDQyIgogICAgICAgeT0iMTIuNDc4OTQ1IgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxNC42NjY2NjY5OHB4O2ZpbGw6I2ZmYzEwNztmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MS4wMjMxNzg0NiI+KTwvdHNwYW4+PC90ZXh0PgogIDxwYXRoCiAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICBkPSJtIDExLjkyNCw0LjA2NTk5OTIgLTQuOTMyMDAwMSw0Ljk3IC0yLjgyOCwtMi44MyBMIDIuNzUsNy42MTc5OTkyIDYuOTkxOTk5OSwxMS44NjEgMTMuMzU3LDUuNDk1OTk5MiBsIC0xLjQzMywtMS40MzIgeiIKICAgICBpZD0icGF0aDgxNiIKICAgICBzdHlsZT0iZmlsbDojZmZjMTA3O2ZpbGwtb3BhY2l0eToxIiAvPgo8L3N2Zz4K);
+ }
+ }
+
+ &.active {
+ background-color: var(--color-main-background);
+ > .icon {
+ cursor: pointer;
+ border: 2px solid;
+ border-radius: var(--border-radius);
+ }
+ &:active {
+ box-shadow: inherit;
+ }
+ }
+
+ }
+
+ @media (max-width: (480px) ) {
+ .vote-item {
+ border-top: 1px solid var(--color-border-dark);
+ &.active {
+ width: 10vw;
+ height: 10vw;
+ }
+ }
+ }
+
+</style>