Skip to content
Merged
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
11 changes: 10 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ jobs:
with:
useLockFile: false

- name: Install Anonymous Button Plugin
run: npm install ./nodebb-plugin-anonymous-button

- name: Setup on Redis
env:
SETUP: >-
Expand All @@ -79,6 +82,12 @@ jobs:
run: |
node app --setup="${SETUP}" --ci="${CI}"

- name: Activate Anonymous Button Plugin
run: ./nodebb activate nodebb-plugin-anonymous-button

- name: Build NodeBB
run: ./nodebb build

- name: Run ESLint
run: npm run lint

Expand All @@ -89,4 +98,4 @@ jobs:
run: npm run coverage

- name: Test coverage
uses: coverallsapp/github-action@v2
uses: coverallsapp/github-action@v2
8 changes: 8 additions & 0 deletions nodebb-plugin-anonymous-button/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "nodebb-plugin-anonymous-button",
"version": "0.1.0",
"main": "library.js",
"nbbpm": {
"compatibility": "^4.1.0"
}
}
9 changes: 9 additions & 0 deletions nodebb-plugin-anonymous-button/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "nodebb-plugin-anonymous-button",
"name": "Anonymous Button",
"description": "Adds an anonymous posting button for the user.",
"library": "",
"scripts": ["public/js/anonymous-button.js"],
"hooks": []
}

Binary file added nodebb-plugin-anonymous-button/public/anon.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
210 changes: 210 additions & 0 deletions nodebb-plugin-anonymous-button/public/js/anonymous-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/* global $, require, app, socket */
'use strict';

(function () {
let isAnonymous = false;

require(['hooks', 'api'], function (hooks, api) {

// the floating button
function initButton() {
if ($('#anon-toggle-floating').length) {
const $existingBtn = $('#anon-toggle-floating');
$existingBtn.toggleClass('btn-success', isAnonymous)
.toggleClass('btn-secondary', !isAnonymous);
return;
}

const $btn = $(`
<button id="anon-toggle-floating"
class="btn btn-sm btn-secondary"
title="Toggle Anonymous Posting"
style="position:fixed;bottom:20px;right:20px;z-index:2000;">
<i class="fa fa-user-secret"></i> Anon
</button>
`);

$btn.on('click', function () {
isAnonymous = !isAnonymous;
$btn.toggleClass('btn-success', isAnonymous)
.toggleClass('btn-secondary', !isAnonymous);
});

$('body').append($btn);
}

hooks.on('action:ajaxify.end', initButton);

// check composer data before submission
hooks.on('filter:composer.submit', function (data) {

if (isAnonymous) {
data.composerData = data.composerData || {};
data.composerData.isAnonymous = 1;

}

return data;
});

// hooks to make sure the flag is passed through
hooks.on('action:composer.submit', function (data) {

if (isAnonymous) {
if (data.composerData) {
data.composerData.isAnonymous = 1;
}
if (data.postData) {
data.postData.isAnonymous = 1;
}
}
});

// rendering anonymous posts
function makePostAnonymous($post) {

// replaces the entire author section with Anonymous
$post.find('[component="post/author"]').each(function() {
$(this).html('<div>Anonymous</div>');
});

$post.find('[itemprop="author"]').each(function() {
$(this).html('<div>Anonymous</div>');
});

$post.find('.username-field').each(function() {
$(this).html('<div>Anonymous</div>');
});

$post.find('a[href*="/user/"]').each(function() {
const $link = $(this);
if ($link.text().trim() !== 'Anonymous') {
$link.replaceWith('<div class="anonymous-user">Anonymous</div>');
}
});

// replace icon with anon.png image
$post.find('[component="user/picture"]').each(function() {
$(this).html('<div class="anonymous-avatar"><img src="nodebb-plugin-anonymous-button/public/anon.jpg" alt="Anonymous" style="width:46px;height:46px;"></div>');
});

$post.find('.avatar').each(function() {
$(this).replaceWith('<div class="anonymous-avatar"><img src="nodebb-plugin-anonymous-button/public/anon.jpg" alt="Anonymous" style="width:46px;height:46px;"></div>');
});

$post.find('img.avatar').each(function() {
$(this).replaceWith('<div class="anonymous-avatar"><img src="nodebb-plugin-anonymous-button/public/anon.jpg" alt="Anonymous" style="width:46px;height:46px;"></div>');
});

// anonymous image
const $authorSection = $post.find('[component="post/author"]').first().parent();
if ($authorSection.length && !$post.find('.anon-badge').length) {
$authorSection.append('<div class="anon-badge"><span class="badge bg-secondary ms-1"><img src="nodebb-plugin-anonymous-button/public/anon.jpg" alt="Anonymous" style="width:20px;height:20px;vertical-align:middle;margin-right:4px;"> Anonymous</span></div>');
}

// mark the post as done
$post.attr('data-anonymous-processed', 'true');

}

// check new posts being added
hooks.on('action:posts.loaded', function (data) {

if (data && data.posts) {
data.posts.forEach(function (post) {


if (post.isAnonymous === 1 || post.isAnonymous === true || post.isAnonymous === '1') {
const $post = $('[component="post"][data-pid="' + post.pid + '"]');
if ($post.length) {
makePostAnonymous($post);
}
}
});
}

// Also scan all posts on the page as backup
$('[component="post"]').each(function() {
const $post = $(this);
const pid = $post.attr('data-pid');

if (pid && data && data.posts) {
const post = data.posts.find(p => p.pid == pid);
if (post && (post.isAnonymous === 1 || post.isAnonymous === true || post.isAnonymous === '1')) {
makePostAnonymous($post);
}
}
});
});

// newly created posts immediately
hooks.on('action:posts.post-added', function (data) {


if (data && data.post) {
const post = data.post;
const pid = post.pid;


// check if this was an anonymous post
if (post.isAnonymous === 1 || post.isAnonymous === true || post.isAnonymous === '1') {
const $post = $('[component="post"][data-pid="' + pid + '"]');
if ($post.length) {
makePostAnonymous($post);
}
}
}
});

// check on topic load
hooks.on('action:topic.loaded', function (data) {

// check if data has posts array with isAnonymous info
if (data && data.posts) {
data.posts.forEach(function(post) {
if (post.isAnonymous === 1 || post.isAnonymous === true || post.isAnonymous === '1') {
const $post = $('[component="post"][data-pid="' + post.pid + '"]');
if ($post.length) {
makePostAnonymous($post);
}
}
});
}

$('[component="post"]').each(function () {
const $post = $(this);
const pid = $post.attr('data-pid');

if (pid) {
socket.emit('posts.getPost', pid, function (err, postData) {
if (!err && postData && (postData.isAnonymous === 1 || postData.isAnonymous === true || postData.isAnonymous === '1')) {
makePostAnonymous($post);
}
});
}
});
});

// post is submitted,check that it was saved
hooks.on('action:composer.submitted', function (data) {

if (data && data.data && data.data.pid) {
const pid = data.data.pid;

// verify the post was saved with isAnonymous flag
setTimeout(function () {
socket.emit('posts.getPost', pid, function (err, postData) {
if (err) {
return;
}
});
}, 500);
}

// reset the anonymous mode
isAnonymous = false;
$('#anon-toggle-floating').removeClass('btn-success').addClass('btn-secondary');
});

});
})();
2 changes: 1 addition & 1 deletion src/api/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,4 +668,4 @@ async function sendQueueNotification(type, targetUid, path, notificationText) {
}
const notifObj = await notifications.create(notifData);
await notifications.push(notifObj, [targetUid]);
}
}
2 changes: 1 addition & 1 deletion src/api/topics.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,4 @@ topicsAPI.move = async (caller, { tid, cid }) => {
}, { batch: 10 });

