Render poll in timeline

This commit is contained in:
Maxim Filippov 2019-04-04 02:25:16 +03:00
parent a3cbe5f512
commit 93aedd6fd7
12 changed files with 128 additions and 20 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="poll-container"> <div class="poll-form">
<hr /> <hr />
<div class="poll-option" <div class="poll-option"
v-for="(option, index) in options" v-for="(option, index) in options"
@ -9,7 +9,8 @@
class="poll-option-input" class="poll-option-input"
type="text" type="text"
:placeholder="$t('polls.option')" :placeholder="$t('polls.option')"
v-model="options[index]" /> @input="onUpdateOption($event, index)"
:value="option" />
</div> </div>
<div class="icon-container"> <div class="icon-container">
<i class="icon-cancel" @click="onDeleteOption(index)"></i> <i class="icon-cancel" @click="onDeleteOption(index)"></i>
@ -28,34 +29,36 @@
const maxOptions = 10 const maxOptions = 10
export default { export default {
name: 'PollContainer', name: 'PollForm',
data () {
return {
options: ['', '']
}
},
computed: { computed: {
optionsLength: function () { optionsLength: function () {
return this.$data.options.length return this.$store.state.poll.pollOptions.length
},
options: function () {
return this.$store.state.poll.pollOptions
} }
}, },
methods: { methods: {
onAddOption () { onAddOption () {
if (this.optionsLength < maxOptions) { if (this.optionsLength < maxOptions) {
this.$data.options.push('') this.$store.commit('addPollOption', '')
} }
}, },
onDeleteOption (index) { onDeleteOption (index) {
console.log(index)
if (this.optionsLength > 1) { if (this.optionsLength > 1) {
this.$data.options.splice(index, 1) this.$store.commit('deletePollOption', index)
} }
},
onUpdateOption (e, index) {
this.$store.commit('updatePollOption', { index, option: e.target.value })
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.poll-container { .poll-form {
padding: 0 0.5em 0.6em; padding: 0 0.5em 0.6em;
hr { hr {
margin: 0 0 0.8em; margin: 0 0 0.8em;

View File

@ -0,0 +1,60 @@
<template>
<div class="poll-status">
<div class="votes">
<div
class="poll-option"
v-for="(pollOption, index) in poll.votes"
:key="index">
<div class="col">{{percentageForOption(pollOption.count)}}%</div>
<div class="col">{{pollOption.name}}</div>
<div class="col"><progress :max="totalVotesCount" :value="pollOption.count"></progress></div>
</div>
</div>
<footer>
<div class="refresh">
<a href="#">Refresh</a>&nbsp;·&nbsp;
</div>
<div class="total">
{{totalVotesCount}} {{ $t("polls.votes") }}
</div>
</footer>
</div>
</template>
<script>
export default {
name: 'PollStatus',
props: ['poll'],
computed: {
totalVotesCount () {
return this.poll.votes.reduce((acc, vote) => { return acc + vote.count }, 0)
}
},
methods: {
percentageForOption: function (count) {
return count / this.totalVotesCount * 100
}
}
}
</script>
<style lang="scss">
.poll-status {
margin: 0.7em 0;
.votes {
display: table;
width: 100%;
margin: 0 0 0.5em;
}
.poll-option {
display: table-row;
.col {
display: table-cell;
padding: 0.7em 0.5em;
}
}
footer {
display: flex;
}
}
</style>

View File

@ -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 PollContainer from '../poll/poll_container/poll_container.vue' import PollForm from '../poll/poll_form/poll_form.vue'
import PollIcon from '../poll/poll_icon/poll_icon.vue' import PollIcon from '../poll/poll_icon/poll_icon.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'
@ -34,7 +34,7 @@ const PostStatusForm = {
components: { components: {
MediaUpload, MediaUpload,
EmojiInput, EmojiInput,
PollContainer, PollForm,
PollIcon, PollIcon,
ScopeSelector ScopeSelector
}, },
@ -68,6 +68,8 @@ const PostStatusForm = {
? this.$store.state.instance.postContentType ? this.$store.state.instance.postContentType
: this.$store.state.config.postContentType : this.$store.state.config.postContentType
const pollOptions = this.$store.state.poll.pollOptions || []
return { return {
dropFiles: [], dropFiles: [],
submitDisabled: false, submitDisabled: false,
@ -79,6 +81,7 @@ const PostStatusForm = {
status: statusText, status: statusText,
nsfw: false, nsfw: false,
files: [], files: [],
pollOptions,
visibility: scope, visibility: scope,
contentType contentType
}, },
@ -257,6 +260,7 @@ const PostStatusForm = {
visibility: newStatus.visibility, visibility: newStatus.visibility,
sensitive: newStatus.nsfw, sensitive: newStatus.nsfw,
media: newStatus.files, media: newStatus.files,
pollOptions: newStatus.pollOptions,
store: this.$store, store: this.$store,
inReplyToStatusId: this.replyTo, inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType contentType: newStatus.contentType

View File

@ -71,7 +71,7 @@
</div> </div>
</div> </div>
</div> </div>
<poll-container /> <poll-form />
<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>
<poll-icon /> <poll-icon />

View File

@ -2,6 +2,7 @@ import Attachment from '../attachment/attachment.vue'
import FavoriteButton from '../favorite_button/favorite_button.vue' import FavoriteButton from '../favorite_button/favorite_button.vue'
import RetweetButton from '../retweet_button/retweet_button.vue' import RetweetButton from '../retweet_button/retweet_button.vue'
import DeleteButton from '../delete_button/delete_button.vue' import DeleteButton from '../delete_button/delete_button.vue'
import PollStatus from '../poll/poll_status/poll_status.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue' import PostStatusForm from '../post_status_form/post_status_form.vue'
import UserCard from '../user_card/user_card.vue' import UserCard from '../user_card/user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
@ -265,6 +266,7 @@ const Status = {
RetweetButton, RetweetButton,
DeleteButton, DeleteButton,
PostStatusForm, PostStatusForm,
PollStatus,
UserCard, UserCard,
UserAvatar, UserAvatar,
Gallery, Gallery,

View File

@ -110,6 +110,10 @@
<a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">{{$t("general.show_less")}}</a> <a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">{{$t("general.show_less")}}</a>
</div> </div>
<div v-if="status.poll.votes">
<poll-status :poll="status.poll" />
</div>
<div v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)" class="attachments media-body"> <div v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)" class="attachments media-body">
<attachment <attachment
class="non-gallery" class="non-gallery"

View File

@ -70,7 +70,8 @@
}, },
"polls": { "polls": {
"add_option": "Add Option", "add_option": "Add Option",
"option": "Option" "option": "Option",
"votes": "votes"
}, },
"post_status": { "post_status": {
"new_status": "Post new status", "new_status": "Post new status",

View File

@ -12,6 +12,7 @@ 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 VueTimeago from 'vue-timeago' import VueTimeago from 'vue-timeago'
import VueI18n from 'vue-i18n' import VueI18n from 'vue-i18n'
@ -68,7 +69,8 @@ const persistedStateOptions = {
chat: chatModule, chat: chatModule,
oauth: oauthModule, oauth: oauthModule,
mediaViewer: mediaViewerModule, mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule oauthTokens: oauthTokensModule,
poll: pollModule
}, },
plugins: [persistedState, pushNotifications], plugins: [persistedState, pushNotifications],
strict: false // Socket modifies itself, let's ignore this for now. strict: false // Socket modifies itself, let's ignore this for now.

18
src/modules/poll.js Normal file
View File

@ -0,0 +1,18 @@
const poll = {
state: {
pollOptions: ['', '']
},
mutations: {
addPollOption (state, option) {
state.pollOptions.push(option)
},
updatePollOption (state, { index, option }) {
state.pollOptions[index] = option
},
deletePollOption (state, index) {
state.pollOptions.splice(index, 1)
}
}
}
export default poll

View File

@ -487,7 +487,7 @@ const unretweet = ({ id, credentials }) => {
.then((data) => parseStatus(data)) .then((data) => parseStatus(data))
} }
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds = [], inReplyToStatusId, contentType}) => { const postStatus = ({credentials, status, spoilerText, visibility, sensitive, pollOptions = [], mediaIds = [], inReplyToStatusId, contentType}) => {
const form = new FormData() const form = new FormData()
form.append('status', status) form.append('status', status)
@ -499,6 +499,9 @@ const postStatus = ({credentials, status, spoilerText, visibility, sensitive, me
mediaIds.forEach(val => { mediaIds.forEach(val => {
form.append('media_ids[]', val) form.append('media_ids[]', val)
}) })
pollOptions.forEach(val => {
form.append('poll_options[]', val)
})
if (inReplyToStatusId) { if (inReplyToStatusId) {
form.append('in_reply_to_id', inReplyToStatusId) form.append('in_reply_to_id', inReplyToStatusId)
} }

View File

@ -193,6 +193,8 @@ export const parseStatus = (data) => {
output.summary_html = addEmojis(data.spoiler_text, data.emojis) output.summary_html = addEmojis(data.spoiler_text, data.emojis)
output.external_url = data.url output.external_url = data.url
output.poll = data.poll
// output.is_local = ??? missing // output.is_local = ??? missing
} else { } else {
output.favorited = data.favorited output.favorited = data.favorited

View File

@ -1,10 +1,19 @@
import { map } from 'lodash' import { map } from 'lodash'
import apiService from '../api/api.service.js' import apiService from '../api/api.service.js'
const postStatus = ({ store, status, spoilerText, visibility, sensitive, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => { const postStatus = ({ store, status, spoilerText, visibility, sensitive, pollOptions = [], media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {
const mediaIds = map(media, 'id') const mediaIds = map(media, 'id')
return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType}) return apiService.postStatus({
credentials: store.state.users.currentUser.credentials,
status,
spoilerText,
visibility,
sensitive,
mediaIds,
inReplyToStatusId,
contentType,
pollOptions})
.then((data) => { .then((data) => {
if (!data.error) { if (!data.error) {
store.dispatch('addNewStatuses', { store.dispatch('addNewStatuses', {