fix radio buttons, polish stuff
This commit is contained in:
parent
d7b75ba037
commit
32dae8eb7a
|
@ -1,4 +1,5 @@
|
||||||
import Timeago from '../timeago/timeago.vue'
|
import Timeago from '../timeago/timeago.vue'
|
||||||
|
import { forEach } from 'lodash'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Poll',
|
name: 'Poll',
|
||||||
|
@ -14,6 +15,7 @@ export default {
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000)
|
this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000)
|
||||||
|
this.multipleChoices = this.poll.options.map(_ => false)
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
clearTimeout(this.refreshInterval)
|
clearTimeout(this.refreshInterval)
|
||||||
|
@ -38,7 +40,7 @@ export default {
|
||||||
},
|
},
|
||||||
choiceIndices () {
|
choiceIndices () {
|
||||||
return this.multipleChoices
|
return this.multipleChoices
|
||||||
.map((entry, index) => index)
|
.map((entry, index) => entry && index)
|
||||||
.filter(value => typeof value === 'number')
|
.filter(value => typeof value === 'number')
|
||||||
},
|
},
|
||||||
isDisabled () {
|
isDisabled () {
|
||||||
|
@ -55,7 +57,7 @@ export default {
|
||||||
this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000)
|
this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000)
|
||||||
},
|
},
|
||||||
percentageForOption (count) {
|
percentageForOption (count) {
|
||||||
return this.totalVotesCount === 0 ? 0 : Math.round((count + 5) / (this.totalVotesCount + 10) * 100)
|
return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
|
||||||
},
|
},
|
||||||
resultTitle (option) {
|
resultTitle (option) {
|
||||||
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
|
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
|
||||||
|
@ -63,6 +65,31 @@ export default {
|
||||||
fetchPoll () {
|
fetchPoll () {
|
||||||
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
|
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
|
||||||
},
|
},
|
||||||
|
activateOption (index) {
|
||||||
|
// forgive me father: doing checking the radio/checkboxes
|
||||||
|
// in code because of customized input elements need either
|
||||||
|
// a) an extra element for the actual graphic, or b) use a
|
||||||
|
// pseudo element for the label. We use b) which mandates
|
||||||
|
// using "for" and "id" matching which isn't nice when the
|
||||||
|
// same poll appears multiple times on the site (notifs and
|
||||||
|
// timeline for example). With code we can make sure it just
|
||||||
|
// works without altering the pseudo element implementation.
|
||||||
|
const clickedElement = this.$el.querySelector(`input[value="${index}"]`)
|
||||||
|
if (this.poll.multiple) {
|
||||||
|
// Checkboxes
|
||||||
|
const wasChecked = this.multipleChoices[index]
|
||||||
|
clickedElement.checked = !wasChecked
|
||||||
|
this.$set(this.multipleChoices, index, !wasChecked)
|
||||||
|
} else {
|
||||||
|
// Radio button
|
||||||
|
const allElements = this.$el.querySelectorAll('input')
|
||||||
|
forEach(allElements, element => {
|
||||||
|
element.checked = false
|
||||||
|
})
|
||||||
|
clickedElement.checked = true
|
||||||
|
this.singleChoiceIndex = index
|
||||||
|
}
|
||||||
|
},
|
||||||
optionId (index) {
|
optionId (index) {
|
||||||
return `poll${this.poll.id}-${index}`
|
return `poll${this.poll.id}-${index}`
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,24 +18,20 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else @click="activateOption(index)">
|
||||||
<input
|
<input
|
||||||
v-if="poll.multiple"
|
v-if="poll.multiple"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:id="optionId(index)"
|
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
:value="option.title"
|
:value="index"
|
||||||
v-model="multipleChoices[index]"
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
type="radio"
|
type="radio"
|
||||||
:id="optionId(index)"
|
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
:value="index"
|
:value="index"
|
||||||
v-model="singleChoiceIndex"
|
|
||||||
>
|
>
|
||||||
<label :for="optionId(index)">
|
<label>
|
||||||
{{option.title}}
|
{{option.title}}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,7 +50,7 @@
|
||||||
{{totalVotesCount}} {{ $t("polls.votes") }} ·
|
{{totalVotesCount}} {{ $t("polls.votes") }} ·
|
||||||
</div>
|
</div>
|
||||||
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
|
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
|
||||||
<Timeago :time="this.poll.expires_at" :auto-update="60" />
|
<Timeago :time="this.poll.expires_at" :auto-update="60" :now-threshold="0" />
|
||||||
</i18n>
|
</i18n>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,6 +76,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
color: $fallback--lightText;
|
||||||
|
color: var(--lightText, $fallback--lightText);
|
||||||
}
|
}
|
||||||
.option-result-label {
|
.option-result-label {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -94,7 +92,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: $fallback--lightBg;
|
background-color: $fallback--lightBg;
|
||||||
background-color: var(--faintLink, $fallback--lightBg);
|
background-color: var(--linkBg, $fallback--lightBg);
|
||||||
border-radius: $fallback--panelRadius;
|
border-radius: $fallback--panelRadius;
|
||||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
export default {
|
||||||
|
name: 'PollForm',
|
||||||
|
props: ['visible'],
|
||||||
|
data: () => ({
|
||||||
|
pollType: 'single',
|
||||||
|
options: ['', ''],
|
||||||
|
expiryAmount: 2,
|
||||||
|
expiryUnit: 'hours',
|
||||||
|
expiryUnits: ['minutes', 'hours', 'days']
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
pollLimits () {
|
||||||
|
return this.$store.state.instance.pollLimits
|
||||||
|
},
|
||||||
|
maxOptions () {
|
||||||
|
return this.pollLimits.max_options
|
||||||
|
},
|
||||||
|
maxLength () {
|
||||||
|
return this.pollLimits.max_option_chars
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clear () {
|
||||||
|
this.pollType = 'single'
|
||||||
|
this.options = ['', '']
|
||||||
|
this.expiryAmount = 1
|
||||||
|
this.expiryUnit = 'minutes'
|
||||||
|
},
|
||||||
|
nextOption (index) {
|
||||||
|
const element = this.$el.querySelector(`#poll-${index+1}`)
|
||||||
|
if (element) {
|
||||||
|
element.focus()
|
||||||
|
} else {
|
||||||
|
// Try adding an option and try focusing on it
|
||||||
|
const addedOption = this.addOption()
|
||||||
|
if (addedOption) {
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.nextOption(index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addOption () {
|
||||||
|
if (this.options.length < this.maxOptions) {
|
||||||
|
this.options.push('')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
deleteOption (index, event) {
|
||||||
|
if (this.options.length > 2) {
|
||||||
|
this.options.splice(index, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expiryAmountChange () {
|
||||||
|
this.expiryAmount = Math.max(1, this.expiryAmount)
|
||||||
|
this.expiryAmount = Math.min(120, this.expiryAmount)
|
||||||
|
this.updatePollToParent()
|
||||||
|
},
|
||||||
|
updatePollToParent () {
|
||||||
|
const unitMultiplier = this.expiryUnit === 'minutes' ? 60
|
||||||
|
: this.expiryUnit === 'hours' ? 60 * 60
|
||||||
|
: 60 * 60 * 24
|
||||||
|
|
||||||
|
const expiresIn = this.expiryAmount * unitMultiplier
|
||||||
|
|
||||||
|
this.$emit('update-poll', {
|
||||||
|
options: this.options,
|
||||||
|
multiple: this.pollType === 'multiple',
|
||||||
|
expiresIn
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,14 +19,14 @@
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
v-if="options.length < maxOptions"
|
v-if="options.length < maxOptions"
|
||||||
class="add-option"
|
class="add-option faint"
|
||||||
@click="addOption"
|
@click="addOption"
|
||||||
>
|
>
|
||||||
<i class="icon-plus" />
|
<i class="icon-plus" />
|
||||||
{{ $t("polls.add_option") }}
|
{{ $t("polls.add_option") }}
|
||||||
</a>
|
</a>
|
||||||
<div class="poll-type-expiry">
|
<div class="poll-type-expiry">
|
||||||
<div class="poll-type">
|
<div class="poll-type" :title="$t('polls.type')">
|
||||||
<label for="poll-type-selector" class="select">
|
<label for="poll-type-selector" class="select">
|
||||||
<select class="select" v-model="pollType" @change="updatePollToParent">
|
<select class="select" v-model="pollType" @change="updatePollToParent">
|
||||||
<option value="single">{{$t('polls.single_choice')}}</option>
|
<option value="single">{{$t('polls.single_choice')}}</option>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<i class="icon-down-open"/>
|
<i class="icon-down-open"/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="poll-expiry">
|
<div class="poll-expiry" :title="$t('polls.expiry')">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
class="expiry-amount hide-number-spinner"
|
class="expiry-amount hide-number-spinner"
|
||||||
|
@ -57,87 +57,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script src="./poll_form.js"></script>
|
||||||
import * as DateUtils from 'src/services/date_utils/date_utils'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PollForm',
|
|
||||||
props: ['visible'],
|
|
||||||
data: () => ({
|
|
||||||
pollType: 'single',
|
|
||||||
options: ['', ''],
|
|
||||||
expiryAmount: 2,
|
|
||||||
expiryUnit: 'hours',
|
|
||||||
expiryUnits: ['minutes', 'hours', 'days']
|
|
||||||
}),
|
|
||||||
computed: {
|
|
||||||
pollLimits () {
|
|
||||||
return this.$store.state.instance.pollLimits
|
|
||||||
},
|
|
||||||
maxOptions () {
|
|
||||||
return this.pollLimits.max_options
|
|
||||||
},
|
|
||||||
maxLength () {
|
|
||||||
return this.pollLimits.max_option_chars
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clear () {
|
|
||||||
this.pollType = 'single'
|
|
||||||
this.options = ['', '']
|
|
||||||
this.expiryAmount = 1
|
|
||||||
this.expiryUnit = 'minutes'
|
|
||||||
},
|
|
||||||
nextOption (index) {
|
|
||||||
const element = this.$el.querySelector(`#poll-${index+1}`)
|
|
||||||
if (element) {
|
|
||||||
element.focus()
|
|
||||||
} else {
|
|
||||||
// Try adding an option and try focusing on it
|
|
||||||
const addedOption = this.addOption()
|
|
||||||
if (addedOption) {
|
|
||||||
this.$nextTick(function () {
|
|
||||||
this.nextOption(index)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addOption () {
|
|
||||||
if (this.options.length < this.maxOptions) {
|
|
||||||
this.options.push('')
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
deleteOption (index, event) {
|
|
||||||
if (this.options.length > 2) {
|
|
||||||
this.options.splice(index, 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
expiryAmountChange () {
|
|
||||||
this.expiryAmount = Math.max(1, this.expiryAmount)
|
|
||||||
this.expiryAmount = Math.min(120, this.expiryAmount)
|
|
||||||
this.updatePollToParent()
|
|
||||||
},
|
|
||||||
updatePollToParent () {
|
|
||||||
const unitMultiplier = this.expiryUnit === 'minutes' ? 60
|
|
||||||
: this.expiryUnit === 'hours' ? 60 * 60
|
|
||||||
: 60 * 60 * 24
|
|
||||||
|
|
||||||
const expiresIn = this.expiryAmount * unitMultiplier
|
|
||||||
|
|
||||||
this.$emit('update-poll', {
|
|
||||||
options: this.options,
|
|
||||||
multiple: this.pollType === 'multiple',
|
|
||||||
expiresIn
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.poll-form {
|
.poll-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -148,8 +71,6 @@ export default {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
padding-top: 0.25em;
|
padding-top: 0.25em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $fallback--faint;
|
|
||||||
color: var(--faint, $fallback--faint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-option {
|
.poll-option {
|
||||||
|
@ -194,6 +115,7 @@ export default {
|
||||||
|
|
||||||
.expiry-amount {
|
.expiry-amount {
|
||||||
width: 3em;
|
width: 3em;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expiry-unit {
|
.expiry-unit {
|
|
@ -1,89 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="poll-results">
|
|
||||||
<div class="votes">
|
|
||||||
<div
|
|
||||||
class="poll-option"
|
|
||||||
v-for="(option, index) in poll.options"
|
|
||||||
:key="index"
|
|
||||||
:title="`${option.votes_count}/${totalVotesCount} ${$t('polls.votes')}`"
|
|
||||||
>
|
|
||||||
<div class="vote-label">
|
|
||||||
<span>{{percentageForOption(option.votes_count)}}%</span>
|
|
||||||
<span>{{option.title}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="fill" :style="{ 'width': `${percentageForOption(option.votes_count)}%` }"></div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<div class="total">
|
|
||||||
{{totalVotesCount}} {{ $t("polls.votes") }} ·
|
|
||||||
</div>
|
|
||||||
<div class="refresh">
|
|
||||||
<a href="#" @click.stop.prevent="fetchPoll(poll.id)">Refresh</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'PollResults',
|
|
||||||
props: ['poll', 'statusId'],
|
|
||||||
computed: {
|
|
||||||
totalVotesCount () {
|
|
||||||
return this.poll.votes_count
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
percentageForOption (count) {
|
|
||||||
return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
|
|
||||||
},
|
|
||||||
fetchPoll () {
|
|
||||||
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import '../../../_variables.scss';
|
|
||||||
|
|
||||||
.poll-results {
|
|
||||||
.votes {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0 0 0.5em;
|
|
||||||
}
|
|
||||||
.poll-option {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin-top: 0.25em;
|
|
||||||
margin-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
.fill {
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
background-color: $fallback--lightBg;
|
|
||||||
background-color: var(--faintLink, $fallback--lightBg);
|
|
||||||
border-radius: $fallback--panelRadius;
|
|
||||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
transition: width 0.5s;
|
|
||||||
}
|
|
||||||
.vote-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.1em 0.25em;
|
|
||||||
z-index: 1;
|
|
||||||
span {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,186 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="poll-vote" v-bind:class="containerClass">
|
|
||||||
<div
|
|
||||||
class="poll-choice"
|
|
||||||
v-for="(option, index) in poll.options"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<div v-if="showResults" :title="resultTitle(option)">
|
|
||||||
<div class="vote-label">
|
|
||||||
<span>{{percentageForOption(option.votes_count)}}%</span>
|
|
||||||
<span>{{option.title}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="fill" :style="{ 'width': `${percentageForOption(option.votes_count)}%` }"></div>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<input
|
|
||||||
v-if="poll.multiple"
|
|
||||||
type="checkbox"
|
|
||||||
:id="optionId(index)"
|
|
||||||
:disabled="loading"
|
|
||||||
:value="option.title"
|
|
||||||
v-model="multipleChoices[index]"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-else
|
|
||||||
type="radio"
|
|
||||||
:id="optionId(index)"
|
|
||||||
:disabled="loading"
|
|
||||||
:value="index"
|
|
||||||
v-model="singleChoiceIndex"
|
|
||||||
>
|
|
||||||
<label :for="optionId(index)">
|
|
||||||
{{option.title}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<button
|
|
||||||
class="btn btn-default poll-vote-button"
|
|
||||||
type="button"
|
|
||||||
@click="vote"
|
|
||||||
:disabled="isDisabled"
|
|
||||||
>
|
|
||||||
{{$t('polls.vote')}}
|
|
||||||
</button>
|
|
||||||
<Timeago :time="this.poll.expires_at" :auto-update="1"></Timeago>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Timeago from '../../timeago/timeago.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PollVote',
|
|
||||||
props: ['poll', 'statusId'],
|
|
||||||
components: { Timeago },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
multipleChoices: [],
|
|
||||||
singleChoiceIndex: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
expired () {
|
|
||||||
return new Date() > this.poll.expires_at
|
|
||||||
},
|
|
||||||
showResults () {
|
|
||||||
return this.poll.voted || this.expired
|
|
||||||
},
|
|
||||||
totalVotesCount () {
|
|
||||||
return this.poll.votes_count
|
|
||||||
},
|
|
||||||
timeleft () {
|
|
||||||
const expiresAt = new Date(this.poll.expires_at)
|
|
||||||
return expiresAt
|
|
||||||
},
|
|
||||||
expiresAt () {
|
|
||||||
return Date.parse(this.poll.expires_at).toLocaleString()
|
|
||||||
},
|
|
||||||
containerClass () {
|
|
||||||
return {
|
|
||||||
loading: this.loading
|
|
||||||
}
|
|
||||||
},
|
|
||||||
choiceIndices () {
|
|
||||||
return this.multipleChoices.map((entry, index) => index).filter(value => typeof value === 'number')
|
|
||||||
},
|
|
||||||
isDisabled () {
|
|
||||||
const noChoice = this.poll.multiple ? this.choiceIndices.length === 0 : this.singleChoiceIndex === undefined
|
|
||||||
return this.loading || noChoice
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
percentageForOption (count) {
|
|
||||||
return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
|
|
||||||
},
|
|
||||||
resultTitle (option) {
|
|
||||||
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
|
|
||||||
},
|
|
||||||
fetchPoll () {
|
|
||||||
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
|
|
||||||
},
|
|
||||||
optionId (index) {
|
|
||||||
return `poll${this.poll.id}-${index}`
|
|
||||||
},
|
|
||||||
vote () {
|
|
||||||
this.loading = true
|
|
||||||
if (this.poll.multiple) {
|
|
||||||
if (this.choiceIndices.length === 0) return
|
|
||||||
this.$store.dispatch(
|
|
||||||
'votePoll',
|
|
||||||
{ id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices }
|
|
||||||
).then(poll => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if (this.singleChoiceIndex === undefined) return
|
|
||||||
this.$store.dispatch(
|
|
||||||
'votePoll',
|
|
||||||
{ id: this.statusId, pollId: this.poll.id, choices: [this.singleChoiceIndex] }
|
|
||||||
).then(poll => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import '../../../_variables.scss';
|
|
||||||
|
|
||||||
.poll-results {
|
|
||||||
.votes {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0 0 0.5em;
|
|
||||||
}
|
|
||||||
.poll-option {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin-top: 0.25em;
|
|
||||||
margin-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
.fill {
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
background-color: $fallback--lightBg;
|
|
||||||
background-color: var(--faintLink, $fallback--lightBg);
|
|
||||||
border-radius: $fallback--panelRadius;
|
|
||||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
transition: width 0.5s;
|
|
||||||
}
|
|
||||||
.vote-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.1em 0.25em;
|
|
||||||
z-index: 1;
|
|
||||||
span {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.poll-vote {
|
|
||||||
margin: 0.7em 0 0;
|
|
||||||
|
|
||||||
&.loading * {
|
|
||||||
cursor: progress;
|
|
||||||
}
|
|
||||||
.poll-choice {
|
|
||||||
padding: 0.4em 0;
|
|
||||||
}
|
|
||||||
.poll-vote-button {
|
|
||||||
margin: 1em 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -2,7 +2,7 @@ import statusPoster from '../../services/status_poster/status_poster.service.js'
|
||||||
import MediaUpload from '../media_upload/media_upload.vue'
|
import MediaUpload from '../media_upload/media_upload.vue'
|
||||||
import ScopeSelector from '../scope_selector/scope_selector.vue'
|
import ScopeSelector from '../scope_selector/scope_selector.vue'
|
||||||
import EmojiInput from '../emoji-input/emoji-input.vue'
|
import EmojiInput from '../emoji-input/emoji-input.vue'
|
||||||
import PollForm from '../poll/poll_form/poll_form.vue'
|
import PollForm from '../poll/poll_form.vue'
|
||||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||||
import Completion from '../../services/completion/completion.js'
|
import Completion from '../../services/completion/completion.js'
|
||||||
import { take, filter, reject, map, uniqBy } from 'lodash'
|
import { take, filter, reject, map, uniqBy } from 'lodash'
|
||||||
|
@ -82,7 +82,7 @@ const PostStatusForm = {
|
||||||
contentType
|
contentType
|
||||||
},
|
},
|
||||||
caret: 0,
|
caret: 0,
|
||||||
pollFormVisible: true
|
pollFormVisible: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -188,7 +188,7 @@ const PostStatusForm = {
|
||||||
return this.$store.state.instance.safeDM
|
return this.$store.state.instance.safeDM
|
||||||
},
|
},
|
||||||
pollsAvailable () {
|
pollsAvailable () {
|
||||||
return true // this.$store.state.instance.pollsAvailable
|
return this.$store.state.instance.pollsAvailable
|
||||||
},
|
},
|
||||||
hideScopeNotice () {
|
hideScopeNotice () {
|
||||||
return this.$store.state.config.hideScopeNotice
|
return this.$store.state.config.hideScopeNotice
|
||||||
|
|
|
@ -103,7 +103,7 @@
|
||||||
<div v-if="pollsAvailable" class="poll-icon">
|
<div v-if="pollsAvailable" class="poll-icon">
|
||||||
<label
|
<label
|
||||||
class="btn btn-default"
|
class="btn btn-default"
|
||||||
:title="$t('tool_tip.poll')"
|
:title="$t('polls.add_poll')"
|
||||||
@click="togglePollForm">
|
@click="togglePollForm">
|
||||||
<i class="icon-chart-bar" :class="pollFormVisible && 'selected'" />
|
<i class="icon-chart-bar" :class="pollFormVisible && 'selected'" />
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import * as DateUtils from 'src/services/date_utils/date_utils.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Timeago',
|
name: 'Timeago',
|
||||||
props: ['time', 'autoUpdate', 'longFormat'],
|
props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
relativeTime: { key: 'time.now', num: 0 },
|
relativeTime: { key: 'time.now', num: 0 },
|
||||||
|
@ -27,18 +27,14 @@ export default {
|
||||||
return typeof this.time === 'string'
|
return typeof this.time === 'string'
|
||||||
? new Date(Date.parse(this.time)).toLocaleString()
|
? new Date(Date.parse(this.time)).toLocaleString()
|
||||||
: this.time.toLocaleString()
|
: this.time.toLocaleString()
|
||||||
},
|
|
||||||
relativeTimeObject () {
|
|
||||||
return this.longFormat
|
|
||||||
? DateUtils.relativeTime(this.time)
|
|
||||||
: DateUtils.relativeTimeShort(this.time)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
refreshRelativeTimeObject () {
|
refreshRelativeTimeObject () {
|
||||||
|
const nowThreshold = typeof this.nowThreshold === 'number' ? this.nowThreshold : 1
|
||||||
this.relativeTime = this.longFormat
|
this.relativeTime = this.longFormat
|
||||||
? DateUtils.relativeTime(this.time)
|
? DateUtils.relativeTime(this.time, nowThreshold)
|
||||||
: DateUtils.relativeTimeShort(this.time)
|
: DateUtils.relativeTimeShort(this.time, nowThreshold)
|
||||||
|
|
||||||
if (this.autoUpdate) {
|
if (this.autoUpdate) {
|
||||||
this.interval = setTimeout(
|
this.interval = setTimeout(
|
||||||
|
|
|
@ -80,10 +80,12 @@
|
||||||
"no_more_notifications": "No more notifications"
|
"no_more_notifications": "No more notifications"
|
||||||
},
|
},
|
||||||
"polls": {
|
"polls": {
|
||||||
|
"add_poll": "Add Poll",
|
||||||
"add_option": "Add Option",
|
"add_option": "Add Option",
|
||||||
"option": "Option",
|
"option": "Option",
|
||||||
"votes": "votes",
|
"votes": "votes",
|
||||||
"vote": "Vote",
|
"vote": "Vote",
|
||||||
|
"type": "Poll type",
|
||||||
"single_choice": "Single choice",
|
"single_choice": "Single choice",
|
||||||
"multiple_choices": "Multiple choices",
|
"multiple_choices": "Multiple choices",
|
||||||
"expiry": "Poll age",
|
"expiry": "Poll age",
|
||||||
|
@ -540,8 +542,7 @@
|
||||||
"repeat": "Repeat",
|
"repeat": "Repeat",
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
"favorite": "Favorite",
|
"favorite": "Favorite",
|
||||||
"user_settings": "User Settings",
|
"user_settings": "User Settings"
|
||||||
"poll": "Add Poll"
|
|
||||||
},
|
},
|
||||||
"upload":{
|
"upload":{
|
||||||
"error": {
|
"error": {
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"chat": "Paikallinen Chat",
|
"chat": "Paikallinen Chat",
|
||||||
"friend_requests": "Seurauspyynnöt",
|
"friend_requests": "Seurauspyynnöt",
|
||||||
"mentions": "Maininnat",
|
"mentions": "Maininnat",
|
||||||
|
"interactions": "Interaktiot",
|
||||||
"dms": "Yksityisviestit",
|
"dms": "Yksityisviestit",
|
||||||
"public_tl": "Julkinen Aikajana",
|
"public_tl": "Julkinen Aikajana",
|
||||||
"timeline": "Aikajana",
|
"timeline": "Aikajana",
|
||||||
|
@ -54,6 +55,24 @@
|
||||||
"repeated_you": "toisti viestisi",
|
"repeated_you": "toisti viestisi",
|
||||||
"no_more_notifications": "Ei enempää ilmoituksia"
|
"no_more_notifications": "Ei enempää ilmoituksia"
|
||||||
},
|
},
|
||||||
|
"polls": {
|
||||||
|
"add_poll": "Lisää äänestys",
|
||||||
|
"add_option": "Lisää vaihtoehto",
|
||||||
|
"option": "Vaihtoehto",
|
||||||
|
"votes": "ääntä",
|
||||||
|
"vote": "Äänestä",
|
||||||
|
"type": "Äänestyksen tyyppi",
|
||||||
|
"single_choice": "Yksi valinta",
|
||||||
|
"multiple_choices": "Monivalinta",
|
||||||
|
"expiry": "Äänestyksen kesto",
|
||||||
|
"expires_in": "Päättyy {0} päästä",
|
||||||
|
"expired": "Päättyi {0} sitten"
|
||||||
|
},
|
||||||
|
"interactions": {
|
||||||
|
"favs_repeats": "Toistot ja tykkäykset",
|
||||||
|
"follows": "Uudet seuraukset",
|
||||||
|
"load_older": "Lataa vanhempia interaktioita"
|
||||||
|
},
|
||||||
"post_status": {
|
"post_status": {
|
||||||
"new_status": "Uusi viesti",
|
"new_status": "Uusi viesti",
|
||||||
"account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi",
|
"account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi",
|
||||||
|
@ -298,9 +317,9 @@
|
||||||
},
|
},
|
||||||
"upload":{
|
"upload":{
|
||||||
"error": {
|
"error": {
|
||||||
"base": "Lataus epäonnistui.",
|
"base": "Lataus epäonnistui.",
|
||||||
"file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
|
"file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
|
||||||
"default": "Yritä uudestaan myöhemmin"
|
"default": "Yritä uudestaan myöhemmin"
|
||||||
},
|
},
|
||||||
"file_size_units": {
|
"file_size_units": {
|
||||||
"B": "tavua",
|
"B": "tavua",
|
||||||
|
|
|
@ -56,11 +56,10 @@ const defaultState = {
|
||||||
backendVersion: '',
|
backendVersion: '',
|
||||||
frontendVersion: '',
|
frontendVersion: '',
|
||||||
|
|
||||||
|
pollsAvailable: true,
|
||||||
pollLimits: {
|
pollLimits: {
|
||||||
max_options: 4,
|
max_options: 4,
|
||||||
max_option_chars: 255,
|
max_option_chars: 255
|
||||||
min_expiration: 60,
|
|
||||||
max_expiration: 600
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,12 @@ export const WEEK = 7 * DAY
|
||||||
export const MONTH = 30 * DAY
|
export const MONTH = 30 * DAY
|
||||||
export const YEAR = 365.25 * DAY
|
export const YEAR = 365.25 * DAY
|
||||||
|
|
||||||
export const relativeTime = date => {
|
export const relativeTime = (date, nowThreshold = 1) => {
|
||||||
if (typeof date === 'string') date = Date.parse(date)
|
if (typeof date === 'string') date = Date.parse(date)
|
||||||
const round = Date.now() > date ? Math.floor : Math.ceil
|
const round = Date.now() > date ? Math.floor : Math.ceil
|
||||||
const d = Math.abs(Date.now() - date)
|
const d = Math.abs(Date.now() - date)
|
||||||
let r = { num: round(d / YEAR), key: 'time.years' }
|
let r = { num: round(d / YEAR), key: 'time.years' }
|
||||||
if (d < 30 * SECOND) {
|
if (d < nowThreshold * SECOND) {
|
||||||
r.num = 0
|
r.num = 0
|
||||||
r.key = 'time.now'
|
r.key = 'time.now'
|
||||||
} else if (d < MINUTE) {
|
} else if (d < MINUTE) {
|
||||||
|
@ -38,8 +38,8 @@ export const relativeTime = date => {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
export const relativeTimeShort = date => {
|
export const relativeTimeShort = (date, nowThreshold = 1) => {
|
||||||
const r = relativeTime(date)
|
const r = relativeTime(date, nowThreshold)
|
||||||
r.key += '_short'
|
r.key += '_short'
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,6 +202,7 @@ const generateColors = (input) => {
|
||||||
colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink)
|
colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink)
|
||||||
|
|
||||||
colors.faintLink = col.faintLink || Object.assign({}, col.link)
|
colors.faintLink = col.faintLink || Object.assign({}, col.link)
|
||||||
|
colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg)
|
||||||
|
|
||||||
colors.icon = mixrgb(colors.bg, colors.text)
|
colors.icon = mixrgb(colors.bg, colors.text)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue