nodebb-plugin-composer-default 10.3.34 → 11.0.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 +42 -35
- package/package.json +3 -4
- package/static/lib/composer/drafts.js +6 -6
- package/static/lib/composer/scheduler.js +3 -3
- package/static/lib/composer/uploads.js +2 -2
- package/static/lib/composer.js +28 -36
- package/static/templates/compose.tpl +3 -1
- package/static/templates/composer.tpl +2 -23
- package/static/templates/partials/composer-formatting.tpl +5 -21
- package/static/templates/partials/composer-mobile-header.tpl +21 -0
- package/static/templates/partials/composer-title-container.tpl +10 -2
- package/websockets.js +2 -2
package/library.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
const nconf = nodebb.require('nconf');
|
|
5
|
-
const validator = require('validator');
|
|
6
5
|
|
|
7
6
|
const plugins = nodebb.require('./src/plugins');
|
|
8
7
|
const topics = nodebb.require('./src/topics');
|
|
@@ -11,7 +10,7 @@ const posts = nodebb.require('./src/posts');
|
|
|
11
10
|
const user = nodebb.require('./src/user');
|
|
12
11
|
const meta = nodebb.require('./src/meta');
|
|
13
12
|
const privileges = nodebb.require('./src/privileges');
|
|
14
|
-
const
|
|
13
|
+
const tx = nodebb.require('./src/translator');
|
|
15
14
|
const utils = nodebb.require('./src/utils');
|
|
16
15
|
const helpers = nodebb.require('./src/controllers/helpers');
|
|
17
16
|
const SocketPlugins = nodebb.require('./src/socket.io/plugins');
|
|
@@ -43,27 +42,20 @@ plugin.addAdminNavigation = async function (header) {
|
|
|
43
42
|
return header;
|
|
44
43
|
};
|
|
45
44
|
|
|
46
|
-
plugin.getFormattingOptions = async function () {
|
|
45
|
+
plugin.getFormattingOptions = async function (uid) {
|
|
47
46
|
const defaultVisibility = {
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// use d-none d-lg-block, visible on desktop
|
|
48
|
+
// use d-block d-lg-none, visible on mobile
|
|
49
|
+
class: 'd-block',
|
|
50
50
|
|
|
51
51
|
// op or reply
|
|
52
52
|
main: true,
|
|
53
53
|
reply: true,
|
|
54
54
|
};
|
|
55
55
|
let payload = {
|
|
56
|
+
uid,
|
|
56
57
|
defaultVisibility,
|
|
57
58
|
options: [
|
|
58
|
-
{
|
|
59
|
-
name: 'tags',
|
|
60
|
-
title: '[[global:tags.tags]]',
|
|
61
|
-
className: 'fa fa-tags',
|
|
62
|
-
visibility: {
|
|
63
|
-
...defaultVisibility,
|
|
64
|
-
desktop: false,
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
59
|
{
|
|
68
60
|
name: 'zen',
|
|
69
61
|
title: '[[modules:composer.zen-mode]]',
|
|
@@ -72,16 +64,36 @@ plugin.getFormattingOptions = async function () {
|
|
|
72
64
|
},
|
|
73
65
|
],
|
|
74
66
|
};
|
|
75
|
-
|
|
67
|
+
const [canUploadImage, canUploadFile] = await privileges.global.can(
|
|
68
|
+
['upload:post:image', 'upload:post:file'], uid
|
|
69
|
+
);
|
|
70
|
+
if (canUploadImage) {
|
|
71
|
+
if (meta.config.allowTopicsThumbnail) {
|
|
72
|
+
payload.options.push({
|
|
73
|
+
name: 'thumbs',
|
|
74
|
+
title: '[[topic:composer.thumb-title]]',
|
|
75
|
+
className: 'fa fa-address-card-o',
|
|
76
|
+
badge: true,
|
|
77
|
+
visibility: {
|
|
78
|
+
...defaultVisibility,
|
|
79
|
+
reply: false,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
76
83
|
payload.options.push({
|
|
77
|
-
name: '
|
|
78
|
-
title: '[[
|
|
79
|
-
className: 'fa fa-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
name: 'picture',
|
|
85
|
+
title: '[[modules:composer.upload-picture]]',
|
|
86
|
+
className: 'fa fa-file-image-o',
|
|
87
|
+
visibility: defaultVisibility,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (canUploadFile) {
|
|
92
|
+
payload.options.push({
|
|
93
|
+
name: 'upload',
|
|
94
|
+
title: '[[modules:composer.upload-file]]',
|
|
95
|
+
className: 'fa fa-file-o',
|
|
96
|
+
visibility: defaultVisibility,
|
|
85
97
|
});
|
|
86
98
|
}
|
|
87
99
|
|
|
@@ -135,7 +147,7 @@ plugin.filterComposerBuild = async function (hookData) {
|
|
|
135
147
|
]),
|
|
136
148
|
user.isAdministrator(req.uid),
|
|
137
149
|
isModerator(req),
|
|
138
|
-
plugin.getFormattingOptions(),
|
|
150
|
+
plugin.getFormattingOptions(req.uid),
|
|
139
151
|
getTagWhitelist(req.query, req.uid),
|
|
140
152
|
privileges.global.get(req.uid),
|
|
141
153
|
canTag(req),
|
|
@@ -161,7 +173,8 @@ plugin.filterComposerBuild = async function (hookData) {
|
|
|
161
173
|
const cid = req.query.cid || '';
|
|
162
174
|
const topicTitle = topicData && topicData.title ?
|
|
163
175
|
topicData.title :
|
|
164
|
-
|
|
176
|
+
String(req.query.title || '');
|
|
177
|
+
|
|
165
178
|
return {
|
|
166
179
|
req: req,
|
|
167
180
|
res: res,
|
|
@@ -180,12 +193,6 @@ plugin.filterComposerBuild = async function (hookData) {
|
|
|
180
193
|
// can't use title property as that is used for page title
|
|
181
194
|
topicTitle: topicTitle,
|
|
182
195
|
titleLength: topicTitle ? topicTitle.length : 0,
|
|
183
|
-
titleLabel: translator.compile(
|
|
184
|
-
isEditing ?
|
|
185
|
-
'topic:composer.editing-in' :
|
|
186
|
-
'topic:composer.replying-to',
|
|
187
|
-
`"${topicTitle}"`
|
|
188
|
-
),
|
|
189
196
|
|
|
190
197
|
topic: topicData,
|
|
191
198
|
thumb: topicData ? topicData.thumb : '',
|
|
@@ -231,7 +238,7 @@ async function checkPrivileges(req, res) {
|
|
|
231
238
|
|
|
232
239
|
function generateDiscardRoute(req, topicData) {
|
|
233
240
|
if (req.query.cid) {
|
|
234
|
-
return `${nconf.get('relative_path')}/category/${
|
|
241
|
+
return `${nconf.get('relative_path')}/category/${req.query.cid}`;
|
|
235
242
|
} else if ((req.query.tid || req.query.pid)) {
|
|
236
243
|
if (topicData) {
|
|
237
244
|
return `${nconf.get('relative_path')}/topic/${topicData.slug}`;
|
|
@@ -245,15 +252,15 @@ async function generateBody(req, postData) {
|
|
|
245
252
|
// Quoted reply
|
|
246
253
|
if (req.query.toPid && parseInt(req.query.quoted, 10) === 1 && postData) {
|
|
247
254
|
const username = await user.getUserField(postData.uid, 'username');
|
|
248
|
-
const translated = await
|
|
255
|
+
const translated = await tx.translateKey('modules:composer.user-said', [tx.escape(username)]);
|
|
249
256
|
body = `${translated}\n` +
|
|
250
257
|
`> ${postData ? `${postData.content.replace(/\n/g, '\n> ')}\n\n` : ''}`;
|
|
251
258
|
} else if (req.query.body || req.query.content) {
|
|
252
|
-
body =
|
|
259
|
+
body = req.query.body || req.query.content;
|
|
253
260
|
} else {
|
|
254
261
|
body = postData ? postData.content : '';
|
|
255
262
|
}
|
|
256
|
-
return
|
|
263
|
+
return body;
|
|
257
264
|
}
|
|
258
265
|
|
|
259
266
|
async function getPostData(req) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-composer-default",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "Default composer for NodeBB",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"repository": {
|
|
@@ -26,11 +26,10 @@
|
|
|
26
26
|
},
|
|
27
27
|
"readmeFilename": "README.md",
|
|
28
28
|
"nbbpm": {
|
|
29
|
-
"compatibility": "^4.
|
|
29
|
+
"compatibility": "^4.14.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"screenfull": "^5.0.2"
|
|
33
|
-
"validator": "^13.7.0"
|
|
32
|
+
"screenfull": "^5.0.2"
|
|
34
33
|
},
|
|
35
34
|
"devDependencies": {
|
|
36
35
|
"eslint": "^10.0.0",
|
|
@@ -66,14 +66,14 @@ define('composer/drafts', ['api', 'hooks'], function (api, hooks) {
|
|
|
66
66
|
}
|
|
67
67
|
$(window).trigger('action:composer.drafts.get', {
|
|
68
68
|
save_id: save_id,
|
|
69
|
-
draft: draft,
|
|
69
|
+
draft: { ...draft },
|
|
70
70
|
storage: storage,
|
|
71
71
|
});
|
|
72
72
|
return draft;
|
|
73
73
|
} catch (e) {
|
|
74
74
|
console.warn(`[composer/drafts] Could not get draft ${save_id}, removing`);
|
|
75
|
-
drafts.removeFromDraftList('available');
|
|
76
|
-
drafts.removeFromDraftList('open');
|
|
75
|
+
drafts.removeFromDraftList('available', save_id);
|
|
76
|
+
drafts.removeFromDraftList('open', save_id);
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
};
|
|
@@ -263,8 +263,8 @@ define('composer/drafts', ['api', 'hooks'], function (api, hooks) {
|
|
|
263
263
|
fromDraft: true,
|
|
264
264
|
save_id: draft.save_id,
|
|
265
265
|
cid: draft.cid,
|
|
266
|
-
handle: app.user && app.user.uid ? undefined :
|
|
267
|
-
title:
|
|
266
|
+
handle: app.user && app.user.uid ? undefined : draft.handle,
|
|
267
|
+
title: draft.title,
|
|
268
268
|
body: draft.text,
|
|
269
269
|
tags: String(draft.tags || '').split(','),
|
|
270
270
|
thumbs: draft.thumbs || [],
|
|
@@ -288,7 +288,7 @@ define('composer/drafts', ['api', 'hooks'], function (api, hooks) {
|
|
|
288
288
|
fromDraft: true,
|
|
289
289
|
save_id: draft.save_id,
|
|
290
290
|
pid: draft.pid,
|
|
291
|
-
title: draft.title
|
|
291
|
+
title: draft.title || undefined,
|
|
292
292
|
body: draft.text,
|
|
293
293
|
thumbs: draft.thumbs || [],
|
|
294
294
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
define('composer/scheduler', ['benchpress', '
|
|
3
|
+
define('composer/scheduler', ['benchpress', 'modals', 'alerts', 'translator'], function (
|
|
4
4
|
Benchpress,
|
|
5
|
-
|
|
5
|
+
modals,
|
|
6
6
|
alerts,
|
|
7
7
|
translator
|
|
8
8
|
) {
|
|
@@ -91,7 +91,7 @@ define('composer/scheduler', ['benchpress', 'bootbox', 'alerts', 'translator'],
|
|
|
91
91
|
|
|
92
92
|
async function openModal() {
|
|
93
93
|
const html = await Benchpress.render('modals/topic-scheduler');
|
|
94
|
-
|
|
94
|
+
modals.dialog({
|
|
95
95
|
message: html,
|
|
96
96
|
title: '[[modules:composer.schedule-for]]',
|
|
97
97
|
className: 'topic-scheduler',
|
|
@@ -20,7 +20,7 @@ define('composer/uploads', [
|
|
|
20
20
|
|
|
21
21
|
addChangeHandlers(post_uuid);
|
|
22
22
|
|
|
23
|
-
translator.
|
|
23
|
+
translator.translateKey('modules:composer.uploading', [0]).then((translated) => {
|
|
24
24
|
uploadingText = translated;
|
|
25
25
|
});
|
|
26
26
|
};
|
|
@@ -177,7 +177,7 @@ define('composer/uploads', [
|
|
|
177
177
|
},
|
|
178
178
|
|
|
179
179
|
uploadProgress: function (event, position, total, percent) {
|
|
180
|
-
translator.
|
|
180
|
+
translator.translateKey('modules:composer.uploading', [percent]).then(function (translated) {
|
|
181
181
|
if (doneUploading) {
|
|
182
182
|
return;
|
|
183
183
|
}
|
package/static/lib/composer.js
CHANGED
|
@@ -16,15 +16,15 @@ define('composer', [
|
|
|
16
16
|
'scrollStop',
|
|
17
17
|
'topicThumbs',
|
|
18
18
|
'api',
|
|
19
|
-
'
|
|
19
|
+
'modals',
|
|
20
20
|
'alerts',
|
|
21
21
|
'hooks',
|
|
22
22
|
'messages',
|
|
23
23
|
'search',
|
|
24
24
|
'screenfull',
|
|
25
|
-
], function (taskbar,
|
|
25
|
+
], function (taskbar, tx, uploads, formatting, drafts, tags,
|
|
26
26
|
categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop,
|
|
27
|
-
topicThumbs, api,
|
|
27
|
+
topicThumbs, api, modals, alerts, hooks, messagesModule, search, screenfull) {
|
|
28
28
|
const composer = {
|
|
29
29
|
active: undefined,
|
|
30
30
|
posts: {},
|
|
@@ -52,7 +52,7 @@ define('composer', [
|
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
composer.discardConfirm =
|
|
55
|
+
composer.discardConfirm = modals.confirm('[[modules:composer.discard]]', function (confirm) {
|
|
56
56
|
if (confirm) {
|
|
57
57
|
composer.discard(composer.active);
|
|
58
58
|
} else {
|
|
@@ -106,7 +106,9 @@ define('composer', [
|
|
|
106
106
|
|
|
107
107
|
// Find a match
|
|
108
108
|
for (const uuid of Object.keys(composer.posts)) {
|
|
109
|
-
|
|
109
|
+
const openPost = composer.posts[uuid];
|
|
110
|
+
// make sure dom element exists too
|
|
111
|
+
if (openPost.hasOwnProperty(type) && id === String(openPost[type]) && $(`.composer[data-uuid="${uuid}"]`).length) {
|
|
110
112
|
return uuid;
|
|
111
113
|
}
|
|
112
114
|
}
|
|
@@ -124,7 +126,7 @@ define('composer', [
|
|
|
124
126
|
if (existingUUID) {
|
|
125
127
|
taskbar.updateActive(existingUUID);
|
|
126
128
|
if (post.body && !post.fromDraft) {
|
|
127
|
-
const postContainer = $(
|
|
129
|
+
const postContainer = $(`.composer[data-uuid="${existingUUID}"]`);
|
|
128
130
|
const bodyEl = postContainer.find('textarea');
|
|
129
131
|
const prevText = bodyEl.val();
|
|
130
132
|
composer.posts[existingUUID].body = (prevText.length ? prevText + '\n\n' : '') + post.body;
|
|
@@ -136,9 +138,9 @@ define('composer', [
|
|
|
136
138
|
const uuid = utils.generateUUID();
|
|
137
139
|
let actionText = '[[topic:composer.new-topic]]';
|
|
138
140
|
if (post.action === 'posts.reply') {
|
|
139
|
-
actionText =
|
|
141
|
+
actionText = tx.compile('topic:composer.replying-to', `"${tx.escape(post.title)}"`);
|
|
140
142
|
} else if (post.action === 'posts.edit') {
|
|
141
|
-
actionText =
|
|
143
|
+
actionText = tx.compile('topic:composer.editing-in', `"${tx.escape(post.title)}"`);
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
taskbar.push('composer', uuid, {
|
|
@@ -203,7 +205,7 @@ define('composer', [
|
|
|
203
205
|
push(pushData);
|
|
204
206
|
};
|
|
205
207
|
|
|
206
|
-
composer.addQuote = function (data) {
|
|
208
|
+
composer.addQuote = async function (data) {
|
|
207
209
|
// tid, toPid, selectedPid, title, username, text, uuid
|
|
208
210
|
data.uuid = data.uuid || composer.active;
|
|
209
211
|
|
|
@@ -224,16 +226,18 @@ define('composer', [
|
|
|
224
226
|
const postHref = `${config.relative_path}/post/${encodeURIComponent(data.selectedPid || data.toPid)}`;
|
|
225
227
|
const topicLink = `[${escapedTitle}](${postHref})`;
|
|
226
228
|
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
const txQuoteText = useTopicLink ?
|
|
230
|
+
await tx.translateKey('modules:composer.user-said-in', [tx.escape(data.username), topicLink]) :
|
|
231
|
+
await tx.translateKey('modules:composer.user-said', [tx.escape(data.username), postHref]);
|
|
232
|
+
|
|
233
|
+
const quoteText = `> ${txQuoteText}\n>\n`;
|
|
230
234
|
|
|
231
235
|
if (data.uuid === undefined) {
|
|
232
236
|
composer.newReply({
|
|
233
237
|
tid: data.tid,
|
|
234
238
|
toPid: data.toPid,
|
|
235
239
|
title: data.title,
|
|
236
|
-
body:
|
|
240
|
+
body: quoteText + data.body,
|
|
237
241
|
});
|
|
238
242
|
return;
|
|
239
243
|
} else if (data.uuid !== composer.active) {
|
|
@@ -245,16 +249,13 @@ define('composer', [
|
|
|
245
249
|
const bodyEl = postContainer.find('textarea');
|
|
246
250
|
const prevText = bodyEl.val();
|
|
247
251
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
preview.render(postContainer);
|
|
253
|
-
});
|
|
252
|
+
composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + quoteText + data.body;
|
|
253
|
+
bodyEl.val(composer.posts[data.uuid].body);
|
|
254
|
+
focusElements(postContainer);
|
|
255
|
+
preview.render(postContainer);
|
|
254
256
|
};
|
|
255
257
|
|
|
256
258
|
composer.newReply = async function (data) {
|
|
257
|
-
const translated = await translator.translate(data.body, config.defaultLang);
|
|
258
259
|
let pushData = {
|
|
259
260
|
fromDraft: data.fromDraft,
|
|
260
261
|
save_id: data.save_id,
|
|
@@ -262,8 +263,8 @@ define('composer', [
|
|
|
262
263
|
tid: data.tid,
|
|
263
264
|
toPid: data.toPid,
|
|
264
265
|
title: data.title,
|
|
265
|
-
body:
|
|
266
|
-
modified: !!(
|
|
266
|
+
body: data.body,
|
|
267
|
+
modified: !!(data.body && data.body.length),
|
|
267
268
|
isMain: false,
|
|
268
269
|
};
|
|
269
270
|
({ pushData } = await hooks.fire('filter:composer.reply.push', {
|
|
@@ -370,7 +371,7 @@ define('composer', [
|
|
|
370
371
|
formatting.exitFullscreen();
|
|
371
372
|
|
|
372
373
|
const btn = $(this).prop('disabled', true);
|
|
373
|
-
|
|
374
|
+
modals.confirm('[[modules:composer.discard]]', function (confirm) {
|
|
374
375
|
if (confirm) {
|
|
375
376
|
composer.discard(post_uuid);
|
|
376
377
|
removeComposerHistory();
|
|
@@ -446,15 +447,10 @@ define('composer', [
|
|
|
446
447
|
const topicTemplate = isTopic && postData.category ? postData.category.topicTemplate : '';
|
|
447
448
|
|
|
448
449
|
let data = {
|
|
450
|
+
action: postData.action,
|
|
449
451
|
topicTitle: postData.title,
|
|
450
452
|
titleLength: postData.title.length,
|
|
451
|
-
|
|
452
|
-
isEditing ?
|
|
453
|
-
'topic:composer.editing-in' :
|
|
454
|
-
'topic:composer.replying-to',
|
|
455
|
-
`"${postData.title}"`
|
|
456
|
-
),
|
|
457
|
-
body: utils.escapeHTML(translator.escape(postData.body) || topicTemplate),
|
|
453
|
+
body: postData.body || topicTemplate,
|
|
458
454
|
mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm',
|
|
459
455
|
resizable: true,
|
|
460
456
|
thumb: postData.thumb,
|
|
@@ -492,7 +488,7 @@ define('composer', [
|
|
|
492
488
|
app.toggleNavbar(false);
|
|
493
489
|
}
|
|
494
490
|
|
|
495
|
-
postData.mobile =
|
|
491
|
+
postData.mobile = data.mobile;
|
|
496
492
|
|
|
497
493
|
({ postData, createData: data } = await hooks.fire('filter:composer.create', {
|
|
498
494
|
postData: postData,
|
|
@@ -505,10 +501,6 @@ define('composer', [
|
|
|
505
501
|
}
|
|
506
502
|
composerTemplate = $(composerTemplate);
|
|
507
503
|
|
|
508
|
-
composerTemplate.find('.title, textarea.write').each(function () {
|
|
509
|
-
$(this).text(translator.unescape($(this).text()));
|
|
510
|
-
});
|
|
511
|
-
|
|
512
504
|
composerTemplate.attr('data-uuid', post_uuid);
|
|
513
505
|
|
|
514
506
|
$(document.body).append(composerTemplate);
|
|
@@ -592,7 +584,7 @@ define('composer', [
|
|
|
592
584
|
helpBtn.on('click', async function () {
|
|
593
585
|
const html = await socket.emit('plugins.composer.renderHelp');
|
|
594
586
|
if (html && html.length > 0) {
|
|
595
|
-
|
|
587
|
+
modals.dialog({
|
|
596
588
|
size: 'large',
|
|
597
589
|
message: html,
|
|
598
590
|
onEscape: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div component="composer" class="composer pb-3 h-100 {{{ if resizable }}} resizable{{{ end }}}{{{ if !isTopicOrMain }}} reply{{{ end }}}"{{{ if !disabled }}} style="visibility: inherit;"{{{ end }}}>
|
|
2
|
-
<div class="composer-container d-flex flex-column gap-1 h-100">
|
|
2
|
+
<div class="composer-container d-flex flex-column gap-1 h-100 position-relative">
|
|
3
3
|
<form id="compose-form" method="post">
|
|
4
4
|
{{{ if pid }}}
|
|
5
5
|
<input type="hidden" name="pid" value="{pid}" />
|
|
@@ -23,5 +23,7 @@
|
|
|
23
23
|
{{{ if isTopicOrMain }}}
|
|
24
24
|
<!-- IMPORT partials/composer-tags.tpl -->
|
|
25
25
|
{{{ end }}}
|
|
26
|
+
|
|
27
|
+
<div class="imagedrop"><div>[[topic:composer.drag-and-drop-images]]</div></div>
|
|
26
28
|
</div>
|
|
27
29
|
</div>
|
|
@@ -1,27 +1,6 @@
|
|
|
1
1
|
<div component="composer" class="composer {{{ if resizable }}} resizable{{{ end }}}{{{ if !isTopicOrMain }}} reply{{{ end }}}">
|
|
2
|
-
<div class="composer-container d-flex flex-column gap-1 h-100">
|
|
3
|
-
<!-- mobile
|
|
4
|
-
<nav class="navbar fixed-top mobile-navbar text-bg-primary d-flex d-md-none flex-nowrap gap-1 px-1">
|
|
5
|
-
<div class="btn-group">
|
|
6
|
-
<button class="btn btn-sm btn-primary composer-discard fs-5" data-action="discard" tabindex="-1"><i class="fa fa-fw fa-times"></i></button>
|
|
7
|
-
<button class="btn btn-sm btn-primary composer-minimize fs-5" data-action="minimize" tabindex="-1"><i class="fa fa-fw fa-minus"></i></button>
|
|
8
|
-
</div>
|
|
9
|
-
{{{ if isTopic }}}
|
|
10
|
-
<div class="flex-1" style="min-width: 0px;">
|
|
11
|
-
<!-- IMPORT partials/category/selector-dropdown-left.tpl -->
|
|
12
|
-
</div>
|
|
13
|
-
{{{ end }}}
|
|
14
|
-
{{{ if !isTopicOrMain }}}
|
|
15
|
-
<h4 class="title text-center text-bg-primary text-truncate fs-6 mb-0 px-2">{titleLabel}</h4>
|
|
16
|
-
{{{ end }}}
|
|
17
|
-
|
|
18
|
-
<div class="d-flex gap-1 flex-nowrap">
|
|
19
|
-
<button class="btn btn-sm btn-primary display-scheduler fs-5 {{{ if !canSchedule }}} hidden{{{ end }}}">
|
|
20
|
-
<i class="fa fa-fw fa-clock-o"></i>
|
|
21
|
-
</button>
|
|
22
|
-
<button class="btn btn-sm btn-primary composer-submit fs-5" data-action="post" tabindex="-1"><i class="fa fa-fw fa-chevron-right"></i></button>
|
|
23
|
-
</div>
|
|
24
|
-
</nav>
|
|
2
|
+
<div class="composer-container d-flex flex-column gap-1 h-100 position-relative">
|
|
3
|
+
<!-- IMPORT partials/composer-mobile-header.tpl -->
|
|
25
4
|
<div class="p-2 d-flex flex-column gap-1 h-100">
|
|
26
5
|
<!-- IMPORT partials/composer-title-container.tpl -->
|
|
27
6
|
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
{{{ if ./spacer }}}
|
|
5
5
|
<li class="small spacer"></li>
|
|
6
6
|
{{{ else }}}
|
|
7
|
-
{{{ if (
|
|
7
|
+
{{{ if ((isTopicOrMain && ./visibility.main) || (!isTopicOrMain && ./visibility.reply)) }}}
|
|
8
8
|
{{{ if ./dropdownItems.length }}}
|
|
9
|
-
<li class="dropdown dropdown-left bottom-sheet" title="{./title}">
|
|
9
|
+
<li class="dropdown dropdown-left bottom-sheet {./visibility.class}" title="{tx(./title)}">
|
|
10
10
|
<button class="btn btn-sm btn-link text-reset" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{./title}">
|
|
11
11
|
<i class="{./className}"></i>
|
|
12
12
|
</button>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
{{{ each ./dropdownItems }}}
|
|
15
15
|
<li>
|
|
16
16
|
<a href="#" data-format="{./name}" class="dropdown-item rounded-1 position-relative" role="menuitem">
|
|
17
|
-
<i class="{./className} text-secondary"></i> {./text}
|
|
17
|
+
<i class="{./className} text-secondary"></i> {tx(./text)}
|
|
18
18
|
{{{ if ./badge }}}
|
|
19
19
|
<span class="px-1 position-absolute top-0 start-100 translate-middle badge rounded text-bg-info"></span>
|
|
20
20
|
{{{ end }}}
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
</ul>
|
|
25
25
|
</li>
|
|
26
26
|
{{{ else }}}
|
|
27
|
-
<li title="{./title}">
|
|
28
|
-
<button data-format="{./name}" class="btn btn-sm btn-link text-reset position-relative" aria-label="{./title}">
|
|
27
|
+
<li title="{tx(./title)}" class="{./visibility.class}">
|
|
28
|
+
<button data-format="{./name}" class="btn btn-sm btn-link text-reset position-relative" aria-label="{tx(./title)}">
|
|
29
29
|
<i class="{./className}"></i>
|
|
30
30
|
{{{ if ./badge }}}
|
|
31
31
|
<span class="px-1 position-absolute top-0 start-100 translate-middle badge rounded text-bg-info"></span>
|
|
@@ -37,22 +37,6 @@
|
|
|
37
37
|
{{{ end }}}
|
|
38
38
|
{{{ end }}}
|
|
39
39
|
|
|
40
|
-
{{{ if canUploadImage }}}
|
|
41
|
-
<li title="[[modules:composer.upload-picture]]">
|
|
42
|
-
<button data-format="picture" class="img-upload-btn btn btn-sm btn-link text-reset" aria-label="[[modules:composer.upload-picture]]">
|
|
43
|
-
<i class="fa fa-file-image-o"></i>
|
|
44
|
-
</button>
|
|
45
|
-
</li>
|
|
46
|
-
{{{ end }}}
|
|
47
|
-
|
|
48
|
-
{{{ if canUploadFile }}}
|
|
49
|
-
<li title="[[modules:composer.upload-file]]">
|
|
50
|
-
<button data-format="upload" class="file-upload-btn btn btn-sm btn-link text-reset" aria-label="[[modules:composer.upload-file]]">
|
|
51
|
-
<i class="fa fa-file-o"></i>
|
|
52
|
-
</button>
|
|
53
|
-
</li>
|
|
54
|
-
{{{ end }}}
|
|
55
|
-
|
|
56
40
|
<form id="fileForm" method="post" enctype="multipart/form-data">
|
|
57
41
|
<input type="file" id="files" name="files[]" multiple class="hide"/>
|
|
58
42
|
</form>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<nav class="navbar fixed-top mobile-navbar text-bg-primary d-flex d-md-none flex-nowrap gap-1 px-1">
|
|
2
|
+
<div class="btn-group">
|
|
3
|
+
<button class="btn btn-sm btn-primary composer-discard fs-5" data-action="discard" tabindex="-1"><i class="fa fa-fw fa-times"></i></button>
|
|
4
|
+
<button class="btn btn-sm btn-primary composer-minimize fs-5" data-action="minimize" tabindex="-1"><i class="fa fa-fw fa-minus"></i></button>
|
|
5
|
+
</div>
|
|
6
|
+
{{{ if isTopic }}}
|
|
7
|
+
<div class="flex-1" style="min-width: 0px;">
|
|
8
|
+
<!-- IMPORT partials/category/selector-dropdown-left.tpl -->
|
|
9
|
+
</div>
|
|
10
|
+
{{{ end }}}
|
|
11
|
+
{{{ if !isTopicOrMain }}}
|
|
12
|
+
<h4 class="title text-center text-bg-primary text-truncate fs-6 mb-0 px-2">{titleLabel}</h4>
|
|
13
|
+
{{{ end }}}
|
|
14
|
+
|
|
15
|
+
<div class="d-flex gap-1 flex-nowrap">
|
|
16
|
+
<button class="btn btn-sm btn-primary display-scheduler fs-5 {{{ if !canSchedule }}} hidden{{{ end }}}">
|
|
17
|
+
<i class="fa fa-fw fa-clock-o"></i>
|
|
18
|
+
</button>
|
|
19
|
+
<button class="btn btn-sm btn-primary composer-submit fs-5" data-action="post" tabindex="-1"><i class="fa fa-fw fa-chevron-right"></i></button>
|
|
20
|
+
</div>
|
|
21
|
+
</nav>
|
|
@@ -15,7 +15,15 @@
|
|
|
15
15
|
{{{ if isTopicOrMain }}}
|
|
16
16
|
<input class="title form-control h-100 rounded-1 shadow-none" type="text" placeholder="[[topic:composer.title-placeholder]]" value="{topicTitle}" />
|
|
17
17
|
{{{ else }}}
|
|
18
|
-
<span class="{{{ if !template.compose }}}d-none d-md-block{{{ else }}}d-block{{{ end }}} title h-100 text-truncate">
|
|
18
|
+
<span class="{{{ if !template.compose }}}d-none d-md-block{{{ else }}}d-block{{{ end }}} title h-100 text-truncate">
|
|
19
|
+
{{{ if (./action == "posts.reply") }}}
|
|
20
|
+
{{tx("topic:composer.replying-to", txEscape(quote(./topicTitle)))}}
|
|
21
|
+
{{{ end }}}
|
|
22
|
+
|
|
23
|
+
{{{ if (./action == "posts.edit") }}}
|
|
24
|
+
{{tx("topic:composer.editing-in", txEscape(quote(./topicTitle)))}}
|
|
25
|
+
{{{ end }}}
|
|
26
|
+
</span>
|
|
19
27
|
{{{ end }}}
|
|
20
28
|
<div id="quick-search-container" class="quick-search-container mt-2 dropdown-menu d-block p-2 hidden">
|
|
21
29
|
<div class="text-center loading-indicator"><i class="fa fa-spinner fa-spin"></i></div>
|
|
@@ -37,7 +45,7 @@
|
|
|
37
45
|
<li><a class="dropdown-item rounded-1 display-scheduler {{{ if !canSchedule }}}hidden{{{ end }}}" role="menuitem">[[topic:composer.post-later]]</a></li>
|
|
38
46
|
<li><a class="dropdown-item rounded-1 cancel-scheduling hidden" role="menuitem">[[modules:composer.cancel-scheduling]]</a></li>
|
|
39
47
|
{{{ each submitOptions }}}
|
|
40
|
-
<li><a class="dropdown-item rounded-1" href="#" data-action="{./action}" role="menuitem">{./text}</a></li>
|
|
48
|
+
<li><a class="dropdown-item rounded-1" href="#" data-action="{./action}" role="menuitem">{{./text}}</a></li>
|
|
41
49
|
{{{ end }}}
|
|
42
50
|
</ul>
|
|
43
51
|
</div>
|
package/websockets.js
CHANGED
|
@@ -64,8 +64,8 @@ Sockets.renderHelp = async function () {
|
|
|
64
64
|
return helpText;
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
Sockets.getFormattingOptions = async function () {
|
|
68
|
-
return await require('./library').getFormattingOptions();
|
|
67
|
+
Sockets.getFormattingOptions = async function (socket) {
|
|
68
|
+
return await require('./library').getFormattingOptions(socket.uid);
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
Sockets.shouldQueue = async function (socket, data) {
|