WIP New API update

This commit is contained in:
Maxim Filippov 2019-05-09 02:29:26 +03:00
parent 96f480dcca
commit 5b6df0fd77
11 changed files with 176 additions and 54 deletions

View File

@ -194,11 +194,13 @@ const getNodeInfo = async ({ store }) => {
if (res.ok) {
const data = await res.json()
const metadata = data.metadata
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
const features = metadata.features
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })

View File

@ -22,7 +22,7 @@ export default {
},
computed: {
currentUserHasVoted () {
return this.poll.user_voted
return this.poll.voted
}
},
methods: {

View File

@ -1,16 +1,16 @@
<template>
<div class="poll-form" v-if="visible">
<hr />
<div class="poll-option"
v-for="(option, index) in options"
:key="index">
<hr>
<div class="poll-option" v-for="(option, index) in options" :key="index">
<div class="input-container">
<input
class="poll-option-input"
type="text"
:placeholder="$t('polls.option')"
@input="onUpdateOption($event, index)"
:value="option" />
:value="option"
:maxlength="maxLength"
>
</div>
<div class="icon-container">
<i class="icon-cancel" @click="onDeleteOption(index)"></i>
@ -19,29 +19,77 @@
<button
class="btn btn-default add-option"
type="button"
@click="onAddOption">{{ $t("polls.add_option") }}
</button>
@click="onAddOption"
>{{ $t("polls.add_option") }}</button>
<div class="poll-type-expiry">
<div class="poll-type">
<label for="poll-type-selector" class="select">
<select id="poll-type-selector" v-model="pollType" @change="onTypeChange">
<option value="single">{{$t('polls.single_choice')}}</option>
<option value="multiple">{{$t('polls.multiple_choices')}}</option>
</select>
<i class="icon-down-open"/>
</label>
</div>
<div class="poll-expiry">
<label for="poll-expiry-selector" class="select">
<select id="poll-expiry-selector" v-model="pollExpiry" @change="onExpiryChange">
<option v-for="(value, key) in expiryOptions" :value="key" v-bind:key="key">
{{ value }}
</option>
</select>
<i class="icon-down-open"/>
</label>
</div>
</div>
</div>
</template>
<script>
// TODO: Make this configurable
const maxOptions = 10
import { pickBy } from 'lodash'
export default {
name: 'PollForm',
props: ['visible'],
data: () => ({
pollType: 'single',
pollExpiry: '86400'
}),
computed: {
optionsLength: function () {
return this.$store.state.poll.pollOptions.length
optionsLength () {
return this.$store.state.poll.options.length
},
options: function () {
return this.$store.state.poll.pollOptions
options () {
return this.$store.state.poll.options
},
pollLimits () {
return this.$store.state.instance.pollLimits
},
maxOptions () {
return this.pollLimits.max_options
},
maxLength () {
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: {
onAddOption () {
if (this.optionsLength < maxOptions) {
if (this.optionsLength < this.maxOptions) {
this.$store.dispatch('addPollOption', { option: '' })
}
},
@ -51,7 +99,18 @@ export default {
}
},
onUpdateOption (e, index) {
this.$store.dispatch('updatePollOption', { index, option: e.target.value })
this.$store.dispatch('updatePollOption', {
index,
option: e.target.value
})
},
onTypeChange (e) {
const multiple = e.target.value === 'multiple'
this.$store.dispatch('setMultiple', { multiple })
},
onExpiryChange (e) {
this.$store.dispatch('setExpiresIn', { expiresIn: e.target.value })
}
}
}
@ -65,7 +124,7 @@ export default {
border: solid 1px #1c2735;
}
.add-option {
margin: 0.8em 0 0;
margin: 0.8em 0 0.8em;
width: 94%;
}
.poll-option {
@ -82,5 +141,16 @@ export default {
.icon-container {
width: 5%;
}
.poll-type-expiry {
display: flex;
justify-content: space-between;
margin: 0 0 0.6em;
}
.poll-expiry-custom {
display: none;
input {
width: 100%;
}
}
}
</style>

View File

@ -1,20 +1,20 @@
<template>
<div class="poll-vote" v-bind:class="containerClass">
<div :id="pollVoteId" class="poll-vote" v-bind:class="containerClass">
<div
class="poll-choice"
v-for="(pollOption, index) in poll.votes"
v-for="(pollOption, index) in poll.options"
:key="index">
<input
:disabled="loading"
type="radio"
type="checkbox"
:id="optionID(index)"
:value="pollOption.name"
:value="pollOption.title"
name="choice"
@change="onChoice">
<label :for="optionID(index)">{{pollOption.name}}</label>
<label :for="optionID(index)">{{pollOption.title}}</label>
</div>
<button class="btn btn-default poll-vote-button" @click="onVote">{{$t('polls.vote')}}</button>
</div>
</template>
<script>
@ -23,7 +23,8 @@ export default {
props: ['poll'],
data () {
return {
loading: false
loading: false,
choices: []
}
},
computed: {
@ -31,18 +32,24 @@ export default {
return {
loading: this.loading
}
},
pollVoteId: function () {
return `poll-vote-${this.poll.id}`
}
},
methods: {
optionID (index) {
return `pollOption${index}`
return `pollOption${this.poll.id}#${index}`
},
async onChoice (e) {
const pollID = this.poll.id
const optionName = e.target.value
// TODO
},
async onVote () {
this.loading = true
const poll = await this.$store.state.api.backendInteractor.vote(pollID, optionName)
const pollID = this.poll.id
const poll = await this.$store.state.api.backendInteractor.vote(pollID, this.choices)
this.loading = false
this.$emit('user-has-voted', poll)
}
@ -60,5 +67,8 @@ export default {
.poll-choice {
padding: 0.4em 0;
}
.poll-vote-button {
margin: 1em 0 0;
}
}
</style>

View File

@ -66,7 +66,7 @@ const PostStatusForm = {
? this.$store.state.instance.postContentType
: this.$store.state.config.postContentType
const pollOptions = this.$store.state.poll.pollOptions || []
const poll = this.$store.state.poll || {}
return {
dropFiles: [],
@ -79,7 +79,7 @@ const PostStatusForm = {
status: statusText,
nsfw: false,
files: [],
pollOptions,
poll,
visibility: scope,
contentType
},
@ -188,6 +188,9 @@ const PostStatusForm = {
},
safeDMEnabled () {
return this.$store.state.instance.safeDM
},
pollsAvailable () {
return this.$store.state.instance.pollsAvailable
}
},
methods: {
@ -262,7 +265,7 @@ const PostStatusForm = {
visibility: newStatus.visibility,
sensitive: newStatus.nsfw,
media: newStatus.files,
pollOptions: newStatus.pollOptions,
poll: newStatus.poll,
store: this.$store,
inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType
@ -273,7 +276,8 @@ const PostStatusForm = {
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType
contentType: newStatus.contentType,
poll: this.$store.state.poll
}
this.$store.dispatch('swapPollOptions', { options: ['', ''] })
this.pollFormVisible = false

View File

@ -74,10 +74,10 @@
</div>
</div>
</div>
<poll-form :visible="pollFormVisible" :options="newStatus.pollOptions" />
<poll-form v-if="pollsAvailable" :visible="pollFormVisible" />
<div class='form-bottom'>
<media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload>
<div class="poll-icon">
<div v-if="pollsAvailable" class="poll-icon">
<label
class="btn btn-default"
:title="$t('tool_tip.poll')"

View File

@ -110,7 +110,7 @@
<a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">{{$t("general.show_less")}}</a>
</div>
<div v-if="status.poll && status.poll.votes">
<div v-if="status.poll && status.poll.options">
<poll :poll="status.poll" />
</div>

View File

@ -72,7 +72,19 @@
"polls": {
"add_option": "Add Option",
"option": "Option",
"votes": "votes"
"votes": "votes",
"vote": "Vote",
"single_choice": "Allow one choice",
"multiple_choices": "Allow 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"
}
},
"post_status": {
"new_status": "Post new status",

View File

@ -1,19 +1,27 @@
const poll = {
state: {
pollOptions: ['', '']
options: ['', ''],
multiple: false,
expiresIn: '86400'
},
mutations: {
ADD_OPTION (state, { option }) {
state.pollOptions.push(option)
state.options.push(option)
},
UPDATE_OPTION (state, { index, option }) {
state.pollOptions[index] = option
state.options[index] = option
},
DELETE_OPTION (state, { index }) {
state.pollOptions.splice(index, 1)
state.options.splice(index, 1)
},
SWAP_OPTIONS (state, { options }) {
state.pollOptions = options
state.options = options
},
SET_MULTIPLE (state, { multiple }) {
state.multiple = multiple
},
SET_EXPIRES_IN (state, { expiresIn }) {
state.expiresIn = expiresIn
}
},
actions: {
@ -28,6 +36,12 @@ const poll = {
},
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 })
}
}
}

View File

@ -49,7 +49,7 @@ const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
const MASTODON_VOTE_URL = '/api/v1/polls/vote'
const MASTODON_VOTE_URL = id => `/api/v1/polls/${id}/votes`
const MASTODON_POLL_URL = id => `/api/v1/polls/${id}`
import { each, map } from 'lodash'
@ -573,8 +573,9 @@ const unretweet = ({ id, credentials }) => {
.then((data) => parseStatus(data))
}
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, pollOptions = [], mediaIds = [], inReplyToStatusId, contentType}) => {
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, poll, mediaIds = [], inReplyToStatusId, contentType}) => {
const form = new FormData()
const pollOptions = poll.options || []
form.append('status', status)
form.append('source', 'Pleroma FE')
@ -585,9 +586,19 @@ const postStatus = ({credentials, status, spoilerText, visibility, sensitive, po
mediaIds.forEach(val => {
form.append('media_ids[]', val)
})
pollOptions.forEach(val => {
form.append('poll_options[]', val)
if (pollOptions.some(option => option !== '')) {
const normalizedPoll = {
expires_in: poll.expiresIn,
multiple: poll.multiple
}
Object.keys(normalizedPoll).forEach(key => {
form.append(`poll[${key}]`, normalizedPoll[key])
})
pollOptions.forEach(option => {
form.append('poll[options][]', option)
})
}
if (inReplyToStatusId) {
form.append('in_reply_to_id', inReplyToStatusId)
}
@ -727,16 +738,15 @@ const markNotificationsAsSeen = ({id, credentials}) => {
}).then((data) => data.json())
}
const vote = ({pollID, optionName, credentials}) => {
const vote = ({pollID, choices, credentials}) => {
const form = new FormData()
form.append('option_name', optionName)
form.append('question_id', pollID)
form.append('choices', choices)
return promisedRequest(
MASTODON_VOTE_URL,
MASTODON_VOTE_URL(encodeURIComponent(pollID)),
{
method: 'PATCH',
method: 'POST',
headers: authHeaders(credentials),
body: form
}

View File

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