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
74 changes: 41 additions & 33 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 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}]]`);
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
41 changes: 16 additions & 25 deletions static/lib/composer.js
Original file line number Diff line number Diff line change
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 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 @@ -227,13 +229,14 @@ define('composer', [
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 quoteText = await translator.translateKey(quoteKey, config.defaultLang);

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 +248,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 @@ -446,15 +446,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 +487,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 +500,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
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
4 changes: 3 additions & 1 deletion static/templates/compose.tpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div component="composer" class="composer pb-3 h-100 {{{ if resizable }}} resizable{{{ end }}}{{{ if !isTopicOrMain }}} reply{{{ end }}}"{{{ if !disabled }}} style="visibility: inherit;"{{{ end }}}>
<div class="composer-container d-flex flex-column gap-1 h-100">
<div class="composer-container d-flex flex-column gap-1 h-100 position-relative">
<form id="compose-form" method="post">
{{{ if pid }}}
<input type="hidden" name="pid" value="{pid}" />
Expand All @@ -23,5 +23,7 @@
{{{ if isTopicOrMain }}}
<!-- IMPORT partials/composer-tags.tpl -->
{{{ end }}}

<div class="imagedrop"><div>[[topic:composer.drag-and-drop-images]]</div></div>
</div>
</div>
25 changes: 2 additions & 23 deletions static/templates/composer.tpl
Original file line number Diff line number Diff line change
@@ -1,27 +1,6 @@
<div component="composer" class="composer {{{ if resizable }}} resizable{{{ end }}}{{{ if !isTopicOrMain }}} reply{{{ end }}}">
<div class="composer-container d-flex flex-column gap-1 h-100">
<!-- mobile header -->
<nav class="navbar fixed-top mobile-navbar text-bg-primary d-flex d-md-none flex-nowrap gap-1 px-1">
<div class="btn-group">
<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>
<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>
</div>
{{{ if isTopic }}}
<div class="flex-1" style="min-width: 0px;">
<!-- IMPORT partials/category/selector-dropdown-left.tpl -->
</div>
{{{ end }}}
{{{ if !isTopicOrMain }}}
<h4 class="title text-center text-bg-primary text-truncate fs-6 mb-0 px-2">{titleLabel}</h4>
{{{ end }}}

<div class="d-flex gap-1 flex-nowrap">
<button class="btn btn-sm btn-primary display-scheduler fs-5 {{{ if !canSchedule }}} hidden{{{ end }}}">
<i class="fa fa-fw fa-clock-o"></i>
</button>
<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>
</div>
</nav>
<div class="composer-container d-flex flex-column gap-1 h-100 position-relative">
<!-- IMPORT partials/composer-mobile-header.tpl -->
<div class="p-2 d-flex flex-column gap-1 h-100">
<!-- IMPORT partials/composer-title-container.tpl -->

Expand Down
Loading