Commits (14)
......@@ -294,7 +294,7 @@ review:stop:
- docker:dind
script:
## Sync assets with CDN
- aws s3 sync dist $S3_REPOSITORY_URL
- aws s3 sync dist $S3_REPOSITORY_URL --cache-control max-age=31536000
- $(aws ecr get-login --no-include-email --region us-east-1)
## Update docker server container
- docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} ${CI_REGISTRY}
......
......@@ -442,24 +442,7 @@ const routes: Routes = [
),
deps: [Client, Injector, RedirectService, Location],
},
{
provide: MetaService,
useFactory: (
titleService,
metaService,
siteService,
location,
configsService
) =>
new MetaService(
titleService,
metaService,
siteService,
location,
configsService
),
deps: [Title, Meta, SiteService, Location, ConfigsService],
},
MetaService,
MediaProxyService,
V2TopbarService,
{
......
......@@ -39,6 +39,7 @@ describe('ChannelModeSelector', () => {
subscribers_count: 182,
impressions: 18200,
mode: ChannelMode.PUBLIC,
nsfw: [],
};
clientMock.response['api/v1/channel/info'] = { status: 'success' };
......
......@@ -54,8 +54,8 @@
</li>
<li>
<a href="https://irl.minds.com/" target="_blank" i18n>
Minds IRL
<a href="https://change.minds.com/" target="_blank" i18n>
Events
</a>
</li>
</ul>
......
......@@ -56,7 +56,6 @@
}
.m-v2-topbar__Container__LoginWrapper > a {
margin-right: 40px;
@include m-theme() {
background: transparent;
border: 1px solid themed($m-black-always);
......@@ -330,9 +329,7 @@
> a.m-v2-topbarLoginWrapper__login {
padding: 0;
border: none !important;
@media screen and(max-width: $max-mobile) {
margin-right: 10px;
}
margin-right: $minds-margin * 2;
}
> a.m-v2-topbarLoginWrapper__joinMindsNow {
......
import { Injectable, Optional } from '@angular/core';
import { Injectable, Optional, Inject } from '@angular/core';
import { Title, Meta } from '@angular/platform-browser';
import { SiteService } from './site.service';
import { Location } from '@angular/common';
import { ConfigsService } from './configs.service';
import { DOCUMENT } from '@angular/common';
const DEFAULT_META_TITLE = 'Minds';
const DEFAULT_META_DESCRIPTION = '...';
......@@ -19,7 +20,8 @@ export class MetaService {
private metaService: Meta,
private site: SiteService,
private location: Location,
private configs: ConfigsService
private configs: ConfigsService,
@Inject(DOCUMENT) private dom
) {
this.reset();
}
......@@ -30,6 +32,12 @@ export class MetaService {
? this.site.title + ' - ' + this.site.oneLineHeadline
: DEFAULT_META_TITLE;
value = this.stripHtml(value);
if (value.length > 60) {
value = value.substr(0, 57) + '...';
}
if (value && join) {
title = [value, defaultTitle]
.filter(fragment => Boolean(fragment))
......@@ -39,12 +47,17 @@ export class MetaService {
} else {
title = defaultTitle;
}
this.title = title;
this.applyTitle();
return this;
}
setDescription(value: string): MetaService {
value = this.stripHtml(value);
if (value.length > 160) {
value = value.substr(0, 157) + '...';
}
this.metaService.updateTag({ name: 'description', content: value });
return this;
}
......@@ -55,6 +68,33 @@ export class MetaService {
return this;
}
setCanonicalUrl(value: string): MetaService {
// Find and clear or canonical links
const links: HTMLLinkElement[] = this.dom.head.querySelectorAll(
'[rel="canonical"]'
);
if (links.length) {
for (const link of links) {
this.dom.head.removeChild(link);
}
}
if (value) {
// TODO: fix duplicated code with ogUrl here...
if (value && value.indexOf('/') === 0) {
// Relative path
value = this.site.baseUrl + value.substr(1);
}
let link: HTMLLinkElement;
link = this.dom.createElement('link');
link.setAttribute('rel', 'canonical');
link.setAttribute('href', value);
this.dom.head.appendChild(link);
}
return this;
}
setOgUrl(value: string): MetaService {
if (value && value.indexOf('/') === 0) {
// Relative path
......@@ -101,11 +141,24 @@ export class MetaService {
return this;
}
setLanguage(language: string): MetaService {
return this;
}
setRobots(value: string): MetaService {
this.metaService.updateTag({ name: 'robots', content: value });
return this;
}
setNsfw(value: boolean): MetaService {
if (value) {
this.metaService.updateTag({ name: 'rating', content: 'adult' });
} else {
this.metaService.removeTag("name='rating'");
}
return this;
}
reset(
data: {
title?: string;
......@@ -120,7 +173,9 @@ export class MetaService {
.setOgType('website')
.setOgUrl(data.ogUrl || this.location.path())
.setOgImage(data.ogImage || null, { width: 0, height: 0 })
.setRobots(data.robots || 'all');
.setCanonicalUrl('') // Only user canonical when required
.setRobots(data.robots || 'all')
.setNsfw(false);
}
private applyTitle(): void {
......@@ -134,4 +189,16 @@ export class MetaService {
content: this.title,
});
}
/**
* Removes any html found and returns on text
* @param value
* @return string
*/
private stripHtml(value: string): string {
if (!value) return '';
const fakeEl = this.dom.createElement('span');
fakeEl.innerHTML = value;
return fakeEl.textContent || fakeEl.innerText;
}
}
......@@ -115,6 +115,7 @@ export interface MindsUser {
has_custom_background?: boolean;
};
mode: ChannelMode;
nsfw: Array<number>;
}
export interface MindsGroup {
......
......@@ -127,10 +127,16 @@ export class BlogViewInfinite {
.setTitle(blog.custom_meta['title'] || blog.title)
.setDescription(description)
//.setAuthor(this.blog.custom_meta['author'] || `@${this.blog.ownerObj.username}`)
.setOgType('article')
.setCanonicalUrl(blog.perma_url)
.setOgUrl(blog.perma_url)
.setOgImage(blog.thumbnail_src)
.setRobots(
blog['thumbs:up:count'] >= MIN_METRIC_FOR_ROBOTS ? 'all' : 'noindex'
);
if (blog.nsfw.length) {
this.metaService.setNsfw(true);
}
}
}
......@@ -96,7 +96,8 @@ describe('ChannelComponent', () => {
{ provide: Client, useValue: clientMock },
{ provide: Upload, useValue: uploadMock },
{ provide: Session, useValue: sessionMock },
{ provide: MetaService, useValue: MockService(MetaService) },
MetaService,
SiteService,
{ provide: ScrollService, useValue: scrollServiceMock },
{ provide: RecentService, useValue: recentServiceMock },
{ provide: ContextService, useValue: contextServiceMock },
......@@ -141,6 +142,7 @@ describe('ChannelComponent', () => {
large: 'thumbs',
master: 'thumbs',
},
nsfw: [],
};
comp.editing = false;
fixture.detectChanges();
......
......@@ -127,21 +127,26 @@ export class ChannelComponent {
private updateMeta(): void {
if (this.user) {
this.metaService.setTitle(`${this.user.name} (@${this.user.username})`);
this.metaService.setDescription(
this.user.briefdescription || `Subscribe to @${this.user.username}`
);
this.metaService.setOgUrl(`/${this.user.username.toLowerCase()}`);
this.metaService.setOgImage(this.user.avatar_url.master, {
width: 2000,
height: 1000,
});
this.metaService.setRobots(
this.user.is_mature ||
const url = `/${this.user.username.toLowerCase()}`;
this.metaService
.setTitle(`${this.user.name} (@${this.user.username})`)
.setDescription(
this.user.briefdescription || `Subscribe to @${this.user.username}`
)
.setOgUrl(url)
.setCanonicalUrl(url)
.setOgImage(this.user.avatar_url.master, {
width: 2000,
height: 1000,
})
.setRobots(
this.user['subscribers_count'] < MIN_METRIC_FOR_ROBOTS
? 'noindex'
: 'all'
);
? 'noindex'
: 'all'
);
if (this.user.is_mature || this.user.nsfw.length) {
this.metaService.setNsfw(true);
}
} else if (this.username) {
this.metaService.setTitle(this.username);
} else {
......
......@@ -91,6 +91,7 @@ describe('ChannelFeed', () => {
impressions: 18200,
pinned_posts: ['a', 'b', 'c'],
mode: ChannelMode.PUBLIC,
nsfw: [],
};
comp.feed = [
{ guid: 'aaaa' },
......
......@@ -191,6 +191,7 @@ describe('ChannelSidebar', () => {
subscribers_count: 182,
impressions: 18200,
mode: ChannelMode.PUBLIC,
nsfw: [],
};
comp.editing = false;
uploadMock.response[`api/v1/channel/avatar`] = {
......
......@@ -48,6 +48,8 @@ export class GroupsSidebarMarkersComponent implements OnInit, OnDestroy {
await this.load(true);
this.listenForMarkers();
this.listenForMembershipUpdates();
} else {
this.inProgress = true; // Server side should start in loading spinner state
}
}
......
......@@ -5,8 +5,6 @@
[showBottombar]="false"
[forceBackground]="false"
[class.m-homepage__formExperiment]="!!registerForm"
pageTitle="Minds Social Network"
i18n-pageTitle
>
<div class="m-marketing__main m-marketing__section--style-2">
<div class="m-grid m-marketing__wrapper">
......@@ -18,13 +16,11 @@
</h1>
<h2 ngPreserveWhitespaces i18n>
Take back control of your social media
{{ headline }}
</h2>
<p class="m-marketing__description" i18n>
A place to have open conversations and bring people together. Free
your mind and get paid for creating content, driving traffic and
referring friends.
{{ description }}
</p>
<button
......
......@@ -8,6 +8,7 @@ import { RegisterForm } from '../forms/register/register';
import { FeaturesService } from '../../services/features.service';
import { ConfigsService } from '../../common/services/configs.service';
import { OnboardingV2Service } from '../onboarding-v2/service/onboarding.service';
import { MetaService } from '../../common/services/meta.service';
@Component({
selector: 'm-homepage__v2',
......@@ -18,16 +19,19 @@ export class HomepageV2Component {
readonly cdnAssetsUrl: string;
readonly siteUrl: string;
readonly headline = 'Take back control of your social media';
readonly description =
'A place to have open conversations and bring people together. Free your mind and get paid for creating content, driving traffic and referring friends.';
constructor(
public client: Client,
public router: Router,
public navigation: NavigationService,
public session: Session,
private loginReferrer: LoginReferrerService,
private featuresService: FeaturesService,
configs: ConfigsService,
private onboardingService: OnboardingV2Service
private onboardingService: OnboardingV2Service,
private metaService: MetaService
) {
this.cdnAssetsUrl = configs.get('cdn_assets_url');
this.siteUrl = configs.get('site_url');
......@@ -38,6 +42,11 @@ export class HomepageV2Component {
this.router.navigate(['/newsfeed']);
return;
}
this.metaService
.setTitle(`Minds - ${this.headline}`, false)
.setDescription(this.description)
.setCanonicalUrl('/')
.setOgUrl('/');
}
registered() {
......
......@@ -21,6 +21,7 @@
>{{activity.ownerObj.name}}</strong
>
<m-channel--badges
data-nosnippet
class="m-channel--badges-activity"
[user]="activity.ownerObj"
badges="[ 'admin', 'verified' ]"
......
......@@ -272,8 +272,6 @@
class="mdl-cell mdl-cell--12-col m-media--description"
*ngIf="entity.description"
[innerHtml]="entity.description | safe"
[hidden]="!descriptionContainer.innerText.trim('')"
#descriptionContainer
></div>
</div>
......
......@@ -157,23 +157,38 @@ export class NewsfeedSingleComponent {
private updateMeta(): void {
const activity = this.activity.remind_object || this.activity;
const title: string =
activity.title ||
activity.message ||
`@${activity.ownerObj.username}'s post on Minds`;
let description: string;
if (title.length > 60) {
description = `...${title.substr(57)}`;
} else {
description = activity.blurb || '';
}
description += `. Subscribe to @${activity.ownerObj.username} on Minds`;
this.metaService
.setTitle(`@${activity.ownerObj.username} on Minds`)
.setDescription(
activity.title ||
activity.message ||
`Subscribe to @${activity.ownerObj.username} on Minds`
)
.setTitle(title)
.setDescription(description)
.setOgImage(
activity.custom_type === 'batch'
? activity.custom_data[0]['src']
: activity.thumbnail_src,
{ width: 2000, height: 1000 }
)
.setCanonicalUrl(`/newsfeed/${activity.guid}`)
.setRobots(
activity['thumbs:up:count'] >= MIN_METRIC_FOR_ROBOTS ? 'all' : 'noindex'
);
if (activity.nsfw.length) {
this.metaService.setNsfw(true);
}
if (activity.custom_type === 'video') {
this.metaService.setOgType('video');
this.metaService.setOgImage(activity.custom_data['thumbnail_src']);
......
......@@ -2,14 +2,16 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject,
PLATFORM_ID,
} from '@angular/core';
import { Router } from '@angular/router';
import { isPlatformServer } from '@angular/common';
import { Client } from '../../../../services/api/client';
import { Session } from '../../../../services/session';
@Component({
moduleId: module.id,
selector: 'm-wallet-token--contributions',
templateUrl: 'contributions.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
......@@ -25,7 +27,8 @@ export class WalletTokenContributionsComponent {
protected client: Client,
protected cd: ChangeDetectorRef,
public session: Session,
protected router: Router
protected router: Router,
@Inject(PLATFORM_ID) protected platformId: Object
) {}
ngOnInit() {
......@@ -45,6 +48,10 @@ export class WalletTokenContributionsComponent {
return;
}
if (isPlatformServer(this.platformId)) {
return;
}
if (refresh) {
this.contributions = [];
}
......
......@@ -24,6 +24,12 @@
sizes="16x16"
href="/en/assets/logos/bulb-16x16.png"
/>
<link
rel="preload"
href="https://cdn-assets.minds.com/front/fonts/material-icons.css"
as="style"
crossorigin
/>
<link
rel="preload"
href="https://cdn-assets.minds.com/front/fonts/material-icons.woff2"
......