...
 
Commits (9)
......@@ -116,18 +116,47 @@ context('Discovery', () => {
cy.get("m-topbar--navigation--options ul > m-nsfw-selector ul > li:contains('Other')").click();
});
it('should allow the user to filter by a single hashtag', () => {
it('should allow the user to turn off single hashtag filter and view all posts', () => {
cy.visit('/newsfeed/global/top');
cy.get('m-hashtagssidebarselector__item')
.first()
.click();
});
it('should allow the user to turn off single hashtag filter and view all posts', () => {
it.skip('should allow the user to toggle a single hashtag and then toggle back to the initial feed', () => {
cy.visit('/newsfeed/global/top');
cy.get('m-hashtagssidebarselector__item')
.first()
.find('.m-hashtagsSidebarSelectorList__visibility > i')
.click();
})
// get first label value
cy.get('.m-hashtagsSidebarSelectorList__label').first().invoke('text').then((text) => {
// repeat twice to capture full cycle.
Cypress._.times(2, (i) => {
// split hashtag off of label text
let label = text.split('#')[1];
// click switch
toggleFirstVisibilitySwitch();
// check location name has updated
cy.location('pathname')
.should('eq', `/newsfeed/global/top;period=12h;hashtag=${label}`);
// click switch
toggleFirstVisibilitySwitch();
// check location name has updated
cy.location('pathname')
.should('eq', `/newsfeed/global/top;period=12h`);
});
});
});
// click first visibility switch
const toggleFirstVisibilitySwitch = () => {
cy.get('m-hashtagssidebarselector__item')
.first()
.find('.m-hashtagsSidebarSelectorList__visibility > i')
.click();
}
})
......@@ -661,4 +661,56 @@ context('Newsfeed', () => {
});
});
// enable once failing tests are fixed
it.skip('should post an nsfw activity when value is held by the selector (is blue) but it has not been clicked yet', () => {
// click on nsfw dropdown
cy.get(
'minds-newsfeed-poster m-nsfw-selector .m-dropdown--label-container'
).click();
// select Nudity
cy.get('minds-newsfeed-poster m-nsfw-selector .m-dropdownList__item')
.contains('Nudity')
.click();
// click away
cy.get('minds-newsfeed-poster m-nsfw-selector .minds-bg-overlay').click();
// navigate away from newsfeed and back.
cy.get('[data-cy=data-minds-nav-wallet-button]').first().click(); // bottom bar exists, so take first child
cy.get('[data-cy=data-minds-nav-newsfeed-button]').first().click();
newActivityContent('This is a nsfw post');
postActivityAndAwaitResponse(200);
// should have the mature text toggle
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-text-toggle'
).should('not.have.class', 'mdl-color-text--red-500');
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-message-content'
).should('have.class', 'm-mature-text');
// click the toggle
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-text-toggle'
).click();
// text should be visible now
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-text-toggle'
).should('have.class', 'mdl-color-text--red-500');
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-message-content'
).should('not.have.class', 'm-mature-text');
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-message-content'
).contains('This is a nsfw post');
deleteActivityFromNewsfeed();
});
});
......@@ -43,7 +43,6 @@
<li
class="m-dropdownList__item m-user-menuDropdown__Item"
(click)="closeMenu()"
*ngIf="getCurrentUser()?.pro"
>
<a routerLink="/analytics/dashboard/traffic">
<i class="material-icons">timeline</i>
......
......@@ -5,6 +5,7 @@
routerLinkActive="m-v2-topbarNav__Item--active"
title="Newsfeed"
i18n-title
data-cy="data-minds-nav-newsfeed-button"
>
<i class="material-icons">home</i>
<span class="m-v2-topbarNavItem__Text" i18n>Newsfeed</span>
......@@ -16,6 +17,7 @@
routerLinkActive="m-v2-topbarNav__Item--active"
title="Discovery"
i18n-title
data-cy="data-minds-nav-discovery-button"
>
<i class="material-icons">search</i>
<span class="m-v2-topbarNavItem__Text" i18n>Discovery</span>
......@@ -27,6 +29,7 @@
routerLinkActive="m-v2-topbarNav__Item--active"
title="Wallet"
i18n-title
data-cy="data-minds-nav-wallet-button"
>
<i class="material-icons">account_balance</i>
<span class="m-v2-topbarNavItem__Text" i18n>Wallet</span>
......
......@@ -32,10 +32,10 @@
class="m-analyticsDashboard__description"
*ngIf="description$ | async as description"
>
{{ description }}
<ng-container *ngIf="(category$ | async) === 'earnings'">
<a *ngIf="!session.getLoggedInUser().pro" routerLink="/pro"
>Upgrade to Pro</a
<span *ngIf="!session.getLoggedInUser().pro">
In order to start earning,
<a routerLink="/pro">upgrade to Pro</a>.</span
>
<a
*ngIf="
......@@ -49,6 +49,7 @@
>Enable payouts</a
>
</ng-container>
{{ description }}
</p>
<m-analytics__layout--chart
m-dashboardLayout__body
......
<div class="m-channel--explicit-overlay--content">
<h3>
This channel contains mature content
</h3>
<div
class="m-btn m-btn--slim m-btn--action m-channel--explicit-overlay--action"
(click)="disableFilter()"
>
View
<div class="m-channel--explicit-overlay--container" *ngIf="!hidden">
<div class="m-channel--explicit-overlay--content">
<h3>
This channel contains mature content
</h3>
<div
class="m-btn m-btn--slim m-btn--action m-channel--explicit-overlay--action"
(click)="disableFilter()"
>
View
</div>
</div>
</div>
m-channel--explicit-overlay {
.m-channel--explicit-overlay--container {
display: flex;
justify-content: center;
align-items: center;
......
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ExplicitOverlayComponent } from './overlay.component';
import { Session } from '../../../services/session';
import { sessionMock } from '../../../../tests/session-mock.spec';
import { Storage } from '../../../services/storage';
import { Router } from '@angular/router';
import { storageMock } from '../../../../tests/storage-mock.spec';
let routerMock = new (function() {
this.navigate = jasmine.createSpy('navigate');
})();
describe('OverlayComponent', () => {
let comp: ExplicitOverlayComponent;
let fixture: ComponentFixture<ExplicitOverlayComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ExplicitOverlayComponent],
imports: [],
providers: [
{ provide: Storage, useValue: storageMock },
{ provide: Session, useValue: sessionMock },
{ provide: Router, useValue: routerMock },
],
}).compileComponents();
}));
beforeEach(done => {
jasmine.MAX_PRETTY_PRINT_DEPTH = 10;
jasmine.clock().uninstall();
jasmine.clock().install();
fixture = TestBed.createComponent(ExplicitOverlayComponent);
comp = fixture.componentInstance;
comp.hidden = true;
fixture.detectChanges();
if (fixture.isStable()) {
done();
} else {
fixture.whenStable().then(() => {
done();
});
}
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should not show overlay when mature visibility is set', () => {
comp.channel = {
mature_visibility: true,
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeTruthy();
});
it('should overlay when channel is mature', () => {
comp._channel = {
mature: true,
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeFalsy();
});
it('should overlay when channel is nsfw for one reason', () => {
comp._channel = {
nsfw: [1],
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeFalsy();
});
it('should overlay when channel is nsfw for multiple reason', () => {
comp._channel = {
nsfw: [1, 2, 3],
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeFalsy();
});
it('should overlay not show overlay if channel is not nsfw, mature and no mature_visibility', () => {
comp._channel = {
mature_visibility: false,
mature: false,
nsfw: [],
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeTruthy();
});
it('should not register undefined values as a false positive, and show the overlay', () => {
comp._channel = {
mature_visibility: undefined,
mature: undefined,
nsfw: undefined,
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeTruthy();
});
});
import { Component, HostBinding, Input } from '@angular/core';
import { Component, Input } from '@angular/core';
import { Session } from '../../../services/session';
import { Router } from '@angular/router';
import { Storage } from '../../../services/storage';
......@@ -8,16 +8,12 @@ import { Storage } from '../../../services/storage';
templateUrl: 'overlay.component.html',
})
export class ExplicitOverlayComponent {
@HostBinding('hidden') hidden: boolean;
_channel: any;
public hidden = true;
public _channel: any;
@Input() set channel(value: any) {
this._channel = value;
this.hidden =
!this._channel ||
!this._channel.is_mature ||
this._channel.mature_visibility;
this.showOverlay();
}
constructor(
......@@ -34,8 +30,31 @@ export class ExplicitOverlayComponent {
this.router.navigate(['/login']);
}
disableFilter() {
/**
* Disables overlay screen, revealing channel.
*/
protected disableFilter(): void {
this._channel.mature_visibility = true;
this.hidden = true;
}
/**
* Determines whether the channel overlay should be shown
* over the a channel.
*/
public showOverlay(): void {
if (!this._channel) {
return;
}
if (this._channel.mature_visibility) {
this.hidden = true;
} else if (this._channel.mature) {
this.hidden = false;
} else if (this._channel.nsfw && this._channel.nsfw.length > 0) {
this.hidden = false;
} else {
this.hidden = true;
}
}
}
......@@ -263,16 +263,14 @@
</button>
</div>
<ng-container *mIfFeature="'purchase-pro'">
<a
*ngIf="showBecomeProButton"
class="m-btn m-link-btn m-btn--with-icon m-btn--slim m-btn--action"
routerLink="/pro"
>
<i class="material-icons">business_center</i>
<span i18n>Become Pro</span>
</a>
</ng-container>
<a
*ngIf="showBecomeProButton"
class="m-btn m-link-btn m-btn--with-icon m-btn--slim m-btn--action"
[routerLink]="proSettingsRouterLink"
>
<i class="material-icons">business_center</i>
<span i18n>Try Pro</span>
</a>
<a
*ngIf="showProSettings"
......
......@@ -122,16 +122,12 @@ export class SidebarSelectorComponent implements OnInit {
}
hashtagVisibilityChange(hashtag) {
if (this.currentHashtag !== hashtag.value) {
this.currentHashtag = hashtag.value;
this.filterChange.emit({
type: 'single',
value: this.currentHashtag,
});
} else {
this.currentHashtag = null;
}
this.currentHashtag =
this.currentHashtag !== hashtag.value ? hashtag.value : null;
this.filterChange.emit({
type: 'single',
value: this.currentHashtag,
});
}
preferredChange() {
......
......@@ -128,11 +128,13 @@
<ng-container *mIfFeature="'pro'">
<a
class="m-page--sidebar--navigation--item"
routerLink="/pro"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
*ngIf="session.isLoggedIn() && !session.getLoggedInUser().pro"
>
<i class="material-icons">business_center</i>
<span>Become Pro</span>
<span>Try Pro</span>
</a>
</ng-container>
......
......@@ -60,7 +60,10 @@
</label>
<ng-container *mIfFeature="'top-feeds'; else oldNSFW">
<m-nsfw-selector (selectedChange)="onNSWFSelections($event)">
<m-nsfw-selector
#nsfwSelector
(selectedChange)="onNSWFSelections($event)"
>
</m-nsfw-selector>
</ng-container>
......
......@@ -277,4 +277,36 @@ describe('PosterComponent', () => {
'api/v1/newsfeed'
);
}));
it('should allow the user to make an NSFW post', fakeAsync(() => {
comp.attachment.setNSFW([
{ value: 'naughty', selected: true },
{ value: 'rude', selected: true },
{ value: 'not very nice', selected: true },
]);
comp.meta.message = 'test #tags ';
comp.hashtagsSelector.parseTags(comp.meta.message);
fixture.detectChanges();
clientMock.response['api/v1/newsfeed'] = { status: 'success' };
spyOn(window, 'alert').and.callFake(function() {
return true;
});
spyOn(comp, 'post').and.callThrough();
getPostButton().nativeElement.click();
tick();
expect(comp.post).toHaveBeenCalled();
expect(clientMock.post).toHaveBeenCalled();
expect(clientMock.post.calls.mostRecent().args[1]['nsfw']).toEqual([
'naughty',
'rude',
'not very nice',
]);
}));
});
......@@ -18,6 +18,7 @@ import { debounceTime } from 'rxjs/operators';
import { Router } from '@angular/router';
import { InMemoryStorageService } from '../../../services/in-memory-storage.service';
import { AutocompleteSuggestionsService } from '../../suggestions/services/autocomplete-suggestions.service';
import { NSFWSelectorComponent } from '../../../common/components/nsfw-selector/nsfw-selector.component';
@Component({
moduleId: module.id,
......@@ -48,6 +49,9 @@ export class PosterComponent {
@ViewChild('hashtagsSelector', { static: false })
hashtagsSelector: HashtagsSelectorComponent;
@ViewChild('nsfwSelector', { static: false })
nsfwSelector: NSFWSelectorComponent;
showActionBarLabels: boolean = false;
protected lastWidth: number;
......@@ -79,6 +83,13 @@ export class PosterComponent {
ngAfterViewInit() {
this.resizeSubject.next(Date.now());
try {
const nsfw = this.nsfwSelector.service.reasons.filter(r => r.selected);
this.setNSFWSelector(nsfw);
} catch (e) {
return;
}
}
ngOnDestroy() {
......@@ -297,4 +308,17 @@ export class PosterComponent {
posterDateSelectorError(msg) {
this.errorMessage = msg;
}
/**
* Set the current NSFW state.
*
* @param { string } nsfw - array of NSFW reasons.
*/
setNSFWSelector(
nsfw: Array<{ value: string; label: string; selected: string }> = null
): void {
if (nsfw.length > 0) {
this.onNSWFSelections(nsfw);
}
}
}
......@@ -3,46 +3,40 @@
<m-shadowboxLayout [scrollableHeader]="false" [isForm]="true">
<div class="m-shadowboxLayout__header hasTitle">
<h3 class="m-shadowboxHeader__title">{{ activeTab | titlecase }}</h3>
<ng-container [ngSwitch]="activeTab">
<ng-template ngSwitchCase="general">
<h4 class="m-shadowboxHeader__subtitle">
<h4 class="m-shadowboxHeader__subtitle">
<ng-container [ngSwitch]="activeTab">
<ng-template ngSwitchCase="general">
Customize your title and headline
</h4>
</ng-template>
<ng-template ngSwitchCase="theme">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="theme">
Set up your site's color theme
</h4>
</ng-template>
<ng-template ngSwitchCase="assets">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="assets">
Upload custom logo and background images
</h4>
</ng-template>
<ng-template ngSwitchCase="hashtags">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="hashtags">
Set up your category filter hashtags
</h4>
</ng-template>
<ng-template ngSwitchCase="footer">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="footer">
Set up your site's footer links
</h4>
</ng-template>
<ng-template ngSwitchCase="domain">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="domain">
Customize your site domain
</h4>
</ng-template>
<ng-template ngSwitchCase="payouts">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="payouts">
Select the currency type you wish you be paid out in. Please note
payouts only occur after your
<a routerLink="/analytics/dashboard/earnings">earnings</a> are
equivalent to $100 or greater.
</h4>
</ng-template>
</ng-container>
</ng-template>
</ng-container>
<a
class="m-proSettings__upgradeLink"
routerLink="/pro"
*ngIf="!isActive"
>(Upgrade to Pro)</a
>
</h4>
</div>
<div class="m-shadowboxLayout__bottom">
......@@ -110,6 +104,7 @@
for="publish"
class="m-proSettings__customInputContainer--checkbox"
i18n
(click)="onEnableProThemeClick($event)"
>Enable Pro theme
<input
type="checkbox"
......@@ -738,11 +733,18 @@
id="domain"
name="domain"
formControlName="domain"
required
/>
<m-formDescriptor>
If you wish to use a custom domain then please email
info@minds.com.
<ng-container
*ngIf="isActive; else previewModeDomainNote"
>
If you wish to use a custom domain then please email
info@minds.com.
</ng-container>
<ng-template #previewModeDomainNote
>You can control your domain once you purchase
Pro.</ng-template
>
</m-formDescriptor>
</div>
<div class="m-proSettings__row--validation">
......
......@@ -8,6 +8,14 @@ m-proSettings {
padding: 0;
}
.m-proSettings__upgradeLink {
display: inline;
font-weight: bold;
color: #4690df;
text-decoration: none;
margin-bottom: 8px;
}
m-tooltip {
margin-left: 10px;
.m-tooltip {
......
......@@ -45,6 +45,7 @@ export class ProSettingsComponent implements OnInit, OnDestroy {
'payouts',
];
isActive: boolean;
settings: any;
inProgress: boolean;
......@@ -131,6 +132,10 @@ export class ProSettingsComponent implements OnInit, OnDestroy {
this.detectChanges();
this.load();
});
if (!this.session.isLoggedIn()) {
this.router.navigate(['/login'], { replaceUrl: true });
return;
}
}
ngOnDestroy() {
......@@ -143,7 +148,22 @@ export class ProSettingsComponent implements OnInit, OnDestroy {
const { isActive, settings } = await this.service.get(this.user);
if (!isActive && !this.user) {
this.isActive = isActive;
if (!isActive) {
// Non-actives have no domain control
this.form
.get('domain')
.get('domain')
.setValidators([]);
this.form
.get('domain')
.get('domain')
.disable();
this.form.get('published').disable();
}
if (!settings) {
this.router.navigate(['/pro'], { replaceUrl: true });
return;
}
......@@ -389,6 +409,12 @@ export class ProSettingsComponent implements OnInit, OnDestroy {
}
}
onEnableProThemeClick(e: MouseEvent): void {
if (!this.isActive) {
this.router.navigate(['/pro']);
}
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
......
......@@ -2,7 +2,7 @@ const sidebarMenu = {
header: {
id: 'pro_settings',
label: 'Pro Settings',
permissions: ['pro'],
permissions: ['user'],
},
links: [
{
......@@ -40,7 +40,7 @@ const sidebarMenu = {
},
{
id: ':username',
label: 'View Pro Channel',
label: 'Preview Pro Channel',
path: 'pro/:username',
newWindow: true,
},
......
......@@ -50,6 +50,16 @@
>
Upgrade to Pro
</button>
<button
class="mf-button mf-button--alt"
*ngIf="session.getLoggedInUser()"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
i18n
>
Try Pro
</button>
</ng-container>
<ng-template #comingSoon>
<button class="mf-button mf-button--alt" i18n>
......@@ -77,6 +87,16 @@
>
Cancel Pro
</button>
<button
class="mf-button mf-button--alt"
*ngIf="session.getLoggedInUser()"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
i18n
>
Settings
</button>
</div>
</ng-template>
......
......@@ -50,7 +50,7 @@ export class ProSubscriptionComponent implements OnInit {
constructor(
protected service: ProService,
protected session: Session,
public session: Session,
protected overlayModal: OverlayModalService,
protected wirePaymentHandlers: WirePaymentHandlersService,
protected cd: ChangeDetectorRef,
......
......@@ -83,4 +83,9 @@ m-pro--subscription {
color: themed($m-red);
}
}
.mf-button {
margin-bottom: $minds-margin;
margin-right: $minds-margin;
}
}
......@@ -111,7 +111,6 @@
<a
class="m-page--sidebar--navigation--item"
*ngIf="session.getLoggedInUser().pro"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
......
......@@ -137,6 +137,17 @@
>
More info
</a>
<a
class="m-upgradesUpgradeOptionsPlan__moreInfo"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
i18n
*ngIf="session.getLoggedInUser()"
style="padding-left: 16px"
>
Try Pro
</a>
</p>
</div>
......
......@@ -14,6 +14,7 @@ export let attachmentServiceMock = new (function() {
this.meta = {};
this.preview = '';
this.blur = false;
this.nsfw = [];
this.load = jasmine.createSpy('load').and.stub();
......@@ -37,10 +38,12 @@ export let attachmentServiceMock = new (function() {
this.setMature = jasmine.createSpy('setMature').and.callFake(mature => {
this.mature = mature;
});
this.isMature = jasmine.createSpy('isMature').and.callFake(() => {
return !!this.mature;
});
this.setNSFW = jasmine.createSpy('setNSFW').and.callFake(nsfw => {
return nsfw;
});
this.toggleMature = jasmine.createSpy('toggleMature').and.callFake(() => {
this.mature = !!this.mature ? 0 : 1;
});
......