Skip to content
Commits on Source (10)
/**
* @author Ben Hayward
* @desc E2E testing for Minds Pro's pages.
*/
context('Pro Page', () => {
const topBar = '.m-proChannel__topbar';
let categories = [
{ label: 'Technology', tag: '#technology' },
{ label: 'Food', tag: '#food' },
{ label: 'News', tag: '#news' }
];
let footerLinks = [
{ label: 'Minds', link: 'https://www.minds.com/' },
{ label: 'Careers', link: 'https://www.minds.com/careers' },
];
const proButton = 'data-minds-sidebar-admin-pro-button';
function resetSettings() {
cy.visit(`/pro/settings`);
cy.route("POST", "**/api/v2/pro/settings").as("settings");
cy.get('#title').focus().clear().type('Title');
cy.get('#headline').focus().clear().type('This is a headline');
cy.contains('Hashtags')
.click();
// remove all hashtags
removeInputs();
for (let i = 0; i < categories.length; i++) {
let cat = categories[i];
addTag(cat.label, cat.tag, i);
}
cy.contains('Footer')
.click();
cy.get('#footer_text')
.clear()
.type('This is the footer text');
// remove all footer links
removeInputs();
for (let i = 0; i < footerLinks.length; i++) {
let link = footerLinks[i];
addFooterLink(link.label, link.link, i);
}
cy.contains('Save')
.click()
.wait('@settings').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body).to.deep.equal({ status: 'success' });
}
);
}
function removeInputs() {
cy.get('.m-draggableList__list .m-proSettings__field .m-proSettings__flexInputs').should('be.visible').within($el => {
for (let i = $el.length - 1; i >= 0; i--) { // flexInput. Start from the last one
let c = $el[i];
for (let j = 0; j < c.children.length; j++) { // inputs and the X button
let cc = c.children[j];
if (cc.nodeName === 'I') { // if it's the X button, click on it
cy.wrap(cc).click();
}
}
}
});
}
function addTag(label, tag, index) {
cy.contains('+ Add Tag')
.click();
cy.get(`#tag-label-${index}`)
.clear()
.type(label);
cy.get(`#tag-tag-${index}`)
.clear()
.type(tag);
}
function addFooterLink(label, link, index) {
cy.contains('Add Link')
.click();
cy.get(`#footer_link-title-${index}`)
.clear()
.type(label);
cy.get(`#footer_link-href-${index}`)
.clear()
.type(link);
}
before(() => {
cy.clearCookies();
cy.getCookie('minds_sess')
.then((sessionCookie) => {
if (sessionCookie === null) {
return cy.login(true);
}
});
// after logging in, we need to get to settings and set everything up
resetSettings();
// go to pro page
cy.visit(`/pro/${Cypress.env().username}`);
cy.get(topBar);
});
beforeEach(() => {
cy.server();
cy.preserveCookies();
});
after(() => {
cy.logout();
cy.clearCookies();
cy.login(false, 'minds','Password00!');
cy.visit(`/${Cypress.env().username}`);
cy.get(proButton).click();
});
it('should load the feed tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/activities/top**").as("activities");
cy.contains('Feed')
.click()
.wait('@activities').then((xhr) => {
expect(xhr.status).to.equal(200);
});
})
it('should load the videos tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/videos/top**").as("videos");
cy.contains('Videos')
.click()
.wait('@videos').then((xhr) => {
expect(xhr.status).to.equal(200);
});
})
it('should load the images tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/images/top**").as("images");
cy.contains('Images')
.click()
.wait('@images').then((xhr) => {
expect(xhr.status).to.equal(200);
});
// should have sub-categories
cy.get('m-pro--channel--categories > .m-proChannel__category').each(($el, $index) => {
let c = categories.slice(0);
c.unshift({ label: 'All', tag: '#all' });
expect($el.text()).to.contain(c[$index].label);
});
cy.get('m-pro--channel .m-overlay-modal').should('not.be.visible');
// click on tile
cy.get('.m-proChannelListContent__list li:first-child m-pro--channel-tile').click();
cy.wait(200);
// media modal should appear
cy.get('m-pro--channel .m-overlay-modal').should('be.visible');
// close media modal
cy.get('m-pro--channel .m-overlay-modal--close').click();
})
it('should load the articles tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/blogs/top**").as("blogs");
cy.contains('Articles')
.click()
.wait('@blogs').then((xhr) => {
expect(xhr.status).to.equal(200);
});
})
it('should load the groups tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/groups/top**").as("groups");
cy.contains('Groups')
.click()
.wait('@groups').then((xhr) => {
expect(xhr.status).to.equal(200);
});
})
it('should have a footer', () => {
// should have a footer text
cy.get('.m-proChannelFooter__text').contains('This is the footer text');
// should have footer links
cy.get('.m-proChannel__footer .m-proChannelFooter .m-proChannelFooter__link').should('be.visible').each(($el, $index) => {
expect($el.text()).to.contain(footerLinks[$index].label);
expect($el.attr('href')).to.contain(footerLinks[$index].link);
});
})
})
/**
* @author Ben Hayward
* @desc E2E testing for Minds Pro's settings.
*/
context('Pro Settings', () => {
const title = '#title';
const headline = '#headline';
const previewButton = '.m-proSettings__previewBtn';
const activityContainer = 'minds-activity';
const footerText = '#footer_text';
const theme = {
primaryColor: '#primary_color',
plainBackgroundColor: '#plain_background_color',
schemeLight: '#scheme_light',
schemeDark: '#scheme_dark',
aspectRatio: {
169: '#tile_ratio_16\:9', // 16:9
1610: '#tile_ratio_16\:10', // 16:10
43: '#tile_ratio_4\:3', // 4:3
11: '#tile_ratio_1\:1' , // 1:1
},
logoGuid: '#logo_guid',
}
const hashtags = {
labelInput0: '#tag-label-0',
hashtagInput0: '#tag-tag-0',
labelInput1: '#tag-label-1',
hashtagInput1: '#tag-tag-1',
label1: 'label1',
label2: 'label2',
label3: 'label3',
hashtag1: '#hashtag1',
hashtag2: '#hashtag2',
hashtag3: '#hashtag3',
}
const footer = {
hrefInput: `#footer_link-href-0`,
titleInput: `#footer_link-title-0`,
}
const strings = {
title: "Minds Pro E2E",
headline: "This headline is a test",
footer: "This is a footer",
footerTitle: "Minds",
footerHref: 'https://www.minds.com/',
}
before(() => {
cy.clearCookies();
cy.getCookie('minds_sess')
.then((sessionCookie) => {
if (sessionCookie === null) {
return cy.login(true);
}
});
});
after(() => {
cy.visit("/pro/settings")
.location('pathname')
.should('eq', '/pro/settings');
clearHashtags();
});
beforeEach(()=> {
cy.preserveCookies();
cy.server();
cy.route("POST", "**/api/v2/pro/settings").as("settings");
cy.visit("/pro/settings")
.location('pathname')
.should('eq', '/pro/settings');
});
it('should update the title and headline', () => {
//enter data
cy.get(title)
.focus()
.clear()
.type(strings.title);
cy.get(headline)
.focus()
.clear()
.type(strings.headline);
saveAndPreview();
//check tab title.
cy.title()
.should('eq', strings.title+' - '+strings.headline+" | Minds");
});
// Need to find a way around the color input in Cypress.
it('should allow the user to set a dark theme for posts', () => {
cy.contains('Theme')
.click();
cy.get(theme.schemeDark)
.click();
saveAndPreview();
cy.contains('Feed')
.click();
cy.get(activityContainer)
.should('have.css', 'background-color')
.and('eq', 'rgb(35, 35, 35)');
});
it('should allow the user to set a light theme for posts', () => {
cy.contains('Theme')
.click();
cy.get(theme.schemeLight)
.click();
saveAndPreview();
cy.contains('Feed')
.click();
cy.get(activityContainer)
.should('have.css', 'background-color')
.and('eq', 'rgb(255, 255, 255)');
});
it('should allow the user to set category hashtags', () => {
cy.contains('Hashtags')
.click();
cy.contains('+ Add Tag')
.click();
cy.get(hashtags.labelInput0)
.clear()
.type(hashtags.label1);
cy.get(hashtags.hashtagInput0)
.clear()
.type(hashtags.hashtag1);
cy.contains('+ Add Tag')
.click();
cy.get(hashtags.labelInput1)
.first()
.clear()
.type(hashtags.label2);
cy.get(hashtags.hashtagInput1)
.first()
.clear()
.type(hashtags.hashtag2);
saveAndPreview();
//check the labels are present and clickable.
cy.contains('label1');
cy.contains('label2');
});
it('should allow the user to set footer', () => {
cy.contains('Footer')
.click();
cy.get(footerText)
.clear()
.type(strings.footer);
cy.contains('Add Link')
.click();
cy.get(footer.hrefInput)
.clear()
.type(strings.footerHref);
cy.get(footer.titleInput)
.clear()
.type(strings.footerTitle);
saveAndPreview();
cy.contains(strings.footerTitle)
.should('have.attr', 'href')
.should('contain', strings.footerHref);
});
//save, await response, preview.
function saveAndPreview() {
//save and await response
cy.contains('Save')
.click()
.wait('@settings').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body).to.deep.equal({ status: 'success' });
});
//go to pro page
cy.get(previewButton)
.click();
}
function clearHashtags() {
cy.contains('Hashtags')
.click();
cy.contains('+ Add Tag')
.click();
cy.contains('clear')
.click({multiple: true});
saveAndPreview();
}
//
// it.only('should update the theme', () => {
// // nav to theme tab
// cy.contains('Theme')
// .click();
// cy.get(theme.plainBackgroundColor).then(elem => {
// elem.val('#00dd00');
// //save and await response
// cy.contains('Save')
// .click()
// .wait('@settings').then((xhr) => {
// expect(xhr.status).to.equal(200);
// expect(xhr.response.body).to.deep.equal({ status: 'success' });
// });
// //go to pro page
// cy.get(previewButton)
// .click();
// });
// })
})
......@@ -81,12 +81,13 @@ Cypress.Commands.add('login', (canary = false, username, password) => {
cy.server();
cy.route("POST", "/api/v1/authenticate").as("postLogin");
cy.get(loginForm.username).type(username);
cy.get(loginForm.password).type(password);
cy.get(loginForm.username).focus().type(username);
cy.get(loginForm.password).focus().type(password);
cy.get(loginForm.submit).click();
cy.wait('@postLogin').then((xhr) => {
cy.get(loginForm.submit)
.focus()
.click({force: true})
.wait('@postLogin').then((xhr) => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body.status).to.equal('success');
});
......@@ -97,8 +98,7 @@ Cypress.Commands.add('login', (canary = false, username, password) => {
* @returns void
*/
Cypress.Commands.add('logout', () => {
cy.get(nav.hamburgerMenu).click();
cy.get(nav.logoutButton).click();
cy.visit('/logout')
});
/**
......
......@@ -13353,6 +13353,14 @@
"double-bits": "^1.1.0"
}
},
"ngx-drag-drop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ngx-drag-drop/-/ngx-drag-drop-2.0.0.tgz",
"integrity": "sha512-t+4/eiC8zaXKqU1ruNfFEfGs1GpMNwpffD0baopvZFKjQHCb5rhNqFilJ54wO4T0OwGp4/RnsVhlcxe1mX6UJg==",
"requires": {
"tslib": "^1.9.0"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
......
<m-v2-topbar *mIfFeature="'top-feeds'; else legacyTopbar">
<ng-container search>
<m-search--bar [defaultSizes]="false"></m-search--bar>
</ng-container>
<ng-container icons>
<m-notifications--topbar-toggle
*ngIf="session.isLoggedIn()"
></m-notifications--topbar-toggle>
</ng-container>
</m-v2-topbar>
<ng-template #legacyTopbar>
<m-topbar class="m-noshadow">
<ng-container *ngIf="!isProDomain">
<m-v2-topbar *mIfFeature="'top-feeds'; else legacyTopbar">
<ng-container search>
<m-search--bar></m-search--bar>
<m-search--bar [defaultSizes]="false"></m-search--bar>
</ng-container>
<ng-container icons>
<m-notifications--topbar-toggle></m-notifications--topbar-toggle>
<m-wallet--topbar-toggle></m-wallet--topbar-toggle>
<m-notifications--topbar-toggle
*ngIf="session.isLoggedIn()"
></m-notifications--topbar-toggle>
</ng-container>
</m-topbar>
</ng-template>
<m-sidebar--markers
</m-v2-topbar>
<ng-template #legacyTopbar>
<m-topbar class="m-noshadow">
<ng-container search>
<m-search--bar></m-search--bar>
</ng-container>
<ng-container icons>
<m-notifications--topbar-toggle></m-notifications--topbar-toggle>
<m-wallet--topbar-toggle></m-wallet--topbar-toggle>
</ng-container>
</m-topbar>
</ng-template>
<m-sidebar--markers
[class.has-v2-navbar]="featuresService.has('top-feeds')"
></m-sidebar--markers>
</ng-container>
<m-body
[class.has-v2-navbar]="featuresService.has('top-feeds')"
></m-sidebar--markers>
<m-body [class.has-v2-navbar]="featuresService.has('top-feeds')">
[class.is-pro-domain]="isProDomain"
>
<m-announcement [id]="'blockchain:sale'" *ngIf="false">
<span
class="m-blockchain--wallet-address-notice--action"
......@@ -40,7 +45,7 @@
<router-outlet></router-outlet>
</m-body>
<m-messenger *ngIf="minds.LoggedIn"></m-messenger>
<m-messenger *ngIf="minds.LoggedIn && !isProDomain"></m-messenger>
<m-hovercard-popup></m-hovercard-popup>
......@@ -48,10 +53,10 @@
<m--blockchain--transaction-overlay></m--blockchain--transaction-overlay>
<m-modal--tos-updated *ngIf="session.isLoggedIn()"></m-modal--tos-updated>
<m-juryDutySession__summons
*ngIf="session.isLoggedIn()"
*ngIf="session.isLoggedIn() && !isProDomain"
></m-juryDutySession__summons>
<m-modal-signup-on-scroll></m-modal-signup-on-scroll>
<m-modal-signup-on-scroll *ngIf="!isProDomain"></m-modal-signup-on-scroll>
<m-channel--onboarding *ngIf="showOnboarding"></m-channel--onboarding>
......
import { ChangeDetectorRef, Component, NgZone } from '@angular/core';
import { Component, HostBinding } from '@angular/core';
import { NotificationService } from './modules/notifications/notification.service';
import { AnalyticsService } from './services/analytics';
......@@ -7,18 +7,20 @@ import { Session } from './services/session';
import { LoginReferrerService } from './services/login-referrer.service';
import { ScrollToTopService } from './services/scroll-to-top.service';
import { ContextService } from './services/context.service';
import { BlockchainService } from './modules/blockchain/blockchain.service';
import { Web3WalletService } from './modules/blockchain/web3-wallet.service';
import { Client } from './services/api/client';
import { WebtorrentService } from './modules/webtorrent/webtorrent.service';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { ChannelOnboardingService } from './modules/onboarding/channel/onboarding.service';
import { BlockListService } from './common/services/block-list.service';
import { FeaturesService } from './services/features.service';
import { ThemeService } from './common/services/theme.service';
import { BannedService } from './modules/report/banned/banned.service';
import { DiagnosticsService } from './services/diagnostics.service';
import { SiteService } from './common/services/site.service';
import { Subscription } from 'rxjs';
import { RouterHistoryService } from './common/services/router-history.service';
import { PRO_DOMAIN_ROUTES } from './modules/pro/pro.module';
@Component({
moduleId: module.id,
......@@ -35,6 +37,8 @@ export class Minds {
paramsSubscription;
protected router$: Subscription;
constructor(
public session: Session,
public route: ActivatedRoute,
......@@ -54,20 +58,30 @@ export class Minds {
public themeService: ThemeService,
private bannedService: BannedService,
private diagnostics: DiagnosticsService,
private routerHistoryService: RouterHistoryService
private routerHistoryService: RouterHistoryService,
private site: SiteService
) {
this.name = 'Minds';
if (this.site.isProDomain) {
this.router.resetConfig(PRO_DOMAIN_ROUTES);
}
}
async ngOnInit() {
this.diagnostics.setUser(this.minds.user);
this.diagnostics.listen(); // Listen for user changes
this.notificationService.getNotifications();
if (!this.site.isProDomain) {
this.notificationService.getNotifications();
}
this.session.isLoggedIn(async is => {
if (is) {
this.showOnboarding = await this.onboardingService.showModal();
if (is && !this.site.isProDomain) {
if (!this.site.isProDomain) {
this.showOnboarding = await this.onboardingService.showModal();
}
if (this.minds.user.language !== this.minds.language) {
console.log(
'[app]:: language change',
......@@ -113,4 +127,16 @@ export class Minds {
this.scrollToTop.unlisten();
this.paramsSubscription.unsubscribe();
}
@HostBinding('class') get cssColorSchemeOverride() {
if (!this.site.isProDomain || !this.site.pro.scheme) {
return '';
}
return `m-theme--wrapper m-theme--wrapper__${this.site.pro.scheme}`;
}
get isProDomain() {
return this.site.isProDomain;
}
}
......@@ -68,6 +68,8 @@ import { IssuesModule } from './modules/issues/issues.module';
import { CanaryModule } from './modules/canary/canary.module';
import { HttpClientModule } from '@angular/common/http';
import { AnalyticsModule } from './modules/analytics/analytics.module';
import { ProModule } from './modules/pro/pro.module';
import { ChannelContainerModule } from './modules/channel-container/channel-container.module';
import * as Sentry from '@sentry/browser';
......@@ -104,6 +106,7 @@ export class SentryErrorHandler implements ErrorHandler {
RouterModule.forRoot(MindsAppRoutes, { onSameUrlNavigation: 'reload' }),
CaptchaModule,
CommonModule,
ProModule, // NOTE: Pro Module should be declared _BEFORE_ anything else
AnalyticsModule,
WalletModule,
//CheckoutModule,
......@@ -143,9 +146,10 @@ export class SentryErrorHandler implements ErrorHandler {
MobileModule,
IssuesModule,
CanaryModule,
ChannelsModule,
//last due to :username route
ChannelsModule,
ChannelContainerModule,
],
providers: [
{ provide: ErrorHandler, useClass: SentryErrorHandler },
......
import { Cookie } from '../../services/cookie';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { environment } from '../../../environments/environment';
import { Location } from '@angular/common';
import { SiteService } from '../services/site.service';
/**
* API Class
*/
export class MindsHttpClient {
base: string = '/';
origin: string = '';
cookie: Cookie = new Cookie();
static _(http: HttpClient) {
return new MindsHttpClient(http);
static _(http: HttpClient, site: SiteService) {
return new MindsHttpClient(http, site);
}
constructor(public http: HttpClient) {}
constructor(public http: HttpClient, protected site: SiteService) {
if (this.site.isProDomain) {
this.base = window.Minds.site_url;
this.origin = document.location.host;
}
}
/**
* Return a GET request
......@@ -61,21 +69,35 @@ export class MindsHttpClient {
.join('&');
}
x;
/**
* Build the options
*/
private buildOptions(options: Object) {
const XSRF_TOKEN = this.cookie.get('XSRF-TOKEN') || '';
const headers = new HttpHeaders({
const headers = {
'X-XSRF-TOKEN': XSRF_TOKEN,
'X-VERSION': environment.version,
});
};
if (this.origin) {
const PRO_XSRF_JWT = this.cookie.get('PRO-XSRF-JWT') || '';
headers['X-MINDS-ORIGIN'] = this.origin;
headers['X-PRO-XSRF-JWT'] = PRO_XSRF_JWT;
}
return Object.assign(options, {
headers: headers,
const builtOptions = {
headers: new HttpHeaders(headers),
cache: true,
});
};
if (this.origin) {
builtOptions['withCredentials'] = true;
}
return Object.assign(options, builtOptions);
}
}
......
......@@ -106,9 +106,18 @@ import { PosterDateSelectorComponent } from './components/poster-date-selector/s
import { ChannelModeSelectorComponent } from './components/channel-mode-selector/channel-mode-selector.component';
import { ShareModalComponent } from '../modules/modals/share/share';
import { RouterHistoryService } from './services/router-history.service';
import { DraggableListComponent } from './components/draggable-list/list.component';
import { DndModule } from 'ngx-drag-drop';
import { SiteService } from './services/site.service';
@NgModule({
imports: [NgCommonModule, RouterModule, FormsModule, ReactiveFormsModule],
imports: [
NgCommonModule,
DndModule,
RouterModule,
FormsModule,
ReactiveFormsModule,
],
declarations: [
MINDS_PIPES,
......@@ -196,8 +205,8 @@ import { RouterHistoryService } from './services/router-history.service';
SwitchComponent,
FeaturedContentComponent,
PosterDateSelectorComponent,
DraggableListComponent,
],
exports: [
MINDS_PIPES,
......@@ -284,8 +293,10 @@ import { RouterHistoryService } from './services/router-history.service';
FeaturedContentComponent,
PosterDateSelectorComponent,
ChannelModeSelectorComponent,
DraggableListComponent,
],
providers: [
SiteService,
{
provide: AttachmentService,
useFactory: AttachmentService._,
......@@ -301,7 +312,7 @@ import { RouterHistoryService } from './services/router-history.service';
{
provide: MindsHttpClient,
useFactory: MindsHttpClient._,
deps: [HttpClient],
deps: [HttpClient, SiteService],
},
{
provide: NSFWSelectorCreatorService,
......
......@@ -43,6 +43,15 @@ minds-button {
}
}
a.m-link-btn {
display: inline-block;
padding: 8px !important;
line-height: 1.2;
height: auto;
text-decoration: none;
font-weight: inherit;
}
.m-btn--slim {
height: 32px;
}
......
@import 'themes';
m-draggable-list {
ul.m-draggableList__list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
transition: all ease 300ms;
&.dndDragover {
padding-top: 16px;
padding-bottom: 16px;
}
li.m-draggableList__listItem {
padding: 8px;
border: 1px solid #ddd;
display: flex;
align-items: center;
.handle {
@include m-theme() {
color: themed($grey-600);
}
}
}
}
}
import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
import { DndDropEvent, EffectAllowed } from 'ngx-drag-drop';
@Component({
selector: 'm-draggable-list',
template: `
<ul
dndDropzone
[dndHorizontal]="false"
[dndEffectAllowed]="dndEffectAllowed"
(dndDrop)="onDrop($event)"
class="m-draggableList__list"
>
<div
class="dndPlaceholder"
dndPlaceholderRef
style="min-height:100px;border:1px dashed green;background-color:rgba(0, 0, 0, 0.1)"
></div>
<li
*ngFor="let item of data; let i = index; trackBy: trackByFunction"
[dndDraggable]="item"
[dndEffectAllowed]="'move'"
class="m-draggableList__listItem"
>
<i class="handle material-icons" dndHandle>reorder</i>
<ng-container
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ item: item, i: i }"
></ng-container>
</li>
</ul>
`,
})
export class DraggableListComponent {
@Input() data: Array<any>;
@Input() dndEffectAllowed: EffectAllowed = 'copyMove';
@Input() id: string;
@ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;
trackByFunction(index, item) {
return this.id ? item[this.id] + index : index;
}
onDrop(event: DndDropEvent) {
if (
this.data &&
(event.dropEffect === 'copy' || event.dropEffect === 'move')
) {
let dragIndex = this.data.findIndex(
item => event.data[this.id] === item[this.id]
);
let dropIndex = event.index || this.data.length;
// remove element
this.data.splice(dragIndex, 1);
// add it back to new index
if (dragIndex < dropIndex) {
dropIndex--;
}
this.data.splice(dropIndex, 0, event.data);
}
}
}
<div class="m-notice--message">
<ng-container *ngIf="cookiesEnabled"
>Minds uses cookies to ensure you get the best experience. By continuing to
use Minds you agree to our privacy policy or you can
<a (click)="toggleCookies(false)">opt-out</a>.</ng-container
>{{ siteTitle }} uses cookies to ensure you get the best experience. By
continuing to use {{ siteTitle }} you agree to Minds privacy policy or you
can <a (click)="toggleCookies(false)">opt-out</a>.</ng-container
>
<ng-container *ngIf="!cookiesEnabled"
>You have disabled cookies from Minds which limits your experience. Consider
opting back in.</ng-container
>You have disabled cookies from {{ siteTitle }} which limits your
experience. Consider opting back in.</ng-container
>
</div>
<ng-container *ngIf="cookiesEnabled">
......
......@@ -8,6 +8,7 @@ import {
} from '@angular/core';
import { Client } from '../../../services/api/client';
import { Storage } from '../../../services/storage';
import { SiteService } from '../../services/site.service';
@Component({
selector: 'm-cookies-notice',
......@@ -21,7 +22,11 @@ export class DismissableNoticeComponent {
cookiesEnabled: boolean = true;
constructor(private client: Client, private storage: Storage) {
constructor(
private client: Client,
private storage: Storage,
private site: SiteService
) {
if (this.storage.get('cookies-notice-dismissed')) {
this.hidden = true;
}
......@@ -53,4 +58,8 @@ export class DismissableNoticeComponent {
this.client.delete(url);
}
}
get siteTitle() {
return this.site.title;
}
}
......@@ -122,7 +122,8 @@
}
}
body.m-overlay-modal--shown {
.m-overlay-modal--shown,
.m-overlay-modal--shown--no-scroll {
overflow: hidden;
}
......
import {
Component,
AfterViewInit,
ViewChild,
Component,
ComponentFactoryResolver,
ComponentRef,
Input,
ElementRef,
Injector,
ViewChild,
} from '@angular/core';
import { DynamicHostDirective } from '../../directives/dynamic-host.directive';
......@@ -19,7 +20,7 @@ import { OverlayModalService } from '../../../services/ux/overlay-modal';
[hidden]="hidden"
(click)="dismiss()"
></div>
<div class="m-overlay-modal {{ class }}" [hidden]="hidden">
<div class="m-overlay-modal {{ class }}" [hidden]="hidden" #modalElement>
<a class="m-overlay-modal--close" (click)="dismiss()"
><i class="material-icons">close</i></a
>
......@@ -30,6 +31,7 @@ import { OverlayModalService } from '../../../services/ux/overlay-modal';
export class OverlayModalComponent implements AfterViewInit {
hidden: boolean = true;
class: string = '';
root: HTMLElement;
@ViewChild(DynamicHostDirective, { static: true })
private host: DynamicHostDirective;
......@@ -37,16 +39,23 @@ export class OverlayModalComponent implements AfterViewInit {
private componentRef: ComponentRef<{}>;
private componentInstance;
@ViewChild('modalElement', { static: true })
protected modalElement: ElementRef;
constructor(
private service: OverlayModalService,
private _componentFactoryResolver: ComponentFactoryResolver
) {}
ngAfterViewInit() {
if (!this.root && document && document.body) {
this.root = document.body;
}
this.service.setContainer(this);
}
create(componentClass, opts?) {
create(componentClass, opts?, injector?: Injector) {
this.dismiss();
opts = {
......@@ -69,8 +78,17 @@ export class OverlayModalComponent implements AfterViewInit {
viewContainerRef.clear();
this.componentRef = viewContainerRef.createComponent(componentFactory);
this.componentRef = viewContainerRef.createComponent(
componentFactory,
void 0,
injector
);
this.componentInstance = this.componentRef.instance;
this.componentInstance.parent = this.modalElement.nativeElement;
}
setRoot(root: HTMLElement) {
this.root = root;
}
setData(data) {
......@@ -97,16 +115,18 @@ export class OverlayModalComponent implements AfterViewInit {
this.hidden = false;
if (document && document.body) {
document.body.classList.add('m-overlay-modal--shown');
if (this.root) {
this.root.classList.add('m-overlay-modal--shown');
document.body.classList.add('m-overlay-modal--shown--no-scroll');
}
}
dismiss() {
this.hidden = true;
if (document && document.body) {
document.body.classList.remove('m-overlay-modal--shown');
if (this.root) {
this.root.classList.remove('m-overlay-modal--shown');
document.body.classList.remove('m-overlay-modal--shown--no-scroll');
}
if (!this.componentInstance) {
......
......@@ -12,4 +12,9 @@ m-body {
&.has-v2-navbar {
margin-top: 52px;
}
&.is-pro-domain {
margin-top: 0;
padding-bottom: 0;
}
}
......@@ -8,6 +8,7 @@
@media screen and (max-width: $min-tablet) {
padding-left: 0;
flex-wrap: wrap;
}
&:not(.m-topbar--navigation--text-only) .m-topbar--navigation--item span {
......
import { Injectable } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { proRoutes } from '../../modules/pro/pro.routes';
@Injectable()
export class SiteService {
get pro() {
return window.Minds.pro;
}
get isProDomain(): boolean {
return Boolean(this.pro);
}
get title(): string {
return this.isProDomain ? this.pro.title || '' : 'Minds';
}
get oneLineHeadline(): string {
return this.isProDomain ? this.pro.one_line_headline || '' : '';
}
private router$: Subscription;
constructor(private router: Router) {
if (this.isProDomain) {
this.listen();
}
}
private listen() {
this.router$ = this.router.events.subscribe(
(navigationEvent: NavigationEnd) => {
try {
if (navigationEvent instanceof NavigationEnd) {
if (!navigationEvent.urlAfterRedirects) {
return;
}
let url = navigationEvent.url
.substring(1, navigationEvent.url.length)
.split('/')[0]
.split(';')[0]
.split('?')[0];
if (!this.searchRoutes(url, proRoutes)) {
window.open(window.Minds.site_url + url, '_blank');
}
}
} catch (e) {
console.error('Minds: router hook(SearchBar)', e);
}
}
);
}
private searchRoutes(url: string, routes: Array<string>): boolean {
for (let route of routes) {
if (route.includes(url)) {
return true;
}
}
return false;
}
}