nodebb-plugin-equipment-calendar 10.0.7 → 10.0.9

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/library.js CHANGED
@@ -54,6 +54,18 @@ plugin.init = async function (params) {
54
54
  // ACP ajaxify endpoint must render the template (not res.json), otherwise admin.min.js crashes
55
55
  router.get('/api/admin/plugins/equipment-calendar', middleware.admin.checkPrivileges, async (req, res) => {
56
56
  res.render('admin/plugins/equipment-calendar', {});
57
+ // Settings JSON endpoints (robust, works with MongoDB+Redis cache setups)
58
+ router.get('/api/admin/plugins/equipment-calendar/settings', middleware.admin.checkPrivileges, async (req, res) => {
59
+ const settings = await getSettings();
60
+ res.json({ settings });
61
+ });
62
+
63
+ router.post('/api/admin/plugins/equipment-calendar/settings', middleware.admin.checkPrivileges, middleware.applyCSRF, async (req, res) => {
64
+ const values = (req.body && req.body.values) ? req.body.values : {};
65
+ // Persist with NodeBB meta.settings (stored in DB, cached in Redis if enabled)
66
+ await meta.settings.set(SETTINGS_KEY, values);
67
+ res.json({ ok: true });
68
+ });
57
69
  });
58
70
 
59
71
  const renderCal = async (req, res) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-equipment-calendar",
3
- "version": "10.0.7",
3
+ "version": "10.0.9",
4
4
  "main": "library.js",
5
5
  "license": "MIT"
6
6
  }
@@ -1,40 +1,86 @@
1
1
  'use strict';
2
- /* globals ajaxify, app, require, $ */
2
+ /* globals ajaxify, app, $ */
3
3
 
