Merge pull request #7 from breez/multi-lang-tabs

Multi lang tabs
This commit is contained in:
Roei Erez
2023-07-03 18:43:02 +02:00
committed by GitHub
9 changed files with 730 additions and 3 deletions

View File

@@ -6,6 +6,7 @@ src = "src"
title = "Breez SDK" title = "Breez SDK"
[output.html] [output.html]
additional-js = ["tabs.js"]
git-repository-url = "https://github.com/breez/breez-sdk-docs" git-repository-url = "https://github.com/breez/breez-sdk-docs"
edit-url-template = "https://github.com/breez/breez-sdk-docs/edit/main/{path}" edit-url-template = "https://github.com/breez/breez-sdk-docs/edit/main/{path}"

View File

@@ -15,6 +15,11 @@ Connecting to a node requires a seed (your master key) and credentials. The seed
Breez SDK is available in several platforms. Follow the [Installing](install.md) page for instructions on how to install on your platform. Breez SDK is available in several platforms. Follow the [Installing](install.md) page for instructions on how to install on your platform.
<custom-tabs category="lang">
<div slot="title">Rust</div>
<section>
The first step is to register a new node. In order to do that a seed is needed.
## Registering a new node ## Registering a new node
```rust,no_run ```rust,no_run
let seed = <your seed>; let seed = <your seed>;
@@ -62,4 +67,138 @@ if let Some(node_state) = sdk.node_info()? {
let balance_onchain = node_state.onchain_balance_msat; let balance_onchain = node_state.onchain_balance_msat;
} }
``` ```
</section>
<div slot="title">Swift</div>
<section>
The first step is to register a new node
## Registering a new node
```swift
do {
let seed = try mnemonicToSeed(phrase: "<mnemonics words>")
let inviteCode = ""
// register_node takes either greenlight credentials (certifate & key) or invite code.
// At this example we are using the invite code option.
let credentials = try registerNode(network: Network.bitcoin, seed: seed, registerCredentials: nil, inviteCode: inviteCode)
} catch {
// handle error
}
```
## Recovering an existing node
```swift
do {
let seed = try mnemonicToSeed(phrase: "<mnemonics words>")
let credentials = try recoverNode(network: Network.bitcoin, seed: seed)
} catch {
// handle error
}
```
Once the credentials are retrieved they should be saved in a secured storage.
The next step is to initialize the SDK and start the node:
## Initializing the SDK
```swift
// SDK events listener
class SDKListener: EventListener {
func onEvent(e: BreezEvent) {
print("received event ", e)
}
}
// Create the default config
var config = defaultConfig(envType: EnvironmentType.production)
// Customize the config object according to your needs
config.apiKey = "your API key";
config.workingDir = "path to an existing directory";
do {
let sdk = try initServices(config: config, seed: seed, creds: credentials, listener: SDKListener());
try sdk.start();
} catch{
// handle error
}
```
At any point we can fetch our balance from the Greenlight node:
```swift
do {
let nodeInfo = try sdk.nodeInfo()
let lnBalance = nodeInfo?.channelsBalanceMsat
let onchainBalance = nodeInfo?.onchainBalanceMsat
} catch {
// handle error
}
```
</section>
<div slot="title">React Native</div>
<section>
The first step is to register a new node
## Registering a new node
```typescript
try {
const seed = await mnemonicToSeed("<mnemonics words>");
const invite_code = "<your greenlight invite code>";
// register_node takes either greenlight credentials (certifate & key) or invite code.
// At this example we are using the invite code option.
const credentials = await registerNode(Network.BITCOIN, seed, inviteCode);
} catch (error) {
console.log(error)
}
```
## Recovering an existing node
```typescript
const seed = await mnemonicToSeed("<mnemonics words>");
const credentials = await recoverNode(Network.BITCOIN, seed);
```
Once the credentials are retrieved they should be saved in a secured storage.
The next step is to initialize the SDK and start the node:
## Initializing the SDK
```typescript
// SDK events listener
addEventListener((type, data) => {
console.log(`received event ${type}`);
})
// Create the default config
let config = defaultConfig(EnvironmentType.PRODUCTION)
// Customize the config object according to your needs
config.apiKey = "your API key";
config.workingDir = "path to an existing directory";
try {
const sdkServices = await initServices(config, credentials.deviceKey, credentials.deviceCert, seed);
await start();
} catch (error) {
console.log(error);
}
```
At any point we can fetch our balance from the Greenlight node:
```typescript
try {
const nodeInfo = await nodeInfo();
const lnBalance = nodeInfo.channelsBalanceMsat;
const onchainBalance = nodeInfo.onchainBalanceMsat;
} catch (error) {
console.log(error);
}
```
</section>
</custom-tabs>
You are now ready to receive a Lightning [payment](payments.md). You are now ready to receive a Lightning [payment](payments.md).

