Files
discourse/app/assets/javascripts/discourse/widgets/post-menu.js.es6
romanrizzi 36425eb9f0 Revert "FEATURE: Publish read state on group messages. (#7989) [Undo revert] (#8024)"
This reverts commit 5dda5c2f7c360f78eb94398d9fed50c38d0b454d.
2019-08-20 13:29:22 -03:00

588 lines
14 KiB
JavaScript

import { applyDecorators, createWidget } from "discourse/widgets/widget";
import { avatarAtts } from "discourse/widgets/actions-summary";
import { h } from "virtual-dom";
import showModal from "discourse/lib/show-modal";
const LIKE_ACTION = 2;
function animateHeart($elem, start, end, complete) {
if (Ember.testing) {
return Ember.run(this, complete);
}
$elem
.stop()
.css("textIndent", start)
.animate(
{ textIndent: end },
{
complete,
step(now) {
$(this)
.css("transform", "scale(" + now + ")")
.addClass("d-liked")
.removeClass("d-unliked");
},
duration: 150
},
"linear"
);
}
const _builders = {};
const _extraButtons = {};
export function addButton(name, builder) {
_extraButtons[name] = builder;
}
function registerButton(name, builder) {
_builders[name] = builder;
}
export function buildButton(name, widget) {
let { attrs, state, siteSettings, settings } = widget;
let builder = _builders[name];
if (builder) {
let button = builder(attrs, state, siteSettings, settings);
if (button && !button.id) {
button.id = name;
}
return button;
}
}
function likeCount(attrs) {
const count = attrs.likeCount;
if (count > 0) {
const title = attrs.liked
? count === 1
? "post.has_likes_title_only_you"
: "post.has_likes_title_you"
: "post.has_likes_title";
let icon = attrs.yours ? "d-liked" : "";
let addContainer = attrs.yours;
const additionalClass = attrs.yours ? "my-likes" : "regular-likes";
if (!attrs.showLike) {
icon = attrs.yours ? "d-liked" : "d-unliked";
addContainer = true;
}
return {
action: "toggleWhoLiked",
title,
className: `button-count like-count highlight-action ${additionalClass}`,
contents: count,
icon,
iconRight: true,
addContainer,
titleOptions: { count: attrs.liked ? count - 1 : count }
};
}
}
registerButton("like-count", likeCount);
registerButton("like", attrs => {
if (!attrs.showLike) {
return likeCount(attrs);
}
const className = attrs.liked
? "toggle-like has-like fade-out"
: "toggle-like like";
const button = {
action: "like",
icon: attrs.liked ? "d-liked" : "d-unliked",
className,
before: "like-count"
};
// If the user has already liked the post and doesn't have permission
// to undo that operation, then indicate via the title that they've liked it
// and disable the button. Otherwise, set the title even if the user
// is anonymous (meaning they don't currently have permission to like);
// this is important for accessibility.
if (attrs.liked && !attrs.canToggleLike) {
button.title = "post.controls.has_liked";
button.disabled = true;
} else {
button.title = attrs.liked
? "post.controls.undo_like"
: "post.controls.like";
}
return button;
});
registerButton("flag-count", attrs => {
let className = "button-count";
if (attrs.reviewableScorePendingCount > 0) {
className += " has-pending";
}
return {
className,
contents: h("span", attrs.reviewableScoreCount.toString()),
url: `/review/${attrs.reviewableId}`
};
});
registerButton("flag", attrs => {
if (attrs.reviewableId || (attrs.canFlag && !attrs.hidden)) {
let button = {
action: "showFlags",
title: "post.controls.flag",
icon: "flag",
className: "create-flag"
};
if (attrs.reviewableId) {
button.before = "flag-count";
}
return button;
}
});
registerButton("edit", attrs => {
if (attrs.canEdit) {
return {
action: "editPost",
className: "edit",
title: "post.controls.edit",
icon: "pencil-alt",
alwaysShowYours: true
};
}
});
registerButton("reply-small", attrs => {
if (!attrs.canCreatePost) {
return;
}
const args = {
action: "replyToPost",
title: "post.controls.reply",
icon: "reply",
className: "reply"
};
return args;
});
registerButton("wiki-edit", attrs => {
if (attrs.canEdit) {
const args = {
action: "editPost",
className: "edit create",
title: "post.controls.edit",
icon: "far-edit",
alwaysShowYours: true
};
if (!attrs.mobileView) {
args.label = "post.controls.edit_action";
}
return args;
}
});
registerButton("replies", (attrs, state, siteSettings) => {
const replyCount = attrs.replyCount;
if (!replyCount) {
return;
}
// Omit replies if the setting `suppress_reply_directly_below` is enabled
if (
replyCount === 1 &&
attrs.replyDirectlyBelow &&
siteSettings.suppress_reply_directly_below
) {
return;
}
return {
action: "toggleRepliesBelow",
className: "show-replies",
icon: state.repliesShown ? "chevron-up" : "chevron-down",
titleOptions: { count: replyCount },
title: "post.has_replies",
labelOptions: { count: replyCount },
label: "post.has_replies",
iconRight: true
};
});
registerButton("share", attrs => {
return {
action: "share",
className: "share",
title: "post.controls.share",
icon: "link",
data: {
"share-url": attrs.shareUrl,
"post-number": attrs.post_number
}
};
});
registerButton("reply", (attrs, state, siteSettings, postMenuSettings) => {
const args = {
action: "replyToPost",
title: "post.controls.reply",
icon: "reply",
className: "reply create fade-out"
};
if (!attrs.canCreatePost) {
return;
}
if (postMenuSettings.showReplyTitleOnMobile || !attrs.mobileView) {
args.label = "topic.reply.title";
}
return args;
});
registerButton("bookmark", attrs => {
if (!attrs.canBookmark) {
return;
}
let className = "bookmark";
if (attrs.bookmarked) {
className += " bookmarked";
}
return {
id: attrs.bookmarked ? "unbookmark" : "bookmark",
action: "toggleBookmark",
title: attrs.bookmarked ? "bookmarks.created" : "bookmarks.not_bookmarked",
className,
icon: "bookmark"
};
});
registerButton("admin", attrs => {
if (!attrs.canManage && !attrs.canWiki) {
return;
}
return {
action: "openAdminMenu",
title: "post.controls.admin",
className: "show-post-admin-menu",
icon: "wrench"
};
});
registerButton("delete", attrs => {
if (attrs.canRecoverTopic) {
return {
id: "recover_topic",
action: "recoverPost",
title: "topic.actions.recover",
icon: "undo",
className: "recover"
};
} else if (attrs.canDeleteTopic) {
return {
id: "delete_topic",
action: "deletePost",
title: "post.controls.delete_topic",
icon: "far-trash-alt",
className: "delete"
};
} else if (attrs.canRecover) {
return {
id: "recover",
action: "recoverPost",
title: "post.controls.undelete",
icon: "undo",
className: "recover"
};
} else if (attrs.canDelete) {
return {
id: "delete",
action: "deletePost",
title: "post.controls.delete",
icon: "far-trash-alt",
className: "delete"
};
} else if (attrs.showFlagDelete) {
return {
id: "delete_topic",
action: "showDeleteTopicModal",
title: "post.controls.delete_topic_disallowed",
icon: "far-trash-alt",
className: "delete"
};
}
});
function replaceButton(buttons, find, replace) {
const idx = buttons.indexOf(find);
if (idx !== -1) {
buttons[idx] = replace;
}
}
export default createWidget("post-menu", {
tagName: "section.post-menu-area.clearfix",
settings: {
collapseButtons: true,
buttonType: "flat-button",
showReplyTitleOnMobile: false
},
defaultState() {
return { collapsed: true, likedUsers: [], adminVisible: false };
},
buildKey: attrs => `post-menu-${attrs.id}`,
attachButton(name) {
let buttonAtts = buildButton(name, this);
if (buttonAtts) {
let button = this.attach(this.settings.buttonType, buttonAtts);
if (buttonAtts.before) {
let before = this.attachButton(buttonAtts.before);
return h("div.double-button", [before, button]);
} else if (buttonAtts.addContainer) {
return h("div.double-button", [button]);
}
return button;
}
},
menuItems() {
let result = this.siteSettings.post_menu.split("|");
return result;
},
html(attrs, state) {
const { currentUser, keyValueStore, siteSettings } = this;
const hiddenSetting = siteSettings.post_menu_hidden_items || "";
const hiddenButtons = hiddenSetting
.split("|")
.filter(s => !attrs.bookmarked || s !== "bookmark");
if (currentUser && keyValueStore) {
const likedPostId = keyValueStore.getInt("likedPostId");
if (likedPostId === attrs.id) {
keyValueStore.remove("likedPostId");
Ember.run.next(() => this.sendWidgetAction("toggleLike"));
}
}
const allButtons = [];
let visibleButtons = [];
const orderedButtons = this.menuItems();
// If the post is a wiki, make Edit more prominent
if (attrs.wiki && attrs.canEdit) {
replaceButton(orderedButtons, "edit", "reply-small");
replaceButton(orderedButtons, "reply", "wiki-edit");
}
orderedButtons.forEach(i => {
const button = this.attachButton(i, attrs);
if (button) {
allButtons.push(button);
if (
(attrs.yours && button.attrs && button.attrs.alwaysShowYours) ||
(attrs.reviewableId && i === "flag") ||
hiddenButtons.indexOf(i) === -1
) {
visibleButtons.push(button);
}
}
});
if (!this.settings.collapseButtons) {
visibleButtons = allButtons;
}
// Only show ellipsis if there is more than one button hidden
// if there are no more buttons, we are not collapsed
if (!state.collapsed || allButtons.length <= visibleButtons.length + 1) {
visibleButtons = allButtons;
if (state.collapsed) {
state.collapsed = false;
}
} else {
const showMore = this.attach("flat-button", {
action: "showMoreActions",
title: "show_more",
className: "show-more-actions",
icon: "ellipsis-h"
});
visibleButtons.splice(visibleButtons.length - 1, 0, showMore);
}
Object.values(_extraButtons).forEach(builder => {
if (builder) {
const buttonAtts = builder(attrs, this.state, this.siteSettings);
if (buttonAtts) {
const { position, beforeButton, afterButton } = buttonAtts;
delete buttonAtts.position;
let button = this.attach(this.settings.buttonType, buttonAtts);
const content = [];
if (beforeButton) {
content.push(beforeButton(h));
}
content.push(button);
if (afterButton) {
content.push(afterButton(h));
}
button = h("span.extra-buttons", content);
if (button) {
switch (position) {
case "first":
visibleButtons.unshift(button);
break;
case "second":
visibleButtons.splice(1, 0, button);
break;
case "second-last-hidden":
if (!state.collapsed) {
visibleButtons.splice(visibleButtons.length - 2, 0, button);
}
break;
default:
visibleButtons.push(button);
break;
}
}
}
}
});
const postControls = [];
const repliesButton = this.attachButton("replies", attrs);
if (repliesButton) {
postControls.push(repliesButton);
}
let extraControls = applyDecorators(this, "extra-controls", attrs, state);
postControls.push(h("div.actions", visibleButtons.concat(extraControls)));
if (state.adminVisible) {
postControls.push(this.attach("post-admin-menu", attrs));
}
const contents = [
h(
"nav.post-controls.clearfix" +
(this.state.collapsed ? ".collapsed" : ".expanded"),
postControls
)
];
if (state.likedUsers.length) {
const remaining = state.total - state.likedUsers.length;
contents.push(
this.attach("small-user-list", {
users: state.likedUsers,
addSelf: attrs.liked && remaining === 0,
listClassName: "who-liked",
description:
remaining > 0
? "post.actions.people.like_capped"
: "post.actions.people.like",
count: remaining
})
);
}
return contents;
},
openAdminMenu() {
this.state.adminVisible = true;
},
closeAdminMenu() {
this.state.adminVisible = false;
},
showDeleteTopicModal() {
showModal("delete-topic-disallowed");
},
showMoreActions() {
this.state.collapsed = false;
if (!this.state.likedUsers.length) {
return this.getWhoLiked();
}
},
like() {
const { attrs, currentUser, keyValueStore } = this;
if (!currentUser) {
keyValueStore &&
keyValueStore.set({ key: "likedPostId", value: attrs.id });
return this.sendWidgetAction("showLogin");
}
if (attrs.liked) {
return this.sendWidgetAction("toggleLike");
}
const $heart = $(`[data-post-id=${attrs.id}] .toggle-like .d-icon`);
$heart.closest("button").addClass("has-like");
const scale = [1.0, 1.5];
return new Ember.RSVP.Promise(resolve => {
animateHeart($heart, scale[0], scale[1], () => {
animateHeart($heart, scale[1], scale[0], () => {
this.sendWidgetAction("toggleLike").then(() => resolve());
});
});
});
},
refreshLikes() {
if (this.state.likedUsers.length) {
return this.getWhoLiked();
}
},
getWhoLiked() {
const { attrs, state } = this;
return this.store
.find("post-action-user", {
id: attrs.id,
post_action_type_id: LIKE_ACTION
})
.then(users => {
state.likedUsers = users.map(avatarAtts);
state.total = users.totalRows;
});
},
toggleWhoLiked() {
const state = this.state;
if (state.likedUsers.length) {
state.likedUsers = [];
} else {
return this.getWhoLiked();
}
}
});