mirror of
https://github.com/discourse/discourse.git
synced 2025-05-25 19:29:34 +08:00
218 lines
6.5 KiB
JavaScript
218 lines
6.5 KiB
JavaScript
import DiscourseURL from "discourse/lib/url";
|
|
import AddArchetypeClass from "discourse/mixins/add-archetype-class";
|
|
import ClickTrack from "discourse/lib/click-track";
|
|
import Scrolling from "discourse/mixins/scrolling";
|
|
import { selectedText } from "discourse/lib/utilities";
|
|
import { observes } from "ember-addons/ember-computed-decorators";
|
|
|
|
function highlight(postNumber) {
|
|
const $contents = $(`#post_${postNumber} .topic-body`);
|
|
|
|
$contents.addClass("highlighted");
|
|
$contents.on("animationend", () => $contents.removeClass("highlighted"));
|
|
}
|
|
|
|
// used to determine scroll direction on mobile
|
|
let lastScroll, scrollDirection, delta;
|
|
|
|
export default Ember.Component.extend(AddArchetypeClass, Scrolling, {
|
|
userFilters: Ember.computed.alias("topic.userFilters"),
|
|
classNameBindings: [
|
|
"multiSelect",
|
|
"topic.archetype",
|
|
"topic.is_warning",
|
|
"topic.category.read_restricted:read_restricted",
|
|
"topic.deleted:deleted-topic",
|
|
"topic.categoryClass"
|
|
],
|
|
menuVisible: true,
|
|
SHORT_POST: 1200,
|
|
|
|
postStream: Ember.computed.alias("topic.postStream"),
|
|
archetype: Ember.computed.alias("topic.archetype"),
|
|
dockAt: 0,
|
|
|
|
_lastShowTopic: null,
|
|
|
|
@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.
|
|
const enteredAt = this.get("enteredAt");
|
|
if (enteredAt && this.get("lastEnteredAt") !== enteredAt) {
|
|
this._lastShowTopic = null;
|
|
Ember.run.schedule("afterRender", () => this.scrolled());
|
|
this.set("lastEnteredAt", enteredAt);
|
|
}
|
|
},
|
|
|
|
didInsertElement() {
|
|
this._super(...arguments);
|
|
this.bindScrolling({ name: "topic-view" });
|
|
|
|
$(window).on("resize.discourse-on-scroll", () => this.scrolled());
|
|
|
|
this.$().on(
|
|
"mouseup.discourse-redirect",
|
|
".cooked a, a.track-link",
|
|
function(e) {
|
|
// bypass if we are selecting stuff
|
|
const selection = window.getSelection && window.getSelection();
|
|
if (selection.type === "Range" || selection.rangeCount > 0) {
|
|
if (selectedText() !== "") {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const $target = $(e.target);
|
|
if (
|
|
$target.hasClass("mention") ||
|
|
$target.parents(".expanded-embed").length
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return ClickTrack.trackClick(e);
|
|
}
|
|
);
|
|
|
|
this.appEvents.on("post:highlight", postNumber => {
|
|
Ember.run.scheduleOnce("afterRender", null, highlight, postNumber);
|
|
});
|
|
|
|
this.appEvents.on("header:update-topic", topic => {
|
|
if (topic === null) {
|
|
this._lastShowTopic = false;
|
|
this.appEvents.trigger("header:hide-topic");
|
|
return;
|
|
}
|
|
|
|
const offset = window.pageYOffset || $("html").scrollTop();
|
|
this._lastShowTopic = this.showTopicInHeader(topic, offset);
|
|
|
|
if (this._lastShowTopic) {
|
|
this.appEvents.trigger("header:show-topic", topic);
|
|
} else {
|
|
this.appEvents.trigger("header:hide-topic");
|
|
}
|
|
});
|
|
// setup mobile scroll logo
|
|
if (this.site.mobileView) {
|
|
this.appEvents.on("topic:scrolled", offset =>
|
|
this.mobileScrollGaurd(offset)
|
|
);
|
|
// used to animate header contents on scroll
|
|
this.appEvents.on("header:show-topic", () => {
|
|
$("header.d-header")
|
|
.removeClass("scroll-up")
|
|
.addClass("scroll-down");
|
|
});
|
|
this.appEvents.on("header:hide-topic", () => {
|
|
$("header.d-header")
|
|
.removeClass("scroll-down")
|
|
.addClass("scroll-up");
|
|
});
|
|
}
|
|
},
|
|
|
|
willDestroyElement() {
|
|
this._super(...arguments);
|
|
this.unbindScrolling("topic-view");
|
|
$(window).unbind("resize.discourse-on-scroll");
|
|
|
|
// Unbind link tracking
|
|
this.$().off("mouseup.discourse-redirect", ".cooked a, a.track-link");
|
|
|
|
this.resetExamineDockCache();
|
|
|
|
// this happens after route exit, stuff could have trickled in
|
|
this.appEvents.trigger("header:hide-topic");
|
|
this.appEvents.off("post:highlight");
|
|
// mobile scroll logo clean up.
|
|
if (this.site.mobileView) {
|
|
this.appEvents.off("topic:scrolled");
|
|
$("header.d-header").removeClass("scroll-down scroll-up");
|
|
}
|
|
},
|
|
|
|
@observes("Discourse.hasFocus")
|
|
gotFocus() {
|
|
if (Discourse.get("hasFocus")) {
|
|
this.scrolled();
|
|
}
|
|
},
|
|
|
|
resetExamineDockCache() {
|
|
this.set("dockAt", 0);
|
|
},
|
|
|
|
showTopicInHeader(topic, offset) {
|
|
// conditions for showing topic title in the header for mobile
|
|
if (
|
|
this.site.mobileView &&
|
|
scrollDirection !== "up" &&
|
|
offset > this.dockAt
|
|
) {
|
|
return true;
|
|
// condition for desktops
|
|
} else {
|
|
return offset > this.dockAt;
|
|
}
|
|
},
|
|
// The user has scrolled the window, or it is finished rendering and ready for processing.
|
|
scrolled() {
|
|
if (this.isDestroyed || this.isDestroying || this._state !== "inDOM") {
|
|
return;
|
|
}
|
|
|
|
const offset = window.pageYOffset || $("html").scrollTop();
|
|
if (this.get("dockAt") === 0) {
|
|
const title = $("#topic-title");
|
|
if (title && title.length === 1) {
|
|
this.set("dockAt", title.offset().top);
|
|
}
|
|
}
|
|
|
|
this.set("hasScrolled", offset > 0);
|
|
|
|
const topic = this.get("topic");
|
|
const showTopic = this.showTopicInHeader(topic, offset);
|
|
if (showTopic !== this._lastShowTopic) {
|
|
if (showTopic) {
|
|
this.appEvents.trigger("header:show-topic", topic);
|
|
this._lastShowTopic = true;
|
|
} else {
|
|
if (!DiscourseURL.isJumpScheduled()) {
|
|
const loadingNear = topic.get("postStream.loadingNearPost") || 1;
|
|
if (loadingNear === 1) {
|
|
this.appEvents.trigger("header:hide-topic");
|
|
this._lastShowTopic = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Trigger a scrolled event
|
|
this.appEvents.trigger("topic:scrolled", offset);
|
|
},
|
|
|
|
// determines scroll direction, triggers header topic info on mobile
|
|
// and ensures that the switch happens only once per scroll direction change
|
|
mobileScrollGaurd(offset) {
|
|
// user hasn't scrolled past topic title.
|
|
if (offset < this.dockAt) return;
|
|
|
|
delta = offset - lastScroll;
|
|
// 3px buffer so that the switch doesn't happen with tiny scrolls
|
|
if (delta > 3 && scrollDirection !== "down") {
|
|
scrollDirection = "down";
|
|
this.appEvents.trigger("header:show-topic", this.topic);
|
|
} else if (delta < -3 && scrollDirection !== "up") {
|
|
scrollDirection = "up";
|
|
this.appEvents.trigger("header:hide-topic");
|
|
}
|
|
lastScroll = offset;
|
|
}
|
|
});
|