View File

@@ -1,6 +1,9 @@
# LNURL-Auth # LNURL-Auth
## Usage ## Usage
<custom-tabs category="lang">
<div slot="title">Rust</div>
<section>
```rust,no_run ```rust,no_run
// Endpoint can also be of the form: // Endpoint can also be of the form:
@@ -21,7 +24,57 @@ if let Ok(LnUrlAuth{data: ad}) = parse(lnurl_auth_url).await {
} }
} }
``` ```
</section>
<div slot="title">Swift</div>
<section>
```swift
// Endpoint can also be of the form:
// keyauth://domain.com/auth?key=val
let lnurlAuthUrl = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttvdankjm3lw3skw0tvdankjm3xdvcn6vtp8q6n2dfsx5mrjwtrxdjnqvtzv56rzcnyv3jrxv3sxqmkyenrvv6kve3exv6nqdtyv43nqcmzvdsnvdrzx33rsenxx5unqc3cxgeqgntfgu"
do {
let inputType = try parseInput(s: lnurlAuthUrl)
if case .lnUrlAuth(let data) = inputType {
let result = try sdk.lnurlAuth(reqData: data)
switch result {
case .ok:
print("Successfully authenticated")
case .errorStatus(let data):
print("Failed to authenticate")
}
}
} catch {
// handle error
}
```
</section>
<div slot="title">React Native</div>
<section>
```typescript
// Endpoint can also be of the form:
// keyauth://domain.com/auth?key=val
let lnurlAuthUrl = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttvdankjm3lw3skw0tvdankjm3xdvcn6vtp8q6n2dfsx5mrjwtrxdjnqvtzv56rzcnyv3jrxv3sxqmkyenrvv6kve3exv6nqdtyv43nqcmzvdsnvdrzx33rsenxx5unqc3cxgeqgntfgu";
try {
const input = await parseInput(lnurlAuthUrl)
if (input.type === InputType.LNURL_AUTH) {
const result = await lnurlAuth(input.data)
if (result.status === "ok") {
print("Successfully authenticated")
} else {
print("Failed to authenticate")
}
}
} catch (error) {
console.log(error)
}
```
</section>
</custom-tab>
## Supported Specs ## Supported Specs

View File

@@ -2,6 +2,10 @@
## Usage ## Usage
<custom-tabs category="lang">
<div slot="title">Rust</div>
<section>
```rust,no_run ```rust,no_run
// Endpoint can also be of the form: // Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val // lnurlp://domain.com/lnurl-pay?key=val
@@ -17,6 +21,47 @@ if let Ok(LnUrlPay{data: pd}) = parse(lnurl_pay_url).await {
} }
``` ```
</section>
<div slot="title">Swift</div>
<section>
```swift
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
let lnurlPayUrl = "lightning@address.com";
do {
let inputType = try parseInput(s: lnurlPayUrl)
if case .lnUrlPay(let data) = inputType {
let amountSats = data.minSendable;
try sdk.payLnurl(reqData: data, amountSats: amountSats, comment: "comment")
}
} catch {
// handle error
}
```
</section>
<div slot="title">React Native</div>
<section>
```typescript
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
let lnurlPayUrl = "lightning@address.com";
try {
const input = await parseInput(lnurlAuthUrl)
if (input.type === InputType.LNURL_PAY) {
const amountSats = input.minSendable;
const result = await payLnurl(input.data, amountSats, "comment")
}
} catch (error) {
console.log(error)
}
```
</section>
</custom-tab>
## Supported Specs ## Supported Specs

View File

