Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 43 additions & 35 deletions library.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@


const nconf = nodebb.require('nconf');
const validator = require('validator');

const plugins = nodebb.require('./src/plugins');
const topics = nodebb.require('./src/topics');
Expand All @@ -11,7 +10,7 @@ const posts = nodebb.require('./src/posts');
const user = nodebb.require('./src/user');
const meta = nodebb.require('./src/meta');
const privileges = nodebb.require('./src/privileges');
const translator = nodebb.require('./src/translator');
const tx = nodebb.require('./src/translator');
const utils = nodebb.require('./src/utils');
const helpers = nodebb.require('./src/controllers/helpers');
const SocketPlugins = nodebb.require('./src/socket.io/plugins');
Expand Down Expand Up @@ -43,27 +42,20 @@ plugin.addAdminNavigation = async function (header) {
return header;
};

plugin.getFormattingOptions = async function () {
plugin.getFormattingOptions = async function (uid) {
const defaultVisibility = {
mobile: true,
desktop: true,
// use d-none d-lg-block, visible on desktop
// use d-block d-lg-none, visible on mobile
class: 'd-block',

// op or reply
main: true,
reply: true,
};
let payload = {
uid,
defaultVisibility,
options: [
{
name: 'tags',
title: '[[global:tags.tags]]',
className: 'fa fa-tags',
visibility: {
...defaultVisibility,
desktop: false,
},
},
{
name: 'zen',
title: '[[modules:composer.zen-mode]]',
Expand All @@ -72,16 +64,36 @@ plugin.getFormattingOptions = async function () {
},
],
};
if (parseInt(meta.config.allowTopicsThumbnail, 10) === 1) {
const [canUploadImage, canUploadFile] = await privileges.global.can(
['upload:post:image', 'upload:post:file'], uid
);
if (canUploadImage) {
if (meta.config.allowTopicsThumbnail) {
payload.options.push({
name: 'thumbs',
title: '[[topic:composer.thumb-title]]',
className: 'fa fa-address-card-o',
badge: true,
visibility: {
...defaultVisibility,
reply: false,
},
});
}
payload.options.push({
name: 'thumbs',
title: '[[topic:composer.thumb-title]]',
className: 'fa fa-address-card-o',
badge: true,
visibility: {
...defaultVisibility,
reply: false,
},
name: 'picture',
title: '[[modules:composer.upload-picture]]',
className: 'fa fa-file-image-o',
visibility: defaultVisibility,
});
}

if (canUploadFile) {
payload.options.push({
name: 'upload',
title: '[[modules:composer.upload-file]]',
className: 'fa fa-file-o',
visibility: defaultVisibility,
});
}

Expand Down Expand Up @@ -135,7 +147,7 @@ plugin.filterComposerBuild = async function (hookData) {
]),
user.isAdministrator(req.uid),
isModerator(req),
plugin.getFormattingOptions(),
plugin.getFormattingOptions(req.uid),
getTagWhitelist(req.query, req.uid),
privileges.global.get(req.uid),
canTag(req),
Expand All @@ -161,7 +173,8 @@ plugin.filterComposerBuild = async function (hookData) {
const cid = req.query.cid || '';
const topicTitle = topicData && topicData.title ?
topicData.title :
validator.escape(String(req.query.title || ''));
String(req.query.title || '');

return {
req: req,
res: res,
Expand All @@ -180,12 +193,6 @@ plugin.filterComposerBuild = async function (hookData) {
// can't use title property as that is used for page title
topicTitle: topicTitle,
titleLength: topicTitle ? topicTitle.length : 0,
titleLabel: translator.compile(
isEditing ?
'topic:composer.editing-in' :
'topic:composer.replying-to',
`"${topicTitle}"`
),

topic: topicData,
thumb: topicData ? topicData.thumb : '',
Expand Down Expand Up @@ -231,7 +238,7 @@ async function checkPrivileges(req, res) {

function generateDiscardRoute(req, topicData) {
if (req.query.cid) {
return `${nconf.get('relative_path')}/category/${validator.escape(String(req.query.cid))}`;
return `${nconf.get('relative_path')}/category/${req.query.cid}`;
} else if ((req.query.tid || req.query.pid)) {
if (topicData) {
return `${nconf.get('relative_path')}/topic/${topicData.slug}`;
Expand All @@ -242,18 +249,19 @@ function generateDiscardRoute(req, topicData) {

async function generateBody(req, postData) {
let body;
console.log('generate body', req.query, postData);
// Quoted reply
if (req.query.toPid && parseInt(req.query.quoted, 10) === 1 && postData) {
const username = await user.getUserField(postData.uid, 'username');
const translated = await translator.translate(`[[modules:composer.user-said, ${username}]]`);
const translated = await tx.translateKey('modules:composer.user-said', [tx.escape(username)]);
body = `${translated}\n` +
`> ${postData ? `${postData.content.replace(/\n/g, '\n> ')}\n\n` : ''}`;
} else if (req.query.body || req.query.content) {
body = validator.escape(String(req.query.body || req.query.content));
body = req.query.body || req.query.content;
} else {
body = postData ? postData.content : '';
}
return translator.escape(body);
return body;
}

async function getPostData(req) {
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@
},
"readmeFilename": "README.md",
"nbbpm": {
"compatibility": "^4.13.0"
"compatibility": "^4.14.0"
},
"dependencies": {
"screenfull": "^5.0.2",
"validator": "^13.7.0"
"screenfull": "^5.0.2"
},
"devDependencies": {
"eslint": "^10.0.0",
Expand Down
64 changes: 28 additions & 36 deletions static/lib/composer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ define('composer', [
'scrollStop',
'topicThumbs',
'api',
'bootbox',
'modals',
'alerts',
'hooks',
'messages',
'search',
'screenfull',
], function (taskbar, translator, uploads, formatting, drafts, tags,
], function (taskbar, tx, uploads, formatting, drafts, tags,
categoryList, preview, resize, autocomplete, scheduler, postQueue, scrollStop,
topicThumbs, api, bootbox, alerts, hooks, messagesModule, search, screenfull) {
topicThumbs, api, modals, alerts, hooks, messagesModule, search, screenfull) {
const composer = {
active: undefined,
posts: {},
Expand Down Expand Up @@ -52,7 +52,7 @@ define('composer', [
return;
}

composer.discardConfirm = bootbox.confirm('[[modules:composer.discard]]', function (confirm) {
composer.discardConfirm = modals.confirm('[[modules:composer.discard]]', function (confirm) {
if (confirm) {
composer.discard(composer.active);
} else {
Expand Down Expand Up @@ -106,7 +106,9 @@ define('composer', [

// Find a match
for (const uuid of Object.keys(composer.posts)) {
if (composer.posts[uuid].hasOwnProperty(type) && id === String(composer.posts[uuid][type])) {
const openPost = composer.posts[uuid];
// make sure dom element exists too
if (openPost.hasOwnProperty(type) && id === String(openPost[type]) && $(`.composer[data-uuid="${uuid}"]`).length) {
return uuid;
}
}
Expand All @@ -124,7 +126,7 @@ define('composer', [
if (existingUUID) {
taskbar.updateActive(existingUUID);
if (post.body && !post.fromDraft) {
const postContainer = $('.composer[data-uuid="' + existingUUID + '"]');
const postContainer = $(`.composer[data-uuid="${existingUUID}"]`);
const bodyEl = postContainer.find('textarea');
const prevText = bodyEl.val();
composer.posts[existingUUID].body = (prevText.length ? prevText + '\n\n' : '') + post.body;
Expand All @@ -136,9 +138,9 @@ define('composer', [
const uuid = utils.generateUUID();
let actionText = '[[topic:composer.new-topic]]';
if (post.action === 'posts.reply') {
actionText = translator.compile('topic:composer.replying-to', `"${post.title}"`);
actionText = tx.compile('topic:composer.replying-to', `"${tx.escape(post.title)}"`);
} else if (post.action === 'posts.edit') {
actionText = translator.compile('topic:composer.editing-in', `"${post.title}"`);
actionText = tx.compile('topic:composer.editing-in', `"${tx.escape(post.title)}"`);
}

taskbar.push('composer', uuid, {
Expand Down Expand Up @@ -203,7 +205,7 @@ define('composer', [
push(pushData);
};

composer.addQuote = function (data) {
composer.addQuote = async function (data) {
// tid, toPid, selectedPid, title, username, text, uuid
data.uuid = data.uuid || composer.active;

Expand All @@ -224,16 +226,18 @@ define('composer', [
const postHref = `${config.relative_path}/post/${encodeURIComponent(data.selectedPid || data.toPid)}`;
const topicLink = `[${escapedTitle}](${postHref})`;

const quoteKey = useTopicLink ?
`> ${translator.compile('modules:composer.user-said-in', data.username, topicLink)}\n>\n` :
`> ${translator.compile('modules:composer.user-said', data.username, postHref)}\n>\n`;
const txQuoteText = useTopicLink ?
await tx.translateKey('modules:composer.user-said-in', [tx.escape(data.username), topicLink]) :
await tx.translateKey('modules:composer.user-said', [tx.escape(data.username), postHref]);

const quoteText = `> ${txQuoteText}\n>\n`;

if (data.uuid === undefined) {
composer.newReply({
tid: data.tid,
toPid: data.toPid,
title: data.title,
body: quoteKey + data.body,
body: quoteText + data.body,
});
return;
} else if (data.uuid !== composer.active) {
Expand All @@ -245,25 +249,22 @@ define('composer', [
const bodyEl = postContainer.find('textarea');
const prevText = bodyEl.val();

translator.translate(quoteKey, config.defaultLang, function (translated) {
composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + data.body;
bodyEl.val(composer.posts[data.uuid].body);
focusElements(postContainer);
preview.render(postContainer);
});
composer.posts[data.uuid].body = (prevText.length ? prevText + '\n\n' : '') + quoteText + data.body;
bodyEl.val(composer.posts[data.uuid].body);
focusElements(postContainer);
preview.render(postContainer);
};

composer.newReply = async function (data) {
const translated = await translator.translate(data.body, config.defaultLang);
let pushData = {
fromDraft: data.fromDraft,
save_id: data.save_id,
action: 'posts.reply',
tid: data.tid,
toPid: data.toPid,
title: data.title,
body: translated,
modified: !!(translated && translated.length),
body: data.body,
modified: !!(data.body && data.body.length),
isMain: false,
};
({ pushData } = await hooks.fire('filter:composer.reply.push', {
Expand Down Expand Up @@ -370,7 +371,7 @@ define('composer', [
formatting.exitFullscreen();

const btn = $(this).prop('disabled', true);
bootbox.confirm('[[modules:composer.discard]]', function (confirm) {
modals.confirm('[[modules:composer.discard]]', function (confirm) {
if (confirm) {
composer.discard(post_uuid);
removeComposerHistory();
Expand Down Expand Up @@ -446,15 +447,10 @@ define('composer', [
const topicTemplate = isTopic && postData.category ? postData.category.topicTemplate : '';

let data = {
action: postData.action,
topicTitle: postData.title,
titleLength: postData.title.length,
titleLabel: translator.compile(
isEditing ?
'topic:composer.editing-in' :
'topic:composer.replying-to',
`"${postData.title}"`
),
body: utils.escapeHTML(translator.escape(postData.body) || topicTemplate),
body: postData.body || topicTemplate,
mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm',
resizable: true,
thumb: postData.thumb,
Expand Down Expand Up @@ -492,7 +488,7 @@ define('composer', [
app.toggleNavbar(false);
}

postData.mobile = composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm';
postData.mobile = data.mobile;

({ postData, createData: data } = await hooks.fire('filter:composer.create', {
postData: postData,
Expand All @@ -505,10 +501,6 @@ define('composer', [
}
composerTemplate = $(composerTemplate);

composerTemplate.find('.title, textarea.write').each(function () {
$(this).text(translator.unescape($(this).text()));
});

composerTemplate.attr('data-uuid', post_uuid);

$(document.body).append(composerTemplate);
Expand Down Expand Up @@ -592,7 +584,7 @@ define('composer', [
helpBtn.on('click', async function () {
const html = await socket.emit('plugins.composer.renderHelp');
if (html && html.length > 0) {
bootbox.dialog({
modals.dialog({
size: 'large',
message: html,
onEscape: true,
Expand Down
12 changes: 6 additions & 6 deletions static/lib/composer/drafts.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ define('composer/drafts', ['api', 'hooks'], function (api, hooks) {
}
$(window).trigger('action:composer.drafts.get', {
save_id: save_id,
draft: draft,
draft: { ...draft },
storage: storage,
});
return draft;
} catch (e) {
console.warn(`[composer/drafts] Could not get draft ${save_id}, removing`);
drafts.removeFromDraftList('available');
drafts.removeFromDraftList('open');
drafts.removeFromDraftList('available', save_id);
drafts.removeFromDraftList('open', save_id);
return null;
}
};
Expand Down Expand Up @@ -263,8 +263,8 @@ define('composer/drafts', ['api', 'hooks'], function (api, hooks) {
fromDraft: true,
save_id: draft.save_id,
cid: draft.cid,
handle: app.user && app.user.uid ? undefined : utils.escapeHTML(draft.handle),
title: utils.escapeHTML(draft.title),
handle: app.user && app.user.uid ? undefined : draft.handle,
title: draft.title,
body: draft.text,
tags: String(draft.tags || '').split(','),
thumbs: draft.thumbs || [],
Expand All @@ -288,7 +288,7 @@ define('composer/drafts', ['api', 'hooks'], function (api, hooks) {
fromDraft: true,
save_id: draft.save_id,
pid: draft.pid,
title: draft.title ? utils.escapeHTML(draft.title) : undefined,
title: draft.title || undefined,
body: draft.text,
thumbs: draft.thumbs || [],
};
Expand Down
Loading