await categories.onTopicsMoved(cids);
};
};
20 changes: 17 additions & 3 deletions src/posts/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ const utils = require('../utils');
module.exports = function (Posts) {
Posts.create = async function (data) {
// This is an internal method, consider using Topics.reply instead
const { uid, tid, _activitypub, sourceContent } = data;
const { uid, tid, _activitypub, sourceContent} = data;
const content = data.content.toString();
const timestamp = data.timestamp || Date.now();
const isMain = data.isMain || false;
const isAnonymousFlag = (
data.isAnonymous === true ||
data.isAnonymous === 'true' ||
data.isAnonymous === 1 ||
data.isAnonymous === '1'
) ? 1 : 0;


if (!uid && parseInt(uid, 10) !== 0) {
throw new Error('[[error:invalid-uid]]');
Expand All @@ -28,8 +35,15 @@ module.exports = function (Posts) {
}

const pid = data.pid || await db.incrObjectField('global', 'nextPid');
let postData = { pid, uid, tid, content, sourceContent, timestamp };

let postData = {
pid,
uid,
tid,
content,
sourceContent,
timestamp,
isAnonymous: isAnonymousFlag,
};
if (data.toPid) {
postData.toPid = data.toPid;
}
Expand Down
2 changes: 1 addition & 1 deletion src/posts/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const utils = require('../utils');
const intFields = [
'uid', 'pid', 'tid', 'deleted', 'timestamp',
'upvotes', 'downvotes', 'deleterUid', 'edited',
'replies', 'bookmarks', 'announces',
'replies', 'bookmarks', 'announces', 'isAnonymous',
];

module.exports = function (Posts) {
Expand Down
Loading