@@ -3,6 +3,10 @@
## Usage ## Usage
<custom-tabs category="lang">
<div slot="title">Rust</div>
<section>
```rust,no_run ```rust,no_run
// Endpoint can also be of the form: // Endpoint can also be of the form:
// lnurlw://domain.com/lnurl-withdraw?key=val // lnurlw://domain.com/lnurl-withdraw?key=val
@@ -16,7 +20,48 @@ if let Ok(LnUrlWithdraw{data: wd}) = parse(lnurl_withdraw_url).await {
sdk.lnurl_withdraw(wd, amount_msat, Some(description)).await?; sdk.lnurl_withdraw(wd, amount_msat, Some(description)).await?;
} }
``` ```
</section>
<div slot="title">Swift</div>
<section>
```swift
// Endpoint can also be of the form:
// lnurlw://domain.com/lnurl-withdraw?key=val
let lnurlWithdrawUrl = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4exctthd96xserjv9mn7um9wdekjmmw843xxwpexdnxzen9vgunsvfexq6rvdecx93rgdmyxcuxverrvcursenpxvukzv3c8qunsdecx33nzwpnvg6ryc3hv93nzvecxgcxgwp3h33lxk"
do {
let inputType = try parseInput(s: lnurlWithdrawUrl)
if case .lnUrlWithdraw(let data) = inputType {
let amountSat = data.minWithdrawable
let description = "Test withdraw"
try sdk.withdrawLnurl(reqData: data, amountSats: amountSat, description: "comment")
}
} catch {
// handle error
}
```
</section>
<div slot="title">React Native</div>
<section>
```typescript
// Endpoint can also be of the form:
// lnurlw://domain.com/lnurl-withdraw?key=val
let lnurlWithdrawUrl = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4exctthd96xserjv9mn7um9wdekjmmw843xxwpexdnxzen9vgunsvfexq6rvdecx93rgdmyxcuxverrvcursenpxvukzv3c8qunsdecx33nzwpnvg6ryc3hv93nzvecxgcxgwp3h33lxk";
try {
const input = await parseInput(lnurlAuthUrl)
if (input.type === InputType.LNURL_WITHDRAW) {
const amountSats = input.minWithdrawable;
const result = await withdrawLnurl(input.data, amountSats, "comment")
}
} catch (error) {
console.log(error)
}
```
</section>
</custom-tab>
## Supported Specs ## Supported Specs

View File

@@ -1,5 +1,8 @@
# Sending and receiving Lightning payments # Sending and receiving Lightning payments
<custom-tabs category="lang">
<div slot="title">Rust</div>
<section>
## Receiving Lightning Payments ## Receiving Lightning Payments
Breez SDK doesn't require you to open a channel and set up your inbound liquidity. Breez SDK doesn't require you to open a channel and set up your inbound liquidity.
Breez SDK automatically connects your node to the LSP peer and you can now receive payments: Breez SDK automatically connects your node to the LSP peer and you can now receive payments:
@@ -20,3 +23,73 @@ let node_id = "...";
sdk.send_payment(node_id.into(), Some(3000)).await?; sdk.send_payment(node_id.into(), Some(3000)).await?;
``` ```
</section>
<div slot="title">Swift</div>
<section>
## Receiving Lightning Payments
Breez SDK doesn't require you to open a channel and set up your inbound liquidity.
Breez SDK automatically connects your node to the LSP peer and you can now receive payments:
```swift
do {
let invoice = try sdk.receivePayment(amountSats: 3000, description: "Invoice for 3000 sats")
} catch {
// handle error
}
```
## Sending Lightning Payments
```swift
let bolt11 = "...";
do {
let payment = try sdk.sendPayment(bolt11: bolt11, amountSats: 3000)
} catch {
// handle error
}
```
## Sending Spontaneous Lightning Payments
```swift
let nodeId = "...";
do {
let payment = try sdk.sendSpontaneousPayment(nodeId: nodeId, amountSats: 3000)
} catch {
// handle error
}
```
</section>
<div slot="title">React Native</div>
<section>
## Receiving Lightning Payments
Breez SDK doesn't require you to open a channel and set up your inbound liquidity.
Breez SDK automatically connects your node to the LSP peer and you can now receive payments:
```typescript
try {
const invoice = await receivePayment(3000, "Invoice for 3000 sats")
} catch (error) {
console.log(error)
}
```
## Sending Lightning Payments
```typescript
const bolt11 = "...";
try {
const payment = await sendPayment(bolt11, 3000)
} catch (error) {
console.log(error)
}
```
## Sending Spontaneous Lightning Payments
```typescript
const nodeId = "...";
try {
const payment = await sendSpontaneousPayment(nodeId, 3000)
} catch (error) {
console.log(error)
}
```
</section>
</custom-tabs>

