Merge pull request #7761 from nextcloud/feature/polls-follow-up

Polls cleanup
This commit is contained in:
Joas Schilling 2022-08-26 22:35:21 +02:00 коммит произвёл GitHub
Родитель 4482bead6a f954227df7
Коммит 3a94a969f9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 180 добавлений и 43 удалений

Просмотреть файл

@ -410,6 +410,7 @@ class SystemMessage {
}
} elseif ($message === 'object_shared') {
$parsedParameters['object'] = $parameters['metaData'];
$parsedParameters['object']['id'] = (string) $parsedParameters['object']['id'];
$parsedMessage = '{object}';
if (isset($parsedParameters['object']['type'])
@ -494,12 +495,14 @@ class SystemMessage {
}
} elseif ($message === 'poll_closed') {
$parsedParameters['poll'] = $parameters['poll'];
$parsedParameters['poll']['id'] = (string) $parsedParameters['poll']['id'];
$parsedMessage = $this->l->t('{actor} closed the poll {poll}');
if ($currentUserIsActor) {
$parsedMessage = $this->l->t('You closed the poll {poll}');
}
} elseif ($message === 'poll_voted') {
$parsedParameters['poll'] = $parameters['poll'];
$parsedParameters['poll']['id'] = (string) $parsedParameters['poll']['id'];
$parsedMessage = $this->l->t('Someone voted on the poll {poll}');
unset($parsedParameters['actor']);
} else {

Просмотреть файл

@ -55,6 +55,14 @@ the main body of the message as well as a quote.
<RichText :text="message" :arguments="richParameters" :autolink="true" />
<CallButton />
</div>
<div v-else-if="showResultsButton" class="message-body__main__text system-message">
<RichText :text="message" :arguments="richParameters" :autolink="true" />
<!-- Displays only the "see results" button with the results modal -->
<Poll :id="messageParameters.poll.id"
:poll-name="messageParameters.poll.name"
:token="token"
:show-as-button="true" />
</div>
<div v-else-if="isDeletedMessage" class="message-body__main__text deleted-message">
<RichText :text="message" :arguments="richParameters" :autolink="true" />
</div>
@ -220,6 +228,7 @@ export default {
NcEmojiPicker,
EmoticonOutline,
NcPopover,
Poll,
},
mixins: [
@ -457,6 +466,10 @@ export default {
&& !this.isInCall
},
showResultsButton() {
return this.systemMessage === 'poll_closed'
},
isSingleEmoji() {
const regex = emojiRegex()
let match
@ -503,7 +516,7 @@ export default {
component: Location,
props: this.messageParameters[p],
}
} else if (type === 'talk-poll') {
} else if (type === 'talk-poll' && this.systemMessage !== 'poll_closed') {
const props = Object.assign({}, this.messageParameters[p])
// Add the token to the component props
props.token = this.token

Просмотреть файл

@ -21,11 +21,13 @@
<template>
<div class="wrapper">
<a v-observe-visibility="getPollData"
<!-- Poll card -->
<a v-if="!showAsButton"
v-observe-visibility="getPollData"
:aria-label="t('spreed', 'Poll')"
class="poll"
role="button"
@click="showModal = true">
@click="openPoll">
<div class="poll__header">
<PollIcon :size="20" />
<p>
@ -33,14 +35,22 @@
</p>
</div>
<div class="poll__footer">
{{ t('spreed', 'Poll ・ Click to vote') }}
{{ pollFooterText }}
</div>
</a>
<!-- Poll results button in system message -->
<div v-else class="poll-closed">
<NcButton type="secondary" @click="openPoll">
{{ t('spreed', 'See results') }}
</NcButton>
</div>
<!-- voting and results dialog -->
<NcModal v-if="vote !== undefined && showModal"
size="small"
@close="showModal = false">
@close="dismissModal">
<div class="poll__modal">
<!-- First screen, displayed while voting-->
<template v-if="modalPage === 'voting'">
@ -78,12 +88,15 @@
</div>
<div class="poll__modal-actions">
<NcButton type="tertiary" @click="dismissModal">
{{ t('spreed', 'Dismiss') }}
</NcButton>
<!-- create poll button-->
<!-- Submit vote button-->
<NcButton type="primary" :disabled="!canSubmitVote" @click="submitVote">
{{ t('spreed', 'Submit') }}
{{ t('spreed', 'Submit vote') }}
</NcButton>
<!-- End poll button-->
<NcButton v-if="canEndPoll"
type="error"
@click="endPoll">
{{ t('spreed', 'End poll') }}
</NcButton>
</div>
</template>
@ -98,7 +111,12 @@
</h2>
</div>
<div class="poll__summary">
{{ n('spreed', 'Poll results • %n vote', 'Poll results • %n votes', votersNumber) }}
<template v-if="currentUserIsPollCreator || currentUserIsModerator || pollIsPublic">
{{ n('spreed', 'Poll results • %n vote', 'Poll results • %n votes', votersNumber) }}
</template>
<template v-else-if="selfHasVoted">
{{ t('spreed', 'Poll ・ You voted') }}
</template>
</div>
<div class="results__options">
<div v-for="(option, index) in options"
@ -112,20 +130,26 @@
{{ getVotePercentage(index) + '%' }}
</p>
</div>
<NcProgressBar :value="getVotePercentage(index)" size="medium" />
<p v-if="selfHasVotedOption(index)" class="results__option-subtitle">
{{ t('spreed','You voted') }}
</p>
<NcProgressBar class="results__option-progress"
:value="getVotePercentage(index)"
size="medium" />
</div>
</div>
<div v-if="pollIsOpen"
class="poll__modal-actions">
<NcButton type="tertiary"
<!-- Vote again-->
<NcButton type="secondary"
@click="modalPage = 'voting'">
{{ t('spreed', 'Back') }}
{{ t('spreed', 'Change your vote') }}
</NcButton>
<!-- create poll button-->
<NcButton v-if="canClosePoll"
<!-- End poll button-->
<NcButton v-if="canEndPoll"
type="error"
@click="closePoll">
{{ t('spreed', 'Close poll') }}
@click="endPoll">
{{ t('spreed', 'End poll') }}
</NcButton>
</div>
</template>
@ -162,7 +186,7 @@ export default {
},
id: {
type: Number,
type: String,
required: true,
},
@ -170,6 +194,11 @@ export default {
type: String,
required: true,
},
showAsButton: {
type: Boolean,
default: false,
},
},
data() {
@ -207,6 +236,19 @@ export default {
},
selfHasVoted() {
if (this.pollLoaded) {
if (typeof this.votedSelf === 'object') {
return this.votedSelf.length > 0
} else {
return !!this.votedSelf
}
} else {
return undefined
}
},
// The actual vote of the user as returned from the server
votedSelf() {
return this.pollLoaded ? this.poll.votedSelf : undefined
},
@ -214,6 +256,10 @@ export default {
return this.pollLoaded ? this.poll.resultMode : undefined
},
pollIsPublic() {
return this.resultMode === 0
},
status() {
return this.pollLoaded ? this.poll.status : undefined
},
@ -222,12 +268,24 @@ export default {
return this.status === 0
},
pollIsClosed() {
return this.status === 1
},
checkboxRadioSwitchType() {
return this.poll.maxVotes === 0 ? 'checkbox' : 'radio'
if (this.pollLoaded) {
return this.poll.maxVotes === 0 ? 'checkbox' : 'radio'
} else {
return undefined
}
},
canSubmitVote() {
return this.vote !== undefined && this.vote !== '' && this.vote !== []
if (typeof this.vote === 'object') {
return this.vote.length > 0
} else {
return this.vote !== undefined && this.vote !== ''
}
},
getVotePercentage() {
@ -235,7 +293,7 @@ export default {
if (this.pollVotes[`option-${index}`] === undefined) {
return 0
}
return this.pollVotes[`option-${index}`] / this.votersNumber * 100
return parseInt(this.pollVotes[`option-${index}`] / this.votersNumber * 100)
}
},
@ -269,17 +327,36 @@ export default {
return [PARTICIPANT.TYPE.OWNER, PARTICIPANT.TYPE.MODERATOR, PARTICIPANT.TYPE.GUEST_MODERATOR].indexOf(this.participantType) !== -1
},
canClosePoll() {
return this.currentUserIsPollCreator || this.currentUserIsModerator
canEndPoll() {
return (this.currentUserIsPollCreator || this.currentUserIsModerator) && this.pollIsOpen
},
pollFooterText() {
if (this.pollIsOpen) {
return this.selfHasVoted ? t('spreed', 'Poll ・ You voted') : t('spreed', 'Poll ・ Click to vote')
} else if (this.pollIsClosed) {
return t('spreed', 'Poll ・ Closed')
}
return ''
},
},
watch: {
pollLoaded() {
this.setComponentData()
this.setVoteData()
},
modalPage(value) {
if (value === 'voting') {
this.setVoteData()
}
},
},
mounted() {
this.setVoteData()
},
methods: {
@ -292,13 +369,27 @@ export default {
}
},
setComponentData() {
setVoteData() {
if (this.checkboxRadioSwitchType === 'radio') {
this.vote = ''
if (this.selfHasVoted) {
this.vote = this.votedSelf[0].toString()
}
} else {
this.vote = []
if (this.selfHasVoted) {
this.vote = this.votedSelf.map(element => element.toString())
}
}
this.pollIsOpen ? this.modalPage = 'voting' : this.modalPage = 'results'
},
openPoll() {
if (this.selfHasVoted || this.pollIsClosed) {
this.modalPage = 'results'
} else {
this.modalPage = 'voting'
}
this.showModal = true
},
dismissModal() {
@ -321,11 +412,20 @@ export default {
this.modalPage = 'results'
},
closePoll() {
this.$store.dispatch('closePoll', {
endPoll() {
this.$store.dispatch('endPoll', {
token: this.token,
pollId: this.id,
})
this.modalPage = 'results'
},
selfHasVotedOption(index) {
if (this.votedSelf.includes(index)) {
return true
} else {
return false
}
},
},
}
@ -354,10 +454,8 @@ export default {
gap: 8px;
white-space: normal;
align-items: flex-start;
position: sticky;
top: 0;
padding: 0 0 8px 0;
background-color: var(--color-main-background);
word-wrap: anywhere;
padding-top: 20px;
@ -374,7 +472,7 @@ export default {
&__modal {
position: relative;
padding: 0 20px;
padding: 20px 20px 0 20px;
}
&__modal-title {
@ -393,7 +491,7 @@ export default {
bottom: 0;
display: flex;
justify-content: center;
gap: 4px;
gap: 8px;
padding: 12px 0 0 0;
background-color: var(--color-main-background);
padding-bottom: 20px;
@ -415,7 +513,13 @@ export default {
.results__option {
display: flex;
flex-direction: column;
gap: 8px;
&-subtitle {
color: var(--color-text-maxcontrast);
}
&-progress {
margin-top: 4px;
}
}
.results__option-title {
@ -428,6 +532,12 @@ export default {
}
}
.poll-closed {
display: flex;
justify-content: center;
margin-top: 4px;
}
// Upstream
::v-deep .checkbox-radio-switch {
&__label {

Просмотреть файл

@ -37,6 +37,7 @@
</p>
<PollOption v-for="option, index in pollOptions"
:key="index"
:ref="`pollOption${index}`"
class="simple-polls-editor__option"
:value.sync="pollOptions[index]"
:placeholder="t('spreed', 'Answer {option}', {option: index + 1})"
@ -123,6 +124,11 @@ export default {
addOption() {
this.pollOptions.push('')
this.$nextTick(() => {
const indexOfNewPollOption = this.pollOptions.length - 1
const refOfNewPollOption = `pollOption${indexOfNewPollOption}`
this.$refs[refOfNewPollOption][0].$el.querySelector('.input-field__input').focus()
})
},
async createPoll() {

Просмотреть файл

@ -68,13 +68,13 @@ const pollService = {
},
/**
* Closes the poll
* Ends the poll
*
* @param {string} token The conversation token
* @param {number} pollId ID of the poll
* @return {object} The poll object
*/
async closePoll(token, pollId) {
async endPoll(token, pollId) {
return axios.delete(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId }))
},
}

Просмотреть файл

@ -422,7 +422,6 @@ const actions = {
|| lastMessage.actorId === 'changelog')
&& lastMessage.systemMessage !== 'reaction'
&& lastMessage.systemMessage !== 'poll_voted'
&& lastMessage.systemMessage !== 'poll_closed'
&& lastMessage.systemMessage !== 'reaction_deleted'
&& lastMessage.systemMessage !== 'reaction_revoked'
&& lastMessage.systemMessage !== 'message_deleted'

Просмотреть файл

@ -146,7 +146,6 @@ const getters = {
|| message.systemMessage === 'reaction_deleted'
|| message.systemMessage === 'reaction_revoked'
|| message.systemMessage === 'poll_voted'
|| message.systemMessage === 'poll_closed'
) {
return false
} else {
@ -423,6 +422,13 @@ const actions = {
})
}
if (message.systemMessage === 'poll_closed') {
context.dispatch('getPollData', {
token: message.token,
pollId: message.messageParameters.poll.id,
})
}
context.commit('addMessage', message)
if ((message.messageType === 'comment' && message.message === '{file}' && message.messageParameters?.file)

Просмотреть файл

@ -114,16 +114,16 @@ const actions = {
}
},
async closePoll(context, { token, pollId }) {
console.debug('Closing poll')
async endPoll(context, { token, pollId }) {
console.debug('Ending poll')
try {
const response = await pollService.closePoll(token, pollId)
const response = await pollService.endPoll(token, pollId)
const poll = response.data.ocs.data
context.dispatch('addPoll', { token, poll })
console.debug('polldata', response)
} catch (error) {
console.error(error)
showError(t('spreed', 'An error occurred while closing the poll'))
showError(t('spreed', 'An error occurred while ending the poll'))
}
},
}

Просмотреть файл

@ -1934,7 +1934,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$result = preg_match('/POLL_ID\(([^)]+)\)/', $expected[$i]['messageParameters'], $matches);
if ($result) {
$expected[$i]['messageParameters'] = str_replace($matches[0], self::$questionToPollId[$matches[1]], $expected[$i]['messageParameters']);
$expected[$i]['messageParameters'] = str_replace($matches[0], '"' . self::$questionToPollId[$matches[1]] . '"', $expected[$i]['messageParameters']);
}
}