From 028dba144d16cea2fa6d58adf4c4ad24752b16fe Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Fri, 5 May 2023 11:18:03 +0200 Subject: [PATCH] UX: preloads a thread when hovering thread indicator (#21406) When hovering a thread indicator in a channel we will now append two `` to the `` of the document. Clicking on it should be significantly faster. Co-authored-by: Martin Brennan --- .../chat-message-thread-indicator.hbs | 1 + .../chat-message-thread-indicator.js | 20 ++++++++- .../discourse/components/chat-thread.js | 2 +- .../discourse/lib/chat-preload-link.js | 17 ++++++++ .../discourse/services/chat-api.js | 15 +++---- .../chat/spec/system/thread_preload_spec.rb | 42 +++++++++++++++++++ 6 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 plugins/chat/assets/javascripts/discourse/lib/chat-preload-link.js create mode 100644 plugins/chat/spec/system/thread_preload_spec.rb diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.hbs index 2412c1192b4..7ee8ea0a3f3 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.hbs @@ -2,6 +2,7 @@ @route="chat.channel.thread" @models={{@message.thread.routeModels}} class="chat-message-thread-indicator" + {{on "mouseenter" this.preloadThread}} > {{i18n "chat.thread.replies" count=@message.threadReplyCount}} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.js b/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.js index a2ef54b411f..1ab39b9659d 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-thread-indicator.js @@ -1,3 +1,21 @@ import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { addPreloadLink } from "../lib/chat-preload-link"; +import { PAGE_SIZE } from "./chat-thread"; -export default class ChatMessageThreadIndicator extends Component {} +export default class ChatMessageThreadIndicator extends Component { + @action + preloadThread() { + const channel = this.args.message.channel; + const thread = this.args.message.thread; + + addPreloadLink( + `/chat/api/channels/${channel.id}/threads/${thread.id}.json`, + `thread-preload-${thread.id}` + ); + addPreloadLink( + `/chat/${channel.id}/messages.json?page_size=${PAGE_SIZE}&thread_id=${thread.id}`, + `thread-preload-messages-${thread.id}` + ); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-thread.js b/plugins/chat/assets/javascripts/discourse/components/chat-thread.js index 2b5d6e6e74c..e3aa455ae72 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-thread.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-thread.js @@ -10,7 +10,7 @@ import { schedule } from "@ember/runloop"; import discourseLater from "discourse-common/lib/later"; import { resetIdle } from "discourse/lib/desktop-notifications"; -const PAGE_SIZE = 50; +export const PAGE_SIZE = 50; export default class ChatThreadPanel extends Component { @service siteSettings; diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-preload-link.js b/plugins/chat/assets/javascripts/discourse/lib/chat-preload-link.js new file mode 100644 index 00000000000..c8f5127222e --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-preload-link.js @@ -0,0 +1,17 @@ +export function addPreloadLink(url, id) { + if (document.querySelector(`link[href="${url}"][rel="preload"]`)) { + return; + } + + const importNode = document.createElement("link"); + importNode.id = id; + importNode.rel = "preload"; + importNode.crossOrigin = "anonymous"; + importNode.as = "fetch"; + importNode.href = url; + importNode.onload = () => { + importNode?.classList?.add("is-preloaded"); + }; + + document.head.appendChild(importNode); +} diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-api.js b/plugins/chat/assets/javascripts/discourse/services/chat-api.js index 6f15734b4b6..9232b80afbc 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-api.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-api.js @@ -39,12 +39,13 @@ export default class ChatApi extends Service { * this.chatApi.thread(5, 1).then(thread => { ... }) */ thread(channelId, threadId) { - return this.#getRequest(`/channels/${channelId}/threads/${threadId}`).then( - (result) => - this.chat.activeChannel.threadsManager.store( - this.chat.activeChannel, - result.thread - ) + return this.#getRequest( + `/channels/${channelId}/threads/${threadId}.json` + ).then((result) => + this.chat.activeChannel.threadsManager.store( + this.chat.activeChannel, + result.thread + ) ); } @@ -266,7 +267,7 @@ export default class ChatApi extends Service { args.chat_channel_id = channelId; } else { args.page_size = data.pageSize; - path = `/chat/${channelId}/messages`; + path = `/chat/${channelId}/messages.json`; if (data.messageId) { args.message_id = data.messageId; diff --git a/plugins/chat/spec/system/thread_preload_spec.rb b/plugins/chat/spec/system/thread_preload_spec.rb new file mode 100644 index 00000000000..2837247af6b --- /dev/null +++ b/plugins/chat/spec/system/thread_preload_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +describe "Thread preload", type: :system, js: true do + fab!(:current_user) { Fabricate(:user) } + fab!(:channel_1) { Fabricate(:chat_channel) } + + let(:chat_page) { PageObjects::Pages::Chat.new } + let(:channel_page) { PageObjects::Pages::ChatChannel.new } + let(:thread_page) { PageObjects::Pages::ChatThread.new } + + before do + SiteSetting.enable_experimental_chat_threaded_discussions = true + chat_system_bootstrap(current_user) + channel_1.add(current_user) + channel_1.update!(threading_enabled: true) + sign_in(current_user) + end + + context "when hovering a thread indicator" do + it "preloads the thread" do + thread = + chat_thread_chain_bootstrap(channel: channel_1, users: [current_user, Fabricate(:user)]) + chat_page.visit_channel(channel_1) + + channel_page.message_thread_indicator(thread.original_message).hover + + expect(page).to have_selector("link#thread-preload-#{thread.id}.is-preloaded", visible: false) + expect(page).to have_selector( + "link#thread-preload-messages-#{thread.id}.is-preloaded", + visible: false, + ) + + page.driver.browser.network_conditions = { offline: true } + + channel_page.message_thread_indicator(thread.original_message).click + + expect(thread_page).to have_message(text: thread.replies.last.message) + ensure + page.driver.browser.network_conditions = { offline: false } + end + end +end