diff --git a/frontend/src/app/components/about/about.component.ts b/frontend/src/app/components/about/about.component.ts index 3fea849a1..44bee5828 100644 --- a/frontend/src/app/components/about/about.component.ts +++ b/frontend/src/app/components/about/about.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'; import { WebsocketService } from '../../services/websocket.service'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; import { StateService } from '../../services/state.service'; import { Observable } from 'rxjs'; import { ApiService } from '../../services/api.service'; @@ -33,6 +34,7 @@ export class AboutComponent implements OnInit { constructor( private websocketService: WebsocketService, private seoService: SeoService, + private ogService: OpenGraphService, public stateService: StateService, private enterpriseService: EnterpriseService, private apiService: ApiService, @@ -46,6 +48,7 @@ export class AboutComponent implements OnInit { this.backendInfo$ = this.stateService.backendInfo$; this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`); this.seoService.setDescription($localize`:@@meta.description.about:Learn more about The Mempool Open Source Project®\: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more.`); + this.ogService.setManualOgImage('about.jpg'); this.websocketService.want(['blocks']); this.profiles$ = this.apiService.getAboutPageProfiles$().pipe( diff --git a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts index 91b77f5e8..877f4414d 100644 --- a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts +++ b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core'; import { SeoService } from '../../../services/seo.service'; +import { OpenGraphService } from '../../../services/opengraph.service'; import { WebsocketService } from '../../../services/websocket.service'; import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface'; import { StateService } from '../../../services/state.service'; @@ -34,11 +35,13 @@ export class AcceleratorDashboardComponent implements OnInit { constructor( private seoService: SeoService, + private ogService: OpenGraphService, private websocketService: WebsocketService, private serviceApiServices: ServicesApiServices, private stateService: StateService, ) { this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Accelerator Dashboard`); + this.ogService.setManualOgImage('accelerator.jpg'); } ngOnInit(): void { diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index 196a0341e..23280e1ef 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -6,6 +6,7 @@ import { ApiService } from '../../services/api.service'; import { StateService } from '../../services/state.service'; import { WebsocketService } from '../../services/websocket.service'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; import { seoDescriptionNetwork } from '../../shared/common.utils'; @Component({ @@ -39,6 +40,7 @@ export class BlocksList implements OnInit { public stateService: StateService, private cd: ChangeDetectorRef, private seoService: SeoService, + private ogService: OpenGraphService, ) { this.isMempoolModule = this.stateService.env.BASE_MODULE === 'mempool'; } @@ -57,6 +59,7 @@ export class BlocksList implements OnInit { if (!this.widget) { this.seoService.setTitle($localize`:@@m8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`); + this.ogService.setManualOgImage('recent-blocks.jpg'); } if( this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet' ) { this.seoService.setDescription($localize`:@@meta.description.liquid.blocks:See the most recent Liquid${seoDescriptionNetwork(this.stateService.network)} blocks along with basic stats such as block height, block size, and more.`); diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts index cfc8ef230..96058e7bf 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; import { WebsocketService } from '../../services/websocket.service'; import { StateService } from '../../services/state.service'; import { EventType, NavigationStart, Router } from '@angular/router'; @@ -15,12 +16,14 @@ export class MiningDashboardComponent implements OnInit, AfterViewInit { constructor( private seoService: SeoService, + private ogService: OpenGraphService, private websocketService: WebsocketService, private stateService: StateService, private router: Router ) { this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Mining Dashboard`); this.seoService.setDescription($localize`:@@meta.description.mining.dashboard:Get real-time Bitcoin mining stats like hashrate, difficulty adjustment, block rewards, pool dominance, and more.`); + this.ogService.setManualOgImage('mining.jpg'); } ngOnInit(): void { diff --git a/frontend/src/app/components/privacy-policy/privacy-policy.component.ts b/frontend/src/app/components/privacy-policy/privacy-policy.component.ts index b98390731..05f77c063 100644 --- a/frontend/src/app/components/privacy-policy/privacy-policy.component.ts +++ b/frontend/src/app/components/privacy-policy/privacy-policy.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { Env, StateService } from '../../services/state.service'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; @Component({ selector: 'app-privacy-policy', @@ -13,10 +14,12 @@ export class PrivacyPolicyComponent { constructor( private stateService: StateService, private seoService: SeoService, + private ogService: OpenGraphService, ) { } ngOnInit(): void { this.seoService.setTitle('Privacy Policy'); this.seoService.setDescription('Trusted third parties are security holes, as are trusted first parties...you should only trust your own self-hosted instance of The Mempool Open Source Project®.'); + this.ogService.setManualOgImage('privacy.jpg'); } } diff --git a/frontend/src/app/components/push-transaction/push-transaction.component.ts b/frontend/src/app/components/push-transaction/push-transaction.component.ts index cbc5d905a..a85cd47db 100644 --- a/frontend/src/app/components/push-transaction/push-transaction.component.ts +++ b/frontend/src/app/components/push-transaction/push-transaction.component.ts @@ -3,6 +3,7 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms import { ApiService } from '../../services/api.service'; import { StateService } from '../../services/state.service'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; import { seoDescriptionNetwork } from '../../shared/common.utils'; @Component({ @@ -21,6 +22,7 @@ export class PushTransactionComponent implements OnInit { private apiService: ApiService, public stateService: StateService, private seoService: SeoService, + private ogService: OpenGraphService, ) { } ngOnInit(): void { @@ -30,6 +32,7 @@ export class PushTransactionComponent implements OnInit { this.seoService.setTitle($localize`:@@meta.title.push-tx:Broadcast Transaction`); this.seoService.setDescription($localize`:@@meta.description.push-tx:Broadcast a transaction to the ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} network using the transaction's hash.`); + this.ogService.setManualOgImage('broadcast-tx.jpg'); } postTx() { diff --git a/frontend/src/app/components/rbf-list/rbf-list.component.ts b/frontend/src/app/components/rbf-list/rbf-list.component.ts index 0721c7fdf..ff30dd1c9 100644 --- a/frontend/src/app/components/rbf-list/rbf-list.component.ts +++ b/frontend/src/app/components/rbf-list/rbf-list.component.ts @@ -7,6 +7,7 @@ import { RbfTree } from '../../interfaces/node-api.interface'; import { ApiService } from '../../services/api.service'; import { StateService } from '../../services/state.service'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; import { seoDescriptionNetwork } from '../../shared/common.utils'; @Component({ @@ -29,6 +30,7 @@ export class RbfList implements OnInit, OnDestroy { public stateService: StateService, private websocketService: WebsocketService, private seoService: SeoService, + private ogService: OpenGraphService, ) { } ngOnInit(): void { @@ -57,6 +59,7 @@ export class RbfList implements OnInit, OnDestroy { this.seoService.setTitle($localize`:@@5e3d5a82750902f159122fcca487b07f1af3141f:RBF Replacements`); this.seoService.setDescription($localize`:@@meta.description.rbf-list:See the most recent RBF replacements on the Bitcoin${seoDescriptionNetwork(this.stateService.network)} network, updated in real-time.`); + this.ogService.setManualOgImage('rbf.jpg'); } ngOnDestroy(): void { diff --git a/frontend/src/app/components/terms-of-service/terms-of-service.component.ts b/frontend/src/app/components/terms-of-service/terms-of-service.component.ts index 708ebad76..71a86c759 100644 --- a/frontend/src/app/components/terms-of-service/terms-of-service.component.ts +++ b/frontend/src/app/components/terms-of-service/terms-of-service.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { Env, StateService } from '../../services/state.service'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; @Component({ selector: 'app-terms-of-service', @@ -12,10 +13,12 @@ export class TermsOfServiceComponent { constructor( private stateService: StateService, private seoService: SeoService, + private ogService: OpenGraphService, ) { } ngOnInit(): void { this.seoService.setTitle('Terms of Service'); this.seoService.setDescription('Out of respect for the Bitcoin community, the mempool.space website is Bitcoin Only and does not display any advertising.'); + this.ogService.setManualOgImage('tos.jpg'); } } diff --git a/frontend/src/app/components/trademark-policy/trademark-policy.component.ts b/frontend/src/app/components/trademark-policy/trademark-policy.component.ts index b8f53afcf..ad8b6b372 100644 --- a/frontend/src/app/components/trademark-policy/trademark-policy.component.ts +++ b/frontend/src/app/components/trademark-policy/trademark-policy.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { Env, StateService } from '../../services/state.service'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; @Component({ selector: 'app-trademark-policy', @@ -13,10 +14,12 @@ export class TrademarkPolicyComponent { constructor( private stateService: StateService, private seoService: SeoService, + private ogService: OpenGraphService, ) { } ngOnInit(): void { this.seoService.setTitle('Trademark Policy'); this.seoService.setDescription('An overview of the trademarks registered by Mempool Space K.K. and The Mempool Open Source Project® and what we consider to be lawful usage of those trademarks.'); + this.ogService.setManualOgImage('trademark-policy.jpg'); } } diff --git a/frontend/src/app/docs/docs/docs.component.ts b/frontend/src/app/docs/docs/docs.component.ts index 6d6c3b0c1..e3737b545 100644 --- a/frontend/src/app/docs/docs/docs.component.ts +++ b/frontend/src/app/docs/docs/docs.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { Env, StateService } from '../../services/state.service'; import { WebsocketService } from '../../services/websocket.service'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; @Component({ selector: 'app-docs', @@ -24,6 +25,7 @@ export class DocsComponent implements OnInit { private stateService: StateService, private websocket: WebsocketService, private seoService: SeoService, + private ogService: OpenGraphService, ) { } ngOnInit(): void { @@ -44,6 +46,7 @@ export class DocsComponent implements OnInit { this.activeTab = 0; this.seoService.setTitle($localize`:@@meta.title.docs.faq:FAQ`); this.seoService.setDescription($localize`:@@meta.description.docs.faq:Get answers to common questions like: What is a mempool? Why isn't my transaction confirming? How can I run my own instance of The Mempool Open Source Project? And more.`); + this.ogService.setManualOgImage('faq.jpg'); } else if( url[1].path === "rest" ) { this.activeTab = 1; this.seoService.setTitle($localize`:@@meta.title.docs.rest:REST API`); diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts index 63cdab42d..a8c931da8 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts @@ -3,6 +3,7 @@ import { Observable } from 'rxjs'; import { share } from 'rxjs/operators'; import { INodesRanking, INodesStatistics } from '../../interfaces/node-api.interface'; import { SeoService } from '../../services/seo.service'; +import { OpenGraphService } from '../../services/opengraph.service'; import { StateService } from '../../services/state.service'; import { LightningApiService } from '../lightning-api.service'; @@ -21,14 +22,16 @@ export class LightningDashboardComponent implements OnInit, AfterViewInit { constructor( private lightningApiService: LightningApiService, private seoService: SeoService, + private ogService: OpenGraphService, private stateService: StateService, ) { } ngOnInit(): void { this.onResize(); - + this.seoService.setTitle($localize`:@@142e923d3b04186ac6ba23387265d22a2fa404e0:Lightning Explorer`); this.seoService.setDescription($localize`:@@meta.description.lightning.dashboard:Get stats on the Lightning network (aggregate capacity, connectivity, etc), Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).`); + this.ogService.setManualOgImage('lightning.jpg'); this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share()); this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share()); diff --git a/frontend/src/app/services/opengraph.service.ts b/frontend/src/app/services/opengraph.service.ts index 9e2fef781..5e429ed70 100644 --- a/frontend/src/app/services/opengraph.service.ts +++ b/frontend/src/app/services/opengraph.service.ts @@ -25,7 +25,7 @@ export class OpenGraphService { ) { // save og:image tag from original template const initialOgImageTag = metaService.getTag("property='og:image'"); - this.defaultImageUrl = initialOgImageTag?.content || 'https://mempool.space/resources/mempool-space-preview.png'; + this.defaultImageUrl = initialOgImageTag?.content || 'https://mempool.space/resources/previews/mempool-space-preview.jpg'; this.router.events.pipe( filter(event => event instanceof NavigationEnd), map(() => this.activatedRoute), @@ -53,7 +53,7 @@ export class OpenGraphService { const lang = this.LanguageService.getLanguage(); const ogImageUrl = `${window.location.protocol}//${window.location.host}/render/${lang}/preview${this.router.url}`; this.metaService.updateTag({ property: 'og:image', content: ogImageUrl }); - this.metaService.updateTag({ property: 'twitter:image:src', content: ogImageUrl }); + this.metaService.updateTag({ name: 'twitter:image', content: ogImageUrl }); this.metaService.updateTag({ property: 'og:image:type', content: 'image/png' }); this.metaService.updateTag({ property: 'og:image:width', content: '1200' }); this.metaService.updateTag({ property: 'og:image:height', content: '600' }); @@ -61,12 +61,21 @@ export class OpenGraphService { clearOgImage() { this.metaService.updateTag({ property: 'og:image', content: this.defaultImageUrl }); - this.metaService.updateTag({ property: 'twitter:image:src', content: this.defaultImageUrl }); + this.metaService.updateTag({ name: 'twitter:image', content: this.defaultImageUrl }); this.metaService.updateTag({ property: 'og:image:type', content: 'image/png' }); this.metaService.updateTag({ property: 'og:image:width', content: '1000' }); this.metaService.updateTag({ property: 'og:image:height', content: '500' }); } + setManualOgImage(imageFilename) { + const ogImage = `${window.location.protocol}//${window.location.host}/resources/previews/${imageFilename}`; + this.metaService.updateTag({ property: 'og:image', content: ogImage }); + this.metaService.updateTag({ property: 'og:image:type', content: 'image/jpeg' }); + this.metaService.updateTag({ property: 'og:image:width', content: '2000' }); + this.metaService.updateTag({ property: 'og:image:height', content: '1000' }); + this.metaService.updateTag({ name: 'twitter:image', content: ogImage }); + } + /// register an event that needs to resolve before we can take a screenshot waitFor(event) { if (!this.previewLoadingEvents[event]) { diff --git a/frontend/src/app/services/seo.service.ts b/frontend/src/app/services/seo.service.ts index 7830690ff..cb9a321d6 100644 --- a/frontend/src/app/services/seo.service.ts +++ b/frontend/src/app/services/seo.service.ts @@ -39,14 +39,14 @@ export class SeoService { setTitle(newTitle: string): void { this.titleService.setTitle(newTitle + ' - ' + this.getTitle()); this.metaService.updateTag({ property: 'og:title', content: newTitle}); - this.metaService.updateTag({ property: 'twitter:title', content: newTitle}); + this.metaService.updateTag({ name: 'twitter:title', content: newTitle}); this.metaService.updateTag({ property: 'og:meta:ready', content: 'ready'}); } resetTitle(): void { this.titleService.setTitle(this.getTitle()); this.metaService.updateTag({ property: 'og:title', content: this.getTitle()}); - this.metaService.updateTag({ property: 'twitter:title', content: this.getTitle()}); + this.metaService.updateTag({ name: 'twitter:title', content: this.getTitle()}); this.metaService.updateTag({ property: 'og:meta:ready', content: 'ready'}); } diff --git a/frontend/src/index.mempool.html b/frontend/src/index.mempool.html index def14434e..838af21d0 100644 --- a/frontend/src/index.mempool.html +++ b/frontend/src/index.mempool.html @@ -8,17 +8,17 @@ - - - - + + + + - + diff --git a/frontend/src/resources/previews/about.jpg b/frontend/src/resources/previews/about.jpg new file mode 100644 index 000000000..720245d9c Binary files /dev/null and b/frontend/src/resources/previews/about.jpg differ diff --git a/frontend/src/resources/previews/accelerator.jpg b/frontend/src/resources/previews/accelerator.jpg new file mode 100644 index 000000000..3a10b7221 Binary files /dev/null and b/frontend/src/resources/previews/accelerator.jpg differ diff --git a/frontend/src/resources/previews/broadcast-tx.jpg b/frontend/src/resources/previews/broadcast-tx.jpg new file mode 100644 index 000000000..f035f9d80 Binary files /dev/null and b/frontend/src/resources/previews/broadcast-tx.jpg differ diff --git a/frontend/src/resources/previews/faq.jpg b/frontend/src/resources/previews/faq.jpg new file mode 100644 index 000000000..4e198ed49 Binary files /dev/null and b/frontend/src/resources/previews/faq.jpg differ diff --git a/frontend/src/resources/previews/lightning.jpg b/frontend/src/resources/previews/lightning.jpg new file mode 100644 index 000000000..59431708c Binary files /dev/null and b/frontend/src/resources/previews/lightning.jpg differ diff --git a/frontend/src/resources/previews/mempool-space-preview.jpg b/frontend/src/resources/previews/mempool-space-preview.jpg new file mode 100644 index 000000000..ca76ba008 Binary files /dev/null and b/frontend/src/resources/previews/mempool-space-preview.jpg differ diff --git a/frontend/src/resources/previews/mining.jpg b/frontend/src/resources/previews/mining.jpg new file mode 100644 index 000000000..f36f0ae57 Binary files /dev/null and b/frontend/src/resources/previews/mining.jpg differ diff --git a/frontend/src/resources/previews/privacy.jpg b/frontend/src/resources/previews/privacy.jpg new file mode 100644 index 000000000..f6d32bf69 Binary files /dev/null and b/frontend/src/resources/previews/privacy.jpg differ diff --git a/frontend/src/resources/previews/rbf.jpg b/frontend/src/resources/previews/rbf.jpg new file mode 100644 index 000000000..f96cf59cb Binary files /dev/null and b/frontend/src/resources/previews/rbf.jpg differ diff --git a/frontend/src/resources/previews/recent-blocks.jpg b/frontend/src/resources/previews/recent-blocks.jpg new file mode 100644 index 000000000..91c44f0de Binary files /dev/null and b/frontend/src/resources/previews/recent-blocks.jpg differ diff --git a/frontend/src/resources/previews/tos.jpg b/frontend/src/resources/previews/tos.jpg new file mode 100644 index 000000000..26865848b Binary files /dev/null and b/frontend/src/resources/previews/tos.jpg differ diff --git a/frontend/src/resources/previews/trademark-policy.jpg b/frontend/src/resources/previews/trademark-policy.jpg new file mode 100644 index 000000000..d9fd24155 Binary files /dev/null and b/frontend/src/resources/previews/trademark-policy.jpg differ diff --git a/unfurler/src/routes.ts b/unfurler/src/routes.ts index f9280369c..fefd89902 100644 --- a/unfurler/src/routes.ts +++ b/unfurler/src/routes.ts @@ -30,7 +30,7 @@ const routes = { }, lightning: { title: "Lightning", - fallbackImg: '/resources/previews/lightning.png', + fallbackImg: '/resources/previews/lightning.jpg', routes: { node: { render: true, @@ -68,7 +68,7 @@ const routes = { }, mining: { title: "Mining", - fallbackImg: '/resources/previews/mining.png', + fallbackImg: '/resources/previews/mining.jpg', routes: { pool: { render: true, @@ -83,7 +83,7 @@ const routes = { const networks = { bitcoin: { - fallbackImg: '/resources/previews/dashboard.png', + fallbackImg: '/resources/previews/mempool-space-preview.jpg', routes: { ...routes // all routes supported } @@ -147,4 +147,4 @@ export function matchRoute(network: string, path: string): Match { } return match; -} \ No newline at end of file +}