...
 
Commits (20)
......@@ -202,6 +202,8 @@ prepare:production:
- docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} ${CI_REGISTRY}
- docker build -t $CI_REGISTRY_IMAGE/server:$CI_PIPELINE_ID -f containers/server/Dockerfile dist/.
- docker push $CI_REGISTRY_IMAGE/server:$CI_PIPELINE_ID
- docker tag $CI_REGISTRY_IMAGE/server:$CI_PIPELINE_ID $CI_REGISTRY_IMAGE/server:latest
- docker push $CI_REGISTRY_IMAGE/server:latest
only:
refs:
- master
......
import generateRandomId from "../../support/utilities";
context('Onboarding', () => {
const remindText = 'remind test text';
const username = generateRandomId();
const password = `${generateRandomId()}0oA!`;
const email = 'test@minds.com';
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';
before(() => {
cy.getCookie('minds_sess')
.then((sessionCookie) => {
if (sessionCookie === null) {
return cy.login(true);
cy.visit('/register');
cy.location('pathname').should('eq', '/register');
cy.server();
cy.route("POST", "**/api/v1/register").as("register");
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);
}
});
cy.visit(`/onboarding`);
);
cy.wait(500);
cy.location('pathname').should('eq', '/onboarding/notice');
});
// create two test groups
after(() => {
cy.deleteUser(username, password);
cy.clearCookies();
});
beforeEach(() => {
......@@ -21,7 +65,7 @@ context('Onboarding', () => {
it('should go through the process of onboarding', () => {
// notice should appear
cy.get('h1.m-onboarding__noticeTitle').contains('Welcome to the Minds Community');
cy.get('h2.m-onboarding__noticeTitle').contains(`@${Cypress.env().username}`);
cy.get('h2.m-onboarding__noticeTitle').contains(username);
// should redirect to /hashtags
cy.get('.m-onboarding__form button.mf-button').contains("Let's Get Setup").click();
......@@ -73,8 +117,8 @@ context('Onboarding', () => {
// should have a Location input
cy.get('.m-onboarding__controls > .m-onboarding__control label[data-minds=location]').contains('Location');
cy.get('.m-onboarding__controls > .m-onboarding__control input[data-minds=locationInput]').type('London');
cy.get('ul.m-onboarding__cities > li:first-child').click();
// cy.get('.m-onboarding__controls > .m-onboarding__control input[data-minds=locationInput]').type('London');
// cy.get('ul.m-onboarding__cities > li:first-child').click();
// should have Date of Birth inputs
......@@ -91,7 +135,11 @@ context('Onboarding', () => {
// should have a continue and a skip button
cy.get('button.mf-button--hollow').contains('Skip');
cy.get('button.mf-button--alt').contains('Continue').click();
cy.get('button.mf-button--alt').contains('Finish').click();
// TODO: disable the following line and uncomment the rest when we re-enable the screens
// should be in the newsfeed
cy.location('pathname').should('eq', '/newsfeed/subscriptions');
// should be in the Avatar step
......@@ -144,8 +192,8 @@ context('Onboarding', () => {
// cy.get('.m-groupList__list .m-groupList__item:first-child .m-join__subscribe i').contains('add');
// should have a continue and a skip button
cy.get('button.mf-button--hollow').contains('Skip');
cy.get('button.mf-button--alt').contains('Continue').click();
// cy.get('button.mf-button--hollow').contains('Skip');
// cy.get('button.mf-button--alt').contains('Continue').click();
// should be in the Channels step
......@@ -161,10 +209,10 @@ context('Onboarding', () => {
// cy.get('.m-channelList__list .m-channelList__item:first-child .m-join__subscribe i').contains('add');
// should have a continue and a skip button
cy.get('button.mf-button--hollow').contains('Skip');
cy.get('button.mf-button--alt').contains('Finish').click();
// cy.get('button.mf-button--hollow').contains('Skip');
// cy.get('button.mf-button--alt').contains('Finish').click();
// should be in the newsfeed
cy.location('pathname').should('eq', '/newsfeed/subscriptions');
// cy.location('pathname').should('eq', '/newsfeed/subscriptions');
});
});
......@@ -60,7 +60,7 @@ context('Registration', () => {
});
cy.wait(500);
cy.location('pathname').should('eq', '/newsfeed/subscriptions');
cy.location('pathname').should('eq', '/onboarding/notice');
});
it('should display an error if passwords do not match', () => {
......
......@@ -120,13 +120,13 @@ const cache = () => {
(isMobileOrTablet() ? '/mobile' : '/desktop');
const exists = myCache.has(key);
if (exists) {
console.log(`from cache: ${key}`);
const cachedBody = myCache.get(key);
res.send(cachedBody);
return;
} else {
res.sendResponse = res.send;
res.send = body => {
if (res.finished) return;
myCache.set(key, body);
res.sendResponse(body);
};
......
......@@ -6,6 +6,10 @@ import { Minds } from './app.component';
import * as PlotlyJS from 'plotly.js/dist/plotly-basic.min.js';
import { PlotlyModule } from 'angular-plotly.js';
import { CookieModule } from '@gorniv/ngx-universal';
import {
RedirectService,
BrowserRedirectService,
} from './common/services/redirect.service';
PlotlyModule.plotlyjs = PlotlyJS;
......@@ -15,6 +19,10 @@ PlotlyModule.plotlyjs = PlotlyJS;
providers: [
{ provide: 'ORIGIN_URL', useValue: location.origin },
{ provide: 'QUERY_STRING', useValue: location.search || '' },
{
provide: RedirectService,
useClass: BrowserRedirectService,
},
],
})
export class AppBrowserModule {}
......@@ -60,6 +60,4 @@
></m-modal-signup>
<m-channel--onboarding *ngIf="showOnboarding"></m-channel--onboarding>
<m-cookies-notice *ngIf="!session.isLoggedIn()"> </m-cookies-notice>
</ng-container>
......@@ -9,6 +9,10 @@ import { MindsModule } from './app.module';
import { Minds } from './app.component';
import { PlotlyModule } from 'angular-plotly.js';
import { CookieService, CookieBackendService } from '@gorniv/ngx-universal';
import {
ServerRedirectService,
RedirectService,
} from './common/services/redirect.service';
PlotlyModule.plotlyjs = {
plot: () => {
......@@ -38,6 +42,10 @@ export class ServerXhr implements XhrFactory {
provide: CookieService,
useClass: CookieBackendService,
},
{
provide: RedirectService,
useClass: ServerRedirectService,
},
],
bootstrap: [Minds],
})
......
......@@ -144,6 +144,7 @@ import { MediaProxyService } from './services/media-proxy.service';
import { HorizontalFeedService } from './services/horizontal-feed.service';
import { FormInputCheckboxComponent } from './components/forms/checkbox/checkbox.component';
import { AttachmentPasteDirective } from './directives/paste/attachment-paste.directive';
import { RedirectService } from './services/redirect.service';
const routes: Routes = [
{
......@@ -432,9 +433,14 @@ const routes: Routes = [
},
{
provide: ConfigsService,
useFactory: (client, injector) =>
new ConfigsService(client, injector.get('QUERY_STRING')),
deps: [Client, Injector],
useFactory: (client, injector, redirect, location) =>
new ConfigsService(
client,
injector.get('QUERY_STRING'),
redirect,
location
),
deps: [Client, Injector, RedirectService, Location],
},
{
provide: MetaService,
......
......@@ -2,27 +2,32 @@ m-date__dropdowns {
display: flex;
justify-content: space-between;
select {
display: inline-block;
background-color: #fff;
box-sizing: border-box;
margin: 0 10px 0 0;
padding: 8px 10px;
height: 36px;
.m-dateDropdowns__selectWrapper {
position: relative;
overflow: hidden;
min-width: 80px;
max-width: 90px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
font-size: 16px;
line-height: 21px;
border-radius: 2px;
border-radius: 3px;
margin: 0 10px 0 0;
@include m-theme() {
color: themed($m-grey-800);
background-color: themed($m-white);
border: 1px solid #e2e2e2;
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.1);
}
&::after {
content: '\25bc';
font-size: 10px;
padding: 10px;
position: absolute;
right: 0;
top: 0;
text-align: center;
pointer-events: none;
@include m-theme() {
color: themed($m-grey-200);
}
}
// month
......@@ -40,4 +45,28 @@ m-date__dropdowns {
min-width: 77px;
}
}
select {
display: inline-block;
background-color: #fff;
box-sizing: border-box;
padding: 8px 10px;
height: 36px;
width: 100%;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
font-size: 16px;
line-height: 21px;
border-radius: 2px;
appearance: none;
@include m-theme() {
color: themed($m-grey-800);
background-color: themed($m-white);
border: 1px solid #e2e2e2;
}
}
}
......@@ -3,27 +3,33 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core';
@Component({
selector: 'm-date__dropdowns',
template: `
<select
data-minds="monthDropdown"
[ngModel]="selectedMonth"
(ngModelChange)="selectMonth($event)"
>
<option *ngFor="let month of monthNames">{{ month }}</option>
</select>
<select
data-minds="dayDropdown"
[ngModel]="selectedDay"
(ngModelChange)="selectDay($event)"
>
<option *ngFor="let day of days">{{ day }}</option>
</select>
<select
data-minds="yearDropdown"
[ngModel]="selectedYear"
(ngModelChange)="selectYear($event)"
>
<option *ngFor="let year of years">{{ year }}</option>
</select>
<div class="m-dateDropdowns__selectWrapper">
<select
data-minds="monthDropdown"
[ngModel]="selectedMonth"
(ngModelChange)="selectMonth($event)"
>
<option *ngFor="let month of monthNames">{{ month }}</option>
</select>
</div>
<div class="m-dateDropdowns__selectWrapper">
<select
data-minds="dayDropdown"
[ngModel]="selectedDay"
(ngModelChange)="selectDay($event)"
>
<option *ngFor="let day of days">{{ day }}</option>
</select>
</div>
<div class="m-dateDropdowns__selectWrapper">
<select
data-minds="yearDropdown"
[ngModel]="selectedYear"
(ngModelChange)="selectYear($event)"
>
<option *ngFor="let year of years">{{ year }}</option>
</select>
</div>
`,
})
export class DateDropdownsComponent implements OnInit {
......@@ -56,30 +62,42 @@ export class DateDropdownsComponent implements OnInit {
ngOnInit() {
this.years = this.range(100, this.selectedYear, false);
this.selectedYear = this.years[0];
this.selectMonth('January');
this.selectMonth('January', false);
}
selectMonth(month: string) {
selectMonth(month: string, emit: boolean = true) {
this.selectedMonth = month;
this.populateDays(
this.getDaysInMonth(this.getMonthNumber(month), this.selectedYear)
);
this.selectedDateChange.emit(this.buildDate());
if (emit) {
this.emitChanges();
}
}
selectDay(day: string) {
selectDay(day: string, emit: boolean = true) {
this.selectedDay = day;
this.selectedDateChange.emit(this.buildDate());
if (emit) {
this.emitChanges();
}
}
selectYear(year) {
selectYear(year, emit: boolean = true) {
this.selectedYear = year;
this.populateDays(
this.getDaysInMonth(this.getMonthNumber(this.selectedMonth), year)
);
if (emit) {
this.emitChanges();
}
}
emitChanges() {
this.selectedDateChange.emit(this.buildDate());
}
......
......@@ -9,7 +9,7 @@
[ngClass]="{ 'm-inline-embed': hasInlineContentLoaded() }"
(click)="action($event)"
target="_blank"
rel="noopener noreferrer"
rel="noopener nofollow ugc"
*ngIf="src.thumbnail_src || inlineEmbed"
>
<div
......
......@@ -20,13 +20,13 @@ export class TagsPipe implements PipeTransform {
url: {
rule: /(\b(https?|ftp|file):\/\/[^\s\]]+)/gim,
replace: m => {
return `<a href="${m.match[1]}" target="_blank" rel="noopener noreferrer">${m.match[1]}</a>`;
return `<a href="${m.match[1]}" target="_blank" rel="noopener nofollow ugc">${m.match[1]}</a>`;
},
},
mail: {
rule: /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/gim,
replace: m => {
return `<a href="mailto:${m.match[0]}" target="_blank" rel="noopener noreferrer">${m.match[0]}</a>`;
return `<a href="mailto:${m.match[0]}" target="_blank" rel="noopener nofollow ugc">${m.match[0]}</a>`;
},
},
hash: {
......
import { Client } from '../api/client.service';
import { Injectable, Inject, Optional } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { Injectable, Inject, Optional, Injector } from '@angular/core';
import { RedirectService } from './redirect.service';
import { Location } from '@angular/common';
@Injectable()
export class ConfigsService {
......@@ -9,7 +9,9 @@ export class ConfigsService {
constructor(
private client: Client,
@Inject('QUERY_STRING') private queryString: string
@Inject('QUERY_STRING') private queryString: string,
private redirectService: RedirectService,
private location: Location
) {}
async loadFromRemote() {
......@@ -17,6 +19,7 @@ export class ConfigsService {
this.configs = await this.client.get(
`api/v1/minds/config${this.queryString}`
);
this.redirectToRootIfInvalidDomain();
} catch (err) {
console.error(err);
}
......@@ -29,4 +32,17 @@ export class ConfigsService {
set(key, value): void {
this.configs[key] = value;
}
/**
* Redirect to the root domain if we have an invalid domain response from configs
* @return void
*/
private redirectToRootIfInvalidDomain(): void {
if (this.get('redirect_to_root_on_init') === true) {
const redirectTo: string =
this.get('site_url') + this.location.path().substr(1);
this.redirectService.redirect(redirectTo);
throw `Invalid domain. Redirecting to ${redirectTo}`;
}
}
}
import { Inject, Injectable } from '@angular/core';
import { RESPONSE } from '@nguniversal/express-engine/tokens';
export class RedirectService {
public redirect(url: string): void {}
}
export class BrowserRedirectService extends RedirectService {
redirect(url: string): void {
window.location.href = url;
}
}
export class ServerRedirectService extends RedirectService {
constructor(@Inject(RESPONSE) private res) {
super();
}
redirect(url: string, permanent: boolean = false): void {
const code = permanent ? 301 : 302;
this.res.redirect(code, url);
this.res.end();
}
}
......@@ -70,6 +70,9 @@ export class ChannelComponent {
ngOnInit() {
this.updateMeta();
if (this.user) {
this.clientMetaService.recordView(this.user);
}
this.context.set('activity');
this.onScroll();
......@@ -171,6 +174,7 @@ export class ChannelComponent {
this.addRecent();
}
// this.load() is only called if this.user was not previously set
this.clientMetaService.recordView(this.user);
})
.catch(e => {
......
......@@ -10,7 +10,7 @@
<a
*ngIf="profile.key && profile.value"
[href]="getSocialProfileURL(profile.value)"
rel="noopener noreferrer"
rel="noopener nofollow ugc"
target="_blank"
><i [ngClass]="[ getSocialProfileIconClass(profile) ]"></i
></a>
......
......@@ -67,6 +67,7 @@ export class CommentPosterComponent {
async post(e) {
e.preventDefault();
this.attachment.resetPreviewRequests();
if (this.content.length > this.maxLength) {
return;
}
......
......@@ -89,7 +89,6 @@
i18n-placeholder="@@M__COMMON__USERNAME"
autocomplete="username"
(keydown.enter)="login(); $event.preventDefault();"
autofocus
/>
<div class="m-login__error" *ngIf="!!usernameError">
<ng-container
......
......@@ -39,7 +39,6 @@
readonly
onfocus="this.removeAttribute('readonly');"
[class.m-input--hide-placeholder]="showLabels"
autofocus
/>
<div
class="m-register__error"
......
......@@ -4,6 +4,8 @@ import {
ViewChild,
ChangeDetectorRef,
HostListener,
OnInit,
OnDestroy,
Inject,
PLATFORM_ID,
} from '@angular/core';
......@@ -20,7 +22,7 @@ import { GroupsService } from '../groups-service';
selector: 'm-group--sidebar-markers',
templateUrl: 'sidebar-markers.component.html',
})
export class GroupsSidebarMarkersComponent {
export class GroupsSidebarMarkersComponent implements OnInit, OnDestroy {
inProgress: boolean = false;
$updateMarker;
markers = [];
......@@ -58,6 +60,10 @@ export class GroupsSidebarMarkersComponent {
return;
}
if (update.show) {
// if the group already exists in the list, don't re-add it
if (this.groups.findIndex(g => g.guid == update.guid) !== -1) {
return;
}
this.groupsService.load(update.guid).then(group => {
this.groups.unshift(group);
});
......
......@@ -38,7 +38,7 @@ export class SidebarSelectorComponent implements OnInit {
showAll: boolean = true;
loading: boolean;
showExtendedList: boolean = false;
showTrending: boolean = false;
showTrending: boolean = true;
constructor(
protected topbarHashtagsService: TopbarHashtagsService,
......
......@@ -394,6 +394,7 @@ m-onboarding {
line-height: 21px;
padding-left: 8px;
margin-bottom: 8px;
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.1);
@include m-theme() {
color: themed($m-grey-800);
......@@ -416,6 +417,7 @@ m-onboarding {
flex-grow: 1;
display: flex;
margin-bottom: 8px;
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.1);
.m-phone-input--wrapper {
justify-content: flex-start;
......@@ -432,6 +434,7 @@ m-onboarding {
font-size: 16px;
line-height: 21px;
box-shadow: none;
@include m-theme() {
color: themed($m-grey-800);
......
......@@ -25,14 +25,14 @@ export class OnboardingComponent implements OnDestroy {
name: 'Avatar',
selected: false,
},
{
name: 'Groups',
selected: false,
},
{
name: 'Channels',
selected: false,
},
// {
// name: 'Groups',
// selected: false,
// },
// {
// name: 'Channels',
// selected: false,
// },
];
showTitle: boolean = false;
shown: boolean = false;
......
......@@ -40,28 +40,6 @@ export class AvatarStepComponent {
if (e) {
this.cropping = true;
}
// const element: any = e.target ? e.target : e.srcElement;
// this.file = element ? element.files[0] : null;
//
// /**
// * Set a live preview
// */
// const reader = new FileReader();
// reader.onloadend = () => {
// this.src =
// typeof reader.result === 'string'
// ? reader.result
// : reader.result.toString();
// if (this.object.type === 'user' && this.isOwnerAvatar()) {
// this.userAvatarService.src$.next(this.src);
// }
// };
// reader.readAsDataURL(this.file);
//
// element.value = '';
//
// console.log(this.waitForDoneSignal);
// if (this.waitForDoneSignal !== true) this.done();
}
async save() {
......@@ -111,12 +89,12 @@ export class AvatarStepComponent {
}
skip() {
this.router.navigate(['/onboarding', 'groups']);
this.router.navigate(['/newsfeed']);
}
async continue() {
await this.save();
this.router.navigate(['/onboarding', 'groups']);
this.router.navigate(['/newsfeed']);
}
private dataURItoBlob(dataURI) {
......
......@@ -9,6 +9,6 @@ export class ChannelsStepComponent {
constructor(private router: Router) {}
finish() {
this.router.navigate(['/newsfeed']);
this.router.navigate(['/newsfeed/global/top']);
}
}
......@@ -4,6 +4,8 @@ m-onboarding__channelList {
.m-onboarding__channelList {
display: flex;
position: relative;
h3 {
margin: 0;
font-size: 13px;
......@@ -33,6 +35,8 @@ m-onboarding__channelList {
padding: 0;
list-style: none;
width: 100%;
max-height: 245px;
overflow-y: auto;
}
.m-channelList__item {
......@@ -43,6 +47,27 @@ m-onboarding__channelList {
border-bottom: 0;
}
&:nth-child(4) {
& ~ :last-child {
position: relative;
z-index: 1;
}
& ~ :nth-last-child(2):after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0) 0%,
#fff 100%
);
height: 65px;
}
}
a {
display: flex;
text-decoration: none;
......
......@@ -61,7 +61,7 @@ export class ChannelListComponent implements OnInit {
query,
nsfw,
})
.setLimit(3)
.setLimit(12)
.setExportUserCounts(true)
.fetch();
} catch (e) {
......
......@@ -4,6 +4,8 @@ m-onboarding__groupList {
.m-onboarding__groupList {
display: flex;
position: relative;
h3 {
margin: 0;
font-size: 13px;
......@@ -33,6 +35,8 @@ m-onboarding__groupList {
padding: 0;
list-style: none;
width: 100%;
max-height: 245px;
overflow-y: auto;
}
.m-groupList__item {
......@@ -43,6 +47,27 @@ m-onboarding__groupList {
border-bottom: 0;
}
&:nth-child(4) {
& ~ :last-child {
position: relative;
z-index: 1;
}
& ~ :nth-last-child(2):after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0) 0%,
#fff 100%
);
height: 65px;
}
}
a {
display: flex;
flex-grow: 1;
......
......@@ -61,7 +61,7 @@ export class GroupListComponent implements OnInit {
query,
nsfw,
})
.setLimit(3)
.setLimit(12)
.setCastToActivities(true)
.fetch();
} catch (e) {
......
import { Component, OnInit } from '@angular/core';
import { TopbarHashtagsService } from '../../../hashtags/service/topbar.service';
import { Router } from '@angular/router';
import { Storage } from '../../../../services/storage';
type Hashtag = {
value: string;
......@@ -16,9 +17,14 @@ export class HashtagsStepComponent implements OnInit {
error: string;
inProgress: boolean;
constructor(private service: TopbarHashtagsService, private router: Router) {}
constructor(
private service: TopbarHashtagsService,
private storage: Storage,
private router: Router
) {}
ngOnInit() {
this.storage.set('preferred_hashtag_state', '1'); // turn on preferred hashtags for discovery
this.load();
}
......
......@@ -70,7 +70,7 @@
Skip
</button>
<button class="mf-button mf-button--alt" (click)="continue()" i18n>
Continue
Finish
</button>
</div>
</div>
......@@ -4,7 +4,6 @@ import { MindsUser } from '../../../../interfaces/entities';
import { Client, Upload } from '../../../../services/api';
import { Router } from '@angular/router';
import { PhoneVerificationComponent } from './phone-input/input.component';
import { MindsAvatar } from '../../../../common/components/avatar/avatar';
import { ConfigsService } from '../../../../common/services/configs.service';
@Component({
......@@ -26,15 +25,13 @@ export class InfoStepComponent {
locationError: string;
date: string;
dateOfBirthError: string;
dateOfBirthChanged: boolean = false;
cities: Array<any> = [];
@ViewChild('phoneVerification', { static: false })
phoneVerification: PhoneVerificationComponent;
@ViewChild('avatar', { static: false })
avatar: MindsAvatar;
constructor(
private session: Session,
private client: Client,
......@@ -47,27 +44,6 @@ export class InfoStepComponent {
this.onResize();
}
addAvatar() {
this.avatar.editing = true;
setTimeout(() => {
this.avatar.openFileDialog();
});
}
async uploadAvatar(file) {
this.avatar.editing = false;
try {
const response: any = await this.upload.post(
'api/v1/channel/avatar',
[file],
{ filekey: 'file' }
);
this.updateUser('icontime', Date.now());
} catch (e) {
console.error(e);
}
}
locationChange(location: string) {
this.location = location;
this.coordinates = null;
......@@ -114,6 +90,9 @@ export class InfoStepComponent {
}
async updateDateOfBirth() {
if (!this.dateOfBirthChanged) {
return true;
}
this.dateOfBirthError = null;
try {
......@@ -140,6 +119,7 @@ export class InfoStepComponent {
selectedDateChange(date: string) {
this.date = date;
this.dateOfBirthChanged = true;
}
cancel() {
......
......@@ -28,7 +28,7 @@ export class NoticeStepComponent implements OnInit {
}
skip() {
this.router.navigate(['/newsfeed']);
this.router.navigate(['/newsfeed/global/top']);
}
isMobile() {
......
......@@ -173,4 +173,38 @@ describe('Service: Attachment Service', () => {
tick(1000);
expect(clientMock.get).toHaveBeenCalledTimes(1);
}));
it('should populate the request array', fakeAsync(() => {
spyOn(service, 'addPreviewRequest');
service.preview('https://github.com/releases');
tick(1000);
expect(service.addPreviewRequest).toHaveBeenCalledTimes(1);
}));
it('should check the request array on response', fakeAsync(() => {
spyOn(service, 'getPreviewRequests');
service.preview('https://github.com/releases');
tick(1000);
expect(service.getPreviewRequests).toHaveBeenCalledTimes(1);
}));
it('should reset the request array when called', fakeAsync(() => {
service.addPreviewRequest('https://github.com/releases');
expect(service.getPreviewRequests().length).toBe(1);
service.resetPreviewRequests();
tick(1000);
expect(service.getPreviewRequests().length).toBe(0);
}));
it('should discard changes if request array has been cleared', fakeAsync(() => {
service.preview('https://github.com/releases');
tick(1000);
expect(this.meta).toBeFalsy();
}));
});
......@@ -33,6 +33,7 @@ export class AttachmentService {
private pendingDelete: boolean = false;
private xhr: XMLHttpRequest = null;
private previewRequests: string[] = [];
constructor(
public session: Session,
......@@ -356,7 +357,37 @@ export class AttachmentService {
this.meta.description = '';
}
preview(content: string, detectChangesFn?: Function) {
/**
* Resets preview requests to null.
*/
resetPreviewRequests(): AttachmentService {
this.previewRequests = [];
return this;
}
/**
* Returns preview requests.
*/
getPreviewRequests(): string[] {
return this.previewRequests;
}
/**
* Adds a new preview request.
* @param { string } url -
*/
addPreviewRequest(url: string): AttachmentService {
this.previewRequests.push(url);
return this;
}
/**
* Gets attachment preview from content.
* @param { string } content - Content to be parsed for preview URL.
* @param { Function } detectChangesFn - Function to be ran on change emission.
* @returns void.
*/
preview(content: string, detectChangesFn?: Function): void {
let match = content.match(/(\b(https?|ftp|file):\/\/[^\s\]\)]+)/gi),
url;
......@@ -389,6 +420,7 @@ export class AttachmentService {
}
this.attachment.richUrl = url;
this.addPreviewRequest(url);
if (detectChangesFn) detectChangesFn();
......@@ -401,7 +433,7 @@ export class AttachmentService {
this.clientService
.get('api/v1/newsfeed/preview', { url })
.then((data: any) => {
if (!data) {
if (!data || this.getPreviewRequests().length < 1) {
this.resetRich();
if (detectChangesFn) detectChangesFn();
return;
......
......@@ -50,6 +50,7 @@ import { ConfigsService } from '../common/services/configs.service';
import { TransferHttpInterceptorService } from './transfer-http-interceptor.service';
import { CookieHttpInterceptorService } from './api/cookie-http-interceptor.service';
import { CookieService } from '../common/services/cookie.service';
import { RedirectService } from '../common/services/redirect.service';
export const MINDS_PROVIDERS: any[] = [
SiteService,
......@@ -186,9 +187,14 @@ export const MINDS_PROVIDERS: any[] = [
},
{
provide: ConfigsService,
useFactory: (client, injector) =>
new ConfigsService(client, injector.get('QUERY_STRING')),
deps: [Client, Injector],
useFactory: (client, injector, redirect, location) =>
new ConfigsService(
client,
injector.get('QUERY_STRING'),
redirect,
location
),
deps: [Client, Injector, RedirectService, Location],
},
{
provide: FeaturesService,
......
......@@ -24,6 +24,13 @@
sizes="16x16"
href="/en/assets/logos/bulb-16x16.png"
/>
<link
rel="preload"
href="https://cdn-assets.minds.com/front/fonts/material-icons.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<title>Minds</title>
</head>
......
......@@ -19,6 +19,7 @@
"baseUrl": "./",
"paths": {
"fs": [ "./shims/noop" ],
"bn.js": [ "../node_modules/bn.js/lib/bn.js" ]
}
}
}
\ No newline at end of file
}