From ffb8ac8ddc43a4fd1c426d1ebc782e7a73ee6a93 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Thu, 4 Jul 2024 15:15:28 +0200 Subject: [PATCH] Poll WIP (#231): * frontend WIP * backend fix --- .../plugins/poll/components/poll-view.js | 32 ++++++++++- conversejs/custom/plugins/poll/index.js | 4 +- .../custom/plugins/poll/styles/poll.scss | 38 +++++++++++++ .../custom/plugins/poll/templates/poll.js | 57 +++++++++++++++---- conversejs/loc.keys.js | 3 +- languages/en.yml | 2 + prosody-modules/mod_muc_poll/README.md | 2 + prosody-modules/mod_muc_poll/poll.lib.lua | 7 ++- 8 files changed, 127 insertions(+), 18 deletions(-) diff --git a/conversejs/custom/plugins/poll/components/poll-view.js b/conversejs/custom/plugins/poll/components/poll-view.js index fd06d552..53d7e939 100644 --- a/conversejs/custom/plugins/poll/components/poll-view.js +++ b/conversejs/custom/plugins/poll/components/poll-view.js @@ -10,21 +10,49 @@ import '../styles/poll.scss' export default class MUCPollView extends CustomElement { static get properties () { return { - model: { type: Object, attribute: true } + model: { type: Object, attribute: true }, + collapsed: { type: Boolean, attribute: false }, + buttonDisabled: { type: Boolean, attribute: false } } } async initialize () { + this.collapsed = false + this.buttonDisabled = false if (!this.model) { return } - this.listenTo(this.model, 'change:current_poll', () => this.requestUpdate()) + this.listenTo(this.model, 'change:current_poll', () => { + this.buttonDisabled = false + this.requestUpdate() + }) } render () { const currentPoll = this.model?.get('current_poll') 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) diff --git a/conversejs/custom/plugins/poll/index.js b/conversejs/custom/plugins/poll/index.js index fae612a3..adf72ca7 100644 --- a/conversejs/custom/plugins/poll/index.js +++ b/conversejs/custom/plugins/poll/index.js @@ -45,6 +45,7 @@ converse.plugins.add('livechat-converse-poll', { votes: parseInt(poll.getAttribute('votes') ?? 0), over: poll.hasAttribute('over'), 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 => { return { 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') - this.set('current_poll', attrs.current_poll) // this will be displayed by the livechat-converse-muc-poll custom element, // which is inserted in the DOM by the muc.js template overload. + this.set('current_poll', attrs.current_poll) + if (attrs.current_poll.over) { console.info('The poll is over, displaying the message in the chat') return this.__super__.onMessage(attrs) diff --git a/conversejs/custom/plugins/poll/styles/poll.scss b/conversejs/custom/plugins/poll/styles/poll.scss index d42b138f..ca5476ec 100644 --- a/conversejs/custom/plugins/poll/styles/poll.scss +++ b/conversejs/custom/plugins/poll/styles/poll.scss @@ -13,12 +13,25 @@ border: 1px solid var(--peertube-menu-background); margin: 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 { text-align: center; font-weight: bold; } + p.livechat-poll-instructions { + text-align: right; + } + p.livechat-poll-end { 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; + } + } + } + } +} diff --git a/conversejs/custom/plugins/poll/templates/poll.js b/conversejs/custom/plugins/poll/templates/poll.js index becc5876..703cb114 100644 --- a/conversejs/custom/plugins/poll/templates/poll.js +++ b/conversejs/custom/plugins/poll/templates/poll.js @@ -6,6 +6,18 @@ import { html } from 'lit' import { repeat } from 'lit/directives/repeat.js' 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`

+ ${i18nPollInstructions} +

` +} + function _tplPollEnd (el, currentPoll) { if (!currentPoll.endDate) { return html`` @@ -36,13 +48,9 @@ function _tplChoice (el, currentPoll, choice) { ` @@ -72,11 +80,36 @@ export function tplPoll (el, currentPoll) { return html`` } - return html`
-

${currentPoll.question}

- - ${repeat(currentPoll.choices ?? [], (c) => c.choice, (c) => _tplChoice(el, currentPoll, c))} -
- ${_tplPollEnd(el, currentPoll)} + return html`
+

+ ${el.collapsed + ? html` + ` + : html` + ` + } + ${currentPoll.question} +

+ ${ + el.collapsed + ? '' + : html` + + ${repeat(currentPoll.choices ?? [], (c) => c.choice, (c) => _tplChoice(el, currentPoll, c))} +
+ ${_tplPollInstructions(el, currentPoll)} + ${_tplPollEnd(el, currentPoll)} + ` + }
` } diff --git a/conversejs/loc.keys.js b/conversejs/loc.keys.js index ee0cadc1..a29e0088 100644 --- a/conversejs/loc.keys.js +++ b/conversejs/loc.keys.js @@ -44,7 +44,8 @@ const locKeys = [ 'poll_title', 'poll_instructions', 'poll_end', - 'poll' + 'poll', + 'poll_vote_instructions' ] module.exports = locKeys diff --git a/languages/en.yml b/languages/en.yml index 7b84ca6e..4807bdb5 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -573,3 +573,5 @@ poll_duration: Poll duration (in minutes) poll_anonymous_results: Anonymous results poll_choice_n: 'Choice {{N}}:' 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) diff --git a/prosody-modules/mod_muc_poll/README.md b/prosody-modules/mod_muc_poll/README.md index 49f6f025..40ec5a4f 100644 --- a/prosody-modules/mod_muc_poll/README.md +++ b/prosody-modules/mod_muc_poll/README.md @@ -29,3 +29,5 @@ Here are the existing strings and default values: * 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_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. diff --git a/prosody-modules/mod_muc_poll/poll.lib.lua b/prosody-modules/mod_muc_poll/poll.lib.lua index f6e110e7..73c16b00 100644 --- a/prosody-modules/mod_muc_poll/poll.lib.lua +++ b/prosody-modules/mod_muc_poll/poll.lib.lua @@ -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 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 function schedule_poll_purge(room_jid) @@ -171,7 +174,7 @@ local function handle_groupchat(event) "cancel", -- error_condition = 'not-allowed' (see RFC 6120 Defined Error Conditions https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions) "bad-request", - "This choice is not valid." + string_poll_invalid_choice )); return true; -- stop! end @@ -205,7 +208,7 @@ local function handle_groupchat(event) "continue", -- error_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! end