diff --git a/app/assets/javascripts/discourse/app/components/discourse-topic.js b/app/assets/javascripts/discourse/app/components/discourse-topic.js
index c22c72b8778..3e9906351b3 100644
--- a/app/assets/javascripts/discourse/app/components/discourse-topic.js
+++ b/app/assets/javascripts/discourse/app/components/discourse-topic.js
@@ -7,7 +7,6 @@ import $ from "jquery";
import ClickTrack from "discourse/lib/click-track";
import DiscourseURL from "discourse/lib/url";
import { highlightPost } from "discourse/lib/utilities";
-import AddArchetypeClass from "discourse/mixins/add-archetype-class";
import MobileScrollDirection from "discourse/mixins/mobile-scroll-direction";
import Scrolling from "discourse/mixins/scrolling";
import discourseLater from "discourse-common/lib/later";
@@ -15,206 +14,193 @@ import { bind, observes } from "discourse-common/utils/decorators";
const MOBILE_SCROLL_DIRECTION_CHECK_THROTTLE = 300;
-export default Component.extend(
- AddArchetypeClass,
- Scrolling,
- MobileScrollDirection,
- {
- userFilters: alias("topic.userFilters"),
- classNameBindings: [
- "multiSelect",
- "topic.archetype",
- "topic.is_warning",
- "topic.category.read_restricted:read_restricted",
- "topic.deleted:deleted-topic",
- ],
- menuVisible: true,
- SHORT_POST: 1200,
+export default Component.extend(Scrolling, MobileScrollDirection, {
+ userFilters: alias("topic.userFilters"),
+ classNameBindings: [
+ "multiSelect",
+ "topic.archetype",
+ "topic.is_warning",
+ "topic.category.read_restricted:read_restricted",
+ "topic.deleted:deleted-topic",
+ ],
+ menuVisible: true,
+ SHORT_POST: 1200,
- postStream: alias("topic.postStream"),
- archetype: alias("topic.archetype"),
- dockAt: 0,
+ postStream: alias("topic.postStream"),
+ dockAt: 0,
- _lastShowTopic: null,
+ _lastShowTopic: null,
- mobileScrollDirection: null,
- pauseHeaderTopicUpdate: false,
+ mobileScrollDirection: null,
+ pauseHeaderTopicUpdate: false,
- @observes("enteredAt")
- _enteredTopic() {
- // Ember is supposed to only call observers when values change but something
- // in our view set up is firing this observer with the same value. This check
- // prevents scrolled from being called twice
- if (this.enteredAt && this.lastEnteredAt !== this.enteredAt) {
- this._lastShowTopic = null;
- schedule("afterRender", this.scrolled);
- this.set("lastEnteredAt", this.enteredAt);
- }
- },
+ @observes("enteredAt")
+ _enteredTopic() {
+ // Ember is supposed to only call observers when values change but something
+ // in our view set up is firing this observer with the same value. This check
+ // prevents scrolled from being called twice
+ if (this.enteredAt && this.lastEnteredAt !== this.enteredAt) {
+ this._lastShowTopic = null;
+ schedule("afterRender", this.scrolled);
+ this.set("lastEnteredAt", this.enteredAt);
+ }
+ },
- _highlightPost(postNumber, options = {}) {
- if (isBlank(options.jump) || options.jump !== false) {
- scheduleOnce("afterRender", null, highlightPost, postNumber);
- }
- },
+ _highlightPost(postNumber, options = {}) {
+ if (isBlank(options.jump) || options.jump !== false) {
+ scheduleOnce("afterRender", null, highlightPost, postNumber);
+ }
+ },
- _hideTopicInHeader() {
- this.appEvents.trigger("header:hide-topic");
- this._lastShowTopic = false;
- },
+ _hideTopicInHeader() {
+ this.appEvents.trigger("header:hide-topic");
+ this._lastShowTopic = false;
+ },
- _showTopicInHeader(topic) {
- if (this.pauseHeaderTopicUpdate) {
- return;
- }
- this.appEvents.trigger("header:show-topic", topic);
- this._lastShowTopic = true;
- },
+ _showTopicInHeader(topic) {
+ if (this.pauseHeaderTopicUpdate) {
+ return;
+ }
+ this.appEvents.trigger("header:show-topic", topic);
+ this._lastShowTopic = true;
+ },
- _updateTopic(topic, debounceDuration) {
- if (topic === null) {
- this._hideTopicInHeader();
-
- if (debounceDuration && !this.pauseHeaderTopicUpdate) {
- this.pauseHeaderTopicUpdate = true;
- this._lastShowTopic = true;
-
- discourseLater(() => {
- this._lastShowTopic = false;
- this.pauseHeaderTopicUpdate = false;
- }, debounceDuration);
- }
-
- return;
- }
-
- const offset = window.pageYOffset || document.documentElement.scrollTop;
- this._lastShowTopic = this.shouldShowTopicInHeader(topic, offset);
-
- if (this._lastShowTopic) {
- this._showTopicInHeader(topic);
- } else {
- this._hideTopicInHeader();
- }
- },
-
- didInsertElement() {
- this._super(...arguments);
-
- this.bindScrolling();
- window.addEventListener("resize", this.scrolled);
- $(this.element).on(
- "click.discourse-redirect",
- ".cooked a, a.track-link",
- (e) => ClickTrack.trackClick(e, getOwner(this))
- );
- this.appEvents.on("discourse:focus-changed", this, "gotFocus");
- this.appEvents.on("post:highlight", this, "_highlightPost");
- this.appEvents.on("header:update-topic", this, "_updateTopic");
- },
-
- willDestroyElement() {
- this._super(...arguments);
-
- this.unbindScrolling();
- window.removeEventListener("resize", this.scrolled);
-
- // Unbind link tracking
- $(this.element).off(
- "click.discourse-redirect",
- ".cooked a, a.track-link"
- );
-
- this.resetExamineDockCache();
-
- // this happens after route exit, stuff could have trickled in
+ _updateTopic(topic, debounceDuration) {
+ if (topic === null) {
this._hideTopicInHeader();
- this.appEvents.off("discourse:focus-changed", this, "gotFocus");
- this.appEvents.off("post:highlight", this, "_highlightPost");
- this.appEvents.off("header:update-topic", this, "_updateTopic");
- },
- gotFocus(hasFocus) {
- if (hasFocus) {
- this.scrolled();
- }
- },
+ if (debounceDuration && !this.pauseHeaderTopicUpdate) {
+ this.pauseHeaderTopicUpdate = true;
+ this._lastShowTopic = true;
- resetExamineDockCache() {
- this.set("dockAt", 0);
- },
-
- shouldShowTopicInHeader(topic, offset) {
- // On mobile, we show the header topic if the user has scrolled past the topic
- // title and the current scroll direction is down
- // On desktop the user only needs to scroll past the topic title.
- return (
- offset > this.dockAt &&
- (!this.site.mobileView || this.mobileScrollDirection === "down")
- );
- },
-
- // The user has scrolled the window, or it is finished rendering and ready for processing.
- @bind
- scrolled() {
- if (this.isDestroyed || this.isDestroying || this._state !== "inDOM") {
- return;
+ discourseLater(() => {
+ this._lastShowTopic = false;
+ this.pauseHeaderTopicUpdate = false;
+ }, debounceDuration);
}
- const offset = window.pageYOffset || document.documentElement.scrollTop;
- if (this.dockAt === 0) {
- const title = document.querySelector("#topic-title");
- if (title) {
- this.set(
- "dockAt",
- title.getBoundingClientRect().top + window.scrollY
- );
- }
+ return;
+ }
+
+ const offset = window.pageYOffset || document.documentElement.scrollTop;
+ this._lastShowTopic = this.shouldShowTopicInHeader(topic, offset);
+
+ if (this._lastShowTopic) {
+ this._showTopicInHeader(topic);
+ } else {
+ this._hideTopicInHeader();
+ }
+ },
+
+ didInsertElement() {
+ this._super(...arguments);
+
+ this.bindScrolling();
+ window.addEventListener("resize", this.scrolled);
+ $(this.element).on(
+ "click.discourse-redirect",
+ ".cooked a, a.track-link",
+ (e) => ClickTrack.trackClick(e, getOwner(this))
+ );
+ this.appEvents.on("discourse:focus-changed", this, "gotFocus");
+ this.appEvents.on("post:highlight", this, "_highlightPost");
+ this.appEvents.on("header:update-topic", this, "_updateTopic");
+ },
+
+ willDestroyElement() {
+ this._super(...arguments);
+
+ this.unbindScrolling();
+ window.removeEventListener("resize", this.scrolled);
+
+ // Unbind link tracking
+ $(this.element).off("click.discourse-redirect", ".cooked a, a.track-link");
+
+ this.resetExamineDockCache();
+
+ // this happens after route exit, stuff could have trickled in
+ this._hideTopicInHeader();
+ this.appEvents.off("discourse:focus-changed", this, "gotFocus");
+ this.appEvents.off("post:highlight", this, "_highlightPost");
+ this.appEvents.off("header:update-topic", this, "_updateTopic");
+ },
+
+ gotFocus(hasFocus) {
+ if (hasFocus) {
+ this.scrolled();
+ }
+ },
+
+ resetExamineDockCache() {
+ this.set("dockAt", 0);
+ },
+
+ shouldShowTopicInHeader(topic, offset) {
+ // On mobile, we show the header topic if the user has scrolled past the topic
+ // title and the current scroll direction is down
+ // On desktop the user only needs to scroll past the topic title.
+ return (
+ offset > this.dockAt &&
+ (!this.site.mobileView || this.mobileScrollDirection === "down")
+ );
+ },
+
+ // The user has scrolled the window, or it is finished rendering and ready for processing.
+ @bind
+ scrolled() {
+ if (this.isDestroyed || this.isDestroying || this._state !== "inDOM") {
+ return;
+ }
+
+ const offset = window.pageYOffset || document.documentElement.scrollTop;
+ if (this.dockAt === 0) {
+ const title = document.querySelector("#topic-title");
+ if (title) {
+ this.set("dockAt", title.getBoundingClientRect().top + window.scrollY);
}
+ }
- this.set("hasScrolled", offset > 0);
+ this.set("hasScrolled", offset > 0);
- const showTopic = this.shouldShowTopicInHeader(this.topic, offset);
+ const showTopic = this.shouldShowTopicInHeader(this.topic, offset);
- if (showTopic !== this._lastShowTopic) {
- if (showTopic) {
- this._showTopicInHeader(this.topic);
- } else {
- if (!DiscourseURL.isJumpScheduled()) {
- const loadingNear =
- this.topic.get("postStream.loadingNearPost") || 1;
- if (loadingNear === 1) {
- this._hideTopicInHeader();
- }
+ if (showTopic !== this._lastShowTopic) {
+ if (showTopic) {
+ this._showTopicInHeader(this.topic);
+ } else {
+ if (!DiscourseURL.isJumpScheduled()) {
+ const loadingNear = this.topic.get("postStream.loadingNearPost") || 1;
+ if (loadingNear === 1) {
+ this._hideTopicInHeader();
}
}
}
+ }
- // Since the user has scrolled, we need to check the scroll direction on mobile.
- // We use throttle instead of debounce because we want the switch to occur
- // at the start of the scroll. This feels a lot more snappy compared to waiting
- // for the scroll to end if we debounce.
- if (this.site.mobileView && this.hasScrolled) {
- throttle(
- this,
- this.calculateDirection,
- offset,
- MOBILE_SCROLL_DIRECTION_CHECK_THROTTLE
- );
- }
-
- // Trigger a scrolled event
- this.appEvents.trigger("topic:scrolled", offset);
- },
-
- // We observe the scroll direction on mobile and if it's down, we show the topic
- // in the header, otherwise, we hide it.
- @observes("mobileScrollDirection")
- toggleMobileHeaderTopic() {
- return this.appEvents.trigger(
- "header:update-topic",
- this.mobileScrollDirection === "down" ? this.topic : null
+ // Since the user has scrolled, we need to check the scroll direction on mobile.
+ // We use throttle instead of debounce because we want the switch to occur
+ // at the start of the scroll. This feels a lot more snappy compared to waiting
+ // for the scroll to end if we debounce.
+ if (this.site.mobileView && this.hasScrolled) {
+ throttle(
+ this,
+ this.calculateDirection,
+ offset,
+ MOBILE_SCROLL_DIRECTION_CHECK_THROTTLE
);
- },
- }
-);
+ }
+
+ // Trigger a scrolled event
+ this.appEvents.trigger("topic:scrolled", offset);
+ },
+
+ // We observe the scroll direction on mobile and if it's down, we show the topic
+ // in the header, otherwise, we hide it.
+ @observes("mobileScrollDirection")
+ toggleMobileHeaderTopic() {
+ return this.appEvents.trigger(
+ "header:update-topic",
+ this.mobileScrollDirection === "down" ? this.topic : null
+ );
+ },
+});
diff --git a/app/assets/javascripts/discourse/app/mixins/add-archetype-class.js b/app/assets/javascripts/discourse/app/mixins/add-archetype-class.js
deleted file mode 100644
index 2e6786547d8..00000000000
--- a/app/assets/javascripts/discourse/app/mixins/add-archetype-class.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { observes, on } from "discourse-common/utils/decorators";
-
-// Mix this in to a view that has a `archetype` property to automatically
-// add it to the body as the view is entered / left / model is changed.
-// This is used for keeping the `body` style in sync for the background image.
-export default {
- _cleanUp() {
- document.body.classList.forEach((name) => {
- if (/\barchetype-\S+/g.test(name)) {
- document.body.classList.remove(name);
- }
- });
- },
-
- @observes("archetype")
- @on("init")
- _archetypeChanged() {
- this._cleanUp();
-
- if (this.archetype) {
- document.body.classList.add(`archetype-${this.archetype}`);
- }
- },
-
- willDestroyElement() {
- this._super(...arguments);
-
- this._cleanUp();
- },
-};
diff --git a/app/assets/javascripts/discourse/app/templates/topic.hbs b/app/assets/javascripts/discourse/app/templates/topic.hbs
index b5cc8aec10a..03b2089df84 100644
--- a/app/assets/javascripts/discourse/app/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/app/templates/topic.hbs
@@ -17,6 +17,7 @@
@tags={{this.model.tags}}
/>