4
4
  (function () {
5
- const HASH = 'equipmentCalendar';
6
-
7
5
  function onPage() {
8
6
  try {
9
7
  const url = (ajaxify && ajaxify.data && typeof ajaxify.data.url === 'string') ? ajaxify.data.url : '';
10
8
  return url === 'admin/plugins/equipment-calendar' || url.startsWith('admin/plugins/equipment-calendar?');
11
- } catch (e) {
12
- return false;
13
- }
9
+ } catch (e) { return false; }
14
10
  }
15
11
 
16
- function init() {
17
- if (!onPage()) return;
12
+ function getCsrf() {
13
+ // NodeBB ACP exposes csrf_token in ajaxify.data in most themes
14
+ try { if (ajaxify && ajaxify.data && ajaxify.data.csrf_token) return ajaxify.data.csrf_token; } catch (e) {}
15
+ // fallback: meta tag
16
+ const meta = document.querySelector('meta[name="csrf-token"]');
17
+ return meta ? meta.getAttribute('content') : '';
18
+ }
18
19
 
20
+ function fields($container) {
21
+ return $container.find('[name]');
22
+ }
23
+
24
+ function setValues($container, values) {
25
+ values = values || {};
26
+ fields($container).each(function () {
27
+ const $el = $(this);
28
+ const key = $el.attr('name');
29
+ const val = values[key];
30
+ $el.val(val != null ? String(val) : '');
31
+ });
32
+ }
33
+
34
+ function getValues($container) {
35
+ const out = {};
36
+ fields($container).each(function () {
37
+ const $el = $(this);
38
+ out[$el.attr('name')] = $el.val();
39
+ });
40
+ return out;
41
+ }
42
+
43
+ async function apiGet() {
44
+ const res = await fetch(config.relative_path + '/api/admin/plugins/equipment-calendar/settings', { credentials: 'same-origin' });
45
+ if (!res.ok) throw new Error('Load failed: ' + res.status);
46
+ return res.json();
47
+ }
48
+
49
+ async function apiSave(values) {
50
+ const csrf = getCsrf();
51
+ const res = await fetch(config.relative_path + '/api/admin/plugins/equipment-calendar/settings', {
52
+ method: 'POST',
53
+ credentials: 'same-origin',
54
+ headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
55
+ body: JSON.stringify({ values }),
56
+ });
57
+ let data = {};
58
+ try { data = await res.json(); } catch (e) {}
59
+ if (!res.ok) throw new Error((data && data.message) ? data.message : ('Save failed: ' + res.status));
60
+ return data;
61
+ }
62
+
63
+ async function init() {
64
+ if (!onPage()) return;
19
65
  const $container = $('.equipment-calendar-settings');
20
66
  if (!$container.length) return;
21
67
 
22
- if (typeof require !== 'function') {
23
- console.error('[equipment-calendar] require() indisponible dans l’ACP, impossible d’utiliser le module settings.');
24
- return;
68
+ try {
69
+ const data = await apiGet();
70
+ setValues($container, data.settings || {});
71
+ } catch (e) {
72
+ console.warn('[equipment-calendar] load settings error', e);
25
73
  }
26
74
 
27
- require(['settings'], function (Settings) {
28
- Settings.load(HASH, $container);
29
-
30
- $('#save').off('click.equipment-calendar').on('click.equipment-calendar', function (e) {
31
- e.preventDefault();
32
- Settings.save(HASH, $container, function () {
33
- if (app && app.alertSuccess) {
34
- app.alertSuccess('[[admin/settings:settings-saved]]');
35
- }
36
- });
37
- });
75
+ $('#ec-save').off('click.equipment-calendar').on('click.equipment-calendar', async function (e) {
76
+ e.preventDefault();
77
+ try {
78
+ await apiSave(getValues($container));
79
+ if (app && app.alertSuccess) app.alertSuccess('[[admin/settings:settings-saved]]');
80
+ } catch (err) {
81
+ if (app && app.alertError) app.alertError(err.message || String(err));
82
+ else console.error(err);
83
+ }
38
84
  });
39
85
  }
40
86
 
@@ -1,23 +1,23 @@
1
- <form class="acp-page-container settings equipment-calendar-settings">
1
+ <div class="acp-page-container equipment-calendar-settings">
2
2
  <h1 class="mb-3">Equipment Calendar</h1>
3
3
 
4
4
  <div class="alert alert-info mb-3">
5
- ACP style “Composer” : champs avec <code>name</code> uniquement + sauvegarde via module <code>settings</code>.
5
+ Sauvegarde ACP via endpoint admin JSON + CSRF (fallback robuste).
6
6
  </div>
7
7
 
8
8
  <div class="card card-body mb-3">
9
9
  <h5 class="mb-3">Permissions</h5>
10
10
  <div class="mb-3">
11
11
  <label class="form-label">Groupes autorisés à créer (creatorGroups, séparés par virgule)</label>
12
- <input class="form-control" type="text" name="creatorGroups">
12
+ <input class="form-control" type="text" name="creatorGroups" data-field="creatorGroups">
13
13
  </div>
14
14
  <div class="mb-3">
15
15
  <label class="form-label">Groupe valideur (approverGroup)</label>
16
- <input class="form-control" type="text" name="approverGroup">
16
+ <input class="form-control" type="text" name="approverGroup" data-field="approverGroup">
17
17
  </div>
18
18
  <div class="mb-0">
19
19
  <label class="form-label">Groupe notifié (notifyGroup)</label>
20
- <input class="form-control" type="text" name="notifyGroup">
20
+ <input class="form-control" type="text" name="notifyGroup" data-field="notifyGroup">
21
21
  </div>
22
22
  </div>
23
23
 
@@ -26,31 +26,31 @@
26
26
  <div class="row g-3">
27
27
  <div class="col-md-6">
28
28
  <label class="form-label">API Base URL (prod/sandbox)</label>
29
- <input class="form-control" type="text" name="ha_apiBaseUrl" placeholder="https://api.helloasso.com ou https://api.helloasso-sandbox.com">
29
+ <input class="form-control" type="text" name="ha_apiBaseUrl" data-field="ha_apiBaseUrl" placeholder="https://api.helloasso.com ou https://api.helloasso-sandbox.com">
30
30
  </div>
31
31
  <div class="col-md-6">
32
32
  <label class="form-label">Organization slug</label>
33
- <input class="form-control" type="text" name="ha_organizationSlug">
33
+ <input class="form-control" type="text" name="ha_organizationSlug" data-field="ha_organizationSlug">
34
34
  </div>
35
35
  <div class="col-md-6">
36
36
  <label class="form-label">Client ID</label>
37
- <input class="form-control" type="text" name="ha_clientId">
37
+ <input class="form-control" type="text" name="ha_clientId" data-field="ha_clientId">
38
38
  </div>
39
39
  <div class="col-md-6">
40
40
  <label class="form-label">Client Secret</label>
41
- <input class="form-control" type="password" name="ha_clientSecret">
41
+ <input class="form-control" type="password" name="ha_clientSecret" data-field="ha_clientSecret">
42
42
  </div>
43
43
  <div class="col-md-6">
44
44
  <label class="form-label">Form Type</label>
45
- <input class="form-control" type="text" name="ha_itemsFormType" placeholder="shop">
45
+ <input class="form-control" type="text" name="ha_itemsFormType" data-field="ha_itemsFormType" placeholder="shop">
46
46
  </div>
47
47
  <div class="col-md-6">
48
48
  <label class="form-label">Form Slug</label>
49
- <input class="form-control" type="text" name="ha_itemsFormSlug" placeholder="locations-materiel-2026">
49
+ <input class="form-control" type="text" name="ha_itemsFormSlug" data-field="ha_itemsFormSlug" placeholder="locations-materiel-2026">
50
50
  </div>
51
51
  <div class="col-12">
52
52
  <label class="form-label">Return URL base (callback)</label>
53
- <input class="form-control" type="text" name="ha_returnUrl" placeholder="https://www.onekite.com">
53
+ <input class="form-control" type="text" name="ha_returnUrl" data-field="ha_returnUrl" placeholder="https://www.onekite.com">
54
54
  </div>
55
55
  </div>
56
56
  </div>
@@ -60,11 +60,11 @@
60
60
  <div class="row g-3">
61
61
  <div class="col-md-6">
62
62
  <label class="form-label">Timeout paiement (minutes)</label>
63
- <input class="form-control" type="number" min="1" name="paymentTimeoutMinutes">
63
+ <input class="form-control" type="number" min="1" name="paymentTimeoutMinutes" data-field="paymentTimeoutMinutes">
64
64
  </div>
65
65
  <div class="col-md-6">
66
66
  <label class="form-label">Vue par défaut</label>
67
- <select class="form-select" name="defaultView">
67
+ <select class="form-select" name="defaultView" data-field="defaultView">
68
68
  <option value="dayGridMonth">Mois</option>
69
69
  <option value="timeGridWeek">Semaine</option>
70
70
  <option value="listWeek">Liste</option>
@@ -73,5 +73,5 @@
73
73
  </div>
74
74
  </div>
75
75
 
76
- <button id="save" class="btn btn-primary" type="button">Enregistrer</button>
77
- </form>
76
+ <button id="ec-save" class="btn btn-primary" type="button">Enregistrer</button>
77
+ </div>