Merge pull request #1473 from hunicus/add-faq

Add faq
This commit is contained in:
softsimon 2022-04-13 02:34:35 +04:00 committed by GitHub
commit 762c75803a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 314 additions and 26 deletions

View File

@ -137,13 +137,17 @@ let routes: Routes = [
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/faq',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
redirectTo: 'docs/faq'
},
{
path: 'api',
@ -276,13 +280,17 @@ let routes: Routes = [
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/faq',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
redirectTo: 'docs/faq'
},
{
path: 'api',
@ -408,13 +416,17 @@ let routes: Routes = [
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/faq',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
redirectTo: 'docs/faq'
},
{
path: 'api',

View File

@ -56,6 +56,7 @@ import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, fa
import { ApiDocsComponent } from './components/docs/api-docs.component';
import { DocsComponent } from './components/docs/docs.component';
import { ApiDocsNavComponent } from './components/docs/api-docs-nav.component';
import { NoSanitizePipe } from './shared/pipes/no-sanitize.pipe';
import { CodeTemplateComponent } from './components/docs/code-template.component';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
@ -121,6 +122,7 @@ import { BlockRewardsGraphComponent } from './components/block-rewards-graph/blo
DashboardComponent,
DifficultyComponent,
ApiDocsComponent,
NoSanitizePipe,
CodeTemplateComponent,
TermsOfServiceComponent,
PrivacyPolicyComponent,

View File

@ -4411,3 +4411,177 @@ export const restApiDocsData = [
},
];
export const faqData = [
{
type: "category",
category: "basics",
fragment: "basics",
title: "Basics",
showConditions: bitcoinNetworks
},
{
type: "endpoint",
category: "basics",
showConditions: bitcoinNetworks,
fragment: "what-is-a-mempool",
title: "What is a mempool?",
answer: "<p>A mempool (short for \"memory pool\") holds the queue of pending and unconfirmed transactions for a cryptocurrency network node. There is no one global mempool: every node on the network maintains its own mempool, so different nodes may hold different transactions in their mempools.</p>"
},
{
type: "endpoint",
category: "basics",
showConditions: bitcoinNetworks,
fragment: "what-is-a-mempool-explorer",
title: "What is a mempool explorer?",
answer: "<p>A mempool explorer is a tool that enables you to view real-time and historical information about a node's mempool, visualize its transactions, and search and view those transactions.</p><p>The mempool.space website invented the concept of visualizing a Bitcoin node's mempool as <b>projected blocks</b>. These blocks are the inspiration for our half-filled block logo.</p><p>Projected blocks are on the left of the dotted white line, and confirmed blocks are on the right.</p>"
},
{
type: "endpoint",
category: "basics",
showConditions: bitcoinNetworks,
fragment: "what-is-a-blockchain",
title: "What is a blockchain?",
answer: "<p>A blockchain is a distributed ledger that records the transactions for a cryptocurrency network. Miners amend the blockchain ledger by mining new blocks.</p>"
},
{
type: "endpoint",
category: "basics",
showConditions: bitcoinNetworks,
fragment: "what-is-a-block-explorer",
title: "What is a block explorer?",
answer: "<p>A block explorer is a tool that enables you to explore real-time and historical information about the blockchain of a cryptocurrency. This includes data related to blocks, transactions, addresses, and more.</p>"
},
{
type: "endpoint",
category: "basics",
showConditions: bitcoinNetworks,
fragment: "what-is-mining",
title: "What is mining?",
answer: "Mining is the process by which unconfirmed transactions in a mempool are confirmed into a block on a blockchain. Miners select unconfirmed transactions from their mempools and arrange them into a block such that they solve a particular math problem.</p><p>The first miner on the network to find a suitable block earns all the transaction fees from the transactions in that block. As a result, miners tend to prioritize transactions with higher transaction fees.</p>"
},
{
type: "endpoint",
category: "basics",
showConditions: bitcoinNetworks,
fragment: "what-are-mining-pools",
title: "What are mining pools?",
answer: "Mining pools are groups of miners that combine their computational power in order to increase the probability of finding new blocks."
},
{
type: "category",
category: "help",
fragment: "help-stuck-transaction",
title: "Help! My transaction is stuck",
showConditions: bitcoinNetworks
},
{
type: "endpoint",
category: "help",
showConditions: bitcoinNetworks,
fragment: "why-is-transaction-stuck-in-mempool",
title: "Why is my transaction stuck in the mempool?",
answer: "<p>Miners decide which transactions are included in the blocks they mine, so they usually prioritize transactions which pay them the highest transaction fees (transaction fees are measured in sats per virtual byte, or sat/vB). If it's been a while and your transcation hasn't been confirmed, your transaction probably has a lower transaction fee relative to other transactions currently in the mempool.</p>"
},
{
type: "endpoint",
category: "help",
showConditions: bitcoinNetworks,
fragment: "how-to-get-transaction-confirmed-quickly",
title: "How can I get my transaction confirmed more quickly?",
answer: "<p>If your wallet supports RBF, and if your transaction was created with RBF enabled, you can bump the fee higher.</p><p>Otherwise, if your wallet does not support RBF, you can increase the effective fee rate of your transaction by spending its change output using a higher fee. This is called CPFP.</p>"
},
{
type: "endpoint",
category: "help",
showConditions: bitcoinNetworks,
fragment: "how-prevent-stuck-transaction",
title: "How can I prevent a transaction from getting stuck in the future?",
answer: "<p>You must use an adequate transaction fee commensurate with how quickly you need the transaction to be confirmed. Also consider using RBF if your wallet supports it so that you can bump the fee rate if needed.</p>"
},
{
type: "category",
category: "using",
fragment: "using-this-website",
title: "Using this website",
showConditions: bitcoinNetworks
},
{
type: "endpoint",
category: "how-to",
showConditions: bitcoinNetworks,
fragment: "looking-up-transactions",
title: "How can I look up a transaction?",
answer: "Search for the transaction ID in the search box at the top-right of this website."
},
{
type: "endpoint",
category: "how-to",
showConditions: bitcoinNetworks,
fragment: "looking-up-addresses",
title: "How can I look up an address?",
answer: "Search for the address in the search box at the top-right of this website."
},
{
type: "endpoint",
category: "how-to",
showConditions: bitcoinNetworks,
fragment: "looking-up-blocks",
title: "How can I look up a block?",
answer: "Search for the block number (or block hash) in the search box at the top-right of this website."
},
{
type: "endpoint",
category: "how-to",
showConditions: bitcoinNetworks,
fragment: "looking-up-fee-estimates",
title: "How can I look up fee estimates?",
answer: "<p>See real-time fee estimates on <a href='/'>the main dashboard</a>.</p><p>Low priority is suggested for confirmation within 6 blocks (~1 hour), Medium priority is suggested for confirmation within 3 blocks (~30 minutes), and High priority is suggested for confirmation in the next block (~10 minutes).</p>"
},
{
type: "endpoint",
category: "how-to",
showConditions: bitcoinNetworks,
fragment: "looking-up-historical-trends",
title: "How can I explore historical trends?",
answer: "See the <a href='/graphs'>graphs page</a> for aggregate trends over time: mempool size over time and incoming transaction velocity over time."
},
{
type: "category",
category: "advanced",
fragment: "advanced",
title: "Advanced",
showConditions: bitcoinNetworks
},
{
type: "endpoint",
category: "advanced",
showConditions: bitcoinNetworks,
fragment: "who-runs-this-website",
title: "Who runs this website?",
answer: "The official mempool.space website is operated by The Mempool Open Source Project. See more information on our <a href='/about'>About page</a>. There are also many unofficial instances of this website operated by individual members of the Bitcoin community."
},
{
type: "endpoint",
category: "advanced",
showConditions: bitcoinNetworks,
fragment: "host-my-own-instance-raspberry-pi",
title: "How can I host my own instance on a Raspberry Pi?",
answer: "We support one-click installation on a number of Raspberry Pi full-node distros including Umbrel, RaspiBlitz, MyNode, and RoninDojo."
},
{
type: "endpoint",
category: "advanced",
showConditions: bitcoinNetworks,
fragment: "host-my-own-instance-linux-server",
title: "How can I host my own instance on a Linux server?",
answer: "You can manually install mempool on your own Linux server, but this requires advanced sysadmin skills since you will be manually configuring everything. We do not provide support for manual deployments."
},
{
type: "endpoint",
category: "advanced",
showConditions: bitcoinNetworks,
fragment: "install-mempool-with-docker",
title: "Can I install Mempool using Docker?",
answer: "Yes, we publish Docker images (or you can build your own), and provide <a href='https://github.com/mempool/mempool/tree/master/docker' target='_blank'>an example docker-compose template</a>."
}
];

View File

@ -1,4 +1,4 @@
<div *ngFor="let item of restDocs">
<div *ngFor="let item of tabData">
<p *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</p>
<a *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )" [routerLink]="['./']" fragment="{{ item.fragment }}" (click)="navLinkClick($event)">{{ item.title }}</a>
</div>

View File

@ -1,5 +1,6 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { restApiDocsData } from './api-docs-data';
import { faqData } from './api-docs-data';
@Component({
selector: 'app-api-docs-nav',
@ -9,13 +10,18 @@ import { restApiDocsData } from './api-docs-data';
export class ApiDocsNavComponent implements OnInit {
@Input() network: any;
@Input() whichTab: string;
@Output() navLinkClickEvent: EventEmitter<any> = new EventEmitter();
restDocs: any[];
tabData: any[];
constructor() { }
ngOnInit(): void {
this.restDocs = restApiDocsData;
if( this.whichTab === 'rest' ) {
this.tabData = restApiDocsData;
} else if( this.whichTab = 'faq' ) {
this.tabData = faqData;
}
}
navLinkClick( event ) {

View File

@ -1,20 +1,45 @@
<ng-container *ngIf="{ val: network$ | async } as network">
<div class="container-xl text-left">
<div id="restAPI" *ngIf="restTabActivated">
<div id="faq" *ngIf="whichTab === 'faq'">
<div id="doc-nav-desktop" class="hide-on-mobile" [ngClass]="desktopDocsNavPosition">
<app-api-docs-nav (navLinkClickEvent)="anchorLinkClick( $event )" [network]="{ val: network$ | async }"></app-api-docs-nav>
<app-api-docs-nav (navLinkClickEvent)="anchorLinkClick( $event )" [network]="{ val: network$ | async }" [whichTab]="whichTab"></app-api-docs-nav>
</div>
<div class="doc-content">
<div class="doc-item-container" *ngFor="let item of faq">
<h3 *ngIf="item.type === 'category'">{{ item.title }}</h3>
<div *ngIf="item.type !== 'category'" class="endpoint-container" id="{{ item.fragment }}">
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}"><table><tr><td>{{ item.title }}</td><td><span>{{ item.category }}</span></td></tr></table></a>
<div class="endpoint-content">
<div class="endpoint" [innerHTML]="item.answer | noSanitize"></div>
<div class="blockchain-wrapper" *ngIf="item.fragment === 'what-is-a-mempool-explorer'">
<app-blockchain></app-blockchain>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="restAPI" *ngIf="whichTab === 'rest'">
<div id="doc-nav-desktop" class="hide-on-mobile" [ngClass]="desktopDocsNavPosition">
<app-api-docs-nav (navLinkClickEvent)="anchorLinkClick( $event )" [network]="{ val: network$ | async }" [whichTab]="whichTab"></app-api-docs-nav>
</div>
<div class="doc-content">
<p class="hide-on-mobile no-bottom-space">Reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title">API service</ng-container>.</p>
<div *ngFor="let item of restDocs">
<div class="doc-item-container" *ngFor="let item of restDocs">
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
<div *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )" class="endpoint-container" id="{{ item.fragment }}">
<a class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}">{{ item.title }} <span>{{ item.category }}</span></a>
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}">{{ item.title }} <span>{{ item.category }}</span></a>
<div class="endpoint-content">
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
@ -65,7 +90,7 @@
</div>
</div>
<div id="websocketAPI" *ngIf="!restTabActivated && ( network.val !== 'bisq' )">
<div id="websocketAPI" *ngIf="( whichTab === 'websocket' ) && ( network.val !== 'bisq' )">
<div class="api-category">
<div class="websocket">
<div class="endpoint">

View File

@ -152,6 +152,14 @@ h3 {
float: right;
}
.endpoint-container .section-header table {
width: 100%;
}
.endpoint-container .section-header table td:first-child {
padding-right: 24px;
}
#doc-nav-mobile {
position: fixed;
top: 20px;
@ -179,6 +187,16 @@ h3 {
border-radius: 0.5rem 0.5rem 0 0;
}
.blockchain-wrapper {
position: relative;
width: 100%;
overflow: auto;
scrollbar-width: none;
}
.blockchain-wrapper::-webkit-scrollbar {
display: none;
}
@media (max-width: 992px) {
.hide-on-mobile {
@ -231,4 +249,8 @@ h3 {
h3 {
display: none;
}
.doc-item-container:last-of-type .endpoint-container {
margin-bottom: 4rem;
}
}

View File

@ -4,7 +4,7 @@ import { Observable, merge, of } from 'rxjs';
import { SeoService } from 'src/app/services/seo.service';
import { tap } from 'rxjs/operators';
import { ActivatedRoute } from "@angular/router";
import { restApiDocsData, wsApiDocsData } from './api-docs-data';
import { faqData, restApiDocsData, wsApiDocsData } from './api-docs-data';
@Component({
selector: 'app-api-docs',
@ -18,8 +18,9 @@ export class ApiDocsComponent implements OnInit {
env: Env;
code: any;
baseNetworkUrl = '';
@Input() restTabActivated: Boolean;
@Input() whichTab: string;
desktopDocsNavPosition = "relative";
faq: any[];
restDocs: any[];
wsDocs: any;
screenWidth: number;
@ -33,7 +34,9 @@ export class ApiDocsComponent implements OnInit {
ngAfterViewInit() {
const that = this;
setTimeout( () => {
this.openEndpointContainer( this.route.snapshot.fragment );
if( this.route.snapshot.fragment ) {
this.openEndpointContainer( this.route.snapshot.fragment );
}
window.addEventListener('scroll', function() {
that.desktopDocsNavPosition = ( window.pageYOffset > 182 ) ? "fixed" : "relative";
});
@ -62,6 +65,7 @@ export class ApiDocsComponent implements OnInit {
this.hostname = `${document.location.protocol}//${this.hostname}`;
this.faq = faqData;
this.restDocs = restApiDocsData;
this.wsDocs = wsApiDocsData;
@ -71,7 +75,16 @@ export class ApiDocsComponent implements OnInit {
}
anchorLinkClick( event: any ) {
const targetId = event.target.hash.substring(1);
let targetId = "";
if( event.target.nodeName === "A" ) {
targetId = event.target.hash.substring(1);
} else {
let element = event.target;
while( element.nodeName !== "A" ) {
element = element.parentElement;
}
targetId = element.hash.substring(1);
}
if( this.route.snapshot.fragment === targetId ) {
document.getElementById( targetId ).scrollIntoView();
}
@ -79,7 +92,8 @@ export class ApiDocsComponent implements OnInit {
}
openEndpointContainer( targetId ) {
if( ( window.innerWidth <= 992 ) && this.restTabActivated && targetId ) {
const tabHeaderHeight = document.getElementById( targetId + "-tab-header" ).scrollHeight;
if( ( window.innerWidth <= 992 ) && ( ( this.whichTab === 'rest' ) || ( this.whichTab === 'faq' ) ) && targetId ) {
const endpointContainerEl = document.querySelector<HTMLElement>( "#" + targetId );
const endpointContentEl = document.querySelector<HTMLElement>( "#" + targetId + " .endpoint-content" );
const endPointContentElHeight = endpointContentEl.clientHeight;
@ -90,8 +104,8 @@ export class ApiDocsComponent implements OnInit {
endpointContentEl.style.opacity = "0";
endpointContentEl.classList.remove( "open" );
} else {
endpointContainerEl.style.height = endPointContentElHeight + 90 + "px";
endpointContentEl.style.top = "90px";
endpointContainerEl.style.height = endPointContentElHeight + tabHeaderHeight + 28 + "px";
endpointContentEl.style.top = tabHeaderHeight + 28 + "px";
endpointContentEl.style.opacity = "1";
endpointContentEl.classList.add( "open" );
}

View File

@ -5,20 +5,29 @@
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs">
<li [ngbNavItem]="0">
<a ngbNavLink routerLink="../rest">API - REST</a>
<li [ngbNavItem]="0" *ngIf="showFaqTab">
<a ngbNavLink [routerLink]="['/docs/faq' | relativeUrl]">FAQ</a>
<ng-template ngbNavContent>
<app-api-docs [restTabActivated]="true"></app-api-docs>
<app-api-docs [whichTab]="'faq'"></app-api-docs>
</ng-template>
</li>
<li [ngbNavItem]="1" *ngIf="showWebSocketTab">
<a ngbNavLink routerLink="../websocket">API - WebSocket</a>
<li [ngbNavItem]="1">
<a ngbNavLink [routerLink]="['/docs/api/rest' | relativeUrl]">API - REST</a>
<ng-template ngbNavContent>
<app-api-docs [restTabActivated]="false"></app-api-docs>
<app-api-docs [whichTab]="'rest'"></app-api-docs>
</ng-template>
</li>
<li [ngbNavItem]="2" *ngIf="showWebSocketTab">
<a ngbNavLink [routerLink]="['/docs/api/websocket' | relativeUrl]">API - WebSocket</a>
<ng-template ngbNavContent>
<app-api-docs [whichTab]="'websocket'"></app-api-docs>
</ng-template>
</li>

View File

@ -1,6 +1,7 @@
import { Component, OnInit, HostBinding } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Env, StateService } from 'src/app/services/state.service';
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({
selector: 'app-docs',
@ -12,19 +13,31 @@ export class DocsComponent implements OnInit {
activeTab = 0;
env: Env;
showWebSocketTab = true;
showFaqTab = true;
@HostBinding('attr.dir') dir = 'ltr';
constructor(
private route: ActivatedRoute,
private stateService: StateService,
private websocket: WebsocketService,
) { }
ngOnInit(): void {
this.websocket.want(['blocks']);
const url = this.route.snapshot.url;
this.activeTab = ( url[2].path === "rest" ) ? 0 : 1;
if( url[1].path === "faq" ) {
this.activeTab = 0;
} else if( url[2].path === "rest" ) {
this.activeTab = 1;
} else {
this.activeTab = 2;
}
this.env = this.stateService.env;
this.showWebSocketTab = ( ! ( ( this.env.BASE_MODULE === "bisq" ) || ( 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;
document.querySelector<HTMLElement>( "html" ).style.scrollBehavior = "smooth";
}

View File

@ -0,0 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Pipe({ name: 'noSanitize' })
export class NoSanitizePipe implements PipeTransform {
constructor(private domSanitizer: DomSanitizer) { }
transform(html: string): SafeHtml {
return this.domSanitizer.bypassSecurityTrustHtml(html);
}
}