nodebb-plugin-simple-contact 1.0.0 → 1.1.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/library.js CHANGED
@@ -12,24 +12,16 @@ ContactPlugin.init = async function (params) {
12
12
  const router = params.router;
13
13
  const middleware = params.middleware;
14
14
 
15
- console.log('--- Contact Plugin Initialized ---'); // בדיקה שהתוסף נטען
16
-
17
- // Route for the user facing page
18
15
  router.get('/contact', middleware.buildHeader, renderContactPage);
19
16
  router.get('/api/contact', renderContactPage);
20
-
21
- // API Route to submit form
22
17
  router.post('/api/contact/send', middleware.applyCSRF, handleContactSubmission);
23
18
 
24
- // Route for Admin Panel
25
19
  router.get('/admin/plugins/contact', middleware.admin.buildHeader, renderAdminPage);
26
20
  router.get('/api/admin/plugins/contact', renderAdminPage);
27
-
28
- // API to mark as handled
29
21
  router.post('/api/admin/plugins/contact/handle', middleware.admin.buildHeader, markAsHandled);
22
+ router.post('/api/admin/plugins/contact/delete', middleware.admin.buildHeader, deleteRequest);
30
23
  };
31
24
 
32
- // Render User Page (עם תיקון פירורי הלחם)
33
25
  async function renderContactPage(req, res) {
34
26
  res.render('contact', {
35
27
  title: 'צור קשר',
@@ -40,11 +32,8 @@ async function renderContactPage(req, res) {
40
32
  });
41
33
  }
42
34
 
43
- // Handle Form Submission (עם הלוגיקה המשופרת והבטוחה)
44
35
  async function handleContactSubmission(req, res) {
45
36
  const data = req.body;
46
-
47
- // Validation
48
37
  if (!data.fullName || !data.email || !data.content) {
49
38
  return res.status(400).json({ error: 'נא למלא את כל שדות החובה.' });
50
39
  }
@@ -63,41 +52,28 @@ async function handleContactSubmission(req, res) {
63
52
  };
64
53
 
65
54
  try {
66
- // Save to DB
67
55
  await db.setObject(key, contactData);
68
56
  await db.sortedSetAdd('contact-requests:sorted', contactId, contactId);
69
57
 
70
- // Send Notification to Admins
71
- try {
72
- const adminUids = await groups.getMembers('administrators', 0, -1);
73
-
74
- if (adminUids && adminUids.length > 0) {
75
- const notification = await notifications.create({
76
- type: 'new-contact',
77
- bodyShort: `פנייה חדשה מאת ${contactData.fullName}`,
78
- bodyLong: contactData.content,
79
- nid: 'contact:' + contactId,
80
- path: '/admin/plugins/contact',
81
- from: 0
82
- });
83
-
84
- await notifications.push(notification, adminUids);
85
- } else {
86
- console.warn('Contact Plugin: No administrators found to notify.');
87
- }
88
- } catch (notifyErr) {
89
- console.error('Contact Plugin Notification Error:', notifyErr);
90
- // לא מכשילים את הבקשה אם רק ההתראה נכשלה
58
+ const adminUids = await groups.getMembers('administrators', 0, -1);
59
+ if (adminUids && adminUids.length > 0) {
60
+ const notification = await notifications.create({
61
+ type: 'new-contact',
62
+ bodyShort: `פנייה חדשה מאת ${contactData.fullName}`,
63
+ bodyLong: contactData.content,
64
+ nid: 'contact:' + contactId,
65
+ path: '/admin/plugins/contact',
66
+ from: 0
67
+ });
68
+ await notifications.push(notification, adminUids);
91
69
  }
92
70
 
93
71
  res.json({ success: true, message: 'הפנייה נשלחה בהצלחה.' });
94
72
  } catch (err) {
95
- console.error('Contact Plugin Error:', err);
96
- res.status(500).json({ error: 'שגיאה פנימית בשליחת הטופס.' });
73
+ res.status(500).json({ error: 'שגיאה פנימית.' });
97
74
  }
98
75
  }
99
76
 
100
- // Render Admin Page
101
77
  async function renderAdminPage(req, res) {
102
78
  const ids = await db.getSortedSetRevRange('contact-requests:sorted', 0, -1);
103
79
  let items = [];
@@ -107,19 +83,26 @@ async function renderAdminPage(req, res) {
107
83
  items = await db.getObjects(keys);
108
84
  }
109
85
 
86
+ const waitingRequests = [];
87
+ const handledRequests = [];
88
+
110
89
  items.forEach(item => {
111
- // המרת תאריך למחרוזת קריאה
112
90
  item.date = new Date(parseInt(item.timestamp)).toLocaleString();
91
+ if (item.handled) {
92
+ handledRequests.push(item);
93
+ } else {
94
+ waitingRequests.push(item);
95
+ }
113
96
  });
114
97
 
115
- res.render('admin/plugins/contact', { requests: items });
98
+ res.render('admin/plugins/contact', {
99
+ waitingRequests: waitingRequests,
100
+ handledRequests: handledRequests
101
+ });
116
102
  }
117
103
 
118
- // Mark as Handled logic
119
104
  async function markAsHandled(req, res) {
120
105
  const id = req.body.id;
121
- if (!id) return res.status(400).json({ error: 'Missing ID' });
122
-
123
106
  try {
124
107
  await db.setObjectField('contact-request:' + id, 'handled', true);
125
108
  res.json({ success: true });
@@ -128,23 +111,24 @@ async function markAsHandled(req, res) {
128
111
  }
129
112
  }
130
113
 
131
- // Add link to main navigation
114
+ async function deleteRequest(req, res) {
115
+ const id = req.body.id;
116
+ try {
117
+ await db.delete('contact-request:' + id);
118
+ await db.sortedSetRemove('contact-requests:sorted', id);
119
+ res.json({ success: true });
120
+ } catch (err) {
121
+ res.status(500).json({ error: err.message });
122
+ }
123
+ }
124
+
132
125
  ContactPlugin.addNavigation = async function (header) {
133
- header.navigation.push({
134
- route: '/contact',
135
- iconClass: 'fa-envelope',
136
- text: 'צור קשר'
137
- });
126
+ header.navigation.push({ route: '/contact', iconClass: 'fa-envelope', text: 'צור קשר' });
138
127
  return header;
139
128
  };
140
129
 
141
- // Add link to admin menu
142
130
  ContactPlugin.addAdminNavigation = async function (header) {
143
- header.plugins.push({
144
- route: '/plugins/contact',
145
- icon: 'fa-envelope',
146
- name: 'פניות צור קשר'
147
- });
131
+ header.plugins.push({ route: '/plugins/contact', icon: 'fa-envelope', name: 'פניות צור קשר' });
148
132
  return header;
149
133
  };
150
134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-simple-contact",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "תוסף טופס צור קשר עם התראות למנהלים וניהול פניות",
5
5
  "main": "library.js",
6
6
  "nbbpm": {
package/plugin.json CHANGED
@@ -1,20 +1,11 @@
1
1
  {
2
2
  "id": "nodebb-plugin-simple-contact",
3
3
  "name": "Simple Contact Form",
4
- "description": "A contact form with admin notifications.",
5
- "url": "http://localhost",
6
4
  "library": "./library.js",
7
5
  "hooks": [
8
6
  { "hook": "static:app.load", "method": "init" },
9
7
  { "hook": "filter:navigation.available", "method": "addNavigation" },
10
8
  { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }
11
9
  ],
12
- "scripts": [
13
- "public/js/client.js"
14
- ],
15
- "acpScripts": [
16
- "public/js/admin.js"
17
- ],
18
- "modules": {},
19
10
  "templates": "public/templates"
20
11
  }
@@ -4,7 +4,8 @@
4
4
  <div class="panel panel-warning">
5
5
  <div class="panel-heading">ממתין - פניות לטיפול</div>
6
6
  <div class="panel-body">
7
- <table class="table table-striped">
7
+ {{{ if waitingRequests.length }}}
8
+ <table class="table table-striped table-hover">
8
9
  <thead>
9
10
  <tr>
10
11
  <th>תאריך</th>
@@ -17,8 +18,7 @@
17
18
  </tr>
18
19
  </thead>
19
20
  <tbody id="contact-list-waiting">
20
- {{{ each requests }}}
21
- {{{ if !./handled }}}
21
+ {{{ each waitingRequests }}}
22
22
  <tr data-id="{./id}">
23
23
  <td>{./date}</td>
24
24
  <td>{./fullName}</td>
@@ -27,13 +27,17 @@
27
27
  <td>{./content}</td>
28
28
  <td><span class="label label-warning">ממתין</span></td>
29
29
  <td>
30
- <button class="btn btn-sm btn-primary mark-handled">סמן כטופל</button>
30
+ <button class="btn btn-xs btn-primary mark-handled" data-id="{./id}">סמן כטופל</button>
31
31
  </td>
32
32
  </tr>
33
33
  {{{ end }}}
34
- {{{ end }}}
35
34
  </tbody>
36
35
  </table>
36
+ {{{ else }}}
37
+ <div class="alert alert-info text-center">
38
+ אין פניות ממתינות כרגע.
39
+ </div>
40
+ {{{ end }}}
37
41
  </div>
38
42
  </div>
39
43
 
@@ -42,7 +46,8 @@
42
46
  <div class="panel panel-success">
43
47
  <div class="panel-heading">מטופל - ארכיון פניות</div>
44
48
  <div class="panel-body">
45
- <table class="table table-striped">
49
+ {{{ if handledRequests.length }}}
50
+ <table class="table table-striped table-hover">
46
51
  <thead>
47
52
  <tr>
48
53
  <th>תאריך</th>
@@ -55,8 +60,7 @@
55
60
  </tr>
56
61
  </thead>
57
62
  <tbody id="contact-list-handled">
58
- {{{ each requests }}}
59
- {{{ if ./handled }}}
63
+ {{{ each handledRequests }}}
60
64
  <tr data-id="{./id}" class="success">
61
65
  <td>{./date}</td>
62
66
  <td>{./fullName}</td>
@@ -65,14 +69,150 @@
65
69
  <td>{./content}</td>
66
70
  <td><span class="label label-success">טופל</span></td>
67
71
  <td>
68
- </td>
72
+ <button class="btn btn-xs btn-danger delete-request" data-id="{./id}">מחק</button>
73
+ </td>
69
74
  </tr>
70
75
  {{{ end }}}
71
- {{{ end }}}
72
76
  </tbody>
73
77
  </table>
78
+ {{{ else }}}
79
+ <div class="alert alert-info text-center">
80
+ ארכיון הפניות ריק.
81
+ </div>
82
+ {{{ end }}}
74
83
  </div>
75
84
  </div>
76
85
 
77
86
  </div>
78
- </div>
87
+ </div>
88
+
89
+ <script>
90
+ (function() {
91
+ if (typeof define === 'function' && define.amd) {
92
+ define('admin/plugins/contact', [], function() { return {}; });
93
+ }
94
+
95
+ function showMessage(type, msg) {
96
+ if (window.app && typeof window.app.require === 'function') {
97
+ window.app.require('alerts').then(function(alerts) {
98
+ if (type === 'success') {
99
+ alerts.success(msg);
100
+ } else {
101
+ alerts.error(msg);
102
+ }
103
+ }).catch(function(err) {
104
+ console.error('Failed to load alerts via app.require:', err);
105
+ fallbackAlert(type, msg);
106
+ });
107
+ return;
108
+ }
109
+
110
+ const req = window.require || window.requirejs;
111
+ if (req) {
112
+ req(['alerts'], function(alerts) {
113
+ if (alerts) {
114
+ if (type === 'success' && alerts.success) alerts.success(msg);
115
+ else if (alerts.error) alerts.error(msg);
116
+ else fallbackAlert(type, msg);
117
+ } else {
118
+ fallbackAlert(type, msg);
119
+ }
120
+ });
121
+ return;
122
+ }
123
+
124
+ if (window.app && window.app.alert) {
125
+ window.app.alert({
126
+ type: type === 'success' ? 'success' : 'danger',
127
+ message: msg,
128
+ timeout: 3000
129
+ });
130
+ return;
131
+ }
132
+
133
+ fallbackAlert(type, msg);
134
+ }
135
+
136
+ function fallbackAlert(type, msg) {
137
+ alert((type === 'success' ? 'הצלחה: ' : 'שגיאה: ') + msg.replace(/<[^>]*>/g, ''));
138
+ }
139
+
140
+ function initContactPage() {
141
+ $('.mark-handled').off('click');
142
+ $('.delete-request').off('click');
143
+
144
+ $('.mark-handled').on('click', function() {
145
+ var btn = $(this);
146
+ var id = btn.data('id');
147
+
148
+ if (window.app && window.app.require) {
149
+ window.app.require('bootbox').then(function(bootbox) {
150
+ bootbox.confirm('האם אתה בטוח שברצונך לסמן פנייה זו כטופלה?', function(confirm) {
151
+ if (confirm) performHandle(id);
152
+ });
153
+ });
154
+ } else if (window.bootbox) {
155
+ window.bootbox.confirm('האם אתה בטוח שברצונך לסמן פנייה זו כטופלה?', function(confirm) {
156
+ if (confirm) performHandle(id);
157
+ });
158
+ } else {
159
+ if (confirm('האם אתה בטוח שברצונך לסמן פנייה זו כטופלה?')) performHandle(id);
160
+ }
161
+ });
162
+
163
+ $('.delete-request').on('click', function() {
164
+ var btn = $(this);
165
+ var id = btn.data('id');
166
+
167
+ if (window.app && window.app.require) {
168
+ window.app.require('bootbox').then(function(bootbox) {
169
+ bootbox.confirm('האם אתה בטוח שברצונך למחוק פנייה זו לצמיתות?', function(confirm) {
170
+ if (confirm) performDelete(id);
171
+ });
172
+ });
173
+ } else if (window.bootbox) {
174
+ window.bootbox.confirm('האם אתה בטוח שברצונך למחוק פנייה זו לצמיתות?', function(confirm) {
175
+ if (confirm) performDelete(id);
176
+ });
177
+ } else {
178
+ if (confirm('האם אתה בטוח שברצונך למחוק פנייה זו לצמיתות?')) performDelete(id);
179
+ }
180
+ });
181
+ }
182
+
183
+ function performHandle(id) {
184
+ $.post(config.relative_path + '/api/admin/plugins/contact/handle', {
185
+ id: id,
186
+ _csrf: config.csrf_token
187
+ }, function(data) {
188
+ if (data.success) {
189
+ showMessage('success', 'הפנייה סומנה כטופלה בהצלחה');
190
+ ajaxify.refresh();
191
+ }
192
+ }).fail(function() {
193
+ showMessage('error', 'שגיאה בעדכון הפנייה');
194
+ });
195
+ }
196
+
197
+ function performDelete(id) {
198
+ $.post(config.relative_path + '/api/admin/plugins/contact/delete', {
199
+ id: id,
200
+ _csrf: config.csrf_token
201
+ }, function(data) {
202
+ if (data.success) {
203
+ showMessage('success', 'הפנייה נמחקה בהצלחה');
204
+ ajaxify.refresh();
205
+ }
206
+ }).fail(function() {
207
+ showMessage('error', 'שגיאה במחיקת הפנייה');
208
+ });
209
+ }
210
+
211
+ $(document).ready(initContactPage);
212
+ $(window).on('action:ajaxify.end', function(event, data) {
213
+ if (data.tpl_url === 'admin/plugins/contact') {
214
+ initContactPage();
215
+ }
216
+ });
217
+ })();
218
+ </script>
@@ -1,7 +1,9 @@
1
1
  <div class="row">
2
2
  <div class="col-lg-8 col-lg-offset-2">
3
3
  <div class="panel panel-default">
4
- <div class="panel-heading"><h3 class="panel-title">צור קשר עם ההנהלה</h3></div>
4
+ <div class="panel-heading">
5
+ <h3 class="panel-title">צור קשר עם ההנהלה</h3>
6
+ </div>
5
7
  <div class="panel-body">
6
8
  <form id="contact-form" role="form">
7
9
  <div class="form-group">
@@ -20,10 +22,90 @@
20
22
  <label for="content">תוכן הפנייה *</label>
21
23
  <textarea class="form-control" id="content" name="content" rows="6" required></textarea>
22
24
  </div>
25
+
23
26
  <button type="submit" class="btn btn-primary" id="submit-btn">שלח פנייה</button>
24
27
  </form>
28
+
25
29
  <div id="contact-alert" class="alert" style="display:none; margin-top: 20px;"></div>
26
30
  </div>
27
31
  </div>
28
32
  </div>
29
- </div>
33
+ </div>
34
+
35
+ <script>
36
+ (function() {
37
+ function initContactForm($) {
38
+ var form = $('#contact-form');
39
+ var btn = $('#submit-btn');
40
+ var alertBox = $('#contact-alert');
41
+
42
+ form.off('submit').on('submit', function(e) {
43
+ e.preventDefault();
44
+
45
+ btn.prop('disabled', true).text('שולח...');
46
+ alertBox.hide().removeClass('alert-success alert-danger');
47
+
48
+ var formData = {
49
+ fullName: form.find('#fullName').val(),
50
+ username: form.find('#username').val(),
51
+ email: form.find('#email').val(),
52
+ content: form.find('#content').val(),
53
+ _csrf: config.csrf_token
54
+ };
55
+
56
+ $.ajax({
57
+ url: config.relative_path + '/api/contact/send',
58
+ type: 'POST',
59
+ data: formData,
60
+ success: function(response) {
61
+ alertBox.addClass('alert-success')
62
+ .text(response.message || 'הפנייה נשלחה בהצלחה!')
63
+ .fadeIn();
64
+ form[0].reset();
65
+ btn.text('שלח פנייה');
66
+ },
67
+ error: function(xhr) {
68
+ var msg = (xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'אירעה שגיאה בשליחה.';
69
+ alertBox.addClass('alert-danger')
70
+ .text(msg)
71
+ .fadeIn();
72
+ },
73
+ complete: function() {
74
+ btn.prop('disabled', false).text('שלח פנייה');
75
+ }
76
+ });
77
+ });
78
+ }
79
+
80
+
81
+ function safeBoot() {
82
+ if (typeof jQuery === 'undefined') {
83
+ setTimeout(safeBoot, 50);
84
+ return;
85
+ }
86
+
87
+ var $ = jQuery;
88
+
89
+ initContactForm($);
90
+
91
+ $(window).off('action:ajaxify.end.contact').on('action:ajaxify.end.contact', function(ev, data) {
92
+ if (data.url === 'contact' || (ajaxify.data.template && ajaxify.data.template.name === 'contact')) {
93
+ initContactForm($);
94
+ }
95
+ });
96
+ }
97
+
98
+ if (typeof define === 'function' && define.amd) {
99
+ define('forum/contact', ['jquery'], function($) {
100
+ return {
101
+ init: function() {
102
+ initContactForm($);
103
+ }
104
+ };
105
+ });
106
+ }
107
+
108
+ safeBoot();
109
+
110
+ })();
111
+ </script>
@@ -1,36 +0,0 @@
1
- 'use strict';
2
-
3
- /* globals $, app, socket */
4
-
5
- // הקוד הזה ירוץ ברגע שפאנל הניהול נטען
6
- $(document).ready(function () {
7
- // האזנה לאירוע ש-NodeBB מסיים לטעון דף חדש (Ajaxify)
8
- $(window).on('action:ajaxify.end', function (event, data) {
9
- // בדיקה: האם הגענו לדף של התוסף שלנו?
10
- if (data.tpl_url === 'admin/plugins/contact') {
11
- initializeAdminPage();
12
- }
13
- });
14
-
15
- // אם אנחנו כבר בדף הזה ברגע הטעינה הראשונה
16
- if (ajaxify.data.template && ajaxify.data.template['admin/plugins/contact']) {
17
- initializeAdminPage();
18
- }
19
- });
20
-
21
- function initializeAdminPage() {
22
- console.log('✅ Contact Admin Page logic triggered via Hook');
23
-
24
- // לוגיקה לסימון כטופל
25
- $('.mark-handled-btn').on('click', function () {
26
- var id = $(this).data('id');
27
- var btn = $(this);
28
-
29
- $.post('/api/admin/plugins/contact/handle', { id: id }, function (data) {
30
- if (data.success) {
31
- btn.closest('tr').addClass('table-success');
32
- btn.replaceWith('<span class="badge bg-success">טופל</span>');
33
- }
34
- });
35
- });
36
- }
@@ -1,54 +0,0 @@
1
- 'use strict';
2
-
3
- // החלק הזה מטפל בלוגיקה של הטופס
4
- $(document).ready(function() {
5
- $(window).on('action:ajaxify.end', function(event, data) {
6
- if (data.url === 'contact' || ajaxify.data.template.name === 'contact') {
7
- $('#contact-form').off('submit').on('submit', function(e) {
8
- e.preventDefault();
9
- var btn = $('#submit-btn');
10
- var alertBox = $('#contact-alert');
11
- var form = $(this);
12
-
13
- btn.prop('disabled', true);
14
-
15
- var formData = {
16
- fullName: form.find('#fullName').val(),
17
- username: form.find('#username').val(),
18
- email: form.find('#email').val(),
19
- content: form.find('#content').val()
20
- };
21
-
22
- $.ajax({
23
- url: config.relative_path + '/api/contact/send',
24
- type: 'POST',
25
- data: formData,
26
- headers: { 'x-csrf-token': config.csrf_token },
27
- success: function(response) {
28
- alertBox.removeClass('alert-danger').addClass('alert-success')
29
- .text(response.message).show();
30
- form[0].reset();
31
- },
32
- error: function(xhr) {
33
- // הצגת הודעת שגיאה מפורטת אם השרת החזיר כזו
34
- var msg = (xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'שגיאה בשליחה';
35
- alertBox.removeClass('alert-success').addClass('alert-danger')
36
- .text(msg).show();
37
- },
38
- complete: function() {
39
- btn.prop('disabled', false);
40
- }
41
- });
42
- });
43
- }
44
- });
45
- });
46
-
47
- // החלק הזה "מרגיע" את NodeBB שלא יחפש מודול חסר
48
- define('forum/contact', [], function() {
49
- return {
50
- init: function() {
51
- console.log('Contact page loaded.');
52
- }
53
- };
54
- });