Files
NodeBB/public/src/client/world.js
2026-03-11 10:11:32 -04:00

299 lines
9.6 KiB
JavaScript

'use strict';
define('forum/world', [
'forum/infinitescroll', 'search', 'sort', 'hooks',
'alerts', 'api', 'bootbox', 'helpers', 'forum/category/tools',
'translator', 'quickreply', 'handleBack',
], function (infinitescroll, search, sort, hooks,
alerts, api, bootbox, helpers, categoryTools,
translator, quickreply, handleBack) {
const World = {};
World.init = function () {
app.enterRoom('world');
quickreply.init({
route: '/topics',
body: {
cid: ajaxify.data.cid,
},
});
sort.handleSort('categoryTopicSort', 'world');
handleImages();
handleButtons();
handleHelp();
handleShowMoreButtons();
categoryTools.init($('#world-feed'));
socket.on('event:new_post', onNewPost);
$(window).one('action:ajaxify.start', function () {
categoryTools.removeListeners();
socket.removeListener('event:new_post', onNewPost);
});
// Add label to sort
const sortLabelEl = document.getElementById('sort-label');
const sortOptionsEl = document.getElementById('sort-options');
if (sortLabelEl && sortOptionsEl) {
const params = new URLSearchParams(window.location.search);
if (params.get('sort') === 'popular') {
translator.translate(`[[world:popular-${params.get('term')}]]`, function (translated) {
sortLabelEl.innerText = translated;
});
} else {
translator.translate('[[world:latest]]', function (translated) {
sortLabelEl.innerText = translated;
});
}
}
handleBack.init((after, handleBackCb) => {
loadTopicsAfter(after, undefined, 1, (payload, callback) => {
app.parseAndTranslate(ajaxify.data.template.name, 'posts', payload, function (html) {
const listEl = document.getElementById('world-feed');
$(listEl).append(html);
html.find('.timeago').timeago();
handleImages();
handleShowMoreButtons();
callback();
handleBackCb();
});
});
}, { container: '#world-feed' });
search.enableQuickSearch({
searchElements: {
inputEl: $('[component="category-search"]'),
resultEl: $('.world .quick-search-container'),
},
searchOptions: {
in: 'categories',
},
dropdown: {
maxWidth: '400px',
maxHeight: '350px',
},
hideOnNoMatches: false,
});
if (!config.usePagination) {
infinitescroll.init((direction) => {
const posts = Array.from(document.querySelectorAll('[component="category/topic"]'));
if (!posts.length) {
return;
}
const afterEl = direction > 0 ? posts.pop() : posts.shift();
const index = (parseInt(afterEl.getAttribute('data-index'), 10) || 0) + (direction > 0 ? 1 : 0);
const after = afterEl.getAttribute('data-tid');
if (index < config.topicsPerPage) {
return;
}
loadTopicsAfter(index, after, direction, (payload, callback) => {
app.parseAndTranslate(ajaxify.data.template.name, 'posts', payload, function (html) {
const listEl = document.getElementById('world-feed');
$(listEl)[direction === -1 ? 'prepend' : 'append'](html);
html.find('.timeago').timeago();
handleImages();
handleShowMoreButtons();
callback();
});
});
});
}
ajaxify.data.categories.forEach(function (category) {
handleIgnoreWatch(category.cid);
});
hooks.fire('action:topics.loaded', { topics: ajaxify.data.topics });
hooks.fire('action:category.loaded', { cid: ajaxify.data.cid });
};
function calculateNextPage(after, direction) {
return Math.floor(after / config.topicsPerPage) + (direction > 0 ? 1 : 0);
}
function loadTopicsAfter(index, after, direction, callback) {
callback = callback || function () {};
const query = utils.params();
query.page = calculateNextPage(index, direction);
query.after = after;
infinitescroll.loadMoreXhr(query, callback);
}
function handleButtons() {
const feedEl = $('#world-feed');
feedEl.on('click', '[data-action="bookmark"]', function () {
const $this = $(this);
const isBookmarked = $this.attr('data-bookmarked') === 'true';
const pid = $this.attr('data-pid');
const bookmarkCount = parseInt($this.attr('data-bookmarks'), 10);
const method = isBookmarked ? 'del' : 'put';
api[method](`/posts/${pid}/bookmark`, undefined, function (err) {
if (err) {
return alerts.error(err);
}
const type = isBookmarked ? 'unbookmark' : 'bookmark';
const newBookmarkCount = bookmarkCount + (isBookmarked ? -1 : 1);
$this.find('[component="bookmark-count"]').text(
helpers.humanReadableNumber(newBookmarkCount)
);
$this.attr('data-bookmarks', newBookmarkCount);
$this.attr('data-bookmarked', isBookmarked ? 'false' : 'true');
$this.find('i').toggleClass('fa text-primary', !isBookmarked)
.toggleClass('fa-regular text-muted', isBookmarked);
hooks.fire(`action:post.${type}`, { pid: pid });
});
});
feedEl.on('click', '[data-action="upvote"]', function () {
const $this = $(this);
const isUpvoted = $this.attr('data-upvoted') === 'true';
const pid = $this.attr('data-pid');
const upvoteCount = parseInt($this.attr('data-upvotes'), 10);
const method = isUpvoted ? 'del' : 'put';
const delta = 1;
api[method](`/posts/${pid}/vote`, { delta }, function (err) {
if (err) {
return alerts.error(err);
}
const newUpvoteCount = upvoteCount + (isUpvoted ? -1 : 1);
$this.find('[component="upvote-count"]').text(
helpers.humanReadableNumber(newUpvoteCount)
);
$this.attr('data-upvotes', newUpvoteCount);
$this.attr('data-upvoted', isUpvoted ? 'false' : 'true');
$this.find('i').toggleClass('fa text-danger', !isUpvoted)
.toggleClass('fa-regular text-muted', isUpvoted);
hooks.fire('action:post.toggleVote', {
pid: pid,
delta: delta,
unvote: method === 'del',
});
});
});
feedEl.on('click', '[data-action="reply"]', function () {
const $this = $(this);
const isMain = $this.attr('data-is-main') === 'true';
app.newReply({
tid: $this.attr('data-tid'),
pid: !isMain ? $this.attr('data-pid') : undefined,
}).catch(alerts.error);
});
}
function handleHelp() {
const trigger = document.getElementById('world-help');
if (!trigger) {
return;
}
const content = [
'<p class="lead">[[world:help.intro]]</p>',
'<p>[[world:help.fediverse]]</p>',
'<p>[[world:help.build]]</p>',
'<p>[[world:help.federating]]</p>',
'<p>[[world:help.next-generation]]</p>',
];
trigger.addEventListener('click', () => {
bootbox.dialog({
title: '[[world:help.title]]',
message: content.join('\n'),
size: 'large',
});
});
}
function handleIgnoreWatch(cid) {
const category = $('[data-cid="' + cid + '"]');
category.find(
'[component="category/watching"], [component="category/tracking"], [component="category/ignoring"], [component="category/notwatching"]'
).on('click', async (e) => {
const state = e.currentTarget.getAttribute('data-state');
const { uid } = ajaxify.data;
const { modified } = await api.put(`/categories/${encodeURIComponent(cid)}/watch`, { state, uid });
updateDropdowns(modified, state);
alerts.success('[[category:' + state + '.message]]');
});
}
function handleImages() {
$('[component="post/content"] img:not(.not-responsive)').addClass('img-fluid');
}
function handleShowMoreButtons() {
const feedEl = document.getElementById('world-feed');
if (!feedEl) {
return;
}
feedEl.querySelectorAll('[component="post/content"]').forEach((el) => {
const initted = el.getAttribute('data-showmore');
if (parseInt(initted, 10) === 1) {
return;
}
if (el.clientHeight < el.scrollHeight - 1) {
el.parentNode.querySelector('[component="show/more"]').classList.remove('hidden');
el.classList.toggle('clamp-fade-6', true);
}
el.setAttribute('data-showmore', '1');
});
if (parseInt(feedEl.getAttribute('data-showmore'), 10) !== 1) {
feedEl.addEventListener('click', (e) => {
const subselector = e.target.closest('[component="show/more"]');
if (subselector) {
const postContent = subselector.closest('.post-body').querySelector('[component="post/content"]');
const isShowingMore = parseInt(subselector.getAttribute('ismore'), 10) === 1;
postContent.classList.toggle('line-clamp-6', isShowingMore);
postContent.classList.toggle('clamp-fade-6', isShowingMore);
$(subselector).translateText(isShowingMore ? '[[world:see-more]]' : '[[world:see-less]]');
subselector.setAttribute('ismore', isShowingMore ? 0 : 1);
}
});
feedEl.setAttribute('data-showmore', '1');
}
}
function updateDropdowns(modified_cids, state) {
modified_cids.forEach(function (cid) {
const category = $('[data-cid="' + cid + '"]');
category.find('[component="category/watching/menu"]').toggleClass('hidden', state !== 'watching');
category.find('[component="category/watching/check"]').toggleClass('fa-check', state === 'watching');
category.find('[component="category/tracking/menu"]').toggleClass('hidden', state !== 'tracking');
category.find('[component="category/tracking/check"]').toggleClass('fa-check', state === 'tracking');
category.find('[component="category/notwatching/menu"]').toggleClass('hidden', state !== 'notwatching');
category.find('[component="category/notwatching/check"]').toggleClass('fa-check', state === 'notwatching');
category.find('[component="category/ignoring/menu"]').toggleClass('hidden', state !== 'ignoring');
category.find('[component="category/ignoring/check"]').toggleClass('fa-check', state === 'ignoring');
});
}
async function onNewPost({ posts }) {
const feedEl = document.getElementById('world-feed');
const html = await app.parseAndTranslate('world', 'posts', { posts });
if (!feedEl || !html) {
return;
}
feedEl.prepend(...html);
handleImages();
handleShowMoreButtons();
}
return World;
});