nodebb-plugin-discord-onekite 1.1.22 → 1.1.25
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 +16 -6
- package/lib/admin.js +41 -0
- package/library.js +30 -65
- package/package.json +1 -1
- package/plugin.json +11 -6
- package/public/admin.js +118 -0
- package/templates/admin/plugins/discord-onekite.tpl +34 -33
- package/static/lib/admin.js +0 -50
package/README.md
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
# nodebb-plugin-discord-onekite v1.1.4.
|
|
1
|
+
# nodebb-plugin-discord-onekite v1.1.4.15
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
3
|
+
ACP is implemented like the working `nodebb-plugin-calendar-onekite` you provided:
|
|
4
|
+
- Admin template uses `admin/partials/settings/header.tpl` + `footer.tpl` (top save bar)
|
|
5
|
+
- Admin JS uses NodeBB v3 admin API endpoints:
|
|
6
|
+
- GET /api/v3/admin/plugins/discord-onekite/settings
|
|
7
|
+
- PUT /api/v3/admin/plugins/discord-onekite/settings
|
|
8
|
+
- Save buttons from header are intercepted and call the PUT endpoint.
|
|
9
|
+
|
|
10
|
+
Toast:
|
|
11
|
+
- Shows "Succès" (title) and "Paramètres enregistrés" (message) via `app.alert` when available.
|
|
12
|
+
|
|
13
|
+
Discord:
|
|
14
|
+
- Embed only: clickable title (embed.url) + message excerpt in embed.description.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Toast updated to match calendar-onekite: alerts.success/error with dedup.
|
package/lib/admin.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const meta = require.main.require('./src/meta');
|
|
4
|
+
|
|
5
|
+
const SETTINGS_KEY = 'discord-onekite';
|
|
6
|
+
|
|
7
|
+
function normalizeCids(v) {
|
|
8
|
+
if (!v) return [];
|
|
9
|
+
if (Array.isArray(v)) return v.map(String).filter(Boolean);
|
|
10
|
+
if (typeof v === 'string') return v.split(',').map(s => s.trim()).filter(Boolean);
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function getSettings() {
|
|
15
|
+
const s = await meta.settings.get(SETTINGS_KEY);
|
|
16
|
+
return {
|
|
17
|
+
webhookUrl: (s && s.webhookUrl) ? String(s.webhookUrl).trim() : '',
|
|
18
|
+
notifyReplies: !!(s && (s.notifyReplies === true || s.notifyReplies === 'on' || s.notifyReplies === 'true')),
|
|
19
|
+
cids: normalizeCids(s && s.cids),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function saveSettings(body) {
|
|
24
|
+
const payload = {
|
|
25
|
+
webhookUrl: body.webhookUrl || '',
|
|
26
|
+
notifyReplies: body.notifyReplies ? 'on' : '',
|
|
27
|
+
// Store as CSV string for meta.settings
|
|
28
|
+
cids: Array.isArray(body.cids) ? body.cids.map(String).filter(Boolean).join(',') : (body.cids || ''),
|
|
29
|
+
};
|
|
30
|
+
await meta.settings.set(SETTINGS_KEY, payload);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
async getSettings(req, res) {
|
|
35
|
+
res.json({ settings: await getSettings() });
|
|
36
|
+
},
|
|
37
|
+
async saveSettings(req, res) {
|
|
38
|
+
await saveSettings(req.body || {});
|
|
39
|
+
res.json({ ok: true });
|
|
40
|
+
},
|
|
41
|
+
};
|
package/library.js
CHANGED
|
@@ -4,13 +4,14 @@ const { request } = require('undici');
|
|
|
4
4
|
|
|
5
5
|
const routeHelpers = require.main.require('./src/routes/helpers');
|
|
6
6
|
const meta = require.main.require('./src/meta');
|
|
7
|
-
const middleware = require.main.require('./src/middleware');
|
|
8
7
|
|
|
9
8
|
const topics = require.main.require('./src/topics');
|
|
10
9
|
const posts = require.main.require('./src/posts');
|
|
11
|
-
const user = require.main.require('./src/user');
|
|
12
10
|
|
|
13
11
|
const controllers = require('./lib/controllers');
|
|
12
|
+
const adminApi = require('./lib/admin');
|
|
13
|
+
|
|
14
|
+
const Plugin = {};
|
|
14
15
|
|
|
15
16
|
const SETTINGS_KEY = 'discord-onekite';
|
|
16
17
|
|
|
@@ -38,15 +39,6 @@ function ensureAbsoluteUrl(baseUrl, maybeUrl) {
|
|
|
38
39
|
return s;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function isValidHttpUrl(s) {
|
|
42
|
-
try {
|
|
43
|
-
const u = new URL(s);
|
|
44
|
-
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
45
|
-
} catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
42
|
function stripHtml(html) {
|
|
51
43
|
if (!html) return '';
|
|
52
44
|
return String(html)
|
|
@@ -99,19 +91,15 @@ async function postToDiscord(webhookUrl, payload) {
|
|
|
99
91
|
}
|
|
100
92
|
}
|
|
101
93
|
|
|
102
|
-
async function sendDiscord(webhookUrl, payload
|
|
94
|
+
async function sendDiscord(webhookUrl, payload) {
|
|
103
95
|
if (!webhookUrl) return;
|
|
104
|
-
|
|
105
96
|
try {
|
|
106
97
|
await postToDiscord(webhookUrl, payload);
|
|
107
98
|
} catch (e) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
} catch (e2) {
|
|
113
|
-
console.error(e2);
|
|
114
|
-
}
|
|
99
|
+
// fallback to plain content if embeds rejected
|
|
100
|
+
if (e && e.statusCode === 400 && payload && payload.embeds) {
|
|
101
|
+
const fallback = payload.embeds[0] && payload.embeds[0].description ? payload.embeds[0].description : 'Notification';
|
|
102
|
+
try { await postToDiscord(webhookUrl, { content: fallback }); return; } catch (e2) { console.error(e2); }
|
|
115
103
|
}
|
|
116
104
|
console.error(e);
|
|
117
105
|
}
|
|
@@ -128,43 +116,20 @@ async function getPostExcerpt(pid) {
|
|
|
128
116
|
}
|
|
129
117
|
}
|
|
130
118
|
|
|
131
|
-
async function
|
|
119
|
+
async function buildEmbed({ tid, pid, isReply }) {
|
|
132
120
|
let baseUrl = normalizeBaseUrl(meta.config.url || meta.config['url']);
|
|
133
|
-
if (!baseUrl)
|
|
121
|
+
if (!baseUrl) baseUrl = 'https://www.onekite.com';
|
|
134
122
|
|
|
135
|
-
const topicData = await topics.getTopicFields(tid, ['tid', '
|
|
123
|
+
const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'title', 'slug', 'mainPid']);
|
|
136
124
|
if (!topicData) return null;
|
|
137
125
|
|
|
138
|
-
// Always build the forum link like the example: https://www.onekite.com/topic/<tid or slug>
|
|
139
126
|
const topicUrl = ensureAbsoluteUrl(baseUrl, `/topic/${topicData.slug || topicData.tid}`);
|
|
127
|
+
const targetUrl = (isReply && pid) ? `${topicUrl}/${pid}` : topicUrl;
|
|
140
128
|
|
|
141
|
-
// For replies, link directly to the post if possible
|
|
142
|
-
const postUrl = (isReply && pid) ? `${topicUrl}/${pid}` : topicUrl;
|
|
143
|
-
|
|
144
|
-
const u = await user.getUserFields(topicData.uid, ['username']);
|
|
145
129
|
const title = (topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet')).toString().slice(0, 256);
|
|
146
|
-
const authorName = (u && u.username ? String(u.username) : 'Utilisateur').slice(0, 256);
|
|
147
|
-
|
|
148
130
|
const excerpt = await getPostExcerpt(isReply ? pid : topicData.mainPid);
|
|
149
|
-
const description = truncate(excerpt || (isReply ? `Réponse de ${authorName}` : `Sujet créé par ${authorName}`), 500);
|
|
150
131
|
|
|
151
|
-
|
|
152
|
-
const embed = {
|
|
153
|
-
title,
|
|
154
|
-
description,
|
|
155
|
-
};
|
|
156
|
-
if (isValidHttpUrl(postUrl)) embed.url = postUrl;
|
|
157
|
-
|
|
158
|
-
// Message format:
|
|
159
|
-
// 1) A single clickable link line: "🆕 Nouveau sujet : [Titre](URL)" (or reply variant)
|
|
160
|
-
// 2) Excerpt below
|
|
161
|
-
const linkLine = isReply
|
|
162
|
-
? `🗨️ Nouvelle réponse : [${title}](${postUrl})`
|
|
163
|
-
: `🆕 Nouveau sujet : [${title}](${topicUrl})`;
|
|
164
|
-
|
|
165
|
-
const content = [linkLine, description].filter(Boolean).join('\n');
|
|
166
|
-
|
|
167
|
-
return { topicData, embed, content };
|
|
132
|
+
return { topicData, embed: { title, url: targetUrl, description: excerpt || '' } };
|
|
168
133
|
}
|
|
169
134
|
|
|
170
135
|
function extractTidPid(data) {
|
|
@@ -173,15 +138,20 @@ function extractTidPid(data) {
|
|
|
173
138
|
return { tid, pid };
|
|
174
139
|
}
|
|
175
140
|
|
|
176
|
-
|
|
141
|
+
Plugin.init = async function (params) {
|
|
142
|
+
const { router, middleware } = params;
|
|
143
|
+
|
|
144
|
+
// ACP page
|
|
145
|
+
routeHelpers.setupAdminPageRoute(router, '/admin/plugins/discord-onekite', [], controllers.renderAdminPage);
|
|
177
146
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
147
|
+
// Admin API v3 routes (same pattern as calendar-onekite)
|
|
148
|
+
const adminBases = ['/api/v3/admin/plugins/discord-onekite'];
|
|
149
|
+
const adminMws = [middleware.admin.checkPrivileges];
|
|
150
|
+
|
|
151
|
+
adminBases.forEach((base) => {
|
|
152
|
+
router.get(`${base}/settings`, ...adminMws, adminApi.getSettings);
|
|
153
|
+
router.put(`${base}/settings`, ...adminMws, adminApi.saveSettings);
|
|
154
|
+
});
|
|
185
155
|
};
|
|
186
156
|
|
|
187
157
|
Plugin.addAdminNavigation = (header) => {
|
|
@@ -200,13 +170,11 @@ Plugin.onTopicPost = async (data) => {
|
|
|
200
170
|
const { tid } = extractTidPid(data);
|
|
201
171
|
if (!tid) return;
|
|
202
172
|
|
|
203
|
-
const built = await
|
|
173
|
+
const built = await buildEmbed({ tid, isReply: false });
|
|
204
174
|
if (!built) return;
|
|
205
|
-
|
|
206
175
|
if (!cidAllowed(built.topicData.cid, settings.cids)) return;
|
|
207
176
|
|
|
208
|
-
|
|
209
|
-
await sendDiscord(settings.webhookUrl, { content: built.content, embeds: [built.embed] }, built.content);
|
|
177
|
+
await sendDiscord(settings.webhookUrl, { embeds: [built.embed] });
|
|
210
178
|
};
|
|
211
179
|
|
|
212
180
|
Plugin.onTopicReply = async (data) => {
|
|
@@ -217,14 +185,11 @@ Plugin.onTopicReply = async (data) => {
|
|
|
217
185
|
const { tid, pid } = extractTidPid(data);
|
|
218
186
|
if (!tid || !pid) return;
|
|
219
187
|
|
|
220
|
-
const built = await
|
|
188
|
+
const built = await buildEmbed({ tid, pid, isReply: true });
|
|
221
189
|
if (!built) return;
|
|
222
|
-
|
|
223
|
-
if (built.topicData?.mainPid && String(built.topicData.mainPid) === String(pid)) return;
|
|
224
|
-
|
|
225
190
|
if (!cidAllowed(built.topicData.cid, settings.cids)) return;
|
|
226
191
|
|
|
227
|
-
await sendDiscord(settings.webhookUrl, {
|
|
192
|
+
await sendDiscord(settings.webhookUrl, { embeds: [built.embed] });
|
|
228
193
|
};
|
|
229
194
|
|
|
230
195
|
module.exports = Plugin;
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "nodebb-plugin-discord-onekite",
|
|
3
3
|
"name": "Discord Onekite Notifier",
|
|
4
|
-
"description": "Notifie Discord via webhook pour nouveaux sujets et/ou r\u00e9ponses
|
|
4
|
+
"description": "Notifie Discord via webhook pour nouveaux sujets et/ou r\u00e9ponses (NodeBB v4.x).",
|
|
5
5
|
"library": "./library.js",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
@@ -21,11 +21,16 @@
|
|
|
21
21
|
"method": "onTopicReply"
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
|
-
"
|
|
24
|
+
"staticDirs": {
|
|
25
|
+
"public": "./public"
|
|
26
|
+
},
|
|
27
|
+
"templates": "./templates",
|
|
28
|
+
"modules": {
|
|
29
|
+
"../admin/plugins/discord-onekite.js": "./public/admin.js",
|
|
30
|
+
"admin/plugins/discord-onekite": "./public/admin.js"
|
|
31
|
+
},
|
|
25
32
|
"acpScripts": [
|
|
26
|
-
"
|
|
33
|
+
"public/admin.js"
|
|
27
34
|
],
|
|
28
|
-
"
|
|
29
|
-
"admin/plugins/discord-onekite.js": "static/lib/admin.js"
|
|
30
|
-
}
|
|
35
|
+
"version": "1.1.4.16"
|
|
31
36
|
}
|
package/public/admin.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
define('admin/plugins/discord-onekite', ['alerts', 'bootbox'], function (alerts) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
function showAlert(type, msg) {
|
|
5
|
+
// Deduplicate identical alerts that can be triggered multiple times
|
|
6
|
+
// by NodeBB ACP save buttons/hooks across ajaxify navigations.
|
|
7
|
+
try {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const last = window.oneKiteDiscordLastAlert;
|
|
10
|
+
if (last && last.type === type && last.msg === msg && (now - last.ts) < 1200) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
window.oneKiteDiscordLastAlert = { type, msg, ts: now };
|
|
14
|
+
} catch (e) {}
|
|
15
|
+
try {
|
|
16
|
+
if (alerts && typeof alerts[type] === 'function') {
|
|
17
|
+
alerts[type](msg);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
} catch (e) {}
|
|
21
|
+
alert(msg);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function fetchJson(url, opts) {
|
|
25
|
+
const res = await fetch(url, Object.assign({
|
|
26
|
+
headers: Object.assign({
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'x-csrf-token': (document.querySelector('meta[name="csrf_token"]') && document.querySelector('meta[name="csrf_token"]').getAttribute('content')) ||
|
|
29
|
+
(document.querySelector('meta[name="csrf-token"]') && document.querySelector('meta[name="csrf-token"]').getAttribute('content')) ||
|
|
30
|
+
(typeof app !== 'undefined' && app && app.csrfToken) ||
|
|
31
|
+
'',
|
|
32
|
+
}, (opts && opts.headers) || {}),
|
|
33
|
+
credentials: 'same-origin',
|
|
34
|
+
}, opts || {}));
|
|
35
|
+
|
|
36
|
+
const text = await res.text();
|
|
37
|
+
let data = null;
|
|
38
|
+
try { data = text ? JSON.parse(text) : null; } catch (e) {}
|
|
39
|
+
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
const err = new Error((data && data.status && data.status.message) || text || 'Request failed');
|
|
42
|
+
err.status = res.status;
|
|
43
|
+
err.data = data;
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function loadSettings() {
|
|
50
|
+
const data = await fetchJson('/api/v3/admin/plugins/discord-onekite/settings');
|
|
51
|
+
return (data && data.settings) ? data.settings : {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function saveSettings(payload) {
|
|
55
|
+
return await fetchJson('/api/v3/admin/plugins/discord-onekite/settings', {
|
|
56
|
+
method: 'PUT',
|
|
57
|
+
body: JSON.stringify(payload),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getSelectedCids() {
|
|
62
|
+
return Array.from(document.querySelectorAll('#cidsMulti option:checked')).map(o => String(o.value));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setSelectedCids(cids) {
|
|
66
|
+
const set = new Set((cids || []).map(String));
|
|
67
|
+
document.querySelectorAll('#cidsMulti option').forEach(opt => {
|
|
68
|
+
opt.selected = set.has(String(opt.value));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getPayload() {
|
|
73
|
+
return {
|
|
74
|
+
webhookUrl: String(document.getElementById('webhookUrl')?.value || '').trim(),
|
|
75
|
+
notifyReplies: !!document.getElementById('notifyReplies')?.checked,
|
|
76
|
+
cids: getSelectedCids(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function doSave() {
|
|
81
|
+
try {
|
|
82
|
+
await saveSettings(getPayload());
|
|
83
|
+
showAlert('success', 'Paramètres enregistrés');
|
|
84
|
+
} catch (e) {
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.error(e);
|
|
87
|
+
showAlert('error', e && e.message ? e.message : 'Erreur lors de l’enregistrement');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function bindSaveButtons() {
|
|
92
|
+
const SAVE_SELECTOR = '#save, .save, [data-action="save"], .settings-save, .floating-save, .btn[data-action="save"], #onekite-save, #save-top';
|
|
93
|
+
if (window.oneKiteDiscordAdminBound) return;
|
|
94
|
+
window.oneKiteDiscordAdminBound = true;
|
|
95
|
+
|
|
96
|
+
document.addEventListener('click', function (ev) {
|
|
97
|
+
const target = ev.target && (ev.target.closest ? ev.target.closest(SAVE_SELECTOR) : null);
|
|
98
|
+
if (!target) return;
|
|
99
|
+
ev.preventDefault();
|
|
100
|
+
doSave();
|
|
101
|
+
}, true);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function init() {
|
|
105
|
+
bindSaveButtons();
|
|
106
|
+
try {
|
|
107
|
+
const s = await loadSettings();
|
|
108
|
+
if (typeof s.webhookUrl !== 'undefined') document.getElementById('webhookUrl').value = s.webhookUrl || '';
|
|
109
|
+
if (typeof s.notifyReplies !== 'undefined') document.getElementById('notifyReplies').checked = !!s.notifyReplies;
|
|
110
|
+
if (Array.isArray(s.cids)) setSelectedCids(s.cids);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
// eslint-disable-next-line no-console
|
|
113
|
+
console.error(e);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { init: init };
|
|
118
|
+
});
|
|
@@ -1,39 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
3
|
-
<div>
|
|
4
|
-
<h4 class="mb-1">Discord Onekite</h4>
|
|
5
|
-
<p class="text-muted mb-0">Notifications Discord via webhook.</p>
|
|
6
|
-
</div>
|
|
7
|
-
<button id="onekite-save" type="button" class="btn btn-primary">Enregistrer les paramètres</button>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<form role="form" class="discord-onekite-settings">
|
|
11
|
-
<div class="mb-3">
|
|
12
|
-
<label class="form-label" for="webhookUrl">Discord Webhook URL</label>
|
|
13
|
-
<input type="text" class="form-control" id="webhookUrl" data-field="webhookUrl" value="{settings.webhookUrl}" placeholder="https://discord.com/api/webhooks/..." />
|
|
14
|
-
</div>
|
|
1
|
+
<!-- IMPORT admin/partials/settings/header.tpl -->
|
|
15
2
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<label class="form-label" for="cidsMulti">Catégories à notifier</label>
|
|
3
|
+
<div class="row">
|
|
4
|
+
<div class="col-lg-8">
|
|
5
|
+
<div class="card mb-3">
|
|
6
|
+
<div class="card-header">
|
|
7
|
+
<strong>Discord Onekite</strong>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="card-body">
|
|
10
|
+
<p class="text-muted">Notifications Discord via webhook.</p>
|
|
25
11
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
<form class="discord-onekite-settings" role="form">
|
|
13
|
+
<div class="mb-3">
|
|
14
|
+
<label class="form-label" for="webhookUrl">Discord Webhook URL</label>
|
|
15
|
+
<input id="webhookUrl" type="text" class="form-control" placeholder="https://discord.com/api/webhooks/..." value="{settings.webhookUrl}" />
|
|
16
|
+
</div>
|
|
31
17
|
|
|
32
|
-
|
|
18
|
+
<div class="form-check mb-3">
|
|
19
|
+
<input class="form-check-input" type="checkbox" id="notifyReplies" <!-- IF settings.notifyReplies -->checked<!-- ENDIF settings.notifyReplies -->>
|
|
20
|
+
<label class="form-check-label" for="notifyReplies">Notifier aussi les réponses</label>
|
|
21
|
+
</div>
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
<div class="mb-3">
|
|
24
|
+
<label class="form-label" for="cidsMulti">Catégories à notifier</label>
|
|
25
|
+
<select class="form-select" id="cidsMulti" multiple size="12">
|
|
26
|
+
<!-- BEGIN categories -->
|
|
27
|
+
<option value="{categories.cid}" <!-- IF categories.selected -->selected<!-- ENDIF categories.selected -->>{categories.name}</option>
|
|
28
|
+
<!-- END categories -->
|
|
29
|
+
</select>
|
|
30
|
+
<p class="form-text text-muted">
|
|
31
|
+
Si aucune catégorie n’est sélectionnée : <strong>toutes les catégories</strong> seront notifiées.
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
</form>
|
|
35
|
+
</div>
|
|
37
36
|
</div>
|
|
38
|
-
</
|
|
37
|
+
</div>
|
|
39
38
|
</div>
|
|
39
|
+
|
|
40
|
+
<!-- IMPORT admin/partials/settings/footer.tpl -->
|
package/static/lib/admin.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/* global $, app */
|
|
3
|
-
|
|
4
|
-
define('admin/plugins/discord-onekite', ['settings'], function (settings) {
|
|
5
|
-
const ACP = {};
|
|
6
|
-
|
|
7
|
-
function parseCsv(v) {
|
|
8
|
-
if (!v) return [];
|
|
9
|
-
return String(v).split(',').map(s => s.trim()).filter(Boolean);
|
|
10
|
-
}
|
|
11
|
-
function toCsv(arr) {
|
|
12
|
-
return (arr || []).map(String).filter(Boolean).join(',');
|
|
13
|
-
}
|
|
14
|
-
function setSelectedCids(cids) {
|
|
15
|
-
const set = new Set((cids || []).map(String));
|
|
16
|
-
$('#cidsMulti option').each(function () {
|
|
17
|
-
$(this).prop('selected', set.has(String($(this).val())));
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
function getSelectedCids() {
|
|
21
|
-
return $('#cidsMulti option:selected').map(function () { return String($(this).val()); }).get();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function toastSuccess() {
|
|
25
|
-
if (window.app && typeof app.alert === 'function') {
|
|
26
|
-
app.alert({ type: 'success', title: 'Succès', message: 'Paramètres enregistrés' });
|
|
27
|
-
} else if (window.app && typeof app.alertSuccess === 'function') {
|
|
28
|
-
app.alertSuccess('Succès: Paramètres enregistrés');
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
ACP.init = function () {
|
|
33
|
-
const $form = $('.discord-onekite-settings');
|
|
34
|
-
if (!$form.length) return;
|
|
35
|
-
|
|
36
|
-
settings.sync('discord-onekite', $form, function () {
|
|
37
|
-
setSelectedCids(parseCsv($('#cids').val()));
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
$('#onekite-save').off('click.discordOnekite').on('click.discordOnekite', function (e) {
|
|
41
|
-
e.preventDefault();
|
|
42
|
-
$('#cids').val(toCsv(getSelectedCids()));
|
|
43
|
-
settings.persist('discord-onekite', $form, function () {
|
|
44
|
-
toastSuccess();
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return ACP;
|
|
50
|
-
});
|