...
 
Commits (2)
......@@ -38,6 +38,11 @@
<li class="mdl-menu__item" *ngIf="!asyncBlockInProgress && !asyncBlock" (click)="block()" i18n="@@COMMON__POST_MENU__BLOCK">Block user</li>
<li class="mdl-menu__item" *ngIf="!asyncBlockInProgress && asyncBlock" (click)="unBlock()" i18n="@@COMMON__POST_MENU__UNBLOCK">Unblock user</li>
</ng-container>
<!-- ALLOW COMMENTS -->
<ng-container *ngIf="featuresService.has('allow-comments-toggle') && options.indexOf('allow-comments') !== -1 && entity.ownerObj.guid == session.getLoggedInUser().guid ">
<li class="mdl-menu__item" *ngIf="!entity.allow_comments" (click)="allowComments(true)" i18n="@@COMMON__POST_MENU__ALLOW_COMMENTS">Allow Comments</li>
<li class="mdl-menu__item" *ngIf="entity.allow_comments" (click)="allowComments(false)" i18n="@@COMMON__POST_MENU__DISABLE_COMMENTS">Disable Comments</li>
</ng-container>
<!-- ADMIN EDIT FLAGS -->
<ng-container *ngIf="options.indexOf('set-explicit') !== -1 && session.isAdmin()">
<li class="mdl-menu__item m-postMenu__item--nsfw">
......
......@@ -15,7 +15,11 @@ import { sessionMock } from '../../../../tests/session-mock.spec';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { BlockListService } from '../../services/block-list.service';
import { ActivityService } from '../../services/activity.service';
import { FeaturesService } from '../../../services/features.service';
import { activityServiceMock } from '../../../../tests/activity-service-mock.spec';
import { storageMock } from '../../../../tests/storage-mock.spec';
import { featuresServiceMock } from '../../../../tests/features-service-mock.spec';
/* tslint:disable */
/* Mock section */
......@@ -95,6 +99,8 @@ describe('PostMenuComponent', () => {
{ provide: Client, useValue: clientMock },
{ provide: Session, useValue: sessionMock },
{ provide: OverlayModalService, useValue: overlayModalServiceMock },
{ provide: ActivityService, useValue: activityServiceMock },
{ provide: FeaturesService, useValue: featuresServiceMock },
{ provide: Storage, useValue: storageMock },
{ provide: BlockListService, useFactory: () => {
return BlockListService._(clientMock, sessionMock, storageMock);
......@@ -110,6 +116,7 @@ describe('PostMenuComponent', () => {
// synchronous beforeEach
beforeEach(() => {
featuresServiceMock.mock('allow-comments-toggle', true);
fixture = TestBed.createComponent(PostMenuComponent);
comp = fixture.componentInstance;
......@@ -117,8 +124,9 @@ describe('PostMenuComponent', () => {
comp.entity = {};
// comp.opened = true;
comp.entity.ownerObj = { guid: '1' };
comp.cardMenuHandler();
comp.cardMenuHandler();
fixture.detectChanges();
});
it('should have dropdown', () => {
......@@ -140,4 +148,19 @@ describe('PostMenuComponent', () => {
fixture.detectChanges();
expect(clientMock.delete.calls.mostRecent().args[0]).toEqual('api/v1/block/1');
});
it('should allow comments', () => {
spyOn(comp.optionSelected, 'emit');
comp.allowComments(true);
expect(activityServiceMock.toggleAllowComments).toHaveBeenCalledWith(comp.entity, true);
expect(comp.entity.allow_comments).toEqual(true);
});
it('should disable comments', () => {
spyOn(comp.optionSelected, 'emit');
comp.allowComments(false);
expect(activityServiceMock.toggleAllowComments).toHaveBeenCalledWith(comp.entity, false);
expect(comp.entity.allow_comments).toEqual(false);
});
});
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
import { Session } from '../../../services/session';
import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { Client } from '../../../services/api/client';
import { ReportCreatorComponent } from '../../../modules/report/creator/creator.component';
import { MindsUser } from '../../../interfaces/entities';
import { SignupModalService } from '../../../modules/modals/signup/service';
import { BlockListService } from "../../services/block-list.service";
import { BlockListService } from '../../services/block-list.service';
import { ActivityService } from '../../../common/services/activity.service';
import { FeaturesService } from '../../../services/features.service';
type Option =
'edit'
......@@ -26,7 +27,8 @@ type Option =
| 'subscribe'
| 'unsubscribe'
| 'rating'
| 'block';
| 'block'
| 'allow-comments';
@Component({
moduleId: module.id,
......@@ -35,7 +37,7 @@ type Option =
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PostMenuComponent {
export class PostMenuComponent implements OnInit {
@Input() entity: any;
@Input() options: Array<Option>;
@Output() optionSelected: EventEmitter<Option> = new EventEmitter<Option>();
......@@ -66,10 +68,13 @@ export class PostMenuComponent {
private overlayModal: OverlayModalService,
public signupModal: SignupModalService,
protected blockListService: BlockListService,
) {
protected activityService: ActivityService,
public featuresService: FeaturesService) {
this.initCategories();
}
ngOnInit() {}
initCategories() {
for (let category in window.Minds.categories) {
this.categories.push({
......@@ -329,4 +334,12 @@ export class PostMenuComponent {
this.entity.nsfw = nsfw;
}
async allowComments(areAllowed: boolean) {
this.entity.allow_comments = areAllowed;
const result = await this.activityService.toggleAllowComments(this.entity, areAllowed);
if (result !== areAllowed) {
this.entity.allow_comments = result;
}
}
}
import { EventEmitter, Injectable } from '@angular/core';
import { Client } from '../../services/api/client';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable()
export class ActivityService {
public allowComment$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
constructor(
private client: Client
) {}
public async toggleAllowComments(entity: any, areAllowed: boolean) {
const payload = {
allowed: areAllowed
};
const oldValue = entity['allow_comments'];
try {
await this.client.post(`api/v2/permissions/comments/${entity.guid}`, payload);
this.allowComment$.next(areAllowed);
return areAllowed;
} catch (ex) {
console.error('Error posting activity comment permissions', ex);
return oldValue;
}
}
}
......@@ -5,7 +5,8 @@ import { WireRewardsStruc } from '../modules/wire/interfaces/wire.interfaces';
export interface MindsActivityObject {
activity : Array<any>;
pinned : Array<any>;
pinned : Array<any>;
allow_comments: boolean;
}
export interface MindsBlogEntity {
......@@ -27,6 +28,7 @@ export interface MindsBlogEntity {
time_published?: number;
access_id?: number;
license?: string;
allow_comments: boolean;
}
export interface Message {
......
///<reference path="../../../../../node_modules/@types/jasmine/index.d.ts"/>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, EventEmitter, Input, Output, Pipe, PipeTransform, NO_ERRORS_SCHEMA } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { CommonModule as NgCommonModule } from '@angular/common';
import { MindsBlogEntity } from '../../../interfaces/entities';
import { BlogView } from './view';
import { SafePipe } from '../../../common/pipes/safe';
import { Client } from '../../../services/api/client';
import { clientMock } from '../../../../tests/client-mock.spec';
import { sessionMock } from '../../../../tests/session-mock.spec';
import { Session } from '../../../services/session';
import { scrollServiceMock } from '../../../../tests/scroll-service-mock.spec';
import { ScrollService } from '../../../services/ux/scroll';
import { mindsTitleMock } from '../../../mocks/services/ux/minds-title.service.mock.spec';
import { MindsTitle } from '../../../services/ux/title';
import { AttachmentService } from '../../../services/attachment';
import { attachmentServiceMock } from '../../../../tests/attachment-service-mock.spec';
import { contextServiceMock } from '../../../../tests/context-service-mock.spec';
import { ContextService } from '../../../services/context.service';
import { AnalyticsService } from '../../../services/analytics';
import { analyticsServiceMock } from '../../../../tests/analytics-service-mock.spec';
import { ActivityService } from '../../../common/services/activity.service';
import { activityServiceMock } from '../../../../tests/activity-service-mock.spec';
describe('Blog view component', () => {
let comp: BlogView;
let fixture: ComponentFixture<BlogView>;
const blog: MindsBlogEntity = {
guid: '1',
title: 'test blog',
description: 'description',
ownerObj: {},
allow_comments: true
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
BlogView,
SafePipe
], // declare the test component
imports: [
NgCommonModule,
RouterTestingModule,
],
providers: [
{ provide: ActivityService, useValue: activityServiceMock },
{ provide: AnalyticsService, useValue: analyticsServiceMock },
{ provide: AttachmentService, useValue: attachmentServiceMock },
{ provide: Client, useValue: clientMock },
{ provide: ContextService, useValue: contextServiceMock },
{ provide: MindsTitle, useValue: mindsTitleMock },
{ provide: ScrollService, useValue: scrollServiceMock },
{ provide: Session, useValue: sessionMock },
],
schemas: [
NO_ERRORS_SCHEMA,
],
})
.overrideProvider( ActivityService, { useValue: activityServiceMock })
.compileComponents(); // compile template and css
}));
// synchronous beforeEach
beforeEach(() => {
fixture = TestBed.createComponent(BlogView);
comp = fixture.componentInstance;
comp.blog = blog;
fixture.detectChanges();
});
});
import { Component, ElementRef, ViewChild } from '@angular/core';
import { Component, ElementRef, ViewChild, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { Client } from '../../../services/api';
......@@ -11,7 +11,9 @@ import { MindsBlogEntity } from '../../../interfaces/entities';
import { AttachmentService } from '../../../services/attachment';
import { ContextService } from '../../../services/context.service';
import { optimizedResize } from '../../../utils/optimized-resize';
import { ActivityService } from '../../../common/services/activity.service';
@Component({
moduleId: module.id,
selector: 'm-blog-view',
......@@ -19,10 +21,11 @@ import { optimizedResize } from '../../../utils/optimized-resize';
host: {
'class': 'm-blog'
},
templateUrl: 'view.html'
templateUrl: 'view.html',
providers: [ActivityService]
})
export class BlogView {
export class BlogView implements OnInit, OnDestroy {
minds;
guid: string;
......@@ -40,7 +43,10 @@ export class BlogView {
scroll_listener;
menuOptions: Array<string> = ['edit', 'follow', 'feature', 'delete', 'report', 'subscribe', 'set-explicit', 'remove-explicit', 'rating'];
menuOptions: Array<string> = ['edit', 'follow', 'feature',
'delete', 'report', 'subscribe',
'set-explicit', 'remove-explicit', 'rating',
'allow-comments'];
@ViewChild('lockScreen', { read: ElementRef, static: false }) lockScreen;
......@@ -55,7 +61,9 @@ export class BlogView {
public attachment: AttachmentService,
private context: ContextService,
public analytics: AnalyticsService,
public analyticsService: AnalyticsService
public analyticsService: AnalyticsService,
protected activityService: ActivityService,
private cd: ChangeDetectorRef,
) {
this.minds = window.Minds;
this.element = _element.nativeElement;
......@@ -69,7 +77,7 @@ export class BlogView {
}
isVisible() {
//listens every 0.6 seconds
// listens every 0.6 seconds
this.scroll_listener = this.scroll.listen((e) => {
const bounds = this.element.getBoundingClientRect();
if (bounds.top < this.scroll.view.clientHeight && bounds.top + bounds.height > this.scroll.view.clientHeight) {
......@@ -113,8 +121,9 @@ export class BlogView {
}
ngOnDestroy() {
if (this.scroll_listener)
if (this.scroll_listener) {
this.scroll.unListen(this.scroll_listener);
}
}
menuOptionSelected(option: string) {
......@@ -144,8 +153,9 @@ export class BlogView {
}
calculateLockScreenHeight() {
if (!this.lockScreen)
if (!this.lockScreen) {
return;
}
const lockScreenOverlay = this.lockScreen.nativeElement.querySelector('.m-wire--lock-screen');
if (lockScreenOverlay) {
const rect = lockScreenOverlay.getBoundingClientRect();
......
......@@ -232,7 +232,7 @@
>
<i class="material-icons">reply</i>
<span *ngIf="comment.replies_count > 0">{{ comment.replies_count }} Replies</span>
<span *ngIf="comment.replies_count <= 0">Reply</span>
<span *ngIf="comment.replies_count <= 0 && canReply">Reply</span>
</span>
</div>
......
......@@ -70,6 +70,7 @@ export class CommentComponent implements OnChanges {
translateToggle: boolean = false;
commentAge$: Observable<number>;
@Input() canEdit: boolean = false;
@Input() canReply = true;
@Output() onReply = new EventEmitter();
......
......@@ -232,9 +232,9 @@
(click)="toggleReplies();"
*ngIf="comment.can_reply"
>
<i class="material-icons">reply</i>
<i *ngIf="comment.replies_count > 0 || (activityService.allowComment$ | async)" class="material-icons">reply</i>
<span *ngIf="comment.replies_count > 0">{{ comment.replies_count }} Replies</span>
<span *ngIf="comment.replies_count <= 0">Reply</span>
<span *ngIf="comment.replies_count <= 0 && (activityService.allowComment$ | async)">Reply</span>
</span>
</div>
......
......@@ -9,6 +9,9 @@ import {
OnChanges,
Input,
ElementRef,
OnInit,
OnDestroy,
AfterViewInit
} from '@angular/core';
import { Session } from '../../../services/session';
......@@ -20,8 +23,9 @@ import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { ReportCreatorComponent } from '../../report/creator/creator.component';
import { CommentsListComponent } from '../list/list.component';
import { TimeDiffService } from '../../../services/timediff.service';
import { Observable } from 'rxjs';
import { map } from "rxjs/operators";
import { Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { ActivityService } from '../../../common/services/activity.service';
@Component({
selector: 'm-comment',
......@@ -40,7 +44,7 @@ import { map } from "rxjs/operators";
],
})
export class CommentComponentV2 implements OnChanges {
export class CommentComponentV2 implements OnChanges, OnInit, OnDestroy, AfterViewInit {
comment: any;
editing: boolean = false;
......@@ -73,6 +77,8 @@ export class CommentComponentV2 implements OnChanges {
translationInProgress: boolean;
translateToggle: boolean = false;
commentAge$: Observable<number>;
canReply = true;
@Input() canEdit: boolean = false;
@Input() canDelete: boolean = false;
@Input() hideToolbar: boolean = false;
......@@ -88,7 +94,8 @@ export class CommentComponentV2 implements OnChanges {
private overlayModal: OverlayModalService,
private cd: ChangeDetectorRef,
private timeDiffService: TimeDiffService,
private el: ElementRef
private el: ElementRef,
protected activityService: ActivityService
) {}
ngOnInit() {
......@@ -97,6 +104,7 @@ export class CommentComponentV2 implements OnChanges {
}));
}
ngAfterViewInit() {
if (this.comment.focused) {
this.el.nativeElement.scrollIntoView(true);
......@@ -106,10 +114,13 @@ export class CommentComponentV2 implements OnChanges {
}
}
ngOnDestroy() {}
@Input('comment')
set _comment(value: any) {
if (!value)
if (!value) {
return;
}
this.comment = value;
this.attachment.load(this.comment);
......@@ -121,7 +132,9 @@ export class CommentComponentV2 implements OnChanges {
}
saveEnabled() {
return !this.inProgress && this.canPost && ((this.comment.description && this.comment.description.trim() !== '') || this.attachment.has());
return !this.inProgress
&& this.canPost
&& ((this.comment.description && this.comment.description.trim() !== '') || this.attachment.has());
}
save() {
......@@ -131,7 +144,7 @@ export class CommentComponentV2 implements OnChanges {
return;
}
let data = this.attachment.exportMeta();
const data = this.attachment.exportMeta();
data['comment'] = this.comment.description;
this.editing = false;
......
......@@ -31,7 +31,8 @@
&& !ascendingInProgress
&& !error
&& comments?.length === 0
&& parent.type == 'activity'"
&& parent.type == 'activity'
&& activityService.allowComment$"
i18n="@@MINDS__COMMENTS__START_CONVERSATION"
>
Start the conversation!
......
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Renderer,
ViewChild
ViewChild,
OnInit,
OnDestroy
} from '@angular/core';
import { Client } from '../../../services/api/client';
......@@ -9,6 +11,7 @@ import { Upload } from '../../../services/api/upload';
import { AttachmentService } from '../../../services/attachment';
import { Textarea } from '../../../common/components/editors/textarea.component';
import { SocketsService } from '../../../services/sockets';
import { ActivityService } from '../../../common/services/activity.service';
@Component({
moduleId: module.id,
......@@ -20,12 +23,13 @@ import { SocketsService } from '../../../services/sockets';
provide: AttachmentService,
useFactory: AttachmentService._,
deps: [Session, Client, Upload]
}
},
ActivityService
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CommentsListComponent {
export class CommentsListComponent implements OnInit, OnDestroy {
minds;
object;
......@@ -61,7 +65,6 @@ export class CommentsListComponent {
socketSubscriptions: any = {
comment: null
};
error: string;
@Input() conversation: boolean = false;
......@@ -81,7 +84,8 @@ export class CommentsListComponent {
public attachment: AttachmentService,
public sockets: SocketsService,
private renderer: Renderer,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
public activityService: ActivityService,
) {
this.minds = window.Minds;
}
......@@ -89,16 +93,18 @@ export class CommentsListComponent {
set _object(value: any) {
this.object = value;
this.guid = this.object.guid;
if (this.object.entity_guid)
if (this.object.entity_guid) {
this.guid = this.object.entity_guid;
}
this.parent = this.object;
}
set _reversed(value: boolean) {
if (value)
if (value) {
this.reversed = true;
else
} else {
this.reversed = false;
}
}
ngOnInit() {
......@@ -143,7 +149,6 @@ export class CommentsListComponent {
descending: descending,
})
.then((response: any) => {
if (!this.socketRoomName && response.socketRoomName) {
this.socketRoomName = response.socketRoomName;
this.joinSocketRoom();
......@@ -155,7 +160,7 @@ export class CommentsListComponent {
} else {
this.ascendingInProgress = false;
}
//this.moreDescendingData = true;
// this.moreDescendingData = true;
if (!response.comments) {
if (descending) {
......@@ -168,8 +173,8 @@ export class CommentsListComponent {
return false;
}
let el = this.scrollView.nativeElement;
let previousScrollHeightMinusTop = el.scrollHeight - el.scrollTop;
const el = this.scrollView.nativeElement;
const previousScrollHeightMinusTop = el.scrollHeight - el.scrollTop;
if (descending) {
this.comments = response.comments.concat(this.comments);
......@@ -239,7 +244,7 @@ export class CommentsListComponent {
this.overscrollAmount += deltaY;
this.overscrollTimer = setTimeout(() => {
if (this.overscrollAmount < -75) { //75px
if (this.overscrollAmount < -75) { // 75px
this.autoloadPrevious();
}
......@@ -289,9 +294,9 @@ export class CommentsListComponent {
return;
}
const parent_path = this.parent.child_path || "0:0:0";
const parent_path = this.parent.child_path || '0:0:0';
this.client.get(`api/v1/comments/${this.guid}/${guid}/${parent_path}`, {
this.client.get(`api/v1/comments/${this.guid}/${guid}/${parent_path}`, {
limit: 1,
reversed: false,
descending: true,
......@@ -302,10 +307,12 @@ export class CommentsListComponent {
}
// if the list is scrolled to the bottom
let scrolledToBottom = this.scrollView.nativeElement.scrollTop + this.scrollView.nativeElement.clientHeight >= this.scrollView.nativeElement.scrollHeight;
const scrolledToBottom = this.scrollView.nativeElement.scrollTop
+ this.scrollView.nativeElement.clientHeight >= this.scrollView.nativeElement.scrollHeight;
if (response.comments[0]._guid == guid)
if (response.comments[0]._guid == guid) {
this.comments.push(response.comments[0]);
}
this.detectChanges();
......@@ -328,14 +335,14 @@ export class CommentsListComponent {
if (this.session.isLoggedIn() && owner_guid === this.session.getLoggedInUser().guid) {
return;
}
let key = 'thumbs:' + direction + ':count';
const key = 'thumbs:' + direction + ':count';
for (let i = 0; i < this.comments.length; i++) {
if (this.comments[i]._guid == guid) {
this.comments[i][key]++;
this.detectChanges();
}
}
//this.comments = this.comments.slice(0);
// this.comments = this.comments.slice(0);
this.detectChanges();
});
......@@ -350,12 +357,14 @@ export class CommentsListComponent {
this.detectChanges();
}
}
});
});
}
postEnabled() {
return !this.descendingInProgress && !this.ascendingInProgress && this.canPost && ((this.content && this.content.trim() !== '') || this.attachment.has());
return !this.descendingInProgress
&& !this.ascendingInProgress
&& this.canPost
&& ((this.content && this.content.trim() !== '') || this.attachment.has());
}
keypress(e: KeyboardEvent) {
......@@ -380,11 +389,11 @@ export class CommentsListComponent {
this.content = this.content.trim();
let data = this.attachment.exportMeta();
const data = this.attachment.exportMeta();
data['comment'] = this.content;
data['parent_path'] = this.parent.child_path || '0:0:0';
let newLength = this.comments.push({ // Optimistic
const newLength = this.comments.push({ // Optimistic
description: this.content,
guid: 0,
ownerObj: this.session.getLoggedInUser(),
......@@ -401,7 +410,7 @@ export class CommentsListComponent {
this.commentsScrollEmitter.emit('bottom');
try {
let response: any = await this.client.post('api/v1/comments/' + this.guid, data);
const response: any = await this.client.post('api/v1/comments/' + this.guid, data);
this.comments[currentIndex] = response.comment;
} catch (e) {
this.comments[currentIndex].error = (e && e.message) || 'There was an error';
......@@ -500,10 +509,10 @@ export class CommentsListComponent {
}
getAvatar() {
if(this.session.isLoggedIn()) {
if (this.session.isLoggedIn()) {
return `${this.minds.cdn_url}icon/${this.session.getLoggedInUser().guid}/small/${this.session.getLoggedInUser().icontime}`;
} else {
return `${this.minds.cdn_assets_url}assets/avatars/default-small.png`
return `${this.minds.cdn_assets_url}assets/avatars/default-small.png`;
}
}
......@@ -512,8 +521,4 @@ export class CommentsListComponent {
this.cd.detectChanges();
}
ngOnChanges(changes) {
// console.log('[comment:list]: on changes', changes);
}
}
......@@ -28,7 +28,8 @@
*ngIf="!inProgress
&& !error
&& comments?.length === 0
&& parent.type == 'activity'"
&& parent.type == 'activity'
&& (activityService.allowComment$ | async)"
i18n="@@MINDS__COMMENTS__START_CONVERSATION"
>
Start the conversation!
......@@ -80,6 +81,7 @@
</div>
<m-comment__poster
*ngIf="(activityService.allowComment$ | async)"
[guid]="guid"
[parent]="parent"
[entity]="entity"
......
......@@ -8,6 +8,8 @@ import {
Output,
Renderer,
ViewChild,
OnInit,
OnDestroy
} from '@angular/core';
import { Client } from '../../../services/api/client';
......@@ -18,6 +20,10 @@ import { Textarea } from '../../../common/components/editors/textarea.component'
import { SocketsService } from '../../../services/sockets';
import { CommentsService } from '../comments.service';
import { BlockListService } from "../../../common/services/block-list.service";
import { ActivityService } from '../../../common/services/activity.service';
import { Subscription } from 'rxjs';
import { TouchSequence } from 'selenium-webdriver';
@Component({
selector: 'm-comments__thread',
......@@ -26,7 +32,7 @@ import { BlockListService } from "../../../common/services/block-list.service";
providers: [ CommentsService ],
})
export class CommentsThreadComponent {
export class CommentsThreadComponent implements OnInit {
minds;
@Input() parent;
......@@ -60,14 +66,15 @@ export class CommentsThreadComponent {
socketSubscriptions: any = {
comment: null
};
constructor(
public session: Session,
private commentsService: CommentsService,
public sockets: SocketsService,
private renderer: Renderer,
protected blockListService: BlockListService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
public activityService: ActivityService
) {
this.minds = window.Minds;
}
......@@ -194,7 +201,8 @@ export class CommentsThreadComponent {
const parent_path = this.parent.child_path || "0:0:0";
let scrolledToBottom = this.scrollView.nativeElement.scrollTop + this.scrollView.nativeElement.clientHeight >= this.scrollView.nativeElement.scrollHeight;
const scrolledToBottom = this.scrollView.nativeElement.scrollTop
+ this.scrollView.nativeElement.clientHeight >= this.scrollView.nativeElement.scrollHeight;
try {
let comment: any = await this.commentsService.single({
......@@ -273,8 +281,6 @@ export class CommentsThreadComponent {
}
onPosted({ comment, index }) {
console.log('onPosted called');
console.log(comment, index);
this.comments[index] = comment;
this.detectChanges();
}
......@@ -295,8 +301,4 @@ export class CommentsThreadComponent {
return true;
}
ngOnChanges(changes) {
// console.log('[comment:list]: on changes', changes);
}
}
......@@ -7,6 +7,8 @@ import {
Input,
Output,
Renderer,
OnInit,
OnDestroy
} from '@angular/core';
import {
ActivatedRoute,
......@@ -37,7 +39,7 @@ import { CommentsService } from '../comments.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CommentsTreeComponent {
export class CommentsTreeComponent implements OnInit, OnDestroy {
minds;
entity;
......
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { Client } from '../../../../services/api';
import { ActivityService } from '../../../../common/services/activity.service';
@Component({
selector: 'minds-button-comment',
inputs: ['_object: object'],
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.Default,
template: `
<a [ngClass]="{'selected': object['comments:count'] > 0 }">
<i class="material-icons">chat_bubble</i>
<i class="material-icons" *ngIf="(activityService.allowComment$ | async) === true">chat_bubble</i>
<i class="material-icons"
*ngIf="(activityService.allowComment$ | async) === false"
title="Comments have been disabled for this post"
i18n-title="@@COMMENTS__DISABLED">
speaker_notes_off
</i>
<span class="minds-counter" *ngIf="object['comments:count'] > 0">{{object['comments:count'] | number}}</span>
</a>
`
})
export class CommentButton {
export class CommentButton implements OnInit, OnDestroy {
object;
constructor(public client : Client) {
constructor(
public client: Client,
public activityService: ActivityService,
protected cd: ChangeDetectorRef) {
}
set _object(value : any){
ngOnInit() {}
ngOnDestroy() { }
set _object(value: any) {
this.object = value;
this.activityService.allowComment$.next(this.object.allow_comments);
}
}
......@@ -235,7 +235,7 @@
[object]="activity.remind_object ? activity.remind_object : activity"
(done)="wireSubmitted($event)"
></m-wire-button>
<minds-button-comment [object]="activity" (click)="openComments()"></minds-button-comment>
<minds-button-comment [object]="activity" (click)="openComments()"></minds-button-comment>
<minds-button-remind [object]="activity"></minds-button-remind>
<a class="mdl-button mdl-color-text--white mdl-button--colored minds-boost-button"
*ngIf ="session.getLoggedInUser().guid == activity.owner_guid"
......
......@@ -26,6 +26,7 @@ import { ActivityAnalyticsOnViewService } from "./activity-analytics-on-view.ser
import { NewsfeedService } from "../../../../newsfeed/services/newsfeed.service";
import { ClientMetaService } from "../../../../../common/services/client-meta.service";
import { AutocompleteSuggestionsService } from "../../../../suggestions/services/autocomplete-suggestions.service";
import { ActivityService } from '../../../../../common/services/activity.service';
@Component({
moduleId: module.id,
......@@ -35,7 +36,7 @@ import { AutocompleteSuggestionsService } from "../../../../suggestions/services
},
inputs: ['object', 'commentsToggle', 'focusedCommentGuid', 'visible', 'canDelete', 'showRatingToggle'],
outputs: ['_delete: delete', 'commentsOpened', 'onViewed'],
providers: [ ClientMetaService, ActivityAnalyticsOnViewService ],
providers: [ ClientMetaService, ActivityAnalyticsOnViewService, ActivityService ],
templateUrl: 'activity.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
......@@ -52,6 +53,7 @@ export class Activity implements OnInit {
translateToggle: boolean = false;
translateEvent: EventEmitter<any> = new EventEmitter();
showBoostOptions: boolean = false;
allowComments = true;
@Input() boost: boolean = false;
@Input('boost-toggle')
@Input() showBoostMenuOptions: boolean = false;
......@@ -90,12 +92,21 @@ export class Activity implements OnInit {
get menuOptions(): Array<string> {
if (!this.activity || !this.activity.ephemeral) {
if (this.showBoostMenuOptions) {
return ['edit', 'translate', 'share', 'follow', 'feature', 'delete', 'report', 'set-explicit', 'block', 'rating'];
return ['edit', 'translate', 'share',
'follow', 'feature', 'delete',
'report', 'set-explicit', 'block',
'rating', 'allow-comments'];
} else {
return ['edit', 'translate', 'share', 'follow', 'feature', 'delete', 'report', 'set-explicit', 'block', 'rating'];
return ['edit', 'translate', 'share',
'follow', 'feature', 'delete',
'report', 'set-explicit', 'block',
'rating', 'allow-comments'];
}
} else {
return ['view', 'translate', 'share', 'follow', 'feature', 'report', 'set-explicit', 'block', 'rating']
return ['view', 'translate', 'share',
'follow', 'feature', 'report',
'set-explicit', 'block', 'rating',
'allow-comments'];
}
}
......@@ -115,6 +126,7 @@ export class Activity implements OnInit {
protected newsfeedService: NewsfeedService,
protected clientMetaService: ClientMetaService,
public suggestions: AutocompleteSuggestionsService,
protected activityService: ActivityService,
@SkipSelf() injector: Injector,
elementRef: ElementRef,
) {
......@@ -170,6 +182,8 @@ export class Activity implements OnInit {
this.translationService.isTranslatable(this.activity) ||
(this.activity.remind_object && this.translationService.isTranslatable(this.activity.remind_object))
);
this.allowComments = this.activity.allow_comments;
}
getOwnerIconTime() {
......@@ -248,6 +262,9 @@ export class Activity implements OnInit {
}*/
openComments() {
if (!this.shouldShowComments()) {
return;
}
this.commentsToggle = !this.commentsToggle;
this.commentsOpened.emit(this.commentsToggle);
}
......@@ -340,10 +357,11 @@ export class Activity implements OnInit {
this.translateToggle = true;
break;
}
this.detectChanges();
}
setExplicit(value: boolean) {
let oldValue = this.activity.mature,
const oldValue = this.activity.mature,
oldMatureVisibility = this.activity.mature_visibility;
this.activity.mature = value;
......@@ -413,6 +431,16 @@ export class Activity implements OnInit {
return activity && activity.pending && activity.pending !== '0';
}
/**
* If an activity allow
*/
shouldShowComments() {
return (
this.activity.allow_comments
|| this.activity['comments:count'] >= 0
);
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
......
......@@ -148,7 +148,7 @@
</div>
<!-- Don't show comments for albums -->
<div class="mdl-grid m-media-content--comments mdl-color--white" *ngIf="entity.guid && entity.subtype != 'album'">
<div class="mdl-grid m-media-content--comments mdl-color--white" *ngIf="canShowComments() && (activityService.allowComment$ | async)">
<m-comments__tree
[entity]="entity"
>
......
import { ChangeDetectorRef, Component } from '@angular/core';
import { ChangeDetectorRef, Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
......@@ -10,19 +10,23 @@ import { RecommendedService } from '../components/video/recommended.service';
import { AttachmentService } from '../../../services/attachment';
import { ContextService } from '../../../services/context.service';
import { MindsTitle } from '../../../services/ux/title';
import { ActivityService } from '../../../common/services/activity.service';
@Component({
moduleId: module.id,
selector: 'm-media--view',
templateUrl: 'view.component.html',
providers: [{
provide: RecommendedService,
useFactory: RecommendedService._,
deps: [Client]
}],
providers: [
{
provide: RecommendedService,
useFactory: RecommendedService._,
deps: [Client]
},
ActivityService
],
})
export class MediaViewComponent {
export class MediaViewComponent implements OnInit, OnDestroy {
minds = window.Minds;
guid: string;
......@@ -32,8 +36,12 @@ export class MediaViewComponent {
deleteToggle: boolean = false;
theaterMode: boolean = false;
allowComments = true;
menuOptions: Array<string> = ['edit', 'follow', 'feature', 'delete', 'report', 'set-explicit', 'subscribe', 'remove-explicit', 'rating'];
menuOptions: Array<string> = ['edit', 'follow', 'feature',
'delete', 'report', 'set-explicit',
'subscribe', 'remove-explicit', 'rating',
'allow-comments', 'disable-comments'];
paramsSubscription: Subscription;
queryParamsSubscription$: Subscription;
......@@ -47,7 +55,8 @@ export class MediaViewComponent {
public route: ActivatedRoute,
public attachment: AttachmentService,
public context: ContextService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
protected activityService: ActivityService
) { }
ngOnInit() {
......@@ -87,7 +96,7 @@ export class MediaViewComponent {
}
if (response.entity) {
this.entity = response.entity;
this.allowComments = this.entity['allow_comments'];
switch (this.entity.subtype) {
case 'video':
this.context.set('object:video');
......@@ -160,7 +169,14 @@ export class MediaViewComponent {
case 'remove-explicit':
this.setExplicit(false);
break;
case 'allow-comments':
this.entity.allow_comments = true;
this.activityService.toggleAllowComments(this.entity, true);
break;
case 'disable-comments':
this.entity.allow_comments = false;
this.activityService.toggleAllowComments(this.entity, false);
break;
}
}
......@@ -176,6 +192,17 @@ export class MediaViewComponent {
});
}
canShowComments() {
if (!this.entity.guid) {
return false;
}
//Don't show comments on albums
if (this.entity.subtype === 'album') {
return false;
}
return (this.entity['comments:count'] >= 1);
}
private detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
......
......@@ -22,11 +22,11 @@ export class FeaturesService {
if (typeof this._features[feature] === 'undefined') {
if (isDevMode() && !this._hasWarned(feature)) {
console.warn(`[FeaturedService] Feature '${feature}' is not declared. Assuming true.`);
console.warn(`[FeaturedService] Feature '${feature}' is not declared. Assuming false.`);
this._warnedCache[feature] = Date.now();
}
return true;
return false;
}
if (this._features[feature] === 'admin' && this.session.isAdmin()) {
......
......@@ -4,8 +4,8 @@ import { Injectable } from '@angular/core';
@Injectable()
export class TimeDiffService {
public source = interval(1000);
static _() {
return new TimeDiffService();
}
}
\ No newline at end of file
}
export let activityServiceMock = new function() {
this.toggleAllowComments = jasmine.createSpy('toggleAllowComponents').and.stub();
};
export let analyticsServiceMock = new function () {
this.send = jasmine.createSpy('send').and.stub();
this.onRouterInit = jasmine.createSpy('onRouterInit').and.stub();
this.onRouteChanged = jasmine.createSpy('onRouteChanged').and.stub();
this.preventDefault = jasmine.createSpy('preventDefault').and.stub();
this.wasDefaultPrevented = jasmine.createSpy('wasDefaultPrevented').and.stub();
};