import PostCooked from "discourse/widgets/post-cooked"; import DecoratorHelper from "discourse/widgets/decorator-helper"; import { createWidget, applyDecorators } from "discourse/widgets/widget"; import RawHtml from "discourse/widgets/raw-html"; import { iconNode } from "discourse-common/lib/icon-library"; import { transformBasicPost } from "discourse/lib/transform-post"; import { postTransformCallbacks } from "discourse/widgets/post-stream"; import { h } from "virtual-dom"; import DiscourseURL from "discourse/lib/url"; import { dateNode } from "discourse/helpers/node"; import { translateSize, avatarUrl, formatUsername } from "discourse/lib/utilities"; import hbs from "discourse/widgets/hbs-compiler"; import { relativeAgeMediumSpan } from "discourse/lib/formatter"; import { prioritizeNameInUx } from "discourse/lib/settings"; import { Promise } from "rsvp"; function transformWithCallbacks(post) { let transformed = transformBasicPost(post); postTransformCallbacks(transformed); return transformed; } export function avatarImg(wanted, attrs) { const size = translateSize(wanted); const url = avatarUrl(attrs.template, size); // We won't render an invalid url if (!url || url.length === 0) { return; } const title = attrs.name || formatUsername(attrs.username); let className = "avatar" + (attrs.extraClasses ? " " + attrs.extraClasses : ""); const properties = { attributes: { alt: "", width: size, height: size, src: Discourse.getURLWithCDN(url), title }, className }; return h("img", properties); } export function avatarFor(wanted, attrs) { return h( "a", { className: `trigger-user-card ${attrs.className || ""}`, attributes: { href: attrs.url, "data-user-card": attrs.username } }, avatarImg(wanted, attrs) ); } // TODO: Improve how helpers are registered for vdom compliation if (typeof Discourse !== "undefined") { Discourse.__widget_helpers.avatar = avatarFor; } createWidget("select-post", { tagName: "div.select-posts", html(attrs) { const buttons = []; if (!attrs.selected && attrs.post_number > 1) { if (attrs.replyCount > 0) { buttons.push( this.attach("button", { label: "topic.multi_select.select_replies.label", title: "topic.multi_select.select_replies.title", action: "selectReplies", className: "select-replies" }) ); } buttons.push( this.attach("button", { label: "topic.multi_select.select_below.label", title: "topic.multi_select.select_below.title", action: "selectBelow", className: "select-below" }) ); } const key = `topic.multi_select.${ attrs.selected ? "selected" : "select" }_post`; buttons.push( this.attach("button", { label: key + ".label", title: key + ".title", action: "togglePostSelection", className: "select-post" }) ); return buttons; } }); createWidget("reply-to-tab", { tagName: "a.reply-to-tab", buildKey: attrs => `reply-to-tab-${attrs.id}`, defaultState() { return { loading: false }; }, html(attrs, state) { if (state.loading) { return I18n.t("loading"); } return [ iconNode("share"), " ", avatarImg("small", { template: attrs.replyToAvatarTemplate, username: attrs.replyToUsername }), " ", h("span", formatUsername(attrs.replyToUsername)) ]; }, click() { this.state.loading = true; this.sendWidgetAction("toggleReplyAbove").then( () => (this.state.loading = false) ); } }); createWidget("post-avatar-user-info", { tagName: "div.post-avatar-user-info", html(attrs) { return this.attach("poster-name", attrs); } }); createWidget("post-avatar", { tagName: "div.topic-avatar", settings: { size: "large", displayPosterName: false }, html(attrs) { let body; if (!attrs.user_id) { body = iconNode("far-trash-alt", { class: "deleted-user-avatar" }); } else { body = avatarFor.call(this, this.settings.size, { template: attrs.avatar_template, username: attrs.username, name: attrs.name, url: attrs.usernameUrl, className: "main-avatar" }); } const result = [body]; if (attrs.primary_group_flair_url || attrs.primary_group_flair_bg_color) { result.push(this.attach("avatar-flair", attrs)); } result.push(h("div.poster-avatar-extra")); if (this.settings.displayPosterName) { result.push(this.attach("post-avatar-user-info", attrs)); } return result; } }); createWidget("post-locked-indicator", { tagName: "div.post-info.post-locked", template: hbs`{{d-icon "lock"}}`, title: () => I18n.t("post.locked") }); createWidget("post-email-indicator", { tagName: "div.post-info.via-email", title(attrs) { return attrs.isAutoGenerated ? I18n.t("post.via_auto_generated_email") : I18n.t("post.via_email"); }, buildClasses(attrs) { return attrs.canViewRawEmail ? "raw-email" : null; }, html(attrs) { return attrs.isAutoGenerated ? iconNode("envelope") : iconNode("far-envelope"); }, click() { if (this.attrs.canViewRawEmail) { this.sendWidgetAction("showRawEmail"); } } }); function showReplyTab(attrs, siteSettings) { return ( attrs.replyToUsername && (!attrs.replyDirectlyAbove || !siteSettings.suppress_reply_directly_above) ); } createWidget("post-meta-data", { tagName: "div.topic-meta-data", settings: { displayPosterName: true }, html(attrs) { let postInfo = []; if (attrs.isWhisper) { postInfo.push( h( "div.post-info.whisper", { attributes: { title: I18n.t("post.whisper") } }, iconNode("far-eye-slash") ) ); } const lastWikiEdit = attrs.wiki && attrs.lastWikiEdit && new Date(attrs.lastWikiEdit); const createdAt = new Date(attrs.created_at); const date = lastWikiEdit ? dateNode(lastWikiEdit) : dateNode(createdAt); const attributes = { class: "post-date", href: attrs.shareUrl, "data-share-url": attrs.shareUrl, "data-post-number": attrs.post_number }; if (lastWikiEdit) { attributes["class"] += " last-wiki-edit"; } if (attrs.via_email) { postInfo.push(this.attach("post-email-indicator", attrs)); } if (attrs.locked) { postInfo.push(this.attach("post-locked-indicator", attrs)); } if (attrs.version > 1 || attrs.wiki) { postInfo.push(this.attach("post-edits-indicator", attrs)); } if (attrs.multiSelect) { postInfo.push(this.attach("select-post", attrs)); } if (showReplyTab(attrs, this.siteSettings)) { postInfo.push(this.attach("reply-to-tab", attrs)); } postInfo.push(h("div.post-info.post-date", h("a", { attributes }, date))); postInfo.push( h( "div.read-state", { className: attrs.read ? "read" : null, attributes: { title: I18n.t("post.unread") } }, iconNode("circle") ) ); let result = []; if (this.settings.displayPosterName) { result.push(this.attach("poster-name", attrs)); } result.push(h("div.post-infos", postInfo)); return result; } }); createWidget("expand-hidden", { tagName: "a.expand-hidden", html() { return I18n.t("post.show_hidden"); }, click() { this.sendWidgetAction("expandHidden"); } }); createWidget("expand-post-button", { tagName: "button.btn.expand-post", buildKey: attrs => `expand-post-button-${attrs.id}`, defaultState() { return { loadingExpanded: false }; }, html(attrs, state) { if (state.loadingExpanded) { return I18n.t("loading"); } else { return [I18n.t("post.show_full"), "..."]; } }, click() { this.state.loadingExpanded = true; this.sendWidgetAction("expandFirstPost"); } }); createWidget("post-contents", { buildKey: attrs => `post-contents-${attrs.id}`, defaultState() { return { expandedFirstPost: false, repliesBelow: [] }; }, buildClasses(attrs) { const classes = ["regular"]; if (!this.state.repliesShown) { classes.push("contents"); } if (showReplyTab(attrs, this.siteSettings)) { classes.push("avoid-tab"); } return classes; }, html(attrs, state) { let result = [ new PostCooked(attrs, new DecoratorHelper(this), this.currentUser) ]; result = result.concat(applyDecorators(this, "after-cooked", attrs, state)); if (attrs.cooked_hidden) { result.push(this.attach("expand-hidden", attrs)); } if (!state.expandedFirstPost && attrs.expandablePost) { result.push(this.attach("expand-post-button", attrs)); } const extraState = { state: { repliesShown: !!state.repliesBelow.length } }; result.push(this.attach("post-menu", attrs, extraState)); const repliesBelow = state.repliesBelow; if (repliesBelow.length) { result.push( h("section.embedded-posts.bottom", [ repliesBelow.map(p => { return this.attach("embedded-post", p, { model: this.store.createRecord("post", p) }); }), this.attach("button", { title: "post.collapse", icon: "chevron-up", action: "toggleRepliesBelow", actionParam: "true", className: "btn collapse-up" }) ]) ); } return result; }, _date(attrs) { const lastWikiEdit = attrs.wiki && attrs.lastWikiEdit && new Date(attrs.lastWikiEdit); const createdAt = new Date(attrs.created_at); return lastWikiEdit ? lastWikiEdit : createdAt; }, toggleRepliesBelow(goToPost = "false") { if (this.state.repliesBelow.length) { this.state.repliesBelow = []; if (goToPost === "true") { DiscourseURL.routeTo( `${this.attrs.topicUrl}/${this.attrs.post_number}` ); } return; } const post = this.findAncestorModel(); const topicUrl = post ? post.get("topic.url") : null; return this.store .find("post-reply", { postId: this.attrs.id }) .then(posts => { this.state.repliesBelow = posts.map(p => { p.shareUrl = `${topicUrl}/${p.post_number}`; return transformWithCallbacks(p); }); }); }, expandFirstPost() { const post = this.findAncestorModel(); return post.expand().then(() => (this.state.expandedFirstPost = true)); } }); createWidget("post-notice", { tagName: "div.post-notice", buildClasses(attrs) { const classes = [attrs.noticeType.replace(/_/g, "-")]; if ( new Date() - new Date(attrs.created_at) > this.siteSettings.old_post_notice_days * 86400000 ) { classes.push("old"); } return classes; }, html(attrs) { const user = this.siteSettings.display_name_on_posts && prioritizeNameInUx(attrs.name) ? attrs.name : attrs.username; let text, icon; if (attrs.noticeType === "custom") { icon = "user-shield"; text = new RawHtml({ html: `