...
 
Commits (21)
......@@ -229,7 +229,7 @@ review:start:
image: minds/helm-eks:latest
script:
- aws eks update-kubeconfig --name=sandbox
- git clone --branch=sandbox-wip https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/minds/helm-charts.git
- git clone --branch=master https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/minds/helm-charts.git
- "helm upgrade \
--install \
--reuse-values \
......
......@@ -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();
});
});
......@@ -33,6 +33,7 @@
[class.has-v2-navbar]="featuresService.has('top-feeds')"
[class.is-pro-domain]="isProDomain"
>
<m-emailConfirmation></m-emailConfirmation>
<m-announcement [id]="'blockchain:sale'" *ngIf="false">
<span
class="m-blockchain--wallet-address-notice--action"
......
import { NgModule } from '@angular/core';
import { CommonModule as NgCommonModule } from '@angular/common';
import { RouterModule, Router } from '@angular/router';
import { RouterModule, Router, Routes } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MINDS_PIPES } from './pipes/pipes';
......@@ -129,9 +129,17 @@ import { SsoService } from './services/sso.service';
import { V2TopbarService } from './layout/v2-topbar/v2-topbar.service';
import { DateDropdownsComponent } from './components/date-dropdowns/date-dropdowns.component';
import { SidebarMarkersService } from './layout/sidebar/markers.service';
import { EmailConfirmationComponent } from './components/email-confirmation/email-confirmation.component';
PlotlyModule.plotlyjs = PlotlyJS;
const routes: Routes = [
{
path: 'email-confirmation',
redirectTo: '/',
},
];
@NgModule({
imports: [
NgCommonModule,
......@@ -140,6 +148,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormsModule,
ReactiveFormsModule,
PlotlyModule,
RouterModule.forChild(routes),
],
declarations: [
MINDS_PIPES,
......@@ -244,6 +253,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormDescriptorComponent,
FormToastComponent,
ShadowboxSubmitButtonComponent,
EmailConfirmationComponent,
DateDropdownsComponent,
],
exports: [
......@@ -344,6 +354,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormDescriptorComponent,
FormToastComponent,
ShadowboxSubmitButtonComponent,
EmailConfirmationComponent,
DateDropdownsComponent,
],
providers: [
......
......@@ -67,4 +67,9 @@ m-announcement {
}
}
}
.m-announcement__clickable {
cursor: pointer;
font-weight: bold;
}
}
......@@ -14,7 +14,7 @@ import { Client } from '../../../services/api';
<ng-content></ng-content>
</div>
<div class="m-announcement--close" (click)="close()">
<div class="m-announcement--close" *ngIf="canClose" (click)="close()">
<i class="material-icons">close</i>
</div>
</div>
......@@ -24,6 +24,8 @@ export class AnnouncementComponent {
minds: Minds = window.Minds;
hidden: boolean = false;
@Input() id: string = 'default';
@Input() canClose: boolean = true;
@Input() remember: boolean = true;
constructor(private storage: Storage) {}
......@@ -32,7 +34,10 @@ export class AnnouncementComponent {
}
close() {
this.storage.set('hide-announcement:' + this.id, true);
if (this.remember) {
this.storage.set('hide-announcement:' + this.id, true);
}
this.hidden = true;
}
}
......@@ -32,25 +32,31 @@
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div
[ngSwitch]="rawData?.unit"
*ngFor="let value of hoverInfo.values"
class="m-chartV2__hoverInfo__row--primary"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.value | number: '1.0-0' | abbr }}
{{ rawData.label | lowercase }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.value | currency }} USD
</ng-template>
<ng-template ngSwitchCase="eth">
{{ hoverInfo.value | number: '1.3-3' }} ETH
</ng-template>
<ng-template ngSwitchCase="tokens">
{{ hoverInfo.value | number: '1.1-3' }} Tokens
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.value | number: '1.0-3' }} {{ rawData?.unit }}
</ng-template>
<span
class="m-chartV2__hoverInfoRow__hex"
[style.background-color]="value.color"
></span>
<ng-container [ngSwitch]="rawData?.unit">
<ng-template ngSwitchCase="number">
{{ value.value | number: '1.0-0' | abbr }}
{{ value.label | lowercase }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ value.value | currency }} USD
</ng-template>
<ng-template ngSwitchCase="eth">
{{ value.value | number: '1.3-3' }} ETH
</ng-template>
<ng-template ngSwitchCase="tokens">
{{ value.value | number: '1.1-3' }} Tokens
</ng-template>
<ng-template ngSwitchDefault>
{{ value.value | number: '1.0-3' }} {{ rawData?.unit }}
</ng-template>
</ng-container>
</div>
<div class="m-chartV2__hoverInfo__row" *ngIf="isComparison">
vs
......
......@@ -58,6 +58,14 @@ m-chartV2 {
}
}
.m-chartV2__hoverInfoRow__hex {
width: 6px;
height: 6px;
display: inline-block;
margin-right: 2px;
border-radius: 50%;
}
.m-chartV2__hoverInfo__closeBtn {
display: none;
font-size: 15px;
......
......@@ -76,11 +76,11 @@ export class ChartV2Component implements OnInit, OnDestroy {
? this.rawData.visualisation.segments.slice(0, 1)
: this.rawData.visualisation.segments;
if (this.segments.length === 2) {
this.isComparison = true;
// this.isComparison = true;
// Reverse the segments so comparison line is layered behind current line
this.segments.reverse();
// this.segments.reverse();
// Current line should be blue, not grey
this.swapSegmentColors();
// this.swapSegmentColors();
this.detectChanges();
}
this.themeSubscription = this.themeService.isDark$.subscribe(isDark => {
......@@ -172,12 +172,12 @@ export class ChartV2Component implements OnInit, OnDestroy {
y: this.unpack(this.segments[i].buckets, 'value'),
};
if (this.segments[i].comparison) {
segment.line.dash = 'dot';
}
this.data[i] = segment;
});
if (this.isComparison) {
this.data[0].line.dash = 'dot';
}
}
setLayout() {
......@@ -309,24 +309,37 @@ export class ChartV2Component implements OnInit, OnDestroy {
}
populateHoverInfo() {
const pt = this.isComparison ? 1 : 0;
// TODO: format value strings here and remove ngSwitch from template?
this.hoverInfo['date'] = this.segments[pt].buckets[this.hoverPoint].date;
this.hoverInfo['date'] = this.segments[0].buckets[this.hoverPoint].date;
this.hoverInfo['value'] =
this.rawData.unit !== 'usd'
? this.segments[pt].buckets[this.hoverPoint].value
: this.segments[pt].buckets[this.hoverPoint].value / 100;
if (this.isComparison && this.segments[1]) {
this.hoverInfo['comparisonValue'] =
this.rawData.unit !== 'usd'
? this.segments[0].buckets[this.hoverPoint].value
: this.segments[0].buckets[this.hoverPoint].value / 100;
this.hoverInfo['comparisonDate'] = this.segments[0].buckets[
this.hoverPoint
].date;
? this.segments[0].buckets[this.hoverPoint].value
: this.segments[0].buckets[this.hoverPoint].value / 100;
this.hoverInfo['values'] = [];
for (const pt in this.segments) {
const segment = this.segments[pt];
this.hoverInfo['values'][pt] = {
value:
this.rawData.unit !== 'usd'
? segment.buckets[this.hoverPoint].value
: segment.buckets[this.hoverPoint].value / 100,
label: segment.label || this.rawData.label,
color: this.getColor(chartPalette.segmentColorIds[pt]),
};
}
// if (this.isComparison && this.segments[1]) {
// this.hoverInfo['comparisonValue'] =
// this.rawData.unit !== 'usd'
// ? this.segments[0].buckets[this.hoverPoint].value
// : this.segments[0].buckets[this.hoverPoint].value / 100;
//
// this.hoverInfo['comparisonDate'] = this.segments[0].buckets[
// this.hoverPoint
// ].date;
// }
}
positionHoverInfo($event) {
......
<ng-container *ngIf="shouldShow">
<m-announcement
id="email-confirmation"
[canClose]="canClose"
[remember]="false"
>
Please confirm your email address.
<ng-container *ngIf="!sent"
>Didn't get it?
<span class="m-announcement__clickable" (click)="send()"
>Click here to send again.</span
></ng-container
>
</m-announcement>
</ng-container>
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnDestroy,
OnInit,
} from '@angular/core';
import { EmailConfirmationService } from './email-confirmation.service';
import { Session } from '../../../services/session';
import { Subscription } from 'rxjs';
/**
* Component that displays an announcement-like banner
* asking the user to confirm their email address and a link
* to re-send the confirmation email.
* @see AnnouncementComponent
*/
@Component({
providers: [EmailConfirmationService],
selector: 'm-emailConfirmation',
templateUrl: 'email-confirmation.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EmailConfirmationComponent implements OnInit, OnDestroy {
sent: boolean = false;
shouldShow: boolean = false;
canClose: boolean = false;
protected userEmitter$: Subscription;
protected canCloseTimer: number;
protected minds = window.Minds;
constructor(
protected service: EmailConfirmationService,
protected session: Session,
protected cd: ChangeDetectorRef
) {}
ngOnInit(): void {
this.setShouldShow(this.session.getLoggedInUser());
this.userEmitter$ = this.session.userEmitter.subscribe(user => {
this.sent = false;
this.setShouldShow(user);
this.detectChanges();
});
this.canCloseTimer = window.setTimeout(() => {
this.canClose = true;
this.detectChanges();
}, 3000);
}
ngOnDestroy(): void {
window.clearTimeout(this.canCloseTimer);
if (this.userEmitter$) {
this.userEmitter$.unsubscribe();
}
}
/**
* Re-calculates the visibility of the banner
* @param {Object} user
*/
setShouldShow(user): void {
this.shouldShow =
!this.minds.from_email_confirmation &&
user &&
user.email_confirmed === false;
}
/**
* Uses the service to re-send the confirmation email
*/
async send(): Promise<void> {
this.sent = true;
this.detectChanges();
try {
const sent = await this.service.send();
if (!sent) {
this.sent = false;
}
} catch (e) {}
this.detectChanges();
}
detectChanges(): void {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
import { Injectable } from '@angular/core';
import { Client } from '../../../services/api/client';
/**
* API implementation service for Email Confirmation component
* @see EmailConfirmationComponent
*/
@Injectable()
export class EmailConfirmationService {
constructor(protected client: Client) {}
/**
* Attempts to re-send the confirmation email to the current logged in user
*/
async send(): Promise<boolean> {
const response = (await this.client.post(
'api/v2/email/confirmation/resend',
{}
)) as any;
return Boolean(response && response.sent);
}
}
......@@ -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 content that is NSFW
</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 = {
is_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,
is_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,
is_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.is_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);
}
}
}
......@@ -51,6 +51,11 @@ m-proChannel__home {
@media screen and (max-width: $max-mobile) {
width: 100%;
}
> * {
min-width: 0;
min-height: 0;
}
}
.m-proChannelHome__featuredContent {
......
......@@ -88,6 +88,11 @@ m-pro--channel-list {
grid-template-columns: 100%;
}
> * {
min-width: 0;
min-height: 0;
}
&.m-proChannelListContent__normalList {
grid-template-columns: 100%;
......
......@@ -19,6 +19,10 @@ m-pro--channel-tile {
> img {
object-fit: cover;
}
.m-videoPlayer__placeholder {
height: 100%;
}
}
.m-proChannelTile__text {
......
......@@ -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;
}
}
......@@ -37,9 +37,14 @@
id="email"
name="email"
[(ngModel)]="email"
(keyup)="change()"
(keyup)="change(); changeEmail()"
/>
</li>
<li class="m-settings--emails-campaigns__note" *ngIf="emailChanged" i18n>
Note: If you change your email address, it will need to be confirmed
again.
</li>
</ul>
</div>
......
......@@ -26,5 +26,15 @@ m-settings--emails {
margin-left: 8px;
}
}
.m-settings--emails-campaigns__note {
font-size: 0.8em;
line-height: 1;
padding: 2px 8px 0;
@include m-theme() {
color: themed($m-grey-300);
}
}
}
}
......@@ -4,6 +4,7 @@ import { Subscription } from 'rxjs';
import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { ConfirmPasswordModalComponent } from '../../modals/confirm-password/modal.component';
import { Session } from '../../../services/session';
@Component({
selector: 'm-settings--emails',
......@@ -33,6 +34,7 @@ export class SettingsEmailsComponent implements OnInit {
error: string = '';
changed: boolean = false;
emailChanged: boolean = false;
saved: boolean = false;
inProgress: boolean = false;
loading: boolean = false;
......@@ -41,7 +43,8 @@ export class SettingsEmailsComponent implements OnInit {
constructor(
public client: Client,
public overlayModal: OverlayModalService
public overlayModal: OverlayModalService,
protected session: Session
) {}
ngOnInit() {
......@@ -77,6 +80,10 @@ export class SettingsEmailsComponent implements OnInit {
this.saved = false;
}
changeEmail() {
this.emailChanged = true;
}
canSubmit() {
return this.changed;
}
......@@ -89,7 +96,13 @@ export class SettingsEmailsComponent implements OnInit {
notifications: this.notifications,
})
.then((response: any) => {
if (this.emailChanged && window.Minds.user) {
window.Minds.user.email_confirmed = false;
this.session.inject(window.Minds.user);
}
this.changed = false;
this.emailChanged = false;
this.saved = true;
this.error = '';
......
......@@ -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;
});
......
......@@ -39,6 +39,7 @@ interface Minds {
};
};
contribution_values: { [key: string]: number };
from_email_confirmation?: boolean;
}
interface MindsNavigation {
......