Poll WIP (#231):

* frontend WIP
* backend fix
This commit is contained in:
John Livingston 2024-07-04 15:15:28 +02:00
parent 4168b2ce41
commit ffb8ac8ddc
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
8 changed files with 127 additions and 18 deletions

View File

@ -10,21 +10,49 @@ import '../styles/poll.scss'
export default class MUCPollView extends CustomElement { export default class MUCPollView extends CustomElement {
static get properties () { static get properties () {
return { return {
model: { type: Object, attribute: true } model: { type: Object, attribute: true },
collapsed: { type: Boolean, attribute: false },
buttonDisabled: { type: Boolean, attribute: false }
} }
} }
async initialize () { async initialize () {
this.collapsed = false
this.buttonDisabled = false
if (!this.model) { if (!this.model) {
return return
} }
this.listenTo(this.model, 'change:current_poll', () => this.requestUpdate()) this.listenTo(this.model, 'change:current_poll', () => {
this.buttonDisabled = false
this.requestUpdate()
})
} }
render () { render () {
const currentPoll = this.model?.get('current_poll') const currentPoll = this.model?.get('current_poll')
return tplPoll(this, currentPoll) return tplPoll(this, currentPoll)
} }
toggle () {
this.collapsed = !this.collapsed
}
voteFor (choice) {
if (this.buttonDisabled) { return }
const currentPoll = this.model?.get('current_poll')
if (!currentPoll) { return }
if (currentPoll.over) { return }
console.info('User has voted for choice: ', choice)
// We disable vote buttons until next refresh:
this.buttonDisabled = true
this.requestUpdate()
this.model.sendMessage({
body: '!' + choice.choice
})
}
} }
api.elements.define('livechat-converse-muc-poll', MUCPollView) api.elements.define('livechat-converse-muc-poll', MUCPollView)

View File

@ -45,6 +45,7 @@ converse.plugins.add('livechat-converse-poll', {
votes: parseInt(poll.getAttribute('votes') ?? 0), votes: parseInt(poll.getAttribute('votes') ?? 0),
over: poll.hasAttribute('over'), over: poll.hasAttribute('over'),
endDate: endDate, endDate: endDate,
time: attrs.time, // this is to be sure that we update the custom element (needed to re-enable buttons)
choices: choices.map(c => { choices: choices.map(c => {
return { return {
label: c.textContent, label: c.textContent,
@ -81,9 +82,10 @@ converse.plugins.add('livechat-converse-poll', {
} }
console.info('Got a poll message, setting it as the current_poll') console.info('Got a poll message, setting it as the current_poll')
this.set('current_poll', attrs.current_poll)
// this will be displayed by the livechat-converse-muc-poll custom element, // this will be displayed by the livechat-converse-muc-poll custom element,
// which is inserted in the DOM by the muc.js template overload. // which is inserted in the DOM by the muc.js template overload.
this.set('current_poll', attrs.current_poll)
if (attrs.current_poll.over) { if (attrs.current_poll.over) {
console.info('The poll is over, displaying the message in the chat') console.info('The poll is over, displaying the message in the chat')
return this.__super__.onMessage(attrs) return this.__super__.onMessage(attrs)

View File

@ -13,12 +13,25 @@
border: 1px solid var(--peertube-menu-background); border: 1px solid var(--peertube-menu-background);
margin: 5px; margin: 5px;
padding: 5px; padding: 5px;
max-height: 150px;
overflow-y: scroll;
.livechat-poll-toggle {
background: unset;
border: 0;
padding-left: 0.25em;
padding-right: 0.25em;
}
p.livechat-poll-question { p.livechat-poll-question {
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
} }
p.livechat-poll-instructions {
text-align: right;
}
p.livechat-poll-end { p.livechat-poll-end {
text-align: right; text-align: right;
} }
@ -74,3 +87,28 @@
} }
} }
} }
body[livechat-viewer-mode="on"] {
livechat-converse-muc-poll {
/* Dont display the poll before user choose a nickname */
display: none !important;
}
}
.livechat-readonly {
.conversejs {
livechat-converse-muc-poll {
/* stylelint-disable-next-line no-descending-specificity */
& > div {
// In readonly mode, dont impose max-height
max-height: initial !important;
overflow-y: visible !important;
&.livechat-poll-over {
// stop showing poll when over in readonly mode
display: none !important;
}
}
}
}
}

View File

@ -6,6 +6,18 @@ import { html } from 'lit'
import { repeat } from 'lit/directives/repeat.js' import { repeat } from 'lit/directives/repeat.js'
import { __ } from 'i18n' import { __ } from 'i18n'
function _tplPollInstructions (el, currentPoll) {
if (currentPoll.over) {
return html``
}
// eslint-disable-next-line no-undef
const i18nPollInstructions = __(LOC_poll_vote_instructions)
return html`<p class="livechat-poll-instructions">
${i18nPollInstructions}
</p>`
}
function _tplPollEnd (el, currentPoll) { function _tplPollEnd (el, currentPoll) {
if (!currentPoll.endDate) { if (!currentPoll.endDate) {
return html`` return html``
@ -36,13 +48,9 @@ function _tplChoice (el, currentPoll, choice) {
<button type="button" class="btn btn-primary btn-sm" <button type="button" class="btn btn-primary btn-sm"
@click=${ev => { @click=${ev => {
ev.preventDefault() ev.preventDefault()
if (currentPoll.over) { return } el.voteFor(choice)
console.info('User has voted for choice: ', choice)
el.model.sendMessage({
body: '!' + choice.choice
})
}} }}
?disabled=${el.buttonDisabled}
> >
${i18nChoiceN} ${i18nChoiceN}
</button>` </button>`
@ -72,11 +80,36 @@ export function tplPoll (el, currentPoll) {
return html`` return html``
} }
return html`<div> return html`<div class="${currentPoll.over ? 'livechat-poll-over' : ''}">
<p class="livechat-poll-question">${currentPoll.question}</p> <p class="livechat-poll-question">
<table><tbody> ${el.collapsed
${repeat(currentPoll.choices ?? [], (c) => c.choice, (c) => _tplChoice(el, currentPoll, c))} ? html`
</tbody></table> <button @click=${el.toggle} class="livechat-poll-toggle">
${_tplPollEnd(el, currentPoll)} <converse-icon
color="var(--muc-toolbar-btn-color)"
class="fa fa-angle-right"
size="1em"></converse-icon>
</button>`
: html`
<button @click=${el.toggle} class="livechat-poll-toggle">
<converse-icon
color="var(--muc-toolbar-btn-color)"
class="fa fa-angle-down"
size="1em"></converse-icon>
</button>`
}
${currentPoll.question}
</p>
${
el.collapsed
? ''
: html`
<table><tbody>
${repeat(currentPoll.choices ?? [], (c) => c.choice, (c) => _tplChoice(el, currentPoll, c))}
</tbody></table>
${_tplPollInstructions(el, currentPoll)}
${_tplPollEnd(el, currentPoll)}
`
}
</div>` </div>`
} }

View File

@ -44,7 +44,8 @@ const locKeys = [
'poll_title', 'poll_title',
'poll_instructions', 'poll_instructions',
'poll_end', 'poll_end',
'poll' 'poll',
'poll_vote_instructions'
] ]
module.exports = locKeys module.exports = locKeys

View File

@ -573,3 +573,5 @@ poll_duration: Poll duration (in minutes)
poll_anonymous_results: Anonymous results poll_anonymous_results: Anonymous results
poll_choice_n: 'Choice {{N}}:' poll_choice_n: 'Choice {{N}}:'
poll_end: 'Poll ends at:' poll_end: 'Poll ends at:'
poll_vote_instructions: |
To vote, click on your choice or send a message with an exclamation mark followed by your choice number (Example: !1)

View File

@ -29,3 +29,5 @@ Here are the existing strings and default values:
* poll_string_over: This poll is now over. * poll_string_over: This poll is now over.
* poll_string_vote_instructions: Send a message with an exclamation mark followed by your choice number to vote. Example: !1 * poll_string_vote_instructions: Send a message with an exclamation mark followed by your choice number to vote. Example: !1
* poll_string_invalid_choice: This choice is not valid.
* poll_string_anonymous_vote_ok: You vote is taken into account. Votes are anonymous, it will not be shown to other participants.

View File

@ -11,6 +11,9 @@ local poll_start_message = module:require("message").poll_start_message;
local poll_end_message = module:require("message").poll_end_message; local poll_end_message = module:require("message").poll_end_message;
local schedule_poll_update_message = module:require("message").schedule_poll_update_message; local schedule_poll_update_message = module:require("message").schedule_poll_update_message;
local string_poll_invalid_choice = module:get_option_string("poll_string_invalid_choice") or "This choice is not valid.";
local string_poll_anonymous_vote_ok = module:get_option_string("poll_string_anonymous_vote_ok") or "You vote is taken into account. Votes are anonymous, it will not be shown to other participants.";
local scheduled_end = {}; local scheduled_end = {};
local function schedule_poll_purge(room_jid) local function schedule_poll_purge(room_jid)
@ -171,7 +174,7 @@ local function handle_groupchat(event)
"cancel", "cancel",
-- error_condition = 'not-allowed' (see RFC 6120 Defined Error Conditions https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions) -- error_condition = 'not-allowed' (see RFC 6120 Defined Error Conditions https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions)
"bad-request", "bad-request",
"This choice is not valid." string_poll_invalid_choice
)); ));
return true; -- stop! return true; -- stop!
end end
@ -205,7 +208,7 @@ local function handle_groupchat(event)
"continue", "continue",
-- error_condition -- error_condition
"undefined-condition", "undefined-condition",
"You vote is taken into account. Votes are anonymous, it will not be shown to other participants." string_poll_anonymous_vote_ok
)); ));
return true; -- stop! return true; -- stop!
end end