mirror of
https://github.com/flarum/core.git
synced 2025-07-30 21:20:24 +02:00
Rename to Flags, tweak flag controls
- Display post "destructiveControls" in flag instead of custom buttons - Make flags more versatile/extensible - Delete associated flags when a post is deleted Uninstall the Reports extension before installing.
This commit is contained in:
@@ -2,6 +2,6 @@ var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modules: {
|
||||
'reports': 'src/**/*.js'
|
||||
'flags': 'src/**/*.js'
|
||||
}
|
||||
});
|
||||
|
@@ -2,18 +2,20 @@ import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import PermissionGrid from 'flarum/components/PermissionGrid';
|
||||
|
||||
app.initializers.add('reports', () => {
|
||||
app.initializers.add('flags', () => {
|
||||
extend(PermissionGrid.prototype, 'moderateItems', items => {
|
||||
items.add('viewReports', {
|
||||
label: 'View reported posts',
|
||||
permission: 'discussion.viewReports'
|
||||
});
|
||||
items.add('viewFlags', {
|
||||
icon: 'flag',
|
||||
label: 'View flagged posts',
|
||||
permission: 'discussion.viewFlags'
|
||||
}, 65);
|
||||
});
|
||||
|
||||
extend(PermissionGrid.prototype, 'replyItems', items => {
|
||||
items.add('reportPosts', {
|
||||
label: 'Report posts',
|
||||
permission: 'discussion.reportPosts'
|
||||
});
|
||||
items.add('flagPosts', {
|
||||
icon: 'flag',
|
||||
label: 'Flag posts',
|
||||
permission: 'discussion.flagPosts'
|
||||
}, 70);
|
||||
});
|
||||
});
|
||||
|
@@ -2,6 +2,6 @@ var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modules: {
|
||||
'reports': 'src/**/*.js'
|
||||
'flags': 'src/**/*.js'
|
||||
}
|
||||
});
|
||||
|
@@ -3,14 +3,14 @@ import app from 'flarum/app';
|
||||
import PostControls from 'flarum/utils/PostControls';
|
||||
import Button from 'flarum/components/Button';
|
||||
|
||||
import ReportPostModal from 'reports/components/ReportPostModal';
|
||||
import FlagPostModal from 'flags/components/FlagPostModal';
|
||||
|
||||
export default function() {
|
||||
extend(PostControls, 'userControls', function(items, post) {
|
||||
if (post.isHidden() || post.contentType() !== 'comment' || !post.canReport() || post.user() === app.session.user) return;
|
||||
if (post.isHidden() || post.contentType() !== 'comment' || !post.canFlag() || post.user() === app.session.user) return;
|
||||
|
||||
items.add('report',
|
||||
<Button icon="flag" onclick={() => app.modal.show(new ReportPostModal({post}))}>Report</Button>
|
||||
items.add('flag',
|
||||
<Button icon="flag" onclick={() => app.modal.show(new FlagPostModal({post}))}>Flag</Button>
|
||||
);
|
||||
});
|
||||
}
|
@@ -1,12 +1,12 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import HeaderSecondary from 'flarum/components/HeaderSecondary';
|
||||
import ReportsDropdown from 'reports/components/ReportsDropdown';
|
||||
import FlagsDropdown from 'flags/components/FlagsDropdown';
|
||||
|
||||
export default function() {
|
||||
extend(HeaderSecondary.prototype, 'items', function(items) {
|
||||
if (app.forum.attribute('canViewReports')) {
|
||||
items.add('reports', <ReportsDropdown/>, 15);
|
||||
if (app.forum.attribute('canViewFlags')) {
|
||||
items.add('flags', <FlagsDropdown/>, 15);
|
||||
}
|
||||
});
|
||||
}
|
109
extensions/flags/js/forum/src/addFlagsToPosts.js
Normal file
109
extensions/flags/js/forum/src/addFlagsToPosts.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import CommentPost from 'flarum/components/CommentPost';
|
||||
import Button from 'flarum/components/Button';
|
||||
import punctuate from 'flarum/helpers/punctuate';
|
||||
import username from 'flarum/helpers/username';
|
||||
import ItemList from 'flarum/utils/ItemList';
|
||||
import PostControls from 'flarum/utils/PostControls';
|
||||
|
||||
export default function() {
|
||||
extend(CommentPost.prototype, 'attrs', function(attrs) {
|
||||
if (this.props.post.flags().length) {
|
||||
attrs.className += ' Post--flagged';
|
||||
}
|
||||
});
|
||||
|
||||
CommentPost.prototype.dismissFlag = function(data) {
|
||||
const post = this.props.post;
|
||||
|
||||
delete post.data.relationships.flags;
|
||||
|
||||
this.subtree.invalidate();
|
||||
|
||||
if (app.cache.flags) {
|
||||
app.cache.flags.some((flag, i) => {
|
||||
if (flag.post() === post) {
|
||||
app.cache.flags.splice(i, 1);
|
||||
|
||||
if (app.cache.flagIndex === post) {
|
||||
let next = app.cache.flags[i];
|
||||
|
||||
if (!next) next = app.cache.flags[0];
|
||||
|
||||
if (next) {
|
||||
const nextPost = next.post();
|
||||
app.cache.flagIndex = nextPost;
|
||||
m.route(app.route.post(nextPost));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return app.request({
|
||||
url: app.forum.attribute('apiUrl') + post.apiEndpoint() + '/flags',
|
||||
method: 'DELETE',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
CommentPost.prototype.flagActionItems = function() {
|
||||
const items = new ItemList();
|
||||
|
||||
const controls = PostControls.destructiveControls(this.props.post);
|
||||
|
||||
Object.keys(controls).forEach(k => {
|
||||
const props = controls[k].content.props;
|
||||
|
||||
props.className = 'Button';
|
||||
|
||||
extend(props, 'onclick', () => this.dismissFlag());
|
||||
});
|
||||
|
||||
items.merge(controls);
|
||||
|
||||
items.add('dismiss', <Button className="Button Button--icon Button--link" icon="times" onclick={this.dismissFlag.bind(this)} title="Dismiss Flag"/>, -100);
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
extend(CommentPost.prototype, 'content', function(vdom) {
|
||||
const post = this.props.post;
|
||||
const flags = post.flags();
|
||||
|
||||
if (!flags.length) return;
|
||||
|
||||
if (post.isHidden()) this.revealContent = true;
|
||||
|
||||
vdom.unshift(
|
||||
<div className="Post-flagged">
|
||||
<div className="Post-flagged-flags">
|
||||
{flags.map(flag =>
|
||||
<div className="Post-flagged-flag">
|
||||
{this.flagReason(flag)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="Post-flagged-actions">
|
||||
{this.flagActionItems().toArray()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
CommentPost.prototype.flagReason = function(flag) {
|
||||
if (flag.type() === 'user') {
|
||||
const user = flag.user();
|
||||
const reason = flag.reason();
|
||||
const detail = flag.reasonDetail();
|
||||
|
||||
return [
|
||||
app.trans(reason ? 'flags.flagged_by_with_reason' : 'flags.flagged_by', {user, reason}),
|
||||
detail ? <span className="Post-flagged-detail">{detail}</span> : ''
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
@@ -1,134 +0,0 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import CommentPost from 'flarum/components/CommentPost';
|
||||
import Button from 'flarum/components/Button';
|
||||
import punctuate from 'flarum/helpers/punctuate';
|
||||
import username from 'flarum/helpers/username';
|
||||
import ItemList from 'flarum/utils/ItemList';
|
||||
import PostControls from 'flarum/utils/PostControls';
|
||||
|
||||
export default function() {
|
||||
extend(CommentPost.prototype, 'attrs', function(attrs) {
|
||||
if (this.props.post.reports().length) {
|
||||
attrs.className += ' Post--reported';
|
||||
}
|
||||
});
|
||||
|
||||
CommentPost.prototype.dismissReport = function(data) {
|
||||
const post = this.props.post;
|
||||
|
||||
delete post.data.relationships.reports;
|
||||
|
||||
this.subtree.invalidate();
|
||||
|
||||
if (app.cache.reports) {
|
||||
app.cache.reports.some((report, i) => {
|
||||
if (report.post() === post) {
|
||||
app.cache.reports.splice(i, 1);
|
||||
|
||||
if (app.cache.reportIndex === post) {
|
||||
let next = app.cache.reports[i];
|
||||
|
||||
if (!next) next = app.cache.reports[0];
|
||||
|
||||
if (next) {
|
||||
const nextPost = next.post();
|
||||
app.cache.reportIndex = nextPost;
|
||||
m.route(app.route.post(nextPost));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return app.request({
|
||||
url: app.forum.attribute('apiUrl') + post.apiEndpoint() + '/reports',
|
||||
method: 'DELETE',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
CommentPost.prototype.reportActionItems = function() {
|
||||
const items = new ItemList();
|
||||
|
||||
if (this.props.post.isHidden()) {
|
||||
if (this.props.post.canDelete()) {
|
||||
items.add('delete',
|
||||
<Button className="Button"
|
||||
icon="trash-o"
|
||||
onclick={() => {
|
||||
this.dismissReport().then(() => {
|
||||
PostControls.deleteAction.apply(this.props.post);
|
||||
m.redraw();
|
||||
});
|
||||
}}>
|
||||
Delete Forever
|
||||
</Button>,
|
||||
100
|
||||
);
|
||||
}
|
||||
} else {
|
||||
items.add('hide',
|
||||
<Button className="Button"
|
||||
icon="trash-o"
|
||||
onclick={() => {
|
||||
this.dismissReport().then(() => {
|
||||
PostControls.hideAction.apply(this.props.post);
|
||||
m.redraw();
|
||||
});
|
||||
}}>
|
||||
Delete Post
|
||||
</Button>,
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
items.add('dismiss', <Button className="Button Button--icon Button--link" icon="times" onclick={this.dismissReport.bind(this)}>Dismiss Report</Button>, -100);
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
extend(CommentPost.prototype, 'content', function(vdom) {
|
||||
const post = this.props.post;
|
||||
const reports = post.reports();
|
||||
|
||||
if (!reports.length) return;
|
||||
|
||||
if (post.isHidden()) this.revealContent = true;
|
||||
|
||||
const users = reports.map(report => {
|
||||
const user = report.user();
|
||||
|
||||
return user
|
||||
? <a href={app.route.user(user)} config={m.route}>{username(user)}</a>
|
||||
: report.reporter();
|
||||
});
|
||||
|
||||
const usedReasons = [];
|
||||
const reasons = reports.map(report => report.reason()).filter(reason => {
|
||||
if (reason && usedReasons.indexOf(reason) === -1) {
|
||||
usedReasons.push(reason);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
const details = reports.map(report => report.reasonDetail()).filter(detail => detail);
|
||||
|
||||
vdom.unshift(
|
||||
<div className="Post-reported">
|
||||
<div className="Post-reported-summary">
|
||||
{app.trans(reasons.length ? 'reports.reported_by_with_reason' : 'reports.reported_by', {
|
||||
reasons: punctuate(reasons.map(reason => app.trans('reports.reason_' + reason, undefined, reason))),
|
||||
users: punctuate(users)
|
||||
})}
|
||||
{details.map(detail => <div className="Post-reported-detail">{detail}</div>)}
|
||||
</div>
|
||||
<div className="Post-reported-actions">
|
||||
{this.reportActionItems().toArray()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
@@ -5,7 +5,7 @@ import username from 'flarum/helpers/username';
|
||||
import icon from 'flarum/helpers/icon';
|
||||
import humanTime from 'flarum/helpers/humanTime';
|
||||
|
||||
export default class ReportList extends Component {
|
||||
export default class FlagList extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
@@ -18,32 +18,32 @@ export default class ReportList extends Component {
|
||||
}
|
||||
|
||||
view() {
|
||||
const reports = app.cache.reports || [];
|
||||
const flags = app.cache.flags || [];
|
||||
|
||||
return (
|
||||
<div className="NotificationList ReportList">
|
||||
<div className="NotificationList FlagList">
|
||||
<div className="NotificationList-header">
|
||||
<h4 className="App-titleControl App-titleControl--text">Reported Posts</h4>
|
||||
<h4 className="App-titleControl App-titleControl--text">Flagged Posts</h4>
|
||||
</div>
|
||||
<div className="NotificationList-content">
|
||||
<ul className="NotificationGroup-content">
|
||||
{reports.length
|
||||
? reports.map(report => {
|
||||
const post = report.post();
|
||||
{flags.length
|
||||
? flags.map(flag => {
|
||||
const post = flag.post();
|
||||
|
||||
return (
|
||||
<li>
|
||||
<a href={app.route.post(post)} className="Notification Report" config={function(element, isInitialized) {
|
||||
<a href={app.route.post(post)} className="Notification Flag" config={function(element, isInitialized) {
|
||||
m.route.apply(this, arguments);
|
||||
|
||||
if (!isInitialized) $(element).on('click', () => app.cache.reportIndex = post);
|
||||
if (!isInitialized) $(element).on('click', () => app.cache.flagIndex = post);
|
||||
}}>
|
||||
{avatar(post.user())}
|
||||
{icon('flag', {className: 'Notification-icon'})}
|
||||
<span className="Notification-content">
|
||||
{username(post.user())} in <em>{post.discussion().title()}</em>
|
||||
</span>
|
||||
{humanTime(report.time())}
|
||||
{humanTime(flag.time())}
|
||||
<div className="Notification-excerpt">
|
||||
{post.contentPlain()}
|
||||
</div>
|
||||
@@ -52,7 +52,7 @@ export default class ReportList extends Component {
|
||||
);
|
||||
})
|
||||
: !this.loading
|
||||
? <div className="NotificationList-empty">{app.trans('reports.no_reports')}</div>
|
||||
? <div className="NotificationList-empty">{app.trans('flags.no_flags')}</div>
|
||||
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -61,20 +61,20 @@ export default class ReportList extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load reports into the application's cache if they haven't already
|
||||
* Load flags into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load() {
|
||||
if (app.cache.reports && !app.forum.attribute('unreadReportsCount')) {
|
||||
if (app.cache.flags && !app.forum.attribute('unreadFlagsCount')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
app.store.find('reports').then(reports => {
|
||||
app.forum.pushAttributes({unreadReportsCount: 0});
|
||||
app.cache.reports = reports.sort((a, b) => b.time() - a.time());
|
||||
app.store.find('flags').then(flags => {
|
||||
app.forum.pushAttributes({unreadFlagsCount: 0});
|
||||
app.cache.flags = flags.sort((a, b) => b.time() - a.time());
|
||||
|
||||
this.loading = false;
|
||||
m.redraw();
|
@@ -1,7 +1,7 @@
|
||||
import Modal from 'flarum/components/Modal';
|
||||
import Button from 'flarum/components/Button';
|
||||
|
||||
export default class ReportPostModal extends Modal {
|
||||
export default class FlagPostModal extends Modal {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
@@ -10,11 +10,11 @@ export default class ReportPostModal extends Modal {
|
||||
}
|
||||
|
||||
className() {
|
||||
return 'ReportPostModal Modal--small';
|
||||
return 'FlagPostModal Modal--small';
|
||||
}
|
||||
|
||||
title() {
|
||||
return 'Report Post';
|
||||
return 'Flag Post';
|
||||
}
|
||||
|
||||
content() {
|
||||
@@ -55,7 +55,7 @@ export default class ReportPostModal extends Modal {
|
||||
type="submit"
|
||||
loading={this.loading}
|
||||
disabled={!this.reason()}>
|
||||
Report Post
|
||||
Flag Post
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +68,7 @@ export default class ReportPostModal extends Modal {
|
||||
|
||||
this.loading = true;
|
||||
|
||||
app.store.createRecord('reports').save({
|
||||
app.store.createRecord('flags').save({
|
||||
reason: this.reason() === 'other' ? null : this.reason(),
|
||||
reasonDetail: this.reasonDetail(),
|
||||
relationships: {
|
26
extensions/flags/js/forum/src/components/FlagsDropdown.js
Normal file
26
extensions/flags/js/forum/src/components/FlagsDropdown.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import NotificationsDropdown from 'flarum/components/NotificationsDropdown';
|
||||
|
||||
import FlagList from 'flags/components/FlagList';
|
||||
|
||||
export default class FlagsDropdown extends NotificationsDropdown {
|
||||
static initProps(props) {
|
||||
props.label = props.label || 'Flagged Posts';
|
||||
props.icon = props.icon || 'flag';
|
||||
|
||||
super.initProps(props);
|
||||
}
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.list = new FlagList();
|
||||
}
|
||||
|
||||
goToRoute() {
|
||||
m.route(app.route('flags'));
|
||||
}
|
||||
|
||||
getUnreadCount() {
|
||||
return app.forum.attribute('unreadFlagsCount');
|
||||
}
|
||||
}
|
24
extensions/flags/js/forum/src/components/FlagsPage.js
Normal file
24
extensions/flags/js/forum/src/components/FlagsPage.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import Page from 'flarum/components/Page';
|
||||
|
||||
import FlagList from 'flags/components/FlagList';
|
||||
|
||||
/**
|
||||
* The `FlagsPage` component shows the flags list. It is only
|
||||
* used on mobile devices where the flags dropdown is within the drawer.
|
||||
*/
|
||||
export default class FlagsPage extends Page {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
app.history.push('flags');
|
||||
|
||||
this.list = new FlagList();
|
||||
this.list.load();
|
||||
|
||||
this.bodyClass = 'App--flags';
|
||||
}
|
||||
|
||||
view() {
|
||||
return <div className="FlagsPage">{this.list.render()}</div>;
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
import NotificationsDropdown from 'flarum/components/NotificationsDropdown';
|
||||
|
||||
import ReportList from 'reports/components/ReportList';
|
||||
|
||||
export default class ReportsDropdown extends NotificationsDropdown {
|
||||
static initProps(props) {
|
||||
props.label = props.label || 'Reports';
|
||||
props.icon = props.icon || 'flag';
|
||||
|
||||
super.initProps(props);
|
||||
}
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.list = new ReportList();
|
||||
}
|
||||
|
||||
goToRoute() {
|
||||
m.route(app.route('reports'));
|
||||
}
|
||||
|
||||
getUnreadCount() {
|
||||
return app.forum.attribute('unreadReportsCount');
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
import Page from 'flarum/components/Page';
|
||||
|
||||
import ReportList from 'reports/components/ReportList';
|
||||
|
||||
/**
|
||||
* The `ReportsPage` component shows the reports list. It is only
|
||||
* used on mobile devices where the reports dropdown is within the drawer.
|
||||
*/
|
||||
export default class ReportsPage extends Page {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
app.history.push('reports');
|
||||
|
||||
this.list = new ReportList();
|
||||
this.list.load();
|
||||
|
||||
this.bodyClass = 'App--reports';
|
||||
}
|
||||
|
||||
view() {
|
||||
return <div className="ReportsPage">{this.list.render()}</div>;
|
||||
}
|
||||
}
|
@@ -1,21 +1,21 @@
|
||||
import app from 'flarum/app';
|
||||
import Model from 'flarum/Model';
|
||||
|
||||
import Report from 'reports/models/Report';
|
||||
import ReportsPage from 'reports/components/ReportsPage';
|
||||
import addReportControl from 'reports/addReportControl';
|
||||
import addReportsDropdown from 'reports/addReportsDropdown';
|
||||
import addReportsToPosts from 'reports/addReportsToPosts';
|
||||
import Flag from 'flags/models/Flag';
|
||||
import FlagsPage from 'flags/components/FlagsPage';
|
||||
import addFlagControl from 'flags/addFlagControl';
|
||||
import addFlagsDropdown from 'flags/addFlagsDropdown';
|
||||
import addFlagsToPosts from 'flags/addFlagsToPosts';
|
||||
|
||||
app.initializers.add('reports', () => {
|
||||
app.store.models.posts.prototype.reports = Model.hasMany('reports');
|
||||
app.store.models.posts.prototype.canReport = Model.attribute('canReport');
|
||||
app.initializers.add('flags', () => {
|
||||
app.store.models.posts.prototype.flags = Model.hasMany('flags');
|
||||
app.store.models.posts.prototype.canFlag = Model.attribute('canFlag');
|
||||
|
||||
app.store.models.reports = Report;
|
||||
app.store.models.flags = Flag;
|
||||
|
||||
app.routes.reports = {path: '/reports', component: <ReportsPage/>};
|
||||
app.routes.flags = {path: '/flags', component: <FlagsPage/>};
|
||||
|
||||
addReportControl();
|
||||
addReportsDropdown();
|
||||
addReportsToPosts();
|
||||
addFlagControl();
|
||||
addFlagsDropdown();
|
||||
addFlagsToPosts();
|
||||
});
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import Model from 'flarum/Model';
|
||||
import mixin from 'flarum/utils/mixin';
|
||||
|
||||
export default class Report extends mixin(Model, {
|
||||
reporter: Model.attribute('reporter'),
|
||||
export default class Flag extends mixin(Model, {
|
||||
type: Model.attribute('type'),
|
||||
reason: Model.attribute('reason'),
|
||||
reasonDetail: Model.attribute('reasonDetail'),
|
||||
time: Model.attribute('time', Model.transformDate),
|
Reference in New Issue
Block a user