polish the poll form

This commit is contained in:
shpuld 2019-06-09 22:33:25 +03:00
parent e2dc0d85fc
commit 1a989d0661
8 changed files with 144 additions and 120 deletions

View File

@ -264,6 +264,15 @@ option {
background-color: var(--bg, $fallback--bg); background-color: var(--bg, $fallback--bg);
} }
.hide-number-spinner {
-moz-appearance: textfield;
&[type=number]::-webkit-inner-spin-button,
&[type=number]::-webkit-outer-spin-button {
opacity: 0;
display: none;
}
}
i[class*=icon-] { i[class*=icon-] {
color: $fallback--icon; color: $fallback--icon;
color: var(--icon, $fallback--icon) color: var(--icon, $fallback--icon)

View File

@ -1,6 +1,5 @@
<template> <template>
<div class="poll-form" v-if="visible"> <div class="poll-form" v-if="visible">
<hr>
<div class="poll-option" v-for="(option, index) in options" :key="index"> <div class="poll-option" v-for="(option, index) in options" :key="index">
<div class="input-container"> <div class="input-container">
<input <input
@ -8,23 +7,28 @@
type="text" type="text"
:placeholder="$t('polls.option')" :placeholder="$t('polls.option')"
:maxlength="maxLength" :maxlength="maxLength"
:id="`poll-${index}`"
v-model="options[index]" v-model="options[index]"
@change="updatePollToParent" @change="updatePollToParent"
@keydown.enter.stop.prevent="nextOption(index)"
> >
</div> </div>
<div class="icon-container" v-if="options.length > 2"> <div class="icon-container" v-if="options.length > 2">
<i class="icon-cancel" @click="deleteOption(index)"></i> <i class="icon-cancel" @click="deleteOption(index)"></i>
</div> </div>
</div> </div>
<button <a
class="btn btn-default add-option" v-if="options.length < maxOptions"
type="button" class="add-option"
@click="addOption" @click="addOption"
>{{ $t("polls.add_option") }}</button> >
<i class="icon-plus" />
{{ $t("polls.add_option") }}
</a>
<div class="poll-type-expiry"> <div class="poll-type-expiry">
<div class="poll-type"> <div class="poll-type">
<label for="poll-type-selector" class="select"> <label for="poll-type-selector" class="select">
<select id="poll-type-selector" 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>
<option value="multiple">{{$t('polls.multiple_choices')}}</option> <option value="multiple">{{$t('polls.multiple_choices')}}</option>
</select> </select>
@ -32,10 +36,18 @@
</label> </label>
</div> </div>
<div class="poll-expiry"> <div class="poll-expiry">
<label for="poll-expiry-selector" class="select"> <input
<select id="poll-expiry-selector" v-model="pollExpiry" @change="updatePollToParent"> type="number"
<option v-for="(value, key) in expiryOptions" :value="key" v-bind:key="key"> class="expiry-amount hide-number-spinner"
{{ value }} min="1"
max="120"
v-model="expiryAmount"
@change="expiryAmountChange"
>
<label class="expiry-unit select">
<select v-model="expiryUnit" @change="updatePollToParent">
<option v-for="unit in expiryUnits" :value="unit">
{{ $t(`time.${unit}_short`, ['']) }}
</option> </option>
</select> </select>
<i class="icon-down-open"/> <i class="icon-down-open"/>
@ -46,15 +58,17 @@
</template> </template>
<script> <script>
import { pickBy } from 'lodash' import * as DateUtils from 'src/services/date_utils/date_utils'
export default { export default {
name: 'PollForm', name: 'PollForm',
props: ['visible'], props: ['visible'],
data: () => ({ data: () => ({
pollType: 'single', pollType: 'single',
pollExpiry: '86400', options: ['', ''],
options: ['', ''] expiryAmount: 1,
expiryUnit: 'minutes',
expiryUnits: ['minutes', 'hours', 'days']
}), }),
computed: { computed: {
pollLimits () { pollLimits () {
@ -65,39 +79,57 @@ export default {
}, },
maxLength () { maxLength () {
return this.pollLimits.max_option_chars return this.pollLimits.max_option_chars
},
expiryOptions () {
const minExpiration = this.pollLimits.min_expiration
const maxExpiration = this.pollLimits.max_expiration
const expiryOptions = this.$t('polls.expiry_options')
return pickBy(expiryOptions, (_value, key) => {
if (key === 'custom') {
return true
}
const parsedKey = parseInt(key)
return (parsedKey >= minExpiration && parsedKey <= maxExpiration)
})
} }
}, },
methods: { 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 () { addOption () {
if (this.options.length < this.maxOptions) { if (this.options.length < this.maxOptions) {
this.options.push('') this.options.push('')
return true
} }
return false
}, },
deleteOption (index, event) { deleteOption (index, event) {
if (this.options.length > 2) { if (this.options.length > 2) {
this.options.splice(index, 1) this.options.splice(index, 1)
} }
}, },
expiryAmountChange () {
this.expiryAmount = Math.max(1, this.expiryAmount)
this.expiryAmount = Math.min(120, this.expiryAmount)
this.updatePollToParent()
},
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', { this.$emit('update-poll', {
options: this.options, options: this.options,
expiresIn: this.pollExpiry, multiple: this.pollType === 'multiple',
multiple: this.pollType === 'multiple' expiresIn
}) })
} }
} }
@ -105,40 +137,70 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import '../../../_variables.scss';
.poll-form { .poll-form {
padding: 0 0.5em 0.6em; display: flex;
hr { flex-direction: column;
margin: 0 0 0.8em; padding: 0 0.5em 0.5em;
border: solid 1px #1c2735;
}
.add-option { .add-option {
margin: 0.8em 0 0.8em; align-self: flex-start;
width: 94%; padding-top: 0.25em;
cursor: pointer;
color: $fallback--faint;
color: var(--faint, $fallback--faint);
} }
.poll-option { .poll-option {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
justify-content: space-between; justify-content: space-between;
margin-bottom: 0.25em;
} }
.input-container { .input-container {
width: 94%; width: 100%;
input { input {
width: 100%; width: 100%;
} }
} }
.icon-container { .icon-container {
width: 5%; // Move the icon over the input box
width: 2em;
margin-left: -2em;
z-index: 1;
} }
.poll-type-expiry { .poll-type-expiry {
margin-top: 0.5em;
display: flex; display: flex;
justify-content: space-between;
margin: 0 0 0.6em;
}
.poll-expiry-custom {
display: none;
input {
width: 100%; width: 100%;
} }
.poll-type {
margin-right: 0.75em;
flex: 1 1 60%;
.select {
border: none;
box-shadow: none;
background-color: transparent;
}
}
.poll-expiry {
display: flex;
.expiry-amount {
width: 3em;
}
.expiry-unit {
border: none;
box-shadow: none;
background-color: transparent;
}
} }
} }
</style> </style>

