From 6669496dba7b081799fb0780dd95611e6be53ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 4 Dec 2020 11:56:10 -0500 Subject: [PATCH] Navigator (#9049) * feat: navigator changes * fix: remove extra code * feat: add lang keys --- public/language/en-GB/topic.json | 4 +- public/src/client/topic.js | 9 +- public/src/modules/navigator.js | 231 +++++++++++++++++++++++-------- src/socket.io/posts.js | 6 +- 4 files changed, 187 insertions(+), 63 deletions(-) diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json index 05b89bb4fc..3de03e6293 100644 --- a/public/language/en-GB/topic.json +++ b/public/language/en-GB/topic.json @@ -180,5 +180,7 @@ "diffs.post-restored": "Post successfully restored to earlier revision", "timeago_later": "%1 later", - "timeago_earlier": "%1 earlier" + "timeago_earlier": "%1 earlier", + "first-post": "First post", + "last-post": "Last post" } diff --git a/public/src/client/topic.js b/public/src/client/topic.js index c54c631c5a..0a5b47fee6 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -45,6 +45,8 @@ define('forum/topic', [ posts.onTopicPageLoad(components.get('post')); + navigator.init('[component="post"]', ajaxify.data.postcount, Topic.toTop, Topic.toBottom, Topic.navigatorCallback); + postTools.init(tid); threadTools.init(tid, $('.topic')); events.init(); @@ -60,7 +62,7 @@ define('forum/topic', [ addDropupHandler(); addRepliesHandler(); - navigator.init('[component="post"]', ajaxify.data.postcount, Topic.toTop, Topic.toBottom, Topic.navigatorCallback); + handleBookmark(tid); @@ -213,12 +215,12 @@ define('forum/topic', [ } var newUrl = 'topic/' + ajaxify.data.slug + (index > 1 ? ('/' + index) : ''); - if (newUrl !== currentUrl) { if (Topic.replaceURLTimeout) { clearTimeout(Topic.replaceURLTimeout); + Topic.replaceURLTimeout = 0; } - + currentUrl = newUrl; Topic.replaceURLTimeout = setTimeout(function () { if (index >= elementCount && app.user.uid) { socket.emit('topics.markAsRead', [ajaxify.data.tid]); @@ -237,7 +239,6 @@ define('forum/topic', [ url: newUrl + search, }, null, window.location.protocol + '//' + window.location.host + config.relative_path + '/' + newUrl + search); } - currentUrl = newUrl; }, 500); } }; diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 97050a59ce..956fc96da4 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -2,15 +2,14 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, components) { var navigator = {}; - var index = 1; + var index = 0; var count = 0; var navigatorUpdateTimeoutId; - var touchTooltipEl; - var touchIntervalId; + var renderPostIntervalId; var touchX; var touchY; - var touchIndex; + var renderPostIndex; var isNavigating = false; var firstMove = true; @@ -20,13 +19,14 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co var paginationTextEl = paginationBlockEl.find('.pagination-text'); var paginationBlockMeterEl = paginationBlockEl.find('meter'); var paginationBlockProgressEl = paginationBlockEl.find('.progress-bar'); + var thumb; $(window).on('action:ajaxify.start', function () { $(window).off('keydown', onKeyDown); }); navigator.init = function (selector, count, toTop, toBottom, callback) { - index = 1; + index = 0; navigator.selector = selector; navigator.callback = callback; navigator.toTop = toTop || function () {}; @@ -37,6 +37,8 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co paginationBlockMeterEl = paginationBlockEl.find('meter'); paginationBlockProgressEl = paginationBlockEl.find('.progress-bar'); + thumb = $('.scroller-thumb'); + $(window).off('scroll', navigator.delayedUpdate).on('scroll', navigator.delayedUpdate); paginationBlockEl.find('.dropdown-menu').off('click').on('click', function (e) { @@ -69,76 +71,184 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co } }); - $('.pagination-block.visible-xs').on('touchstart', function (e) { - touchTooltipEl = $('.navigator-thumb'); - touchX = Math.min($(window).width(), Math.max(0, e.touches[0].clientX)); - touchY = Math.min($(window).height(), Math.max(0, e.touches[0].clientY)); + if (ajaxify.data.template.topic) { + handleScrollNav(); + } + + handleKeys(); + + navigator.setCount(count); + navigator.update(0); + }; + + function clampTop(newTop) { + var parent = thumb.parent(); + var parentOffset = parent.offset(); + if (newTop < parentOffset.top) { + newTop = parentOffset.top; + } else if (newTop > parentOffset.top + parent.height() - thumb.height()) { + newTop = parentOffset.top + parent.height() - thumb.height(); + } + return newTop; + } + + function setThumbToIndex(index) { + if (!thumb.length || thumb.is(':hidden')) { + return; + } + var parent = thumb.parent(); + var thumbText = thumb.find('.thumb-text'); + var parentOffset = parent.offset(); + var percent = (index - 1) / ajaxify.data.postcount; + if (index === count) { + percent = 1; + } + var newTop = clampTop(parentOffset.top + ((parent.height() - thumb.height()) * percent)); + + var offset = { top: newTop, left: thumb.offset().left }; + thumb.offset(offset); + thumbText.text(index + '/' + ajaxify.data.postcount); + renderPost(index); + } + + function handleScrollNav() { + if (!thumb.length) { + return; + } + var thumbText = thumb.find('.thumb-text'); + var thumbHeight = thumb.height(); + var thumbHalfHeight = thumbHeight / 2; + var parent = thumb.parent(); + var parentHeight = parent.height(); + var mouseDragging = false; + $(window).on('action:ajaxify.end', function () { + renderPostIndex = null; + }); + $('.pagination-block .dropdown-menu').parent().on('shown.bs.dropdown', function () { + setThumbToIndex(index); + }); + + thumb.on('mousedown', function () { + mouseDragging = true; + $(window).on('mousemove', mousemove); + firstMove = true; + }); + + function mouseup() { + $(window).off('mousemove', mousemove); + if (mouseDragging) { + navigator.scrollToIndex(index - 1, true, 0); + paginationBlockEl.find('[data-toggle="dropdown"]').trigger('click'); + } + clearRenderInterval(); + mouseDragging = false; + firstMove = false; + } + + function mousemove(ev) { + var newTop = clampTop(ev.pageY - thumbHalfHeight); + var parentOffset = parent.offset(); + + var offset = { top: newTop, left: thumb.offset().left }; + thumb.offset(offset); + + var percent = (newTop - parentOffset.top) / (parentHeight - thumbHeight); + index = Math.max(1, Math.ceil(ajaxify.data.postcount * percent)); + navigator.updateTextAndProgressBar(); + thumbText.text(index + '/' + ajaxify.data.postcount); + if (firstMove) { + delayedRenderPost(); + } + firstMove = false; + ev.stopPropagation(); + return false; + } + + function delayedRenderPost() { + clearRenderInterval(); + renderPostIntervalId = setInterval(function () { + renderPost(index); + }, 250); + } + + $(window).off('mousemove', mousemove); + $(window).off('mouseup', mouseup).on('mouseup', mouseup); + + thumb.on('touchstart', function (ev) { + isNavigating = true; + touchX = Math.min($(window).width(), Math.max(0, ev.touches[0].clientX)); + touchY = Math.min($(window).height(), Math.max(0, ev.touches[0].clientY)); firstMove = true; - }).on('touchmove', function (e) { + }); + + thumb.on('touchmove', function (ev) { var windowWidth = $(window).width(); var windowHeight = $(window).height(); - var deltaX = Math.abs(touchX - Math.min(windowWidth, Math.max(0, e.touches[0].clientX))); - var deltaY = Math.abs(touchY - Math.min(windowHeight, Math.max(0, e.touches[0].clientY))); - touchX = Math.min(windowWidth, Math.max(0, e.touches[0].clientX)); - touchY = Math.min(windowHeight, Math.max(0, e.touches[0].clientY)); - if (deltaX >= deltaY && firstMove) { + var deltaX = Math.abs(touchX - Math.min(windowWidth, Math.max(0, ev.touches[0].clientX))); + var deltaY = Math.abs(touchY - Math.min(windowHeight, Math.max(0, ev.touches[0].clientY))); + touchX = Math.min(windowWidth, Math.max(0, ev.touches[0].clientX)); + touchY = Math.min(windowHeight, Math.max(0, ev.touches[0].clientY)); + + if (deltaY >= deltaX && firstMove) { isNavigating = true; - touchIntervalId = setInterval(updateTooltip, 100); + delayedRenderPost(); } + if (isNavigating) { - e.preventDefault(); - e.stopPropagation(); - var percent = touchX / windowWidth; + ev.preventDefault(); + ev.stopPropagation(); + var newTop = clampTop(touchY + $(window).scrollTop() - thumbHalfHeight); + var parentOffset = thumb.parent().offset(); + var offset = { top: newTop, left: thumb.offset().left }; + thumb.offset(offset); + + var percent = (newTop - parentOffset.top) / (parentHeight - thumbHeight); index = Math.max(1, Math.ceil(count * percent)); index = index > count ? count : index; + + thumbText.text(index + '/' + ajaxify.data.postcount); + if (firstMove) { - updateTooltip(function () { - touchTooltipEl.removeClass('hidden'); - }); + renderPost(index); } navigator.updateTextAndProgressBar(); } firstMove = false; - }).on('touchend', function () { - if (touchIntervalId) { - clearInterval(touchIntervalId); - touchIntervalId = 0; - } + }); + thumb.on('touchend', function () { + clearRenderInterval(); if (isNavigating) { - touchTooltipEl.addClass('hidden'); navigator.scrollToIndex(index - 1, true, 0); isNavigating = false; + paginationBlockEl.find('[data-toggle="dropdown"]').trigger('click'); } }); + } - handleKeys(); - - navigator.setCount(count); - navigator.update(0); - }; + function clearRenderInterval() { + if (renderPostIntervalId) { + clearInterval(renderPostIntervalId); + renderPostIntervalId = 0; + } + } - function updateTooltip(callback) { + function renderPost(index, callback) { callback = callback || function () {}; - if (touchIndex === index) { + if (renderPostIndex === index || paginationBlockEl.find('.post-content').is(':hidden')) { return; } - touchIndex = index; - touchTooltipEl.css({ left: Math.min($(window).width() - touchTooltipEl.outerWidth(), Math.max(touchX - (touchTooltipEl.outerWidth() / 2), 0)) }); + renderPostIndex = index; - socket.emit('posts.getTimestampByIndex', { tid: ajaxify.data.tid, index: index - 1 }, function (err, timestamp) { + socket.emit('posts.getPostSummaryByIndex', { tid: ajaxify.data.tid, index: index - 1 }, function (err, postData) { if (err) { return app.alertError(err.message); } + app.parseAndTranslate('partials/topic/navigation-post', { post: postData }, function (html) { + html.find('.timeago').timeago(); + paginationBlockEl.find('.post-content').html(html); + }); - var date = new Date(timestamp); - var ds = date.toLocaleString(config.userLang, { month: 'long' }); - touchTooltipEl.find('.text').translateText('[[global:pagination.out_of, ' + index + ', ' + count + ']]'); - if (timestamp > Date.now() - (30 * 24 * 60 * 60 * 1000)) { - touchTooltipEl.find('.time').text(ds + ' ' + date.getDate()); - } else { - touchTooltipEl.find('.time').text(ds + ' ' + date.getFullYear()); - } callback(); }); } @@ -171,7 +281,11 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co } navigator.setCount = function (value) { - count = parseInt(value, 10); + value = parseInt(value, 10); + if (value === count) { + return; + } + count = value; navigator.updateTextAndProgressBar(); }; @@ -213,10 +327,10 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co a spot where a user is expecting to begin reading. */ threshold = typeof threshold === 'number' ? threshold : undefined; - + var newIndex = index; var els = $(navigator.selector); if (els.length) { - index = parseInt(els.first().attr('data-index'), 10) + 1; + newIndex = parseInt(els.first().attr('data-index'), 10) + 1; } var scrollTop = $(window).scrollTop(); @@ -235,7 +349,7 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co } if (distanceToMiddle < previousDistance) { - index = elIndex + 1; + newIndex = elIndex + 1; previousDistance = distanceToMiddle; } } @@ -245,13 +359,13 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co var nearBottom = scrollTop + windowHeight > documentHeight - 100 && parseInt(els.last().attr('data-index'), 10) === count - 1; if (atTop) { - index = 1; + newIndex = 1; } else if (nearBottom) { - index = count; + newIndex = count; } // If a threshold is undefined, try to determine one based on new index - if (threshold === undefined && ajaxify.data.template.topic === true) { + if (threshold === undefined && ajaxify.data.template.topic) { if (atTop) { threshold = 0; } else { @@ -264,10 +378,15 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co } if (typeof navigator.callback === 'function') { - navigator.callback(index, count, threshold); + navigator.callback(newIndex, count, threshold); + } + + if (newIndex !== index) { + index = newIndex; + navigator.updateTextAndProgressBar(); + setThumbToIndex(index); } - navigator.updateTextAndProgressBar(); toggle(!!count); }; @@ -422,7 +541,7 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co var scrollTop = 0; if (postHeight < viewportHeight) { - scrollTop = (scrollTo.offset().top - (viewportHeight / 2) + (postHeight / 2)) - topicHeaderHeight; + scrollTop = (scrollTo.offset().top - (viewportHeight / 2) + (postHeight)) - topicHeaderHeight; } else { scrollTop = scrollTo.offset().top - navbarHeight - topicHeaderHeight; } diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 9c5318198e..6df687ed14 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -71,7 +71,7 @@ SocketPosts.getRawPost = async function (socket, pid) { return result.postData.content; }; -SocketPosts.getTimestampByIndex = async function (socket, data) { +SocketPosts.getPostSummaryByIndex = async function (socket, data) { if (data.index < 0) { data.index = 0; } @@ -90,7 +90,9 @@ SocketPosts.getTimestampByIndex = async function (socket, data) { if (!canRead) { throw new Error('[[error:no-privileges]]'); } - return await posts.getPostField(pid, 'timestamp'); + + const postsData = await posts.getPostSummaryByPids([pid], socket.uid, { stripTags: true }); + return postsData[0]; }; SocketPosts.getPost = async function (socket, pid) {