nodebb-plugin-composer-default 10.0.17 → 10.0.19

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.19",
4
4
  "description": "Default composer for NodeBB",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -30,7 +30,7 @@ $(document).ready(function () {
30
30
  $(window).on('action:composer.post.edit', function (ev, data) {
31
31
  if (config['composer-default'].composeRouteEnabled !== 'on') {
32
32
  require(['composer'], function (composer) {
33
- composer.editPost(data.pid);
33
+ composer.editPost({ pid: data.pid });
34
34
  });
35
35
  } else {
36
36
  ajaxify.go('compose?pid=' + data.pid);
@@ -40,7 +40,12 @@ $(document).ready(function () {
40
40
  $(window).on('action:composer.post.new', function (ev, data) {
41
41
  if (config['composer-default'].composeRouteEnabled !== 'on') {
42
42
  require(['composer'], function (composer) {
43
- composer.newReply(data.tid, data.pid, data.topicName, data.text);
43
+ composer.newReply({
44
+ tid: data.tid,
45
+ pid: data.pid,
46
+ title: data.topicName,
47
+ body: data.text,
48
+ });
44
49
  });
45
50
  } else {
46
51
  ajaxify.go(
@@ -56,7 +61,15 @@ $(document).ready(function () {
56
61
  if (config['composer-default'].composeRouteEnabled !== 'on') {
57
62
  require(['composer'], function (composer) {
58
63
  var topicUUID = composer.findByTid(data.tid);
59
- composer.addQuote(data.tid, data.pid, data.selectedPid, data.topicName, data.username, data.text, topicUUID);
64
+ composer.addQuote({
65
+ tid: data.tid,
66
+ toPid: data.pid,
67
+ selectedPid: data.selectedPid,
68
+ title: data.topicName,
69
+ username: data.username,
70
+ body: data.text,
71
+ uuid: topicUUID,
72
+ });
60
73
  });
61
74
  } else {
62
75
  ajaxify.go('compose?tid=' + data.tid + '&toPid=' + data.pid + '&quoted=1&username=' + data.username);
@@ -1,19 +1,24 @@
1
1
  'use strict';
2
2
 
3
3
  define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
4
- var drafts = {};
5
-
4
+ const drafts = {};
5
+ const draftSaveDelay = 2000;
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
- postContainer.on('keyup', 'textarea, input.handle, input.title', utils.debounce(doSaveDraft, 1000));
16
- postContainer.on('click', 'input[type="checkbox"]', utils.debounce(doSaveDraft, 1000));
18
+ postContainer.on('keyup', 'textarea, input.handle, input.title', utils.debounce(doSaveDraft, draftSaveDelay));
19
+ postContainer.on('click', 'input[type="checkbox"]', utils.debounce(doSaveDraft, draftSaveDelay));
20
+ postContainer.on('click', '[component="category/list"] [data-cid]', utils.debounce(doSaveDraft, draftSaveDelay));
21
+ postContainer.on('itemAdded', '.tags', utils.debounce(doSaveDraft, draftSaveDelay));
17
22
  postContainer.on('thumb.uploaded', doSaveDraft);
18
23
 
19
24
  draftIconEl.on('animationend', function () {
@@ -21,19 +26,10 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
21
26
  });
22
27
 
23
28
  $(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
- }
29
+ // remove all drafts from the open list
30
+ const open = drafts.getList('open');
33
31
  if (open.length) {
34
- open.forEach(function (save_id) {
35
- drafts.updateVisibility('open', save_id);
36
- });
32
+ open.forEach(save_id => drafts.removeFromDraftList('open', save_id));
37
33
  }
38
34
  });
39
35
 
@@ -46,62 +42,72 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
46
42
  }
47
43
 
48
44
  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');
45
+ if (!save_id) {
46
+ return null;
63
47
  }
64
- if (draft.timestamp) {
65
- draft.timestampISO = utils.toISOString(draft.timestamp);
48
+ const uid = save_id.split(':')[1];
49
+ const storage = getStorage(uid);
50
+ try {
51
+ const draftJson = storage.getItem(save_id);
52
+ const draft = JSON.parse(draftJson) || null;
53
+ if (!draft) {
54
+ throw new Error(`can't parse draft json for ${save_id}`);
55
+ }
56
+ draft.save_id = save_id;
57
+ if (draft.timestamp) {
58
+ draft.timestampISO = utils.toISOString(draft.timestamp);
59
+ }
60
+ $(window).trigger('action:composer.drafts.get', {
61
+ save_id: save_id,
62
+ draft: draft,
63
+ storage: storage,
64
+ });
65
+ return draft;
66
+ } catch (e) {
67
+ console.warn(`[composer/drafts] Could not get draft ${save_id}, removing`);
68
+ drafts.removeFromDraftList('available');
69
+ drafts.removeFromDraftList('open');
70
+ return null;
66
71
  }
67
-
68
- $(window).trigger('action:composer.drafts.get', {
69
- save_id: save_id,
70
- draft: draft,
71
- storage: storage,
72
- });
73
- return draft;
74
72
  };
75
73
 
76
74
  function saveDraft(postContainer, draftIconEl, postData) {
77
75
  if (canSave(app.user.uid ? 'localStorage' : 'sessionStorage') && postData && postData.save_id && postContainer.length) {
78
76
  const titleEl = postContainer.find('input.title');
79
77
  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
- }
78
+ const raw = postContainer.find('textarea').val();
79
+ const storage = getStorage(app.user.uid);
88
80
 
89
81
  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')) {
82
+ const draftData = {
83
+ save_id: postData.save_id,
84
+ action: postData.action,
85
+ text: raw,
86
+ uuid: postContainer.attr('data-uuid'),
87
+ timestamp: Date.now(),
88
+ };
89
+
90
+ if (postData.action === 'topics.post') {
95
91
  // New topic only
96
92
  const tags = postContainer.find('input.tags').val();
97
- storage.setItem(postData.save_id + ':tags', tags);
98
- storage.setItem(postData.save_id + ':title', title);
93
+ draftData.tags = tags;
94
+ draftData.title = title;
95
+ draftData.cid = postData.cid;
96
+ } else if (postData.action === 'posts.reply') {
97
+ // new reply only
98
+ draftData.title = postData.title;
99
+ draftData.tid = postData.tid;
100
+ draftData.toPid = postData.toPid;
101
+ } else if (postData.action === 'posts.edit') {
102
+ draftData.pid = postData.pid;
99
103
  }
100
104
  if (!app.user.uid) {
101
- var handle = postContainer.find('input.handle').val();
102
- storage.setItem(postData.save_id + ':handle', handle);
105
+ draftData.handle = postContainer.find('input.handle').val();
103
106
  }
104
107
 
108
+ // save all draft data into single item as json
109
+ storage.setItem(postData.save_id, JSON.stringify(draftData));
110
+
105
111
  $(window).trigger('action:composer.drafts.save', {
106
112
  storage: storage,
107
113
  postData: postData,
@@ -120,54 +126,64 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
120
126
  }
121
127
 
122
128
  // 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));
129
+ drafts.removeFromDraftList('available', save_id);
130
+ drafts.removeFromDraftList('open', save_id);
131
+ const uid = save_id.split(':')[1];
132
+ const storage = getStorage(uid);
133
+ storage.removeItem(save_id);
134
+
129
135
  $(window).trigger('action:composer.drafts.remove', {
130
136
  storage: storage,
131
137
  save_id: save_id,
132
138
  });
133
139
  };
134
140
 
135
- drafts.updateVisibility = function (set, save_id, add) {
136
- if (!canSave(app.user.uid ? 'localStorage' : 'sessionStorage') || !save_id) {
137
- return;
138
- }
139
- var open = [];
141
+ drafts.getList = function (set) {
140
142
  try {
141
- open = localStorage.getItem('drafts:' + set);
142
- open = open ? JSON.parse(open) : [];
143
+ const draftIds = localStorage.getItem(`drafts:${set}`);
144
+ return JSON.parse(draftIds) || [];
143
145
  } catch (e) {
144
- console.warn('[composer/drafts] Could not read list of open drafts');
145
- open = [];
146
+ console.warn('[composer/drafts] Could not read list of available drafts');
147
+ return [];
146
148
  }
147
- var idx = open.indexOf(save_id);
149
+ };
148
150
 
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
151
+ drafts.addToDraftList = function (set, save_id) {
152
+ if (!canSave(app.user.uid ? 'localStorage' : 'sessionStorage') || !save_id) {
153
+ return;
154
+ }
155
+ const list = drafts.getList(set);
156
+ if (!list.includes(save_id)) {
157
+ list.push(save_id);
158
+ localStorage.setItem('drafts:' + set, JSON.stringify(list));
159
+ }
160
+ };
154
161
 
155
- localStorage.setItem('drafts:' + set, JSON.stringify(open));
162
+ drafts.removeFromDraftList = function (set, save_id) {
163
+ if (!canSave(app.user.uid ? 'localStorage' : 'sessionStorage') || !save_id) {
164
+ return;
165
+ }
166
+ const list = drafts.getList(set);
167
+ if (list.includes(save_id)) {
168
+ list.splice(list.indexOf(save_id), 1);
169
+ localStorage.setItem('drafts:' + set, JSON.stringify(list));
170
+ }
156
171
  };
157
172
 
158
173
  drafts.migrateGuest = function () {
159
174
  // If any drafts are made while as guest, and user then logs in, assume control of those drafts
160
175
  if (canSave('localStorage') && app.user.uid) {
161
- var test = /^composer:\d+:\w+:\d+(:\w+)?$/;
162
- var keys = Object.keys(sessionStorage).filter(function (key) {
176
+ // composer:<uid>:<timestamp>
177
+ const test = /^composer:\d+:\d$/;
178
+ const keys = Object.keys(sessionStorage).filter(function (key) {
163
179
  return test.test(key);
164
180
  });
165
- var migrated = new Set([]);
166
- var renamed = keys.map(function (key) {
167
- var parts = key.split(':');
181
+ const migrated = new Set([]);
182
+ const renamed = keys.map(function (key) {
183
+ const parts = key.split(':');
168
184
  parts[1] = app.user.uid;
169
185
 
170
- migrated.add(parts.slice(0, 4).join(':'));
186
+ migrated.add(parts.join(':'));
171
187
  return parts.join(':');
172
188
  });
173
189
 
@@ -177,7 +193,7 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
177
193
  });
178
194
 
179
195
  migrated.forEach(function (save_id) {
180
- drafts.updateVisibility('available', save_id, 1);
196
+ drafts.addToDraftList('available', save_id);
181
197
  });
182
198
 
183
199
  return migrated;
@@ -205,14 +221,8 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
205
221
  };
206
222
 
207
223
  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 [];
224
+ const available = drafts.getList('available');
225
+ return available.map(drafts.get);
216
226
  };
217
227
 
218
228
  drafts.getAvailableCount = function () {
@@ -228,40 +238,23 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
228
238
  };
229
239
 
230
240
  drafts.loadOpen = function () {
231
- if (ajaxify.data.template.login || ajaxify.data.template.register) {
241
+ if (ajaxify.data.template.login || ajaxify.data.template.register || !config.hasOwnProperty('openDraftsOnPageLoad') || !config.openDraftsOnPageLoad) {
232
242
  return;
233
243
  }
234
244
  // 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
- }
245
+ const available = drafts.getList('available');
246
+ const open = drafts.getList('open');
247
247
 
248
248
  if (available.length) {
249
249
  // Deconstruct each save_id and open up composer
250
250
  available.forEach(function (save_id) {
251
- if (!save_id) {
252
- return;
253
- }
254
- var draft = drafts.get(save_id);
255
-
256
- // If draft is already open, do nothing
257
- if (open.indexOf(save_id) !== -1) {
251
+ if (!save_id || open.includes(save_id)) {
258
252
  return;
259
253
  }
260
-
254
+ const draft = drafts.get(save_id);
261
255
  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);
256
+ drafts.removeFromDraftList('available', save_id);
257
+ drafts.removeFromDraftList('open', save_id);
265
258
  return;
266
259
  }
267
260
  openComposer(save_id, draft);
@@ -270,32 +263,42 @@ define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
270
263
  };
271
264
 
272
265
  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];
266
+ const saveObj = save_id.split(':');
267
+ const uid = saveObj[1];
277
268
  // Don't open other peoples' drafts
278
269
  if (parseInt(app.user.uid, 10) !== parseInt(uid, 10)) {
279
270
  return;
280
271
  }
281
272
  require(['composer'], function (composer) {
282
- if (type === 'cid') {
273
+ if (draft.action === 'topics.post') {
283
274
  composer.newTopic({
284
- cid: id,
275
+ save_id: draft.save_id,
276
+ cid: draft.cid,
285
277
  handle: app.user && app.user.uid ? undefined : utils.escapeHTML(draft.handle),
286
278
  title: utils.escapeHTML(draft.title),
287
279
  body: utils.escapeHTML(draft.text),
288
280
  tags: String(draft.tags || '').split(','),
289
281
  });
290
- } else if (type === 'tid') {
291
- api.get('/topics/' + id, {}, function (err, topicObj) {
282
+ } else if (draft.action === 'posts.reply') {
283
+ api.get('/topics/' + draft.tid, {}, function (err, topicObj) {
292
284
  if (err) {
293
285
  return alerts.error(err);
294
286
  }
295
- composer.newReply(id, undefined, topicObj.title, utils.escapeHTML(draft.text));
287
+
288
+ composer.newReply({
289
+ save_id: draft.save_id,
290
+ tid: draft.tid,
291
+ toPid: draft.toPid,
292
+ title: topicObj.title,
293
+ body: utils.escapeHTML(draft.text),
294
+ });
295
+ });
296
+ } else if (draft.action === 'posts.edit') {
297
+ composer.editPost({
298
+ save_id: draft.save_id,
299
+ pid: draft.pid,
300
+ body: utils.escapeHTML(draft.text),
296
301
  });
297
- } else if (type === 'pid') {
298
- composer.editPost(id);
299
302
  }
300
303
  });
301
304
  }
@@ -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
  }
@@ -197,7 +188,8 @@ define('composer', [
197
188
  };
198
189
 
199
190
  composer.newTopic = async (data) => {
200
- var pushData = {
191
+ let pushData = {
192
+ save_id: data.save_id,
201
193
  action: 'topics.post',
202
194
  cid: data.cid,
203
195
  handle: data.handle,
@@ -216,72 +208,90 @@ define('composer', [
216
208
  push(pushData);
217
209
  };
218
210
 
219
- composer.addQuote = function (tid, toPid, selectedPid, title, username, text, uuid) {
220
- uuid = uuid || composer.active;
211
+ composer.addQuote = function (data) {
212
+ // tid, toPid, selectedPid, title, username, text, uuid
213
+ data.uuid = data.uuid || composer.active;
221
214
 
222
- var escapedTitle = (title || '')
215
+ var escapedTitle = (data.title || '')
223
216
  .replace(/([\\`*_{}[\]()#+\-.!])/g, '\\$1')
224
217
  .replace(/\[/g, '&#91;')
225
218
  .replace(/\]/g, '&#93;')
226
219
  .replace(/%/g, '&#37;')
227
220
  .replace(/,/g, '&#44;');
228
221
 
229
- if (text) {
230
- text = '> ' + text.replace(/\n/g, '\n> ') + '\n\n';
222
+ if (data.body) {
223
+ data.body = '> ' + data.body.replace(/\n/g, '\n> ') + '\n\n';
231
224
  }
232
- var link = '[' + escapedTitle + '](' + config.relative_path + '/post/' + (selectedPid || toPid) + ')';
233
- if (uuid === undefined) {
234
- if (title && (selectedPid || toPid)) {
235
- composer.newReply(tid, toPid, title, '[[modules:composer.user_said_in, ' + username + ', ' + link + ']]\n' + text);
225
+ var link = '[' + escapedTitle + '](' + config.relative_path + '/post/' + (data.selectedPid || data.toPid) + ')';
226
+ if (data.uuid === undefined) {
227
+ if (data.title && (data.selectedPid || data.toPid)) {
228
+ composer.newReply({
229
+ tid: data.tid,
230
+ toPid: data.toPid,
231
+ title: data.title,
232
+ body: '[[modules:composer.user_said_in, ' + data.username + ', ' + link + ']]\n' + data.body,
233
+ });
236
234
  } else {
237
- composer.newReply(tid, toPid, title, '[[modules:composer.user_said, ' + username + ']]\n' + text);
235
+ composer.newReply({
236
+ tid: data.tid,
237
+ toPid: data.toPid,
238
+ title: data.title,
239
+ body: '[[modules:composer.user_said, ' + data.username + ']]\n' + data.body,
240
+ });
238
241
  }
239
242
  return;
240
- } else if (uuid !== composer.active) {
243
+ } else if (data.uuid !== composer.active) {
241
244
  // If the composer is not currently active, activate it
242
- composer.load(uuid);
245
+ composer.load(data.uuid);
243
246
  }
244
247
 
245
- var postContainer = $('.composer[data-uuid="' + uuid + '"]');
248
+ var postContainer = $('.composer[data-uuid="' + data.uuid + '"]');
246
249
  var bodyEl = postContainer.find('textarea');
247
250
  var prevText = bodyEl.val();
248
- if (title && (selectedPid || toPid)) {
249
- translator.translate('[[modules:composer.user_said_in, ' + username + ', ' + link + ']]\n', config.defaultLang, onTranslated);
251
+ if (data.title && (data.selectedPid || data.toPid)) {
252
+ translator.translate('[[modules:composer.user_said_in, ' + data.username + ', ' + link + ']]\n', config.defaultLang, onTranslated);
250
253
  } else {
251
- translator.translate('[[modules:composer.user_said, ' + username + ']]\n', config.defaultLang, onTranslated);
254
+ translator.translate('[[modules:composer.user_said, ' + data.username + ']]\n', config.defaultLang, onTranslated);
252
255
  }
253
256
 
254
257
  function onTranslated(translated) {
255
- composer.posts[uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + text;
256
- bodyEl.val(composer.posts[uuid].body);
258
+ composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + data.body;
259
+ bodyEl.val(composer.posts[data.uuid].body);
257
260
  focusElements(postContainer);
258
261
  preview.render(postContainer);
259
262
  }
260
263
  };
261
264
 
262
- composer.newReply = function (tid, toPid, title, text) {
263
- translator.translate(text, config.defaultLang, function (translated) {
265
+ composer.newReply = function (data) {
266
+ translator.translate(data.body, config.defaultLang, function (translated) {
264
267
  push({
268
+ save_id: data.save_id,
265
269
  action: 'posts.reply',
266
- tid: tid,
267
- toPid: toPid,
268
- title: title,
270
+ tid: data.tid,
271
+ toPid: data.toPid,
272
+ title: data.title,
269
273
  body: translated,
270
- modified: !!((title && title.length) || (translated && translated.length)),
274
+ modified: !!((data.title && data.title.length) || (translated && translated.length)),
271
275
  isMain: false,
272
276
  });
273
277
  });
274
278
  };
275
279
 
276
- composer.editPost = function (pid) {
277
- socket.emit('plugins.composer.push', pid, function (err, threadData) {
280
+ composer.editPost = function (data) {
281
+ // pid, text
282
+ socket.emit('plugins.composer.push', data.pid, function (err, postData) {
278
283
  if (err) {
279
284
  return alerts.error(err);
280
285
  }
281
- threadData.action = 'posts.edit';
282
- threadData.pid = pid;
283
- threadData.modified = false;
284
- push(threadData);
286
+ postData.save_id = data.save_id;
287
+ postData.action = 'posts.edit';
288
+ postData.pid = data.pid;
289
+ postData.modified = false;
290
+ if (data.body) {
291
+ postData.body = data.body;
292
+ postData.modified = true;
293
+ }
294
+ push(postData);
285
295
  });
286
296
  };
287
297
 
@@ -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>