nodebb-plugin-poll 3.2.8 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -32
- package/languages/en_GB/poll.json +12 -4
- package/languages/fr/poll.json +46 -38
- package/lib/config.js +13 -29
- package/lib/hooks.js +82 -67
- package/lib/nodebb.js +0 -1
- package/lib/poll.js +242 -121
- package/lib/sockets.js +60 -44
- package/lib/vote.js +29 -16
- package/library.js +27 -25
- package/package.json +4 -4
- package/plugin.json +8 -5
- package/public/js/admin.js +3 -32
- package/public/js/poll/creator.js +230 -177
- package/public/js/poll/main.js +64 -28
- package/public/js/poll/view.js +67 -81
- package/public/scss/style.scss +1 -4
- package/schema.md +24 -0
- package/templates/admin/partials/widgets/poll.tpl +2 -2
- package/templates/admin/plugins/poll.tpl +63 -48
- package/templates/poll/creator.tpl +18 -22
- package/templates/poll/manage.tpl +18 -0
- package/templates/poll/option-input.tpl +6 -0
- package/templates/poll/view/details.tpl +7 -6
- package/templates/poll/view/voting.tpl +4 -4
- package/templates/poll/view.tpl +14 -9
- package/upgrades/poll-4.0-upgrade.js +128 -0
- package/lib/scheduler.js +0 -52
- package/lib/serializer.js +0 -4
- package/public/js/poll/serializer.js +0 -173
package/public/js/poll/view.js
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
(function (Poll) {
|
|
4
4
|
function vote(view, options) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return parseInt(option.value, 10);
|
|
8
|
-
});
|
|
5
|
+
const form = view.dom.votingPanel.find('form');
|
|
6
|
+
const votes = form.serializeArray().map(option => parseInt(option.value, 10));
|
|
9
7
|
|
|
10
8
|
if (votes.length > 0) {
|
|
11
|
-
|
|
9
|
+
const voteData = {
|
|
12
10
|
pollId: view.pollData.info.pollId,
|
|
13
11
|
options: votes,
|
|
14
12
|
voteAnon: options.voteAnon,
|
|
@@ -28,11 +26,11 @@
|
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
const Actions = [
|
|
32
30
|
{
|
|
33
31
|
// Voting
|
|
34
32
|
register: function (view) {
|
|
35
|
-
|
|
33
|
+
const self = this;
|
|
36
34
|
view.dom.voteButton.off('click').on('click', function () {
|
|
37
35
|
self.handle(view);
|
|
38
36
|
});
|
|
@@ -46,7 +44,7 @@
|
|
|
46
44
|
{
|
|
47
45
|
// vote anon
|
|
48
46
|
register: function (view) {
|
|
49
|
-
|
|
47
|
+
const self = this;
|
|
50
48
|
view.dom.voteAnonButton.off('click').on('click', function () {
|
|
51
49
|
self.handle(view);
|
|
52
50
|
});
|
|
@@ -60,19 +58,19 @@
|
|
|
60
58
|
{
|
|
61
59
|
// update voting
|
|
62
60
|
register: function (view) {
|
|
63
|
-
|
|
61
|
+
const self = this;
|
|
64
62
|
view.dom.updateVoteButton.off('click').on('click', function () {
|
|
65
63
|
self.handle(view);
|
|
66
64
|
});
|
|
67
65
|
},
|
|
68
66
|
handle: function (view) {
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
const form = view.dom.votingPanel.find('form');
|
|
68
|
+
const votes = form.serializeArray().map(function (option) {
|
|
71
69
|
return parseInt(option.value, 10);
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
if (votes.length > 0) {
|
|
75
|
-
|
|
73
|
+
const voteData = {
|
|
76
74
|
pollId: view.pollData.info.pollId,
|
|
77
75
|
options: votes,
|
|
78
76
|
};
|
|
@@ -88,13 +86,13 @@
|
|
|
88
86
|
{
|
|
89
87
|
// Remove vote
|
|
90
88
|
register: function (view) {
|
|
91
|
-
|
|
89
|
+
const self = this;
|
|
92
90
|
view.dom.removeVoteButton.off('click').on('click', function () {
|
|
93
91
|
self.handle(view);
|
|
94
92
|
});
|
|
95
93
|
},
|
|
96
94
|
handle: function (view) {
|
|
97
|
-
|
|
95
|
+
const voteData = { pollId: view.pollData.info.pollId };
|
|
98
96
|
|
|
99
97
|
socket.emit('plugins.poll.removeVote', voteData, function (err) {
|
|
100
98
|
if (err) {
|
|
@@ -107,7 +105,7 @@
|
|
|
107
105
|
{
|
|
108
106
|
// Results button
|
|
109
107
|
register: function (view) {
|
|
110
|
-
|
|
108
|
+
const self = this;
|
|
111
109
|
view.dom.resultsPanelButton.off('click').on('click', function () {
|
|
112
110
|
self.handle(view);
|
|
113
111
|
});
|
|
@@ -119,7 +117,7 @@
|
|
|
119
117
|
{
|
|
120
118
|
// To Voting button
|
|
121
119
|
register: function (view) {
|
|
122
|
-
|
|
120
|
+
const self = this;
|
|
123
121
|
view.dom.votingPanelButton.off('click').on('click', function () {
|
|
124
122
|
self.handle(view);
|
|
125
123
|
});
|
|
@@ -131,13 +129,13 @@
|
|
|
131
129
|
{
|
|
132
130
|
// Option details
|
|
133
131
|
register: function (view) {
|
|
134
|
-
|
|
132
|
+
const self = this;
|
|
135
133
|
view.dom.resultsPanel.off('click').on('click', '.poll-result-votecount', function (e) {
|
|
136
134
|
self.handle(view, e);
|
|
137
135
|
});
|
|
138
136
|
},
|
|
139
137
|
handle: function (view, e) {
|
|
140
|
-
|
|
138
|
+
const optionId = $(e.currentTarget).parents('[data-poll-option-id]').data('poll-option-id');
|
|
141
139
|
|
|
142
140
|
socket.emit('plugins.poll.getOptionDetails', {
|
|
143
141
|
pollId: view.pollData.info.pollId,
|
|
@@ -151,66 +149,50 @@
|
|
|
151
149
|
});
|
|
152
150
|
},
|
|
153
151
|
},
|
|
154
|
-
{
|
|
155
|
-
// Editing
|
|
156
|
-
register: function (view) {
|
|
157
|
-
var self = this;
|
|
158
|
-
view.dom.editButton.off('click').on('click', function () {
|
|
159
|
-
self.handle(view);
|
|
160
|
-
});
|
|
161
|
-
},
|
|
162
|
-
handle: function (view) {
|
|
163
|
-
socket.emit('plugins.poll.getConfig', null, function (err, config) {
|
|
164
|
-
if (err) {
|
|
165
|
-
console.error(err);
|
|
166
|
-
}
|
|
167
|
-
Poll.creator.show(view.pollData, config, function () {});
|
|
168
|
-
});
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
152
|
];
|
|
172
153
|
|
|
173
|
-
|
|
154
|
+
const View = function (pollData) {
|
|
174
155
|
this.pollData = pollData;
|
|
175
156
|
};
|
|
176
157
|
|
|
177
|
-
View.prototype.load = function () {
|
|
178
|
-
|
|
158
|
+
View.prototype.load = async function () {
|
|
159
|
+
const self = this;
|
|
179
160
|
if (!self.pollData.container.length) {
|
|
180
161
|
return;
|
|
181
162
|
}
|
|
182
163
|
|
|
183
|
-
app.parseAndTranslate('poll/view', {
|
|
184
|
-
self.pollData
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
164
|
+
const panel = await app.parseAndTranslate('poll/view', {
|
|
165
|
+
poll: self.pollData,
|
|
166
|
+
isAdmin: app.user.isAdmin,
|
|
167
|
+
});
|
|
168
|
+
self.pollData.container.prepend(panel);
|
|
169
|
+
self.dom = {
|
|
170
|
+
panel: panel,
|
|
171
|
+
votingForm: panel.find('.poll-voting-form'),
|
|
172
|
+
messages: panel.find('.poll-view-messages'),
|
|
173
|
+
votingPanel: panel.find('.poll-view-voting'),
|
|
174
|
+
resultsPanel: panel.find('.poll-view-results'),
|
|
175
|
+
voteButton: panel.find('.poll-button-vote'),
|
|
176
|
+
voteAnonButton: panel.find('.poll-button-vote-anon'),
|
|
177
|
+
updateVoteButton: panel.find('.poll-button-update-vote'),
|
|
178
|
+
removeVoteButton: panel.find('.poll-button-remove-vote'),
|
|
179
|
+
votingPanelButton: panel.find('.poll-button-voting'),
|
|
180
|
+
resultsPanelButton: panel.find('.poll-button-results'),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
self.hideMessage();
|
|
184
|
+
|
|
185
|
+
self.pollEndedOrDeleted();
|
|
186
|
+
self.hasVotedAndVotingUpdateDisallowed();
|
|
187
|
+
|
|
188
|
+
if (!app.user.uid || self.pollData.hasVoted) {
|
|
189
|
+
self.showResultsPanel();
|
|
190
|
+
} else {
|
|
191
|
+
self.showVotingPanel();
|
|
192
|
+
}
|
|
210
193
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
});
|
|
194
|
+
Actions.forEach(function (action) {
|
|
195
|
+
action.register(self);
|
|
214
196
|
});
|
|
215
197
|
};
|
|
216
198
|
|
|
@@ -219,7 +201,7 @@
|
|
|
219
201
|
};
|
|
220
202
|
|
|
221
203
|
View.prototype.voteUpdateAllowed = function () {
|
|
222
|
-
return parseInt(this.pollData.
|
|
204
|
+
return parseInt(this.pollData.disallowVoteUpdate, 10) !== 1;
|
|
223
205
|
};
|
|
224
206
|
|
|
225
207
|
View.prototype.pollEndedOrDeleted = function () {
|
|
@@ -252,7 +234,7 @@
|
|
|
252
234
|
this.pollEndedOrDeleted();
|
|
253
235
|
|
|
254
236
|
this.pollData.options.forEach(function (option) {
|
|
255
|
-
|
|
237
|
+
const el = this.dom.resultsPanel.find('[data-poll-option-id=' + option.id + ']');
|
|
256
238
|
el.find('.poll-result-votecount span').translateText(`[[poll:x-votes, ${option.voteCount}]]`);
|
|
257
239
|
el.find('.poll-result-progressbar').css('width', option.percentage + '%')
|
|
258
240
|
.find('span.percent').text(option.percentage);
|
|
@@ -264,7 +246,7 @@
|
|
|
264
246
|
};
|
|
265
247
|
|
|
266
248
|
View.prototype.showMessage = function (title, content) {
|
|
267
|
-
|
|
249
|
+
const self = this;
|
|
268
250
|
|
|
269
251
|
app.parseAndTranslate('poll/view/messages', { title: title, content: content }, function (html) {
|
|
270
252
|
self.dom.messages.html(html).removeClass('hidden');
|
|
@@ -279,6 +261,7 @@
|
|
|
279
261
|
require(['bootbox'], function (bootbox) {
|
|
280
262
|
app.parseAndTranslate('poll/view/details', details, function (html) {
|
|
281
263
|
bootbox.dialog({
|
|
264
|
+
title: `[[poll:x-users-voted-for-this-option, ${details.voteCount}]]`,
|
|
282
265
|
message: html,
|
|
283
266
|
backdrop: true,
|
|
284
267
|
buttons: {
|
|
@@ -292,7 +275,7 @@
|
|
|
292
275
|
};
|
|
293
276
|
|
|
294
277
|
View.prototype.fillVotingForm = function () {
|
|
295
|
-
|
|
278
|
+
const self = this;
|
|
296
279
|
this.resetVotingForm();
|
|
297
280
|
if (this.pollData.vote && this.pollData.vote.options) {
|
|
298
281
|
this.pollData.vote.options.forEach(function (id) {
|
|
@@ -318,7 +301,7 @@
|
|
|
318
301
|
}
|
|
319
302
|
} else {
|
|
320
303
|
this.showVoteButton();
|
|
321
|
-
if (this.pollData.
|
|
304
|
+
if (this.pollData.allowAnonVoting) {
|
|
322
305
|
this.showVoteAnonButton();
|
|
323
306
|
}
|
|
324
307
|
}
|
|
@@ -399,16 +382,19 @@
|
|
|
399
382
|
};
|
|
400
383
|
|
|
401
384
|
Poll.view = {
|
|
402
|
-
polls:
|
|
403
|
-
load: function (pollData) {
|
|
404
|
-
|
|
405
|
-
this.polls
|
|
406
|
-
|
|
407
|
-
|
|
385
|
+
polls: Object.create(null),
|
|
386
|
+
load: async function (pollData) {
|
|
387
|
+
const pollId = pollData.info.pollId;
|
|
388
|
+
if (Object.hasOwn(this.polls, pollId)) {
|
|
389
|
+
pollData.container.find(`[data-poll-id="${pollId}"]`).remove();
|
|
390
|
+
}
|
|
391
|
+
const view = new View(pollData);
|
|
392
|
+
this.polls[pollId] = view;
|
|
393
|
+
await view.load();
|
|
408
394
|
},
|
|
409
395
|
update: function (pollData, uid) {
|
|
410
|
-
|
|
411
|
-
if (this.polls
|
|
396
|
+
const pollId = pollData.info.pollId;
|
|
397
|
+
if (Object.hasOwn(this.polls, pollId)) {
|
|
412
398
|
this.polls[pollId].update(pollData, uid);
|
|
413
399
|
}
|
|
414
400
|
},
|
package/public/scss/style.scss
CHANGED
package/schema.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
post:100 {
|
|
3
|
+
...
|
|
4
|
+
pollIds: ["200", "201", ...]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
poll:200 {
|
|
8
|
+
"_key" : "poll:200",
|
|
9
|
+
"deleted" : 0,
|
|
10
|
+
"end" : 0, // or unix timestamp when voting will close
|
|
11
|
+
"pid" : 100,
|
|
12
|
+
"pollId" : 200,
|
|
13
|
+
"timestamp" : 1772893625284,
|
|
14
|
+
"uid" : 1,
|
|
15
|
+
"options": [
|
|
16
|
+
{ id: "300", title: "option 1" }, { id: "301", title: "option 2" }, { id: "302", title: "option 3" }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
poll:123:voters // uids of voters zset
|
|
21
|
+
|
|
22
|
+
poll:123:options:300:votes { //zset of uids who voted for this option
|
|
23
|
+
score: "<timestamp>", value: "1"
|
|
24
|
+
}
|
|
@@ -1,57 +1,72 @@
|
|
|
1
1
|
<div class="acp-page-container">
|
|
2
2
|
<div component="settings/main/header" class="row border-bottom py-2 m-0 sticky-top acp-page-main-header align-items-center">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
</div>
|
|
3
|
+
<div class="col-12 col-md-8 px-0 mb-1 mb-md-0">
|
|
4
|
+
<h4 class="fw-bold tracking-tight mb-0">{title}</h4>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="col-12 col-md-4 px-0 px-md-3 text-end">
|
|
7
|
+
<button id="save" class="btn btn-primary btn-sm fw-semibold ff-secondary text-center text-nowrap">[[admin/admin:save-changes]]</button>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
11
10
|
|
|
12
11
|
<div class="row m-0">
|
|
13
|
-
<div
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<p class="form-text">[[poll:info_choices]]</p>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</form>
|
|
12
|
+
<div class="col-12 col-lg-9">
|
|
13
|
+
<div class="table-responsive">
|
|
14
|
+
<table id="poll-table" class="table">
|
|
15
|
+
<thead>
|
|
16
|
+
<tr class="text-sm">
|
|
17
|
+
<th>ID</th>
|
|
18
|
+
<th class="text-nowrap">[[poll:poll_title]]</th>
|
|
19
|
+
<th class="text-nowrap">[[poll:created-time]]</th>
|
|
20
|
+
<th class="text-nowrap">[[poll:end-time]]</th>
|
|
21
|
+
<th class="text-end text-nowrap text-capitalize">[[poll:votes]]</th>
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
<tbody class="text-xs text-tabular">
|
|
25
|
+
{{{ each polls }}}
|
|
26
|
+
<tr class="align-middle">
|
|
27
|
+
<td>{./pollId}</td>
|
|
28
|
+
<td><a href="{config.relative_path}/post/{./pid}">{./title}</a></td>
|
|
29
|
+
<td class="text-nowrap"><span class="timeago" title="{./timestampISO}"></span></td>
|
|
30
|
+
<td class="text-nowrap">
|
|
31
|
+
{{{ if ./end }}}
|
|
32
|
+
<span class="timeago" title="{./endISO}"></span>
|
|
33
|
+
{{{ else }}}
|
|
34
|
+
-
|
|
35
|
+
{{{ end }}}
|
|
36
|
+
</td>
|
|
37
|
+
<td class="text-end">
|
|
38
|
+
{./voteCount}
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
41
|
+
{{{ end }}}
|
|
42
|
+
</tbody>
|
|
43
|
+
</table>
|
|
44
|
+
</div>
|
|
45
|
+
<!-- IMPORT admin/partials/paginator.tpl -->
|
|
52
46
|
</div>
|
|
47
|
+
<div id="spy-container" class="col-12 col-lg-3" tabindex="0">
|
|
48
|
+
<form class="form poll-settings">
|
|
49
|
+
<div class="form-check mb-3">
|
|
50
|
+
<label class="form-check-label" for="allowGuestsToViewResults">[[poll:allow_guests]]</label>
|
|
51
|
+
<input class="form-check-input" type="checkbox" id="allowGuestsToViewResults" name="allowGuestsToViewResults">
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="mb-3">
|
|
55
|
+
<label class="form-label" for="maxOptions">[[poll:max_options]]</label>
|
|
56
|
+
<input type="number" class="form-control" id="maxOptions" placeholder="10" min="1" max="100" name="maxOptions">
|
|
57
|
+
</div>
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
<div class="mb-3">
|
|
60
|
+
<label class="form-label" for="defaultTitle">[[poll:default_title]]</label>
|
|
61
|
+
<input type="text" class="form-control" id="defaultTitle" placeholder="Poll" name="defaultTitle">
|
|
62
|
+
</div>
|
|
63
|
+
<div>
|
|
64
|
+
<label class="form-label" for="maximumVotesPerUser">[[poll:max_votes]]</label>
|
|
65
|
+
<input type="number" class="form-control" id="maximumVotesPerUser" placeholder="1" min="1" max="100" name="maximumVotesPerUser">
|
|
66
|
+
<p class="form-text">[[poll:info_choices]]</p>
|
|
67
|
+
</div>
|
|
68
|
+
</form>
|
|
69
|
+
</div>
|
|
55
70
|
</div>
|
|
56
71
|
</div>
|
|
57
72
|
|
|
@@ -1,52 +1,48 @@
|
|
|
1
|
-
<form class="form" id="pollCreator">
|
|
1
|
+
<form class="form" id="pollCreator" onsubmit="return false;">
|
|
2
2
|
<div id="pollErrorBox" class="alert alert-danger hidden"></div>
|
|
3
3
|
|
|
4
|
+
<input type="hidden" name="pollId" value="{poll.pollId}">
|
|
5
|
+
|
|
4
6
|
<div class="mb-3">
|
|
5
7
|
<label class="form-label" for="pollInputTitle">[[poll:poll_title]]</label>
|
|
6
|
-
<input type="text" name="
|
|
8
|
+
<input type="text" name="title" id="pollInputTitle" value="{poll.title}" placeholder="[[poll:poll_title_placeholder]]" class="form-control">
|
|
7
9
|
</div>
|
|
8
10
|
|
|
9
|
-
<div class="mb-3">
|
|
11
|
+
<div class="mb-3 d-flex flex-column gap-1">
|
|
10
12
|
<label class="form-label" for="pollInputOptions">[[poll:options_title]]</label>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<!--
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<button type="button" id="pollAddOption" class="btn btn-primary btn-sm btn-block">[[poll:options_add]]</button>
|
|
13
|
+
<div id="poll-options-container" class="d-flex flex-column gap-1">
|
|
14
|
+
{{{ if poll.options.length }}}
|
|
15
|
+
{{{ each poll.options }}}
|
|
16
|
+
<!-- IMPORT poll/option-input.tpl -->
|
|
17
|
+
{{{ end }}}
|
|
18
|
+
{{{ end }}}
|
|
19
|
+
</div>
|
|
20
|
+
<button type="button" id="pollAddOption" class="btn btn-primary btn-sm btn-block mt-2">[[poll:options_add]]</button>
|
|
19
21
|
</div>
|
|
20
22
|
|
|
21
23
|
<hr>
|
|
22
24
|
|
|
23
25
|
<div class="mb-3">
|
|
24
26
|
<label class="form-label" for="pollInputAmount">[[poll:max_votes]]</label>
|
|
25
|
-
<input type="number" name="
|
|
26
|
-
|
|
27
|
+
<input type="number" name="maximumVotesPerUser" id="pollInputAmount" value="{poll.maximumVotesPerUser}" min="1" max="10" step="1" placeholder="[[poll:max_votes_placeholder]]" class="form-control">
|
|
28
|
+
<p class="form-text">[[poll:info_choices]]</p>
|
|
27
29
|
</div>
|
|
28
30
|
|
|
29
31
|
<div class="form-check mb-3">
|
|
30
32
|
<label class="form-check-label" for="pollDisallowVoteUpdate">[[poll:disallow_vote_update]]</label>
|
|
31
|
-
<input class="form-check-input" type="checkbox" name="
|
|
33
|
+
<input class="form-check-input" type="checkbox" name="disallowVoteUpdate" id="pollDisallowVoteUpdate" {{{if poll.disallowVoteUpdate}}}checked{{{end}}}>
|
|
32
34
|
</div>
|
|
33
35
|
|
|
34
36
|
<div class="form-check mb-3">
|
|
35
37
|
<label class="form-check-label" for="allowAnonVoting">[[poll:allow_anon_voting]]</label>
|
|
36
|
-
<input class="form-check-input" type="checkbox" name="
|
|
38
|
+
<input class="form-check-input" type="checkbox" name="allowAnonVoting" id="allowAnonVoting" {{{if poll.allowAnonVoting}}}checked{{{end}}}>
|
|
37
39
|
</div>
|
|
38
40
|
|
|
39
41
|
<div class="mb-3">
|
|
40
42
|
<label class="form-label" for="pollInputEnd">[[poll:auto_end_title]]</label>
|
|
41
43
|
<div class="input-group date">
|
|
42
|
-
<input id="pollInputEnd" placeholder="[[poll:auto_end_placeholder]]" name="
|
|
44
|
+
<input id="pollInputEnd" placeholder="[[poll:auto_end_placeholder]]" name="end" class="form-control" value="{poll.end}" type="datetime-local">
|
|
43
45
|
</div>
|
|
44
46
|
<p class="form-text">[[poll:auto_end_help]]</p>
|
|
45
47
|
</div>
|
|
46
|
-
|
|
47
|
-
{{{ if isRedactor }}}
|
|
48
|
-
<div class="alert alert-warning" role="alert">
|
|
49
|
-
[[poll:warning.redactor]]
|
|
50
|
-
</div>
|
|
51
|
-
{{{ end }}}
|
|
52
48
|
</form>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<div class="topic-poll-modal">
|
|
2
|
+
{{{ if !post.polls.length }}}
|
|
3
|
+
<div class="alert alert-info">[[poll:no-polls]]</div>
|
|
4
|
+
{{{ end }}}
|
|
5
|
+
{{{ each post.polls }}}
|
|
6
|
+
<div component="post/poll/item" data-poll-id="{./pollId}" class="bg-body d-flex align-items-center justify-content-between mb-3 p-1 border rounded-1">
|
|
7
|
+
<div class="d-flex align-items-center gap-1">
|
|
8
|
+
<a href="#" component="sort/handle" class="btn btn-ghost btn-sm align-self-start" style="cursor:grab;"><i class="fa fa-arrows-up-down text-muted"></i></a>
|
|
9
|
+
<div class="text-nowrap flex-grow-1">{./title}</div>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="d-flex align-items-center gap-1">
|
|
13
|
+
<button class="btn btn-ghost btn-sm text-nowrap" data-action="edit" data-poll-id="{./pollId}"><i class="fa fa-pencil text-primary"></i></button>
|
|
14
|
+
<button class="btn btn-ghost btn-sm text-nowrap" data-action="remove" data-poll-id="{./pollId}"><i class="fa fa-times text-danger"></i></button>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
{{{ end }}}
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<div component="post/poll/option/item" data-option-id="{./id}" class="bg-body d-flex align-items-center justify-content-between gap-2 p-1 rounded-1">
|
|
2
|
+
<a href="#" component="sort/handle" class="btn btn-ghost btn-sm" style="cursor:grab;"><i class="fa fa-arrows-up-down text-muted"></i></a>
|
|
3
|
+
<input type="text" name="options" id="pollInputOptions" value="{./title}" class="form-control"/>
|
|
4
|
+
<input type="hidden" name="id" value="{./id}"/>
|
|
5
|
+
<button class="btn btn-ghost btn-sm" data-action="remove-option" data-option-id="{./id}"><i class="fa fa-trash text-danger"></i></button>
|
|
6
|
+
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
<a href="{config.relative_path}/user/{votes.userslug}" class="poll-result-details">
|
|
4
|
-
|
|
5
|
-
</a>
|
|
6
|
-
|
|
1
|
+
<div class="d-flex flex-wrap gap-2">
|
|
2
|
+
{{{ each votes }}}
|
|
3
|
+
<a href="{config.relative_path}/user/{votes.userslug}" class="poll-result-details">
|
|
4
|
+
{buildAvatar(votes, "24px", true)}
|
|
5
|
+
</a>
|
|
6
|
+
{{{ end }}}
|
|
7
|
+
</div>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<form role="form" class="poll-voting-form mb-3 d-flex flex-column gap-3">
|
|
2
2
|
<!-- BEGIN poll.options -->
|
|
3
3
|
<div class="poll-view-option {poll.optionType}" data-poll-option-id="{poll.options.id}">
|
|
4
|
-
<
|
|
5
|
-
<input type="{poll.optionType}" name="pollVoteSelection" value="{poll.options.id}">
|
|
6
|
-
{poll.options.title}
|
|
7
|
-
</
|
|
4
|
+
<div class="form-check">
|
|
5
|
+
<input class="form-check-input" type="{poll.optionType}" name="pollVoteSelection" value="{poll.options.id}" id="poll-{poll.info.pollId}-option-id-{poll.options.id}">
|
|
6
|
+
<label class="form-check-label" for="poll-{poll.info.pollId}-option-id-{poll.options.id}">{poll.options.title}</label>
|
|
7
|
+
</div>
|
|
8
8
|
</div>
|
|
9
9
|
<!-- END poll.options -->
|
|
10
10
|
</form>
|
package/templates/poll/view.tpl
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
<div class="poll-view mb-3" data-poll-id="{poll.info.pollId}">
|
|
2
|
-
<div class="card">
|
|
3
|
-
<div class="card-header d-flex justify-content-between align-items-center">
|
|
4
|
-
<h5 class="card-title mb-0">
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
<div class="card shadow-sm border-0">
|
|
3
|
+
<div class="card-header border-0 d-flex justify-content-between align-items-center">
|
|
4
|
+
<h5 class="card-title mb-0">
|
|
5
|
+
{{{ if poll.isWidget }}}
|
|
6
|
+
<a class="text-reset" href="{config.relative_path}/post/{poll.info.pid}">{poll.info.title}</a>
|
|
7
|
+
{{{ else }}}
|
|
8
|
+
{poll.info.title}
|
|
9
|
+
{{{ end }}}
|
|
10
|
+
</h5>
|
|
11
|
+
{{{ if isAdmin }}}
|
|
12
|
+
<span class="text-sm text-secondary">[[poll:poll-id-x, {poll.info.pollId}]]</span>
|
|
13
|
+
{{{ end }}}
|
|
9
14
|
</div>
|
|
10
15
|
<div class="card-body">
|
|
11
16
|
<div class="poll-view-messages hidden"></div>
|
|
12
|
-
<div class="poll-view-voting
|
|
17
|
+
<div class="poll-view-voting {{{ if poll.hasVoted }}}hidden{{{ end }}}">
|
|
13
18
|
<!-- IMPORT poll/view/voting.tpl -->
|
|
14
19
|
</div>
|
|
15
|
-
<div class="poll-view-results d-flex flex-column gap-3 mb-3
|
|
20
|
+
<div class="poll-view-results d-flex flex-column gap-3 mb-3 {{{ if !poll.hasVoted }}}hidden{{{ end }}}">
|
|
16
21
|
<!-- IMPORT poll/view/results.tpl -->
|
|
17
22
|
<div class="text-end text-sm text-secondary poll-result-total-votecount">[[poll:total-votes-x, {poll.info.voteCount}]]</div>
|
|
18
23
|
</div>
|