UX: Improve interface for flagging with many flaggers

This commit is contained in:
Robin Ward 2017-09-14 12:44:49 -04:00
parent 64fae87470
commit 079f108ceb
10 changed files with 189 additions and 171 deletions

View File

@ -1,6 +1,8 @@
import showModal from 'discourse/lib/show-modal'; import showModal from 'discourse/lib/show-modal';
export default Ember.Component.extend({ export default Ember.Component.extend({
expanded: false,
tagName: 'div', tagName: 'div',
classNameBindings: [ classNameBindings: [
':flagged-post', ':flagged-post',
@ -36,6 +38,12 @@ export default Ember.Component.extend({
defer() { defer() {
this.removeAfter(this.get('flaggedPost').deferFlags()); this.removeAfter(this.get('flaggedPost').deferFlags());
},
expand() {
this.get('flaggedPost').expandHidden().then(() => {
this.set('expanded', true);
});
} }
} }
}); });

View File

@ -66,4 +66,5 @@ export default Post.extend({
postHidden: Ember.computed.alias('hidden'), postHidden: Ember.computed.alias('hidden'),
deleted: Ember.computed.or('deleted_at', 'topic_deleted_at'), deleted: Ember.computed.or('deleted_at', 'topic_deleted_at'),
}); });

View File

