diff --git a/lib/Chat/Parser/SystemMessage.php b/lib/Chat/Parser/SystemMessage.php
index 2f4d128d051..1124dd1e64f 100644
--- a/lib/Chat/Parser/SystemMessage.php
+++ b/lib/Chat/Parser/SystemMessage.php
@@ -410,6 +410,7 @@ public function parseMessage(Message $chatMessage): void {
}
} 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 @@ public function parseMessage(Message $chatMessage): void {
}
} 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 {
diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue
index 5e55dac4332..1ea27ae873e 100644
--- a/src/components/MessagesList/MessagesGroup/Message/Message.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue
@@ -55,6 +55,14 @@ the main body of the message as well as a quote.
+
@@ -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
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue
index 38d7a541629..3b94890d011 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue
@@ -21,11 +21,13 @@
-
+
+ @click="openPoll">
+
+
+
+
+ {{ t('spreed', 'See results') }}
+
+
+
+ @close="dismissModal">
@@ -78,12 +88,15 @@
-
- {{ t('spreed', 'Dismiss') }}
-
-
+
- {{ t('spreed', 'Submit') }}
+ {{ t('spreed', 'Submit vote') }}
+
+
+
+ {{ t('spreed', 'End poll') }}
@@ -98,7 +111,12 @@
- {{ n('spreed', 'Poll results • %n vote', 'Poll results • %n votes', votersNumber) }}
+
+ {{ n('spreed', 'Poll results • %n vote', 'Poll results • %n votes', votersNumber) }}
+
+
+ {{ t('spreed', 'Poll ・ You voted') }}
+
-
+
+ {{ t('spreed','You voted') }}
+
+
-
+
- {{ t('spreed', 'Back') }}
+ {{ t('spreed', 'Change your vote') }}
-
-
+
- {{ t('spreed', 'Close poll') }}
+ @click="endPoll">
+ {{ t('spreed', 'End poll') }}
@@ -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,19 +327,38 @@ 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: {
getPollData() {
if (!this.pollLoaded) {
@@ -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 {
diff --git a/src/components/NewMessageForm/SimplePollsEditor/SimplePollsEditor.vue b/src/components/NewMessageForm/SimplePollsEditor/SimplePollsEditor.vue
index 07fa4a580cc..d63aa48f9d1 100644
--- a/src/components/NewMessageForm/SimplePollsEditor/SimplePollsEditor.vue
+++ b/src/components/NewMessageForm/SimplePollsEditor/SimplePollsEditor.vue
@@ -37,6 +37,7 @@
{
+ const indexOfNewPollOption = this.pollOptions.length - 1
+ const refOfNewPollOption = `pollOption${indexOfNewPollOption}`
+ this.$refs[refOfNewPollOption][0].$el.querySelector('.input-field__input').focus()
+ })
},
async createPoll() {
diff --git a/src/services/pollService.js b/src/services/pollService.js
index ca973afc37f..f218daee0f9 100644
--- a/src/services/pollService.js
+++ b/src/services/pollService.js
@@ -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 }))
},
}
diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js
index 810278f7834..3ddbfa1acd1 100644
--- a/src/store/conversationsStore.js
+++ b/src/store/conversationsStore.js
@@ -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'
diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js
index ccc7d1b6f4a..b19b0054501 100644
--- a/src/store/messagesStore.js
+++ b/src/store/messagesStore.js
@@ -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)
diff --git a/src/store/pollStore.js b/src/store/pollStore.js
index 1d0d0c861fd..a0a7fcee24e 100644
--- a/src/store/pollStore.js
+++ b/src/store/pollStore.js
@@ -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'))
}
},
}
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 738a691eebd..ba930a1956d 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -1934,7 +1934,7 @@ protected function compareDataResponse(TableNode $formData = null) {
$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']);
}
}