This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/app/widgets/post-stream.js
Robin Ward 78852e9754 FIX: Tests should never cloak posts
Depending on the load order of modules, the post cloaking code might
not be disabled properly in test mode, which results in flakey failures.
2022-01-21 14:32:26 -05:00

317 lines
8.4 KiB
JavaScript

import DiscourseURL from "discourse/lib/url";
import I18n from "I18n";
import { Placeholder } from "discourse/lib/posts-with-placeholders";
import { addWidgetCleanCallback } from "discourse/components/mount-widget";
import { avatarFor } from "discourse/widgets/post";
import { createWidget } from "discourse/widgets/widget";
import discourseDebounce from "discourse-common/lib/debounce";
import { h } from "virtual-dom";
import { iconNode } from "discourse-common/lib/icon-library";
import transformPost from "discourse/lib/transform-post";
let transformCallbacks = null;
export function postTransformCallbacks(transformed) {
if (transformCallbacks === null) {
return;
}
for (let i = 0; i < transformCallbacks.length; i++) {
transformCallbacks[i].call(this, transformed);
}
}
export function addPostTransformCallback(callback) {
transformCallbacks = transformCallbacks || [];
transformCallbacks.push(callback);
}
let _enabled = true;
const DAY = 1000 * 60 * 60 * 24;
const _dontCloak = {};
let _cloaked = {};
let _heights = {};
export function disableCloaking() {
_enabled = false;
}
export function preventCloak(postId) {
_dontCloak[postId] = true;
}
export function cloak(post, component) {
if (!_enabled || _cloaked[post.id] || _dontCloak[post.id]) {
return;
}
const $post = $(`#post_${post.post_number}`).parent();
_cloaked[post.id] = true;
_heights[post.id] = $post.outerHeight();
component.dirtyKeys.keyDirty(`post-${post.id}`);
discourseDebounce(component, "queueRerender", 1000);
}
export function uncloak(post, component) {
if (!_enabled || !_cloaked[post.id]) {
return;
}
_cloaked[post.id] = null;
component.dirtyKeys.keyDirty(`post-${post.id}`);
component.queueRerender();
}
addWidgetCleanCallback("post-stream", () => {
_cloaked = {};
_heights = {};
});
createWidget("posts-filtered-notice", {
buildKey: (attrs) => `posts-filtered-notice-${attrs.id}`,
buildClasses() {
return ["posts-filtered-notice"];
},
html(attrs) {
const filters = attrs.streamFilters;
if (filters.filter_upwards_post_id || filters.mixedHiddenPosts) {
return [
h(
"span.filtered-replies-viewing",
I18n.t("post.filtered_replies.viewing_subset")
),
this.attach("filter-show-all", attrs),
];
} else if (filters.replies_to_post_number) {
const sourcePost = attrs.posts.findBy(
"post_number",
filters.replies_to_post_number
);
return [
h(
"span.filtered-replies-viewing",
I18n.t("post.filtered_replies_viewing", {
count: sourcePost.reply_count,
})
),
h("span.filtered-user-row", [
h(
"span.filtered-avatar",
avatarFor.call(this, "small", {
template: sourcePost.avatar_template,
username: sourcePost.username,
url: sourcePost.usernameUrl,
})
),
this.attach("filter-jump-to-post", {
username: sourcePost.username,
postNumber: filters.replies_to_post_number,
}),
]),
this.attach("filter-show-all", attrs),
];
} else if (filters.filter && filters.filter === "summary") {
return [
h(
"span.filtered-replies-viewing",
I18n.t("post.filtered_replies.viewing_summary")
),
this.attach("filter-show-all", attrs),
];
} else if (filters.username_filters) {
const firstUserPost = attrs.posts[1],
userPostsCount = parseInt(attrs.filteredPostsCount, 10) - 1;
return [
h(
"span.filtered-replies-viewing",
I18n.t("post.filtered_replies.viewing_posts_by", {
post_count: userPostsCount,
})
),
h(
"span.filtered-avatar",
avatarFor.call(this, "small", {
template: firstUserPost.avatar_template,
username: firstUserPost.username,
url: firstUserPost.usernameUrl,
})
),
this.attach("poster-name", firstUserPost),
this.attach("filter-show-all", attrs),
];
}
return [];
},
});
createWidget("filter-jump-to-post", {
tagName: "a.filtered-jump-to-post",
buildKey: (attrs) => `jump-to-post-${attrs.id}`,
html(attrs) {
return I18n.t("post.filtered_replies.post_number", {
username: attrs.username,
post_number: attrs.postNumber,
});
},
click() {
DiscourseURL.jumpToPost(this.attrs.postNumber);
},
});
createWidget("filter-show-all", {
tagName: "button.filtered-replies-show-all",
buildKey: (attrs) => `filtered-show-all-${attrs.id}`,
buildClasses() {
return ["btn", "btn-primary"];
},
html() {
return [iconNode("arrows-alt-v"), I18n.t("post.filtered_replies.show_all")];
},
click() {
this.sendWidgetAction("cancelFilter");
this.appEvents.trigger(
"post-stream:filter-show-all",
this.attrs.streamFilters
);
},
});
export default createWidget("post-stream", {
tagName: "div.post-stream",
html(attrs) {
const posts = attrs.posts || [];
const postArray = posts.toArray();
const postArrayLength = postArray.length;
const maxPostNumber =
postArrayLength > 0 ? postArray[postArrayLength - 1].post_number : 0;
const result = [];
const before = attrs.gaps && attrs.gaps.before ? attrs.gaps.before : {};
const after = attrs.gaps && attrs.gaps.after ? attrs.gaps.after : {};
const mobileView = this.site.mobileView;
let prevPost;
let prevDate;
for (let i = 0; i < postArrayLength; i++) {
const post = postArray[i];
if (post instanceof Placeholder) {
result.push(this.attach("post-placeholder"));
continue;
}
const nextPost = i < postArray.length - 1 ? postArray[i + 1] : null;
const transformed = transformPost(
this.currentUser,
this.site,
post,
prevPost,
nextPost
);
transformed.canCreatePost = attrs.canCreatePost;
transformed.mobileView = mobileView;
if (transformed.canManage || transformed.canSplitMergeTopic) {
transformed.multiSelect = attrs.multiSelect;
if (attrs.multiSelect) {
transformed.selected = attrs.selectedQuery(post);
}
}
if (attrs.searchService) {
transformed.highlightTerm = attrs.searchService.highlightTerm;
}
// Post gap - before
const beforeGap = before[post.id];
if (beforeGap) {
result.push(
this.attach(
"post-gap",
{ pos: "before", postId: post.id, gap: beforeGap },
{ model: post }
)
);
}
// Handle time gaps
const curTime = new Date(transformed.created_at).getTime();
if (prevDate) {
const daysSince = Math.floor((curTime - prevDate) / DAY);
if (daysSince > this.siteSettings.show_time_gap_days) {
result.push(this.attach("time-gap", { daysSince }));
}
}
prevDate = curTime;
transformed.height = _heights[post.id];
transformed.cloaked = _cloaked[post.id];
postTransformCallbacks(transformed);
if (transformed.isSmallAction) {
result.push(
this.attach("post-small-action", transformed, { model: post })
);
} else {
transformed.showReadIndicator = attrs.showReadIndicator;
result.push(this.attach("post", transformed, { model: post }));
}
// Post gap - after
const afterGap = after[post.id];
if (afterGap) {
result.push(
this.attach(
"post-gap",
{ pos: "after", postId: post.id, gap: afterGap },
{ model: post }
)
);
}
if (
i !== postArrayLength - 1 &&
maxPostNumber <= attrs.highestPostNumber &&
attrs.lastReadPostNumber === post.post_number
) {
result.push(
this.attach("topic-post-visited-line", {
post_number: post.post_number,
})
);
}
prevPost = post;
}
if (
attrs.streamFilters &&
Object.keys(attrs.streamFilters).length &&
(Object.keys(before).length > 0 || Object.keys(after).length > 0)
) {
result.push(
this.attach("posts-filtered-notice", {
posts: postArray,
streamFilters: attrs.streamFilters,
filteredPostsCount: attrs.filteredPostsCount,
})
);
}
return result;
},
});