...
 
Commits (3)
......@@ -42,6 +42,7 @@
<m-overlay-modal></m-overlay-modal>
<m--blockchain--transaction-overlay></m--blockchain--transaction-overlay>
<m-modal--tos-updated *ngIf="session.isLoggedIn()"></m-modal--tos-updated>
<m-juryDutySession__summons *ngIf="session.isLoggedIn()"></m-juryDutySession__summons>
<m-modal-signup-on-scroll></m-modal-signup-on-scroll>
......
......@@ -13,20 +13,21 @@
<div class="m-moderationAppeal__composer" *ngIf="!appeal.note">
<textarea
#appealContent
[(ngModel)]="note"
placeholder="Please type in this box if you wish to appeal this decision, explaining your reasons"
i18n="@@REPORT__CONSOLE__APPEAL_COMPOSER_PLACEHOLDER"
></textarea>
<button class="m-moderationAppeal__button"
[disabled]="appeal.inProgress"
(click)="sendAppeal(appeal, appealContent.value, i)"
(click)="sendAppeal()"
i18n="@@REPORT__CONSOLE__APPEAL_ACTION"
>
Appeal
</button>
</div>
<div class="m-moderationAppeal__appealNote" *ngIf="appeal.note">
<p>{{appeal.appeal_note}}</p>
<p>{{appeal.note}}</p>
</div>
</div>
......
......@@ -2,6 +2,7 @@ import {
Component,
OnInit,
Input,
ChangeDetectorRef,
} from '@angular/core';
import { Client } from '../../../services/api/client';
......@@ -20,6 +21,7 @@ export class ModerationAppealComponent {
constructor(
private client: Client,
public service: JurySessionService,
private cd: ChangeDetectorRef,
) { }
async sendAppeal() {
......@@ -31,8 +33,12 @@ export class ModerationAppealComponent {
});
this.appeal.note = this.note;
this.detectChanges();
} catch (e) {
alert((e && e.message) || 'Error sending appeal');
} finally {
this.appeal.inProgress = false;
}
}
......@@ -42,4 +48,8 @@ export class ModerationAppealComponent {
action;
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
......@@ -24,13 +24,15 @@
class="m-btn m-btn--slim"
(click)="uphold()"
>
Uphold Report
<ng-container *ngIf="report.is_appeal; else upholdLabel">Reject Appeal</ng-container>
<ng-template #upholdLabel>Uphold Report</ng-template>
</button>
<button
class="m-btn m-btn--slim"
(click)="overturn()"
>
Overturn Report
<ng-container *ngIf="report.is_appeal; else overturnLabel">Accept Appeal</ng-container>
<ng-template #overturnLabel>Overturn Report</ng-template>
</button>
</div>
</ng-container>
......
......@@ -15,6 +15,10 @@ export class JurySessionService {
return await this.client.get('api/v2/moderation/jury/' + opts.juryType);
}
async getReport(urn) {
return (<any>(await this.client.get('api/v2/moderation/jury/appeal/' + urn))).report;
}
async overturn(report) {
const juryType = report.is_appeal ? 'appeal' : 'initial';
return await this.client.post(`api/v2/moderation/jury/${juryType}/${report.urn}`, {
......@@ -32,11 +36,14 @@ export class JurySessionService {
getReasonString(report) {
return REASONS.filter((item) => {
if (item.hasMore && item.reasons) {
return item.reasons[report.sub_reason_code].value === report.sub_reason_code;
return item.value === report.reason_code && item.reasons[report.sub_reason_code - 1].value === report.sub_reason_code;
}
return item.value === report.reason_code;
})
.map((item) => {
if (item.hasMore && item.reasons) {
return item.reasons[report.sub_reason_code - 1].label;
}
return item.label;
})
.join(', ');
......@@ -49,6 +56,11 @@ export class JurySessionService {
case 2:
friendlyString = 'marked NSFW';
break;
case 4:
case 8:
if (report.entity && report.entity.type == 'user')
friendlyString = 'given a strike';
break;
}
return friendlyString;
......
<m-modal
[open]="true"
(closed)="onClose($event)"
*ngIf="showModal"
>
<div class="m-juryDutySession__summons">
<h2>You've been selected for Jury Duty</h2>
<p>
The Minds Jury decides on what content is within the terms of service.
We randomly select 12 members of the community to uphold or overturn decisions made.
</p>
<div class="m-juryDutySessionSummons__buttons" *ngIf="!accepted">
<button
class="m-btn m-btn--slim m-btn--action"
(click)="accept()"
>
Accept
</button>
<button
class="m-btn m-btn--slim"
(click)="decline()"
>
<span>Decline - </span>
<span class="countdown">{{ expires | timediff: true }}</span>
</button>
</div>
<div *ngIf="inProgress"
class="mdl-spinner mdl-js-spinner is-active"
[mdl]
></div>
<div class="m-juryDutySessionSummons__report" *ngIf="report">
<m-juryDutySession__content [report]="report">
</m-juryDutySession__content>
</div>
</div>
</m-modal>
.m-juryDutySession__summons {
padding: 24px 16px;
h2 {
font-weight: 600;
font-size: 26px;
line-height: 1;
margin: 0;
}
p {
line-height: 1.25;
font-size: 16px;
margin-top: 16px;
@include m-theme() {
color: themed($m-grey-400);
}
}
.m-btn {
margin: 8px;
.countdown {
font-weight: bold;
}
}
}
.m-juryDutySessionSummons__countdown {
padding: 16px 0;
span {
font-weight: 600;
font-size: 24px;
@include m-theme() {
color: themed($m-grey-300);
}
}
}
import { Component, Input, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
import { interval, Subscription } from 'rxjs';
import { map, take } from "rxjs/operators";
import { OverlayModalService } from '../../../../services/ux/overlay-modal';
import { Client } from '../../../../services/api';
import { Session } from '../../../../services/session';
import { REASONS } from '../../../../services/list-options';
import { JurySessionService } from './session.service';
import { SocketsService } from '../../../../services/sockets';
@Component({
selector: 'm-juryDutySession__summons',
templateUrl: 'summons.component.html'
})
export class JuryDutySessionSummonsComponent {
showModal: boolean = false;
expires: number = 0;
expiresCountdown$: Subscription;
accepted: boolean = false;
inProgress: boolean = false;
summons;
reportUrn: string = '';
report;
constructor(
private sessionService: JurySessionService,
private client: Client,
private socketsService: SocketsService,
private cd: ChangeDetectorRef,
) {
this.expires = 60; // 60 seconds
}
ngOnInit() {
this.socketsService.join(`moderation_summon`);
this.socketsService.subscribe(`moderation_summon`, (summons) => {
this.summons = JSON.parse(summons);
this.startSummons();
});
}
startSummons() {
if (this.expiresCountdown$)
this.expiresCountdown$.unsubscribe();
this.expires = parseInt(this.summons.ttl) / 2;
this.reportUrn = this.summons.report_urn;
this.showModal = true;
this.detectChanges();
this.expiresCountdown$ = interval(1000)
.pipe(
take(this.expires),
map((v) => --this.expires)
)
.subscribe((expires) => {
this.expires = expires;
if (this.expires <= 0 && !this.accepted)
this.showModal = false;
this.detectChanges()
});
}
ngOnDestroy() {
if (this.expiresCountdown$)
this.expiresCountdown$.unsubscribe();
}
async accept() {
if (!confirm("I am 18 years old and volunteer to participate in this jury. I acknowledge that I may be exposed to content not safe for work (NSFW) and understand that the purpose of the trial is to enforce the site content policy.")) {
return;
}
this.accepted = true;
this.detectChanges();
this.inProgress = true;
this.report = (<any> await this.client.post(`api/v2/moderation/summons`, {
report_urn: this.summons.report_urn,
jury_type: this.summons.jury_type,
status: 'accepted',
})).report;
this.inProgress = false;
}
async decline() {
await this.client.post(`api/v2/moderation/summons`, {
report_urn: this.summons.report_urn,
jury_type: this.summons.jury_type,
status: 'declined',
});
this.showModal = false;
this.detectChanges();
}
onClose(e) {
this.showModal = false;
this.detectChanges();
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
......@@ -11,7 +11,7 @@
The Minds Jury decides on what content is within the terms of service.
</h3>
<button class="m-btn m-btn--slim" (click)="startJuryDuty()">
<button class="m-btn m-btn--slim" (click)="startJuryDuty()" *ngIf="session.isAdmin()">
Start Jury Duty
</button>
</div>
......@@ -53,11 +53,11 @@
<span>Content Reported</span>
</div>
<div class="m-layout__cell">
<b>{{ stats.appealedPct }}%</b>
<b>{{ stats.appealedPct | number }}%</b>
<span>Appealed</span>
</div>
<div class="m-layout__cell">
<b>{{ stats.upheldPct }}%</b>
<b>{{ stats.upheldPct | number }}%</b>
<span>Upheld by Community Jury</span>
</div>
</div>
......
......@@ -3,7 +3,7 @@
.m-marketing__hero {
background: #132552;
background-color: #132552 !important;
padding-bottom: 72px;
.m-marketingHero__inner {
......
......@@ -10,7 +10,7 @@ import { Router } from '@angular/router';
import { Client } from '../../../common/api/client.service';
import { MindsTitle } from '../../../services/ux/title';
import { REASONS as REASONS_LIST } from '../../../services/list-options';
import { Session } from '../../../services/session';
@Component({
selector: 'm-reports__marketing',
......@@ -35,6 +35,7 @@ export class ReportsMarketingComponent {
private cd: ChangeDetectorRef,
private router: Router,
private title: MindsTitle,
public session: Session,
) {
title.setTitle('Community Moderation');
}
......@@ -44,7 +45,7 @@ export class ReportsMarketingComponent {
}
startJuryDuty() {
this.router.navigate(['/moderation/juryduty/appeal']);
this.router.navigate(['/moderation/juryduty/initial']);
}
async loadStats() {
......
......@@ -14,6 +14,7 @@ import { CommentsModule } from '../comments/comments.module';
import { JuryDutySessionListComponent } from './juryduty/session/list.component';
import { JurySessionService } from './juryduty/session/session.service';
import { JuryDutySessionContentComponent } from './juryduty/session/content.component';
import { JuryDutySessionSummonsComponent } from './juryduty/session/summons.component';
import { StrikesComponent } from './strikes/strikes.component';
import { BannedService } from './banned/banned.service';
import { BannedComponent } from './banned/banned.component';
......@@ -43,17 +44,20 @@ import { ModerationAppealComponent } from './console/appeal.component';
JuryDutySessionComponent,
JuryDutySessionListComponent,
JuryDutySessionContentComponent,
JuryDutySessionSummonsComponent,
StrikesComponent,
BannedComponent,
ModerationAppealComponent,
],
exports: [
ReportConsoleComponent
ReportConsoleComponent,
JuryDutySessionSummonsComponent,
],
entryComponents: [
ReportCreatorComponent,
ReportsMarketingComponent,
JuryDutySessionComponent,
JuryDutySessionSummonsComponent,
StrikesComponent,
BannedComponent,
],
......
......@@ -3,13 +3,13 @@
<h2>Strikes</h2>
<p>Strikes are imposed for violations against the terms of service. Following 3 strikes, channels are banned.</p>
<ul>
<li *ngFor="let strike of strikes">
{{ strike['@timestamp'] | date: 'medium' }}
<ul class="m-moderationStrike__list">
<li *ngFor="let strike of strikes"
class="m-border m-moderation__strike"
>
{{ strike['@timestamp'] * 1000 | date: 'medium' }}
<div>
<b> {{ service.getAction(strike) }} </b>
<span>for</span>
<b> {{ service.getReasonString(strike) }} </b>
</div>
</li>
......
m-moderation__strikes h2 {
font-weight: 600;
}
\ No newline at end of file
}
.m-moderationStrike__list {
list-style: none;
margin: 0;
padding: 0;
}
.m-moderation__strike {
@include m-theme() {
background-color: themed($m-white);
}
padding: 16px;
margin-bottom: 16px;
}
......@@ -46,7 +46,7 @@ export const REASONS : Array<any> = [
{ value: 1, label: 'Nudity' },
{ value: 2, label: 'Pornography' },
{ value: 3, label: 'Profanity' },
{ value: 4, label: 'Violance and Gore' },
{ value: 4, label: 'Violence and Gore' },
{ value: 5, label: 'Race, Religion, Gender' },
],
},
......