@ -0,0 +1,18 @@
<div class='flag-user'>
{{#link-to 'adminUser' user.id user.username class='flag-user-avatar'}}
{{avatar user imageSize="small"}}
{{/link-to}}
<div class='flag-user-details'>
<div class='flag-user-who'>
{{#link-to 'adminUser' user.id user.username class="flag-user-username"}}
{{user.username}}
{{/link-to}}
<div class='flag-user-date'>
{{format-age date}}
</div>
</div>
<div class='flag-user-extra'>
{{yield}}
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
{{#link-to 'adminUser' response.user.id response.user.username class="response-avatar"}} {{#link-to 'adminUser' response.user.id response.user.username class="response-avatar"}}
{{avatar response.user imageSize="medium"}} {{avatar response.user imageSize="small"}}
{{/link-to}} {{/link-to}}
<div class='excerpt'>{{{response.excerpt}}}</div> <div class='excerpt'>{{{response.excerpt}}}</div>
{{#if hasMore}} {{#if hasMore}}

View File

@ -17,7 +17,8 @@
{{/if}} {{/if}}
</div> </div>
<div class="flagged-post-excerpt"> <div class="flagged-post-contents">
<div class='flagged-post-excerpt'>
{{#unless hideTitle}} {{#unless hideTitle}}
<h3> <h3>
{{#if flaggedPost.topic.isPrivateMessage}} {{#if flaggedPost.topic.isPrivateMessage}}
@ -28,86 +29,75 @@
</h3> </h3>
{{/unless}} {{/unless}}
{{#if flaggedPost.postAuthorFlagged}} {{#if flaggedPost.postAuthorFlagged}}
<p>{{{flaggedPost.excerpt}}}</p> {{#if expanded}}
{{{flaggedPost.cooked}}}
{{else}}
<p>
{{{flaggedPost.excerpt}}}
<a href {{action "expand"}}>{{i18n "admin.flags.show_full"}}</a>
</p>
{{/if}}
{{/if}} {{/if}}
</div> </div>
<div class='flaggers'> {{#if flaggedPost.topicFlagged}}
{{#if site.mobileView}}
<div class='flaggers-title'>
{{i18n 'admin.flags.flagged_by'}}
</div>
{{/if}}
{{#each flaggedPost.post_actions as |postAction|}}
<div class='flagger'>
{{#link-to 'adminUser' postAction.user.id postAction.user.username class='flagger-avatar'}}
{{avatar postAction.user imageSize="medium"}}
{{/link-to}}
<div class='flagger-details'>
<div class='flagger-username'>
{{#link-to 'adminUser' postAction.user.id postAction.user.username}}
{{postAction.user.username}}
{{/link-to}}
</div>
<div class='flagger-flagged-at'>
{{format-age postAction.created_at}}
</div>
<div class='flagger-flag-type'>
{{i18n (concat "admin.flags.summary.action_type_" postAction.post_action_type_id) count=1}}
</div>
</div>
</div>
{{/each}}
</div>
{{#if showResolvedBy}}
<div class='flagged-post-resolved-by'>
{{#each flaggedPost.post_actions as |postAction|}}
<div class='disposer'>
{{#link-to
'adminUser'
postAction.disposed_by.id
postAction.disposed_by.username
class="disposer-avatar"}}
{{avatar postAction.disposed_by imageSize="medium"}}
{{/link-to}}
<div class='disposer-details'>
{{format-age postAction.disposed_at}}
{{disposition-icon postAction.disposition}}
{{#if postAction.staff_took_action}}
{{d-icon "gavel" title="admin.flags.took_action"}}
{{/if}}
</div>
</div>
{{/each}}
</div>
{{/if}}
</div>
{{#if flaggedPost.topicFlagged}}
<div class='flagged-post-message'> <div class='flagged-post-message'>
<span class='text'>{{{i18n 'admin.flags.topic_flagged'}}}</span> <span class='text'>{{{i18n 'admin.flags.topic_flagged'}}}</span>
<a href={{flaggedPost.url}} class="btn">{{i18n 'admin.flags.visit_topic'}}</a> <a href={{flaggedPost.url}} class="btn">{{i18n 'admin.flags.visit_topic'}}</a>
</div> </div>
{{/if}} {{/if}}
{{#each flaggedPost.conversations as |c|}} {{#each flaggedPost.conversations as |c|}}
<div class='flagged-post-message'> <div class='flag-conversation'>
{{#if c.response}} {{#if c.response}}
{{flagged-post-response response=c.response}} {{flagged-post-response response=c.response}}
{{#if c.reply}} {{#if c.reply}}
{{flagged-post-response response=c.reply hasMore=c.hasMore permalink=c.permalink}} {{flagged-post-response response=c.reply hasMore=c.hasMore permalink=c.permalink}}
{{/if}} {{/if}}
<a href={{c.permalink}} class="btn btn-reply"> <a href={{c.permalink}} class="btn reply-conversation btn-small">
{{d-icon "reply"}} {{d-icon "reply"}}
{{i18n "admin.flags.reply_message"}} {{i18n "admin.flags.reply_message"}}
</a> </a>
{{/if}} {{/if}}
</div> </div>
{{/each}} {{/each}}
{{#if canAct}} <div class='flag-user-lists'>
<div class='flagged-by'>
<div class='user-list-title'>
{{i18n "admin.flags.flagged_by"}}
</div>
<div class='flag-users'>
{{#each flaggedPost.post_actions as |postAction|}}
{{#flag-user user=postAction.user date=postAction.created_at}}
<div class='flagger-flag-type'>
{{i18n (concat "admin.flags.summary.action_type_" postAction.post_action_type_id) count=1}}
</div>
{{/flag-user}}
{{/each}}
</div>
</div>
{{#if showResolvedBy}}
<div class='flagged-post-resolved-by'>
<div class='user-list-title'>
{{i18n "admin.flags.resolved_by"}}
</div>
<div class='flag-users'>
{{#each flaggedPost.post_actions as |postAction|}}
{{#flag-user user=postAction.disposed_by date=postAction.disposed_at}}
{{disposition-icon postAction.disposition}}
{{#if postAction.staff_took_action}}
{{d-icon "gavel" title="admin.flags.took_action"}}
{{/if}}
{{/flag-user}}
{{/each}}
</div>
</div>
{{/if}}
</div>
{{#if canAct}}
<div class='flagged-post-controls'> <div class='flagged-post-controls'>
{{d-button {{d-button
title="admin.flags.agree_title" title="admin.flags.agree_title"
@ -147,4 +137,6 @@
icon="trash-o" icon="trash-o"
label="admin.flags.delete"}} label="admin.flags.delete"}}
</div> </div>
{{/if}} {{/if}}
</div>
</div>

View File

@ -1,14 +1,5 @@
{{#if flaggedPosts}} {{#if flaggedPosts}}
{{#load-more selector=".flagged-post" action=(action "loadMore")}} {{#load-more selector=".flagged-post" action=(action "loadMore")}}
<div class='flagged-posts-header'>
<div class='flagged-by-header'>
{{i18n 'admin.flags.flagged_by'}}
</div>
{{#if showResolvedBy}}
{{i18n 'admin.flags.resolved_by'}}
{{/if}}
</div>
<div class='flagged-posts'> <div class='flagged-posts'>
{{#each flaggedPosts as |flaggedPost|}} {{#each flaggedPosts as |flaggedPost|}}
{{flagged-post {{flagged-post

View File

@ -53,7 +53,11 @@ const Post = RestModel.extend({
}.property('firstPost', 'deleted_at', 'topic.deleted_at'), }.property('firstPost', 'deleted_at', 'topic.deleted_at'),
url: function() { url: function() {
return postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number')); return postUrl(
this.get('topic.slug') || this.get('topic_slug'),
this.get('topic_id') || this.get('topic.id'),
this.get('post_number')
);
}.property('post_number', 'topic_id', 'topic.slug'), }.property('post_number', 'topic_id', 'topic.slug'),
// Don't drop the /1 // Don't drop the /1

View File

@ -1,15 +1,3 @@
.flagged-posts-header {
display: flex;
justify-content: flex-end;
font-weight: bold;
.flagged-by-header {
width: 12em;
}
padding-bottom: 0.5em;
border-bottom: 1px solid $primary-low;
}
.flagged-post.hidden-post { .flagged-post.hidden-post {
.flagged-post-excerpt, .flagged-post-avatar { .flagged-post-excerpt, .flagged-post-avatar {
opacity: 0.5; opacity: 0.5;
@ -23,12 +11,11 @@
} }
.flagged-post { .flagged-post {
padding: 1em 0 0 0; padding: 1em 0 0.55em 0;
border-bottom: 1px solid $primary-low; border-bottom: 1px solid $primary-low;
.flagged-post-details { .flagged-post-details {
display: flex; display: flex;
justify-content: space-between;
.flagged-post-avatar { .flagged-post-avatar {
margin-right: 1em; margin-right: 1em;
@ -46,9 +33,7 @@
} }
} }
.flagged-post-excerpt { .flagged-post-contents {
min-width: 70%;
width: 80%;
word-wrap: break-word; word-wrap: break-word;
.d-icon { .d-icon {
display: inline-block; display: inline-block;
@ -64,26 +49,53 @@
} }
} }
.flagger { .flag-user-lists {
width: 12em; display: flex;
align-items: flex-start;
margin-top: 1em;
}
.user-list-title {
font-weight: bold;
margin: 0;
}
.flag-users {
margin: 0.5em 0;
display: flex;
flex-wrap: wrap;
}
.flag-user {
display: flex; display: flex;
margin-bottom: 1em; margin-bottom: 1em;
} margin-right: 2em;
align-items: center;
justify-content: space-between;
.flagger-avatar, .disposer-avatar { .flag-user-who {
margin-right: 1em;
min-width: 32px;
}
.disposer {
width: 7em;
justify-content: flex-end;
display: flex; display: flex;
margin-bottom: 1em; width: 13em;
}
.flag-user-username {
font-weight: bold;
margin-right: 0.5em;
}
.flag-user-date {
color: $primary-medium;
} }
.flagged-post-resolved-by { .flag-user-avatar {
width: 12em; margin-right: 0.5em;
}
}
.flag-conversation {
padding: 1em;
.reply-conversation {
margin-left: 32px;
}
} }
.flagged-post-message { .flagged-post-message {
@ -117,7 +129,6 @@
.flagged-post-controls { .flagged-post-controls {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end;
button { button {
margin-right: 0.5em; margin-right: 0.5em;
@ -164,9 +175,6 @@
} }
.mobile-view { .mobile-view {
.flagged-posts-header {
display: none;
}
.flagged-posts { .flagged-posts {
.flagged-post-details { .flagged-post-details {
flex-wrap: wrap; flex-wrap: wrap;
@ -180,11 +188,6 @@
width: 70%; width: 70%;
} }
.flaggers-title {
font-weight: bold;
margin: 0.5em 0;
}
.flaggers { .flaggers {
margin-left: 4em; margin-left: 4em;
margin-bottom: 1em; margin-bottom: 1em;

View File

@ -2641,6 +2641,7 @@ en:
reply_message: "Reply" reply_message: "Reply"
no_results: "There are no flaged posts." no_results: "There are no flaged posts."
topic_flagged: "This <strong>topic</strong> has been flagged." topic_flagged: "This <strong>topic</strong> has been flagged."
show_full: "show full post"
visit_topic: "Visit the topic to take action" visit_topic: "Visit the topic to take action"
was_edited: "Post was edited after the first flag" was_edited: "Post was edited after the first flag"
previous_flags_count: "This post has already been flagged {{count}} times." previous_flags_count: "This post has already been flagged {{count}} times."

View File

@ -5,7 +5,7 @@ QUnit.test("flagged posts", assert => {
visit("/admin/flags/active"); visit("/admin/flags/active");
andThen(() => { andThen(() => {
assert.equal(find('.flagged-posts .flagged-post').length, 1); assert.equal(find('.flagged-posts .flagged-post').length, 1);
assert.equal(find('.flagged-post .flaggers .flagger').length, 1, 'shows who flagged it'); assert.equal(find('.flagged-post .flag-user').length, 1, 'shows who flagged it');
assert.equal(find('.flagged-post-response').length, 2); assert.equal(find('.flagged-post-response').length, 2);
assert.equal(find('.flagged-post-response:eq(0) img.avatar').length, 1); assert.equal(find('.flagged-post-response:eq(0) img.avatar').length, 1);
}); });