From dac4b493c8e658c9216c105e01296812361b18fd Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 26 May 2019 16:01:14 -0700 Subject: [PATCH] fix: poll for updates to timeago displays (#1232) * fix: poll for updates to timeago displays * code cleanup * avoid some recomputes * avoid costly recomputes --- .../profile/AccountDisplayName.html | 2 +- src/routes/_components/status/Status.html | 37 +++++++++------ .../_components/status/StatusContent.html | 5 +- .../_components/status/StatusDetails.html | 5 +- .../_components/status/StatusSpoiler.html | 5 +- src/routes/_intl/formatTimeagoDate.js | 5 +- src/routes/_store/observers/nowObservers.js | 47 +++++++++++++++++++ src/routes/_store/observers/observers.js | 2 + src/routes/_thirdparty/timeago/timeago.js | 12 ++--- 9 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 src/routes/_store/observers/nowObservers.js diff --git a/src/routes/_components/profile/AccountDisplayName.html b/src/routes/_components/profile/AccountDisplayName.html index 96165aa1..3dff1ff8 100644 --- a/src/routes/_components/profile/AccountDisplayName.html +++ b/src/routes/_components/profile/AccountDisplayName.html @@ -29,4 +29,4 @@ } } } - \ No newline at end of file + diff --git a/src/routes/_components/status/Status.html b/src/routes/_components/status/Status.html index 10e53f42..d8929120 100644 --- a/src/routes/_components/status/Status.html +++ b/src/routes/_components/status/Status.html @@ -13,11 +13,11 @@ {#if !isStatusInOwnThread} - + {/if} {#if spoilerText} - + {/if} {#if !showContent} @@ -35,9 +35,9 @@ {/if} {#if isStatusInOwnThread} - + {/if} - + {#if replyShown} {/if} @@ -277,13 +277,15 @@ originalStatus.media_attachments.length ), originalAccountEmojis: ({ originalAccount }) => (originalAccount.emojis || []), + originalStatusEmojis: ({ originalStatus }) => (originalStatus.emojis || []), originalAccountDisplayName: ({ originalAccount }) => (originalAccount.display_name || originalAccount.username), originalAccountAccessibleName: ({ originalAccount, $omitEmojiInDisplayNames }) => { return getAccountAccessibleName(originalAccount, $omitEmojiInDisplayNames) }, createdAtDate: ({ originalStatus }) => originalStatus.created_at, - absoluteFormattedDate: ({ createdAtDate }) => absoluteDateFormatter.format(new Date(createdAtDate)), - timeagoFormattedDate: ({ createdAtDate }) => formatTimeagoDate(createdAtDate), + createdAtDateTS: ({ createdAtDate }) => new Date(createdAtDate).getTime(), + absoluteFormattedDate: ({ createdAtDateTS }) => absoluteDateFormatter.format(createdAtDateTS), + timeagoFormattedDate: ({ createdAtDateTS, $now }) => formatTimeagoDate(createdAtDateTS, $now), reblog: ({ status }) => status.reblog, ariaLabel: ({ originalAccount, account, plainTextContent, timeagoFormattedDate, spoilerText, showContent, reblog, notification, visibility, $omitEmojiInDisplayNames, $disableLongAriaLabels }) => ( @@ -307,11 +309,22 @@ )), content: ({ originalStatus }) => originalStatus.content || '', showContent: ({ spoilerText, spoilerShown }) => !spoilerText || spoilerShown, + // These timestamp params may change every 10 seconds due to now() polling, so keep them + // separate from the generic `params` list to avoid costly recomputes. + timestampParams: ({ createdAtDate, createdAtDateTS, timeagoFormattedDate, absoluteFormattedDate }) => ({ + createdAtDate, + createdAtDateTS, + timeagoFormattedDate, + absoluteFormattedDate + }), + // This params list deliberately does *not* include `spoilersShown` or `replyShown`, because these + // change frequently and would therefore cause costly recomputes if included here. + // The main goal here is to avoid typing by passing as many params as possible to child components. params: ({ notification, notificationId, status, statusId, timelineType, account, accountId, uuid, isStatusInNotification, isStatusInOwnThread, - originalAccount, originalAccountId, spoilerShown, visibility, replyShown, + originalAccount, originalAccountId, visibility, replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId, - createdAtDate, timeagoFormattedDate, enableShortcuts, absoluteFormattedDate, shortcutScope }) => ({ + enableShortcuts, shortcutScope, originalStatusEmojis }) => ({ notification, notificationId, status, @@ -324,19 +337,15 @@ isStatusInOwnThread, originalAccount, originalAccountId, - spoilerShown, visibility, - replyShown, replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId, - createdAtDate, - timeagoFormattedDate, enableShortcuts, - absoluteFormattedDate, - shortcutScope + shortcutScope, + originalStatusEmojis }) }, events: { diff --git a/src/routes/_components/status/StatusContent.html b/src/routes/_components/status/StatusContent.html index 1f2b1215..043c6da8 100644 --- a/src/routes/_components/status/StatusContent.html +++ b/src/routes/_components/status/StatusContent.html @@ -76,8 +76,9 @@ ) }, content: ({ originalStatus }) => (originalStatus.content || ''), - emojis: ({ originalStatus }) => originalStatus.emojis, - massagedContent: ({ content, emojis, $autoplayGifs }) => massageUserText(content, emojis, $autoplayGifs) + massagedContent: ({ content, originalStatusEmojis, $autoplayGifs }) => ( + massageUserText(content, originalStatusEmojis, $autoplayGifs) + ) }, methods: { hydrateContent () { diff --git a/src/routes/_components/status/StatusDetails.html b/src/routes/_components/status/StatusDetails.html index cc9cf953..d3c4b2a2 100644 --- a/src/routes/_components/status/StatusDetails.html +++ b/src/routes/_components/status/StatusDetails.html @@ -158,7 +158,6 @@ application: ({ originalStatus }) => originalStatus.application, applicationName: ({ application }) => (application && application.name), applicationWebsite: ({ application }) => (application && application.website), - createdAtDate: ({ originalStatus }) => originalStatus.created_at, numReblogs: ({ overrideNumReblogs, originalStatus }) => { if (typeof overrideNumReblogs === 'number') { return overrideNumReblogs @@ -171,8 +170,8 @@ } return originalStatus.favourites_count || 0 }, - displayAbsoluteFormattedDate: ({ createdAtDate, $isMobileSize }) => ( - $isMobileSize ? shortAbsoluteDateFormatter : absoluteDateFormatter).format(new Date(createdAtDate) + displayAbsoluteFormattedDate: ({ createdAtDateTS, $isMobileSize }) => ( + ($isMobileSize ? shortAbsoluteDateFormatter : absoluteDateFormatter).format(createdAtDateTS) ), reblogsLabel: ({ numReblogs }) => { // TODO: intl diff --git a/src/routes/_components/status/StatusSpoiler.html b/src/routes/_components/status/StatusSpoiler.html index 3d54621d..acabf18c 100644 --- a/src/routes/_components/status/StatusSpoiler.html +++ b/src/routes/_components/status/StatusSpoiler.html @@ -64,10 +64,9 @@ Shortcut }, computed: { - emojis: ({ originalStatus }) => originalStatus.emojis, - massagedSpoilerText: ({ spoilerText, emojis, $autoplayGifs }) => { + massagedSpoilerText: ({ spoilerText, originalStatusEmojis, $autoplayGifs }) => { spoilerText = escapeHtml(spoilerText) - return emojifyText(spoilerText, emojis, $autoplayGifs) + return emojifyText(spoilerText, originalStatusEmojis, $autoplayGifs) }, elementId: ({ uuid }) => `spoiler-${uuid}` }, diff --git a/src/routes/_intl/formatTimeagoDate.js b/src/routes/_intl/formatTimeagoDate.js index ec63b7c3..0756e4b5 100644 --- a/src/routes/_intl/formatTimeagoDate.js +++ b/src/routes/_intl/formatTimeagoDate.js @@ -1,9 +1,10 @@ import { format } from '../_thirdparty/timeago/timeago' import { mark, stop } from '../_utils/marks' -export function formatTimeagoDate (date) { +export function formatTimeagoDate (date, now) { mark('formatTimeagoDate') - let res = format(date) + // use Math.max() to avoid things like "in 10 seconds" when the timestamps are slightly off + let res = format(date, Math.max(now, date)) stop('formatTimeagoDate') return res } diff --git a/src/routes/_store/observers/nowObservers.js b/src/routes/_store/observers/nowObservers.js new file mode 100644 index 00000000..df055e62 --- /dev/null +++ b/src/routes/_store/observers/nowObservers.js @@ -0,0 +1,47 @@ +// For convenience, periodically re-compute the current time. This ensures freshness of +// displays like "x minutes ago" without having to jump through a lot of hoops. +import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' +import lifecycle from 'page-lifecycle/dist/lifecycle.mjs' + +const POLL_INTERVAL = 10000 + +export function nowObservers (store) { + let interval + + function updateNow () { + store.set({ now: Date.now() }) + } + + function startPolling () { + interval = setInterval(() => scheduleIdleTask(updateNow), POLL_INTERVAL) + } + + function stopPolling () { + if (interval) { + clearInterval(interval) + interval = null + } + } + + function restartPolling () { + stopPolling() + scheduleIdleTask(updateNow) + startPolling() + } + + updateNow() + + if (process.browser) { + startPolling() + + lifecycle.addEventListener('statechange', e => { + if (e.newState === 'passive') { + console.log('stopping Date.now() observer...') + stopPolling() + } else if (e.newState === 'active') { + console.log('restarting Date.now() observer...') + restartPolling() + } + }) + } +} diff --git a/src/routes/_store/observers/observers.js b/src/routes/_store/observers/observers.js index fe278cc4..ff8f6b1f 100644 --- a/src/routes/_store/observers/observers.js +++ b/src/routes/_store/observers/observers.js @@ -1,4 +1,5 @@ import { onlineObservers } from './onlineObservers' +import { nowObservers } from './nowObservers' import { navObservers } from './navObservers' import { pageVisibilityObservers } from './pageVisibilityObservers' import { resizeObservers } from './resizeObservers' @@ -8,6 +9,7 @@ import { touchObservers } from './touchObservers' export function observers (store) { onlineObservers(store) + nowObservers(store) navObservers(store) pageVisibilityObservers(store) resizeObservers(store) diff --git a/src/routes/_thirdparty/timeago/timeago.js b/src/routes/_thirdparty/timeago/timeago.js index db8fddd1..6a1ff1ef 100644 --- a/src/routes/_thirdparty/timeago/timeago.js +++ b/src/routes/_thirdparty/timeago/timeago.js @@ -4,7 +4,7 @@ * Contract: i@hust.cc */ -var IndexMapEn = 'second_minute_hour_day_week_month_year'.split('_') +var IndexMapEn = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'] var SEC_ARRAY = [60, 60, 24, 7, 365 / 7 / 12, 12] /** @@ -63,16 +63,14 @@ function formatDiff (diff) { * @param nowDate * @returns {number} */ -function diffSec (date) { - var nowDate = new Date() - var otherDate = new Date(date) - return (nowDate - otherDate) / 1000 +function diffSec (date, now) { + return (now - date) / 1000 } /** * Created by hustcc on 18/5/20. * Contract: i@hust.cc */ -export function format (date) { - return formatDiff(diffSec(date)) +export function format (date, now) { + return formatDiff(diffSec(date, now)) }