Merge pull request #4221 from hunicus/add-meta-descriptions
Add meta descriptions
This commit is contained in:
		
						commit
						bf4a1fcd7a
					
				| @ -41,6 +41,7 @@ export class BisqAddressComponent implements OnInit, OnDestroy { | |||||||
|           document.body.scrollTo(0, 0); |           document.body.scrollTo(0, 0); | ||||||
|           this.addressString = params.get('id') || ''; |           this.addressString = params.get('id') || ''; | ||||||
|           this.seoService.setTitle($localize`:@@bisq-address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`); |           this.seoService.setTitle($localize`:@@bisq-address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.bisq.address:See current balance, pending transactions, and history of confirmed transactions for BSQ address ${this.addressString}:INTERPOLATION:.`); | ||||||
| 
 | 
 | ||||||
|           return this.bisqApiService.getAddress$(this.addressString) |           return this.bisqApiService.getAddress$(this.addressString) | ||||||
|             .pipe( |             .pipe( | ||||||
|  | |||||||
| @ -88,6 +88,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy { | |||||||
|         this.isLoading = false; |         this.isLoading = false; | ||||||
|         this.blockHeight = block.height; |         this.blockHeight = block.height; | ||||||
|         this.seoService.setTitle($localize`:@@bisq-block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.hash}:BLOCK_HASH:`); |         this.seoService.setTitle($localize`:@@bisq-block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.hash}:BLOCK_HASH:`); | ||||||
|  |         this.seoService.setDescription($localize`:@@meta.description.bisq.block:See all BSQ transactions in Bitcoin block ${block.height}:BLOCK_HEIGHT: (block hash ${block.hash}:BLOCK_HASH:).`); | ||||||
|         this.block = block; |         this.block = block; | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ export class BisqBlocksComponent implements OnInit { | |||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.websocketService.want(['blocks']); |     this.websocketService.want(['blocks']); | ||||||
|     this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`); |     this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bisq.blocks:See a list of recent Bitcoin blocks with BSQ transactions, total BSQ sent per block, and more.`); | ||||||
|     this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10); |     this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10); | ||||||
|     this.loadingItems = Array(this.itemsPerPage); |     this.loadingItems = Array(this.itemsPerPage); | ||||||
|     if (document.body.clientWidth < 670) { |     if (document.body.clientWidth < 670) { | ||||||
|  | |||||||
| @ -29,7 +29,8 @@ export class BisqDashboardComponent implements OnInit { | |||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle(`Markets`); |     this.seoService.setTitle($localize`:@@meta.title.bisq.markets:Markets`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bisq.markets:Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more.`); | ||||||
|     this.websocketService.want(['blocks']); |     this.websocketService.want(['blocks']); | ||||||
| 
 | 
 | ||||||
|     this.volumes$ = this.bisqApiService.getAllVolumesDay$() |     this.volumes$ = this.bisqApiService.getAllVolumesDay$() | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ export class BisqMainDashboardComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.resetTitle(); |     this.seoService.resetTitle(); | ||||||
|  |     this.seoService.resetDescription(); | ||||||
|     this.websocketService.want(['blocks']); |     this.websocketService.want(['blocks']); | ||||||
| 
 | 
 | ||||||
|     this.usdPrice$ = this.stateService.conversions$.asObservable().pipe( |     this.usdPrice$ = this.stateService.conversions$.asObservable().pipe( | ||||||
|  | |||||||
| @ -48,7 +48,8 @@ export class BisqMarketComponent implements OnInit, OnDestroy { | |||||||
|         map(([markets, routeParams]) => { |         map(([markets, routeParams]) => { | ||||||
|           const pair = routeParams.get('pair'); |           const pair = routeParams.get('pair'); | ||||||
|           const pairUpperCase = pair.replace('_', '/').toUpperCase(); |           const pairUpperCase = pair.replace('_', '/').toUpperCase(); | ||||||
|           this.seoService.setTitle(`Bisq market: ${pairUpperCase}`); |           this.seoService.setTitle($localize`:@@meta.title.bisq.market:Bisq market: ${pairUpperCase}`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.bisq.market:See price history, current buy/sell offers, and latest trades for the ${pairUpperCase} market on Bisq.`); | ||||||
| 
 | 
 | ||||||
|           return { |           return { | ||||||
|             pair: pairUpperCase, |             pair: pairUpperCase, | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ export class BisqStatsComponent implements OnInit { | |||||||
|     this.websocketService.want(['blocks']); |     this.websocketService.want(['blocks']); | ||||||
| 
 | 
 | ||||||
|     this.seoService.setTitle($localize`:@@2a30a4cdb123a03facc5ab8c5b3e6d8b8dbbc3d4:BSQ statistics`); |     this.seoService.setTitle($localize`:@@2a30a4cdb123a03facc5ab8c5b3e6d8b8dbbc3d4:BSQ statistics`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bisq.stats:See high-level stats on the BSQ economy: supply metrics, number of addresses, BSQ price, market cap, and more.`); | ||||||
|     this.stateService.bsqPrice$ |     this.stateService.bsqPrice$ | ||||||
|       .subscribe((bsqPrice) => { |       .subscribe((bsqPrice) => { | ||||||
|         this.price = bsqPrice; |         this.price = bsqPrice; | ||||||
|  | |||||||
| @ -48,6 +48,7 @@ export class BisqTransactionComponent implements OnInit, OnDestroy { | |||||||
|         document.body.scrollTo(0, 0); |         document.body.scrollTo(0, 0); | ||||||
|         this.txId = params.get('id') || ''; |         this.txId = params.get('id') || ''; | ||||||
|         this.seoService.setTitle($localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`); |         this.seoService.setTitle($localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`); | ||||||
|  |         this.seoService.setDescription($localize`:@@meta.description.bisq.transaction:See inputs, outputs, transaction type, burnt amount, and more for transaction with txid ${this.txId}:INTERPOLATION:.`); | ||||||
|         if (history.state.data) { |         if (history.state.data) { | ||||||
|           return of(history.state.data); |           return of(history.state.data); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -79,6 +79,7 @@ export class BisqTransactionsComponent implements OnInit, OnDestroy { | |||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.websocketService.want(['blocks']); |     this.websocketService.want(['blocks']); | ||||||
|     this.seoService.setTitle($localize`:@@add4cd82e3e38a3110fe67b3c7df56e9602644ee:Transactions`); |     this.seoService.setTitle($localize`:@@add4cd82e3e38a3110fe67b3c7df56e9602644ee:Transactions`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bisq.transactions:See recent BSQ transactions: amount, txid, associated Bitcoin block, transaction type, and more.`); | ||||||
| 
 | 
 | ||||||
|     this.radioGroupForm = this.formBuilder.group({ |     this.radioGroupForm = this.formBuilder.group({ | ||||||
|       txTypes: [this.txTypesDefaultChecked], |       txTypes: [this.txTypesDefaultChecked], | ||||||
|  | |||||||
| @ -43,6 +43,7 @@ export class AboutComponent implements OnInit { | |||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|     this.backendInfo$ = this.stateService.backendInfo$; |     this.backendInfo$ = this.stateService.backendInfo$; | ||||||
|     this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`); |     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.websocketService.want(['blocks']); |     this.websocketService.want(['blocks']); | ||||||
| 
 | 
 | ||||||
|     this.profiles$ = this.apiService.getAboutPageProfiles$().pipe( |     this.profiles$ = this.apiService.getAboutPageProfiles$().pipe( | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import { AudioService } from '../../services/audio.service'; | |||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { of, merge, Subscription, Observable } from 'rxjs'; | import { of, merge, Subscription, Observable } from 'rxjs'; | ||||||
| import { SeoService } from '../../services/seo.service'; | import { SeoService } from '../../services/seo.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| import { AddressInformation } from '../../interfaces/node-api.interface'; | import { AddressInformation } from '../../interfaces/node-api.interface'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
| @ -68,6 +69,7 @@ export class AddressPreviewComponent implements OnInit, OnDestroy { | |||||||
|             this.addressString = this.addressString.toLowerCase(); |             this.addressString = this.addressString.toLowerCase(); | ||||||
|           } |           } | ||||||
|           this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`); |           this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`); | ||||||
| 
 | 
 | ||||||
|           return (this.addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/) |           return (this.addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/) | ||||||
|               ? this.electrsApiService.getPubKeyAddress$(this.addressString) |               ? this.electrsApiService.getPubKeyAddress$(this.addressString) | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import { AudioService } from '../../services/audio.service'; | |||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { of, merge, Subscription, Observable } from 'rxjs'; | import { of, merge, Subscription, Observable } from 'rxjs'; | ||||||
| import { SeoService } from '../../services/seo.service'; | import { SeoService } from '../../services/seo.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| import { AddressInformation } from '../../interfaces/node-api.interface'; | import { AddressInformation } from '../../interfaces/node-api.interface'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
| @ -76,6 +77,7 @@ export class AddressComponent implements OnInit, OnDestroy { | |||||||
|             this.addressString = this.addressString.toLowerCase(); |             this.addressString = this.addressString.toLowerCase(); | ||||||
|           } |           } | ||||||
|           this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`); |           this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`); | ||||||
| 
 | 
 | ||||||
|           return merge( |           return merge( | ||||||
|             of(true), |             of(true), | ||||||
|  | |||||||
| @ -40,6 +40,7 @@ export class AssetsNavComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`); |     this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.liquid.assets:Explore all the assets issued on the Liquid network like L-BTC, L-CAD, USDT, and more.`); | ||||||
|     this.typeaheadSearchFn = this.typeaheadSearch; |     this.typeaheadSearchFn = this.typeaheadSearch; | ||||||
| 
 | 
 | ||||||
|     this.searchForm = this.formBuilder.group({ |     this.searchForm = this.formBuilder.group({ | ||||||
|  | |||||||
| @ -64,6 +64,7 @@ export class BlockFeeRatesGraphComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`:@@ed8e33059967f554ff06b4f5b6049c465b92d9b3:Block Fee Rates`); |     this.seoService.setTitle($localize`:@@ed8e33059967f554ff06b4f5b6049c465b92d9b3:Block Fee Rates`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-fee-rates:See Bitcoin feerates visualized over time, including minimum and maximum feerates per block along with feerates at various percentiles.`); | ||||||
|     this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); |     this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
|     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); |     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); | ||||||
|  | |||||||
| @ -65,6 +65,7 @@ export class BlockFeesGraphComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`:@@6c453b11fd7bd159ae30bc381f367bc736d86909:Block Fees`); |     this.seoService.setTitle($localize`:@@6c453b11fd7bd159ae30bc381f367bc736d86909:Block Fees`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-fees:See the average mining fees earned per Bitcoin block visualized in BTC and USD over time.`); | ||||||
|     this.miningWindowPreference = this.miningService.getDefaultTimespan('1m'); |     this.miningWindowPreference = this.miningService.getDefaultTimespan('1m'); | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
|     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); |     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); | ||||||
| @ -192,7 +193,7 @@ export class BlockFeesGraphComponent implements OnInit { | |||||||
|           { |           { | ||||||
|             name: 'Fees ' + this.currency, |             name: 'Fees ' + this.currency, | ||||||
|             inactiveColor: 'rgb(110, 112, 121)', |             inactiveColor: 'rgb(110, 112, 121)', | ||||||
|             textStyle: {   |             textStyle: { | ||||||
|               color: 'white', |               color: 'white', | ||||||
|             }, |             }, | ||||||
|             icon: 'roundRect', |             icon: 'roundRect', | ||||||
|  | |||||||
| @ -61,6 +61,7 @@ export class BlockHealthGraphComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`:@@d7d5fcf50179ad70c938491c517efb82de2c8146:Block Health`); |     this.seoService.setTitle($localize`:@@d7d5fcf50179ad70c938491c517efb82de2c8146:Block Health`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-health:See Bitcoin block health visualized over time. Block health is a measure of how many expected transactions were included in an actual mined block. Expected transactions are determined using Mempool's re-implementation of Bitcoin Core's transaction selection algorithm.`); | ||||||
|     this.miningWindowPreference = '24h';//this.miningService.getDefaultTimespan('24h');
 |     this.miningWindowPreference = '24h';//this.miningService.getDefaultTimespan('24h');
 | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
|     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); |     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); | ||||||
|  | |||||||
| @ -63,6 +63,7 @@ export class BlockRewardsGraphComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`:@@8ba8fe810458280a83df7fdf4c614dfc1a826445:Block Rewards`); |     this.seoService.setTitle($localize`:@@8ba8fe810458280a83df7fdf4c614dfc1a826445:Block Rewards`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-rewards:See Bitcoin block rewards in BTC and USD visualized over time. Block rewards are the total funds miners earn from the block subsidy and fees.`); | ||||||
|     this.miningWindowPreference = this.miningService.getDefaultTimespan('3m'); |     this.miningWindowPreference = this.miningService.getDefaultTimespan('3m'); | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
|     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); |     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); | ||||||
| @ -191,7 +192,7 @@ export class BlockRewardsGraphComponent implements OnInit { | |||||||
|           { |           { | ||||||
|             name: 'Rewards ' + this.currency, |             name: 'Rewards ' + this.currency, | ||||||
|             inactiveColor: 'rgb(110, 112, 121)', |             inactiveColor: 'rgb(110, 112, 121)', | ||||||
|             textStyle: {   |             textStyle: { | ||||||
|               color: 'white', |               color: 'white', | ||||||
|             }, |             }, | ||||||
|             icon: 'roundRect', |             icon: 'roundRect', | ||||||
|  | |||||||
| @ -60,6 +60,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { | |||||||
|     let firstRun = true; |     let firstRun = true; | ||||||
| 
 | 
 | ||||||
|     this.seoService.setTitle($localize`:@@56fa1cd221491b6478998679cba2dc8d55ba330d:Block Sizes and Weights`); |     this.seoService.setTitle($localize`:@@56fa1cd221491b6478998679cba2dc8d55ba330d:Block Sizes and Weights`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-sizes:See Bitcoin block sizes (MB) and block weights (weight units) visualized over time.`); | ||||||
|     this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); |     this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
|     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); |     this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ import { SeoService } from '../../services/seo.service'; | |||||||
| import { OpenGraphService } from '../../services/opengraph.service'; | import { OpenGraphService } from '../../services/opengraph.service'; | ||||||
| import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface'; | import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface'; | ||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; | import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
| @ -97,6 +98,11 @@ export class BlockPreviewComponent implements OnInit, OnDestroy { | |||||||
|         this.blockHeight = block.height; |         this.blockHeight = block.height; | ||||||
| 
 | 
 | ||||||
|         this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`); |         this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`); | ||||||
|  |         if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) { | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`); | ||||||
|  |         } else { | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`); | ||||||
|  |         } | ||||||
|         this.isLoadingBlock = false; |         this.isLoadingBlock = false; | ||||||
|         this.setBlockSubsidy(); |         this.setBlockSubsidy(); | ||||||
|         if (block?.extras?.reward !== undefined) { |         if (block?.extras?.reward !== undefined) { | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces | |||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; | import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; | ||||||
| import { detectWebGL } from '../../shared/graphs.utils'; | import { detectWebGL } from '../../shared/graphs.utils'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| import { PriceService, Price } from '../../services/price.service'; | import { PriceService, Price } from '../../services/price.service'; | ||||||
| import { CacheService } from '../../services/cache.service'; | import { CacheService } from '../../services/cache.service'; | ||||||
| 
 | 
 | ||||||
| @ -261,6 +262,11 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|         this.setNextAndPreviousBlockLink(); |         this.setNextAndPreviousBlockLink(); | ||||||
| 
 | 
 | ||||||
|         this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`); |         this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`); | ||||||
|  |         if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) { | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`); | ||||||
|  |         } else { | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`); | ||||||
|  |         } | ||||||
|         this.isLoadingBlock = false; |         this.isLoadingBlock = false; | ||||||
|         this.setBlockSubsidy(); |         this.setBlockSubsidy(); | ||||||
|         if (block?.extras?.reward !== undefined) { |         if (block?.extras?.reward !== undefined) { | ||||||
| @ -325,7 +331,7 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|         ]); |         ]); | ||||||
|       }) |       }) | ||||||
|     ) |     ) | ||||||
|     .subscribe(([transactions, blockAudit]) => {       |     .subscribe(([transactions, blockAudit]) => { | ||||||
|       if (transactions) { |       if (transactions) { | ||||||
|         this.strippedTransactions = transactions; |         this.strippedTransactions = transactions; | ||||||
|       } else { |       } else { | ||||||
| @ -680,7 +686,7 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|       this.setAuditAvailable(false); |       this.setAuditAvailable(false); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|    | 
 | ||||||
|   isAuditAvailableFromBlockHeight(blockHeight: number): boolean { |   isAuditAvailableFromBlockHeight(blockHeight: number): boolean { | ||||||
|     if (!this.auditSupported) { |     if (!this.auditSupported) { | ||||||
|       return false; |       return false; | ||||||
| @ -729,4 +735,4 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|       this.block.canonical = block.id; |       this.block.canonical = block.id; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,6 +5,8 @@ import { BlockExtended } from '../../interfaces/node-api.interface'; | |||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { StateService } from '../../services/state.service'; | import { StateService } from '../../services/state.service'; | ||||||
| import { WebsocketService } from '../../services/websocket.service'; | import { WebsocketService } from '../../services/websocket.service'; | ||||||
|  | import { SeoService } from '../../services/seo.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-blocks-list', |   selector: 'app-blocks-list', | ||||||
| @ -35,6 +37,7 @@ export class BlocksList implements OnInit { | |||||||
|     private websocketService: WebsocketService, |     private websocketService: WebsocketService, | ||||||
|     public stateService: StateService, |     public stateService: StateService, | ||||||
|     private cd: ChangeDetectorRef, |     private cd: ChangeDetectorRef, | ||||||
|  |     private seoService: SeoService, | ||||||
|   ) { |   ) { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -50,6 +53,14 @@ export class BlocksList implements OnInit { | |||||||
|     this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()]; |     this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()]; | ||||||
|     this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; |     this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; | ||||||
| 
 | 
 | ||||||
|  |     this.seoService.setTitle($localize`:@@meta.title.blocks-list:Blocks`); | ||||||
|  |     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.`); | ||||||
|  |     } else { | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.bitcoin.blocks:See the most recent Bitcoin${seoDescriptionNetwork(this.stateService.network)} blocks along with basic stats such as block height, block reward, block size, and more.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     this.blocks$ = combineLatest([ |     this.blocks$ = combineLatest([ | ||||||
|       this.fromHeightSubject.pipe( |       this.fromHeightSubject.pipe( | ||||||
|         switchMap((fromBlockHeight) => { |         switchMap((fromBlockHeight) => { | ||||||
| @ -129,4 +140,4 @@ export class BlocksList implements OnInit { | |||||||
|   isEllipsisActive(e): boolean { |   isEllipsisActive(e): boolean { | ||||||
|     return (e.offsetWidth < e.scrollWidth); |     return (e.offsetWidth < e.scrollWidth); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ import { MiningService } from '../../services/mining.service'; | |||||||
| import { download } from '../../shared/graphs.utils'; | import { download } from '../../shared/graphs.utils'; | ||||||
| import { ActivatedRoute } from '@angular/router'; | import { ActivatedRoute } from '@angular/router'; | ||||||
| import { StateService } from '../../services/state.service'; | import { StateService } from '../../services/state.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-hashrate-chart', |   selector: 'app-hashrate-chart', | ||||||
| @ -71,6 +72,7 @@ export class HashrateChartComponent implements OnInit { | |||||||
|       this.miningWindowPreference = '1y'; |       this.miningWindowPreference = '1y'; | ||||||
|     } else { |     } else { | ||||||
|       this.seoService.setTitle($localize`:@@3510fc6daa1d975f331e3a717bdf1a34efa06dff:Hashrate & Difficulty`); |       this.seoService.setTitle($localize`:@@3510fc6daa1d975f331e3a717bdf1a34efa06dff:Hashrate & Difficulty`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.hashrate:See hashrate and difficulty for the Bitcoin${seoDescriptionNetwork(this.network)} network visualized over time.`); | ||||||
|       this.miningWindowPreference = this.miningService.getDefaultTimespan('3m'); |       this.miningWindowPreference = this.miningService.getDefaultTimespan('3m'); | ||||||
|     } |     } | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
| @ -256,7 +258,7 @@ export class HashrateChartComponent implements OnInit { | |||||||
|               let difficultyPowerOfTen = hashratePowerOfTen; |               let difficultyPowerOfTen = hashratePowerOfTen; | ||||||
|               let difficulty = tick.data[1]; |               let difficulty = tick.data[1]; | ||||||
|               if (difficulty === null) { |               if (difficulty === null) { | ||||||
|                 difficultyString = `${tick.marker} ${tick.seriesName}: No data<br>`;   |                 difficultyString = `${tick.marker} ${tick.seriesName}: No data<br>`; | ||||||
|               } else { |               } else { | ||||||
|                 if (this.isMobile()) { |                 if (this.isMobile()) { | ||||||
|                   difficultyPowerOfTen = selectPowerOfTen(tick.data[1]); |                   difficultyPowerOfTen = selectPowerOfTen(tick.data[1]); | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import { switchMap, map, tap, filter } from 'rxjs/operators'; | |||||||
| import { MempoolBlock, TransactionStripped } from '../../interfaces/websocket.interface'; | import { MempoolBlock, TransactionStripped } from '../../interfaces/websocket.interface'; | ||||||
| import { Observable, BehaviorSubject } from 'rxjs'; | import { Observable, BehaviorSubject } from 'rxjs'; | ||||||
| import { SeoService } from '../../services/seo.service'; | import { SeoService } from '../../services/seo.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| import { WebsocketService } from '../../services/websocket.service'; | import { WebsocketService } from '../../services/websocket.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
| @ -54,6 +55,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { | |||||||
|                 const ordinal = this.getOrdinal(mempoolBlocks[this.mempoolBlockIndex]); |                 const ordinal = this.getOrdinal(mempoolBlocks[this.mempoolBlockIndex]); | ||||||
|                 this.ordinal$.next(ordinal); |                 this.ordinal$.next(ordinal); | ||||||
|                 this.seoService.setTitle(ordinal); |                 this.seoService.setTitle(ordinal); | ||||||
|  |                 this.seoService.setDescription($localize`:@@meta.description.mempool-block:See stats for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transactions in the mempool: fee range, aggregate size, and more. Mempool blocks are updated in real-time as the network receives new transactions.`); | ||||||
|                 mempoolBlocks[this.mempoolBlockIndex].isStack = mempoolBlocks[this.mempoolBlockIndex].blockVSize > this.stateService.blockVSize; |                 mempoolBlocks[this.mempoolBlockIndex].isStack = mempoolBlocks[this.mempoolBlockIndex].blockVSize > this.stateService.blockVSize; | ||||||
|                 return mempoolBlocks[this.mempoolBlockIndex]; |                 return mempoolBlocks[this.mempoolBlockIndex]; | ||||||
|               }) |               }) | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ export class MiningDashboardComponent implements OnInit, AfterViewInit { | |||||||
|     private router: Router |     private router: Router | ||||||
|   ) { |   ) { | ||||||
|     this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Mining Dashboard`); |     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.`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
| @ -29,7 +30,7 @@ export class MiningDashboardComponent implements OnInit, AfterViewInit { | |||||||
|     this.router.events.subscribe((e: NavigationStart) => { |     this.router.events.subscribe((e: NavigationStart) => { | ||||||
|       if (e.type === EventType.NavigationStart) { |       if (e.type === EventType.NavigationStart) { | ||||||
|         if (e.url.indexOf('graphs') === -1) { // The mining dashboard and the graph component are part of the same module so we can't use ngAfterViewInit in graphs.component.ts to blur the input
 |         if (e.url.indexOf('graphs') === -1) { // The mining dashboard and the graph component are part of the same module so we can't use ngAfterViewInit in graphs.component.ts to blur the input
 | ||||||
|           this.stateService.focusSearchInputDesktop();  |           this.stateService.focusSearchInputDesktop(); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|  | |||||||
| @ -56,6 +56,7 @@ export class PoolRankingComponent implements OnInit { | |||||||
|       this.miningWindowPreference = '1w'; |       this.miningWindowPreference = '1w'; | ||||||
|     } else { |     } else { | ||||||
|       this.seoService.setTitle($localize`:@@mining.mining-pools:Mining Pools`); |       this.seoService.setTitle($localize`:@@mining.mining-pools:Mining Pools`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.pool-ranking:See the top Bitcoin mining pools ranked by number of blocks mined, over your desired timeframe.`); | ||||||
|       this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); |       this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); | ||||||
|     } |     } | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
| @ -116,7 +117,7 @@ export class PoolRankingComponent implements OnInit { | |||||||
|     } else if (this.widget) { |     } else if (this.widget) { | ||||||
|       poolShareThreshold = 1; |       poolShareThreshold = 1; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     const data: object[] = []; |     const data: object[] = []; | ||||||
|     let totalShareOther = 0; |     let totalShareOther = 0; | ||||||
|     let totalBlockOther = 0; |     let totalBlockOther = 0; | ||||||
|  | |||||||
| @ -83,6 +83,7 @@ export class PoolPreviewComponent implements OnInit { | |||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           this.seoService.setTitle(poolStats.pool.name); |           this.seoService.setTitle(poolStats.pool.name); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.mining.pool:See mining pool stats for ${poolStats.pool.name}\: most recent mined blocks, hashrate over time, total block reward to date, known coinbase addresses, and more.`); | ||||||
|           let regexes = '"'; |           let regexes = '"'; | ||||||
|           for (const regex of poolStats.pool.regexes) { |           for (const regex of poolStats.pool.regexes) { | ||||||
|             regexes += regex + '", "'; |             regexes += regex + '", "'; | ||||||
|  | |||||||
| @ -83,6 +83,7 @@ export class PoolComponent implements OnInit { | |||||||
|         }), |         }), | ||||||
|         map((poolStats) => { |         map((poolStats) => { | ||||||
|           this.seoService.setTitle(poolStats.pool.name); |           this.seoService.setTitle(poolStats.pool.name); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.mining.pool:See mining pool stats for ${poolStats.pool.name}\: most recent mined blocks, hashrate over time, total block reward to date, known coinbase addresses, and more.`); | ||||||
|           let regexes = '"'; |           let regexes = '"'; | ||||||
|           for (const regex of poolStats.pool.regexes) { |           for (const regex of poolStats.pool.regexes) { | ||||||
|             regexes += regex + '", "'; |             regexes += regex + '", "'; | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { Component } from '@angular/core'; | import { Component } from '@angular/core'; | ||||||
| import { Env, StateService } from '../../services/state.service'; | import { Env, StateService } from '../../services/state.service'; | ||||||
|  | import { SeoService } from '../../services/seo.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-privacy-policy', |   selector: 'app-privacy-policy', | ||||||
| @ -11,5 +12,11 @@ export class PrivacyPolicyComponent { | |||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private stateService: StateService, |     private stateService: StateService, | ||||||
|  |     private seoService: SeoService, | ||||||
|   ) { } |   ) { } | ||||||
|  | 
 | ||||||
|  |   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™.'); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,9 @@ | |||||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit } from '@angular/core'; | ||||||
| import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; | import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; | ||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
|  | import { StateService } from '../../services/state.service'; | ||||||
|  | import { SeoService } from '../../services/seo.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-push-transaction', |   selector: 'app-push-transaction', | ||||||
| @ -16,12 +19,17 @@ export class PushTransactionComponent implements OnInit { | |||||||
|   constructor( |   constructor( | ||||||
|     private formBuilder: UntypedFormBuilder, |     private formBuilder: UntypedFormBuilder, | ||||||
|     private apiService: ApiService, |     private apiService: ApiService, | ||||||
|  |     public stateService: StateService, | ||||||
|  |     private seoService: SeoService, | ||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.pushTxForm = this.formBuilder.group({ |     this.pushTxForm = this.formBuilder.group({ | ||||||
|       txHash: ['', Validators.required], |       txHash: ['', Validators.required], | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     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.`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   postTx() { |   postTx() { | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ import { WebsocketService } from '../../services/websocket.service'; | |||||||
| import { RbfTree } from '../../interfaces/node-api.interface'; | import { RbfTree } from '../../interfaces/node-api.interface'; | ||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { StateService } from '../../services/state.service'; | import { StateService } from '../../services/state.service'; | ||||||
|  | import { SeoService } from '../../services/seo.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-rbf-list', |   selector: 'app-rbf-list', | ||||||
| @ -26,6 +28,7 @@ export class RbfList implements OnInit, OnDestroy { | |||||||
|     private apiService: ApiService, |     private apiService: ApiService, | ||||||
|     public stateService: StateService, |     public stateService: StateService, | ||||||
|     private websocketService: WebsocketService, |     private websocketService: WebsocketService, | ||||||
|  |     private seoService: SeoService, | ||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
| @ -51,9 +54,12 @@ export class RbfList implements OnInit, OnDestroy { | |||||||
|         this.isLoading = false; |         this.isLoading = false; | ||||||
|       }) |       }) | ||||||
|     ); |     ); | ||||||
|  | 
 | ||||||
|  |     this.seoService.setTitle($localize`:@@meta.title.rbf-list: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.`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnDestroy(): void { |   ngOnDestroy(): void { | ||||||
|     this.websocketService.stopTrackRbf(); |     this.websocketService.stopTrackRbf(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -62,6 +62,7 @@ export class StatisticsComponent implements OnInit { | |||||||
|     this.inverted = this.storageService.getValue('inverted-graph') === 'true'; |     this.inverted = this.storageService.getValue('inverted-graph') === 'true'; | ||||||
|     this.setFeeLevelDropdownData(); |     this.setFeeLevelDropdownData(); | ||||||
|     this.seoService.setTitle($localize`:@@5d4f792f048fcaa6df5948575d7cb325c9393383:Graphs`); |     this.seoService.setTitle($localize`:@@5d4f792f048fcaa6df5948575d7cb325c9393383:Graphs`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.mempool:See mempool size (in MvB) and transactions per second (in vB/s) visualized over time.`); | ||||||
|     this.stateService.networkChanged$.subscribe((network) => this.network = network); |     this.stateService.networkChanged$.subscribe((network) => this.network = network); | ||||||
|     this.graphWindowPreference = this.storageService.getValue('graphWindowPreference') ? this.storageService.getValue('graphWindowPreference').trim() : '2h'; |     this.graphWindowPreference = this.storageService.getValue('graphWindowPreference') ? this.storageService.getValue('graphWindowPreference').trim() : '2h'; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -37,6 +37,7 @@ export class TelevisionComponent implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|     this.seoService.setTitle($localize`:@@46ce8155c9ab953edeec97e8950b5a21e67d7c4e:TV view`); |     this.seoService.setTitle($localize`:@@46ce8155c9ab953edeec97e8950b5a21e67d7c4e:TV view`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.tv:See Bitcoin blocks and mempool congestion in real-time in a simplified format perfect for a TV.`); | ||||||
|     this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']); |     this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']); | ||||||
| 
 | 
 | ||||||
|     this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { |     this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { Component } from '@angular/core'; | import { Component } from '@angular/core'; | ||||||
| import { Env, StateService } from '../../services/state.service'; | import { Env, StateService } from '../../services/state.service'; | ||||||
|  | import { SeoService } from '../../services/seo.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-terms-of-service', |   selector: 'app-terms-of-service', | ||||||
| @ -10,5 +11,11 @@ export class TermsOfServiceComponent { | |||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private stateService: StateService, |     private stateService: StateService, | ||||||
|  |     private seoService: SeoService, | ||||||
|   ) { } |   ) { } | ||||||
|  | 
 | ||||||
|  |   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.'); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { Component } from '@angular/core'; | import { Component } from '@angular/core'; | ||||||
| import { Env, StateService } from '../../services/state.service'; | import { Env, StateService } from '../../services/state.service'; | ||||||
|  | import { SeoService } from '../../services/seo.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-trademark-policy', |   selector: 'app-trademark-policy', | ||||||
| @ -11,5 +12,11 @@ export class TrademarkPolicyComponent { | |||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private stateService: StateService, |     private stateService: StateService, | ||||||
|  |     private seoService: SeoService, | ||||||
|   ) { } |   ) { } | ||||||
|  | 
 | ||||||
|  |   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.'); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ import { CacheService } from '../../services/cache.service'; | |||||||
| import { OpenGraphService } from '../../services/opengraph.service'; | import { OpenGraphService } from '../../services/opengraph.service'; | ||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { SeoService } from '../../services/seo.service'; | import { SeoService } from '../../services/seo.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| import { CpfpInfo } from '../../interfaces/node-api.interface'; | import { CpfpInfo } from '../../interfaces/node-api.interface'; | ||||||
| import { LiquidUnblinding } from './liquid-ublinding'; | import { LiquidUnblinding } from './liquid-ublinding'; | ||||||
| 
 | 
 | ||||||
| @ -87,6 +88,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy { | |||||||
|           this.seoService.setTitle( |           this.seoService.setTitle( | ||||||
|             $localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:` |             $localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:` | ||||||
|           ); |           ); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.bitcoin.transaction:Get real-time status, addresses, fees, script info, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transaction with txid {txid}.`); | ||||||
|           this.resetTransaction(); |           this.resetTransaction(); | ||||||
|           return merge( |           return merge( | ||||||
|             of(true), |             of(true), | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ import { WebsocketService } from '../../services/websocket.service'; | |||||||
| import { AudioService } from '../../services/audio.service'; | import { AudioService } from '../../services/audio.service'; | ||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { SeoService } from '../../services/seo.service'; | import { SeoService } from '../../services/seo.service'; | ||||||
|  | import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||||
| import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment } from '../../interfaces/node-api.interface'; | import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment } from '../../interfaces/node-api.interface'; | ||||||
| import { LiquidUnblinding } from './liquid-ublinding'; | import { LiquidUnblinding } from './liquid-ublinding'; | ||||||
| import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; | import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; | ||||||
| @ -274,6 +275,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|           this.seoService.setTitle( |           this.seoService.setTitle( | ||||||
|             $localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:` |             $localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:` | ||||||
|           ); |           ); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.bitcoin.transaction:Get real-time status, addresses, fees, script info, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transaction with txid {txid}.`); | ||||||
|           this.resetTransaction(); |           this.resetTransaction(); | ||||||
|           return merge( |           return merge( | ||||||
|             of(true), |             of(true), | ||||||
| @ -376,7 +378,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|               this.blockConversion = price; |               this.blockConversion = price; | ||||||
|             }) |             }) | ||||||
|           ).subscribe(); |           ).subscribe(); | ||||||
|        | 
 | ||||||
|           setTimeout(() => { this.applyFragment(); }, 0); |           setTimeout(() => { this.applyFragment(); }, 0); | ||||||
|         }, |         }, | ||||||
|         (error) => { |         (error) => { | ||||||
|  | |||||||
| @ -69,6 +69,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; |     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; | ||||||
|     this.seoService.resetTitle(); |     this.seoService.resetTitle(); | ||||||
|  |     this.seoService.resetDescription(); | ||||||
|     this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']); |     this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']); | ||||||
|     this.websocketService.startTrackRbfSummary(); |     this.websocketService.startTrackRbfSummary(); | ||||||
|     this.network$ = merge(of(''), this.stateService.networkChanged$); |     this.network$ = merge(of(''), this.stateService.networkChanged$); | ||||||
|  | |||||||
| @ -28,21 +28,6 @@ export class DocsComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.websocket.want(['blocks']); |     this.websocket.want(['blocks']); | ||||||
|     const url = this.route.snapshot.url; |  | ||||||
|     if (url[0].path === "faq" ) { |  | ||||||
|         this.activeTab = 0; |  | ||||||
|         this.seoService.setTitle($localize`:@@docs.faq.button-title:FAQ`); |  | ||||||
|     } else if( url[1].path === "rest" ) { |  | ||||||
|         this.activeTab = 1; |  | ||||||
|         this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`); |  | ||||||
|     } else if( url[1].path === "websocket" ) { |  | ||||||
|         this.activeTab = 2; |  | ||||||
|         this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`); |  | ||||||
|     } else { |  | ||||||
|         this.activeTab = 3; |  | ||||||
|         this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.env = this.stateService.env; |     this.env = this.stateService.env; | ||||||
|     this.showWebSocketTab = ( ! ( ( this.stateService.network === "bisq" ) || ( this.stateService.network === "liquidtestnet" ) ) ); |     this.showWebSocketTab = ( ! ( ( this.stateService.network === "bisq" ) || ( this.stateService.network === "liquidtestnet" ) ) ); | ||||||
|     this.showFaqTab = ( this.env.BASE_MODULE === 'mempool' ) ? true : false; |     this.showFaqTab = ( this.env.BASE_MODULE === 'mempool' ) ? true : false; | ||||||
| @ -51,6 +36,40 @@ export class DocsComponent implements OnInit { | |||||||
|     document.querySelector<HTMLElement>( "html" ).style.scrollBehavior = "smooth"; |     document.querySelector<HTMLElement>( "html" ).style.scrollBehavior = "smooth"; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   ngDoCheck(): void { | ||||||
|  | 
 | ||||||
|  |     const url = this.route.snapshot.url; | ||||||
|  | 
 | ||||||
|  |     if (url[0].path === "faq" ) { | ||||||
|  |       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.`); | ||||||
|  |     } else if( url[1].path === "rest" ) { | ||||||
|  |       this.activeTab = 1; | ||||||
|  |       this.seoService.setTitle($localize`:@@meta.title.docs.rest:REST API`); | ||||||
|  |       if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) { | ||||||
|  |         this.seoService.setDescription($localize`:@@meta.description.docs.rest-liquid:Documentation for the liquid.network REST API service: get info on addresses, transactions, assets, blocks, and more.`); | ||||||
|  |       } else if( this.stateService.network === 'bisq' ) { | ||||||
|  |         this.seoService.setDescription($localize`:@@meta.description.docs.rest-bisq:Documentation for the bisq.markets REST API service: get info on recent trades, current offers, transactions, network state, and more.`); | ||||||
|  |       } else { | ||||||
|  |         this.seoService.setDescription($localize`:@@meta.description.docs.rest-bitcoin:Documentation for the mempool.space REST API service: get info on addresses, transactions, blocks, fees, mining, the Lightning network, and more.`); | ||||||
|  |       } | ||||||
|  |     } else if( url[1].path === "websocket" ) { | ||||||
|  |       this.activeTab = 2; | ||||||
|  |       this.seoService.setTitle($localize`:@@meta.title.docs.websocket:WebSocket API`); | ||||||
|  |       if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) { | ||||||
|  |         this.seoService.setDescription($localize`:@@meta.description.docs.websocket-liquid:Documentation for the liquid.network WebSocket API service: get real-time info on blocks, mempools, transactions, addresses, and more.`); | ||||||
|  |       } else { | ||||||
|  |         this.seoService.setDescription($localize`:@@meta.description.docs.websocket-bitcoin:Documentation for the mempool.space WebSocket API service: get real-time info on blocks, mempools, transactions, addresses, and more.`); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       this.activeTab = 3; | ||||||
|  |       this.seoService.setTitle($localize`:@@meta.title.docs.websocket:Electrum RPC`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.docs.electrumrpc:Documentation for our Electrum RPC interface: get instant, convenient, and reliable access to an Esplora instance.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   ngOnDestroy(): void { |   ngOnDestroy(): void { | ||||||
|     document.querySelector<HTMLElement>( "html" ).style.scrollBehavior = "auto"; |     document.querySelector<HTMLElement>( "html" ).style.scrollBehavior = "auto"; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ export class ChannelPreviewComponent implements OnInit { | |||||||
|           this.openGraphService.waitFor('channel-data-' + this.shortId); |           this.openGraphService.waitFor('channel-data-' + this.shortId); | ||||||
|           this.error = null; |           this.error = null; | ||||||
|           this.seoService.setTitle(`Channel: ${params.get('short_id')}`); |           this.seoService.setTitle(`Channel: ${params.get('short_id')}`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.lightning.channel:Overview for Lightning channel ${params.get('short_id')}. See channel capacity, the Lightning nodes involved, related on-chain transactions, and more.`); | ||||||
|           return this.lightningApiService.getChannel$(params.get('short_id')) |           return this.lightningApiService.getChannel$(params.get('short_id')) | ||||||
|             .pipe( |             .pipe( | ||||||
|               tap((data) => { |               tap((data) => { | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ export class ChannelComponent implements OnInit { | |||||||
|             .pipe( |             .pipe( | ||||||
|               tap((value) => { |               tap((value) => { | ||||||
|                 this.seoService.setTitle($localize`Channel: ${value.short_id}`); |                 this.seoService.setTitle($localize`Channel: ${value.short_id}`); | ||||||
|  |                 this.seoService.setDescription($localize`:@@meta.description.lightning.channel:Overview for Lightning channel ${value.short_id}. See channel capacity, the Lightning nodes involved, related on-chain transactions, and more.`); | ||||||
|               }), |               }), | ||||||
|               catchError((err) => { |               catchError((err) => { | ||||||
|                 this.error = err; |                 this.error = err; | ||||||
|  | |||||||
| @ -31,6 +31,7 @@ export class GroupPreviewComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle(`Mempool.Space Lightning Nodes`); |     this.seoService.setTitle(`Mempool.Space Lightning Nodes`); | ||||||
|  |     this.seoService.setDescription(`See all Lightning nodes run by mempool.space -- these are the nodes that provide the data on the mempool.space Lightning dashboard.`); | ||||||
| 
 | 
 | ||||||
|     this.nodes$ = this.activatedRoute.paramMap |     this.nodes$ = this.activatedRoute.paramMap | ||||||
|       .pipe( |       .pipe( | ||||||
|  | |||||||
| @ -39,6 +39,7 @@ export class GroupComponent implements OnInit { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     this.seoService.setTitle(`Mempool.space Lightning Nodes`); |     this.seoService.setTitle(`Mempool.space Lightning Nodes`); | ||||||
|  |     this.seoService.setDescription(`See all Lightning nodes run by mempool.space -- these are the nodes that provide the data on the mempool.space Lightning dashboard.`); | ||||||
| 
 | 
 | ||||||
|     this.nodes$ = this.lightningApiService.getNodGroupNodes$('mempool.space') |     this.nodes$ = this.lightningApiService.getNodGroupNodes$('mempool.space') | ||||||
|       .pipe( |       .pipe( | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ export class LightningDashboardComponent implements OnInit, AfterViewInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`:@@142e923d3b04186ac6ba23387265d22a2fa404e0:Lightning Explorer`); |     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) and Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).`); | ||||||
| 
 | 
 | ||||||
|     this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share()); |     this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share()); | ||||||
|     this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share()); |     this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share()); | ||||||
|  | |||||||
| @ -49,6 +49,7 @@ export class NodePreviewComponent implements OnInit { | |||||||
|         }), |         }), | ||||||
|         map((node) => { |         map((node) => { | ||||||
|           this.seoService.setTitle(`Node: ${node.alias}`); |           this.seoService.setTitle(`Node: ${node.alias}`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.lightning.node:Overview for the Lightning network node named ${node.alias}. See channels, capacity, location, fee stats, and more.`); | ||||||
| 
 | 
 | ||||||
|           const socketsObject = []; |           const socketsObject = []; | ||||||
|           const socketTypesMap = {}; |           const socketTypesMap = {}; | ||||||
|  | |||||||
| @ -60,6 +60,7 @@ export class NodeComponent implements OnInit { | |||||||
|         }), |         }), | ||||||
|         map((node) => { |         map((node) => { | ||||||
|           this.seoService.setTitle($localize`Node: ${node.alias}`); |           this.seoService.setTitle($localize`Node: ${node.alias}`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.lightning.node:Overview for the Lightning network node named ${node.alias}. See channels, capacity, location, fee stats, and more.`); | ||||||
|           this.clearnetSocketCount = 0; |           this.clearnetSocketCount = 0; | ||||||
|           this.torSocketCount = 0; |           this.torSocketCount = 0; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ export class NodesChannelsMap implements OnInit { | |||||||
|   @Input() disableSpinner = false; |   @Input() disableSpinner = false; | ||||||
|   @Output() readyEvent = new EventEmitter(); |   @Output() readyEvent = new EventEmitter(); | ||||||
| 
 | 
 | ||||||
|   channelsObservable: Observable<any>;  |   channelsObservable: Observable<any>; | ||||||
| 
 | 
 | ||||||
|   center: number[] | undefined; |   center: number[] | undefined; | ||||||
|   zoom: number | undefined; |   zoom: number | undefined; | ||||||
| @ -41,7 +41,7 @@ export class NodesChannelsMap implements OnInit { | |||||||
|   chartOptions: EChartsOption = {}; |   chartOptions: EChartsOption = {}; | ||||||
|   chartInitOptions = { |   chartInitOptions = { | ||||||
|     renderer: 'canvas', |     renderer: 'canvas', | ||||||
|   };  |   }; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private seoService: SeoService, |     private seoService: SeoService, | ||||||
| @ -64,15 +64,16 @@ export class NodesChannelsMap implements OnInit { | |||||||
|       this.zoom = 1.4; |       this.zoom = 1.4; | ||||||
|       this.center = [0, 10]; |       this.center = [0, 10]; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     if (this.style === 'graph') { |     if (this.style === 'graph') { | ||||||
|       this.seoService.setTitle($localize`Lightning Nodes Channels World Map`); |       this.seoService.setTitle($localize`Lightning Nodes Channels World Map`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.lightning.node-map:See the channels of non-Tor Lightning network nodes visualized on a world map. Hover/tap on points on the map for node names and details.`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (['nodepage', 'channelpage'].includes(this.style)) { |     if (['nodepage', 'channelpage'].includes(this.style)) { | ||||||
|       this.nodeSize = 8; |       this.nodeSize = 8; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     this.channelsObservable = this.activatedRoute.paramMap |     this.channelsObservable = this.activatedRoute.paramMap | ||||||
|      .pipe( |      .pipe( | ||||||
|        delay(100), |        delay(100), | ||||||
| @ -81,7 +82,7 @@ export class NodesChannelsMap implements OnInit { | |||||||
|         if (this.style === 'channelpage' && this.channel.length === 0 || !this.hasLocation) { |         if (this.style === 'channelpage' && this.channel.length === 0 || !this.hasLocation) { | ||||||
|           this.isLoading = false; |           this.isLoading = false; | ||||||
|         } |         } | ||||||
|              | 
 | ||||||
|         return zip( |         return zip( | ||||||
|           this.assetsService.getWorldMapJson$, |           this.assetsService.getWorldMapJson$, | ||||||
|           this.style !== 'channelpage' ? this.apiService.getChannelsGeo$(params.get('public_key') ?? undefined, this.style) : [''], |           this.style !== 'channelpage' ? this.apiService.getChannelsGeo$(params.get('public_key') ?? undefined, this.style) : [''], | ||||||
| @ -140,7 +141,7 @@ export class NodesChannelsMap implements OnInit { | |||||||
|             // on top of each other
 |             // on top of each other
 | ||||||
|             let random = Math.random() * 2 * Math.PI; |             let random = Math.random() * 2 * Math.PI; | ||||||
|             let random2 = Math.random() * 0.01; |             let random2 = Math.random() * 0.01; | ||||||
|              | 
 | ||||||
|             if (!nodesPubkeys[node1UniqueId]) { |             if (!nodesPubkeys[node1UniqueId]) { | ||||||
|               nodes.push([ |               nodes.push([ | ||||||
|                 channel[node1GpsLat] + random2 * Math.cos(random), |                 channel[node1GpsLat] + random2 * Math.cos(random), | ||||||
| @ -167,7 +168,7 @@ export class NodesChannelsMap implements OnInit { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const channelLoc = []; |             const channelLoc = []; | ||||||
|             channelLoc.push(nodesPubkeys[node1UniqueId].slice(0, 2));             |             channelLoc.push(nodesPubkeys[node1UniqueId].slice(0, 2)); | ||||||
|             channelLoc.push(nodesPubkeys[node2UniqueId].slice(0, 2)); |             channelLoc.push(nodesPubkeys[node2UniqueId].slice(0, 2)); | ||||||
|             channelsLoc.push(channelLoc); |             channelsLoc.push(channelLoc); | ||||||
|           } |           } | ||||||
| @ -326,7 +327,7 @@ export class NodesChannelsMap implements OnInit { | |||||||
|     this.chartInstance.on('finished', () => { |     this.chartInstance.on('finished', () => { | ||||||
|       this.isLoading = false; |       this.isLoading = false; | ||||||
|     }); |     }); | ||||||
|      | 
 | ||||||
|     if (this.style === 'widget') { |     if (this.style === 'widget') { | ||||||
|       this.chartInstance.getZr().on('click', (e) => { |       this.chartInstance.getZr().on('click', (e) => { | ||||||
|         this.zone.run(() => { |         this.zone.run(() => { | ||||||
| @ -335,7 +336,7 @@ export class NodesChannelsMap implements OnInit { | |||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|        | 
 | ||||||
|     this.chartInstance.on('click', (e) => { |     this.chartInstance.on('click', (e) => { | ||||||
|       if (e.data) { |       if (e.data) { | ||||||
|         this.zone.run(() => { |         this.zone.run(() => { | ||||||
|  | |||||||
| @ -48,6 +48,7 @@ export class NodesMap implements OnInit, OnChanges { | |||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     if (!this.widget) { |     if (!this.widget) { | ||||||
|       this.seoService.setTitle($localize`:@@af8560ca50882114be16c951650f83bca73161a7:Lightning Nodes World Map`); |       this.seoService.setTitle($localize`:@@af8560ca50882114be16c951650f83bca73161a7:Lightning Nodes World Map`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.lightning.node-channel-map:See the locations of non-Tor Lightning network nodes visualized on a world map. Hover/tap on points on the map for node names and details.`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!this.inputNodes$) { |     if (!this.inputNodes$) { | ||||||
|  | |||||||
| @ -65,6 +65,7 @@ export class NodesNetworksChartComponent implements OnInit { | |||||||
|       this.miningWindowPreference = '3y'; |       this.miningWindowPreference = '3y'; | ||||||
|     } else { |     } else { | ||||||
|       this.seoService.setTitle($localize`:@@b420668a91f8ebaf6e6409c4ba87f1d45961d2bd:Lightning Nodes Per Network`); |       this.seoService.setTitle($localize`:@@b420668a91f8ebaf6e6409c4ba87f1d45961d2bd:Lightning Nodes Per Network`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-network:See the number of Lightning network nodes visualized over time by network: clearnet only (IPv4, IPv6), darknet (Tor, I2p, cjdns), and both.`); | ||||||
|       this.miningWindowPreference = this.miningService.getDefaultTimespan('all'); |       this.miningWindowPreference = this.miningService.getDefaultTimespan('all'); | ||||||
|     } |     } | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
| @ -375,7 +376,7 @@ export class NodesNetworksChartComponent implements OnInit { | |||||||
|         // We create dummy duplicated series so when we use the data zoom, the y axis
 |         // We create dummy duplicated series so when we use the data zoom, the y axis
 | ||||||
|         // both scales properly
 |         // both scales properly
 | ||||||
|         const invisibleSerie = {...serie}; |         const invisibleSerie = {...serie}; | ||||||
|         invisibleSerie.name = 'ignored' + Math.random().toString();  |         invisibleSerie.name = 'ignored' + Math.random().toString(); | ||||||
|         invisibleSerie.stack = 'ignored'; |         invisibleSerie.stack = 'ignored'; | ||||||
|         invisibleSerie.yAxisIndex = 1; |         invisibleSerie.yAxisIndex = 1; | ||||||
|         invisibleSerie.lineStyle = { |         invisibleSerie.lineStyle = { | ||||||
|  | |||||||
| @ -44,7 +44,7 @@ export class NodesPerCountryChartComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`:@@9d3ad4c6623870d96b65fb7a708fed6ce7c20044:Lightning Nodes Per Country`); |     this.seoService.setTitle($localize`:@@9d3ad4c6623870d96b65fb7a708fed6ce7c20044:Lightning Nodes Per Country`); | ||||||
| 
 |     this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-country-overview:See a geographical breakdown of the Lightning network: how many Lightning nodes are hosted in countries around the world, aggregate BTC capacity for each country, and more.`); | ||||||
|     this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry$() |     this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry$() | ||||||
|       .pipe( |       .pipe( | ||||||
|         map(data => { |         map(data => { | ||||||
|  | |||||||
| @ -33,6 +33,7 @@ export class NodesPerCountry implements OnInit { | |||||||
|       .pipe( |       .pipe( | ||||||
|         map(response => { |         map(response => { | ||||||
|           this.seoService.setTitle($localize`Lightning nodes in ${response.country.en}`); |           this.seoService.setTitle($localize`Lightning nodes in ${response.country.en}`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-country:Explore all the Lightning nodes hosted in ${response.country.en} and see an overview of each node's capacity, number of open channels, and more.`); | ||||||
| 
 | 
 | ||||||
|           this.country = { |           this.country = { | ||||||
|             name: response.country.en, |             name: response.country.en, | ||||||
| @ -47,7 +48,7 @@ export class NodesPerCountry implements OnInit { | |||||||
|               iso: response.nodes[i].iso_code, |               iso: response.nodes[i].iso_code, | ||||||
|             }; |             }; | ||||||
|           } |           } | ||||||
|            | 
 | ||||||
|           const sumLiquidity = response.nodes.reduce((partialSum, a) => partialSum + a.capacity, 0); |           const sumLiquidity = response.nodes.reduce((partialSum, a) => partialSum + a.capacity, 0); | ||||||
|           const sumChannels = response.nodes.reduce((partialSum, a) => partialSum + a.channels, 0); |           const sumChannels = response.nodes.reduce((partialSum, a) => partialSum + a.channels, 0); | ||||||
|           const isps = {}; |           const isps = {}; | ||||||
| @ -70,14 +71,14 @@ export class NodesPerCountry implements OnInit { | |||||||
|               isps[node.isp].asns.push(node.as_number); |               isps[node.isp].asns.push(node.as_number); | ||||||
|             } |             } | ||||||
|             isps[node.isp].count++; |             isps[node.isp].count++; | ||||||
|              | 
 | ||||||
|             if (isps[node.isp].count > topIsp.count) { |             if (isps[node.isp].count > topIsp.count) { | ||||||
|               topIsp.count = isps[node.isp].count; |               topIsp.count = isps[node.isp].count; | ||||||
|               topIsp.id = isps[node.isp].asns.join(','); |               topIsp.id = isps[node.isp].asns.join(','); | ||||||
|               topIsp.name = node.isp; |               topIsp.name = node.isp; | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|            | 
 | ||||||
|           return { |           return { | ||||||
|             nodes: response.nodes, |             nodes: response.nodes, | ||||||
|             sumLiquidity: sumLiquidity, |             sumLiquidity: sumLiquidity, | ||||||
|  | |||||||
| @ -42,6 +42,7 @@ export class NodesPerISPPreview implements OnInit { | |||||||
|             id: this.route.snapshot.params.isp.split(',').join(', ') |             id: this.route.snapshot.params.isp.split(',').join(', ') | ||||||
|           }; |           }; | ||||||
|           this.seoService.setTitle($localize`Lightning nodes on ISP: ${response.isp} [AS${this.route.snapshot.params.isp}]`); |           this.seoService.setTitle($localize`Lightning nodes on ISP: ${response.isp} [AS${this.route.snapshot.params.isp}]`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-isp:Browse all Bitcoin Lightning nodes using the ${response.isp} [AS${this.route.snapshot.params.isp}] ISP and see aggregate stats like total number of nodes, total capacity, and more for the ISP.`); | ||||||
| 
 | 
 | ||||||
|           for (const i in response.nodes) { |           for (const i in response.nodes) { | ||||||
|             response.nodes[i].geolocation = <GeolocationData>{ |             response.nodes[i].geolocation = <GeolocationData>{ | ||||||
|  | |||||||
| @ -37,6 +37,7 @@ export class NodesPerISP implements OnInit { | |||||||
|             id: this.route.snapshot.params.isp.split(',').join(', ') |             id: this.route.snapshot.params.isp.split(',').join(', ') | ||||||
|           }; |           }; | ||||||
|           this.seoService.setTitle($localize`Lightning nodes on ISP: ${response.isp} [AS${this.route.snapshot.params.isp}]`); |           this.seoService.setTitle($localize`Lightning nodes on ISP: ${response.isp} [AS${this.route.snapshot.params.isp}]`); | ||||||
|  |           this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-isp:Browse all Bitcoin Lightning nodes using the ${response.isp} [AS${this.route.snapshot.params.isp}] ISP and see aggregate stats like total number of nodes, total capacity, and more for the ISP.`); | ||||||
| 
 | 
 | ||||||
|           for (const i in response.nodes) { |           for (const i in response.nodes) { | ||||||
|             response.nodes[i].geolocation = <GeolocationData>{ |             response.nodes[i].geolocation = <GeolocationData>{ | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ export class OldestNodes implements OnInit { | |||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     if (!this.widget) { |     if (!this.widget) { | ||||||
|       this.seoService.setTitle($localize`Oldest lightning nodes`); |       this.seoService.setTitle($localize`Oldest lightning nodes`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.lightning.ranking.oldest:See the oldest nodes on the Lightning network along with their capacity, number of channels, location, etc.`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (let i = 1; i <= (this.widget ? 10 : 100); ++i) { |     for (let i = 1; i <= (this.widget ? 10 : 100); ++i) { | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ import { LightningApiService } from '../../lightning-api.service'; | |||||||
| export class TopNodesPerCapacity implements OnInit { | export class TopNodesPerCapacity implements OnInit { | ||||||
|   @Input() nodes$: Observable<INodesRanking>; |   @Input() nodes$: Observable<INodesRanking>; | ||||||
|   @Input() widget: boolean = false; |   @Input() widget: boolean = false; | ||||||
|    | 
 | ||||||
|   topNodesPerCapacity$: Observable<ITopNodesPerCapacity[]>; |   topNodesPerCapacity$: Observable<ITopNodesPerCapacity[]>; | ||||||
|   skeletonRows: number[] = []; |   skeletonRows: number[] = []; | ||||||
|   currency$: Observable<string>; |   currency$: Observable<string>; | ||||||
| @ -31,6 +31,7 @@ export class TopNodesPerCapacity implements OnInit { | |||||||
| 
 | 
 | ||||||
|     if (!this.widget) { |     if (!this.widget) { | ||||||
|       this.seoService.setTitle($localize`:@@2d9883d230a47fbbb2ec969e32a186597ea27405:Liquidity Ranking`); |       this.seoService.setTitle($localize`:@@2d9883d230a47fbbb2ec969e32a186597ea27405:Liquidity Ranking`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.lightning.ranking.liquidity:See Lightning nodes with the most BTC liquidity deployed along with high-level stats like number of open channels, location, node age, and more.`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (let i = 1; i <= (this.widget ? 6 : 100); ++i) { |     for (let i = 1; i <= (this.widget ? 6 : 100); ++i) { | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ export class TopNodesPerChannels implements OnInit { | |||||||
| 
 | 
 | ||||||
|     if (this.widget === false) { |     if (this.widget === false) { | ||||||
|       this.seoService.setTitle($localize`:@@c50bf442cf99f6fc5f8b687c460f33234b879869:Connectivity Ranking`); |       this.seoService.setTitle($localize`:@@c50bf442cf99f6fc5f8b687c460f33234b879869:Connectivity Ranking`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.lightning.ranking.channels:See Lightning nodes with the most channels open along with high-level stats like total node capacity, node age, and more.`); | ||||||
| 
 | 
 | ||||||
|       this.topNodesPerChannels$ = this.apiService.getTopNodesByChannels$().pipe( |       this.topNodesPerChannels$ = this.apiService.getTopNodesByChannels$().pipe( | ||||||
|         map((ranking) => { |         map((ranking) => { | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ export class NodesRankingsDashboard implements OnInit { | |||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`Top lightning nodes`); |     this.seoService.setTitle($localize`Top lightning nodes`); | ||||||
|  |     this.seoService.setDescription($localize`:@@meta.description.lightning.rankings-dashboard:See top the Lightning network nodes ranked by liquidity, connectivity, and age.`); | ||||||
|     this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share()); |     this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -64,6 +64,7 @@ export class LightningStatisticsChartComponent implements OnInit { | |||||||
|       this.miningWindowPreference = '3y'; |       this.miningWindowPreference = '3y'; | ||||||
|     } else { |     } else { | ||||||
|       this.seoService.setTitle($localize`:@@ea8db27e6db64f8b940711948c001a1100e5fe9f:Lightning Network Capacity`); |       this.seoService.setTitle($localize`:@@ea8db27e6db64f8b940711948c001a1100e5fe9f:Lightning Network Capacity`); | ||||||
|  |       this.seoService.setDescription($localize`:@@meta.description.lightning.stats-chart:See the capacity of the Lightning network visualized over time in terms of the number of open channels and total bitcoin capacity.`); | ||||||
|       this.miningWindowPreference = this.miningService.getDefaultTimespan('all'); |       this.miningWindowPreference = this.miningService.getDefaultTimespan('all'); | ||||||
|     } |     } | ||||||
|     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); |     this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import { StateService } from './state.service'; | |||||||
| export class SeoService { | export class SeoService { | ||||||
|   network = ''; |   network = ''; | ||||||
|   baseTitle = 'mempool'; |   baseTitle = 'mempool'; | ||||||
|  |   baseDescription = 'Explore the full Bitcoin ecosystem with The Mempool Open Project™.'; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private titleService: Title, |     private titleService: Title, | ||||||
| @ -52,6 +53,18 @@ export class SeoService { | |||||||
|     this.resetTitle(); |     this.resetTitle(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   setDescription(newDescription: string): void { | ||||||
|  |     this.metaService.updateTag({ name: 'description', content: newDescription}); | ||||||
|  |     this.metaService.updateTag({ name: 'twitter:description', content: newDescription}); | ||||||
|  |     this.metaService.updateTag({ property: 'og:description', content: newDescription}); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   resetDescription(): void { | ||||||
|  |     this.metaService.updateTag({ name: 'description', content: this.getDescription()}); | ||||||
|  |     this.metaService.updateTag({ name: 'twitter:description', content: this.getDescription()}); | ||||||
|  |     this.metaService.updateTag({ property: 'og:description', content: this.getDescription()}); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getTitle(): string { |   getTitle(): string { | ||||||
|     if (this.network === 'testnet') |     if (this.network === 'testnet') | ||||||
|       return this.baseTitle + ' - Bitcoin Testnet'; |       return this.baseTitle + ' - Bitcoin Testnet'; | ||||||
| @ -66,6 +79,15 @@ export class SeoService { | |||||||
|     return this.baseTitle + ' - ' + (this.network ? this.ucfirst(this.network) : 'Bitcoin') + ' Explorer'; |     return this.baseTitle + ' - ' + (this.network ? this.ucfirst(this.network) : 'Bitcoin') + ' Explorer'; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   getDescription(): string { | ||||||
|  |     if ( (this.network === 'testnet') || (this.network === 'signet') || (this.network === '') || (this.network == 'mainnet') ) | ||||||
|  |       return this.baseDescription + ' See the real-time status of your transactions, browse network stats, and more.'; | ||||||
|  |     if ( (this.network === 'liquid') || (this.network === 'liquidtestnet') ) | ||||||
|  |       return this.baseDescription + ' See Liquid transactions & assets, get network info, and more.'; | ||||||
|  |     if (this.network === 'bisq') | ||||||
|  |       return this.baseDescription + ' See Bisq market prices, trading activity, and more.'; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   ucfirst(str: string) { |   ucfirst(str: string) { | ||||||
|     return str.charAt(0).toUpperCase() + str.slice(1); |     return str.charAt(0).toUpperCase() + str.slice(1); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -135,4 +135,13 @@ export function haversineDistance(lat1: number, lon1: number, lat2: number, lon2 | |||||||
| 
 | 
 | ||||||
| export function kmToMiles(km: number): number { | export function kmToMiles(km: number): number { | ||||||
|   return km * 0.62137119; |   return km * 0.62137119; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export function seoDescriptionNetwork(network: string): string { | ||||||
|  |   if( network === 'liquidtestnet' || network === 'testnet' ) { | ||||||
|  |     return ' Testnet'; | ||||||
|  |   } else if( network === 'signet' || network === 'testnet' ) { | ||||||
|  |     return ' ' + network.charAt(0).toUpperCase() + network.slice(1); | ||||||
|  |   } | ||||||
|  |   return ''; | ||||||
|  | } | ||||||
|  | |||||||
| @ -7,18 +7,18 @@ | |||||||
|   <script src="/resources/config.js"></script> |   <script src="/resources/config.js"></script> | ||||||
|   <base href="/"> |   <base href="/"> | ||||||
| 
 | 
 | ||||||
|   <meta name="description" content="The Mempool Open Source Project® - Explore the full Bitcoin ecosystem."> |   <meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more."> | ||||||
| 
 | 
 | ||||||
|   <meta property="og:image" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" /> |   <meta property="og:image" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" /> | ||||||
|   <meta property="og:image:type" content="image/jpeg" /> |   <meta property="og:image:type" content="image/jpeg" /> | ||||||
| 
 |   <meta property="og:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more." /> | ||||||
|   <meta property="twitter:card" content="summary_large_image"> |   <meta name="twitter:card" content="summary_large_image"> | ||||||
|   <meta property="twitter:site" content="https://bisq.markets/"> |   <meta name="twitter:site" content="https://bisq.markets/"> | ||||||
|   <meta property="twitter:creator" content="@bisq_network"> |   <meta name="twitter:creator" content="@bisq_network"> | ||||||
|   <meta property="twitter:title" content="The Mempool Open Source Project®"> |   <meta name="twitter:title" content="The Mempool Open Source Project®"> | ||||||
|   <meta property="twitter:description" content="Explore the full Bitcoin ecosystem with mempool.space™" /> |   <meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more." /> | ||||||
|   <meta property="twitter:image:src" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" /> |   <meta name="twitter:image:src" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" /> | ||||||
|   <meta property="twitter:domain" content="bisq.markets"> |   <meta name="twitter:domain" content="bisq.markets"> | ||||||
| 
 | 
 | ||||||
|   <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> |   <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> | ||||||
|   <meta name="msapplication-TileColor" content="#000000"> |   <meta name="msapplication-TileColor" content="#000000"> | ||||||
|  | |||||||
| @ -7,18 +7,19 @@ | |||||||
|   <script src="/resources/config.js"></script> |   <script src="/resources/config.js"></script> | ||||||
|   <base href="/"> |   <base href="/"> | ||||||
| 
 | 
 | ||||||
|   <meta name="description" content="The Mempool Open Source Project® - Explore the full Bitcoin ecosystem."> |   <meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Liquid transactions & assets, get network info, and more."> | ||||||
|   <meta property="og:image" content="https://liquid.network/resources/liquid/liquid-network-preview.png" /> |   <meta property="og:image" content="https://liquid.network/resources/liquid/liquid-network-preview.png" /> | ||||||
|   <meta property="og:image:type" content="image/png" /> |   <meta property="og:image:type" content="image/png" /> | ||||||
|   <meta property="og:image:width" content="1000" /> |   <meta property="og:image:width" content="1000" /> | ||||||
|   <meta property="og:image:height" content="500" /> |   <meta property="og:image:height" content="500" /> | ||||||
|   <meta property="twitter:card" content="summary_large_image"> |   <meta property="og:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Liquid transactions & assets, get network info, and more." /> | ||||||
|   <meta property="twitter:site" content="@mempool"> |   <meta name="twitter:card" content="summary_large_image"> | ||||||
|   <meta property="twitter:creator" content="@mempool"> |   <meta name="twitter:site" content="@mempool"> | ||||||
|   <meta property="twitter:title" content="The Mempool Open Source Project®"> |   <meta name="twitter:creator" content="@mempool"> | ||||||
|   <meta property="twitter:description" content="Explore the full Bitcoin ecosystem with mempool.space™" /> |   <meta name="twitter:title" content="The Mempool Open Source Project®"> | ||||||
|   <meta property="twitter:image:src" content="https://liquid.network/resources/liquid/liquid-network-preview.png" /> |   <meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Liquid transactions & assets, get network info, and more." /> | ||||||
|   <meta property="twitter:domain" content="liquid.network"> |   <meta name="twitter:image:src" content="https://liquid.network/resources/liquid/liquid-network-preview.png" /> | ||||||
|  |   <meta name="twitter:domain" content="liquid.network"> | ||||||
| 
 | 
 | ||||||
|   <link rel="apple-touch-icon" sizes="180x180" href="/resources/liquid/favicons/apple-touch-icon.png"> |   <link rel="apple-touch-icon" sizes="180x180" href="/resources/liquid/favicons/apple-touch-icon.png"> | ||||||
|   <link rel="icon" type="image/png" sizes="48x48" href="/resources/liquid/favicons/favicon-48x48.png"> |   <link rel="icon" type="image/png" sizes="48x48" href="/resources/liquid/favicons/favicon-48x48.png"> | ||||||
|  | |||||||
| @ -7,18 +7,19 @@ | |||||||
|   <script src="/resources/config.js"></script> |   <script src="/resources/config.js"></script> | ||||||
|   <base href="/"> |   <base href="/"> | ||||||
| 
 | 
 | ||||||
|   <meta name="description" content="The Mempool Open Source Project® - Explore the full Bitcoin ecosystem." /> |   <meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project™. See the real-time status of your transactions, get network info, and more." /> | ||||||
|   <meta property="og:image" content="https://mempool.space/resources/mempool-space-preview.png" /> |   <meta property="og:image" content="https://mempool.space/resources/mempool-space-preview.png" /> | ||||||
|   <meta property="og:image:type" content="image/png" /> |   <meta property="og:image:type" content="image/png" /> | ||||||
|   <meta property="og:image:width" content="1000" /> |   <meta property="og:image:width" content="1000" /> | ||||||
|   <meta property="og:image:height" content="500" /> |   <meta property="og:image:height" content="500" /> | ||||||
|   <meta property="twitter:card" content="summary_large_image"> |   <meta property="og:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project™. See the real-time status of your transactions, get network info, and more." /> | ||||||
|   <meta property="twitter:site" content="@mempool"> |   <meta name="twitter:card" content="summary_large_image"> | ||||||
|   <meta property="twitter:creator" content="@mempool"> |   <meta name="twitter:site" content="@mempool"> | ||||||
|   <meta property="twitter:title" content="The Mempool Open Source Project®"> |   <meta name="twitter:creator" content="@mempool"> | ||||||
|   <meta property="twitter:description" content="Explore the full Bitcoin ecosystem with mempool.space™" /> |   <meta name="twitter:title" content="The Mempool Open Source Project®"> | ||||||
|   <meta property="twitter:image:src" content="https://mempool.space/resources/mempool-space-preview.png" /> |   <meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project™. See the real-time status of your transactions, get network info, and more." /> | ||||||
|   <meta property="twitter:domain" content="mempool.space"> |   <meta name="twitter:image:src" content="https://mempool.space/resources/mempool-space-preview.png" /> | ||||||
|  |   <meta name="twitter:domain" content="mempool.space"> | ||||||
| 
 | 
 | ||||||
|   <link rel="apple-touch-icon" sizes="180x180" href="/resources/favicons/apple-touch-icon.png"> |   <link rel="apple-touch-icon" sizes="180x180" href="/resources/favicons/apple-touch-icon.png"> | ||||||
|   <link rel="icon" type="image/png" sizes="32x32" href="/resources/favicons/favicon-32x32.png"> |   <link rel="icon" type="image/png" sizes="32x32" href="/resources/favicons/favicon-32x32.png"> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user