View File

@@ -1,10 +1,14 @@
# Receiving an on-chain transaction (swap-in) # Receiving an on-chain transaction (swap-in)
There are cases when you have funds in some bitcoin address and you would like to send those to your lightning node. There are cases when you have funds in some bitcoin address and you would like to send those to your lightning node.
<custom-tabs category="lang">
<div slot="title">Rust</div>
<section>
```rust,no_run ```rust,no_run
let swap_info = sdk.receive_onchain().await?; let swap_info = sdk.receive_onchain().await?;
// Send your funds to the bellow bitcoin address // Send your funds to the below bitcoin address
let address = swap_info.bitcoin_address; let address = swap_info.bitcoin_address;
``` ```
@@ -29,6 +33,114 @@ Once you have a refundable swap in hand, use the follwing code to execute a refu
```rust,no_run ```rust,no_run
let destination_address = "...".into() let destination_address = "...".into()
let sat_per_byte = <efund tx fee rate> let sat_per_vbyte = <refund tx fee rate>
sdk.refund(refundable.bitcoin_address, destination_address, sat_per_byte).await? sdk.refund(refundable.bitcoin_address, destination_address, sat_per_vbyte).await?
``` ```
</section>
<div slot="title">Swift</div>
<section>
```swift
do {
let swapInfo = try sdk.receiveOnchain()
// Send your funds to the bellow bitcoin address
let address = swapInfo.bitcoinAddress;
} catch {
// handle error
}
```
Once you've sent the funds to the above address, the SDK will monitor this address for unspent confirmed outputs and use a trustless submarine swap to receive these into your Lightning node. You can always monitor the status of the current in-progress swap using the following code:
```swift
do {
let swapInfo = try sdk.inProgressSwap()
} catch {
// handle error
}
```
The process of receiving funds via an on-chain address is trustless and uses a submarine swap. This means there are two ways to spend the sent funds:
1. Either by a preimage that is exposed when the Lightning payment is completed - this is the positive case where the swap was successful.
2. Or by your node when the swap didn't complete within a certain timeout - this is the negative case where your node will execute a refund.
In order to execute a refund, you need to supply an on-chain address to where the refunded amount will be sent. The following code will retrieve the refundable swaps:
```swift
do {
let refundables = try sdk.listRefundables()
} catch {
// handle error
}
```
Once you have a refundable swap in hand, use the follwing code to execute a refund:
```swift
let destinationAddress = "..."
let satPerVbyte = <refund tx fee rate>
do {
try sdk.refund(
swapAddress: "",
toAddress: destinationAddress,
satPerVbyte: satPerVbyte)
} catch {
// handle error
}
```
</section>
<div slot="title">React Native</div>
<section>
```typescript
try {
const swapInfo = await receiveOnchain();
// Send your funds to the below bitcoin address
const address = swapInfo.bitcoinAddress;
} catch (error) {
console.log(error)
}
```
Once you've sent the funds to the above address, the SDK will monitor this address for unspent confirmed outputs and use a trustless submarine swap to receive these into your Lightning node. You can always monitor the status of the current in-progress swap using the following code:
```typescript
try {
const swapInfo = await inProgressSwap()
} catch (error) {
console.log(error)
}
```
The process of receiving funds via an on-chain address is trustless and uses a submarine swap. This means there are two ways to spend the sent funds:
1. Either by a preimage that is exposed when the Lightning payment is completed - this is the positive case where the swap was successful.
2. Or by your node when the swap didn't complete within a certain timeout - this is the negative case where your node will execute a refund.
In order to execute a refund, you need to supply an on-chain address to where the refunded amount will be sent. The following code will retrieve the refundable swaps:
```typescript
try {
const refundables = await listRefundables()
} catch (error) {
console.log(error)
}
```
Once you have a refundable swap in hand, use the follwing code to execute a refund:
```typescript
const destinationAddress = "..."
const satPerVbyte = <refund tx fee rate>
try {
const result = await refund(refundable.bitcoinAddress, destinationAddress, satPerVbyte)
} catch (error) {
console.log(error)
}
```
</section>
</custom-tabs>

