nodebb-plugin-poll 3.2.9 → 4.0.1
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 +2 -19
- 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 -68
- package/lib/nodebb.js +0 -1
- package/lib/poll.js +242 -121
- package/lib/sockets.js +63 -47
- 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 -170
- 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.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/README.md
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
# NodeBB Poll plugin
|
|
2
2
|
|
|
3
|
-
This NodeBB plugin will allow you to add polls to
|
|
3
|
+
This NodeBB plugin will allow you to add polls to posts.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Poll option
|
|
7
|
-
- Another option
|
|
8
|
-
[/poll]
|
|
9
|
-
|
|
10
|
-
Currently supported settings:
|
|
11
|
-
|
|
12
|
-
maxvotes="1" //Max number of votes per user. If larger than 1, a multiple choice poll will be created
|
|
13
|
-
disallowVoteUpdate="0" //if set, users won't be able to update/remove their vote
|
|
14
|
-
allowAnonVoting="0" // if set to 1, users will be able to vote anonymously
|
|
15
|
-
title="Poll title" //Poll title
|
|
16
|
-
|
|
17
|
-
There's also a helpful modal available that will allow you to easily create a poll:
|
|
5
|
+
There's helpful modal available that will allow you to easily create a poll:
|
|
18
6
|

|
|
19
7
|
|
|
20
|
-
## Todo
|
|
21
|
-
|
|
22
|
-
- Add the ability to edit a poll
|
|
23
|
-
- A lot more...
|
|
24
|
-
|
|
25
8
|
If you're willing to help, please make any improvements you want and submit a PR.
|
|
26
9
|
|
|
27
10
|
## Installation
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"poll": "Poll",
|
|
3
|
+
"poll-id-x": "Poll ID: %1",
|
|
3
4
|
"toggles": "Toggles",
|
|
4
5
|
"allow_guests": "Allow guests to view poll results",
|
|
5
6
|
"limits": "Limits",
|
|
@@ -13,8 +14,12 @@
|
|
|
13
14
|
"settings": "Settings",
|
|
14
15
|
"save": "Save",
|
|
15
16
|
"reset": "Reset",
|
|
16
|
-
|
|
17
|
+
"manage-polls": "Manage polls",
|
|
18
|
+
"add-poll": "Add poll",
|
|
19
|
+
"no-polls": "No polls found",
|
|
20
|
+
"confirm-remove": "Are you sure you want to remove this poll?",
|
|
17
21
|
"creator_title": "Create a poll",
|
|
22
|
+
"edit-a-poll-title": "Edit a poll",
|
|
18
23
|
"poll_title": "Poll Title",
|
|
19
24
|
"poll_title_placeholder": "Enter poll title",
|
|
20
25
|
"options_title": "Options",
|
|
@@ -24,11 +29,12 @@
|
|
|
24
29
|
"auto_end_placeholder": "Click to enter date and time",
|
|
25
30
|
"auto_end_help": "Leaving this empty will never end the poll.",
|
|
26
31
|
|
|
27
|
-
"error.max_options": "You can only create %
|
|
32
|
+
"error.max_options": "You can only create %1 options.",
|
|
28
33
|
"error.no_options": "Create at least one option.",
|
|
29
34
|
"error.valid_date": "Please enter a valid date.",
|
|
30
35
|
|
|
31
36
|
"error.not_main": "Can only add poll in main post.",
|
|
37
|
+
"error.invalid-post": "Invalid post.",
|
|
32
38
|
"error.privilege.create": "You're not allowed to create a poll",
|
|
33
39
|
"error.anon-voting-not-allowed": "This poll does not allow voting anonymously",
|
|
34
40
|
|
|
@@ -45,9 +51,11 @@
|
|
|
45
51
|
"voting_update_disallowed_message": "You have already voted and changing your vote is not allowed for this poll",
|
|
46
52
|
"vote_is_final": "Changing your vote is not allowed for this poll so your vote is final",
|
|
47
53
|
|
|
48
|
-
"
|
|
54
|
+
"x-users-voted-for-this-option": "%1 user(s) voted for this option",
|
|
49
55
|
"votes": "votes",
|
|
50
56
|
"x-votes": "%1 votes",
|
|
51
57
|
"total-votes-x": "Total Votes: %1",
|
|
52
|
-
"admin.create-poll": "Create Poll"
|
|
58
|
+
"admin.create-poll": "Create Poll",
|
|
59
|
+
"created-time": "Created",
|
|
60
|
+
"end-time": "End"
|
|
53
61
|
}
|
package/languages/fr/poll.json
CHANGED
|
@@ -1,39 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"poll": "Sondage",
|
|
3
|
-
"toggles": "Activateurs",
|
|
4
|
-
"allow_guests": "Permettre aux invités de voir les résultats du sondage",
|
|
5
|
-
"limits": "Limites",
|
|
6
|
-
"max_options": "Nombre maximum d'options par sondage",
|
|
7
|
-
"defaults": "
|
|
8
|
-
"default_title": "Titre par défaut",
|
|
9
|
-
"max_votes": "Nombre maximum de votes par utilisateur",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
"error.max_options": "
|
|
26
|
-
"error.no_options": "
|
|
27
|
-
"error.valid_date": "
|
|
28
|
-
|
|
29
|
-
"error.privilege.create": "
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
1
|
+
{
|
|
2
|
+
"poll": "Sondage",
|
|
3
|
+
"toggles": "Activateurs",
|
|
4
|
+
"allow_guests": "Permettre aux invités de voir les résultats du sondage",
|
|
5
|
+
"limits": "Limites",
|
|
6
|
+
"max_options": "Nombre maximum d'options par sondage",
|
|
7
|
+
"defaults": "Valeurs par défaut",
|
|
8
|
+
"default_title": "Titre du sondage par défaut",
|
|
9
|
+
"max_votes": "Nombre maximum de votes par utilisateur",
|
|
10
|
+
"disallow_vote_update": "Ne pas autoriser les utilisateurs à modifier leur vote",
|
|
11
|
+
"allow_anon_voting": "Autoriser le vote anonyme",
|
|
12
|
+
"info_choices": "Un nombre supérieur à 1 permet d'activer les sondages à choix multiple.",
|
|
13
|
+
"settings": "Paramètres",
|
|
14
|
+
"save": "Enregistrer",
|
|
15
|
+
"reset": "Réinitialiser",
|
|
16
|
+
"creator_title": "Créer un sondage",
|
|
17
|
+
"poll_title": "Titre du sondage",
|
|
18
|
+
"poll_title_placeholder": "Entrez le titre du sondage",
|
|
19
|
+
"options_title": "Options",
|
|
20
|
+
"options_add": "Ajouter une option",
|
|
21
|
+
"max_votes_placeholder": "Entrez le nombre",
|
|
22
|
+
"auto_end_title": "Terminer le sondage automatiquement",
|
|
23
|
+
"auto_end_placeholder": "Cliquez pour entrer la date et l'heure",
|
|
24
|
+
"auto_end_help": "Laisser ce champ vide pour que le sondage ne se termine jamais.",
|
|
25
|
+
"error.max_options": "Vous ne pouvez créer que %d options.",
|
|
26
|
+
"error.no_options": "Créez au moins une option.",
|
|
27
|
+
"error.valid_date": "Veuillez entrer une date valide.",
|
|
28
|
+
"error.not_main": "Le sondage ne peut être ajouté que dans le post principal.",
|
|
29
|
+
"error.privilege.create": "Vous n'êtes pas autorisé à créer un sondage.",
|
|
30
|
+
"error.anon-voting-not-allowed": "Ce sondage n'autorise pas le vote anonyme.",
|
|
31
|
+
"warning.redactor": "Vous utilisez Redactor. Ne modifiez pas le balisage manuellement. Rouvrez plutôt le créateur de sondage.",
|
|
32
|
+
"vote": "Voter",
|
|
33
|
+
"vote_anonymously": "Voter anonymement",
|
|
34
|
+
"update_vote": "Modifier le vote",
|
|
35
|
+
"remove_vote": "Supprimer le vote",
|
|
36
|
+
"to_voting": "Aller au vote",
|
|
37
|
+
"to_results": "Voir les résultats",
|
|
38
|
+
"voting_unavailable_title": "Vote indisponible",
|
|
39
|
+
"voting_unavailable_message": "Ce sondage est terminé ou a été marqué comme supprimé. Vous pouvez toujours consulter les résultats.",
|
|
40
|
+
"voting_update_disallowed_message": "Vous avez déjà voté et la modification de votre vote n'est pas autorisée pour ce sondage.",
|
|
41
|
+
"vote_is_final": "La modification de votre vote n'est pas autorisée pour ce sondage, votre vote est donc définitif.",
|
|
42
|
+
"vote_count": "utilisateurs ont voté pour cette option",
|
|
43
|
+
"votes": "votes",
|
|
44
|
+
"x-votes": "%1 votes",
|
|
45
|
+
"total-votes-x": "Total des votes : %1",
|
|
46
|
+
"admin.create-poll": "Créer un sondage"
|
|
39
47
|
}
|
package/lib/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const meta = require.main.require('./src/meta');
|
|
4
4
|
|
|
5
5
|
const packageInfo = require('../package.json');
|
|
6
6
|
const pluginInfo = require('../plugin.json');
|
|
@@ -18,35 +18,19 @@ Config.plugin = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
Config.defaults = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
defaults: {
|
|
28
|
-
title: 'Poll',
|
|
29
|
-
maxvotes: 1,
|
|
30
|
-
disallowVoteUpdate: 0,
|
|
31
|
-
allowAnonVoting: 0,
|
|
32
|
-
end: 0,
|
|
33
|
-
},
|
|
21
|
+
defaultTitle: 'Poll',
|
|
22
|
+
maxOptions: 10,
|
|
23
|
+
maximumVotesPerUser: 1,
|
|
24
|
+
allowGuestsToViewResults: 0,
|
|
25
|
+
disallowVoteUpdate: 0,
|
|
26
|
+
allowAnonVoting: 0,
|
|
34
27
|
};
|
|
35
28
|
|
|
36
|
-
Config.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
Config.adminSockets = {
|
|
45
|
-
sync: function () {
|
|
46
|
-
Config.settings.sync();
|
|
47
|
-
},
|
|
48
|
-
getDefaults: function (socket, data, callback) {
|
|
49
|
-
callback(null, Config.settings.createDefaultWrapper());
|
|
50
|
-
},
|
|
29
|
+
Config.getSettings = async function () {
|
|
30
|
+
const settings = await meta.settings.get(Config.plugin.id);
|
|
31
|
+
return {
|
|
32
|
+
...Config.defaults,
|
|
33
|
+
...settings,
|
|
34
|
+
};
|
|
51
35
|
};
|
|
52
36
|
|
package/lib/hooks.js
CHANGED
|
@@ -1,117 +1,131 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const db = require.main.require('./src/database');
|
|
4
|
+
const topics = require.main.require('./src/topics');
|
|
5
|
+
const posts = require.main.require('./src/posts');
|
|
6
|
+
const privileges = require.main.require('./src/privileges');
|
|
7
|
+
|
|
4
8
|
|
|
5
9
|
const Config = require('./config');
|
|
6
10
|
const Poll = require('./poll');
|
|
7
|
-
const Serializer = require('./serializer');
|
|
8
11
|
|
|
9
12
|
const Hooks = exports;
|
|
10
13
|
|
|
11
14
|
Hooks.filter = {};
|
|
12
15
|
Hooks.action = {};
|
|
13
16
|
|
|
14
|
-
Hooks.filter.
|
|
15
|
-
|
|
17
|
+
Hooks.filter.configGet = async function (config) {
|
|
18
|
+
config.poll = await Config.getSettings();
|
|
19
|
+
return config;
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
Hooks.filter.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
Hooks.filter.registerFormatting = function (payload) {
|
|
23
|
+
payload.options.push({
|
|
24
|
+
name: 'poll',
|
|
25
|
+
className: `fa ${Config.plugin.icon}`,
|
|
26
|
+
title: '[[poll:manage-polls]]',
|
|
27
|
+
badge: true,
|
|
28
|
+
});
|
|
29
|
+
return payload;
|
|
23
30
|
};
|
|
24
31
|
|
|
25
|
-
Hooks.filter.
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
32
|
+
Hooks.filter.composerPush = async function (hookData) {
|
|
33
|
+
// used for editing, add the polls so they are avable in composer
|
|
34
|
+
if (hookData.pid) {
|
|
35
|
+
const callerUid = hookData?.caller?.uid || 0;
|
|
36
|
+
const pollIds = await Poll.getPollIdsByPid(hookData.pid);
|
|
37
|
+
const polls = await Promise.all(pollIds.map(pollId => Poll.get(pollId, callerUid)));
|
|
38
|
+
hookData.polls = polls.map(p => p && p.info).filter(Boolean);
|
|
29
39
|
}
|
|
40
|
+
return hookData;
|
|
41
|
+
};
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
Hooks.filter.postCreate = async function (hookData) {
|
|
44
|
+
// post is going to be saved to db, data is what is submitted by user
|
|
45
|
+
const { post, data } = hookData;
|
|
46
|
+
if (Array.isArray(data?.polls) && data.polls.length) {
|
|
47
|
+
const savedPolls = await Poll.add(post, data.polls);
|
|
48
|
+
if (savedPolls.length) {
|
|
49
|
+
post.pollIds = JSON.stringify(savedPolls.map(p => String(p.pollId)));
|
|
50
|
+
}
|
|
34
51
|
}
|
|
52
|
+
return hookData;
|
|
53
|
+
};
|
|
35
54
|
|
|
36
|
-
|
|
55
|
+
Hooks.filter.postGetFields = async function (hookData) {
|
|
56
|
+
if (!hookData.fields.includes('pollIds')) {
|
|
57
|
+
// when a new post is created it doesnt send pollIds to the client
|
|
58
|
+
// force load pollIds so it is available when the client needs to load the poll data
|
|
59
|
+
const postData = await db.getObjectsFields(hookData.pids.map(pid => `post:${pid}`), ['pollIds']);
|
|
60
|
+
hookData.posts.forEach((post, index) => {
|
|
61
|
+
if (post) {
|
|
62
|
+
post.pollIds = postData[index].pollIds || '';
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return hookData;
|
|
67
|
+
};
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
pid: obj.data.pid,
|
|
42
|
-
tid: tid,
|
|
43
|
-
});
|
|
44
|
-
delete postData.uid;
|
|
45
|
-
delete postData.pid;
|
|
46
|
-
delete postData.tid;
|
|
47
|
-
obj.post = postData;
|
|
69
|
+
Hooks.filter.postEdit = async function (hookData) {
|
|
70
|
+
const currentPollIds = await Poll.getPollIdsByPid(hookData.data.pid);
|
|
71
|
+
const toAdd = hookData.data.polls.filter(p => !currentPollIds.includes(String(p.pollId)));
|
|
48
72
|
|
|
49
|
-
if (
|
|
50
|
-
|
|
73
|
+
if (toAdd.length) {
|
|
74
|
+
const cid = await posts.getCidByPid(hookData.data.pid, 'cid');
|
|
75
|
+
await canCreate(cid, hookData.data.uid);
|
|
51
76
|
}
|
|
52
77
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
await Poll.edit({
|
|
79
|
+
...hookData.post,
|
|
80
|
+
uid: hookData.data.uid,
|
|
81
|
+
pid: hookData.data.pid,
|
|
82
|
+
}, hookData.data.polls);
|
|
83
|
+
hookData.post.pollIds = JSON.stringify(hookData.data.polls.map(p => String(p.pollId)));
|
|
84
|
+
return hookData;
|
|
56
85
|
};
|
|
57
86
|
|
|
58
87
|
Hooks.filter.topicPost = async function (data) {
|
|
59
|
-
if (
|
|
88
|
+
if (Array.isArray(data.polls) && data.polls.length) {
|
|
60
89
|
await canCreate(data.cid, data.uid);
|
|
61
|
-
return data;
|
|
62
90
|
}
|
|
63
91
|
return data;
|
|
64
92
|
};
|
|
65
93
|
|
|
66
|
-
Hooks.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
await
|
|
94
|
+
Hooks.filter.topicReply = async function (data) {
|
|
95
|
+
if (Array.isArray(data.polls) && data.polls.length) {
|
|
96
|
+
const cid = await topics.getTopicField(data.tid, 'cid');
|
|
97
|
+
await canCreate(cid, data.uid);
|
|
70
98
|
}
|
|
99
|
+
return data;
|
|
71
100
|
};
|
|
72
101
|
|
|
73
|
-
Hooks.action.
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
await Poll.
|
|
102
|
+
Hooks.action.postDelete = async function (data) {
|
|
103
|
+
const pollIds = await Poll.getPollIdsByPid(data.post.pid);
|
|
104
|
+
if (pollIds.length) {
|
|
105
|
+
await Poll.delete(pollIds);
|
|
77
106
|
}
|
|
78
107
|
};
|
|
79
108
|
|
|
80
|
-
Hooks.action.
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
Poll.
|
|
109
|
+
Hooks.action.postRestore = async function (data) {
|
|
110
|
+
const pollIds = await Poll.getPollIdsByPid(data.post.pid);
|
|
111
|
+
if (pollIds.length) {
|
|
112
|
+
await Poll.restore(pollIds);
|
|
84
113
|
}
|
|
85
114
|
};
|
|
86
115
|
|
|
87
|
-
Hooks.action.
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
Hooks.action.postsPurge = async function (data) {
|
|
117
|
+
const { posts } = data;
|
|
118
|
+
const pollIds = await Poll.getPollIdsByPids(posts.map(p => p.pid));
|
|
119
|
+
const toDelete = pollIds.flat();
|
|
120
|
+
if (toDelete.length) {
|
|
121
|
+
await Poll.deletePolls(toDelete);
|
|
91
122
|
}
|
|
92
123
|
};
|
|
93
124
|
|
|
94
125
|
async function canCreate(cid, uid) {
|
|
95
|
-
const can = await
|
|
126
|
+
const can = await privileges.categories.can('poll:create', cid, uid);
|
|
96
127
|
if (!can) {
|
|
97
128
|
throw new Error('[[poll:error.privilege.create]]');
|
|
98
129
|
}
|
|
99
130
|
}
|
|
100
131
|
|
|
101
|
-
// called from filter:post.edit and filter:post.create
|
|
102
|
-
async function savePoll(obj) {
|
|
103
|
-
const postobj = obj.post ? obj.post : obj;
|
|
104
|
-
const pollData = Serializer.serialize(postobj.content, Config.settings.get());
|
|
105
|
-
|
|
106
|
-
if (!pollData || !pollData.options.length) {
|
|
107
|
-
return obj;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const pollId = await Poll.add(pollData, postobj);
|
|
111
|
-
|
|
112
|
-
postobj.pollId = pollId;
|
|
113
|
-
postobj.content = Serializer.removeMarkup(postobj.content);
|
|
114
|
-
|
|
115
|
-
return obj;
|
|
116
|
-
}
|
|
117
|
-
|
package/lib/nodebb.js
CHANGED
|
@@ -10,7 +10,6 @@ module.exports = {
|
|
|
10
10
|
Privileges: require.main.require('./src/privileges'),
|
|
11
11
|
Plugins: require.main.require('./src/plugins'),
|
|
12
12
|
PluginSockets: require.main.require('./src/socket.io/plugins'),
|
|
13
|
-
AdminSockets: require.main.require('./src/socket.io/admin').plugins,
|
|
14
13
|
SocketIndex: require.main.require('./src/socket.io/index'),
|
|
15
14
|
Translator: require.main.require('./src/translator'),
|
|
16
15
|
winston: require.main.require('winston'),
|