View File

@ -82,7 +82,7 @@ const PostStatusForm = {
contentType contentType
}, },
caret: 0, caret: 0,
pollFormVisible: false pollFormVisible: true
} }
}, },
computed: { computed: {
@ -259,6 +259,8 @@ const PostStatusForm = {
} }
} }
const poll = this.pollFormVisible ? this.poll : {}
this.posting = true this.posting = true
statusPoster.postStatus({ statusPoster.postStatus({
status: newStatus.status, status: newStatus.status,
@ -266,10 +268,10 @@ const PostStatusForm = {
visibility: newStatus.visibility, visibility: newStatus.visibility,
sensitive: newStatus.nsfw, sensitive: newStatus.nsfw,
media: newStatus.files, media: newStatus.files,
poll: newStatus.poll,
store: this.$store, store: this.$store,
inReplyToStatusId: this.replyTo, inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType contentType: newStatus.contentType,
poll
}).then((data) => { }).then((data) => {
if (!data.error) { if (!data.error) {
this.newStatus = { this.newStatus = {
@ -282,6 +284,7 @@ const PostStatusForm = {
} }
this.pollFormVisible = false this.pollFormVisible = false
this.$refs.mediaUpload.clearFile() this.$refs.mediaUpload.clearFile()
this.$refs.pollForm.clear()
this.$emit('posted') this.$emit('posted')
let el = this.$el.querySelector('textarea') let el = this.$el.querySelector('textarea')
el.style.height = 'auto' el.style.height = 'auto'

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="post-status-form"> <div class="post-status-form">
<form @submit.prevent="postStatus(newStatus)"> <form @submit.prevent="postStatus(newStatus)" autocomplete="off">
<div class="form-group" > <div class="form-group" >
<i18n <i18n
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'" v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
@ -92,7 +92,12 @@
</div> </div>
</div> </div>
</div> </div>
<poll-form v-if="pollsAvailable" :visible="pollFormVisible" @update-poll="setPoll" /> <poll-form
ref="pollForm"
v-if="pollsAvailable"
:visible="pollFormVisible"
@update-poll="setPoll"
/>
<div class='form-bottom'> <div class='form-bottom'>
<media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload> <media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload>
<div v-if="pollsAvailable" class="poll-icon"> <div v-if="pollsAvailable" class="poll-icon">
@ -100,7 +105,7 @@
class="btn btn-default" class="btn btn-default"
:title="$t('tool_tip.poll')" :title="$t('tool_tip.poll')"
@click="togglePollForm"> @click="togglePollForm">
<i class="icon-chart-bar"></i> <i class="icon-chart-bar" :class="pollFormVisible && 'selected'" />
</label> </label>
</div> </div>
<p v-if="isOverLengthLimit" class="error">{{ charactersLeft }}</p> <p v-if="isOverLengthLimit" class="error">{{ charactersLeft }}</p>
@ -300,6 +305,11 @@
.poll-icon { .poll-icon {
font-size: 26px; font-size: 26px;
flex: 1; flex: 1;
.selected {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
} }
.icon-chart-bar { .icon-chart-bar {

View File

@ -84,17 +84,8 @@
"option": "Option", "option": "Option",
"votes": "votes", "votes": "votes",
"vote": "Vote", "vote": "Vote",
"single_choice": "Allow one choice", "single_choice": "Single choice",
"multiple_choices": "Allow multiple choices", "multiple_choices": "Multiple choices"
"expiry_options": {
"300": "5 minutes",
"1800": "30 minutes",
"3600": "1 hour",
"21600": "6 hours",
"86400": "1 day",
"259200": "3 days",
"604800": "7 days"
}
}, },
"interactions": { "interactions": {
"favs_repeats": "Repeats and Favorites", "favs_repeats": "Repeats and Favorites",

View File

@ -12,7 +12,6 @@ import chatModule from './modules/chat.js'
import oauthModule from './modules/oauth.js' import oauthModule from './modules/oauth.js'
import mediaViewerModule from './modules/media_viewer.js' import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js' import oauthTokensModule from './modules/oauth_tokens.js'
import pollModule from './modules/poll.js'
import reportsModule from './modules/reports.js' import reportsModule from './modules/reports.js'
import VueI18n from 'vue-i18n' import VueI18n from 'vue-i18n'
@ -69,7 +68,6 @@ const persistedStateOptions = {
oauth: oauthModule, oauth: oauthModule,
mediaViewer: mediaViewerModule, mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule, oauthTokens: oauthTokensModule,
poll: pollModule,
reports: reportsModule reports: reportsModule
}, },
plugins: [persistedState, pushNotifications], plugins: [persistedState, pushNotifications],

View File

@ -60,7 +60,7 @@ const defaultState = {
max_options: 4, max_options: 4,
max_option_chars: 255, max_option_chars: 255,
min_expiration: 60, min_expiration: 60,
max_expiration: 300 max_expiration: 600
} }
} }

View File

@ -1,49 +0,0 @@
const poll = {
state: {
options: ['', ''],
multiple: false,
expiresIn: '86400'
},
mutations: {
ADD_OPTION (state, { option }) {
state.options.push(option)
},
UPDATE_OPTION (state, { index, option }) {
state.options[index] = option
},
DELETE_OPTION (state, { index }) {
state.options.splice(index, 1)
},
SWAP_OPTIONS (state, { options }) {
state.options = options
},
SET_MULTIPLE (state, { multiple }) {
state.multiple = multiple
},
SET_EXPIRES_IN (state, { expiresIn }) {
state.expiresIn = expiresIn
}
},
actions: {
addPollOption (store, { option }) {
store.commit('ADD_OPTION', { option })
},
updatePollOption (store, { index, option }) {
store.commit('UPDATE_OPTION', { index, option })
},
deletePollOption (store, { index }) {
store.commit('DELETE_OPTION', { index })
},
swapPollOptions (store, { options }) {
store.commit('SWAP_OPTIONS', { options })
},
setMultiple (store, { multiple }) {
store.commit('SET_MULTIPLE', { multiple })
},
setExpiresIn (store, { expiresIn }) {
store.commit('SET_EXPIRES_IN', { expiresIn })
}
}
}
export default poll