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/widgets/post-menu.js.es6
2018-05-24 11:08:13 -04:00

453 lines
12 KiB
JavaScript

import { applyDecorators, createWidget } from 'discourse/widgets/widget';
import { avatarAtts } from 'discourse/widgets/actions-summary';
import { h } from 'virtual-dom';
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;
}
}
registerButton('like', attrs => {
if (!attrs.showLike) { return; }
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
};
if (attrs.canToggleLike) {
button.title = attrs.liked ? 'post.controls.undo_like' : 'post.controls.like';
} else if (attrs.liked) {
button.title = 'post.controls.has_liked';
button.disabled = true;
}
return button;
});
registerButton('like-count', 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';
const icon = attrs.yours ? 'd-liked' : '';
const additionalClass = attrs.yours ? 'my-likes' : 'regular-likes';
return { action: 'toggleWhoLiked',
title,
className: `like-count highlight-action ${additionalClass}`,
contents: count,
icon,
iconRight: true,
titleOptions: {count: attrs.liked ? (count-1) : count }
};
}
});
registerButton('flag', attrs => {
if (attrs.canFlag) {
return { action: 'showFlags',
title: 'post.controls.flag',
icon: 'flag',
className: 'create-flag' };
}
});
registerButton('edit', attrs => {
if (attrs.canEdit) {
return {
action: 'editPost',
className: 'edit',
title: 'post.controls.edit',
icon: 'pencil',
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: 'pencil-square-o',
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: 'topic.actions.delete', icon: 'trash-o', 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: 'trash-o', 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) {
return this.attach(this.settings.buttonType, buttonAtts);
}
},
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) {
replaceButton(orderedButtons, 'edit', 'reply-small');
replaceButton(orderedButtons, 'reply', 'wiki-edit');
}
orderedButtons
.filter(x => x !== "like-count" && x !== "like")
.forEach(i => {
const button = this.attachButton(i, attrs);
if (button) {
allButtons.push(button);
if ((attrs.yours && button.attrs.alwaysShowYours) || (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);
}
visibleButtons.unshift(h('div.like-button', [
this.attachButton("like-count", attrs),
this.attachButton("like", attrs)
]));
Object.keys(_extraButtons).forEach(k => {
const builder = _extraButtons[k];
if (builder) {
const buttonAtts = builder(attrs, this.state, this.siteSettings);
if (buttonAtts) {
const { position, beforeButton } = buttonAtts;
delete buttonAtts.position;
let button = this.attach(this.settings.buttonType, buttonAtts);
if (beforeButton) {
button = h('span', [beforeButton(h), button]);
}
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', 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;
},
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();
}
},
});