From 64975a6ecb5999addc63497891417cea63e4ace4 Mon Sep 17 00:00:00 2001 From: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com> Date: Tue, 21 Feb 2023 08:23:40 -0600 Subject: [PATCH] DEV: Remove deprecated topic timeline (#20314) Continuing from https://github.com/discourse/discourse/pull/20313 - Drop deprecated topic timeline widget / component logic --- .../app/components/topic-timeline.js | 133 ---- .../discourse/app/widgets/topic-timeline.js | 628 ------------------ 2 files changed, 761 deletions(-) delete mode 100644 app/assets/javascripts/discourse/app/components/topic-timeline.js delete mode 100644 app/assets/javascripts/discourse/app/widgets/topic-timeline.js diff --git a/app/assets/javascripts/discourse/app/components/topic-timeline.js b/app/assets/javascripts/discourse/app/components/topic-timeline.js deleted file mode 100644 index d37ced63530..00000000000 --- a/app/assets/javascripts/discourse/app/components/topic-timeline.js +++ /dev/null @@ -1,133 +0,0 @@ -import Docking from "discourse/mixins/docking"; -import MountWidget from "discourse/components/mount-widget"; -import { headerOffset } from "discourse/lib/offset-calculator"; -import { next } from "@ember/runloop"; -import { observes } from "discourse-common/utils/decorators"; -import optionalService from "discourse/lib/optional-service"; - -export default MountWidget.extend(Docking, { - adminTools: optionalService(), - widget: "topic-timeline-container", - dockBottom: null, - dockAt: null, - intersectionObserver: null, - - buildArgs() { - let attrs = { - topic: this.topic, - notificationLevel: this.notificationLevel, - topicTrackingState: this.topicTrackingState, - enteredIndex: this.enteredIndex, - dockAt: this.dockAt, - dockBottom: this.dockBottom, - mobileView: this.get("site.mobileView"), - }; - - let event = this.prevEvent; - if (event) { - attrs.enteredIndex = event.postIndex - 1; - } - - if (this.fullscreen) { - attrs.fullScreen = true; - attrs.addShowClass = this.addShowClass; - } - - return attrs; - }, - - @observes("topic.highest_post_number", "loading") - newPostAdded() { - this.queueRerender(() => this.queueDockCheck()); - }, - - @observes("topic.details.notification_level") - _queueRerender() { - this.queueRerender(); - }, - - dockCheck() { - const timeline = this.element.querySelector(".timeline-container"); - const timelineHeight = (timeline && timeline.offsetHeight) || 400; - - const prev = this.dockAt; - const posTop = headerOffset() + window.pageYOffset; - const pos = posTop + timelineHeight; - - this.dockBottom = false; - if (posTop < this.topicTop) { - this.dockAt = parseInt(this.topicTop, 10); - } else if (pos > this.topicBottom) { - this.dockAt = parseInt(this.topicBottom - timelineHeight, 10); - this.dockBottom = true; - if (this.dockAt < 0) { - this.dockAt = 0; - } - } else { - this.dockAt = null; - this.fastDockAt = parseInt(this.topicBottom - timelineHeight, 10); - } - - if (this.dockAt !== prev) { - this.queueRerender(); - } - }, - - didInsertElement() { - this._super(...arguments); - - if (this.fullscreen && !this.addShowClass) { - next(() => { - this.set("addShowClass", true); - this.queueRerender(); - }); - } - - this.dispatch( - "topic:current-post-scrolled", - () => `timeline-scrollarea-${this.topic.id}` - ); - this.dispatch("topic:toggle-actions", "topic-admin-menu-button"); - if (!this.site.mobileView) { - this.appEvents.on("composer:opened", this, this.queueRerender); - this.appEvents.on("composer:resized", this, this.queueRerender); - this.appEvents.on("composer:closed", this, this.queueRerender); - if ("IntersectionObserver" in window) { - this.intersectionObserver = new IntersectionObserver((entries) => { - for (const entry of entries) { - const bounds = entry.boundingClientRect; - - if (entry.target.id === "topic-bottom") { - this.set("topicBottom", bounds.y + window.scrollY); - } else { - this.set("topicTop", bounds.y + window.scrollY); - } - } - }); - - const elements = [ - document.querySelector(".container.posts"), - document.querySelector("#topic-bottom"), - ]; - - for (let i = 0; i < elements.length; i++) { - this.intersectionObserver.observe(elements[i]); - } - } - } - }, - - willDestroyElement() { - this._super(...arguments); - - if (!this.site.mobileView) { - this.appEvents.off("composer:opened", this, this.queueRerender); - this.appEvents.off("composer:resized", this, this.queueRerender); - this.appEvents.off("composer:closed", this, this.queueRerender); - if ("IntersectionObserver" in window) { - this.intersectionObserver?.disconnect(); - this.intersectionObserver = null; - } - } - }, -}); diff --git a/app/assets/javascripts/discourse/app/widgets/topic-timeline.js b/app/assets/javascripts/discourse/app/widgets/topic-timeline.js deleted file mode 100644 index 45a24e1ad2e..00000000000 --- a/app/assets/javascripts/discourse/app/widgets/topic-timeline.js +++ /dev/null @@ -1,628 +0,0 @@ -import ComponentConnector from "discourse/widgets/component-connector"; -import I18n from "I18n"; -import RawHtml from "discourse/widgets/raw-html"; -import { createWidget } from "discourse/widgets/widget"; -import { actionDescriptionHtml } from "discourse/widgets/post-small-action"; -import { h } from "virtual-dom"; -import { iconNode } from "discourse-common/lib/icon-library"; -import discourseLater from "discourse-common/lib/later"; -import { relativeAge } from "discourse/lib/formatter"; -import renderTags from "discourse/lib/render-tags"; -import renderTopicFeaturedLink from "discourse/lib/render-topic-featured-link"; -import { hideUserTip } from "discourse/lib/user-tips"; - -const SCROLLER_HEIGHT = 50; -const LAST_READ_HEIGHT = 20; -const MIN_SCROLLAREA_HEIGHT = 170; -const MAX_SCROLLAREA_HEIGHT = 300; - -function scrollareaHeight() { - const composerHeight = - document.getElementById("reply-control").offsetHeight || 0, - headerHeight = document.querySelectorAll(".d-header")[0].offsetHeight || 0; - - // scrollarea takes up about half of the timeline's height - const availableHeight = - (window.innerHeight - composerHeight - headerHeight) / 2; - - return Math.max( - MIN_SCROLLAREA_HEIGHT, - Math.min(availableHeight, MAX_SCROLLAREA_HEIGHT) - ); -} - -function scrollareaRemaining() { - return scrollareaHeight() - SCROLLER_HEIGHT; -} - -function clamp(p, min = 0.0, max = 1.0) { - return Math.max(Math.min(p, max), min); -} - -function attachBackButton(widget) { - return widget.attach("button", { - className: "btn-primary btn-small back-button", - label: "topic.timeline.back", - title: "topic.timeline.back_description", - action: "goBack", - }); -} - -createWidget("timeline-last-read", { - tagName: "div.timeline-last-read", - - buildAttributes(attrs) { - const bottom = scrollareaHeight() - LAST_READ_HEIGHT / 2; - const top = attrs.top > bottom ? bottom : attrs.top; - return { style: `height: ${LAST_READ_HEIGHT}px; top: ${top}px` }; - }, - - html(attrs) { - const result = [iconNode("minus", { class: "progress" })]; - if (attrs.showButton) { - result.push(attachBackButton(this)); - } - - return result; - }, -}); - -function timelineDate(date) { - const fmt = - date.getFullYear() === new Date().getFullYear() - ? "long_no_year_no_time" - : "timeline_date"; - return moment(date).format(I18n.t(`dates.${fmt}`)); -} - -createWidget("timeline-scroller", { - tagName: "div.timeline-scroller", - buildKey: (attrs) => `timeline-scroller-${attrs.topicId}`, - - defaultState() { - return { dragging: false }; - }, - - buildAttributes() { - return { style: `height: ${SCROLLER_HEIGHT}px` }; - }, - - html(attrs, state) { - const { current, total, date } = attrs; - - const contents = [ - h( - "div.timeline-replies", - I18n.t(`topic.timeline.replies_short`, { current, total }) - ), - ]; - - if (date) { - contents.push(h("div.timeline-ago", timelineDate(date))); - } - - if (attrs.showDockedButton && !state.dragging) { - contents.push(attachBackButton(this)); - } - let result = [ - h("div.timeline-handle"), - h("div.timeline-scroller-content", contents), - ]; - - if (attrs.fullScreen) { - result = [result[1], result[0]]; - } - - return result; - }, - - drag(e) { - this.state.dragging = true; - this.sendWidgetAction("updatePercentage", e.pageY); - }, - - dragEnd(e) { - this.state.dragging = false; - if ($(e.target).is("button")) { - this.sendWidgetAction("goBack"); - } else { - this.sendWidgetAction("commit"); - } - }, -}); - -createWidget("timeline-padding", { - tagName: "div.timeline-padding", - buildAttributes(attrs) { - return { style: `height: ${attrs.height}px` }; - }, - - click(e) { - this.sendWidgetAction("updatePercentage", e.pageY); - this.sendWidgetAction("commit"); - }, -}); - -createWidget("timeline-scrollarea", { - tagName: "div.timeline-scrollarea", - buildKey: (attrs) => `timeline-scrollarea-${attrs.topic.id}`, - - buildAttributes() { - return { style: `height: ${scrollareaHeight()}px` }; - }, - - defaultState(attrs) { - return { - percentage: this._percentFor(attrs.topic, attrs.enteredIndex + 1), - scrolledPost: 1, - }; - }, - - position() { - const { attrs } = this; - const percentage = this.state.percentage; - const topic = attrs.topic; - const postStream = topic.get("postStream"); - const total = postStream.get("filteredPostsCount"); - - const scrollPosition = clamp(Math.floor(total * percentage), 0, total) + 1; - const current = clamp(scrollPosition, 1, total); - - const daysAgo = postStream.closestDaysAgoFor(current); - let date; - - if (daysAgo === undefined) { - const post = postStream - .get("posts") - .findBy("id", postStream.get("stream")[current]); - - if (post) { - date = new Date(post.get("created_at")); - } - } else if (daysAgo !== null) { - date = new Date(); - date.setDate(date.getDate() - daysAgo || 0); - } else { - date = null; - } - - const result = { - current, - scrollPosition, - total, - date, - lastRead: null, - lastReadPercentage: null, - }; - - const lastReadId = topic.last_read_post_id; - const lastReadNumber = topic.last_read_post_number; - - if (lastReadId && lastReadNumber) { - const idx = postStream.get("stream").indexOf(lastReadId) + 1; - result.lastRead = idx; - result.lastReadPercentage = this._percentFor(topic, idx); - } - - if (this.state.position !== result.scrollPosition) { - this.state.position = result.scrollPosition; - this.sendWidgetAction("updatePosition", current); - } - - return result; - }, - - html(attrs, state) { - const position = this.position(); - - state.scrolledPost = position.current; - const percentage = state.percentage; - if (percentage === null) { - return; - } - - const before = scrollareaRemaining() * percentage; - const after = scrollareaHeight() - before - SCROLLER_HEIGHT; - - let showButton = false; - const hasBackPosition = - position.lastRead && - position.lastRead > 3 && - position.lastRead > position.current && - Math.abs(position.lastRead - position.current) > 3 && - Math.abs(position.lastRead - position.total) > 1 && - position.lastRead !== position.total; - - if (hasBackPosition) { - const lastReadTop = Math.round( - position.lastReadPercentage * scrollareaHeight() - ); - showButton = - before + SCROLLER_HEIGHT - 5 < lastReadTop || before > lastReadTop + 25; - } - - let scrollerAttrs = position; - scrollerAttrs.showDockedButton = - !attrs.mobileView && hasBackPosition && !showButton; - scrollerAttrs.fullScreen = attrs.fullScreen; - scrollerAttrs.topicId = attrs.topic.id; - - const result = [ - this.attach("timeline-padding", { height: before }), - this.attach("timeline-scroller", scrollerAttrs), - this.attach("timeline-padding", { height: after }), - ]; - - if (hasBackPosition) { - const lastReadTop = Math.round( - position.lastReadPercentage * scrollareaHeight() - ); - result.push( - this.attach("timeline-last-read", { - top: lastReadTop, - lastRead: position.lastRead, - showButton, - }) - ); - } - - return result; - }, - - updatePercentage(y) { - const $area = $(".timeline-scrollarea"); - const areaTop = $area.offset().top; - - const percentage = clamp(parseFloat(y - areaTop) / $area.height()); - - this.state.percentage = percentage; - }, - - commit() { - const position = this.position(); - this.state.scrolledPost = position.current; - - if (position.current === position.scrollPosition) { - this.sendWidgetAction("jumpToIndex", position.current); - } else { - this.sendWidgetAction("jumpEnd"); - } - }, - - topicCurrentPostScrolled(event) { - this.state.percentage = event.percent; - }, - - _percentFor(topic, postIndex) { - const total = topic.get("postStream.filteredPostsCount"); - return clamp(parseFloat(postIndex - 1.0) / total); - }, - - goBack() { - this.sendWidgetAction("jumpToIndex", this.position().lastRead); - }, -}); - -createWidget("topic-timeline-container", { - tagName: "div.timeline-container", - buildClasses(attrs) { - if (attrs.fullScreen) { - if (attrs.addShowClass) { - return "timeline-fullscreen show"; - } else { - return "timeline-fullscreen"; - } - } - if (attrs.dockAt) { - const result = ["timeline-docked"]; - if (attrs.dockBottom) { - result.push("timeline-docked-bottom"); - } - return result.join(" "); - } - }, - - html(attrs) { - return this.attach("topic-timeline", attrs); - }, -}); - -createWidget("timeline-controls", { - tagName: "div.timeline-controls", - - html(attrs) { - const controls = []; - const { fullScreen, currentUser, topic } = attrs; - - if (!fullScreen && currentUser) { - controls.push( - this.attach("topic-admin-menu-button", { - topic, - addKeyboardTargetClass: true, - }) - ); - } - - return controls; - }, -}); - -createWidget("timeline-footer-controls", { - tagName: "div.timeline-footer-controls", - - html(attrs) { - const controls = []; - const { currentUser, fullScreen, topic, notificationLevel } = attrs; - - if ( - this.siteSettings.summary_timeline_button && - !fullScreen && - topic.has_summary && - !topic.postStream.summary - ) { - controls.push( - this.attach("button", { - className: "show-summary btn-small", - icon: "layer-group", - label: "summary.short_label", - title: "summary.short_title", - action: "showSummary", - }) - ); - } - - if (currentUser && !fullScreen) { - if (topic.get("details.can_create_post")) { - controls.push( - this.attach("button", { - className: "btn-default create reply-to-post", - icon: "reply", - title: "topic.reply.help", - action: "replyToPost", - }) - ); - } - } - - if (fullScreen) { - controls.push( - this.attach("button", { - className: "jump-to-post", - title: "topic.progress.jump_prompt_long", - label: "topic.progress.jump_prompt", - action: "jumpToPostPrompt", - }) - ); - } - - if (currentUser) { - controls.push( - new ComponentConnector( - this, - "topic-notifications-button", - { - notificationLevel, - topic, - showFullTitle: false, - appendReason: false, - placement: "bottom-end", - mountedAsWidget: true, - showCaret: false, - }, - ["notificationLevel"] - ) - ); - if (this.site.mobileView) { - controls.push( - this.attach("topic-admin-menu-button", { - topic, - addKeyboardTargetClass: true, - openUpwards: true, - }) - ); - } - } - - return controls; - }, -}); - -export default createWidget("topic-timeline", { - tagName: "div.topic-timeline", - buildKey: (attrs) => `topic-timeline-area-${attrs.topic.id}`, - - defaultState() { - return { position: null, excerpt: null }; - }, - - updatePosition(scrollPosition) { - if (!this.attrs.fullScreen) { - return; - } - - this.state.position = scrollPosition; - this.state.excerpt = ""; - const stream = this.attrs.topic.get("postStream"); - - // a little debounce to avoid flashing - discourseLater(() => { - if (!this.state.position === scrollPosition) { - return; - } - - // we have an off by one, stream is zero based, - stream.excerpt(scrollPosition - 1).then((info) => { - if (info && this.state.position === scrollPosition) { - let excerpt = ""; - - if (info.username) { - excerpt = "" + info.username + ": "; - } - - if (info.excerpt) { - this.state.excerpt = excerpt + info.excerpt; - } else if (info.action_code) { - this.state.excerpt = `${excerpt} ${actionDescriptionHtml( - info.action_code, - info.created_at, - info.username - )}`; - } - - this.scheduleRerender(); - } - }); - }, 50); - }, - - html(attrs) { - const { topic } = attrs; - const createdAt = new Date(topic.created_at); - const { currentUser } = this; - const { tagging_enabled, topic_featured_link_enabled } = this.siteSettings; - - attrs["currentUser"] = currentUser; - - let result = []; - - if (attrs.fullScreen) { - let titleHTML = ""; - if (attrs.mobileView) { - titleHTML = new RawHtml({ - html: `${topic.get("fancyTitle")}`, - }); - } - - let elems = [ - h( - "h2", - this.attach("link", { - contents: () => titleHTML, - className: "fancy-title", - action: "jumpTop", - }) - ), - ]; - - // duplicate of the {{topic-category}} component - let category = []; - - if (!topic.get("isPrivateMessage")) { - if (topic.category.parentCategory) { - category.push( - this.attach("category-link", { - category: topic.category.parentCategory, - }) - ); - } - category.push( - this.attach("category-link", { category: topic.category }) - ); - } - - const showTags = tagging_enabled && topic.tags && topic.tags.length > 0; - - if (showTags || topic_featured_link_enabled) { - let extras = []; - if (showTags) { - const tagsHtml = new RawHtml({ - html: renderTags(topic, { mode: "list" }), - }); - extras.push(h("div.list-tags", tagsHtml)); - } - if (topic_featured_link_enabled) { - extras.push(new RawHtml({ html: renderTopicFeaturedLink(topic) })); - } - category.push(h("div.topic-header-extra", extras)); - } - - if (category.length > 0) { - elems.push(h("div.topic-category", category)); - } - - if (this.state.excerpt) { - elems.push( - new RawHtml({ - html: `