nodebb-plugin-composer-default 10.0.17 → 10.0.18

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
@@ -3,7 +3,6 @@
3
3
  const url = require('url');
4
4
 
5
5
  const nconf = require.main.require('nconf');
6
- const winston = require.main.require('winston');
7
6
  const validator = require('validator');
8
7
 
9
8
  const plugins = require.main.require('./src/plugins');
@@ -14,6 +13,7 @@ const user = require.main.require('./src/user');
14
13
  const meta = require.main.require('./src/meta');
15
14
  const privileges = require.main.require('./src/privileges');
16
15
  const translator = require.main.require('./src/translator');
16
+ const utils = require.main.require('./src/utils');
17
17
  const helpers = require.main.require('./src/controllers/helpers');
18
18
  const SocketPlugins = require.main.require('./src/socket.io/plugins');
19
19
  const socketMethods = require('./websockets');
@@ -164,7 +164,7 @@ plugin.filterComposerBuild = async function (hookData) {
164
164
 
165
165
  const isEditing = !!req.query.pid;
166
166
  const isGuestPost = postData && parseInt(postData.uid, 10) === 0;
167
- const save_id = generateSaveId(req);
167
+ const save_id = utils.generateSaveId(req.uid);
168
168
  const discardRoute = generateDiscardRoute(req, topicData);
169
169
  const body = await generateBody(req, postData);
170
170
 
@@ -251,16 +251,6 @@ async function generateBody(req, postData) {
251
251
  return postData ? postData.content : '';
252
252
  }
253
253
 
254
- function generateSaveId(req) {
255
- if (req.query.cid) {
256
- return ['composer', req.uid, 'cid', req.query.cid].join(':');
257
- } else if (req.query.tid) {
258
- return ['composer', req.uid, 'tid', req.query.tid].join(':');
259
- } else if (req.query.pid) {
260
- return ['composer', req.uid, 'pid', req.query.pid].join(':');
261
- }
262
- }
263
-
264
254
  async function getPostData(req) {
265
255
  if (!req.query.pid && !req.query.toPid) {
266
256
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-composer-default",
3
- "version": "10.0.17",
3
+ "version": "10.0.18",
4
4
  "description": "Default composer for NodeBB",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -1,19 +1,23 @@
1
1
  'use strict';
2
2
 
3
3
  define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
4
- var drafts = {};
4
+ const drafts = {};
5
5
 
6
6
  drafts.init = function (postContainer, postData) {
7
- var draftIconEl = postContainer.find('.draft-icon');
7
+ const draftIconEl = postContainer.find('.draft-icon');
8
8
  function doSaveDraft() {
9
+ if (!postData.save_id) {
10
+ postData.save_id = utils.generateSaveId(app.user.uid);
11
+ }
9
12
  // Post is modified, save to list of opened drafts
10
- drafts.updateVisibility('available', postData.save_id, true);
11
- drafts.updateVisibility('open', postData.save_id, true);
13
+ drafts.addToDraftList('available', postData.save_id);
14
+ drafts.addToDraftList('open', postData.save_id);
12
15
  saveDraft(postContainer, draftIconEl, postData);
13
16
  }
14
17
 
15
18
  postContainer.on('keyup', 'textarea, input.handle, input.title', utils.debounce(doSaveDraft, 1000));
16
19
  postContainer.on('click', 'input[type="checkbox"]', utils.debounce(doSaveDraft, 1000));
20
+ postContainer.on('click', '[component="category/list"] [data-cid]', utils.debounce(doSaveDraft, 1000));
17
21
  postContainer.on('thumb.uploaded', doSaveDraft);
18
22
 
19
23
  draftIconEl.on('animationend', function () {
@@ -21,19 +25,10 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
21
25
  });
22
26
 
23
27
  $(window).on('unload', function () {
24
- // Update visibility on all open composers
25
- var open = [];
26
- try {
27
- open = localStorage.getItem('drafts:open');
28
- open = JSON.parse(open) || [];
29
- } catch (e) {
30
- console.warn('[composer/drafts] Could not read list of open/available drafts');
31
- open = [];
32
- }
28
+ // remove all drafts from the open list
29
+ const open = drafts.getList('open');
33
30
  if (open.length) {
34
- open.forEach(function (save_id) {
35
- drafts.updateVisibility('open', save_id);
36
- });
31
+ open.forEach(save_id => drafts.removeFromDraftList('open', save_id));
37
32
  }
38
33
  });
39
34
 
@@ -46,62 +41,70 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
46
41
  }
47
42
 
48
43
  drafts.get = function (save_id) {
49
- var uid = save_id.split(':')[1];
50
- var storage = getStorage(uid);
51
- var draft = {
52
- text: storage.getItem(save_id),
53
- save_id: save_id,
54
- };
55
- ['cid', 'title', 'tags', 'uuid', 'timestamp'].forEach(function (key) {
56
- const value = storage.getItem(save_id + ':' + key);
57
- if (value) {
58
- draft[key] = value;
59
- }
60
- });
61
- if (!parseInt(uid, 10)) {
62
- draft.handle = storage.getItem(save_id + ':handle');
44
+ if (!save_id) {
45
+ return null;
63
46
  }
64
- if (draft.timestamp) {
65
- draft.timestampISO = utils.toISOString(draft.timestamp);
47
+ const uid = save_id.split(':')[1];
48
+ const storage = getStorage(uid);
49
+ try {
50
+ const draftJson = storage.getItem(save_id);
51
+ const draft = JSON.parse(draftJson) || null;
52
+ if (!draft) {
53
+ throw new Error(`can't parse draft json for ${save_id}`);
54
+ }
55
+ draft.save_id = save_id;
56
+ if (draft.timestamp) {
57
+ draft.timestampISO = utils.toISOString(draft.timestamp);
58
+ }
59
+ $(window).trigger('action:composer.drafts.get', {
60
+ save_id: save_id,
61
+ draft: draft,
62
+ storage: storage,
63
+ });
64
+ return draft;
65
+ } catch (e) {
66
+ console.warn(`[composer/drafts] Could not get draft ${save_id}, removing`);
67
+ drafts.removeFromDraftList('available');
68
+ drafts.removeFromDraftList('open');
69
+ return null;
66
70
  }
67
-
68
- $(window).trigger('action:composer.drafts.get', {
69
- save_id: save_id,
70
- draft: draft,
71
- storage: storage,
72
- });
73
- return draft;
74
71
  };
75
72
 
76
73
  function saveDraft(postContainer, draftIconEl, postData) {
77
74
  if (canSave(app.user.uid ? 'localStorage' : 'sessionStorage') && postData && postData.save_id && postContainer.length) {
78
75
  const titleEl = postContainer.find('input.title');
79
76
  const title = titleEl && titleEl.val();
80
- var raw = postContainer.find('textarea').val();
81
- var storage = getStorage(app.user.uid);
82
-
83
- if (postData.hasOwnProperty('cid') && !postData.save_id.endsWith(':cid:' + postData.cid)) {
84
- // A new cid was selected, the save_id needs updating
85
- drafts.removeDraft(postData.save_id); // First, delete the old draft
86
- postData.save_id = postData.save_id.replace(/cid:\d+$/, 'cid:' + postData.cid); // then create a new save_id
87
- }
77
+ const raw = postContainer.find('textarea').val();
78
+ const storage = getStorage(app.user.uid);
88
79
 
89
80
  if (raw.length || (title && title.length)) {
90
- storage.setItem(postData.save_id, raw);
91
- storage.setItem(`${postData.save_id}:uuid`, postContainer.attr('data-uuid'));
92
- storage.setItem(`${postData.save_id}:timestamp`, Date.now());
93
-
94
- if (postData.hasOwnProperty('cid')) {
81
+ const draftData = {
82
+ action: postData.action,
83
+ text: raw,
84
+ uuid: postContainer.attr('data-uuid'),
85
+ timestamp: Date.now(),
86
+ };
87
+
88
+ if (postData.action === 'topics.post') {
95
89
  // New topic only
96
90
  const tags = postContainer.find('input.tags').val();
97
- storage.setItem(postData.save_id + ':tags', tags);
98
- storage.setItem(postData.save_id + ':title', title);
91
+ draftData.tags = tags;
92
+ draftData.title = title;
93
+ draftData.cid = postData.cid;
94
+ } else if (postData.action === 'posts.reply') {
95
+ // new reply only
96
+ draftData.title = postData.title;
97
+ draftData.tid = postData.tid;
98
+ } else if (postData.action === 'posts.edit') {
99
+ draftData.pid = postData.pid;
99
100
  }
100
101
  if (!app.user.uid) {
101
- var handle = postContainer.find('input.handle').val();
102
- storage.setItem(postData.save_id + ':handle', handle);
102
+ draftData.handle = postContainer.find('input.handle').val();
103
103
  }
104
104
 
105
+ // save all draft data into single item as json
106
+ storage.setItem(postData.save_id, JSON.stringify(draftData));
107
+
105
108
  $(window).trigger('action:composer.drafts.save', {
106
109
  storage: storage,
107
110
  postData: postData,
@@ -120,54 +123,64 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
120
123
  }
121
124
 
122
125
  // Remove save_id from list of open and available drafts
123
- drafts.updateVisibility('available', save_id);
124
- drafts.updateVisibility('open', save_id);
125
- var uid = save_id.split(':')[1];
126
- var storage = getStorage(uid);
127
- const keys = Object.keys(storage).filter(key => key.startsWith(save_id));
128
- keys.forEach(key => storage.removeItem(key));
126
+ drafts.removeFromDraftList('available', save_id);
127
+ drafts.removeFromDraftList('open', save_id);
128
+ const uid = save_id.split(':')[1];
129
+ const storage = getStorage(uid);
130
+ storage.removeItem(save_id);
131
+
129
132
  $(window).trigger('action:composer.drafts.remove', {
130
133
  storage: storage,
131
134
  save_id: save_id,
132
135
  });
133
136
  };
134
137
 
135
- drafts.updateVisibility = function (set, save_id, add) {
136
- if (!canSave(app.user.uid ? 'localStorage' : 'sessionStorage') || !save_id) {
137
- return;
138
- }
139
- var open = [];
138
+ drafts.getList = function (set) {
140
139
  try {
141
- open = localStorage.getItem('drafts:' + set);
142
- open = open ? JSON.parse(open) : [];
140
+ const draftIds = localStorage.getItem(`drafts:${set}`);
141
+ return JSON.parse(draftIds) || [];
143
142
  } catch (e) {
144
- console.warn('[composer/drafts] Could not read list of open drafts');
145
- open = [];
143
+ console.warn('[composer/drafts] Could not read list of available drafts');
144
+ return [];
146
145
  }
147
- var idx = open.indexOf(save_id);
146
+ };
148
147
 
149
- if (add && idx === -1) {
150
- open.push(save_id);
151
- } else if (!add && idx !== -1) {
152
- open.splice(idx, 1);
153
- } // otherwise do nothing
148
+ drafts.addToDraftList = function (set, save_id) {
149
+ if (!canSave(app.user.uid ? 'localStorage' : 'sessionStorage') || !save_id) {
150
+ return;
151
+ }
152
+ const list = drafts.getList(set);
153
+ if (!list.includes(save_id)) {
154
+ list.push(save_id);
155
+ localStorage.setItem('drafts:' + set, JSON.stringify(list));
156
+ }
157
+ };
154
158
 
155
- localStorage.setItem('drafts:' + set, JSON.stringify(open));
159
+ drafts.removeFromDraftList = function (set, save_id) {
160
+ if (!canSave(app.user.uid ? 'localStorage' : 'sessionStorage') || !save_id) {
161
+ return;
162
+ }
163
+ const list = drafts.getList(set);
164
+ if (list.includes(save_id)) {
165
+ list.splice(list.indexOf(save_id), 1);
166
+ localStorage.setItem('drafts:' + set, JSON.stringify(list));
167
+ }
156
168
  };
157
169
 
158
170
  drafts.migrateGuest = function () {
159
171
  // If any drafts are made while as guest, and user then logs in, assume control of those drafts
160
172
  if (canSave('localStorage') && app.user.uid) {
161
- var test = /^composer:\d+:\w+:\d+(:\w+)?$/;
162
- var keys = Object.keys(sessionStorage).filter(function (key) {
173
+ // composer:<uid>:<timestamp>
174
+ const test = /^composer:\d+:\d$/;
175
+ const keys = Object.keys(sessionStorage).filter(function (key) {
163
176
  return test.test(key);
164
177
  });
165
- var migrated = new Set([]);
166
- var renamed = keys.map(function (key) {
167
- var parts = key.split(':');
178
+ const migrated = new Set([]);
179
+ const renamed = keys.map(function (key) {
180
+ const parts = key.split(':');
168
181
  parts[1] = app.user.uid;
169
182
 
170
- migrated.add(parts.slice(0, 4).join(':'));
183
+ migrated.add(parts.join(':'));
171
184
  return parts.join(':');
172
185
  });
173
186
 
@@ -177,7 +190,7 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
177
190
  });
178
191
 
179
192
  migrated.forEach(function (save_id) {
180
- drafts.updateVisibility('available', save_id, 1);
193
+ drafts.addToDraftList('available', save_id);
181
194
  });
182
195
 
183
196
  return migrated;
@@ -205,14 +218,8 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
205
218
  };
206
219
 
207
220
  drafts.listAvailable = function () {
208
- try {
209
- let available = localStorage.getItem('drafts:available');
210
- available = JSON.parse(available) || [];
211
- return available.map(drafts.get);
212
- } catch (e) {
213
- console.warn('[composer/drafts] Could not read list of available drafts');
214
- }
215
- return [];
221
+ const available = drafts.getList('available');
222
+ return available.map(drafts.get);
216
223
  };
217
224
 
218
225
  drafts.getAvailableCount = function () {
@@ -228,40 +235,23 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
228
235
  };
229
236
 
230
237
  drafts.loadOpen = function () {
231
- if (ajaxify.data.template.login || ajaxify.data.template.register) {
238
+ if (ajaxify.data.template.login || ajaxify.data.template.register || !config.hasOwnProperty('openDraftsOnPageLoad') || !config.openDraftsOnPageLoad) {
232
239
  return;
233
240
  }
234
241
  // Load drafts if they were open
235
- var available;
236
- var open = [];
237
- try {
238
- available = localStorage.getItem('drafts:available');
239
- open = localStorage.getItem('drafts:open');
240
- available = JSON.parse(available) || [];
241
- open = JSON.parse(open) || [];
242
- } catch (e) {
243
- console.warn('[composer/drafts] Could not read list of open/available drafts');
244
- available = [];
245
- open = [];
246
- }
242
+ const available = drafts.getList('available');
243
+ const open = drafts.getList('open');
247
244
 
248
245
  if (available.length) {
249
246
  // Deconstruct each save_id and open up composer
250
247
  available.forEach(function (save_id) {
251
- if (!save_id) {
248
+ if (!save_id || open.includes(save_id)) {
252
249
  return;
253
250
  }
254
- var draft = drafts.get(save_id);
255
-
256
- // If draft is already open, do nothing
257
- if (open.indexOf(save_id) !== -1) {
258
- return;
259
- }
260
-
251
+ const draft = drafts.get(save_id);
261
252
  if (!draft || (!draft.text && !draft.title)) {
262
- // Empty content, remove from list of open drafts
263
- drafts.updateVisibility('available', save_id);
264
- drafts.updateVisibility('open', save_id);
253
+ drafts.removeFromDraftList('available', save_id);
254
+ drafts.removeFromDraftList('open', save_id);
265
255
  return;
266
256
  }
267
257
  openComposer(save_id, draft);
@@ -270,32 +260,30 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
270
260
  };
271
261
 
272
262
  function openComposer(save_id, draft) {
273
- var saveObj = save_id.split(':');
274
- var uid = saveObj[1];
275
- var type = saveObj[2];
276
- var id = saveObj[3];
263
+ const saveObj = save_id.split(':');
264
+ const uid = saveObj[1];
277
265
  // Don't open other peoples' drafts
278
266
  if (parseInt(app.user.uid, 10) !== parseInt(uid, 10)) {
279
267
  return;
280
268
  }
281
269
  require(['composer'], function (composer) {
282
- if (type === 'cid') {
270
+ if (draft.action === 'topics.post') {
283
271
  composer.newTopic({
284
- cid: id,
272
+ cid: draft.cid,
285
273
  handle: app.user && app.user.uid ? undefined : utils.escapeHTML(draft.handle),
286
274
  title: utils.escapeHTML(draft.title),
287
275
  body: utils.escapeHTML(draft.text),
288
276
  tags: String(draft.tags || '').split(','),
289
277
  });
290
- } else if (type === 'tid') {
291
- api.get('/topics/' + id, {}, function (err, topicObj) {
278
+ } else if (draft.action === 'posts.reply') {
279
+ api.get('/topics/' + draft.tid, {}, function (err, topicObj) {
292
280
  if (err) {
293
281
  return alerts.error(err);
294
282
  }
295
- composer.newReply(id, undefined, topicObj.title, utils.escapeHTML(draft.text));
283
+ composer.newReply(draft.tid, undefined, topicObj.title, utils.escapeHTML(draft.text));
296
284
  });
297
- } else if (type === 'pid') {
298
- composer.editPost(id);
285
+ } else if (draft.action === 'posts.edit') {
286
+ composer.editPost(draft.pid, draft.text);
299
287
  }
300
288
  });
301
289
  }
@@ -152,15 +152,6 @@ define('composer', [
152
152
  });
153
153
  });
154
154
 
155
- // Construct a save_id
156
- if (post.hasOwnProperty('cid')) {
157
- post.save_id = ['composer', app.user.uid, 'cid', post.cid].join(':');
158
- } else if (post.hasOwnProperty('tid')) {
159
- post.save_id = ['composer', app.user.uid, 'tid', post.tid].join(':');
160
- } else if (post.hasOwnProperty('pid')) {
161
- post.save_id = ['composer', app.user.uid, 'pid', post.pid].join(':');
162
- }
163
-
164
155
  composer.posts[uuid] = post;
165
156
  composer.load(uuid);
166
157
  }
@@ -273,7 +264,7 @@ define('composer', [
273
264
  });
274
265
  };
275
266
 
276
- composer.editPost = function (pid) {
267
+ composer.editPost = function (pid, text) {
277
268
  socket.emit('plugins.composer.push', pid, function (err, threadData) {
278
269
  if (err) {
279
270
  return alerts.error(err);
@@ -281,6 +272,10 @@ define('composer', [
281
272
  threadData.action = 'posts.edit';
282
273
  threadData.pid = pid;
283
274
  threadData.modified = false;
275
+ if (text) {
276
+ threadData.body = text;
277
+ threadData.modified = true;
278
+ }
284
279
  push(threadData);
285
280
  });
286
281
  };
@@ -1,6 +1,5 @@
1
1
  .composer {
2
- @include no-select;
3
-
2
+ user-select: none;
4
3
  background-color: $body-bg;
5
4
  color: $body-color;
6
5
  z-index: $zindex-modal;
@@ -1,21 +1,21 @@
1
1
  <div class="title-container">
2
2
  {{{ if isTopic }}}
3
- <div class="category-list-container hidden-sm hidden-xs">
3
+ <div class="category-list-container hidden-sm hidden-xs align-self-center">
4
4
  <!-- IMPORT partials/category-selector.tpl -->
5
5
  </div>
6
6
  {{{ end }}}
7
7
 
8
- <!-- IF showHandleInput -->
8
+ {{{ if showHandleInput }}}
9
9
  <div data-component="composer/handle">
10
10
  <input class="handle form-control h-100" type="text" tabindex="1" placeholder="[[topic:composer.handle_placeholder]]" value="{handle}" />
11
11
  </div>
12
- <!-- ENDIF showHandleInput -->
12
+ {{{ end }}}
13
13
  <div data-component="composer/title" class="position-relative">
14
- <!-- IF isTopicOrMain -->
14
+ {{{ if isTopicOrMain }}}
15
15
  <input class="title form-control h-100" type="text" tabindex="1" placeholder="[[topic:composer.title_placeholder]]" value="{topicTitle}"/>
16
- <!-- ELSE -->
17
- <span class="title h-100">[[topic:composer.replying_to, "{topicTitle}"]]</span>
18
- <!-- ENDIF isTopicOrMain -->
16
+ {{{ else }}}
17
+ <span class="title h-100">{{{ if isEditing }}}[[topic:composer.editing]]{{{ else }}}[[topic:composer.replying_to, "{topicTitle}"]]{{{ end }}}</span>
18
+ {{{ end }}}
19
19
  <div id="quick-search-container" class="quick-search-container mt-2 dropdown-menu d-block p-2 hidden">
20
20
  <div class="text-center loading-indicator"><i class="fa fa-spinner fa-spin"></i></div>
21
21
  <div class="quick-search-results-container"></div>