...
 
Commits (17)
context('Boost Creation', () => {
const duplicateError = "There's already an ongoing boost for this entity";
const postContent = "Test boost, please reject..." + Math.random().toString(36).substring(8);
const nonParticipationError = 'Boost target should participate in the Rewards program.'
before(() => {
cy.server();
})
beforeEach(() => {
cy.getCookie('minds_sess')
.then((sessionCookie) => {
if (sessionCookie === null) {
return cy.login(true);
}
});
cy.visit('/newsfeed/subscriptions');
cy.location('pathname')
.should('eq', `/newsfeed/subscriptions`);
cy.route("GET", '**/api/v2/boost/prepare/**').as('prepare');
cy.route("POST", '**/api/v2/boost/activity/**').as('activity');
cy.route("GET", '**/api/v2/blockchain/wallet/balance*').as('balance');
cy.route("GET", '**/api/v2/search/suggest/**').as('suggest');
});
it('should redirect a user to buy tokens when clicked', () => {
openTopModal();
cy.get('m-boost--creator-payment-methods li h5 span')
.contains('Buy Tokens')
.click();
cy.location('pathname', { timeout: 30000 })
.should('eq', `/token`);
});
it('should allow a user to make an offchain boost for 5000 tokens', () => {
cy.post(postContent);
openTopModal();
cy.get('.m-boost--creator-section-amount input')
.type(5000);
cy.get('m-overlay-modal > div.m-overlay-modal > m-boost--creator button')
.click()
.wait('@prepare').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.deep.equal("success");
}).wait('@activity').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.deep.equal("success");
});
cy.get('.m-overlay-modal')
.should('not.be.visible')
});
it('should error if the boost is a duplicate', () => {
openTopModal();
cy.get('.m-boost--creator-section-amount input')
.type(5000);
cy.get('m-overlay-modal > div.m-overlay-modal > m-boost--creator button')
.click()
.wait('@prepare').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.deep.equal("success");
}).wait('@activity').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.deep.equal("error");
});
cy.get('[data-cy=data-minds-boost-creation-error]')
.contains(duplicateError);
});
it('should display an error if boost offer receiver has not signed up for rewards', () => {
openTopModal();
cy.get('h4')
.contains('Offers')
.click();
cy.get('m-boost--creator-p2p-search .m-boost--creator-wide-input input')
.type("minds").wait('@suggest').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.deep.equal("success");
});
cy.get('.m-boost--creator-autocomplete--results .m-boost--creator-autocomplete--result-content')
.first()
.click({force: true});
cy.get('[data-cy=data-minds-boost-creation-error]')
.contains(nonParticipationError);
});
function openTopModal() {
cy.get('#boost-actions')
.first()
.click()
.wait('@balance').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.deep.equal("success");
});
}
})
context('Channel image upload', () => {
before(() => {
cy.getCookie('minds_sess')
.then((sessionCookie) => {
if (sessionCookie === null) {
return cy.login(true);
}
});
});
beforeEach(()=> {
cy.preserveCookies();
cy.server();
cy.route("POST", "**/api/v1/newsfeed").as("newsfeedPOST");
cy.route("POST", "**/api/v1/media").as("mediaPOST");
});
it('should post an activity with an image attachment', () => {
cy.get('minds-newsfeed-poster').should('be.visible');
cy.get('minds-newsfeed-poster textarea').type('This is a post with an image');
cy.uploadFile('#attachment-input-poster', '../fixtures/international-space-station-1776401_1920.jpg', 'image/jpg');
//upload image
cy.wait('@mediaPOST');
cy.get('.m-posterActionBar__PostButton').click();
//await response for activity
cy.wait('@newsfeedPOST').then((xhr) => {
expect(xhr.status).to.equal(200);
const uploadedImageGuid = xhr.response.body.activity.entity_guid
const activityGuid = xhr.response.body.guid;
cy.get('.minds-list > minds-activity:first-child .message').contains('This is a post with an image');
// assert image
cy.get('.minds-list > minds-activity:first-child .item-image img').should('be.visible');
cy.visit(`/${Cypress.env().username}`);
let mediaHref = `/media/${uploadedImageGuid}`;
cy.get("m-channels--sorted-module[title='Images']")
.find(`a[href='${mediaHref}']`);
cy.get(`[data-minds-activity-guid='${activityGuid}']`)
.find('m-post-menu .minds-more')
.click();
cy.get(`[data-minds-activity-guid='${activityGuid}']`)
.find("li:contains('Delete')")
.click();
cy.get(`[data-minds-activity-guid='${activityGuid}'] m-post-menu m-modal-confirm .mdl-button--colored`).click();
});
});
});
context('Comment Permissions', () => {
const postMenu = 'minds-activity:first > div > m-post-menu';
const deletePostOption = "m-post-menu > ul > li:visible:contains('Delete')";
const deletePostButton = ".m-modal-confirm-buttons > button:contains('Delete')";
before(() => {
//make a post new.
cy.getCookie('minds_sess')
.then((sessionCookie) => {
if (sessionCookie === null) {
return cy.login(true);
}
});
cy.visit('/newsfeed/subscriptions');
cy.location('pathname')
.should('eq', `/newsfeed/subscriptions`);
});
afterEach(() => {
//delete the post
cy.get(postMenu).click();
cy.get(deletePostOption).click();
cy.get(deletePostButton).click();
});
beforeEach(()=> {
cy.preserveCookies();
cy.post('test post');
});
it('should disable comments', () => {
cy.server();
cy.route("POST", "**/api/v2/permissions/comments/**").as("commentToggle");
cy.get(postMenu)
.click()
.find("li:visible:contains('Disable Comments')")
.click();
cy.wait('@commentToggle').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal("success");
expect(xhr.response.body.allowed).to.equal(false);
});
//close menu
cy.get(postMenu)
.click();
cy.get('minds-activity:first')
.find("i:contains('speaker_notes_off')")
.click();
});
it('should allow comments', () => {
cy.server();
cy.route("POST", "**/api/v2/permissions/comments/**").as("commentToggle");
cy.get(postMenu)
.click()
.find("li:visible:contains('Disable Comments')")
.click();
cy.wait('@commentToggle').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal("success");
expect(xhr.response.body.allowed).to.equal(false);
});
//Menu stays open
cy.get("li:visible:contains('Allow Comments')")
.click();
cy.wait('@commentToggle').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal("success");
expect(xhr.response.body.allowed).to.equal(true);
});
//close menu
cy.get(postMenu)
.click();
cy.get('minds-activity:first')
.find("i:contains('chat_bubble')");
});
});
\ No newline at end of file
/**
* @author Ben Hayward
* @desc Spec tests for comment threads.
*/
import generateRandomId from '../support/utilities';
context('Messenger', () => {
const targetUser = 'minds';
const messagePassword = 'Passw0rd!';
const messageContent = 'this is a test message!';
const undecryptedMessage = ''
const testUsername = generateRandomId();
const testPassword = generateRandomId()+'X#';
const openMessenger = '.m-messenger--dockpane-tab';
const userSearch = '.m-messenger--userlist-search > input[type=text]';
const userList = (i) => `.m-messenger--userlist-conversations > div:nth-child(${i}) > span.m-conversation-label`;
const passwordInput = (i) => `input[type=password]:nth-child(${i})`;
const submitPassword = 'm-messenger--encryption > div > button';
const messageInput = '.m-messenger--conversation-composer > textarea';
const sendButton = '[data-cy=data-minds-conversation-send]';
const messageBubble = '.m-messenger--conversation-message-bubble';
const settingsButton = '[data-cy=data-minds-conversation-options]';
const closeButton = '[data-cy=data-minds-conversation-close]';
const destroyButton = '[data-cy=data-minds-conversation-destroy]';
before(() => {
cy.newUser(testUsername, testPassword);
});
beforeEach(() => {
cy.preserveCookies();
cy.server();
cy.route('GET', '**/api/v2/messenger/search?*').as('search');
cy.route('GET', '**/api/v2/messenger/conversations/**').as('conversations');
cy.route('POST', '**/api/v2/messenger/conversations/**').as('send');
cy.route('POST', '**/api/v2/messenger/keys/setup**').as('keys')
cy.get(openMessenger)
.click();
});
afterEach(() => {
cy.get(closeButton)
.click({multiple: true});
cy.get(openMessenger)
.click({multiple: true});
});
after(() => {
cy.deleteUser(testUsername, testPassword);
});
it('should allow a new user to set a password and send a message', () => {
cy.get(userSearch)
.type(Cypress.env().username)
.wait('@search').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal("success");
});
cy.get(userList(1))
.click();
cy.get(passwordInput(3))
.type(messagePassword)
cy.get(passwordInput(4))
.type(messagePassword)
cy.get(submitPassword)
.click();
cy.get(messageInput)
.type(messageContent);
cy.get(sendButton)
.click()
.wait('@send').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal("success");
});
});
it('should allow a user to destroy their chat content', () => {
cy.get(userSearch)
.clear()
.type(Cypress.env().username)
.wait('@search').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal("success");
});
cy.get(userList(1))
.click();
cy.get(passwordInput(3))
.type(messagePassword)
cy.get(passwordInput(4))
.type(messagePassword)
cy.get(submitPassword)
.click()
.wait('@keys').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal("success");
})
.wait('@conversations').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal("success");
});
cy.get(settingsButton)
.click();
cy.get(destroyButton)
.first()
.click();
cy.get(messageBubble)
.should('not.exist');
});
});
import generateRandomId from '../support/utilities';
context('Registration', () => {
const username = generateRandomId();
const password = `${generateRandomId()}0oA!`;
const email = 'test@minds.com';
const noSymbolPass = 'Passw0rd';
const welcomeText = "Welcome to Minds!";
const passwordDontMatch = "Passwords must match.";
const passwordInvalid = " Password must have more than 8 characters. Including uppercase, numbers, special characters (ie. !,#,@), and cannot have spaces. ";
const usernameField = 'minds-form-register #username';
const emailField = 'minds-form-register #email';
const passwordField = 'minds-form-register #password';
const password2Field = 'minds-form-register #password2';
const checkbox = '[data-cy=data-minds-accept-tos-input]';
const submitButton = 'minds-form-register .mdl-card__actions button';
beforeEach(() => {
cy.visit('/login');
cy.location('pathname').should('eq', '/login');
cy.server();
cy.route("POST", "**/api/v1/register").as("register");
});
after(() => {
cy.visit('/login');
cy.location('pathname').should('eq', '/login');
cy.login(false, username, password);
cy.deleteUser(username, password);
})
it('should allow a user to register', () => {
//type values
cy.get(usernameField)
.focus()
.type(username);
cy.get(emailField)
.focus()
.type(email);
cy.get(passwordField)
.focus()
.type(password);
cy.wait(500);
cy.get(password2Field)
.focus()
.type(password);
cy.get(checkbox)
.click({force: true});
//submit
cy.get(submitButton)
.click()
.wait('@register').then((xhr) => {
expect(xhr.status).to.equal(200);
});
//onboarding modal shown
cy.contains(welcomeText);
});
it('should display an error if password is invalid', () => {
cy.get(usernameField)
.focus()
.type(generateRandomId());
cy.get(emailField)
.focus()
.type(email);
cy.get(passwordField)
.focus()
.type(noSymbolPass);
cy.wait(500);
cy.get(password2Field)
.focus()
.type(noSymbolPass);
cy.get(checkbox)
.click({force: true});
//submit
cy.get(submitButton)
.click()
.wait('@register').then((xhr) => {
expect(xhr.status).to.equal(200);
});
cy.scrollTo('top');
cy.contains(passwordInvalid);
});
it('should display an error if passwords do not match', () => {
cy.get(usernameField)
.focus()
.type(generateRandomId());
cy.get(emailField)
.focus()
.type(email);
cy.get('minds-form-register #password')
.focus()
.type(password);
cy.wait(500);
cy.get(password2Field)
.focus()
.type(password + '!');
cy.get(checkbox)
.click({force: true});
//submit
cy.get(submitButton).click();
cy.scrollTo('top');
cy.contains(passwordDontMatch);
});
})
context('Subscription', () => {
const user = 'minds';
const subscribeButton = 'minds-button-subscribe > button';
const messageButton = 'm-messenger--channel-button > button';
const userDropdown = 'minds-button-user-dropdown > button';
beforeEach(()=> {
cy.login(true);
cy.location('pathname', { timeout: 30000 })
.should('eq', `/newsfeed/subscriptions`);
cy.visit(`/${user}`);
})
it('should allow a user to subscribe to another', () => {
subscribe();
});
it('should allow a user to unsubscribe',() => {
unsubscribe();
})
function subscribe() {
cy.get(subscribeButton).click();
cy.get(messageButton).should('be.visible');
}
function unsubscribe() {
cy.get(userDropdown).click();
cy.contains('Unsubscribe').click();
cy.get(subscribeButton).should('be.visible');
}
});
......@@ -8,6 +8,8 @@ import {
ChangeDetectorRef,
ComponentRef,
ElementRef,
Injector,
SkipSelf,
} from '@angular/core';
import { DynamicHostDirective } from '../../directives/dynamic-host.directive';
......@@ -20,12 +22,14 @@ import { VideoCard } from '../../../modules/legacy/components/cards/object/video
import { AlbumCard } from '../../../modules/legacy/components/cards/object/album/album';
import { BlogCard } from '../../../modules/blogs/card/card';
import { CommentComponentV2 } from '../../../modules/comments/comment/comment.component';
import { ActivityService } from '../../services/activity.service';
@Component({
selector: 'minds-card',
template: `
<ng-template dynamic-host></ng-template>
`,
providers: [ActivityService],
})
export class MindsCard implements AfterViewInit {
@ViewChild(DynamicHostDirective, { static: true })
......@@ -43,7 +47,10 @@ export class MindsCard implements AfterViewInit {
private initialized: boolean = false;
constructor(private _componentFactoryResolver: ComponentFactoryResolver) {}
constructor(
private _componentFactoryResolver: ComponentFactoryResolver,
private _injector: Injector
) {}
@Input('object') set _object(value: any) {
const oldType = this.type;
......@@ -121,7 +128,11 @@ export class MindsCard implements AfterViewInit {
viewContainerRef.clear();
this.componentRef = viewContainerRef.createComponent(componentFactory);
this.componentRef = viewContainerRef.createComponent(
componentFactory,
undefined,
this._injector
);
this.componentInstance = this.componentRef.instance;
this.anchorRef = viewContainerRef.element;
......
......@@ -206,6 +206,10 @@ export class ButtonsPlugin {
let $buttons = this.$element.querySelector('.medium-insert-buttons');
let $p = this.$element.querySelector('.medium-insert-active');
if (!$buttons) {
return;
}
if ($p !== null) {
let $lastCaption = $p.classList.contains('medium-insert-images-grid')
? []
......
......@@ -34,7 +34,7 @@ export class FeaturedContentComponent implements OnInit {
protected componentFactoryResolver: ComponentFactoryResolver,
protected cd: ChangeDetectorRef,
protected clientMetaService: ClientMetaService,
@SkipSelf() injector: Injector
@SkipSelf() protected injector: Injector
) {
this.clientMetaService.inherit(injector).setMedium('featured-content');
}
......@@ -81,7 +81,11 @@ export class FeaturedContentComponent implements OnInit {
const componentRef: ComponentRef<
any
> = this.dynamicHost.viewContainerRef.createComponent(componentFactory);
> = this.dynamicHost.viewContainerRef.createComponent(
componentFactory,
void 0,
this.injector
);
injector.call(this, componentRef, this.entity);
}
}
......
......@@ -198,6 +198,31 @@
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()"
......
......@@ -21,7 +21,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 */
......@@ -93,6 +97,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,
......@@ -107,6 +113,7 @@ describe('PostMenuComponent', () => {
// synchronous beforeEach
beforeEach(() => {
featuresServiceMock.mock('allow-comments-toggle', true);
fixture = TestBed.createComponent(PostMenuComponent);
comp = fixture.componentInstance;
......@@ -152,4 +159,25 @@ describe('PostMenuComponent', () => {
'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);
});
});
......@@ -5,6 +5,7 @@ import {
EventEmitter,
Input,
Output,
OnInit,
} from '@angular/core';
import { Session } from '../../../services/session';
import { OverlayModalService } from '../../../services/ux/overlay-modal';
......@@ -13,6 +14,8 @@ import { ReportCreatorComponent } from '../../../modules/report/creator/creator.
import { MindsUser } from '../../../interfaces/entities';
import { SignupModalService } from '../../../modules/modals/signup/service';
import { BlockListService } from '../../services/block-list.service';
import { ActivityService } from '../../../common/services/activity.service';
import { FeaturesService } from '../../../services/features.service';
import { ShareModalComponent } from '../../../modules/modals/share/share';
type Option =
......@@ -33,7 +36,8 @@ type Option =
| 'subscribe'
| 'unsubscribe'
| 'rating'
| 'block';
| 'block'
| 'allow-comments';
@Component({
moduleId: module.id,
......@@ -41,7 +45,7 @@ type Option =
templateUrl: 'post-menu.component.html',
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>();
......@@ -70,11 +74,15 @@ export class PostMenuComponent {
private cd: ChangeDetectorRef,
private overlayModal: OverlayModalService,
public signupModal: SignupModalService,
protected blockListService: BlockListService
protected blockListService: BlockListService,
protected activityService: ActivityService,
public featuresService: FeaturesService
) {
this.initCategories();
}
ngOnInit() {}
initCategories() {
for (let category in window.Minds.categories) {
this.categories.push({
......@@ -342,6 +350,17 @@ 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;
}
}
openShareModal() {
this.overlayModal
.create(ShareModalComponent, this.entity.url, {
......
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;
}
}
}
......@@ -102,6 +102,7 @@
[object]="boost.entity"
class="mdl-card mdl-shadow--8dp"
*ngIf="boost.entity.type == 'activity'"
[attr.data-minds-activity-guid]="boost.entity.guid"
></minds-activity>
<minds-card-group
[group]="boost.entity"
......
......@@ -19,6 +19,7 @@
*ngIf="entity"
[object]="entity"
class="mdl-card m-border item"
[attr.data-minds-activity-guid]="entity.guid"
></minds-activity>
</div>
<div
......
......@@ -42,6 +42,7 @@
[object]="entity"
class="mdl-card"
*ngIf="entity.type == 'activity'"
[attr.data-minds-activity-guid]="entity.guid"
></minds-activity>
<div class="mdl-card__supporting-text m-action-buttons">
......
......@@ -6,6 +6,7 @@ import { WireRewardsStruc } from '../modules/wire/interfaces/wire.interfaces';
export interface MindsActivityObject {
activity: 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';
......@@ -12,6 +19,7 @@ import { AttachmentService } from '../../../services/attachment';
import { ContextService } from '../../../services/context.service';
import { optimizedResize } from '../../../utils/optimized-resize';
import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { ActivityService } from '../../../common/services/activity.service';
import { ShareModalComponent } from '../../../modules/modals/share/share';
@Component({
......@@ -22,8 +30,9 @@ import { ShareModalComponent } from '../../../modules/modals/share/share';
class: 'm-blog',
},
templateUrl: 'view.html',
providers: [ActivityService],
})
export class BlogView {
export class BlogView implements OnInit, OnDestroy {
minds;
guid: string;
blog: MindsBlogEntity;
......@@ -50,6 +59,7 @@ export class BlogView {
'set-explicit',
'remove-explicit',
'rating',
'allow-comments',
];
@ViewChild('lockScreen', { read: ElementRef, static: false }) lockScreen;
......@@ -65,6 +75,8 @@ export class BlogView {
private context: ContextService,
public analytics: AnalyticsService,
public analyticsService: AnalyticsService,
protected activityService: ActivityService,
private cd: ChangeDetectorRef,
private overlayModal: OverlayModalService
) {
this.minds = window.Minds;
......@@ -78,7 +90,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();
......@@ -132,7 +144,9 @@ export class BlogView {
}
ngOnDestroy() {
if (this.scroll_listener) this.scroll.unListen(this.scroll_listener);
if (this.scroll_listener) {
this.scroll.unListen(this.scroll_listener);
}
}
menuOptionSelected(option: string) {
......@@ -165,7 +179,9 @@ export class BlogView {
}
calculateLockScreenHeight() {
if (!this.lockScreen) return;
if (!this.lockScreen) {
return;
}
const lockScreenOverlay = this.lockScreen.nativeElement.querySelector(
'.m-wire--lock-screen'
);
......
......@@ -370,7 +370,7 @@
class="m-boost--creator--submit-error"
>
<i class="material-icons">close</i>
<span>{{ error }}</span>
<span data-cy="data-minds-boost-creation-error">{{ error }}</span>
</div>
</div>
</section>
......
......@@ -20,6 +20,7 @@
class="mdl-card m-border item"
(delete)="delete(activity)"
[slot]="i + 1"
[attr.data-minds-activity-guid]="activity.guid"
></minds-activity>
<minds-activity
*ngFor="let activity of feed; let i = index"
......@@ -28,6 +29,7 @@
class="mdl-card m-border item"
(delete)="delete(activity)"
[slot]="i + (pinned?.length || 0) + 1"
[attr.data-minds-activity-guid]="activity.guid"
></minds-activity>
<infinite-scroll
distance="25%"
......
......@@ -385,7 +385,7 @@
<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>
......
......@@ -86,6 +86,7 @@ export class CommentComponent implements OnChanges {
@ViewChild('batchImage', { static: false }) batchImage: ElementRef;
@Input() canEdit: boolean = false;
@Input() canReply = true;
@Output() onReply = new EventEmitter();
......
......@@ -384,11 +384,24 @@
(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>
......
......@@ -10,6 +10,9 @@ import {
Input,
ViewChild,
ElementRef,
OnInit,
OnDestroy,
AfterViewInit,
} from '@angular/core';
import { Session } from '../../../services/session';
......@@ -21,8 +24,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 { Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { ActivityService } from '../../../common/services/activity.service';
import { Router } from '@angular/router';
import { FeaturesService } from '../../../services/features.service';
import { MindsVideoComponent } from '../../media/components/video/video.component';
......@@ -45,7 +49,8 @@ import isMobile from '../../../helpers/is-mobile';
},
],
})
export class CommentComponentV2 implements OnChanges {
export class CommentComponentV2
implements OnChanges, OnInit, OnDestroy, AfterViewInit {
comment: any;
editing: boolean = false;
minds = window.Minds;
......@@ -78,6 +83,7 @@ export class CommentComponentV2 implements OnChanges {
translateToggle: boolean = false;
commentAge$: Observable<number>;
canReply = true;
videoDimensions: Array<any> = null;
@ViewChild('player', { static: false }) player: MindsVideoComponent;
@ViewChild('batchImage', { static: false }) batchImage: ElementRef;
......@@ -98,6 +104,7 @@ export class CommentComponentV2 implements OnChanges {
private timeDiffService: TimeDiffService,
private el: ElementRef,
private router: Router,
protected activityService: ActivityService,
protected featuresService: FeaturesService
) {}
......@@ -118,9 +125,13 @@ export class CommentComponentV2 implements OnChanges {
}
}
ngOnDestroy() {}
@Input('comment')
set _comment(value: any) {
if (!value) return;
if (!value) {
return;
}
this.comment = value;
this.attachment.load(this.comment);
......@@ -147,7 +158,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;
......
......@@ -49,7 +49,8 @@
!ascendingInProgress &&
!error &&
comments?.length === 0 &&
parent.type == 'activity'
parent.type == 'activity' &&
activityService.allowComment$
"
i18n="@@MINDS__COMMENTS__START_CONVERSATION"
>
......
......@@ -7,6 +7,8 @@ import {
Input,
Renderer,
ViewChild,
OnInit,
OnDestroy,
} from '@angular/core';
import { Client } from '../../../services/api/client';
......@@ -15,6 +17,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,
......@@ -33,10 +36,11 @@ import { SocketsService } from '../../../services/sockets';
useFactory: AttachmentService._,
deps: [Session, Client, Upload],
},
ActivityService,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CommentsListComponent {
export class CommentsListComponent implements OnInit, OnDestroy {
minds;
object;
guid: string = '';
......@@ -71,7 +75,6 @@ export class CommentsListComponent {
socketSubscriptions: any = {
comment: null,
};
error: string;
@Input() conversation: boolean = false;
......@@ -91,7 +94,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;
}
......@@ -99,13 +103,18 @@ export class CommentsListComponent {
set _object(value: any) {
this.object = value;
this.guid = this.object.guid;
if (this.object.entity_guid) this.guid = 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) this.reversed = true;
else this.reversed = false;
if (value) {
this.reversed = true;
} else {
this.reversed = false;
}
}
ngOnInit() {
......@@ -165,7 +174,7 @@ export class CommentsListComponent {
} else {
this.ascendingInProgress = false;
}
//this.moreDescendingData = true;
// this.moreDescendingData = true;
if (!response.comments) {
if (descending) {
......@@ -178,8 +187,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);
......@@ -250,7 +259,7 @@ export class CommentsListComponent {
this.overscrollTimer = setTimeout(() => {
if (this.overscrollAmount < -75) {
//75px
// 75px
this.autoloadPrevious();
}
......@@ -319,13 +328,14 @@ export class CommentsListComponent {
}
// if the list is scrolled to the bottom
let scrolledToBottom =
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();
......@@ -352,14 +362,14 @@ export class CommentsListComponent {
) {
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();
});
......@@ -415,11 +425,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({
const newLength = this.comments.push({
// Optimistic
description: this.content,
guid: 0,
......@@ -438,7 +448,7 @@ export class CommentsListComponent {
this.commentsScrollEmitter.emit('bottom');
try {
let response: any = await this.client.post(
const response: any = await this.client.post(
'api/v1/comments/' + this.guid,
data
);
......
......@@ -45,7 +45,8 @@
!inProgress &&
!error &&
comments?.length === 0 &&
parent.type == 'activity'
parent.type == 'activity' &&
(activityService.allowComment$ | async)
"
i18n="@@MINDS__COMMENTS__START_CONVERSATION"
>
......@@ -112,6 +113,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,9 @@ 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',
......@@ -25,7 +30,7 @@ import { BlockListService } from '../../../common/services/block-list.service';
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [CommentsService],
})
export class CommentsThreadComponent {
export class CommentsThreadComponent implements OnInit {
minds;
@Input() parent;
@Input() entity;
......@@ -67,7 +72,8 @@ export class CommentsThreadComponent {
public sockets: SocketsService,
private renderer: Renderer,
protected blockListService: BlockListService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
public activityService: ActivityService
) {
this.minds = window.Minds;
}
......@@ -198,7 +204,7 @@ export class CommentsThreadComponent {
const parent_path = this.parent.child_path || '0:0:0';
let scrolledToBottom =
const scrolledToBottom =
this.scrollView.nativeElement.scrollTop +
this.scrollView.nativeElement.clientHeight >=
this.scrollView.nativeElement.scrollHeight;
......@@ -289,8 +295,6 @@ export class CommentsThreadComponent {
}
onPosted({ comment, index }) {
console.log('onPosted called');
console.log(comment, index);
this.comments[index] = comment;
this.detectChanges();
}
......
......@@ -7,6 +7,8 @@ import {
Input,
Output,
Renderer,
OnInit,
OnDestroy,
} from '@angular/core';
import {
ActivatedRoute,
......@@ -38,7 +40,7 @@ import { CommentsService } from '../comments.service';
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CommentsTreeComponent {
export class CommentsTreeComponent implements OnInit, OnDestroy {
minds;
entity;
guid: string = '';
......
......@@ -125,6 +125,7 @@
type="checkbox"
class="mdl-checkbox__input"
formControlName="tos"
data-cy="data-minds-accept-tos-input"
/>
<span
......
......@@ -39,6 +39,7 @@
[showRatingToggle]="true"
[slot]="i + 1"
class="mdl-card item"
[attr.data-minds-activity-guid]="activity.guid"
>
</minds-activity>
......@@ -49,6 +50,7 @@
[canDelete]="group['is:owner'] || group['is:moderator']"
(delete)="delete(a)"
[slot]="i + (pinned?.length || 0) + 1"
[attr.data-minds-activity-guid]="a.guid"
>
<!-- Menu Actions -->
<li
......
......@@ -52,6 +52,7 @@
[canDelete]="group['is:owner'] || group['is:moderator']"
(delete)="delete(entity)"
[slot]="i + 1"
[attr.data-minds-activity-guid]="entity.guid"
>
<!-- Menu Actions -->
......
......@@ -22,10 +22,12 @@ import { HashtagsSelectorComponent } from '../../hashtags/selector/selector.comp
import { VideoChatService } from '../../videochat/videochat.service';
import { UpdateMarkersService } from '../../../common/services/update-markers.service';
import { filter, map, startWith, throttle } from 'rxjs/operators';
import { ActivityService } from '../../../common/services/activity.service';
@Component({
selector: 'm-groups--profile',
templateUrl: 'profile.html',
providers: [ActivityService],
})
export class GroupsProfile {
guid;
......
......@@ -8,6 +8,7 @@
[canDelete]="group['is:owner'] || group['is:moderator']"
(delete)="delete(entity)"
[hideTabs]="true"
[attr.data-minds-activity-guid]="entity.guid"
>
<!-- Menu Actions -->
......
......@@ -4,5 +4,6 @@
*ngFor="let post of posts; let i = index"
class="mdl-card item"
[object]="post"
[attr.data-minds-activity-guid]="post.guid"
></minds-activity>
</div>
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
) {}
ngOnInit() {}
ngOnDestroy() {}
set _object(value: any) {
this.object = value;
this.activityService.allowComment$.next(this.object.allow_comments);
}
}
......@@ -27,6 +27,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';
import { FeaturesService } from '../../../../../services/features.service';
import isMobile from '../../../../../helpers/is-mobile';
......@@ -45,7 +46,11 @@ import isMobile from '../../../../../helpers/is-mobile';
'showRatingToggle',
],
outputs: ['_delete: delete', 'commentsOpened', 'onViewed'],
providers: [ClientMetaService, ActivityAnalyticsOnViewService],
providers: [
ClientMetaService,
ActivityAnalyticsOnViewService,
ActivityService,
],
templateUrl: 'activity.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
......@@ -60,6 +65,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()
......@@ -78,7 +84,7 @@ export class Activity implements OnInit {
type: string;
element: any;
visible: boolean = false;
deleted: boolean = false;
editing: boolean = false;
@Input() hideTabs: boolean;
......@@ -114,6 +120,7 @@ export class Activity implements OnInit {
'set-explicit',
'block',
'rating',
'allow-comments',
];
} else {
return [
......@@ -127,6 +134,7 @@ export class Activity implements OnInit {
'set-explicit',
'block',
'rating',
'allow-comments',
];
}
} else {
......@@ -140,6 +148,7 @@ export class Activity implements OnInit {
'set-explicit',
'block',
'rating',
'allow-comments',
];
}
}
......@@ -162,6 +171,7 @@ export class Activity implements OnInit {
protected clientMetaService: ClientMetaService,
protected featuresService: FeaturesService,
public suggestions: AutocompleteSuggestionsService,
protected activityService: ActivityService,
@SkipSelf() injector: Injector,
elementRef: ElementRef
) {
......@@ -222,6 +232,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() {
......@@ -263,6 +275,7 @@ export class Activity implements OnInit {
$event.completed.emit(0);
}
this._delete.next(this.activity);
this.deleted = true;
})
.catch(e => {
if ($event.inProgress) {
......@@ -301,6 +314,9 @@ export class Activity implements OnInit {
}*/
openComments() {
if (!this.shouldShowComments()) {
return;
}
this.commentsToggle = !this.commentsToggle;
this.commentsOpened.emit(this.commentsToggle);
}
......@@ -399,10 +415,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;
......@@ -500,6 +517,10 @@ export class Activity implements OnInit {
this.activity.mature_visibility = !this.activity.mature_visibility;
}
shouldShowComments() {
return this.activity.allow_comments || this.activity['comments:count'] >= 0;
}
setVideoDimensions($event) {
this.videoDimensions = $event.dimensions;
this.activity.custom_data.dimensions = this.videoDimensions;
......
......@@ -9,6 +9,7 @@ import { Client } from '../../../../../services/api';
import { Session } from '../../../../../services/session';
import { AttachmentService } from '../../../../../services/attachment';
import { ActivityService } from '../../../../../common/services/activity.service';
@Component({
moduleId: module.id,
......@@ -18,6 +19,7 @@ import { AttachmentService } from '../../../../../services/attachment';
host: {
class: 'mdl-shadow--8dp',
},
providers: [ActivityService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ActivityPreview {
......@@ -25,6 +27,7 @@ export class ActivityPreview {
activity: any;
hideTabs: boolean;
deleted: boolean = false;
editing: boolean = false;
commentsToggle: boolean = false;
showBoostOptions: boolean = false;
......
......@@ -14,6 +14,7 @@ import { Router } from '@angular/router';
import { Client } from '../../../../../services/api';
import { Session } from '../../../../../services/session';
import { AttachmentService } from '../../../../../services/attachment';
import { ActivityService } from '../../../../../common/services/activity.service';
import { OverlayModalService } from '../../../../../services/ux/overlay-modal';
import { MediaModalComponent } from '../../../../media/modal/modal.component';
import { FeaturesService } from '../../../../../services/features.service';
......@@ -23,6 +24,7 @@ import isMobile from '../../../../../helpers/is-mobile';
moduleId: module.id,
selector: 'minds-remind',
inputs: ['object', '_events: events'],
providers: [ActivityService],
templateUrl: '../activity/activity.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
......@@ -36,6 +38,7 @@ export class Remind {
events: EventEmitter<any>;
eventsSubscription: any;
deleted: boolean = false;
editing: boolean = false;
commentsToggle: boolean = false;
showBoostOptions: boolean = false;
......
import { Component, EventEmitter } from '@angular/core';
import { Client } from '../../../services/api';
@Component({
<<<<<<< a596794609983ebbce246788506c54ac2ace58a8
selector: 'm-channel--carousel',
inputs: ['_banners: banners', '_editMode: editMode'],
=======
selector: 'minds-carousel',
inputs: ['_banners: banners', '_editMode: editMode', 'blurred'],
>>>>>>> (feat): mark a channel profile explicit
outputs: ['done_event: done', 'delete_event: delete'],
template: `
<i class="material-icons left" (click)="prev()" [hidden]="banners.length <= 1">keyboard_arrow_left</i>
<div *ngFor="let banner of banners; let i = index">
<minds-banner
[src]="banner.src"
[top]="banner.top_offset"
[overlay]="true"
[ngClass]="{'is-hidden': i != index, 'edit-mode': editing, 'm-mature-banner': blurred}"
[editMode]="editing"
[done]="done"
(added)="added($event, i)"
></minds-banner>
<div class="delete-button" (click)="delete(i)" [hidden]="i != index || !editing">
<button class="mdl-button mdl-button--raised mdl-button--colored material-icons">X</button>
</div>
</div>
<i class="material-icons right" (click)="next()" [hidden]="banners.length <= 1">keyboard_arrow_right</i>
`
})
export class CarouselComponent {
minds: Minds = window.Minds;
banners: Array<any> = [];
editing: boolean = false;
src: string = '';
modified: Array<any> = []; //all banners should be exported to here on the done event, and sent to parent
done_event = new EventEmitter();
delete_event = new EventEmitter();
done: boolean = false; //if set to true, tells the child component to return "added"
rotate: boolean = true; //if set to true enabled rotation
rotate_timeout; //the timeout for the rotator
interval: number = 3000; //the interval for each banner to stay before rotating
index: number = 0; //the current visible index of the carousel.
blurred: boolean = false;
constructor() {
this.run();
}
/**
* A list of banners are sent from the parent, if done are sent a blank one is entered
*/
set _banners(value: any) {
if (value) {
this.banners = value;
} else {
this.banners.push({
src: null
});
}
}
/**
* If the parent set edit mode
*/
set _editMode(value: boolean) {
console.log('[carousel]: edit mode event received');
//was in edit more, now settings not in edit more
if (this.editing && !value) {
console.log('[carousel]: edit mode ended');
this._done();
return;
}
this.editing = value;
if (!this.editing) {
return;
}
console.log('[carousel]: edit mode enabled');
this.rotate = false;
this.done = false;
var blank_banner = false;
for (var i in this.banners) {
if (!this.banners[i].src)
blank_banner = true;
}
if (!blank_banner) {
this.banners.push({
src: null
});
}
}
/**
* Fired when the child component adds a new banner
*/
added(value: any, index) {
console.log(this.banners[index].guid, value.file);
if (!this.banners[index].guid && !value.file)
return; //this is our 'add new' post
//detect if we have changed
var changed = false;
if (value.top !== this.banners[index].top)
changed = false;
if (value.file)
changed = true;
if (!changed)
return;
if (!this.banners[index].src) {
this.banners[index].src = value.file;
}
this.modified.push({
guid: this.banners[index].guid,
index: index,
file: value.file,
top: value.top
});
}
delete(index) {
this.delete_event.next(this.banners[index]);
this.banners.splice(index, 1);
if (this.banners.length === 0) {
this.banners.push({ src: null });
}
this.next();
}
/**
* Once we retreive all the modified banners, we fire back to the parent the new list
*/
_done() {
this.editing = false; //this should update each banner (I'd prefer even driven but change detection works..)
this.done = true;
console.log('[carousel]: received done event');
//after one second?
setTimeout(() => {
this.done_event.next(this.modified);
this.modified = [];
let blank_banner: any = false;
for (var i in this.banners) {
if (!this.banners[i].src)
blank_banner = i;
}
if (blank_banner !== false) {
this.banners.splice(blank_banner, 1);
this.next();
}
}, 1000);
}
prev() {
var max = this.banners.length - 1;
if (this.index === 0)
this.index = max;
else
this.index--;
this.run();//resets the carousel
}
next() {
var max = this.banners.length - 1;
if (this.index >= max)
this.index = 0;
else
this.index++;
this.run();//resets the carousel
}
run() {
if (this.rotate_timeout)
clearTimeout(this.rotate_timeout);
this.rotate_timeout = setTimeout(() => {
if (this.rotate) {
var max = this.banners.length - 1;
if (this.index >= max)
this.index = 0;
else
this.index++;
}
this.run();
}, this.interval);
}
ngOnDestroy() {
clearTimeout(this.rotate_timeout);
}
}
......@@ -21,6 +21,7 @@ import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { AnalyticsService } from '../../../services/analytics';
import { MindsVideoComponent } from '../components/video/video.component';
import isMobileOrTablet from '../../../helpers/is-mobile-or-tablet';
import { ActivityService } from '../../../common/services/activity.service';
@Component({
selector: 'm-media--modal',
......@@ -51,6 +52,7 @@ import isMobileOrTablet from '../../../helpers/is-mobile-or-tablet';
transition(':leave', [animate('300ms', style({ opacity: 0 }))]),
]),
],
providers: [ActivityService],
})
export class MediaModalComponent implements OnInit, OnDestroy {
minds = window.Minds;
......
......@@ -275,7 +275,7 @@
<!-- Don't show comments for albums -->
<div
class="mdl-grid m-media-content--comments mdl-color--white"
*ngIf="entity.guid && entity.subtype != 'album'"
*ngIf="canShowComments() || (activityService.allowComment$ | async)"
>
<m-comments__tree [entity]="entity"> </m-comments__tree>
</div>
......
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,6 +10,7 @@ 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,
......@@ -21,9 +22,10 @@ import { MindsTitle } from '../../../services/ux/title';
useFactory: RecommendedService._,
deps: [Client],
},
ActivityService,
],
})
export class MediaViewComponent {
export class MediaViewComponent implements OnInit, OnDestroy {
minds = window.Minds;
guid: string;
entity: any = {};
......@@ -32,6 +34,7 @@ export class MediaViewComponent {
deleteToggle: boolean = false;
theaterMode: boolean = false;
allowComments = true;
menuOptions: Array<string> = [
'edit',
......@@ -43,6 +46,8 @@ export class MediaViewComponent {
'subscribe',
'remove-explicit',
'rating',
'allow-comments',
'disable-comments',
];
paramsSubscription: Subscription;
......@@ -57,7 +62,8 @@ export class MediaViewComponent {
public route: ActivatedRoute,
public attachment: AttachmentService,
public context: ContextService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
protected activityService: ActivityService
) {}
ngOnInit() {
......@@ -100,7 +106,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');
......@@ -174,6 +180,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;
}
}
......@@ -191,6 +205,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();
......
......@@ -19,8 +19,16 @@
<i class="material-icons mdl-color-text--blue-grey-100" [hidden]="live"
>sync_problem</i
>
<i class="material-icons" (click)="ribbonToggle()">more_vert</i>
<i class="material-icons" (click)="dockpanes.close(conversation)"
<i
class="material-icons"
(click)="ribbonToggle()"
data-cy="data-minds-conversation-options"
>more_vert</i
>
<i
class="material-icons"
(click)="dockpanes.close(conversation)"
data-cy="data-minds-conversation-close"
>close</i
>
</div>
......@@ -35,6 +43,7 @@
<div
class="m-messenger--dockpane-tab-icon mdl-color-text--blue-grey-300"
(click)="deleteHistory(); ribbonOpened = false"
data-cy="data-minds-conversation-destroy"
>
<i
class="material-icons mdl-color-text--blue-grey-100"
......@@ -244,6 +253,7 @@
<i
class="material-icons mdl-color-text--blue-grey-600"
(click)="send($event); emoji.close()"
data-cy="data-minds-conversation-send"
>send_arrow</i
>
<minds-emoji [localDirective]="emoji"></minds-emoji>
......
......@@ -48,6 +48,7 @@
[showBoostMenuOptions]="true"
[slot]="i + 1"
[visibilityEvents]="false"
[attr.data-minds-activity-guid]="boost.guid"
#activities
></minds-activity>
</ng-container>
......@@ -17,6 +17,7 @@
(delete)="delete(activity)"
[slot]="i + 1"
class="mdl-card m-border item"
[attr.data-minds-activity-guid]="activity.guid"
>
</minds-activity>
</ng-container>
......
......@@ -14,6 +14,7 @@
[showRatingToggle]="true"
[slot]="slot"
class="mdl-card m-border item"
[attr.data-minds-activity-guid]="entity.guid"
></minds-activity>
</ng-container>
</ng-container>
......@@ -21,6 +21,7 @@
(delete)="delete(preActivity)"
[showRatingToggle]="true"
class="mdl-card m-border item"
[attr.minds-data-activity-guid]="preActivity.guid"
></minds-activity>
<m-newsfeed--boost-rotator
......@@ -46,6 +47,7 @@
(delete)="delete(activity)"
[showRatingToggle]="true"
[slot]="i + 1"
[attr.data-minds-activity-guid]="activity.guid"
></minds-activity>
</ng-container>
......
......@@ -7,6 +7,7 @@
[showRatingToggle]="true"
class="mdl-card m-border item"
[slot]="i + 1"
[attr.data-minds-activity-guid]="activity.guid"
></minds-activity>
<infinite-scroll
distance="25%"
......
......@@ -9,6 +9,7 @@
[showRatingToggle]="true"
[slot]="i + 1"
class="mdl-card m-border item"
[attr.data-minds-activity-guid]="activity.guid"
></minds-activity>
<infinite-scroll
distance="25%"
......
......@@ -36,6 +36,7 @@
[showRatingToggle]="true"
[slot]="1"
class="mdl-card mdl-shadow--2dp item"
[attr.data-minds-activity-guid]="activity.guid"
>
</minds-activity>
</div>
......
......@@ -3,12 +3,16 @@ import { Client } from '../../services/api';
import { SocketsService } from '../../services/sockets';
import { Session } from '../../services/session';
import { MindsTitle } from '../../services/ux/title';
import { Subscription, timer } from 'rxjs';
export class NotificationService {
socketSubscriptions: any = {
notification: null,
};
onReceive: EventEmitter<any> = new EventEmitter();
notificationPollTimer;
private updateNotificationCountSubscription: Subscription;
static _(
session: Session,
......@@ -71,22 +75,26 @@ export class NotificationService {
* Return the notifications
*/
getNotifications() {
var self = this;
setInterval(function() {
// console.log('getting notifications');
const pollIntervalSeconds = 60;
this.notificationPollTimer = timer(0, pollIntervalSeconds * 1000);
this.updateNotificationCountSubscription = this.notificationPollTimer.subscribe(
() => this.updateNotificationCount()
);
}
if (!self.session.isLoggedIn()) return;
updateNotificationCount() {
if (!this.session.isLoggedIn()) {
return;
}
if (!window.Minds.notifications_count)
window.Minds.notifications_count = 0;
if (!window.Minds.notifications_count) {
window.Minds.notifications_count = 0;
}
self.client
.get('api/v1/notifications/count', {})
.then((response: any) => {
window.Minds.notifications_count = response.count;
self.sync();
});
}, 60000);
this.client.get('api/v1/notifications/count', {}).then((response: any) => {
window.Minds.notifications_count = response.count;
this.sync();
});
}
/**
......@@ -101,4 +109,8 @@ export class NotificationService {
}
this.title.setCounter(window.Minds.notifications_count);
}
ngOnDestroy() {
this.notificationPollTimer.unsubscribe();
}
}
......@@ -70,7 +70,7 @@
<input
type="text"
class="m-wire--creator-wide-input--edit"
[ngModel]="wire.amount | number"
[ngModel]="wire.amount"
(ngModelChange)="setAmount($event)"
(focus)="amountEditorFocus()"
(blur)="amountEditorBlur()"
......
......@@ -47,6 +47,7 @@ import { sessionMock } from '../../../../tests/session-mock.spec';
import { web3WalletServiceMock } from '../../../../tests/web3-wallet-service-mock.spec';
import { IfFeatureDirective } from '../../../common/directives/if-feature.directive';
import { FeaturesService } from '../../../services/features.service';
import { featuresServiceMock } from '../../../../tests/features-service-mock.spec';
import { MockComponent } from '../../../utils/mock';
/* tslint:disable */
......@@ -219,7 +220,7 @@ describe('WireCreatorComponent', () => {
{ provide: WireContractService, useValue: wireContractServiceMock },
{ provide: WireService, useValue: wireServiceMock },
Web3WalletService,
FeaturesService,
{ provide: FeaturesService, useValue: featuresServiceMock },
{ provide: Web3WalletService, useValue: web3WalletServiceMock },
{ provide: OverlayModalService, useValue: overlayModalServiceMock },
{ provide: TokenContractService, useValue: tokenContractServiceMock },
......@@ -238,7 +239,7 @@ describe('WireCreatorComponent', () => {
jasmine.clock().uninstall();
jasmine.clock().install();
fixture = TestBed.createComponent(WireCreatorComponent);
featuresServiceMock.mock('wire-multi-currency', true);
comp = fixture.componentInstance; // LoginForm test instance
clientMock.response = {};
clientMock.response[`api/v2/boost/rates`] = {
......
......@@ -322,13 +322,24 @@ export class WireCreatorComponent {
return;
}
if (amount.indexOf('.') === 0) {
if (amount.length === 1) {
return; // not propogration
}
amount = `0${amount}`;
}
if (typeof amount === 'number') {
this.wire.amount = amount;
console.log('amount is a number');
return;
}
amount = amount.replace(/,/g, '');
this.wire.amount = parseFloat(amount);
const amountAsFloat = parseFloat(amount);
if (amountAsFloat) {
this.wire.amount = amountAsFloat;
}
}
/**
......@@ -355,11 +366,11 @@ export class WireCreatorComponent {
}
/**
* Round by 4
* Round by 6
*/
roundAmount() {
this.wire.amount =
Math.round(parseFloat(`${this.wire.amount}`) * 10000) / 10000;
Math.round(parseFloat(`${this.wire.amount}`) * 1000000) / 1000000;
}
// Charge and rates
......
......@@ -23,12 +23,12 @@ export class FeaturesService {
if (typeof this._features[feature] === 'undefined') {
if (isDevMode() && !this._hasWarned(feature)) {
console.warn(
`[FeaturedService] Feature '${feature}' is not declared. Assuming true.`
`[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()) {
......
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();
})();