mirror of
https://github.com/flarum/framework.git
synced 2025-06-05 23:44:34 +08:00
Initial commit
This commit is contained in:
3
extensions/flags/js/.gitignore
vendored
Normal file
3
extensions/flags/js/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
bower_components
|
||||
node_modules
|
||||
dist
|
7
extensions/flags/js/admin/Gulpfile.js
Normal file
7
extensions/flags/js/admin/Gulpfile.js
Normal file
@ -0,0 +1,7 @@
|
||||
var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modules: {
|
||||
'reports': 'src/**/*.js'
|
||||
}
|
||||
});
|
7
extensions/flags/js/admin/package.json
Normal file
7
extensions/flags/js/admin/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"gulp": "^3.8.11",
|
||||
"flarum-gulp": "^0.1.0"
|
||||
}
|
||||
}
|
19
extensions/flags/js/admin/src/main.js
Normal file
19
extensions/flags/js/admin/src/main.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import PermissionGrid from 'flarum/components/PermissionGrid';
|
||||
|
||||
app.initializers.add('reports', () => {
|
||||
extend(PermissionGrid.prototype, 'moderateItems', items => {
|
||||
items.add('viewReports', {
|
||||
label: 'View reported posts',
|
||||
permission: 'discussion.viewReports'
|
||||
});
|
||||
});
|
||||
|
||||
extend(PermissionGrid.prototype, 'replyItems', items => {
|
||||
items.add('reportPosts', {
|
||||
label: 'Report posts',
|
||||
permission: 'discussion.reportPosts'
|
||||
});
|
||||
});
|
||||
});
|
7
extensions/flags/js/forum/Gulpfile.js
Normal file
7
extensions/flags/js/forum/Gulpfile.js
Normal file
@ -0,0 +1,7 @@
|
||||
var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modules: {
|
||||
'reports': 'src/**/*.js'
|
||||
}
|
||||
});
|
7
extensions/flags/js/forum/package.json
Normal file
7
extensions/flags/js/forum/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"gulp": "^3.8.11",
|
||||
"flarum-gulp": "^0.1.0"
|
||||
}
|
||||
}
|
16
extensions/flags/js/forum/src/addReportControl.js
Normal file
16
extensions/flags/js/forum/src/addReportControl.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import PostControls from 'flarum/utils/PostControls';
|
||||
import Button from 'flarum/components/Button';
|
||||
|
||||
import ReportPostModal from 'reports/components/ReportPostModal';
|
||||
|
||||
export default function() {
|
||||
extend(PostControls, 'userControls', function(items, post) {
|
||||
if (post.isHidden() || post.contentType() !== 'comment' || !post.canReport() || post.user() === app.session.user) return;
|
||||
|
||||
items.add('report',
|
||||
<Button icon="flag" onclick={() => app.modal.show(new ReportPostModal({post}))}>Report</Button>
|
||||
);
|
||||
});
|
||||
}
|
12
extensions/flags/js/forum/src/addReportsDropdown.js
Normal file
12
extensions/flags/js/forum/src/addReportsDropdown.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import app from 'flarum/app';
|
||||
import HeaderSecondary from 'flarum/components/HeaderSecondary';
|
||||
import ReportsDropdown from 'reports/components/ReportsDropdown';
|
||||
|
||||
export default function() {
|
||||
extend(HeaderSecondary.prototype, 'items', function(items) {
|
||||
if (app.forum.attribute('canViewReports')) {
|
||||
items.add('reports', <ReportsDropdown/>, 15);
|
||||
}
|
||||
});
|
||||
}
|
134
extensions/flags/js/forum/src/addReportsToPosts.js
Normal file
134
extensions/flags/js/forum/src/addReportsToPosts.js
Normal file
@ -0,0 +1,134 @@
|
||||
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>
|
||||
);
|
||||
});
|
||||
}
|
83
extensions/flags/js/forum/src/components/ReportList.js
Normal file
83
extensions/flags/js/forum/src/components/ReportList.js
Normal file
@ -0,0 +1,83 @@
|
||||
import Component from 'flarum/Component';
|
||||
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||
import avatar from 'flarum/helpers/avatar';
|
||||
import username from 'flarum/helpers/username';
|
||||
import icon from 'flarum/helpers/icon';
|
||||
import humanTime from 'flarum/helpers/humanTime';
|
||||
|
||||
export default class ReportList extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
/**
|
||||
* Whether or not the notifications are loading.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
view() {
|
||||
const reports = app.cache.reports || [];
|
||||
|
||||
return (
|
||||
<div className="NotificationList ReportList">
|
||||
<div className="NotificationList-header">
|
||||
<h4 className="App-titleControl App-titleControl--text">Reported Posts</h4>
|
||||
</div>
|
||||
<div className="NotificationList-content">
|
||||
<ul className="NotificationGroup-content">
|
||||
{reports.length
|
||||
? reports.map(report => {
|
||||
const post = report.post();
|
||||
|
||||
return (
|
||||
<li>
|
||||
<a href={app.route.post(post)} className="Notification Report" config={function(element, isInitialized) {
|
||||
m.route.apply(this, arguments);
|
||||
|
||||
if (!isInitialized) $(element).on('click', () => app.cache.reportIndex = 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())}
|
||||
<div className="Notification-excerpt">
|
||||
{post.contentPlain()}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
: !this.loading
|
||||
? <div className="NotificationList-empty">{app.trans('reports.no_reports')}</div>
|
||||
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load reports into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load() {
|
||||
if (app.cache.reports && !app.forum.attribute('unreadReportsCount')) {
|
||||
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());
|
||||
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
85
extensions/flags/js/forum/src/components/ReportPostModal.js
Normal file
85
extensions/flags/js/forum/src/components/ReportPostModal.js
Normal file
@ -0,0 +1,85 @@
|
||||
import Modal from 'flarum/components/Modal';
|
||||
import Button from 'flarum/components/Button';
|
||||
|
||||
export default class ReportPostModal extends Modal {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.reason = m.prop('');
|
||||
this.reasonDetail = m.prop('');
|
||||
}
|
||||
|
||||
className() {
|
||||
return 'ReportPostModal Modal--small';
|
||||
}
|
||||
|
||||
title() {
|
||||
return 'Report Post';
|
||||
}
|
||||
|
||||
content() {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form">
|
||||
<div className="Form-group">
|
||||
<label>Choose a Reason</label>
|
||||
<div>
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'off_topic'} value="off_topic" onclick={m.withAttr('value', this.reason)}/>
|
||||
Off-topic
|
||||
</label>
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'inappropriate'} value="inappropriate" onclick={m.withAttr('value', this.reason)}/>
|
||||
Inappropriate
|
||||
</label>
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'spam'} value="spam" onclick={m.withAttr('value', this.reason)}/>
|
||||
Spam
|
||||
</label>
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'other'} value="other" onclick={m.withAttr('value', this.reason)}/>
|
||||
Other
|
||||
{this.reason() === 'other' ? (
|
||||
<textarea className="FormControl" value={this.reasonDetail()} oninput={m.withAttr('value', this.reasonDetail)}></textarea>
|
||||
) : ''}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
{Button.component({
|
||||
children: 'Report Post',
|
||||
className: 'Button Button--primary',
|
||||
loading: this.loading,
|
||||
disabled: !this.reason()
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onsubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.loading = true;
|
||||
|
||||
app.store.createRecord('reports').save({
|
||||
reason: this.reason() === 'other' ? null : this.reason(),
|
||||
reasonDetail: this.reasonDetail(),
|
||||
relationships: {
|
||||
user: app.session.user,
|
||||
post: this.props.post
|
||||
}
|
||||
}).then(
|
||||
() => this.hide(),
|
||||
() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
26
extensions/flags/js/forum/src/components/ReportsDropdown.js
Normal file
26
extensions/flags/js/forum/src/components/ReportsDropdown.js
Normal file
@ -0,0 +1,26 @@
|
||||
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');
|
||||
}
|
||||
}
|
24
extensions/flags/js/forum/src/components/ReportsPage.js
Normal file
24
extensions/flags/js/forum/src/components/ReportsPage.js
Normal file
@ -0,0 +1,24 @@
|
||||
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>;
|
||||
}
|
||||
}
|
21
extensions/flags/js/forum/src/main.js
Normal file
21
extensions/flags/js/forum/src/main.js
Normal file
@ -0,0 +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';
|
||||
|
||||
app.initializers.add('reports', () => {
|
||||
app.store.models.posts.prototype.reports = Model.hasMany('reports');
|
||||
app.store.models.posts.prototype.canReport = Model.attribute('canReport');
|
||||
|
||||
app.store.models.reports = Report;
|
||||
|
||||
app.routes.reports = {path: '/reports', component: <ReportsPage/>};
|
||||
|
||||
addReportControl();
|
||||
addReportsDropdown();
|
||||
addReportsToPosts();
|
||||
});
|
12
extensions/flags/js/forum/src/models/Report.js
Normal file
12
extensions/flags/js/forum/src/models/Report.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Model from 'flarum/Model';
|
||||
import mixin from 'flarum/utils/mixin';
|
||||
|
||||
export default class Report extends mixin(Model, {
|
||||
reporter: Model.attribute('reporter'),
|
||||
reason: Model.attribute('reason'),
|
||||
reasonDetail: Model.attribute('reasonDetail'),
|
||||
time: Model.attribute('time', Model.transformDate),
|
||||
|
||||
post: Model.hasOne('post'),
|
||||
user: Model.hasOne('user')
|
||||
}) {}
|
Reference in New Issue
Block a user