View File

@@ -4,6 +4,10 @@ You can send funds from the Breez SDK wallet to an on-chain address as follows.
First, fetch the current reverse swap fees: First, fetch the current reverse swap fees:
<custom-tabs category="lang">
<div slot="title">Rust</div>
<section>
```rust,no_run ```rust,no_run
let current_fees = sdk.fetch_reverse_swap_fees().await?; let current_fees = sdk.fetch_reverse_swap_fees().await?;
@@ -43,7 +47,115 @@ for rs in sdk.in_progress_reverse_swaps().await? {
info!("Reverse swap {} in progress, status is {}", rs.id, rs.breez_status); info!("Reverse swap {} in progress, status is {}", rs.id, rs.breez_status);
} }
``` ```
</section>
<div slot="title">Swift</div>
<section>
```swift
try {
let currentFees = try sdk.fetchReverseSwapFees()
println("Percentage fee for the reverse swap service: \(currentFees.feesPercentage)")
println("Estimated miner fees in sats for locking up funds: \(currentFees.feesLockup)")
println("Estimated miner fees in sats for claiming funds: \(currentFees.feesClaim)")
} catch {
// handle error
}
```
The reverse swap will involve two on-chain transactions, for which the mining fees can only be estimated. They will happen
automatically once the process is started, but the last two values above are these estimates to help you get a picture
of the total costs.
Fetching the fees also tells you what is the range of amounts you can send:
```swift
println("Minimum amount, in sats: \(current_fees.min)")
println("Maximum amount, in sats: \(current_fees.max)")
```
Once you checked the fees are acceptable, you can start the reverse swap:
```swift
let destinationAddress = "bc1.."
let amountSat = currentFees.min
let satPerVbyte = <fee rate>
try {
try sdk.sendOnchain(amountSat: amountSat, onchainRecipientAddress: destinationAddress, pairHash: currentFees.feesHash, satPerVbyte: satPerVbyte)
} catch {
// handle error
}
```
Starting the reverse swap will trigger a HODL invoice payment, which will only be settled if the entire swap completes.
This means you will see an outgoing pending payment in your list of payments, which locks those funds until the invoice
is either settled or cancelled. This will happen automatically at the end of the reverse swap.
You can check its status with:
```swift
for rs in sdk.inProgressReverseSwaps() {
println("Reverse swap \(rs.id) in progress, status is \(rs.breezStatus)")
}
```
</section>
<div slot="title">React Native</div>
<section>
```typescript
try {
const currentFees = await fetchReverseSwapFees()
console.log(`Percentage fee for the reverse swap service: ${currentFees.feesPercentage}`);
console.log(`Estimated miner fees in sats for locking up funds: ${currentFees.feesLockup}`);
console.log(`Estimated miner fees in sats for claiming funds: ${currentFees.feesClaim}`);
} catch (error) {
console.log(error)
}
```
The reverse swap will involve two on-chain transactions, for which the mining fees can only be estimated. They will happen
automatically once the process is started, but the last two values above are these estimates to help you get a picture
of the total costs.
Fetching the fees also tells you what is the range of amounts you can send:
```typescript
console.log(`Minimum amount, in sats: ${current_fees.min}`);
console.log(`Maximum amount, in sats: ${current_fees.max}`);
```
Once you checked the fees are acceptable, you can start the reverse swap:
```typescript
const destinationAddress = "bc1..";
const amountSat = currentFees.min;
const satPerVbyte = <fee rate>
try {
const reverseSwapInfo = sendOnchain(amountSat, destinationAddress, currentFees.feesHash, satPerVbyte)
} catch (error) {
console.log(error)
}
```
Starting the reverse swap will trigger a HODL invoice payment, which will only be settled if the entire swap completes.
This means you will see an outgoing pending payment in your list of payments, which locks those funds until the invoice
is either settled or cancelled. This will happen automatically at the end of the reverse swap.
You can check its status with:
```typescript
try {
const swaps = await inProgressReverseSwaps()
for (const swap in swaps) {
println(`Reverse swap ${swap.id} in progress, status is ${swap.breezStatus}`);
}
} catch (error) {
console.log(error)
}
```
</section>
</custom-tabs>
If the reverse swap is successful, you'll get the on-chain payment on your destination address and the HODL invoice will If the reverse swap is successful, you'll get the on-chain payment on your destination address and the HODL invoice will
change from pending to settled. change from pending to settled.

