toplevel comments pagination: initial commit

This commit is contained in:
Mario
2025-05-29 10:13:32 +00:00
parent bdcee46138
commit 20148d23f1
7 changed files with 119 additions and 35 deletions

View File

@@ -230,9 +230,8 @@ class ThreadItem {
$this->check_wall_to_wall();
$toplevel_comments_total = 0;
if($this->is_toplevel()) {
$toplevel_comments_total = $responses['comment']['count'] ?? 0;
$conv->comments_total = $responses['comment']['count'] ?? 0;
if((local_channel() && $conv->get_profile_owner() === local_channel()) || (local_channel() && App::$module === 'pubstream')) {
$star = [
@@ -351,12 +350,14 @@ class ThreadItem {
$contact = App::$contacts[$item['author_xchan']];
}
$load_more = '';
$load_more = false;
$load_more_title = '';
if ($toplevel_comments_total > $total_children) {
$comments_total_percent = 0;
if ($conv->comments_total > 3) {
// provide a load more comments button
$load_more = t('Load more');
$load_more_title = sprintf(t('Load more of %d replies'), $toplevel_comments_total);
$load_more = true;
$load_more_title = sprintf(t('Load the next batch of total %d replies'), $conv->comments_total);
$comments_total_percent = round(100 * 3 / $conv->comments_total);
}
$tmp_item = array(
@@ -433,6 +434,7 @@ class ThreadItem {
'share' => $share,
'embed' => $embed,
'rawmid' => $item['mid'],
'parent_mid' => $item['parent_mid'],
'plink' => get_plink($item),
'edpost' => $edpost,
'star' => ((feature_enabled($conv->get_profile_owner(),'star_posts') && ($item['item_type'] == ITEM_TYPE_POST)) ? $star : ''),
@@ -486,6 +488,8 @@ class ThreadItem {
'expand_comments' => $this->threaded ? t('show more') : t('show all'),
'load_more' => $load_more,
'load_more_title' => $load_more_title,
'comments_total' => $conv->comments_total,
'comments_total_percent' => $comments_total_percent
);
$arr = array('item' => $item, 'output' => $tmp_item);
@@ -496,7 +500,7 @@ class ThreadItem {
$result['children'] = array();
$nb_children = count($children);
$visible_comments = Config::Get('system', 'expanded_comments', 3);
$visible_comments = 3; // Config::Get('system', 'expanded_comments', 3);
if(($this->get_display_mode() === 'normal') && ($nb_children > 0)) {
foreach($children as $child) {

View File

@@ -25,6 +25,7 @@ class ThreadStream {
public $reload = '';
private $cipher = 'AES-128-CCM';
public $mid_uuid_map = [];
public $comments_total = 0;
// $prepared_item is for use by alternate conversation structures such as photos

View File

@@ -29,9 +29,15 @@ class Request extends Controller
{
$mid = $_GET['mid'];
$parent = intval($_GET['parent']);
$offset = null;
if ($_GET['verb'] === 'load') {
$offset = intval($_GET['offset']);
}
$module = strip_tags($_GET['module']);
$items = items_by_thr_parent($mid, $parent);
$items = items_by_thr_parent($mid, $parent, $offset);
xchan_query($items);
$items = fetch_post_tags($items,true);
@@ -44,7 +50,7 @@ class Request extends Controller
public function get() : string
{
if ($_GET['verb'] === 'comment') {
if (in_array($_GET['verb'], ['comment', 'load'])) {
return self::processSubthreadRequest();
}

View File

@@ -70,7 +70,7 @@ require_once('include/security.php');
define('PLATFORM_NAME', 'hubzilla');
define('STD_VERSION', '10.3.37');
define('STD_VERSION', '10.3.38');
define('ZOT_REVISION', '6.0');
define('DB_UPDATE_VERSION', 1263);

View File

@@ -5407,7 +5407,7 @@ function item_by_item_id(int $id, int $parent): array
* @param bool $blog_mode (optional) - if set to yes only the parent items will be returned
*/
function items_by_parent_ids(array $parents, array $thr_parents = [], string $permission_sql = '', bool $blog_mode = false): array
function items_by_parent_ids(array $parents, null|array $thr_parents = null, string $permission_sql = '', bool $blog_mode = false): array
{
if (!$parents) {
return [];
@@ -5416,9 +5416,11 @@ function items_by_parent_ids(array $parents, array $thr_parents = [], string $pe
$ids = ids_to_querystr($parents, 'item_id');
$thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
$item_normal_sql = item_normal();
$limit = 3;
$thr_parent_sql = (($thread_allow) ? " AND item.thr_parent = item.parent_mid " : '');
if ($thr_parents && $thread_allow) {
$limit = 100;
$thr_parent_str = stringify_array($thr_parents, true);
$thr_parent_sql = " AND item.thr_parent IN (" . protect_sprintf($thr_parent_str) . ") ";
}
@@ -5488,7 +5490,7 @@ function items_by_parent_ids(array $parents, array $thr_parents = [], string $pe
all_comments.*
FROM
all_comments
WHERE all_comments.rn <= 100
WHERE all_comments.rn <= $limit
),
final_selection AS (
@@ -5595,7 +5597,7 @@ function item_reaction_sql(string $ids, string $permission_sql = '', string $joi
* @param int $parent
*/
function items_by_thr_parent(string $mid, int $parent): array
function items_by_thr_parent(string $mid, int $parent, int|null $offset = null): array
{
if (!$mid && !$parent) {
return [];
@@ -5605,6 +5607,11 @@ function items_by_thr_parent(string $mid, int $parent): array
intval($parent)
);
$order_sql = "ORDER BY item.created";
if (isset($offset)) {
$order_sql = "ORDER BY item.created DESC LIMIT 3 OFFSET $offset";
}
$owner_uid = intval($parent_item[0]['uid']);
$item_normal_sql = item_normal($owner_uid);
@@ -5626,7 +5633,8 @@ function items_by_thr_parent(string $mid, int $parent): array
AND item.uid = %d
AND item.verb NOT IN ('Like', 'Dislike', 'Announce', 'Accept', 'Reject', 'TentativeAccept')
AND item.item_thread_top = 0
$item_normal_sql",
$item_normal_sql
$order_sql",
dbesc($mid),
intval($owner_uid)
);
@@ -5648,12 +5656,17 @@ function items_by_thr_parent(string $mid, int $parent): array
AND item.verb NOT IN ('Like', 'Dislike', 'Announce', 'Accept', 'Reject', 'TentativeAccept')
AND item.item_thread_top = 0
$sql_extra
$item_normal_sql",
$item_normal_sql
$order_sql",
dbesc($mid),
intval($owner_uid)
);
}
if (isset($offset)) {
$ret = array_reverse($ret);
}
return $ret;
}
@@ -5726,8 +5739,13 @@ function item_activity_xchans(string $mid, int $parent, string $verb): array
* @param array $item
*/
function get_recursive_thr_parents(array $item): array
function get_recursive_thr_parents(array $item): array|null
{
if ($item['id'] === $item['parent']) {
// This is a toplevel post, return null.
return null;
}
$thr_parents[] = $item['thr_parent'];
$mid = $item['thr_parent'];

View File

@@ -1293,6 +1293,53 @@ function justifyPhotosAjax(id) {
}
function request(id, mid, verb, parent, uuid) {
if (verb === 'load') {
const dots = document.getElementById('load-more-dots-' + parent);
dots.classList.add('jumping-dots');
const parent_sub = document.getElementById('wall-item-sub-thread-wrapper-' + parent);
const offset = parent_sub.children.length;
fetch('/request?offset=' + offset + '&verb=' + verb + '&mid=' + mid + '&parent=' + parent + '&module=' + module)
.then(response => response.json())
.then(obj => {
let parser = new DOMParser();
let doc = parser.parseFromString(obj.html, 'text/html');
let b64mids = [];
doc.querySelectorAll('.thread-wrapper').forEach(function (e) {
let data = JSON.parse(e.dataset.b64mids);
b64mids.push(...data);
});
imagesLoaded(doc.querySelectorAll('.wall-item-body img'), function () {
injectWithAnimation('wall-item-sub-thread-wrapper-' + parent, doc);
dots.classList.remove('jumping-dots');
const loadmore_progress = document.getElementById('load-more-progress-' + parent);
loadmore_progress.style.width = Math.round(100 * parent_sub.children.length / loadmore_progress.dataset.commentsTotal) + '%';
if (Number(parent_sub.children.length) === Number(loadmore_progress.dataset.commentsTotal)) {
const loadmore = document.getElementById('load-more-' + parent);
loadmore.remove();
}
updateRelativeTime('.autotime');
collapseHeight();
document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids }));
document.dispatchEvent(new Event('hz:sse_bs_counts'));
});
})
.catch(error => {
console.error('Error fetching data:', error);
});
return;
}
const loading = document.getElementById('like-rotator-' + id);
loading.style.display = 'block';
@@ -1327,7 +1374,7 @@ function request(id, mid, verb, parent, uuid) {
});
imagesLoaded(doc.querySelectorAll('.wall-item-body img'), function () {
injectWithAnimation('wall-item-sub-thread-wrapper-' + id, obj.html);
injectWithAnimation('wall-item-sub-thread-wrapper-' + id, doc, true);
updateRelativeTime('.autotime');
loading.style.display = 'none';
collapseHeight();
@@ -1376,30 +1423,30 @@ function request(id, mid, verb, parent, uuid) {
}
function injectWithAnimation(container, html) {
const target = document.getElementById(container);
target.innerHTML = html;
function injectWithAnimation(containerId, parsedDoc, overwrite = false) {
const container = document.getElementById(containerId);
if (!container) return;
target.animate([
{ opacity: 0, transform: 'translateY(-20px)' },
{ opacity: 1, transform: 'translateY(0)' }
], {
duration: 300,
easing: 'ease-out'
});
if (overwrite) {
container.innerHTML = '';
}
const newElements = Array.from(parsedDoc.body.children);
for (let i = newElements.length - 1; i >= 0; i--) {
const el = newElements[i].cloneNode(true);
container.insertBefore(el, container.firstChild);
// For children animation
Array.from(target.children).forEach((el, i) => {
el.animate([
{ opacity: 0, transform: 'scale(.7)' },
{ opacity: 1, transform: 'scale(1)' }
{ opacity: 0, transform: 'scale(.7) translateY(-20px)' },
{ opacity: 1, transform: 'scale(1) translateY(0)' }
], {
duration: 300,
delay: i * 50,
delay: (newElements.length - 1 - i) * 50,
fill: 'both',
easing: 'ease-out'
});
});
}
}
function stringToHexColor(str) {

View File

@@ -1,4 +1,4 @@
{{if $item.comment_firstcollapsed}}
{{if !$item.threaded && $item.comment_firstcollapsed}}
<div id="hide-comments-outer-{{$item.parent}}" class="hide-comments-outer fakelink small" onclick="showHideComments({{$item.id}});">
<i id="hide-comments-icon-{{$item.id}}" class="bi bi-chevron-down align-middle hide-comments-icon"></i> <span id="hide-comments-label-{{$item.id}}" class="hide-comments-label align-middle" data-expanded="{{$item.collapse_comments}}" data-collapsed="{{$item.expand_comments}}">{{$item.expand_comments}}</span>{{if !$item.threaded}}&nbsp;<span id="hide-comments-total-{{$item.id}}" class="hide-comments-label align-middle">{{$item.num_comments}}</span>{{/if}}
</div>
@@ -219,6 +219,14 @@
</div>
</div>
{{if $item.thread_level == 1}}
{{if $item.toplevel && $item.load_more && !$item.blog_mode}}
<div id="load-more-progress-wrapper-{{$item.id}}" class="progress " role="progressbar" aria-valuenow="{{$item.comments_total_percent}}" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
<div id="load-more-progress-{{$item.id}}" class="progress-bar bg-info" style="width: {{$item.comments_total_percent}}%; margin-left: auto; margin-right: auto;" data-comments-total="{{$item.comments_total}}"></div>
</div>
<div id="load-more-{{$item.id}}" class="text-center text-secondary cursor-pointer" title="{{$item.load_more_title}}" onclick="request(0, '{{$item.rawmid}}', 'load', {{$item.parent}}, ''); return false;">
<span id="load-more-dots-{{$item.id}}" class=""><span class="dot-1">-</span> <span class="dot-2">-</span> <span class="dot-3">-</span></span>
</div>
{{/if}}
<div id="wall-item-sub-thread-wrapper-{{$item.id}}" class="wall-item-sub-thread-wrapper">
{{foreach $item.children as $child}}
{{include file="{{$child.template}}" item=$child}}
@@ -238,6 +246,6 @@
</div>
{{/if}}
</div>
{{if $item.comment_lastcollapsed}}
{{if !$item.threaded && $item.comment_lastcollapsed}}
</div>
{{/if}}