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,19 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
define('composer/drafts', ['api', 'alerts'], function (api, alerts) {
|
|
4
|
-
|
|
4
|
+
const drafts = {};
|
|
5
5
|
|
|
6
6
|
drafts.init = function (postContainer, postData) {
|
|
7
|
-
|
|
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.
|
|
11
|
-
drafts.
|
|
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
|
-
//
|
|
25
|
-
|
|
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(
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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.
|
|
124
|
-
drafts.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
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
|
-
|
|
142
|
-
|
|
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
|
|
145
|
-
|
|
143
|
+
console.warn('[composer/drafts] Could not read list of available drafts');
|
|
144
|
+
return [];
|
|
146
145
|
}
|
|
147
|
-
|
|
146
|
+
};
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
drafts.
|
|
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
|
-
|
|
274
|
-
|
|
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 (
|
|
270
|
+
if (draft.action === 'topics.post') {
|
|
283
271
|
composer.newTopic({
|
|
284
|
-
cid:
|
|
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 (
|
|
291
|
-
api.get('/topics/' +
|
|
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(
|
|
283
|
+
composer.newReply(draft.tid, undefined, topicObj.title, utils.escapeHTML(draft.text));
|
|
296
284
|
});
|
|
297
|
-
} else if (
|
|
298
|
-
composer.editPost(
|
|
285
|
+
} else if (draft.action === 'posts.edit') {
|
|
286
|
+
composer.editPost(draft.pid, draft.text);
|
|
299
287
|
}
|
|
300
288
|
});
|
|
301
289
|
}
|
package/static/lib/composer.js
CHANGED
|
@@ -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,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
|
-
|
|
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
|
-
|
|
12
|
+
{{{ end }}}
|
|
13
13
|
<div data-component="composer/title" class="position-relative">
|
|
14
|
-
|
|
14
|
+
{{{ if isTopicOrMain }}}
|
|
15
15
|
<input class="title form-control h-100" type="text" tabindex="1" placeholder="[[topic:composer.title_placeholder]]" value="{topicTitle}"/>
|
|
16
|
-
|
|
17
|
-
<span class="title h-100">[[topic:composer.replying_to, "{topicTitle}"]]</span>
|
|
18
|
-
|
|
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>
|