147
tabs.js Normal file
View File

@@ -0,0 +1,147 @@
(function () {
'use strict';
let selected_ = null;
customElements.define('custom-tabs', class extends HTMLElement {
constructor() {
super(); // always call super() first in the ctor.
// Create shadow DOM for the component.
let shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
contain: content;
border: 1px solid var(--quote-border);
border-radius: 8px;
width: 100%;
}
#panels {
padding: 10px;
}
#tabs {
border-bottom: 1px solid var(--quote-border);
background-color: var(--sidebar-bg);
}
#tabs slot {
display: inline-flex; /* Safari bug. Treats <slot> as a parent */
}
#tabs ::slotted(*) {
color: var(--sidebar-fg);
padding: 16px 8px;
margin: 0;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
border-top-left-radius: 8px;
border-top-right-radius: 3px;
border: none; /* if the user users a <button> */
}
#tabs ::slotted([tabindex="0"]), #tabs ::slotted(*:hover) {
color: var(--sidebar-active);
}
#panels ::slotted([aria-hidden="true"]) {
display: none;
}
pre {
margin: 0;
}
</style>
<div id="tabs">
<slot id="tabsSlot" name="title"></slot>
</div>
<div id="panels">
<slot id="panelsSlot"></slot>
</div>
`;
}
get selected() {
return selected_;
}
set selected(idx) {
selected_ = idx;
this._selectTab(idx);
this.setAttribute('selected', idx);
}
connectedCallback() {
this.setAttribute('role', 'tablist');
const tabsSlot = this.shadowRoot.querySelector('#tabsSlot');
const panelsSlot = this.shadowRoot.querySelector('#panelsSlot');
this.tabs = tabsSlot.assignedNodes({ flatten: true });
this.panels = panelsSlot.assignedNodes({ flatten: true }).filter(el => {
return el.nodeType === Node.ELEMENT_NODE;
});
// Save refer to we can remove listeners later.
this._boundOnTitleClick = this._onTitleClick.bind(this);
tabsSlot.addEventListener('click', this._boundOnTitleClick);
this.selected = this._findFirstSelectedTab() || this._findStoredSelectedTab() || 0;
}
disconnectedCallback() {
const tabsSlot = this.shadowRoot.querySelector('#tabsSlot');
tabsSlot.removeEventListener('click', this._boundOnTitleClick);
}
_onTitleClick(e) {
if (e.target.slot === 'title') {
this.selected = this.tabs.indexOf(e.target);
e.target.focus();
}
}
_findFirstSelectedTab() {
let selectedIdx;
for (let [i, tab] of this.tabs.entries()) {
tab.setAttribute('role', 'tab');
if (tab.hasAttribute('selected')) {
selectedIdx = i;
}
}
return selectedIdx;
}
_findStoredSelectedTab() {
let selectedIdx;
if (this.getAttribute("category")) {
let selectedText;
try { selectedText = localStorage.getItem('mdbook-tabs-' + this.getAttribute("category")); } catch (e) { }
if (selectedText) {
for (let [i, tab] of this.tabs.entries()) {
if (tab.textContent === selectedText) {
selectedIdx = i;
break;
}
}
}
}
return selectedIdx;
}
_selectTab(idx = null) {
for (let i = 0, tab; tab = this.tabs[i]; ++i) {
let select = i === idx;
tab.setAttribute('tabindex', select ? 0 : -1);
tab.setAttribute('aria-selected', select);
this.panels[i].setAttribute('aria-hidden', !select);
if (select && this.getAttribute("category") && tab.textContent) {
try { localStorage.setItem('mdbook-tabs-' + this.getAttribute("category"), tab.textContent); } catch (e) { }
}
}
}
});
})();