diff --git a/.github/workflows/deploy-docs.yaml b/.github/workflows/deploy-docs.yaml new file mode 100644 index 000000000..0c496f513 --- /dev/null +++ b/.github/workflows/deploy-docs.yaml @@ -0,0 +1,57 @@ +name: Deploy Documentation to GitHub Pages + +on: + push: + branches: + - main + paths: + - 'docs/**' + - '.github/workflows/deploy-docs.yaml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build-and-deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Mintlify CLI + run: npm install -g mintlify@latest + + - name: Build documentation + run: | + cd docs + mintlify build + # Ensure .nojekyll is present for GitHub Pages + touch .mintlify/dist/.nojekyll + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './docs/.mintlify/dist' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + diff --git a/.github/workflows/ext-check.yml b/.github/workflows/ext-check.yml new file mode 100644 index 000000000..16bae79aa --- /dev/null +++ b/.github/workflows/ext-check.yml @@ -0,0 +1,18 @@ +name: Extensions Check + +on: + pull_request: + branches: [ "*" ] + +jobs: + ext-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - run: npm ci + - run: npm run ext:check + + diff --git a/.gitignore b/.gitignore index e5194a0db..7b55d93dd 100644 --- a/.gitignore +++ b/.gitignore @@ -106,7 +106,28 @@ external/superhero-ui-vue-for-reference/node_modules/ # Banner suggestions (design mockups) banner-suggestions/ -# Python virtual environment for docs +# --- Python docs tooling --- +# Local virtual environments +.venv/ +.venv-*/ +venv/ +venv-*/ .venv-docs/ +docs/.venv/ +docs/venv/ + +# Documentation build outputs (legacy MkDocs - kept for reference) +site/ +docs/site/ +site-docs/ + +# Mintlify build artifacts (generated by `mintlify build`) +.mintlify/ +docs/.mintlify/ + +# Python caches +__pycache__/ +*.pyc +.pytest_cache/ diff --git a/README.md b/README.md index abc8f3d2d..e4c22a96b 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,15 @@ This app is a static site once built. You can deploy the `dist/` directory to an - Netlify configuration is included: `netlify.toml` and `public/_redirects` for SPA routing - Typical build settings: build command `npm run build`, publish directory `dist` +## Extending with Plugins + +See `docs/plugin-sdk.md` for the public plugin SDK. It covers: + +- Feed plugins (new entry kinds) +- Composer actions and attachments (inline panels such as Poll) +- Item actions, routes and modals +- Runtime loading and capability gating + ## Contributing 1. Create a feature branch from `main` diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..b6f5a95c9 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,73 @@ +# Superhero Documentation + +**Live Documentation**: [docs.superhero.com](https://docs.superhero.com) + +This directory contains the source files for the Superhero plugin development documentation, built with [Mintlify](https://mintlify.com). + +## Run Documentation Locally + +To preview the documentation locally during development: + +```bash +# Install Mintlify CLI globally (requires Node.js v19+) +npm i -g mintlify + +# Start local development server +cd docs +mintlify dev --port 3002 +``` + +The documentation will be available at `http://localhost:3002`. + +### Build Static Files + +To build the documentation for production: + +```bash +cd docs +mintlify build +``` + +The static files will be generated in `docs/.mintlify/dist/`. + +## Deployment + +The documentation is automatically deployed to GitHub Pages at `docs.superhero.com` when changes are pushed to the `main` branch. + +### Automatic Deployment + +The GitHub Actions workflow (`.github/workflows/deploy-docs.yaml`) automatically: +- Builds the documentation using Mintlify +- Deploys to GitHub Pages on pushes to `main` that affect the `docs/` directory +- Supports manual deployment via Actions → "Deploy Documentation to GitHub Pages" → Run workflow + +### GitHub Pages Setup + +To enable GitHub Pages deployment: + +1. **Enable GitHub Pages** in repository settings: + - Go to Settings → Pages + - Source: GitHub Actions + - Custom domain: `docs.superhero.com` (optional) + +2. **Configure Custom Domain** (if using `docs.superhero.com`): + - Add a CNAME record pointing `docs.superhero.com` to `your-org.github.io` + - Or add an A record pointing to GitHub Pages IP addresses + - Enable "Enforce HTTPS" in GitHub Pages settings + +## Documentation Structure + +- `mint.json` - Mintlify configuration (navigation, branding, etc.) +- `tutorials/hackathon/` - Plugin development tutorials and guides +- `plugin-sdk.md` - Plugin SDK API reference +- `index.md` - Main documentation landing page + +## Contributing + +When contributing to the documentation: + +1. Edit the Markdown files in this directory +2. Test locally using `mintlify dev --port 3002` +3. Commit changes to `main` branch to trigger automatic deployment +4. Verify deployment at `docs.superhero.com` + diff --git a/docs/_archive/EXTENSIONS.md b/docs/_archive/EXTENSIONS.md new file mode 100644 index 000000000..c36240187 --- /dev/null +++ b/docs/_archive/EXTENSIONS.md @@ -0,0 +1,79 @@ +# Superhero App Extensions + +> [!NOTE] +> For a step-by-step walkthrough, see [How to build a Superhero App Extension (Polls example)](./tutorials/build-governance-poll-extension.md). + +Extensions are first-class modules that can add routes, menu items, feed entries, composer actions, item actions, modals, and composer attachments. They are authored with the existing plugin SDK and loaded at runtime. + +## Capabilities +- routes: add pages (mounted dynamically via routeRegistry) +- menu: add header navigation items (merged via navRegistry) +- feed: contribute feed entries and renderers +- composer: add composer actions and attachments +- item-actions: add per-feed-item actions +- modals: register modal components + +## Authoring an extension +Minimal example: +```tsx +import React from 'react'; +import { definePlugin } from '@/plugin-sdk'; +import MyApp from './ui/MyApp'; + +export default definePlugin({ + meta: { + id: 'my-ext', + name: 'My Extension', + version: '0.1.0', + apiVersion: '1.x', + capabilities: ['routes', 'menu', 'composer'], + }, + setup({ register }) { + register({ + routes: [{ path: '/my-ext', element: }], + menu: [{ id: 'my-ext', label: 'My Ext', path: '/my-ext', icon: '🧩' }], + // attachments: () => [myAttachmentSpec], + }); + }, +}); +``` + +## Loading modes +- Local (first‑party): register in `src/plugins/local.ts` for development. Not enabled by default in production. +- External (remote URLs): `CONFIG.PLUGINS` can point to remote modules. The loader enforces the `capabilities` allowlist. + +## Capability allowlist +- Use `CONFIG.PLUGIN_CAPABILITIES_ALLOWLIST` to restrict what plugins may register, e.g. `['routes','feed','composer']`. + +## Registries lifecycle +- Registries live in `features/social/plugins/registries.ts`. +- In development, local loader resets registries on hot reload to avoid duplicates. +- Plugin bootstrap is guarded to run once per app lifecycle. + +## Contracts +Use `createContractLoader(sdk)` from `src/extensions/contract.ts` to load ACI-based contract instances. + +## Backend integration +Point your extension to backend services. For Social Superhero governance backend, see: +- repo: https://github.com/superhero-com/superhero-api +- flow: index chain data → expose REST/WebSocket → consume from extension client + +## Environment & config +- `public/superconfig.json` (or `window.__SUPERCONFIG__`) provides runtime keys like `NODE_URL`, `MIDDLEWARE_URL`, `GOVERNANCE_API_URL`, `PLUGINS`, `PLUGIN_CAPABILITIES_ALLOWLIST`. + +## CI +- `npm run ext:check` validates extension modules. The CI workflow runs it on pull requests. + +## Security & UX notes +- Keep capabilities minimal; prefer read-only routes for untrusted plugins. +- Gating composer/actions behind the allowlist reduces risk. +- Validate inputs and handle network failures gracefully. + +## Troubleshooting +- Duplicate nav or routes: ensure bootstrap runs once; in dev registries are reset before loading local plugins. +- Wallet not connected: ensure wallet connect flow completes before signing. +- ACI mismatch: recompile and update contract address. + +## Full tutorial (Polls example) +See the detailed guide: +`docs/tutorials/build-governance-poll-extension.md` diff --git a/docs/_archive/backend-integration.md b/docs/_archive/backend-integration.md new file mode 100644 index 000000000..27666d56a --- /dev/null +++ b/docs/_archive/backend-integration.md @@ -0,0 +1,26 @@ +# Backend Integration for App Extensions + +This guide explains how to integrate an extension with a backend service. For Social Superhero, we reference the `superhero-api` repository. + +## When to use a backend +- Indexing historical data and building feeds +- Search and rich queries +- WebSocket push updates +- Off-chain validation + +## Setup superhero-api +- Repo: https://github.com/superhero-com/superhero-api +- Quick start (Docker): see repository README +- Configure ENV in frontend: `VITE_EXT__API_URL` + +## Client in extension +Create a small client under `src/plugins//client/backend.ts` and read the base URL from `VITE_EXT__API_URL` or runtime `__SUPERCONFIG__`. + +## Patterns +- Pagination tokens instead of page numbers +- Idempotent POSTs, signed requests where needed +- WebSocket channels for live updates + +## Error handling +- Map HTTP errors to user-friendly messages +- Retry with backoff diff --git a/docs/_archive/build-governance-poll-extension.md b/docs/_archive/build-governance-poll-extension.md new file mode 100644 index 000000000..9198bd495 --- /dev/null +++ b/docs/_archive/build-governance-poll-extension.md @@ -0,0 +1,130 @@ +# How to build a Superhero App Extension (Polls example) + +This guide shows how to build a full Superhero App Extension using the Governance Polls example. You will add a feed plugin that renders polls, wire voting/revoking via Sophia contracts, add a composer attachment, integrate the Governance backend, and test locally. Follow the steps in order. + +See the [Extensions overview](../EXTENSIONS.md) for capabilities, loading modes, configuration and CI checks. + +## Prerequisites +- Node 18+, pnpm +- Funded Aeternity testnet account (for signing votes) +- Superhero dev environment and wallet +- Endpoints (confirm or adjust in runtime config): + - NODE_URL, MIDDLEWARE_URL, AE_COMPILER_URL + - GOVERNANCE_API_URL + +## 1) Project setup +1. Clone the repo and install dependencies. +2. Start the dev server. +3. Review runtime config (`public/superconfig.json` or `window.__SUPERCONFIG__`) and ensure Governance and MDW endpoints are set. + +## 2) Scaffold an extension +Use the scaffold script to generate a minimal plugin. +```bash +pnpm run ext:scaffold governance-polls +``` +This creates `src/plugins/governance-polls/index.tsx` and `src/plugins/governance-polls/ui/App.tsx`. + +## 3) Add capabilities and registration +In `src/plugins/governance-polls/index.tsx`: +- Set `capabilities: ['feed', 'composer']` +- Register `attachments: () => [pollAttachmentSpec]` +- Optionally add a route and menu for discovery + +## 4) Implement the feed plugin +- Create a `PollCreatedEntryData` type and a mapper `adaptPollToEntry`. +- Implement `fetchPage(page)` that: + - Queries open and closed polls via `GovernanceApi.getPollOrdering(false/true)` + - For each poll, fetch creation time via MDW (contract create tx) and set `createdAt` on entries + - Sort entries by `createdAt` descending +- Render entries using `PollCreatedCard` and `FeedRenderer`. + +## 5) Wire voting flows (Sophia contract integration) +- Use `useAeSdk()` to get `sdk` and ensure wallet is connected before voting. +- Load the poll contract with `GovernancePollACI.json` and call `vote(option)`. +- Optimistically update UI (increment selected option, update totals) and refresh from backend. +- Implement `revoke_vote()` similarly and update UI accordingly. + +## 6) Add composer attachment (create polls) +- Import `pollAttachmentSpec` from `features/social/feed-plugins/poll-attachment` and register via `attachments: () => [pollAttachmentSpec]`. +- This surfaces a poll creation UI in the composer toolbar; after posting, it runs its `onAfterPost` hook. + +## 6.5) Add translations (optional) +Plugins can include their own translation files for internationalization: + +1. Create `locales/` directory in your plugin: + ```bash + mkdir -p src/plugins/governance-polls/locales + ``` + +2. Create `locales/en.json` with your translation keys: + ```json + { + "createdAPoll": "created a poll", + "pending": "Pending…", + "yourVote": "Your vote", + "retractVote": "Retract vote", + "votes": "votes" + } + ``` + +3. Create `locales/index.ts` to export translations: + ```typescript + import en from './en.json'; + export const translations = { en }; + ``` + +4. Import and export translations in your plugin definition: + ```typescript + import { translations } from './locales'; + + export default definePlugin({ + meta: { id: 'governance-polls', ... }, + translations, // Export translations + setup({ register }) { ... } + }); + ``` + +5. Use translations in your components: + ```typescript + import { useTranslation } from 'react-i18next'; + + const { t } = useTranslation('governance-polls'); // Use plugin ID as namespace + return {t('createdAPoll')}; + ``` + +See [Plugin SDK docs](../plugin-sdk.md#translations) for more details. + +## 7) Local registration (dev only) +- Temporarily import your new plugin in `src/plugins/local.ts` and add to the `localPlugins` array for local testing. +- Do not commit this if you don’t want it enabled by default; guard with an env flag if needed. + +## 8) Testing +- Load the app, ensure feed renders poll cards. +- Click an option to vote; confirm signing flow and UI updates. +- Revoke vote; confirm UI updates and backend reflects the change after refresh. + +## 9) Backend integration notes +- Governance endpoints supply ordering, overviews and accept event submissions to speed up cache updates. +- You can change the base URL via runtime config. For advanced cases, add a tiny client wrapper (`src/plugins/governance-polls/client/backend.ts`). +- Consider WebSocket updates or polling intervals for faster UI convergence. + +## 10) Troubleshooting +- Wallet not connected: ensure `useWalletConnect` flow and wallet pairing works +- Compiler/runtime mismatch: confirm `AE_COMPILER_URL` and SDK versions +- ACI mismatch or wrong contract address: recompile and point to correct address +- MDW differences: different deployments may expose slightly different transaction shapes; handle both `micro_time` and `block_time` + +## 11) Submission checklist +- Extension compiles and passes `pnpm run ext:check` +- Polls render; voting flows work +- Composer poll attachment shows and works + +## 12) Submit your extension (Fork + PR) +To contribute your extension to Superhero: +1. Fork the repository to your GitHub account. +2. Create a feature branch (e.g., `feat/polls-extension`). +3. Add your plugin module under `src/plugins//` and any supporting files. +4. Ensure local testing is done via `src/plugins/local.ts` (do not auto-register in production by default). +5. Run `pnpm run ext:check` and fix any validation issues. +6. Open a Pull Request from your fork/branch to this repository with a brief description and screenshots. +7. Our CI will run the extension checks; address any feedback from reviewers. diff --git a/docs/_archive/build-nft-marketplace-extension.md b/docs/_archive/build-nft-marketplace-extension.md new file mode 100644 index 000000000..c624d17c5 --- /dev/null +++ b/docs/_archive/build-nft-marketplace-extension.md @@ -0,0 +1,37 @@ +# Tutorial: Build an NFT Marketplace App Extension + +This guide walks you through creating a full-stack extension that adds an `/nft` route and integrates with contracts and an optional backend. + +## Prerequisites +- Node 18+, pnpm +- Funded Aeternity testnet account +- Superhero dev env running +- ENV: `VITE_EXT_NFT_API_URL` (optional) + +## 1) Scaffold +```bash +pnpm run ext:scaffold nft-marketplace +``` + +## 2) Register route & menu +See `src/plugins/nft-marketplace/index.tsx`. + +## 3) UI stub +Edit `src/plugins/nft-marketplace/ui/MarketApp.tsx` to render listings. + +## 4) Contracts +- Write `contracts/Marketplace.aes` +- Compile & export ACI via a script +- Load with `createContractLoader(sdk)` + +## 5) Backend integration (optional) +- Add `client/backend.ts` and point to `VITE_EXT_NFT_API_URL` +- Reference backend repo: https://github.com/superhero-com/superhero-api + +## 6) Test & commit +- Run app, navigate to `/nft` +- Conventional commits after each step + +## Troubleshooting +- Compiler mismatch → update compiler URL +- Missing ACI → recompile contract diff --git a/docs/_archive/hackathon-legacy/01-setup-environment.md b/docs/_archive/hackathon-legacy/01-setup-environment.md new file mode 100644 index 000000000..bfbfc1243 --- /dev/null +++ b/docs/_archive/hackathon-legacy/01-setup-environment.md @@ -0,0 +1,35 @@ +# Setup Environment + +This page gets you ready to build and test Sophia contracts and Superhero extensions with Cursor. + +## Install prerequisites +- Node.js LTS (e.g., 20.x) +- Git +- Docker +- Cursor (or VS Code) + +## Create an æternity account +You need a private key for local/devnet and a funded key for testnet. + +- Generate a dev key locally for development tests. +- Obtain testnet funds for a test key via the official faucet (see docs hub for current link). + +Never commit private keys. Use `.env*` files locally only. + +## Project directories +We will use the following folders in later pages: +- `contracts/` — Sophia source files (`.aes`) +- `tests/` — TypeScript tests +- `scripts/` — utility scripts (optional) + +## Recommended global checks +```bash +node -v +npm -v +docker --version +``` + +If Docker is installed, you’ll be able to run the Sophia HTTP compiler locally in the next steps. + +## What’s next +- Move to [02 — Project scaffold](./02-project-scaffold.md) to initialize a workspace, add dependencies, and create `.env` profiles for devnet and testnet. diff --git a/docs/_archive/hackathon-legacy/01a-superhero-wallet-and-account.md b/docs/_archive/hackathon-legacy/01a-superhero-wallet-and-account.md new file mode 100644 index 000000000..1c1ba54b2 --- /dev/null +++ b/docs/_archive/hackathon-legacy/01a-superhero-wallet-and-account.md @@ -0,0 +1,28 @@ +# Superhero Wallet & Account + +!!! note + You'll use Superhero Wallet to hold your æternity account, sign transactions, and connect your mini‑æpp to the Superhero app. + +## Install the wallet +- Chrome: [Superhero Wallet extension](https://chromewebstore.google.com/detail/superhero-wallet/mnhmmkepfddpifjkamaligfeemcbhdne) + +!!! tip + After installing, pin the extension for quick access. + +## Create or import an account +- Choose "Create" to generate a new seed phrase, or "Import" to restore an existing one +- Write down the seed phrase offline; never share it + +!!! important + Back up your seed phrase. Anyone with your seed can control your account. + +## Switch to testnet and fund +- In settings, select æternity testnet +- Use the testnet faucet to fund your account with AE for testing (see Reference page) + +## Connect to your extension +- Your extension will interact with the wallet via the JS SDK and/or Wallet Connect flows where applicable + +## Next +- Continue with [02 — Project scaffold](./02-project-scaffold.md) +- Or jump to [00a — Quickstart](./00a-quickstart.md) diff --git a/docs/_archive/hackathon-legacy/02-project-scaffold.md b/docs/_archive/hackathon-legacy/02-project-scaffold.md new file mode 100644 index 000000000..7dc72324e --- /dev/null +++ b/docs/_archive/hackathon-legacy/02-project-scaffold.md @@ -0,0 +1,49 @@ +# Project Scaffold + +Initialize a minimal workspace that works well with Cursor and supports compiling, deploying, and testing Sophia contracts. + +## Create folders +```bash +mkdir -p contracts tests scripts +``` + +## Initialize package.json and install deps +```bash +npm init -y +npm i @aeternity/aepp-sdk dotenv +npm i -D vitest ts-node typescript @types/node +npx tsc --init +``` + +Add scripts to `package.json`: +```json +{ + "scripts": { + "test": "vitest run", + "test:watch": "vitest" + } +} +``` + +## Environment profiles +Create two files in the project root (do not commit real keys): + +`.env.local` (devnet/local) +``` +NODE_URL=http://localhost:3013 +COMPILER_URL=http://localhost:3080 +SECRET_KEY=your_local_dev_private_key_hex +``` + +`.env.testnet` +``` +NODE_URL=https://testnet.aeternity.io +COMPILER_URL=http://localhost:3080 +SECRET_KEY=your_funded_testnet_private_key_hex +``` + +Switch by exporting variables, or use a small helper script to load one profile before running tests. + +## Next steps +- [03 — Sophia basics for builders (skim)](./03-sophia-basics-for-builders.md) +- [04 — Compiler and build](./04-compiler-and-build.md) diff --git a/docs/_archive/hackathon-legacy/03-sophia-basics-for-builders.md b/docs/_archive/hackathon-legacy/03-sophia-basics-for-builders.md new file mode 100644 index 000000000..f1f0628eb --- /dev/null +++ b/docs/_archive/hackathon-legacy/03-sophia-basics-for-builders.md @@ -0,0 +1,25 @@ +# Sophia Basics for Builders + +You can rely on Cursor to scaffold most code, but read this once to avoid pitfalls. + +## Core concepts +- Contract structure: `contract`, `record state`, `init`, `entrypoint`, `stateful entrypoint` +- Auth: `Call.caller` for owner/role checks; enforce one‑vote‑per‑address +- Time: choose `Chain.height` or timestamp; enforce open/close windows consistently +- Data structures: prefer `Map` for lookups; avoid unbounded loops over growing lists +- Errors: `require`/`abort` with clear messages; fail early +- Events: emit small events for create/vote/close; useful for off‑chain indexing + +## Minimal code patterns +- Guard first, then mutate state +- Validate indices (option bounds) +- Keep return types simple for SDK decoding + +## Where to skim in the docs +- [Syntax](https://github.com/aeternity/aesophia/blob/master/docs/sophia_syntax.md) +- [Features (state, entrypoints, exceptions, events)](https://github.com/aeternity/aesophia/blob/master/docs/sophia_features.md) +- [Stdlib (Map, List, Option, Chain, Call)](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md) + +## Next +- [04 — Compiler and build](./04-compiler-and-build.md) +- [05 — Contract walkthrough: Poll](./05-contract-poll-walkthrough.md) diff --git a/docs/_archive/hackathon-legacy/04-compiler-and-build.md b/docs/_archive/hackathon-legacy/04-compiler-and-build.md new file mode 100644 index 000000000..737e8c9a0 --- /dev/null +++ b/docs/_archive/hackathon-legacy/04-compiler-and-build.md @@ -0,0 +1,24 @@ +# Compiler and Build + +Set up the Sophia HTTP compiler and connect your tests to it. + +## Local compiler (recommended in hackathons) +Run the compiler in Docker: +```bash +docker run --rm -p 3080:3080 aeternity/aesophia_http:latest +``` +This exposes the compiler at `http://localhost:3080`. + +## Remote compiler (optional) +If a public compiler is provided in official docs, set `COMPILER_URL` to that. Otherwise use the local Docker compiler above for reliable results. + +## Pinning versions +In your Sophia sources, use the compiler pragma (example): +``` +@compiler >= 6.5.0 +``` +Adjust to a version supported by your `aesophia_http` image. Check the `aesophia` release notes if compilation fails. + +## Next +- [05 — Contract walkthrough: Poll](./05-contract-poll-walkthrough.md) +- [06 — Testing with Vitest](./06-testing-with-vitest.md) diff --git a/docs/_archive/hackathon-legacy/05-contract-poll-walkthrough.md b/docs/_archive/hackathon-legacy/05-contract-poll-walkthrough.md new file mode 100644 index 000000000..46df33ddc --- /dev/null +++ b/docs/_archive/hackathon-legacy/05-contract-poll-walkthrough.md @@ -0,0 +1,34 @@ +# Contract Walkthrough: Poll + +Design a simple, gas‑aware poll contract newcomers can extend. + +## Data model +- `Poll`: id, question, options, open/close heights, owner, isClosed +- `votesByAddress`: Address → optionIdx (one vote per address) +- `tally`: optionIdx → count (avoid iterating all voters) + +## Entrypoints (suggested) +- `create_poll(question: string, options: list(string), open_h: int, close_h: int)` +- `vote(poll_id: int, option_idx: int)` +- `close(poll_id: int)` +- `get_results(poll_id: int) : list(int)` (or a record) + +## Invariants & guards +- Creation: `options` length ≥ 2; unique options; `open_h < close_h` +- Voting: now in [open_h, close_h); one vote per address; index bounds +- Closing: only owner or after `close_h`; id must exist; idempotent + +## Events +- `PollCreated(poll_id, owner)` +- `Voted(poll_id, voter, option_idx)` +- `Closed(poll_id)` + +Keep events small. Index off‑chain when needed. + +## Gas considerations +- Use `Map` for `tally` and `votesByAddress` +- Do not loop over all voters on‑chain to compute results +- Keep strings reasonably short + +## Next +- [06 — Testing with Vitest](./06-testing-with-vitest.md) diff --git a/docs/_archive/hackathon-legacy/06-testing-with-vitest.md b/docs/_archive/hackathon-legacy/06-testing-with-vitest.md new file mode 100644 index 000000000..7f78b1729 --- /dev/null +++ b/docs/_archive/hackathon-legacy/06-testing-with-vitest.md @@ -0,0 +1,53 @@ +# Testing with Vitest + +Compile, deploy, and call your Sophia contract using the JS SDK in Vitest. + +## Setup +Ensure you have `.env.local` or `.env.testnet` prepared. + +## Example test +```ts +// tests/poll.test.ts +import { describe, it, expect } from 'vitest' +import { readFileSync } from 'fs' +import 'dotenv/config' +import { AeSdk, Node, MemoryAccount } from '@aeternity/aepp-sdk' + +const NODE_URL = process.env.NODE_URL! +const COMPILER_URL = process.env.COMPILER_URL! +const SECRET_KEY = process.env.SECRET_KEY! + +describe('Poll contract', () => { + it('compiles, deploys, and allows a vote', async () => { + const node = new Node(NODE_URL) + const aeSdk = new AeSdk({ + nodes: [{ name: 'net', instance: node }], + compilerUrl: COMPILER_URL, + accounts: [new MemoryAccount(SECRET_KEY)], + }) + + const source = readFileSync('contracts/Poll.aes', 'utf8') + + const contract = await aeSdk.getContractInstance({ source }) + await contract.deploy(['My poll?', ['Yes', 'No'], 100000]) + + const voteTx = await contract.methods.vote(0, 0) + expect(voteTx.hash).toBeDefined() + + const res = await contract.methods.get_results(0) + expect(res.decodedResult).toBeDefined() + }) +}) +``` + +## Negative tests to add +- Duplicate vote should fail +- Voting outside open/close window should fail +- Invalid option index should fail + +## Tips +- Use small strings for options and questions +- Prefer height windows for deterministic tests + +## Next +- [07 — Deploy: devnet and testnet](./07-deploy-devnet-and-testnet.md) diff --git a/docs/_archive/hackathon-legacy/07-deploy-devnet-and-testnet.md b/docs/_archive/hackathon-legacy/07-deploy-devnet-and-testnet.md new file mode 100644 index 000000000..517250fc6 --- /dev/null +++ b/docs/_archive/hackathon-legacy/07-deploy-devnet-and-testnet.md @@ -0,0 +1,28 @@ +# Deploy: Devnet and Testnet + +Run your tests and deployments against a local devnet or the public testnet. + +## Devnet (local) +- Start a local node (see official docs) and point `NODE_URL` to it +- Use the local Sophia HTTP compiler at `http://localhost:3080` +- Fund your dev key in the local node if needed (or use pre-funded accounts) + +## Testnet (public) +- Set `.env.testnet` with: +``` +NODE_URL=https://testnet.aeternity.io +COMPILER_URL=http://localhost:3080 +SECRET_KEY=your_funded_testnet_private_key_hex +``` +- Fund your test key using the official faucet (see docs hub for current link) + +## Deploy once, reuse address +In longer tests, deploy once and reuse the contract address to reduce cost/time. Persist addresses per network (e.g., `.contracts.testnet.json`). + +## Sanity checks before testnet deploy +- All negative tests pass locally +- Compiler version pinned and consistent +- Events are emitted where needed for indexing + +## Next +- [08 — Integrate into Superhero extension](./08-integrate-into-superhero-extension.md) diff --git a/docs/_archive/hackathon-legacy/07a-middleware-and-data-access.md b/docs/_archive/hackathon-legacy/07a-middleware-and-data-access.md new file mode 100644 index 000000000..c717162ca --- /dev/null +++ b/docs/_archive/hackathon-legacy/07a-middleware-and-data-access.md @@ -0,0 +1,29 @@ +# Middleware and Data Access + +!!! note + Your extension will often need to read blockchain data (txs, contracts, events). The æternity middleware provides indexed APIs so you don't have to parse blocks manually. + +## What is the middleware? +A service indexing on‑chain data (transactions, contracts, names, etc.) and exposing REST/GraphQL endpoints for query and filtering. + +## Typical queries +- Fetch account transactions +- Lookup contract calls/events by contract address +- Paginate and filter by time or height + +!!! tip + Keep responses small: request only the fields you need and paginate. + +## Usage patterns +- Server‑side: call the middleware from your extension’s backend +- Client‑side: call from the extension UI (consider CORS and rate limits) + +## Superhero API (optional, upcoming) +We're adding an option to build Superhero API extensions that tailor data (transactions and contract interactions) for your use case. This can simplify complex queries you'd otherwise perform directly against middleware. + +!!! warning + Until the Superhero API extension path is available, plan to fetch directly from middleware. + +## Next +- Back to [Deploy: devnet and testnet](./07-deploy-devnet-and-testnet.md) +- Continue to [Integrate into Superhero extension](./08-integrate-into-superhero-extension.md) diff --git a/docs/_archive/hackathon-legacy/08-integrate-into-superhero-extension.md b/docs/_archive/hackathon-legacy/08-integrate-into-superhero-extension.md new file mode 100644 index 000000000..02e05f060 --- /dev/null +++ b/docs/_archive/hackathon-legacy/08-integrate-into-superhero-extension.md @@ -0,0 +1,50 @@ +# Integrate into Superhero Extension + +Call your contract from a Superhero extension using the JS SDK and Plugin SDK. + +!!! note + New to the Plugin SDK? Read the [Plugin SDK deep dive](./08a-plugin-sdk-deep-dive.md) for capabilities and examples. + +## Expose contract address +Provide a network‑specific address via env, e.g. `VITE_POLL_CONTRACT`. + +## Obtain ACI +Load from source in tests, or keep a built ACI JSON alongside your extension. + +## Wallet connect +Use `ensureWallet()` in attachments (or initialize via your app shell) to obtain a connected SDK before sending transactions. + +## Call from the extension +- Initialize an `AeSdk` instance at app start or via `ensureWallet` +- Load the contract by address and ACI +- Call view/stateful methods accordingly + +Sketch: +```ts +import { AeSdk, Node } from '@aeternity/aepp-sdk' + +const aeSdk = new AeSdk({ + nodes: [{ name: 'net', instance: new Node(import.meta.env.VITE_NODE_URL) }], + compilerUrl: import.meta.env.VITE_COMPILER_URL, +}) + +// later +const contract = await aeSdk.getContractInstance({ + aci: pollAciJson, + address: import.meta.env.VITE_POLL_CONTRACT, +}) + +const results = await contract.methods.get_results(0) +``` + +!!! tip + Consider emitting small on‑chain events and handling heavy processing off‑chain, then pushing entries into the feed via `pushFeedEntry`. + +## UI/UX tips +- Disable action buttons if not in open window or while pending +- Reflect on‑chain errors with friendly messages +- Show tallies and event‑driven updates + +## Next +- Continue to [Plugin SDK deep dive](./08a-plugin-sdk-deep-dive.md) or +- Jump to [AI workflows in Cursor](./09-ai-workflows-in-cursor.md) diff --git a/docs/_archive/hackathon-legacy/08a-plugin-sdk-deep-dive.md b/docs/_archive/hackathon-legacy/08a-plugin-sdk-deep-dive.md new file mode 100644 index 000000000..d5219645c --- /dev/null +++ b/docs/_archive/hackathon-legacy/08a-plugin-sdk-deep-dive.md @@ -0,0 +1,86 @@ +# Plugin SDK Deep Dive + +!!! note + The Plugin SDK lets your mini‑æpp integrate into Superhero: add composer attachments, feed entries, item actions, custom routes/modals, and menu entries. + +## Capabilities (v1.x) +- `feed`: add new item kinds to the unified feed +- `composer`: add actions and attachments (interactive panels) +- `item-actions`: add contextual actions to feed items +- `routes`: add pages to the app router +- `modals`: register reusable modals +- `menu`: contribute navigation items + +## Core types (simplified) +```ts +// See src/plugin-sdk/index.ts +export type PluginMeta = { + id: string; name: string; version: string; apiVersion: '1.x'; + capabilities: Array<'feed' | 'composer' | 'item-actions' | 'routes' | 'modals'>; +}; + +export type ComposerActionCtx = { + insertText(text: string): void; + navigate(to: string): void; + storage: { get(k: string): any; set(k: string, v: any): void }; + theme: { colorScheme: 'light' | 'dark' }; + events: { emit(e: string, p?: any): void; on(e: string, h: (p: any)=>void): () => void }; + cacheLink?: (postId: string, kind: string, payload: any) => void; + pushFeedEntry?: (kind: string, entry: any) => void; +}; + +export type ComposerAttachmentCtx = ComposerActionCtx & { + getValue(ns: string): T | undefined; + setValue(ns: string, value: T): void; + ensureWallet(): Promise<{ sdk: any; currentBlockHeight?: number }> +}; +``` + +## Attachments (recommended surface) +```ts +import { definePlugin, type ComposerAttachmentSpec } from '@superhero/plugin-sdk'; + +export default definePlugin({ + meta: { id: 'org.example', name: 'Example', version: '1.0.0', apiVersion: '1.x', capabilities: ['composer'] }, + setup({ register }) { + const spec: ComposerAttachmentSpec = { + id: 'example-attachment', + label: 'Example', + Panel: ({ ctx, onRemove }) => null, // render your UI + validate: (ctx) => [], + onAfterPost: async (ctx, post) => { + const { sdk } = await ctx.ensureWallet(); + // interact with chain, then push feed entry if needed + ctx.pushFeedEntry?.('example', { id: post.id, createdAt: new Date().toISOString(), kind: 'example', data: {} }); + }, + }; + register({ attachments: () => [spec] }); + } +}); +``` + +!!! tip + Use `ensureWallet()` inside attachments to request a connected wallet SDK and optional chain context (e.g., current height). + +## Feed plugins +- Implement `fetchPage(page)` and `Render({ entry })` +- Use `Skeleton` for loading placeholders + +## Item actions +- Gate actions with `when(entry)` and call chain or UI APIs inside `onClick` + +## Routes and modals +- Contribute routes (`{ path, element }`) and `modals` as a `Record` +- Add `menu` entries to surface your feature in app navigation + +## Events, storage, theme +- `events.emit/on` to integrate with host events +- `storage.get/set` for lightweight persistence +- `theme.colorScheme` to adapt visuals + +!!! warning + Keep attachments lean: validate inputs, avoid heavy on-chain loops, and prefer emitting small events or pushing feed entries for off‑chain processing. + +## Next +- Back to [Integrate into Superhero extension](./08-integrate-into-superhero-extension.md) +- Or continue to [AI workflows in Cursor](./09-ai-workflows-in-cursor.md) diff --git a/docs/_archive/hackathon-legacy/09-ai-workflows-in-cursor.md b/docs/_archive/hackathon-legacy/09-ai-workflows-in-cursor.md new file mode 100644 index 000000000..a1215e5c3 --- /dev/null +++ b/docs/_archive/hackathon-legacy/09-ai-workflows-in-cursor.md @@ -0,0 +1,26 @@ +# AI Workflows in Cursor + +Use AI effectively while keeping control of correctness and gas. + +## Prompt recipes +- Scaffold contract + - “Create a Sophia Poll contract with state: question, options, open/close heights, one vote per address. Add events and guards.” +- Tighten invariants + - “Add `require` checks for time window and unique voting; validate option index; return clear errors.” +- Extend tests + - “Add Vitest cases for duplicate vote, before open, after close, and invalid index.” +- Gas review + - “Replace list scans with `Map` lookups; avoid unbounded loops; keep events minimal.” + +## Verification loop +1) Run tests +2) If failing, ask AI to propose minimal edits +3) Re-run tests and inspect diffs + +## Pitfalls +- Hallucinated SDK APIs → check `@aeternity/aepp-sdk` docs +- Version mismatches → pin compiler and SDK; verify with a small compile test +- Large loops or strings → watch gas usage + +## Next +- [10 — Troubleshooting & FAQ](./10-troubleshooting-and-faq.md) diff --git a/docs/_archive/hackathon-legacy/10-troubleshooting-and-faq.md b/docs/_archive/hackathon-legacy/10-troubleshooting-and-faq.md new file mode 100644 index 000000000..960f37eb4 --- /dev/null +++ b/docs/_archive/hackathon-legacy/10-troubleshooting-and-faq.md @@ -0,0 +1,31 @@ +# Troubleshooting & FAQ + +## Compiler fails with version error +- Adjust the pragma to a version supported by your `aesophia_http` image +- Pull latest image or pin a matching version + +## Cannot reach compiler at http://localhost:3080 +- Ensure Docker container is running +- Check port 3080 is free +- Verify `COMPILER_URL` env variable is set in tests/app + +## SDK call throws decoding error +- Simplify return types; prefer lists/records with primitive fields +- Ensure ACI matches deployed bytecode + +## Transactions are failing on testnet +- Verify your test key is funded +- Check gas/fee parameters and network URL + +## Duplicate vote not rejected +- Add `require(votesByAddress[caller] == None, "Already voted")` logic + +## Results computation times out +- Avoid iterating over all voters; maintain a `tally` map updated per vote + +## How do I debug quickly? +- Use the SDK dry‑run to simulate calls +- Add temporary view entrypoints that expose internal state (remove before deploy) + +## Where to ask for help? +- [Community Q&A](https://forum.aeternity.com/c/sophia-smart-contracts/38) diff --git a/docs/_archive/hackathon-legacy/11-security-checklist.md b/docs/_archive/hackathon-legacy/11-security-checklist.md new file mode 100644 index 000000000..a066153fa --- /dev/null +++ b/docs/_archive/hackathon-legacy/11-security-checklist.md @@ -0,0 +1,24 @@ +# Security Checklist + +Use this before deploying to testnet/mainnet. + +- Auth + - Use `Call.caller` and check owner/admin where required + - Reject unauthorized state changes early +- Time windows + - Enforce open/close windows consistently (height or timestamp) + - Prevent voting after close; prevent closing before close unless owner +- Input validation + - Non‑empty question; ≥ 2 options; option indices in range; unique options +- One vote per address + - Track in `votesByAddress`; guard with `require` +- Gas & storage + - Use `Map` for tallies; avoid unbounded loops; keep strings small +- Events + - Emit `PollCreated`, `Voted`, `Closed`; keep payloads minimal +- ACI & interface + - Keep return types simple; maintain stable entrypoint names +- Testing + - Negative tests for all guards; dry‑run for heavy paths +- Versioning + - Pin compiler version; document init args; plan for migration diff --git a/docs/_archive/hackathon-legacy/12-reference-links-and-glossary.md b/docs/_archive/hackathon-legacy/12-reference-links-and-glossary.md new file mode 100644 index 000000000..040667f4e --- /dev/null +++ b/docs/_archive/hackathon-legacy/12-reference-links-and-glossary.md @@ -0,0 +1,21 @@ +# Reference Links & Glossary + +## Official links +- [Docs hub](https://docs.aeternity.com) +- [Sophia syntax](https://github.com/aeternity/aesophia/blob/master/docs/sophia_syntax.md) +- [Sophia features](https://github.com/aeternity/aesophia/blob/master/docs/sophia_features.md) +- [Sophia stdlib](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md) +- [Sophia compiler (HTTP)](https://github.com/aeternity/aesophia_http) +- [JS SDK](https://github.com/aeternity/aepp-sdk-js) +- [Community forum](https://forum.aeternity.com/c/sophia-smart-contracts/38) + +## Network +- [Testnet RPC](https://testnet.aeternity.io) +- Faucet: see docs hub for the current faucet link + +## Glossary +- ACI: Interface describing contract entrypoints and types +- FATE: æternity's virtual machine that executes compiled contracts +- Entrypoint: Public function callable from transactions +- Stateful: Entrypoint that mutates contract `state` +- Dry‑run: Simulate execution without broadcasting a transaction diff --git a/docs/_archive/hackathon-legacy/13-checklist-and-deploy-extension.md b/docs/_archive/hackathon-legacy/13-checklist-and-deploy-extension.md new file mode 100644 index 000000000..d36383582 --- /dev/null +++ b/docs/_archive/hackathon-legacy/13-checklist-and-deploy-extension.md @@ -0,0 +1,31 @@ +# Checklist & Deploy Extension + +Use this checklist before you submit or deploy your hackathon project. + +## Technical +- Env: `VITE_*` variables set for node/compiler/contract addresses +- Contract: compiler version pinned; events emitted where needed +- Tests: happy/negative paths; no flaky height assumptions +- Integration: wallet connect flow; ACI loaded; error handling in UI +- Data: middleware queries scoped/paginated; plan for rate limits + +## UX +- Clear affordances; disabled states while pending +- Friendly error messages; confirmations +- Dark/light theme compatible (use `theme.colorScheme`) + +## Security +- One‑vote‑per‑address (if voting); input validation +- Avoid unbounded loops and large payloads +- Never expose private keys; use Wallet for signing + +!!! important + Review the [Security checklist](./11-security-checklist.md) before deploying. + +## Submission & deploy +- Include README with setup and env docs +- Provide testnet contract addresses and sample accounts +- Optional: link to a short demo video + +## Next +- Back to [Overview](./00-overview.md) or [Quickstart](./00a-quickstart.md) diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100644 index 000000000..0bbd3c72c --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..67e37ef5e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,56 @@ +--- +title: Build Superhero Plugins +--- + + +Welcome, hackathon builders! This guide is crafted by Superhero to help you ship a mini‑æpp that integrates directly into the Superhero app (social + DeFi) using Sophia smart contracts and the Plugin SDK. + + +This tutorial is designed for newcomers using Cursor or other AI tools to build Superhero plugins backed by their own Sophia smart contracts. + +## What you'll build +- A small æternity mini‑æpp (plugin) that integrates into superhero.com +- A TypeScript test harness that compiles, deploys, and calls your contract +- A plugin that calls the contract via the JS SDK and Plugin SDK + +## Repo model + +You'll work with multiple repositories: + +1. **Contracts** (create your own): Sophia code, tests, aeproject, deployments, ACI artifacts. +2. **[Superhero UI](https://github.com/superhero-com/superhero)**: the complete Superhero frontend application where you'll add your frontend plugin. +3. **[Superhero API](https://github.com/superhero-com/superhero-api)** (optional): backend application for transaction processing and popular feed integration. + +Recommended structure: +``` +superhero-plugin-workspace/ + superhero/ # UI repo (cloned) + src/plugins/your-plugin/ + superhero-api/ # API repo (cloned, optional) + src/plugins/your-plugin/ + your-plugin-contracts/ # Contracts repo (your repo) + contracts/ + tests/ + aeproject.json + deployments/ # addresses per network + aci/ # compiled ACIs (JSON) + superhero.code-workspace # IDE workspace file +``` + +## Fast Feedback Loop +1) Write/refine contract with Cursor → 2) Compile via `aeproject compile` → 3) Test with `aeproject test` → 4) Integrate into a Superhero plugin → 5) `aeproject deploy` to devnet/testnet. + +## High‑Level Architecture +- Contract (Sophia) ↔ ACI ↔ JS SDK (`@aeternity/aepp-sdk`) ↔ Superhero Plugin SDK ↔ Plugin UI + + +Prefer the aeproject path for the fastest setup. See [Project Scaffold](./tutorials/hackathon/scaffold-and-compiler) for details. + + +## Quick Links + +- **[Quickstart](./tutorials/hackathon/quickstart)** - Fast track to get a plugin running with AI assistance +- **[Setup](./tutorials/hackathon/setup)** - Complete environment setup +- **[Plugin SDK Documentation](./plugin-sdk)** - Complete API reference +- **[References](./tutorials/hackathon/references)** - Documentation links and tools + diff --git a/docs/mint.json b/docs/mint.json new file mode 100644 index 000000000..dbd747918 --- /dev/null +++ b/docs/mint.json @@ -0,0 +1,66 @@ +{ + "name": "Superhero Hackathon", + "logo": { + "light": "/favicon.svg", + "dark": "/favicon.svg" + }, + "favicon": "/favicon.svg", + "colors": { + "primary": "#1161FE", + "light": "#3D7FFF", + "dark": "#0D4F8C" + }, + "topbarLinks": [ + { + "name": "GitHub", + "url": "https://github.com/superhero-com/superhero" + } + ], + "navigation": [ + { + "group": "Getting Started", + "pages": [ + "index", + "tutorials/hackathon/quickstart" + ] + }, + { + "group": "Setup", + "pages": [ + "tutorials/hackathon/setup", + "tutorials/hackathon/prerequisites", + "tutorials/hackathon/wallet-setup", + "tutorials/hackathon/configure-cursor", + "tutorials/hackathon/project-setup", + "tutorials/hackathon/backend-setup" + ] + }, + { + "group": "Contract Development", + "pages": [ + "tutorials/hackathon/scaffold-and-compiler", + "tutorials/hackathon/contracts", + "tutorials/hackathon/test-and-deploy" + ] + }, + { + "group": "Plugin Development", + "pages": [ + "tutorials/hackathon/integrate-and-plugin-sdk", + "tutorials/hackathon/feed-plugins", + "tutorials/hackathon/api-plugin-development" + ] + }, + { + "group": "Resources", + "pages": [ + "tutorials/hackathon/hints", + "tutorials/hackathon/references" + ] + } + ], + "footerSocials": { + "github": "https://github.com/superhero-com/superhero" + } +} + diff --git a/docs/plugin-sdk.md b/docs/plugin-sdk.md new file mode 100644 index 000000000..ec2aaadb4 --- /dev/null +++ b/docs/plugin-sdk.md @@ -0,0 +1,229 @@ +# Superhero Plugin SDK (v1.x) + +This guide explains how to extend the app with external plugins. You can add feed entries, composer actions, and now composer attachments (inline panels like Poll). + +## Concepts +- Host: the app that loads plugins at runtime +- Capabilities: `feed`, `composer` (actions, attachments), `item-actions`, `routes`, `modals` +- SDK: small, versioned API `@superhero/plugin-sdk` (local path `src/plugin-sdk`) + +## Quickstart (attachments) +```ts +import { definePlugin, type ComposerAttachmentSpec } from '@superhero/plugin-sdk'; + +export default definePlugin({ + meta: { id: 'org.polls', name: 'Polls', version: '1.0.0', apiVersion: '1.x', capabilities: ['composer'] }, + setup({ register, ctx }) { + const spec: ComposerAttachmentSpec = { + id: 'poll', + label: 'Poll', + Panel: ({ ctx, onRemove }) => /* your UI */ null, + validate: (ctx) => [], + onAfterPost: async (ctx, post) => { /* async work */ }, + }; + register({ attachments: () => [spec] }); + } +}); +``` + +## Composer attachments +Attachments add an expandable panel to the post composer. The main post text remains unchanged; attachments may validate inputs and (optionally) run async tasks after posting. + +Key types (simplified): +```ts +type ComposerAttachmentCtx = { + insertText(text: string): void; + navigate(to: string): void; + getValue(ns: string): T | undefined; + setValue(ns: string, v: T): void; + ensureWallet(): Promise<{ sdk: any; currentBlockHeight?: number }>; + cacheLink?(postId: string, kind: string, payload: any): void; + pushFeedEntry?(kind: string, entry: any): void; +}; + +type ComposerAttachmentSpec = { + id: string; + label: string; + Panel: React.FC<{ ctx: ComposerAttachmentCtx; onRemove: () => void }>; + validate(ctx: ComposerAttachmentCtx): { field?: string; message: string }[]; + onAfterPost(ctx: ComposerAttachmentCtx, post: { id: string; text: string }): Promise; +}; +``` + +### Poll example +- Panel with dynamic options and close height; show estimated close date/time. +- After posting, deploy the poll, then update inline cache and push a `poll-created` feed entry. + +## Feed plugins + +Feed plugins allow you to inject custom content into the unified feed. See the [Feed Plugins & Popular Feed Injection](./tutorials/hackathon/feed-plugins) guide for complete documentation. + +Key features: +- **Unified Feed**: Your content appears alongside regular posts +- **Popular Feed**: Optional integration with popular ranking system +- **Live Updates**: Real-time content updates via WebSocket +- **Pagination**: Built-in pagination support + +Quick example: +```typescript +import { FeedPlugin } from '@/features/social/feed-plugins/types'; +import { registerPlugin } from '@/features/social/feed-plugins/registry'; + +const myFeedPlugin: FeedPlugin = { + kind: 'my-content', + async fetchPage(page: number) { + const items = await fetchMyContent(page); + return { + entries: items.map(item => ({ + id: item.id, + kind: 'my-content', + createdAt: item.created_at, + data: item, + })), + nextPage: items.length > 0 ? page + 1 : undefined, + }; + }, + Render: ({ entry }) => , +}; + +registerPlugin(myFeedPlugin); +``` + +## Actions and item actions +- Composer actions: simple buttons near Emoji/GIF. +- Item actions: add contextual actions in item menus. + +## Translations + +Plugins can include their own translation files. Each plugin's translations are automatically registered using the plugin ID as the i18n namespace. + +### Structure + +Create a `locales/` directory in your plugin: + +``` +src/plugins/social/my-plugin/ +├── index.tsx +├── locales/ +│ ├── en.json +│ └── index.ts +└── components/ + └── MyPluginCard.tsx +``` + +### Translation Files + +**`locales/en.json`:** +```json +{ + "createdAPoll": "created a poll", + "pending": "Pending…", + "yourVote": "Your vote", + "retractVote": "Retract vote", + "votes": "votes", + "actions": { + "like": "Like", + "share": "Share" + } +} +``` + +**`locales/index.ts`:** +```typescript +import en from './en.json'; + +export const translations = { + en, + // Add more languages: + // de: require('./de.json'), + // fr: require('./fr.json'), +}; +``` + +### Exporting Translations + +In your plugin definition, export translations: + +```typescript +import { definePlugin } from '@/plugin-sdk'; +import { translations } from './locales'; + +export default definePlugin({ + meta: { + id: 'my-plugin', // This becomes the i18n namespace + // ... + }, + translations, // Export translations + setup({ register }) { + // ... + }, +}); +``` + +### Using in Components + +Use the plugin ID as the namespace in your React components: + +```typescript +import { useTranslation } from 'react-i18next'; + +export default function MyPluginCard() { + // Use plugin ID as namespace + const { t } = useTranslation('my-plugin'); + + return ( +
+ {t('createdAPoll')} + {t('pending')} + {t('actions.like')} +
+ ); +} +``` + +### Multi-language Support + +To add more languages: + +1. Create additional JSON files: `locales/de.json`, `locales/fr.json`, etc. +2. Export them in `locales/index.ts`: + ```typescript + import en from './en.json'; + import de from './de.json'; + + export const translations = { en, de }; + ``` +3. Translations are automatically registered when the plugin loads. + +### Benefits + +- **Self-contained**: Each plugin manages its own translations +- **Namespace isolation**: Plugin ID becomes the i18n namespace, preventing conflicts +- **Easy to extend**: Add language files as needed +- **Works for external plugins**: External plugins can bundle their own translations + +## API Plugin Development + +For backend plugins that process blockchain transactions and contribute to the popular feed, see the [API Plugin Development](./tutorials/hackathon/api-plugin-development) guide. + +Backend plugins can: +- Process blockchain transactions +- Extract and store plugin data +- Contribute content to popular feed ranking +- Handle reorgs and transaction updates + +## Popular Feed Integration + +Plugins can contribute content to the popular feed through: + +1. **Frontend**: Use feed plugins with proper ID format (`{plugin-name}:{id}`) +2. **Backend**: Implement `PopularRankingContributor` interface + +See [Feed Plugins & Popular Feed Injection](./tutorials/hackathon/feed-plugins) for frontend integration and [API Plugin Development](./tutorials/hackathon/api-plugin-development) for backend integration. + +## Testing +- Develop locally as ESM; add your plugin URL to `CONFIG.PLUGINS`. +- Mock `ensureWallet` and `cacheLink` for unit tests. +- Test feed plugins with mock data and pagination scenarios. + + diff --git a/docs/tutorials/hackathon/README.md b/docs/tutorials/hackathon/README.md new file mode 100644 index 000000000..f12e8194d --- /dev/null +++ b/docs/tutorials/hackathon/README.md @@ -0,0 +1,31 @@ +--- +title: Hackathon Tutorial +--- + +# Superhero Hackathon Tutorial + +Welcome to the Superhero hackathon! This guide helps you build a Superhero plugin that integrates with the Superhero app using Sophia contracts and our Plugin SDK. + +## Getting Started + +- **[Quickstart](./quickstart)** - Fast track to get a plugin running with AI assistance +- **[Overview](../../index)** - Complete guide overview and architecture + +## Setup + +- **[Setup Overview](./setup)** - Start here for environment setup +- **[Prerequisites](./prerequisites)** - Install required software +- **[Wallet Setup](./wallet-setup)** - Configure Superhero Wallet +- **[Configure Cursor](./configure-cursor)** - Set up Cursor with documentation context +- **[Project Setup](./project-setup)** - Clone repositories and configure workspace +- **[Backend API Setup](./backend-setup)** - Set up backend API (optional, only if building backend plugins) + +## Development Guides + +- **[Contract Development](./scaffold-and-compiler)** - Build Sophia smart contracts +- **[Plugin Development](./integrate-and-plugin-sdk)** - Create Superhero plugins + +## Resources + +- **[Hints & Tips](./hints)** - Development tips and troubleshooting +- **[References](./references)** - Documentation links and tools diff --git a/docs/tutorials/hackathon/SUMMARY.md b/docs/tutorials/hackathon/SUMMARY.md new file mode 100644 index 000000000..127dcdb3a --- /dev/null +++ b/docs/tutorials/hackathon/SUMMARY.md @@ -0,0 +1,32 @@ +--- +title: Summary +--- + +# Documentation Summary + +## Getting Started +- [Overview](../../index) +- [Quickstart](./quickstart) + +## Setup +- [Setup Overview](./setup) +- [Prerequisites](./prerequisites) +- [Wallet Setup](./wallet-setup) +- [Configure Cursor](./configure-cursor) +- [Project Setup](./project-setup) +- [Backend API Setup](./backend-setup) + +## Contract Development +- [Project Scaffold](./scaffold-and-compiler) +- [Smart Contracts](./contracts) +- [Testing & Deployment](./test-and-deploy) + +## Plugin Development +- [Plugin Integration](./integrate-and-plugin-sdk) +- [Feed Plugins](./feed-plugins) +- [API Plugin Development](./api-plugin-development) + +## Resources +- [Hints & Tips](./hints) +- [References](./references) +- [Plugin SDK Documentation](../../plugin-sdk) diff --git a/docs/tutorials/hackathon/api-plugin-development.md b/docs/tutorials/hackathon/api-plugin-development.md new file mode 100644 index 000000000..d410e9bd8 --- /dev/null +++ b/docs/tutorials/hackathon/api-plugin-development.md @@ -0,0 +1,355 @@ +--- +title: API Plugin Development +--- + +## Overview + +API plugins extend the Superhero backend to process blockchain transactions, extract data, and contribute content to features like the popular feed. They run server-side and sync with the æternity blockchain. + +## Plugin Architecture + +API plugins extend `BasePlugin` and implement transaction processing logic. They can also implement `PopularRankingContributor` to inject content into the popular feed. + +## Base Plugin Structure + +```typescript +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Tx } from '@/mdw-sync/entities/tx.entity'; +import { PluginSyncState } from '@/mdw-sync/entities/plugin-sync-state.entity'; +import { BasePlugin } from '../base-plugin'; +import { PluginFilter } from '../plugin.interface'; +import { BasePluginSyncService } from '../base-plugin-sync.service'; + +@Injectable() +export class MyPlugin extends BasePlugin { + protected readonly logger = new Logger(MyPlugin.name); + readonly name = 'my-plugin'; + readonly version = 1; // Increment when data structure changes + + constructor( + @InjectRepository(Tx) + protected readonly txRepository: Repository, + @InjectRepository(PluginSyncState) + protected readonly pluginSyncStateRepository: Repository, + private syncService: BasePluginSyncService, + ) { + super(); + } + + startFromHeight(): number { + // Block height to start syncing from + return 1000000; + } + + filters(): PluginFilter[] { + return [ + { + type: 'contract_call', + contractIds: ['ct_your_contract_address'], + functions: ['create_item', 'update_item'], + }, + ]; + } + + protected getSyncService(): BasePluginSyncService { + return this.syncService; + } +} +``` + +## Transaction Processing + +Create a sync service that extends `BasePluginSyncService`: + +```typescript +import { Injectable, Logger } from '@nestjs/common'; +import { BasePluginSyncService } from '../base-plugin-sync.service'; +import { Tx } from '@/mdw-sync/entities/tx.entity'; +import { SyncDirection } from '../plugin.interface'; + +@Injectable() +export class MyPluginSyncService extends BasePluginSyncService { + private readonly logger = new Logger(MyPluginSyncService.name); + + async processBatch(txs: Tx[], syncDirection: SyncDirection): Promise { + for (const tx of txs) { + await this.processTransaction(tx, syncDirection); + } + } + + private async processTransaction(tx: Tx, syncDirection: SyncDirection) { + const pluginData = tx.data?.['my-plugin']?.data; + if (!pluginData) return; + + // Extract data from transaction + const functionName = tx.function; + const decodedData = pluginData; + + switch (functionName) { + case 'create_item': + await this.handleCreateItem(tx, decodedData); + break; + case 'update_item': + await this.handleUpdateItem(tx, decodedData); + break; + } + } + + private async handleCreateItem(tx: Tx, data: any) { + // Save to database, emit events, etc. + this.logger.log(`Created item: ${data.item_id}`); + } + + private async handleUpdateItem(tx: Tx, data: any) { + // Update database, etc. + this.logger.log(`Updated item: ${data.item_id}`); + } +} +``` + +## Popular Feed Integration + +Implement `PopularRankingContributor` to inject content into the popular feed: + +```typescript +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { PopularRankingContributor, PopularRankingContentItem } from '../popular-ranking.interface'; +import { PopularWindow } from '@/social/services/popular-ranking.service'; +import { MyItem } from './entities/my-item.entity'; + +@Injectable() +export class MyPluginPopularRankingService implements PopularRankingContributor { + readonly name = 'my-content'; + private readonly logger = new Logger(MyPluginPopularRankingService.name); + + constructor( + @InjectRepository(MyItem) + private readonly itemRepository: Repository, + ) {} + + async getRankingCandidates( + window: PopularWindow, + since: Date | null, + limit: number, + ): Promise { + try { + const queryBuilder = this.itemRepository + .createQueryBuilder('item') + .orderBy('item.created_at', 'DESC') + .limit(limit); + + // Apply time window filter + if (since) { + queryBuilder.where('item.created_at >= :since', { since }); + } + + const items = await queryBuilder.getMany(); + + return items.map((item) => ({ + id: `my-content:${item.id}`, // Must match format: {plugin-name}:{id} + type: 'my-content', + created_at: item.created_at, + sender_address: item.creator_address, + content: item.title || item.description || '', + total_comments: item.comments_count || 0, + topics: item.tags?.map(tag => ({ name: tag })) || [], + metadata: { + item_id: item.id, + // Add any additional metadata + }, + })); + } catch (error) { + this.logger.error(`Failed to fetch items for popular ranking:`, error); + return []; + } + } +} +``` + +## Plugin Module Setup + +Create a module to wire everything together: + +```typescript +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { MyPlugin } from './my-plugin'; +import { MyPluginSyncService } from './my-plugin-sync.service'; +import { MyPluginPopularRankingService } from './my-plugin-popular-ranking.service'; +import { MyItem } from './entities/my-item.entity'; +import { POPULAR_RANKING_CONTRIBUTOR } from '../plugin.tokens'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([MyItem]), + ], + providers: [ + MyPlugin, + MyPluginSyncService, + { + provide: POPULAR_RANKING_CONTRIBUTOR, + useClass: MyPluginPopularRankingService, + multi: true, // Allow multiple contributors + }, + ], + exports: [MyPlugin], +}) +export class MyPluginModule {} +``` + +## Registering the Plugin + +Add your plugin to the main plugin index: + +```typescript +// src/plugins/index.ts +import { MyPluginModule } from './my-plugin/my-plugin.module'; +import { MyPluginPopularRankingService } from './my-plugin/my-plugin-popular-ranking.service'; + +export const PLUGIN_MODULES: Type[] = [ + // ... other plugins + MyPluginModule, +]; + +export const getPopularRankingContributorProvider = (): Provider => ({ + provide: POPULAR_RANKING_CONTRIBUTOR, + useFactory: ( + governanceRankingService: GovernancePopularRankingService, + myPluginRankingService: MyPluginPopularRankingService, + ): PopularRankingContributor[] => { + return [ + governanceRankingService, + myPluginRankingService, // Add your contributor + ]; + }, + inject: [GovernancePopularRankingService, MyPluginPopularRankingService], +}); +``` + +## Transaction Filters + +Define filters to match relevant transactions: + +```typescript +filters(): PluginFilter[] { + return [ + { + type: 'contract_call', + contractIds: ['ct_your_contract_address'], + functions: ['create_item', 'update_item', 'delete_item'], + }, + // Custom predicate for complex filtering + { + predicate: (tx: Partial) => { + return ( + tx.type === 'ContractCallTx' && + tx.contract_id === 'ct_your_contract_address' && + tx.function?.startsWith('item_') + ); + }, + }, + ]; +} +``` + +## Version Management + +Increment the plugin version when data structures change: + +```typescript +readonly version = 2; // Increment when breaking changes occur +``` + +This triggers a re-sync of plugin data for affected transactions. + +## Update Queries + +Override `getUpdateQueries` to handle transaction updates: + +```typescript +getUpdateQueries(pluginName: string, currentVersion: number) { + return [ + async (repo, limit, cursor) => { + const query = repo.createQueryBuilder('tx') + .where('tx.function IN (:...functions)', { + functions: ['create_item', 'update_item'] + }) + .andWhere( + `(tx.data->>'${pluginName}' IS NULL OR (tx.data->'${pluginName}'->>'_version')::int != :version)`, + { version: currentVersion } + ) + .orderBy('tx.block_height', 'ASC') + .addOrderBy('tx.micro_time', 'ASC') + .limit(limit); + + if (cursor) { + query.andWhere( + '(tx.block_height > :height OR (tx.block_height = :height AND tx.micro_time > :microTime))', + { height: cursor.block_height, microTime: cursor.micro_time } + ); + } + + return query.getMany(); + }, + ]; +} +``` + +## Error Handling + +Handle errors gracefully: + +```typescript +async processTransaction(tx: Tx, syncDirection: SyncDirection) { + try { + // Process transaction + } catch (error) { + this.logger.error(`Failed to process transaction ${tx.hash}:`, error); + // Don't throw - continue processing other transactions + } +} +``` + +## Testing + +Test your plugin with sample transactions: + +```typescript +describe('MyPlugin', () => { + it('should process create_item transactions', async () => { + const mockTx = { + hash: 'th_test', + function: 'create_item', + data: { + 'my-plugin': { + _version: 1, + data: { item_id: '123', title: 'Test Item' }, + }, + }, + }; + + await plugin.processBatch([mockTx], SyncDirectionEnum.LIVE); + // Assert expected behavior + }); +}); +``` + +## Best Practices + +1. **Idempotency**: Ensure processing is idempotent +2. **Error Handling**: Log errors but don't crash the sync +3. **Performance**: Process in batches when possible +4. **Versioning**: Increment version for breaking changes +5. **Logging**: Use structured logging for debugging +6. **Testing**: Test with real transaction data + +## Next Steps + +- **[Feed Plugins](./feed-plugins)** - Frontend feed plugin integration +- **[Plugin Integration](./integrate-and-plugin-sdk)** - Complete plugin development guide +- **[Hints & Tips](./hints)** - Troubleshooting and best practices + diff --git a/docs/tutorials/hackathon/backend-setup.md b/docs/tutorials/hackathon/backend-setup.md new file mode 100644 index 000000000..036a69cd2 --- /dev/null +++ b/docs/tutorials/hackathon/backend-setup.md @@ -0,0 +1,124 @@ +--- +title: Backend API Setup +--- + + +Set up the Superhero API repository for backend plugin development. This is only needed if you're building backend plugins that process blockchain transactions or contribute to the popular feed. + + +## Clone Backend Repository + +Clone the Superhero API repository: + +```bash +git clone https://github.com/superhero-com/superhero-api.git +cd superhero-api +``` + + +You'll also need the Superhero UI repository for frontend plugin development. Clone it with: +```bash +git clone https://github.com/superhero-com/superhero.git +``` + + +## Install Dependencies + +```bash +npm install +``` + +## Environment Configuration + +Create `.env` file based on `.env.example`: + +```bash +cp .env.example .env +``` + +Configure the following environment variables: + +```bash +# Database Configuration +DB_TYPE=postgres +DB_HOST=127.0.0.1 +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=postgres +DB_DATABASE=superhero_api +DB_SYNC=true + +# Redis Configuration +REDIS_HOST=localhost +REDIS_PORT=6379 + +# Network Configuration +AE_NETWORK_ID=ae_uat # or ae_mainnet for mainnet + +# Application Configuration +APP_PORT=3000 +``` + +## Start Required Services + +### Option 1: Docker Compose (Recommended) + +Start PostgreSQL and Redis using Docker Compose: + +```bash +docker-compose up -d postgres redis +``` + +### Option 2: Local Services + +Ensure PostgreSQL and Redis are running locally: + +- **PostgreSQL**: Version 16 or later +- **Redis**: Latest version + +## Run Database Migrations + +```bash +npm run migration:run +``` + +## Start Development Server + +```bash +npm run start:dev +``` + +The API will be available at `http://localhost:3000`. + +## Verify Setup + +Check that the API is running: + +```bash +curl http://localhost:3000/health +``` + +## Project Structure + +Backend plugins are located in: + +``` +src/plugins// +├── .plugin.ts # Main plugin class (extends BasePlugin) +├── -sync.service.ts # Transaction sync service +├── -popular-ranking.service.ts # Popular feed contributor (optional) +├── entities/ +│ └── .entity.ts # Database entities +└── .module.ts # NestJS module +``` + +## Next Steps + +- **[API Plugin Development](./api-plugin-development)** - Build your backend plugin +- **[Feed Plugins](./feed-plugins)** - Understand frontend-backend integration +- **[Hints & Tips](./hints)** - Troubleshooting and best practices + + +Make sure your database and Redis are running before starting the development server. Check logs if you encounter connection errors. + + diff --git a/docs/tutorials/hackathon/configure-cursor.md b/docs/tutorials/hackathon/configure-cursor.md new file mode 100644 index 000000000..f83b641af --- /dev/null +++ b/docs/tutorials/hackathon/configure-cursor.md @@ -0,0 +1,63 @@ +--- +title: Configure Cursor +--- + + +Configure Cursor with MCP to access this documentation for better AI assistance. All pages are in Markdown—add `.md` to any URL to get the raw source (e.g., `/quickstart` → `/quickstart.md`). + + +## Connect to MCP Server for Superhero Documentation + +This documentation automatically provides a Model Context Protocol (MCP) server. Configure Cursor to access it: + +### Step 1: Open Cursor Settings + +1. Open **Cursor Settings** → **Tools & Integrations** → **MCP Tools** +2. Click **"Add Custom MCP"** to open the `mcp.json` editor + +### Step 2: Add MCP Configuration + +Add the following configuration to your `mcp.json`: + +```json +{ + "mcpServers": { + "superhero-docs": { + "url": "https://docs.superhero.com/mcp" + } + } +} +``` + +### Step 3: Save and Restart + +1. Save the configuration file +2. Restart Cursor to apply the changes + +## Alternative: Run Documentation Locally + +If you prefer to run the documentation locally (useful for development or offline access): + +1. Start the Mintlify dev server: + +```bash +cd docs +mintlify dev --port 3002 +``` + +2. Update your MCP configuration to use the local URL: + +```json +{ + "mcpServers": { + "superhero-docs": { + "url": "http://localhost:3002/mcp" + } + } +} +``` + + +Make sure the Mintlify dev server is running when using a local URL. The server must be active for Cursor to access the documentation via MCP. + + diff --git a/docs/tutorials/hackathon/contracts.md b/docs/tutorials/hackathon/contracts.md new file mode 100644 index 000000000..b8122992f --- /dev/null +++ b/docs/tutorials/hackathon/contracts.md @@ -0,0 +1,50 @@ +--- +title: Smart Contracts +--- + +## Sophia basics (skim) +- Contract structure: `contract`, `record state`, `init`, `entrypoint`, `stateful entrypoint` +- Auth: `Call.caller` for owner/role checks; enforce one‑vote‑per‑address +- Time: choose `Chain.height` or timestamp; enforce open/close windows consistently +- Data structures: prefer `Map` for lookups; avoid unbounded loops +- Errors: `require`/`abort` with clear messages +- Events: emit small events for create/vote/close; useful for indexing + +## Poll example (walkthrough) +### Data model +- `Poll`: id, question, options, open/close heights, owner, isClosed +- `votesByAddress`: Address → optionIdx (one vote per address) +- `tally`: optionIdx → count (avoid iterating all voters) + +### Entrypoints (suggested) +- `create_poll(question: string, options: list(string), open_h: int, close_h: int)` +- `vote(poll_id: int, option_idx: int)` +- `close(poll_id: int)` +- `get_results(poll_id: int) : list(int)` + +### Invariants & guards +- Creation: `options` length ≥ 2; unique options; `open_h < close_h` +- Voting: now in [open_h, close_h); one vote per address; index bounds +- Closing: only owner or after `close_h`; id must exist; idempotent + +### Events +- `PollCreated(poll_id, owner)` +- `Voted(poll_id, voter, option_idx)` +- `Closed(poll_id)` + +### Gas considerations +- Use `Map` for `tally` and `votesByAddress` +- Avoid loops over all voters to compute results +- Keep strings short + +## Next Steps + +- **[Testing & Deployment](./test-and-deploy)** - Test and deploy your contracts +- **[Plugin Integration](./integrate-and-plugin-sdk)** - Integrate your contract into Superhero + +## Further Reading + +- **[References](./references)** - Complete list of documentation links +- [Sophia Syntax](https://github.com/aeternity/aesophia/blob/master/docs/sophia_syntax.md) - Language syntax reference +- [Sophia Features](https://github.com/aeternity/aesophia/blob/master/docs/sophia_features.md) - Language features +- [Sophia Stdlib](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md) - Standard library diff --git a/docs/tutorials/hackathon/feed-plugins.md b/docs/tutorials/hackathon/feed-plugins.md new file mode 100644 index 000000000..7d0418ada --- /dev/null +++ b/docs/tutorials/hackathon/feed-plugins.md @@ -0,0 +1,204 @@ +--- +title: Feed Plugins +--- + +## Overview + +Feed plugins allow you to inject custom content into the Superhero unified feed. Your plugin content can appear alongside regular posts and can optionally be included in the popular feed ranking system. + +## Feed Plugin Interface + +A feed plugin implements the `FeedPlugin` interface: + +```typescript +import type { FeedPlugin, FeedEntry } from '@/features/social/feed-plugins/types'; + +export type FeedPage = { + entries: FeedEntry[]; + nextPage?: number; // undefined when no more pages +}; + +export type FeedPlugin = { + kind: string; // Unique identifier for your content type (e.g., "poll", "nft") + fetchPage?: (page: number) => Promise>; + useLive?: () => { push: (entry: FeedEntry) => void } | void; + Render: (props: { entry: FeedEntry; onOpen?: (id: string) => void }) => JSX.Element; + Skeleton?: React.FC; // Optional loading placeholder + getComposerActions?: (ctx: ComposerActionCtx) => ComposerAction[]; +}; +``` + +## Feed Entry Structure + +```typescript +export type FeedEntry = { + id: string; // Unique identifier + kind: string; // Content type (matches plugin.kind) + createdAt: string; // ISO timestamp + data: T; // Your custom data +}; +``` + +## Basic Feed Plugin Example + +```typescript +import { FeedPlugin, FeedEntry } from '@/features/social/feed-plugins/types'; +import { registerPlugin } from '@/features/social/feed-plugins/registry'; + +const myFeedPlugin: FeedPlugin = { + kind: 'my-content', + + async fetchPage(page: number) { + // Fetch your content from API or contract + const response = await fetch(`/api/my-content?page=${page}`); + const items = await response.json(); + + return { + entries: items.map(item => ({ + id: item.id, + kind: 'my-content', + createdAt: item.created_at, + data: item, + })), + nextPage: items.length > 0 ? page + 1 : undefined, + }; + }, + + Render: ({ entry, onOpen }) => { + return ( +
onOpen?.(entry.id)}> +

{entry.data.title}

+

{entry.data.description}

+
+ ); + }, + + Skeleton: () =>
Loading...
, +}; + +// Register the plugin +registerPlugin(myFeedPlugin); +``` + +## Popular Feed Injection + +To have your plugin content appear in the popular feed, you need to: + +1. **Implement PopularRankingContributor** (backend API plugin) +2. **Use proper ID format** in feed entries + +### ID Format for Popular Feed + +Your feed entry IDs should follow this format: +``` +{plugin-name}:{unique-id} +``` + +Example: +- `poll:123` for a poll with sequence ID 123 +- `poll:hash_abc123` for a poll with hash identifier + +### Backend Integration + +See the [API Plugin Development](./api-plugin-development) guide for implementing `PopularRankingContributor` on the backend. + +## Next Steps + +- **[API Plugin Development](./api-plugin-development)** - Build backend plugins for ranking +- **[Plugin Integration](./integrate-and-plugin-sdk)** - Complete plugin integration guide +- **[Plugin SDK Documentation](../../plugin-sdk)** - Full Plugin SDK API reference + +## Live Updates + +Use the `useLive` hook to push real-time updates: + +```typescript +const myFeedPlugin: FeedPlugin = { + kind: 'my-content', + + useLive() { + const { push } = useWebSocket('/ws/my-content', (event) => { + push({ + id: event.id, + kind: 'my-content', + createdAt: event.created_at, + data: event, + }); + }); + + return { push }; + }, + + // ... rest of plugin +}; +``` + +## Composer Integration + +Feed plugins can also provide composer actions: + +```typescript +const myFeedPlugin: FeedPlugin = { + kind: 'my-content', + + getComposerActions(ctx) { + return [ + { + id: 'create-my-content', + label: 'Create My Content', + icon: '🎨', + onClick: () => { + ctx.navigate('/create-my-content'); + }, + }, + ]; + }, + + // ... rest of plugin +}; +``` + +## Best Practices + +1. **Pagination**: Always implement `fetchPage` with proper pagination +2. **Error Handling**: Handle errors gracefully in `fetchPage` +3. **Performance**: Use `Skeleton` component for loading states +4. **ID Uniqueness**: Ensure IDs are globally unique +5. **Timestamps**: Use ISO 8601 format for `createdAt` + +## Example: Poll Feed Plugin + +```typescript +import { FeedPlugin, FeedEntry } from '@/features/social/feed-plugins/types'; +import { GovernanceApi } from '@/api/governance'; + +const pollFeedPlugin: FeedPlugin = { + kind: 'poll-created', + + async fetchPage(page: number) { + const polls = await GovernanceApi.getPolls({ page, limit: 20 }); + + return { + entries: polls.items.map(poll => ({ + id: `poll:${poll.poll_seq_id}`, + kind: 'poll-created', + createdAt: poll.created_at, + data: poll, + })), + nextPage: polls.hasMore ? page + 1 : undefined, + }; + }, + + Render: ({ entry, onOpen }) => ( + onOpen?.(entry.id)} + /> + ), + + Skeleton: () => , +}; + +registerPlugin(pollFeedPlugin); +``` + diff --git a/docs/tutorials/hackathon/hints.md b/docs/tutorials/hackathon/hints.md new file mode 100644 index 000000000..ec900882c --- /dev/null +++ b/docs/tutorials/hackathon/hints.md @@ -0,0 +1,84 @@ +--- +title: Hints & Tips +--- + +## AI-Assisted Development + + +Adding this documentation to MCP (Model Context Protocol) in Cursor or other AI editors will significantly improve your AI-assisted development and debugging experience. The AI will have access to all documentation context, making it more accurate and helpful. See [Configure Cursor](./configure-cursor) for setup instructions. + + +### Using Cursor for Contract Development +- **Scaffold contracts**: Describe your contract rules and let Cursor generate the state and entrypoints +- **Tighten invariants**: Add `require` checks for time windows, unique votes, and bounds +- **Extend tests**: Add negative test cases first, then fix the contract based on failures +- **Gas optimization**: Avoid unbounded loops; use `Map` lookups; keep events small + +### Using Cursor for Plugin Development +- **Generate plugin structure**: Describe your plugin's purpose and let Cursor scaffold the basic structure +- **Implement interfaces**: Use Cursor to implement `FeedPlugin` or `PopularRankingContributor` interfaces +- **Add error handling**: Generate comprehensive error handling for API calls and data processing +- **Write tests**: Create test cases for your plugin's functionality + +## Troubleshooting + +### Common Contract Issues +- **Compiler version error**: Adjust the pragma version or Docker image version +- **Cannot reach compiler**: Ensure Docker container is running on port `:3080`; check environment variables +- **Decoding errors**: Simplify return types; ensure ACI matches bytecode +- **Testnet transaction failures**: Ensure your key is funded; verify gas/fee limits and node URL +- **Debug quickly**: Use dry-run calls; add temporary view entrypoints (remove before deploying) + +### Common Plugin Issues +- **Plugin not loading**: Check that the plugin is properly registered in the configuration +- **API errors**: Verify API endpoints and authentication tokens +- **Feed not updating**: Ensure your plugin is correctly implementing the `FeedPlugin` interface +- **Ranking not working**: Verify that `PopularRankingContributor` is properly implemented and registered + +### Getting Help +- Check the [æforum](https://forum.aeternity.com/) for community discussions +- Review the [References](./references) page for documentation links +- Search existing GitHub issues in the Superhero repositories + +## Security Best Practices + +### Contract Security +- **Authentication**: Use `Call.caller` to verify transaction sender; reject unauthorized changes early +- **Time windows**: Enforce open/close times consistently; prevent late voting or expired actions +- **Input validation**: Validate all inputs (non-empty strings, ≥ 2 options, index bounds, unique values) +- **Access control**: Guard against duplicate actions (e.g., one vote per address via `votesByAddress`) +- **Gas optimization**: Use `Map` for lookups; avoid unbounded loops; keep strings small +- **Events**: Emit events (`PollCreated`, `Voted`, `Closed`) with small payloads +- **ACI stability**: Use simple return types; maintain stable entrypoint names +- **Testing**: Include negative test cases; dry-run heavy execution paths +- **Versioning**: Pin compiler version; document init arguments; plan for migrations + +### Plugin Security +- **Input sanitization**: Validate and sanitize all user inputs and API responses +- **Error handling**: Never expose sensitive information in error messages +- **Rate limiting**: Implement rate limiting for API calls to prevent abuse +- **Authentication**: Securely store and use API keys and tokens +- **Data validation**: Validate all data before processing or storing + +## Pre-Deployment Checklist + +### Contract Checklist +- [ ] Environment ready (`VITE_*` variables set); addresses configured per network +- [ ] Contract compiled and tested (positive + negative test cases) +- [ ] Integration wired (wallet connect; ACI loaded; errors handled) +- [ ] Gas costs reviewed and optimized +- [ ] Security audit completed (or at least reviewed) +- [ ] README includes setup instructions, environment docs, contract addresses, and sample accounts + +### Plugin Checklist +- [ ] Plugin properly registered in configuration +- [ ] All required interfaces implemented correctly +- [ ] Error handling comprehensive and tested +- [ ] API endpoints verified and authenticated +- [ ] Rate limiting implemented where needed +- [ ] Documentation updated with plugin usage instructions +- [ ] Integration tests passing + + +Keep a development log of issues you encounter and their solutions. This will help you debug faster in the future! + diff --git a/docs/tutorials/hackathon/integrate-and-plugin-sdk.md b/docs/tutorials/hackathon/integrate-and-plugin-sdk.md new file mode 100644 index 000000000..269f59db4 --- /dev/null +++ b/docs/tutorials/hackathon/integrate-and-plugin-sdk.md @@ -0,0 +1,239 @@ +--- +title: Plugin Integration +--- + +## Integrate into Superhero (where code goes) +Call your contract from a Superhero plugin using the JS SDK and Plugin SDK. + +Folder layout in this repo: +``` +src/plugins/your-plugin/ + index.tsx # exports `definePlugin({...})` (entry) + contract-artifacts/ # optional: copied ACI JSON, addresses + components/... # your UI +``` + +Register your plugin in `src/plugins/local.ts` (static import + add to the `localPlugins` array): +```ts +// src/plugins/local.ts +import yourPlugin from '@/plugins/your-plugin'; + +const localPlugins = [ + // ...other plugins + yourPlugin, +]; +``` + + +New to the Plugin SDK? See below for capabilities and examples. + + +## Bring in your contract artifacts +- Address: read from your contracts repo (e.g., `deployments/testnet/Poll.address`). +- ACI: use the compiled JSON (e.g., `aci/Poll.json`) to instantiate the contract via the JS SDK. + +Options: + +- Publish artifacts from the contracts repo (package, URL, or raw files) and import them in your plugin. +- For hackathons, you can copy them into `src/plugins/your-plugin/contract-artifacts/` and reference locally. + +### Expose contract address +Provide a network‑specific address via env, e.g. `VITE_POLL_CONTRACT` in `.env.local`. + +### Obtain ACI +Load from source in tests, or keep a built ACI JSON alongside your plugin. + +### Wallet connect +Use `ensureWallet()` in attachments (or initialize via your app shell) to obtain a connected SDK before sending transactions. + +### Plugin entry skeleton +```ts +// src/plugins/your-plugin/index.tsx +import { definePlugin, type ComposerAttachmentSpec } from '@/plugin-sdk'; + +export default definePlugin({ + meta: { id: 'your-plugin', name: 'Your Plugin', version: '0.1.0', apiVersion: '1.x', capabilities: ['composer'] }, + setup({ register }) { + const attachment: ComposerAttachmentSpec = { + id: 'your-attachment', + label: 'Your Action', + Panel: () => null, + validate: () => [], + onAfterPost: async (ctx) => { + const { sdk } = await ctx.ensureWallet(); + // use sdk + your ACI/address here + }, + }; + register({ attachments: () => [attachment] }); + }, +}); +``` + +### Add translations (optional) + +Plugins can include their own translation files for internationalization. Each plugin's translations are automatically registered using the plugin ID as the i18n namespace. + +**1. Create `locales/` directory structure:** +``` +src/plugins/your-plugin/ +├── index.tsx +├── locales/ +│ ├── en.json +│ └── index.ts +└── components/ + └── YourComponent.tsx +``` + +**2. Create `locales/en.json` with your translation keys:** +```json +{ + "createdItem": "created an item", + "pending": "Pending…", + "actions": { + "like": "Like", + "share": "Share" + } +} +``` + +**3. Create `locales/index.ts` to export translations:** +```ts +import en from './en.json'; + +export const translations = { + en, + // Add more languages: + // de: require('./de.json'), + // fr: require('./fr.json'), +}; +``` + +**4. Import and export translations in your plugin definition:** +```ts +// src/plugins/your-plugin/index.tsx +import { definePlugin, type ComposerAttachmentSpec } from '@/plugin-sdk'; +import { translations } from './locales'; + +export default definePlugin({ + meta: { id: 'your-plugin', name: 'Your Plugin', version: '0.1.0', apiVersion: '1.x', capabilities: ['composer'] }, + translations, // Export translations + setup({ register }) { + // ... + }, +}); +``` + +**5. Use translations in your components:** +```ts +import { useTranslation } from 'react-i18next'; + +export default function YourComponent() { + // Use plugin ID as namespace + const { t } = useTranslation('your-plugin'); + + return ( +
+ {t('createdItem')} + {t('pending')} + {t('actions.like')} +
+ ); +} +``` + + +See the [Plugin SDK docs](../../plugin-sdk#translations) for more details on translations and multi-language support. + + +### Example +```ts +import { AeSdk, Node } from '@aeternity/aepp-sdk' + +const aeSdk = new AeSdk({ + nodes: [{ name: 'net', instance: new Node(import.meta.env.VITE_NODE_URL) }], + compilerUrl: import.meta.env.VITE_COMPILER_URL, +}) + +const contract = await aeSdk.getContractInstance({ + aci: pollAciJson, + address: import.meta.env.VITE_POLL_CONTRACT, +}) + +const results = await contract.methods.get_results(0) +``` + + +Emit small on‑chain events and push entries to the feed via `pushFeedEntry` for heavier off‑chain work. + + +## Plugin SDK Capabilities + +### Core Capabilities (v1.x) +- `feed`: add new item kinds to the unified feed (see [Feed Plugins Guide](./feed-plugins)) +- `composer`: add actions and attachments (interactive panels) +- `item-actions`: contextual actions on feed items +- `routes`: add pages to the app router +- `modals`: register reusable modals +- `menu`: contribute navigation items + +### Popular Feed Integration + +Plugins can contribute content to the popular feed through: + +1. **Frontend Feed Plugins**: Register feed plugins with proper ID format (`{plugin-name}:{id}`) + - See [Feed Plugins](./feed-plugins) for complete guide + +2. **Backend API Plugins**: Implement `PopularRankingContributor` interface + - See [API Plugin Development](./api-plugin-development) for backend integration + +## Next Steps + +- **[Feed Plugins](./feed-plugins)** - Add content to the unified feed +- **[API Plugin Development](./api-plugin-development)** - Build backend plugins +- **[Plugin SDK Documentation](../../plugin-sdk)** - Complete API reference +- **[Hints & Tips](./hints)** - Development tips and troubleshooting + +### Core types (simplified) +```ts +export type ComposerActionCtx = { + insertText(text: string): void; + navigate(to: string): void; + storage: { get(k: string): any; set(k: string, v: any): void }; + theme: { colorScheme: 'light' | 'dark' }; + events: { emit(e: string, p?: any): void; on(e: string, h: (p: any)=>void): () => void }; + cacheLink?: (postId: string, kind: string, payload: any) => void; + pushFeedEntry?: (kind: string, entry: any) => void; +}; + +export type ComposerAttachmentCtx = ComposerActionCtx & { + getValue(ns: string): T | undefined; + setValue(ns: string, value: T): void; + ensureWallet(): Promise<{ sdk: any; currentBlockHeight?: number }> +}; +``` + +### Attachment example +```ts +import { definePlugin, type ComposerAttachmentSpec } from '@superhero/plugin-sdk'; + +export default definePlugin({ + meta: { id: 'org.example', name: 'Example', version: '1.0.0', apiVersion: '1.x', capabilities: ['composer'] }, + setup({ register }) { + const spec: ComposerAttachmentSpec = { + id: 'example-attachment', + label: 'Example', + Panel: ({ ctx, onRemove }) => null, + validate: (ctx) => [], + onAfterPost: async (ctx, post) => { + const { sdk } = await ctx.ensureWallet(); + ctx.pushFeedEntry?.('example', { id: post.id, createdAt: new Date().toISOString(), kind: 'example', data: {} }); + }, + }; + register({ attachments: () => [spec] }); + } +}); +``` + + +Keep attachments lean: validate inputs, avoid heavy on-chain loops, and prefer emitting small events or pushing feed entries for off‑chain processing. + diff --git a/docs/tutorials/hackathon/prerequisites.md b/docs/tutorials/hackathon/prerequisites.md new file mode 100644 index 000000000..d255412e1 --- /dev/null +++ b/docs/tutorials/hackathon/prerequisites.md @@ -0,0 +1,44 @@ +--- +title: Prerequisites +--- + + +Install the required tools and dependencies before starting development. + + +## Required Software + +- **Node.js** LTS (e.g., 20.x) +- **Git** +- **Docker** +- **Cursor** (or VS Code) + +## Install aeproject + +aeproject is the recommended CLI tool for Sophia project management: + +```bash +npm i -g @aeternity/aeproject@latest +``` + +Verify the installation: + +```bash +aeproject --version +``` + +## Verify Installation + +Check that all tools are properly installed: + +```bash +node -v +npm -v +docker --version +aeproject --version +``` + + +If any command fails, ensure the tool is installed and added to your system PATH. + + diff --git a/docs/tutorials/hackathon/project-setup.md b/docs/tutorials/hackathon/project-setup.md new file mode 100644 index 000000000..00c1edbf5 --- /dev/null +++ b/docs/tutorials/hackathon/project-setup.md @@ -0,0 +1,211 @@ +--- +title: Project Setup +--- + +You'll work with multiple repositories: + +1. **Contracts** (create your own): Sophia code, tests, aeproject, deployments, ACI artifacts. +2. **[Superhero UI](https://github.com/superhero-com/superhero)**: the complete Superhero frontend application where you'll add your frontend plugin. +3. **[Superhero API](https://github.com/superhero-com/superhero-api)** (optional): backend application for transaction processing and popular feed integration. + +## Quick Setup (All-in-One) + +Run this script to set up everything at once. Replace `` with your plugin identifier (e.g., `crowdfunding`): + +```bash +#!/bin/bash +# Superhero Plugin Workspace Setup Script + +# Set your plugin ID (replace with your actual plugin name) +PLUGIN_ID="" + +# Create project directory +mkdir -p superhero-plugin-workspace && cd superhero-plugin-workspace + +# Clone repositories +echo "📦 Cloning repositories..." +git clone https://github.com/superhero-com/superhero.git +git clone https://github.com/superhero-com/superhero-api.git + +# Create and initialize contracts repository +echo "📁 Creating contracts repository..." +mkdir -p ${PLUGIN_ID}-contracts && cd ${PLUGIN_ID}-contracts +git init +cd .. + +# Create workspace file +echo "⚙️ Creating workspace file..." +cat > superhero.code-workspace << EOF +{ + "folders": [ + { + "name": "Contracts", + "path": "./${PLUGIN_ID}-contracts" + }, + { + "name": "Superhero UI", + "path": "./superhero" + }, + { + "name": "Superhero API", + "path": "./superhero-api" + } + ], + "settings": { + "files.exclude": { + "**/node_modules": true, + "**/dist": true, + "**/.git": false + }, + "search.exclude": { + "**/node_modules": true, + "**/dist": true + } + } +} +EOF + +echo "" +echo "✅ Setup complete! Project structure:" +echo " 📁 Contracts: ./${PLUGIN_ID}-contracts/" +echo " 📁 UI: ./superhero/" +echo " 📁 API: ./superhero-api/" +echo "" +echo "📝 Next steps:" +echo " 1. Open workspace: cursor superhero.code-workspace" +echo " 2. Initialize contracts: cd ${PLUGIN_ID}-contracts && aeproject init" +echo " 3. Follow the setup guides for UI and API repositories" +``` + +**To use this script:** + +1. Copy the script above +2. Replace `` with your plugin name (e.g., `crowdfunding`) +3. Save as `setup-workspace.sh` +4. Make it executable: `chmod +x setup-workspace.sh` +5. Run: `./setup-workspace.sh` + +Or run directly in one line (replace ``): + +```bash +PLUGIN_ID="" && mkdir -p superhero-plugin-workspace && cd superhero-plugin-workspace && git clone https://github.com/superhero-com/superhero.git && git clone https://github.com/superhero-com/superhero-api.git && mkdir -p ${PLUGIN_ID}-contracts && cd ${PLUGIN_ID}-contracts && git init && cd .. && cat > superhero.code-workspace << EOF +{ + "folders": [ + {"name": "Contracts", "path": "./${PLUGIN_ID}-contracts"}, + {"name": "Superhero UI", "path": "./superhero"}, + {"name": "Superhero API", "path": "./superhero-api"} + ], + "settings": { + "files.exclude": {"**/node_modules": true, "**/dist": true, "**/.git": false}, + "search.exclude": {"**/node_modules": true, "**/dist": true} + } +} +EOF +echo "✅ Setup complete! Open with: cursor superhero.code-workspace" +``` + +## Manual Setup + +If you prefer to set up step by step: + +### Step 1: Clone Repositories + +```bash +# Create project directory +mkdir superhero-plugin-workspace && cd superhero-plugin-workspace + +# Clone Superhero UI +git clone https://github.com/superhero-com/superhero.git + +# Clone Superhero API (optional, only if building backend plugins) +git clone https://github.com/superhero-com/superhero-api.git +``` + +### Step 2: Create Contracts Repository + +```bash +# Create contracts repository (replace with your plugin name) +PLUGIN_ID="" +mkdir -p ${PLUGIN_ID}-contracts && cd ${PLUGIN_ID}-contracts +git init +cd .. +``` + +### Step 3: Create Workspace File + +Create `superhero.code-workspace` in the root directory: + +```json +{ + "folders": [ + { + "name": "Contracts", + "path": "./-contracts" + }, + { + "name": "Superhero UI", + "path": "./superhero" + }, + { + "name": "Superhero API", + "path": "./superhero-api" + } + ], + "settings": { + "files.exclude": { + "**/node_modules": true, + "**/dist": true, + "**/.git": false + }, + "search.exclude": { + "**/node_modules": true, + "**/dist": true + } + } +} +``` + +Replace `` with your actual plugin identifier. + +### Step 4: Open Workspace + +**Cursor**: `File → Open Workspace from File...` → select `superhero.code-workspace` + +**VS Code**: `File → Open Workspace from File...` → select `superhero.code-workspace` + +Or from terminal: +```bash +cursor superhero.code-workspace +# or +code superhero.code-workspace +``` + +## Initialize Contracts Repository + +After creating your contracts repository, initialize it with aeproject: + +```bash +cd -contracts +aeproject init +``` + +This will create the basic project structure: +- `contracts/` - Sophia source files (`.aes`) +- `tests/` - TypeScript tests (if using SDK/Vitest) +- `scripts/` - Utility scripts (optional) +- `aeproject.json` - aeproject configuration +- `deployments/` - Contract addresses per network +- `aci/` - Compiled ACIs (JSON) + +## Workspace Benefits + +- Navigate between repositories easily +- Search across all repos +- Better code completion and IntelliSense +- Single terminal for all repos +- Unified Git operations + + +Keep your contracts repository separate from the Superhero UI and API repositories. This allows you to version control contracts independently and reuse them across different projects. Use workspace configuration to manage all repos together in your IDE. + + diff --git a/docs/tutorials/hackathon/quickstart.md b/docs/tutorials/hackathon/quickstart.md new file mode 100644 index 000000000..7b3c6a93f --- /dev/null +++ b/docs/tutorials/hackathon/quickstart.md @@ -0,0 +1,193 @@ +--- +title: Quickstart & AI Prompt +--- + + +Short on time? Follow this quick path to get a plugin running end‑to‑end. + + + +Configure Cursor with MCP access to this documentation for the best AI assistance experience. See [Configure Cursor](./configure-cursor) to set up the MCP server connection. This significantly improves AI accuracy and speeds up development. + + +## AI‑assisted quick start (Cursor or similar) + +Use this copy‑paste bootstrap prompt in an AI IDE like [Cursor](https://www.cursor.com) or any editor with an agent. **Make sure you've configured MCP access** (see tip above) for optimal results. The prompt will guide the agent to ask for a short description first, then scaffold both repos and wire the plugin end‑to‑end on æternity testnet. + +```text +Goal: Build a complete Superhero plugin (contracts + frontend plugin + optional backend plugin) on æternity testnet with robust Sophia/compiler hygiene and a clean UI integration. + +Strict interaction rule +- First, send exactly the question below and STOP. Do not proceed until I reply. +- First message to send: + "Please describe in 1–2 sentences what you want the Superhero plugin to do (the core action, any write operations, what users see, and whether you need backend processing for the popular feed)." +- After I reply, proceed autonomously with the plan below. If I reply "default", use the Crowdfunding example defined here. + +Defaults +- Network: testnet + - VITE_NODE_URL=https://testnet.aeternity.io + - COMPILER_URL=https://compiler.aeternity.io +- Names (derived from my description unless I say “default”): + - CONTRACT_NAME: PascalCase (default Crowdfund) + - PLUGIN_ID: kebab-case (default crowdfunding) + - PLUGIN_NAME: Title Case (default Crowdfunding) + - Contracts repo folder: -contracts + - UI branch: feat/ + - Address env: VITE__CONTRACT +- Secrets live in .env.* only; never print them. + +Project structure setup +- Use the workspace setup script from Project Setup guide to create the complete structure: + - Clone Superhero UI: https://github.com/superhero-com/superhero.git + - Clone Superhero API: https://github.com/superhero-com/superhero-api.git (if backend plugin needed) + - Create contracts repo: -contracts and initialize git + - Create workspace file: superhero.code-workspace +- Or follow manual setup steps from Project Setup guide + +UI repository +- Repo: https://github.com/superhero-com/superhero.git +- Create branch: feat/ in the cloned repo + +Backend API repository +- Repo: https://github.com/superhero-com/superhero-api.git +- Clone and set up backend repo for transaction processing and popular feed integration +- Implement backend plugins to process blockchain transactions and contribute to the popular feed +- Follow Backend API Setup guide for environment configuration + +Sophia contract generation rules (hosted compiler) +- Target https://compiler.aeternity.io. +- Use indentation-sensitive Sophia with spaces only (no tabs). Keep 2 or 4 spaces consistently. +- Inside the contract, define `record state` first. Align closing `}` with the opening keyword column. +- Declare init exactly: `entrypoint init() = { ... }`. +- Do not declare extra record types above the `contract` line. If needed, prefer only `record state` or simple maps in `state`. +- No leading blank line after `contract Name =`, avoid stray blank lines in the header. +- Emit events only after state and init, using: + - `datatype event = ...` + - `Chain.event(EventCtor(...))` + +Local checks / workarounds +- If `/aci` returns “Unexpected indentation”, sanity-check: + contract X = + record state = { n : int } + entrypoint init() = { n = 0 } +- If ACI endpoint remains strict, deploy via SDK and capture ACI; write: + - deployments/testnet/.address + - aci/.json + +Plan (after I reply to the initial question) + +0) Project setup +- Use workspace setup script or manual steps to create project structure: + - Clone Superhero UI and API repos + - Create contracts repo -contracts and initialize git + - Create workspace file superhero.code-workspace +- Open workspace in Cursor/IDE + +1) Contracts repo (aeproject) +- Navigate to -contracts directory +- Initialize aeproject: `aeproject init` +- Create `contracts/.aes` implementing the requested behavior (default Crowdfunding with create_campaign, contribute, refund, withdraw, get_campaign). +- Emit events for off-chain indexing. +- Add tests (positive + negative). +- Deploy to testnet; write artifacts: + - deployments/testnet/.address + - aci/.json +- Conventional commits. + +2) Superhero UI plugin +- Navigate to superhero directory +- Create branch: `git checkout -b feat/` +- Create `src/plugins//` (+ `contract-artifacts/`). +- Plugin entry using `definePlugin`: composer attachment(s) and item actions as needed by the plugin description. +- Load ACI from `src/plugins//contract-artifacts/.json` (or import from the contracts repo), address from `VITE__CONTRACT`. +- Register in `src/plugins/local.ts`. +- `.env.local`: + - VITE_NODE_URL=https://testnet.aeternity.io + - VITE_COMPILER_URL=https://compiler.aeternity.io + - VITE__CONTRACT=
+- Conventional commit. + +3) Backend API Plugin +- Navigate to superhero-api directory +- Set up environment: copy `.env.example` to `.env` and configure database/Redis +- Create backend plugin for transaction processing: + - Extend `BasePlugin` in `src/plugins//.plugin.ts` + - Create sync service extending `BasePluginSyncService` to process blockchain transactions + - Register plugin in `src/app.module.ts` + - Configure filters to match contract calls + - Process transactions and extract data +- For popular feed integration, implement `PopularRankingContributor` interface +- See API Plugin Development guide for details. + +4) Smoke test +- Run contract tests; start UI; verify reads; perform one write with Superhero Wallet on testnet. +- If backend plugin exists, start API server and verify transaction processing and popular feed contribution. + +Output format +- Commands in fenced code blocks +- File diffs for created/edited files +- Conventional commits with short bullet bodies + +Collision avoidance +- Before choosing PLUGIN_ID, list `src/plugins/` and avoid existing IDs; if taken, append “-ext”. +- Ensure the contracts repo folder name is unique locally; if needed append “-2”. +``` + +## 1) Setup Environment + +Complete the setup steps (5–10 minutes): +- [Prerequisites](./prerequisites) - Install required software (Node.js, Git, Docker, Cursor) +- [Wallet Setup](./wallet-setup) - Install and configure Superhero Wallet, fund testnet account +- [Configure Cursor](./configure-cursor) - **Important**: Set up MCP access to documentation for better AI assistance +- [Project Setup](./project-setup) - Create your contracts repository and configure workspace +- [Backend API Setup](./backend-setup) - Set up backend API repository (only if building backend plugins) + +## 2) Scaffold Project (2–5 minutes) + +- Follow: [Project Scaffold](./scaffold-and-compiler) +- Initialize aeproject: `aeproject init` +- Install dependencies, set up `contracts/`, `tests/`, `scripts/` + +## 3) Write Your Contract (10–20 minutes) + +- Learn Sophia basics: [Smart Contracts](./contracts) +- Use the contract examples and patterns provided +- With AI assistance, describe your contract logic and let Cursor generate the code +- Emit events for off-chain indexing + +## 4) Test Locally (5–10 minutes) + +- Follow: [Testing & Deployment](./test-and-deploy) +- Compile: `aeproject compile` +- Test: `aeproject test` or write custom Vitest tests +- Verify all entrypoints work correctly + +## 5) Deploy to Testnet (2–5 minutes) + +- Continue with: [Testing & Deployment](./test-and-deploy) +- Deploy: `aeproject deploy --network testnet` +- Save the contract address and ACI JSON files +- Store in `deployments/testnet/` and `aci/` directories + +## 6) Integrate into Superhero (10–15 minutes) + +- Follow: [Plugin Integration](./integrate-and-plugin-sdk) +- Create plugin directory: `src/plugins//` +- Copy contract ACI and address to plugin +- Implement plugin with `definePlugin` +- Register in `src/plugins/local.ts` +- Configure `.env.local` with contract address +- Test the integration + +## Next Steps + +Once your plugin is running: + +- **[Feed Plugins](./feed-plugins)** - Add your plugin's content to the unified feed +- **[API Plugin Development](./api-plugin-development)** - Build backend plugins for transaction processing +- **[Plugin SDK Documentation](../../plugin-sdk)** - Complete API reference for advanced features +- **[Hints & Tips](./hints)** - Troubleshooting and best practices + + +Having MCP configured makes it much easier to iterate and debug. The AI will have full context of the documentation, making it more helpful for fixing issues and adding features. + diff --git a/docs/tutorials/hackathon/references.md b/docs/tutorials/hackathon/references.md new file mode 100644 index 000000000..69fa162df --- /dev/null +++ b/docs/tutorials/hackathon/references.md @@ -0,0 +1,68 @@ +--- +title: References +--- + +## Superhero Wallet + +### Browser Extensions +- **[Chrome Extension](https://chromewebstore.google.com/detail/superhero-wallet/mnhmmkepfddpifjkamaligfeemcbhdne)** - Install Superhero Wallet for Chrome/Chromium +- **[Firefox Extension](https://addons.mozilla.org/en-US/firefox/addon/superhero-wallet/)** - Install Superhero Wallet for Firefox + +### Mobile Apps +- **[Android App](https://play.google.com/store/apps/details?id=com.superhero.cordova)** - Download Superhero Wallet for Android +- **[iOS App](https://apps.apple.com/us/app/superhero-wallet/id1502786641)** - Download Superhero Wallet for iOS + +### Web Wallet +- **[Web Wallet](https://wallet.superhero.com/)** - Access Superhero Wallet via web browser + +## Testnet Faucet + +Get testnet AE tokens for development and testing: +- **[æternity Testnet Faucet](https://faucet.aepps.com/)** - Request testnet AE tokens + + +Switch your wallet to testnet in settings before requesting tokens from the faucet. + + +## æternity Documentation + +### Core Documentation +- **[Docs hub](https://docs.aeternity.com)** - Complete æternity blockchain documentation +- **[Syntax](https://github.com/aeternity/aesophia/blob/master/docs/sophia_syntax.md)** - Sophia smart contract syntax reference +- **[Features](https://github.com/aeternity/aesophia/blob/master/docs/sophia_features.md)** - Sophia language features and capabilities +- **[Stdlib](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md)** - Standard library functions and modules + +### Development Tools +- **[Compiler HTTP](https://github.com/aeternity/aesophia_http)** - HTTP API for Sophia compiler +- **[JS SDK](https://github.com/aeternity/aepp-sdk-js)** - JavaScript SDK for æternity development +- **[aeproject](https://github.com/aeternity/aeproject)** - CLI tool for Sophia project management + +### Community & Support +- **[æforum](https://forum.aeternity.com/)** - Ask questions and get help from the community + +## Superhero Resources + +### Plugin Development +- **[Plugin SDK Documentation](../../plugin-sdk)** - Complete Plugin SDK reference +- **[Feed Plugins Guide](./feed-plugins)** - Feed plugin development guide +- **[API Plugin Development](./api-plugin-development)** - Backend plugin development guide + +### GitHub Repositories +- **[Superhero Frontend](https://github.com/superhero-com/superhero)** - Main Superhero application repository +- **[Superhero API](https://github.com/superhero-com/superhero-api)** - Backend API repository + +### Block Explorers +- **[ÆScan](https://aescan.io/)** - æternity blockchain explorer +- **[Superhero Explorer](https://explorer.superhero.com/)** - Superhero's blockchain explorer + +## Development Tools + +### CLI Tools +- **[aeproject](https://github.com/aeternity/aeproject)** - Project scaffolding and management +- **[aeternity CLI](https://github.com/aeternity/aeternity)** - æternity node and tools + +### Testing & Deployment +- **[Testnet Node](https://testnet.aeternity.io/)** - æternity testnet node endpoint +- **[Mainnet Node](https://mainnet.aeternity.io/)** - æternity mainnet node endpoint +- **[Compiler Service](https://compiler.aeternity.io/)** - Public Sophia compiler endpoint + diff --git a/docs/tutorials/hackathon/scaffold-and-compiler.md b/docs/tutorials/hackathon/scaffold-and-compiler.md new file mode 100644 index 000000000..24c1314b6 --- /dev/null +++ b/docs/tutorials/hackathon/scaffold-and-compiler.md @@ -0,0 +1,103 @@ +--- +title: Project Scaffold +--- + +## Recommended: aeproject +Initialize a project and run local services with one CLI. + + +Run all commands in your contracts repo (created in Setup). This repo holds contracts, tests, and aeproject config. + + +### Init a new project +```bash +mkdir my-aepp && cd my-aepp +aeproject init +``` +This creates a standard structure (contracts, deploy/test scaffolds, config). + +### Start local node and compiler +```bash +aeproject node # starts a local devnet in Docker +# (Optional) compiler is started automatically for commands that need it +``` + +### Compile contracts +```bash +aeproject compile +``` + + +Check `aeproject.json` (or CLI help) for network profiles and paths. + + +## Backend API Setup (Optional) + +If you're building backend plugins for transaction processing or popular feed integration: + +- **[Backend API Setup](./backend-setup)** - Set up the Superhero API repository +- Clone the backend repo and configure environment +- Set up database and Redis services + +## Next Steps + +- **[Smart Contracts](./contracts)** - Learn Sophia contract development +- **[Testing & Deployment](./test-and-deploy)** - Test and deploy your contracts +- **[Backend API Setup](./backend-setup)** - Set up backend for API plugins (if needed) + +## Optional: manual setup (SDK + Docker compiler) +If you prefer a minimal stack, you can still set up manually: + +### Create folders +```bash +mkdir -p contracts tests scripts +``` + +### Initialize project and install deps +```bash +npm init -y +npm i @aeternity/aepp-sdk dotenv +npm i -D vitest ts-node typescript @types/node +npx tsc --init +``` + +Add scripts to `package.json`: +```json +{ + "scripts": { + "test": "vitest run", + "test:watch": "vitest" + } +} +``` + +### Environment profiles +`.env.local` +``` +NODE_URL=http://localhost:3013 +COMPILER_URL=http://localhost:3080 +SECRET_KEY=your_local_dev_private_key_hex +``` + +`.env.testnet` +``` +NODE_URL=https://testnet.aeternity.io +COMPILER_URL=http://localhost:3080 +SECRET_KEY=your_funded_testnet_private_key_hex +``` + +### Start the compiler (Docker) +```bash +docker run --rm -p 3080:3080 aeternity/aesophia_http:latest +``` + + +This exposes the compiler at `http://localhost:3080`. + + +### Pinning versions +Use the compiler pragma in your Sophia source (example): +``` +@compiler >= 6.5.0 +``` +Adjust to a version supported by your `aesophia_http` image. diff --git a/docs/tutorials/hackathon/setup.md b/docs/tutorials/hackathon/setup.md new file mode 100644 index 000000000..42bbd6e7a --- /dev/null +++ b/docs/tutorials/hackathon/setup.md @@ -0,0 +1,19 @@ +--- +title: Setup Overview +--- + + +Follow these setup pages in order to prepare your development environment for building Superhero plugins. + + +## Setup Steps + +1. **[Prerequisites](./prerequisites)** - Install required software and tools +2. **[Wallet Setup](./wallet-setup)** - Install and configure Superhero Wallet +3. **[Configure Cursor](./configure-cursor)** - Set up Cursor with documentation context +4. **[Project Setup](./project-setup)** - Clone repositories (Superhero UI, your contracts repo, and optionally Superhero API) and configure workspace +5. **[Backend API Setup](./backend-setup)** - Set up backend API repository (optional, only if building backend plugins) + + +You can complete these steps in any order, but we recommend following them sequentially for the smoothest setup experience. Backend setup is only needed if you're building API plugins for transaction processing or popular feed integration. + diff --git a/docs/tutorials/hackathon/test-and-deploy.md b/docs/tutorials/hackathon/test-and-deploy.md new file mode 100644 index 000000000..c692ea42d --- /dev/null +++ b/docs/tutorials/hackathon/test-and-deploy.md @@ -0,0 +1,86 @@ +--- +title: Testing & Deployment +--- + +## Recommended: aeproject + + +Keep deployment outputs (addresses per network) and ACIs in your contracts repo (e.g., `deployments/` and `aci/`). You'll import them in your Superhero plugin later. + + +### Run tests +```bash +# If your project has tests scaffolded by aeproject +aeproject test +``` +This command compiles contracts and runs the test suite using the project’s configuration. + +### Deploy +```bash +# Local devnet (node started via `aeproject node`) +aeproject deploy + +# Example: deploy to testnet (ensure a funded key is configured) +aeproject deploy --network testnet +``` + + +See `aeproject --help` for supported networks and config options. + + +## Optional: SDK + Vitest path +If you prefer a custom SDK test harness, use the example below. + +Ensure `.env.local` or `.env.testnet` is set. + +Example test: +```ts +// tests/poll.test.ts +import { describe, it, expect } from 'vitest' +import { readFileSync } from 'fs' +import 'dotenv/config' +import { AeSdk, Node, MemoryAccount } from '@aeternity/aepp-sdk' + +const NODE_URL = process.env.NODE_URL! +const COMPILER_URL = process.env.COMPILER_URL! +const SECRET_KEY = process.env.SECRET_KEY! + +describe('Poll contract', () => { + it('compiles, deploys, and allows a vote', async () => { + const node = new Node(NODE_URL) + const aeSdk = new AeSdk({ + nodes: [{ name: 'net', instance: node }], + compilerUrl: COMPILER_URL, + accounts: [new MemoryAccount(SECRET_KEY)], + }) + + const source = readFileSync('contracts/Poll.aes', 'utf8') + + const contract = await aeSdk.getContractInstance({ source }) + await contract.deploy(['My poll?', ['Yes', 'No'], 100000]) + + const voteTx = await contract.methods.vote(0, 0) + expect(voteTx.hash).toBeDefined() + + const res = await contract.methods.get_results(0) + expect(res.decodedResult).toBeDefined() + }) +}) +``` + +Negative tests to add: + +- Duplicate vote should fail +- Voting outside open/close window should fail +- Invalid option index should fail + +## Deploy Sanity Checks +- All negative tests pass locally +- Compiler version pinned and consistent +- Events emitted where needed for indexing + +## Next Steps + +- **[Plugin Integration](./integrate-and-plugin-sdk)** - Integrate your deployed contract into Superhero +- **[Feed Plugins](./feed-plugins)** - Add your contract's data to the unified feed +- **[Hints & Tips](./hints)** - Troubleshooting and best practices diff --git a/docs/tutorials/hackathon/wallet-setup.md b/docs/tutorials/hackathon/wallet-setup.md new file mode 100644 index 000000000..482eda9b3 --- /dev/null +++ b/docs/tutorials/hackathon/wallet-setup.md @@ -0,0 +1,63 @@ +--- +title: Wallet Setup +--- + + +Set up Superhero Wallet to create and manage your æternity accounts for development and testing. + + +## Install Superhero Wallet + +### Browser Extensions +- **Chrome**: [Superhero Wallet extension](https://chromewebstore.google.com/detail/superhero-wallet/mnhmmkepfddpifjkamaligfeemcbhdne) +- **Firefox**: [Superhero Wallet extension](https://addons.mozilla.org/en-US/firefox/addon/superhero-wallet/) + +### Mobile Apps +- **Android**: [Google Play Store](https://play.google.com/store/apps/details?id=com.superhero.cordova) +- **iOS**: [App Store](https://apps.apple.com/us/app/superhero-wallet/id1502786641) + + +Pin the browser extension for quick access. + + +## Create or Import an Account + +1. Open Superhero Wallet +2. Choose **"Create"** to generate a new seed phrase, or **"Import"** to restore an existing one +3. Write down the seed phrase offline and store it securely + + +Back up your seed phrase. Anyone with your seed can control your account. Never share it or commit it to version control. + + +## Switch to Testnet + +1. Open Wallet settings +2. Select **æternity testnet** +3. Your wallet will now connect to the testnet network + +## Fund Your Testnet Account + +Get testnet AE tokens for development and testing: + +1. Copy your testnet account address from the wallet +2. Visit the [æternity Testnet Faucet](https://faucet.aepps.com/) +3. Paste your address and request tokens + + +Testnet tokens are free and used only for testing. They have no real value. + + +## Create a Development Key (Optional) + +For automated testing, you can generate a dev key locally: + +```bash +# Generate a test keypair +aecli key generate +``` + + +Never commit private keys to version control. Store them in `.env*` files locally only, and add these files to `.gitignore`. + + diff --git a/netlify.toml b/netlify.toml index c40f5f7df..ec98693c0 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,5 +1,4 @@ [build] - publish = "dist" command = "npm run build" [build.environment] @@ -8,10 +7,12 @@ # Tell Netlify's secret scanner to ignore this env var so builds don't fail. SECRETS_SCAN_OMIT_KEYS = "VITE_WALLET_CONNECT_PROJECT_ID" +# SPA redirect - catch-all for app routes +# Note: More specific paths (like docs) should be served directly if files exist [[redirects]] - from = "/*" - to = "/index.html" - status = 200 +from = "/*" +to = "/index.html" +status = 200 [dev] command = "npm run dev" @@ -40,7 +41,7 @@ for = "/*" Referrer-Policy = "strict-origin-when-cross-origin" X-Content-Type-Options = "nosniff" Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload" - Permissions-Policy = "interest-cohort=()" + Permissions-Policy = "interest-cohort=(), clipboard-write=(self)" [[headers]] for = "/assets/*" diff --git a/netlify/edge-functions/seo.ts b/netlify/edge-functions/seo.ts index cd74e5d76..650429b34 100644 --- a/netlify/edge-functions/seo.ts +++ b/netlify/edge-functions/seo.ts @@ -17,15 +17,31 @@ export default async (request: Request, context: any) => { const url = new URL(request.url); const pathname = url.pathname; + // Skip docs paths - Mintlify serves its own HTML and shouldn't be modified + // Docs are typically hosted separately on Mintlify's platform + if (pathname.startsWith('/hackathon/') || + pathname.startsWith('/site/') || + pathname.includes('/tutorials/') || + pathname.includes('/docs/')) { + return context.next(); + } + const res = await context.next(); // Only inject into HTML documents const contentType = res.headers.get('content-type') || ''; if (!contentType.includes('text/html')) return res; - const html = await res.text(); + // Skip if this looks like Mintlify HTML (has Mintlify-specific structure) + const html = await res.text(); + if (html.includes('mintlify') || + html.includes('data-mint') || + html.includes('mintlify-app')) { + // Return original HTML unchanged (recreate response since we consumed the body) + return new Response(html, { status: res.status, headers: res.headers }); + } - const meta = await buildMeta(pathname, url); - const injected = injectHead(html, meta, url.origin); + const meta = await buildMeta(pathname, url); + const injected = injectHead(html, meta, url.origin); const newHeaders = new Headers(res.headers); newHeaders.set('content-length', String(new TextEncoder().encode(injected).length)); diff --git a/package.json b/package.json index 8d1e17812..4cb1fd54d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "preview": "vite preview --port 5174", "test": "vitest", "generate:api": "openapi --input http://localhost:3000/api-json --output ./src/api/generated --useOptions", + "ext:scaffold": "node scripts/ext-scaffold.mjs", + "ext:check": "node scripts/ext-check.mjs", "generate:sitemap": "ts-node ./scripts/generate-sitemap.ts", "postinstall": "node ./scripts/update-package-type.cjs" }, diff --git a/scripts/check-docs-links.mjs b/scripts/check-docs-links.mjs new file mode 100755 index 000000000..018055e29 --- /dev/null +++ b/scripts/check-docs-links.mjs @@ -0,0 +1,174 @@ +#!/usr/bin/env node + +import { readFileSync, existsSync } from 'fs'; +import { join, dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; +import { glob } from 'glob'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const docsDir = resolve(__dirname, '../docs'); + +// Patterns to match markdown links +const linkPatterns = [ + /\[([^\]]+)\]\(([^)]+)\)/g, // [text](url) + /\[([^\]]+)\]\(([^)]+)#([^)]+)\)/g, // [text](url#anchor) +]; + +// Patterns for URLs that should be ignored (external URLs, code blocks, etc.) +const ignorePatterns = [ + /^https?:\/\//, // External URLs + /^mailto:/, // Email links + /^#/, // Anchors only + /^\.\.\/\.\./, // Too many parent directories (likely wrong) +]; + +const issues = []; + +function checkFile(filePath) { + const content = readFileSync(filePath, 'utf-8'); + const fileDir = dirname(filePath); + const relativePath = filePath.replace(docsDir + '/', ''); + + // Check for localhost URLs (should only be in README.md or code examples) + const localhostMatches = content.match(/http:\/\/localhost:\d+/g); + if (localhostMatches && !filePath.includes('README.md') && !filePath.includes('configure-cursor.md')) { + localhostMatches.forEach(match => { + // Check if it's in a code block + const lines = content.split('\n'); + const matchLine = lines.findIndex(line => line.includes(match)); + if (matchLine !== -1) { + // Check if it's in a code block + let inCodeBlock = false; + for (let i = 0; i < matchLine; i++) { + if (lines[i].trim().startsWith('```')) { + inCodeBlock = !inCodeBlock; + } + } + if (!inCodeBlock) { + issues.push({ + file: relativePath, + line: matchLine + 1, + type: 'localhost_url', + issue: `Found localhost URL outside code block: ${match}`, + severity: 'warning' + }); + } + } + }); + } + + // Extract all markdown links + let match; + const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; + while ((match = linkRegex.exec(content)) !== null) { + const [, linkText, linkUrl] = match; + + // Skip external URLs and anchors + if (ignorePatterns.some(pattern => pattern.test(linkUrl))) { + continue; + } + + // Skip if it's in a code block + const lines = content.split('\n'); + const matchLine = lines.findIndex(line => line.includes(match[0])); + if (matchLine !== -1) { + let inCodeBlock = false; + for (let i = 0; i < matchLine; i++) { + if (lines[i].trim().startsWith('```')) { + inCodeBlock = !inCodeBlock; + } + } + if (inCodeBlock) { + continue; + } + } + + // Resolve relative path + let targetPath; + if (linkUrl.startsWith('/')) { + // Absolute path from docs root + targetPath = join(docsDir, linkUrl); + } else { + // Relative path + targetPath = resolve(fileDir, linkUrl); + } + + // Remove anchor if present + const [pathPart, anchor] = targetPath.split('#'); + const cleanPath = pathPart; + + // Check if file exists + let exists = existsSync(cleanPath); + + // Try with .md extension if it doesn't exist (for links without extension) + if (!exists && !cleanPath.endsWith('.md')) { + exists = existsSync(cleanPath + '.md'); + } + + // Try without .md extension if it ends with .md (for backward compatibility) + if (!exists && cleanPath.endsWith('.md')) { + exists = existsSync(cleanPath.replace(/\.md$/, '')); + } + + // Also try as directory with index.md (for links without extension) + if (!exists && !cleanPath.endsWith('.md')) { + exists = existsSync(join(cleanPath, 'index.md')); + } + + if (!exists) { + const lineNum = content.substring(0, match.index).split('\n').length; + issues.push({ + file: relativePath, + line: lineNum, + type: 'broken_link', + issue: `Broken link: [${linkText}](${linkUrl})`, + target: linkUrl, + severity: 'error' + }); + } + } +} + +// Find all markdown files +const mdFiles = glob.sync('**/*.md', { cwd: docsDir, ignore: ['**/_archive/**'] }); + +console.log(`Checking ${mdFiles.length} markdown files...\n`); + +mdFiles.forEach(file => { + const fullPath = join(docsDir, file); + checkFile(fullPath); +}); + +// Report issues +if (issues.length === 0) { + console.log('✅ No broken links found!'); + process.exit(0); +} else { + console.log(`❌ Found ${issues.length} issue(s):\n`); + + const errors = issues.filter(i => i.severity === 'error'); + const warnings = issues.filter(i => i.severity === 'warning'); + + if (errors.length > 0) { + console.log('Errors:'); + errors.forEach(issue => { + console.log(` ${issue.file}:${issue.line} - ${issue.issue}`); + if (issue.target) { + console.log(` → ${issue.target}`); + } + }); + console.log(''); + } + + if (warnings.length > 0) { + console.log('Warnings:'); + warnings.forEach(issue => { + console.log(` ${issue.file}:${issue.line} - ${issue.issue}`); + }); + console.log(''); + } + + process.exit(errors.length > 0 ? 1 : 0); +} + diff --git a/scripts/ext-check.mjs b/scripts/ext-check.mjs new file mode 100644 index 000000000..547baf70e --- /dev/null +++ b/scripts/ext-check.mjs @@ -0,0 +1,46 @@ +#!/usr/bin/env node +import fs from 'fs'; +import path from 'path'; + +const root = process.cwd(); +const pluginsDir = path.join(root, 'src', 'plugins'); + +function walk(dir) { + const out = []; + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + const p = path.join(dir, e.name); + if (e.isDirectory()) out.push(...walk(p)); + else out.push(p); + } + return out; +} + +function check() { + if (!fs.existsSync(pluginsDir)) return 0; + const files = walk(pluginsDir).filter((p) => + /index\.tsx?$/.test(p) && !/[/\\]locales[/\\]/.test(p) + ); + let errors = 0; + for (const f of files) { + const src = fs.readFileSync(f, 'utf8'); + if (!src.includes('definePlugin')) { + console.error(`[ext-check] ${f} does not export a plugin via definePlugin`); + errors += 1; + } + if (!/capabilities:\s*\[/.test(src)) { + console.error(`[ext-check] ${f} missing capabilities declaration`); + errors += 1; + } + } + return errors; +} + +const errs = check(); +if (errs > 0) { + console.error(`[ext-check] Failed with ${errs} error(s)`); + process.exit(1); +} else { + console.log('[ext-check] OK'); +} + + diff --git a/scripts/ext-scaffold.mjs b/scripts/ext-scaffold.mjs new file mode 100644 index 000000000..c0fbb13b8 --- /dev/null +++ b/scripts/ext-scaffold.mjs @@ -0,0 +1,59 @@ +#!/usr/bin/env node +import fs from 'fs'; +import path from 'path'; + +const root = process.cwd(); +const name = (process.argv[2] || '').trim(); +if (!name) { + console.error('Usage: node scripts/ext-scaffold.mjs '); + process.exit(1); +} + +const baseDir = path.join(root, 'src', 'plugins', name); +const uiDir = path.join(baseDir, 'ui'); + +fs.mkdirSync(uiDir, { recursive: true }); + +const indexContent = `import React from 'react'; +import { definePlugin } from '@/plugin-sdk'; +import App from './ui/App'; + +export default definePlugin({ + meta: { + id: '${name}', + name: '${name.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase())}', + version: '0.1.0', + apiVersion: '1.x', + capabilities: ['routes'], + description: '${name} extension', + }, + setup({ register }) { + register({ + routes: [{ path: '/${name}', element: }], + menu: [{ id: '${name}', label: '${name.toUpperCase()}', path: '/${name}', icon: '🧩' }], + }); + }, +}); +`; + +const uiContent = `import React from 'react'; + +export default function App() { + return ( +
+

${name}

+

New Superhero App Extension scaffolded.

+
+ ); +} +`; + +const indexPath = path.join(baseDir, 'index.tsx'); +const uiPath = path.join(uiDir, 'App.tsx'); + +if (!fs.existsSync(indexPath)) fs.writeFileSync(indexPath, indexContent, 'utf8'); +if (!fs.existsSync(uiPath)) fs.writeFileSync(uiPath, uiContent, 'utf8'); + +console.log(`Scaffolded extension at ${baseDir}`); + + diff --git a/scripts/remove-md-extensions.mjs b/scripts/remove-md-extensions.mjs new file mode 100755 index 000000000..a9f5e118f --- /dev/null +++ b/scripts/remove-md-extensions.mjs @@ -0,0 +1,51 @@ +#!/usr/bin/env node + +import { readFileSync, writeFileSync } from 'fs'; +import { glob } from 'glob'; +import { join, dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const docsDir = resolve(__dirname, '../docs'); + +// Pattern to match markdown links with .md extension (but not external URLs) +const linkPattern = /(\[[^\]]+\]\()(\.\.?\/[^)]+)(\.md)(#?[^)]*)(\))/g; + +function processFile(filePath) { + const content = readFileSync(filePath, 'utf-8'); + let modified = false; + + // Replace internal .md links (but keep external https:// links) + const newContent = content.replace(linkPattern, (match, prefix, path, mdExt, anchor, suffix) => { + // Only remove .md from relative paths (./ or ../) + if (path.startsWith('./') || path.startsWith('../')) { + modified = true; + return `${prefix}${path}${anchor}${suffix}`; + } + return match; + }); + + if (modified) { + writeFileSync(filePath, newContent, 'utf-8'); + return true; + } + return false; +} + +// Find all markdown files +const mdFiles = glob.sync('**/*.md', { cwd: docsDir, ignore: ['**/_archive/**'] }); + +console.log(`Processing ${mdFiles.length} markdown files...\n`); + +let modifiedCount = 0; +mdFiles.forEach(file => { + const fullPath = join(docsDir, file); + if (processFile(fullPath)) { + console.log(`✓ Updated: ${file}`); + modifiedCount++; + } +}); + +console.log(`\n✅ Updated ${modifiedCount} file(s)`); + diff --git a/site/404.html b/site/404.html new file mode 100644 index 000000000..9071c8077 --- /dev/null +++ b/site/404.html @@ -0,0 +1,786 @@ + + + + + + + + + + + + + + + + + + + Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/DEX_REFACTORING_SUMMARY/index.html b/site/_archive/DEX_REFACTORING_SUMMARY/index.html new file mode 100644 index 000000000..21ffb9843 --- /dev/null +++ b/site/_archive/DEX_REFACTORING_SUMMARY/index.html @@ -0,0 +1,1402 @@ + + + + + + + + + + + + + + + + + + + + + DEX Refactoring Migration Summary - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

DEX Refactoring Migration Summary

+

Overview

+

Successfully completed a full migration of the monolithic DEX component (1335 lines) into a modular, maintainable architecture with focused components and custom hooks.

+

What Was Accomplished

+

1. Component Architecture

+
    +
  • Before: Single Dex.tsx file with 1335 lines handling all DEX functionality
  • +
  • After: 15+ focused components organized by responsibility
  • +
+

2. Custom Hooks

+
    +
  • Before: All logic embedded in the main component
  • +
  • After: 4 custom hooks for specific concerns:
  • +
  • useTokenList - Token management
  • +
  • useTokenBalances - Balance fetching
  • +
  • useSwapQuote - Quote handling with debouncing
  • +
  • useSwapExecution - Transaction execution
  • +
+

3. Type Safety

+
    +
  • Before: Minimal TypeScript interfaces
  • +
  • After: Comprehensive type system with 15+ interfaces
  • +
+

4. Directory Structure

+
src/components/dex/
+├── core/           # 6 core components
+├── widgets/        # 3 specialized widgets
+├── supporting/     # 1 supporting component
+├── hooks/          # 4 custom hooks
+├── types/          # TypeScript definitions
+└── index.ts        # Clean exports
+
+

Components Created

+

Core Components

+
    +
  1. SwapForm.tsx - Main swap interface orchestrator
  2. +
  3. TokenInput.tsx - Token selection + amount input
  4. +
  5. TokenSelector.tsx - Enhanced token selection with search
  6. +
  7. SwapSettings.tsx - Slippage and deadline configuration
  8. +
  9. SwapRouteInfo.tsx - Route information display
  10. +
  11. SwapConfirmation.tsx - Confirmation modal
  12. +
+

Widget Components

+
    +
  1. WrapUnwrapWidget.tsx - AE ↔ WAE conversion
  2. +
  3. EthxitWidget.tsx - aeETH → AE conversion
  4. +
  5. EthBridgeWidget.tsx - ETH → AE bridge + swap
  6. +
+

Supporting Components

+
    +
  1. RecentActivity.tsx - Transaction history
  2. +
+

Key Benefits Achieved

+

1. Maintainability

+
    +
  • Each component has a single responsibility
  • +
  • Clear separation of concerns
  • +
  • Easier to locate and fix issues
  • +
+

2. Reusability

+
    +
  • Components can be used independently
  • +
  • Hooks can be reused across different contexts
  • +
  • Modular architecture supports composition
  • +
+

3. Testability

+
    +
  • Smaller components are easier to unit test
  • +
  • Hooks can be tested in isolation
  • +
  • Clear interfaces make mocking easier
  • +
+

4. Developer Experience

+
    +
  • Better code organization
  • +
  • Comprehensive TypeScript types
  • +
  • Clear component boundaries
  • +
  • Easier onboarding for new developers
  • +
+

5. Performance

+
    +
  • Better code splitting potential
  • +
  • Lazy loading opportunities
  • +
  • Reduced bundle size for specific features
  • +
+

Technical Improvements

+

1. State Management

+
    +
  • Centralized state in custom hooks
  • +
  • Better state isolation
  • +
  • Reduced prop drilling
  • +
+

2. Error Handling

+
    +
  • Consistent error handling patterns
  • +
  • Better user feedback
  • +
  • Graceful degradation
  • +
+

3. Type Safety

+
    +
  • Comprehensive TypeScript coverage
  • +
  • Better IDE support
  • +
  • Runtime type safety
  • +
+

4. Code Quality

+
    +
  • Consistent coding patterns
  • +
  • Better separation of concerns
  • +
  • Improved readability
  • +
+

Migration Strategy

+

Phase 1: Foundation

+
    +
  • Created type definitions
  • +
  • Implemented custom hooks
  • +
  • Established component architecture
  • +
+

Phase 2: Core Components

+
    +
  • Built core swap functionality
  • +
  • Implemented token management
  • +
  • Created UI components
  • +
+

Phase 3: Specialized Widgets

+
    +
  • Implemented wrap/unwrap functionality
  • +
  • Added ETH bridge features
  • +
  • Created specialized conversion widgets
  • +
+

Phase 4: Integration

+
    +
  • Connected all components
  • +
  • Ensured backward compatibility
  • +
  • Added comprehensive documentation
  • +
+

Files Created/Modified

+

New Files (15+)

+
    +
  • src/components/dex/types/dex.ts
  • +
  • src/components/dex/hooks/useTokenList.ts
  • +
  • src/components/dex/hooks/useTokenBalances.ts
  • +
  • src/components/dex/hooks/useSwapQuote.ts
  • +
  • src/components/dex/hooks/useSwapExecution.ts
  • +
  • src/components/dex/core/SwapForm.tsx
  • +
  • src/components/dex/core/TokenInput.tsx
  • +
  • src/components/dex/core/TokenSelector.tsx
  • +
  • src/components/dex/core/SwapSettings.tsx
  • +
  • src/components/dex/core/SwapRouteInfo.tsx
  • +
  • src/components/dex/core/SwapConfirmation.tsx
  • +
  • src/components/dex/widgets/WrapUnwrapWidget.tsx
  • +
  • src/components/dex/widgets/EthxitWidget.tsx
  • +
  • src/components/dex/widgets/EthBridgeWidget.tsx
  • +
  • src/components/dex/supporting/RecentActivity.tsx
  • +
  • src/components/dex/index.ts
  • +
  • src/components/dex/README.md
  • +
  • src/views/DexRefactored.tsx
  • +
+

Preserved Files

+
    +
  • src/views/Dex.tsx (original preserved for reference)
  • +
+

Usage Examples

+

Basic Usage

+
import { SwapForm, WrapUnwrapWidget } from '../components/dex';
+
+function MyDexPage() {
+  return (
+    <div>
+      <SwapForm />
+      <WrapUnwrapWidget />
+    </div>
+  );
+}
+
+

Advanced Usage

+
import { useTokenList, useSwapQuote } from '../components/dex';
+
+function CustomSwap() {
+  const { tokens, loading } = useTokenList();
+  const { quoteLoading, error, routeInfo } = useSwapQuote();
+
+  // Custom implementation
+}
+
+

Next Steps

+

Immediate

+
    +
  1. Test the refactored components
  2. +
  3. Update routing to use DexRefactored.tsx
  4. +
  5. Add comprehensive unit tests
  6. +
+

Future

+
    +
  1. Implement error boundaries
  2. +
  3. Add loading skeletons
  4. +
  5. Improve accessibility
  6. +
  7. Add more widget components
  8. +
  9. Implement advanced routing features
  10. +
+

Conclusion

+

The DEX refactoring successfully transformed a monolithic 1335-line component into a modular, maintainable architecture with:

+
    +
  • 15+ focused components
  • +
  • 4 custom hooks
  • +
  • Comprehensive TypeScript types
  • +
  • Better separation of concerns
  • +
  • Improved developer experience
  • +
  • Enhanced maintainability
  • +
+

The refactoring preserves all original functionality while providing a solid foundation for future development and maintenance.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/EXTENSIONS/index.html b/site/_archive/EXTENSIONS/index.html new file mode 100644 index 000000000..43893dbde --- /dev/null +++ b/site/_archive/EXTENSIONS/index.html @@ -0,0 +1,1011 @@ + + + + + + + + + + + + + + + + + + + + + Superhero App Extensions - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Superhero App Extensions

+
+

[!NOTE] +For a step-by-step walkthrough, see How to build a Superhero App Extension (Polls example).

+
+

Extensions are first-class modules that can add routes, menu items, feed entries, composer actions, item actions, modals, and composer attachments. They are authored with the existing plugin SDK and loaded at runtime.

+

Capabilities

+
    +
  • routes: add pages (mounted dynamically via routeRegistry)
  • +
  • menu: add header navigation items (merged via navRegistry)
  • +
  • feed: contribute feed entries and renderers
  • +
  • composer: add composer actions and attachments
  • +
  • item-actions: add per-feed-item actions
  • +
  • modals: register modal components
  • +
+

Authoring an extension

+

Minimal example: +

import React from 'react';
+import { definePlugin } from '@/plugin-sdk';
+import MyApp from './ui/MyApp';
+
+export default definePlugin({
+  meta: {
+    id: 'my-ext',
+    name: 'My Extension',
+    version: '0.1.0',
+    apiVersion: '1.x',
+    capabilities: ['routes', 'composer'],
+  },
+  setup({ register }) {
+    register({
+      routes: [{ path: '/my-ext', element: <MyApp /> }],
+      menu: [{ id: 'my-ext', label: 'My Ext', path: '/my-ext', icon: '🧩' }],
+      // attachments: () => [myAttachmentSpec],
+    });
+  },
+});
+

+

Loading modes

+
    +
  • Local (first‑party): register in src/plugins/local.ts for development. Not enabled by default in production.
  • +
  • External (remote URLs): CONFIG.PLUGINS can point to remote modules. The loader enforces the capabilities allowlist.
  • +
+

Capability allowlist

+
    +
  • Use CONFIG.PLUGIN_CAPABILITIES_ALLOWLIST to restrict what plugins may register, e.g. ['routes','feed','composer'].
  • +
+

Registries lifecycle

+
    +
  • Registries live in features/social/plugins/registries.ts.
  • +
  • In development, local loader resets registries on hot reload to avoid duplicates.
  • +
  • Plugin bootstrap is guarded to run once per app lifecycle.
  • +
+

Contracts

+

Use createContractLoader(sdk) from src/extensions/contract.ts to load ACI-based contract instances.

+

Backend integration

+

Point your extension to backend services. For Social Superhero governance backend, see: +- repo: https://github.com/superhero-com/superhero-api +- flow: index chain data → expose REST/WebSocket → consume from extension client

+

Environment & config

+
    +
  • public/superconfig.json (or window.__SUPERCONFIG__) provides runtime keys like NODE_URL, MIDDLEWARE_URL, GOVERNANCE_API_URL, PLUGINS, PLUGIN_CAPABILITIES_ALLOWLIST.
  • +
+

CI

+
    +
  • npm run ext:check validates extension modules. The CI workflow runs it on pull requests.
  • +
+

Security & UX notes

+
    +
  • Keep capabilities minimal; prefer read-only routes for untrusted plugins.
  • +
  • Gating composer/actions behind the allowlist reduces risk.
  • +
  • Validate inputs and handle network failures gracefully.
  • +
+

Troubleshooting

+
    +
  • Duplicate nav or routes: ensure bootstrap runs once; in dev registries are reset before loading local plugins.
  • +
  • Wallet not connected: ensure wallet connect flow completes before signing.
  • +
  • ACI mismatch: recompile and update contract address.
  • +
+

Full tutorial (Polls example)

+

See the detailed guide: +docs/tutorials/build-governance-poll-extension.md

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/MIGRATION_COMPLETE/index.html b/site/_archive/MIGRATION_COMPLETE/index.html new file mode 100644 index 000000000..7ef8c261d --- /dev/null +++ b/site/_archive/MIGRATION_COMPLETE/index.html @@ -0,0 +1,1143 @@ + + + + + + + + + + + + + + + + + + + + + DEX Migration Complete! 🎉 - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

DEX Migration Complete! 🎉

+

What Was Accomplished

+

Successfully migrated the monolithic DEX component (1335 lines) to a modular architecture

+

Files Moved/Updated:

+
    +
  • Archived: src/views/Dex.tsxsrc/archive/Dex.original.tsx
  • +
  • New: src/views/DexRefactored.tsxsrc/views/Dex.tsx (now the active component)
  • +
+

New Architecture Created:

+
src/components/dex/
+├── core/           # 6 core components
+├── widgets/        # 3 specialized widgets  
+├── supporting/     # 1 supporting component
+├── hooks/          # 4 custom hooks
+├── types/          # TypeScript definitions
+└── index.ts        # Clean exports
+
+

Components Created

+

Core Components (6)

+
    +
  1. SwapForm.tsx - Main swap interface orchestrator
  2. +
  3. TokenInput.tsx - Token selection + amount input
  4. +
  5. TokenSelector.tsx - Enhanced token selection with search
  6. +
  7. SwapSettings.tsx - Slippage and deadline configuration
  8. +
  9. SwapRouteInfo.tsx - Route information display
  10. +
  11. SwapConfirmation.tsx - Confirmation modal
  12. +
+

Widget Components (3)

+
    +
  1. WrapUnwrapWidget.tsx - AE ↔ WAE conversion
  2. +
  3. EthxitWidget.tsx - aeETH → AE conversion
  4. +
  5. EthBridgeWidget.tsx - ETH → AE bridge + swap
  6. +
+

Supporting Components (1)

+
    +
  1. RecentActivity.tsx - Transaction history
  2. +
+

Custom Hooks (4)

+
    +
  1. useTokenList.ts - Token management
  2. +
  3. useTokenBalances.ts - Balance fetching
  4. +
  5. useSwapQuote.ts - Quote handling with debouncing
  6. +
  7. useSwapExecution.ts - Transaction execution
  8. +
+

Key Benefits Achieved

+

Maintainability - Each component has a single responsibility
+✅ Reusability - Components can be used independently
+✅ Testability - Smaller components are easier to test
+✅ Developer Experience - Better code organization and TypeScript support
+✅ Performance - Better code splitting potential

+

Current Status

+

✅ Completed

+
    +
  • Full component refactoring
  • +
  • Custom hooks implementation
  • +
  • TypeScript type system
  • +
  • Component exports and documentation
  • +
  • File migration and routing update
  • +
  • Syntax error fixes
  • +
+

⚠️ Known Issues

+
    +
  • Tests: The existing tests need to be updated for the new component structure
  • +
  • WAE ACI: Using a mock ACI file (needs real ACI in production)
  • +
  • JSX in hooks: Converted to React.createElement for compatibility
  • +
+

🔄 Next Steps

+ +
    +
  1. Test the new component manually in the browser
  2. +
  3. Update existing tests to work with new component structure
  4. +
  5. Add comprehensive unit tests for new components
  6. +
  7. Replace mock WAE ACI with real ACI file when available
  8. +
+

Future Improvements

+
    +
  1. Error boundaries for better error handling
  2. +
  3. Loading skeletons for better UX
  4. +
  5. Accessibility improvements
  6. +
  7. Performance optimizations
  8. +
  9. Additional widget components
  10. +
+

Usage

+

The new refactored DEX is now active and accessible at /dex. All functionality has been preserved while providing a much more maintainable architecture.

+

Basic Usage

+
// The main Dex component now uses the refactored architecture
+import Dex from '../views/Dex'; // This now uses the new modular components
+
+

Advanced Usage

+
// Individual components can be used independently
+import { SwapForm, WrapUnwrapWidget } from '../components/dex';
+
+// Custom hooks can be used for specific functionality
+import { useTokenList, useSwapQuote } from '../components/dex';
+
+

Documentation

+
    +
  • Component Documentation: src/components/dex/README.md
  • +
  • Migration Summary: DEX_REFACTORING_SUMMARY.md
  • +
  • Original Component: src/archive/Dex.original.tsx (for reference)
  • +
+

Conclusion

+

The DEX refactoring has been successfully completed! The monolithic 1335-line component has been transformed into a modular, maintainable architecture with:

+
    +
  • 15+ focused components
  • +
  • 4 custom hooks
  • +
  • Comprehensive TypeScript types
  • +
  • Better separation of concerns
  • +
  • Improved developer experience
  • +
+

The application is now using the new refactored DEX component, and all original functionality has been preserved while providing a solid foundation for future development and maintenance.

+

🚀 Ready for production use!

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/MOBILE_OPTIMIZATIONS/index.html b/site/_archive/MOBILE_OPTIMIZATIONS/index.html new file mode 100644 index 000000000..16458d913 --- /dev/null +++ b/site/_archive/MOBILE_OPTIMIZATIONS/index.html @@ -0,0 +1,1998 @@ + + + + + + + + + + + + + + + + + + + + + Mobile Optimizations for Superhero React App - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Mobile Optimizations for Superhero React App

+

This document outlines all the mobile optimizations implemented to enhance the user experience on mobile devices.

+

🎯 Overview

+

The application has been comprehensively optimized for mobile devices with a focus on: +- Touch-friendly interactions - Larger touch targets and better feedback +- Mobile-first responsive design - Progressive enhancement from mobile to desktop +- Performance optimizations - Faster loading and smoother interactions +- Enhanced mobile navigation - Intuitive mobile-specific navigation +- Mobile-specific components - Optimized forms, buttons, and layouts +- Native ETH -> AE swap interface - Replaced iframe with mobile-optimized component

+

🚀 Latest Updates

+

ETH -> AE Component Replacement

+
    +
  • Location: src/components/SwapCard.tsx
  • +
  • Change: Removed iframe-based Superhero Swap integration and replaced with native mobile-optimized ETH -> AE swap component
  • +
  • Features:
  • +
  • Native mobile-optimized interface using MobileInput, AeButton, and MobileCard components
  • +
  • Direct integration with æternity DEX for aeETH → AE swaps
  • +
  • Wallet connection and address display
  • +
  • Real-time quote fetching and swap execution
  • +
  • Configurable slippage settings
  • +
  • Error handling and user feedback
  • +
  • Touch-friendly input fields and buttons
  • +
  • Responsive design that works on all screen sizes
  • +
  • Link to external Superhero Swap for ETH → aeETH bridging
  • +
+

Benefits of Native Component

+
    +
  • Better Performance: No iframe loading delays or cross-origin restrictions
  • +
  • Improved UX: Seamless integration with app's design system and navigation
  • +
  • Mobile Optimization: Touch-friendly interface with proper spacing and sizing
  • +
  • Offline Capability: Works without external dependencies (except for bridging)
  • +
  • Better Error Handling: Native error messages and retry mechanisms
  • +
  • Accessibility: Proper ARIA labels and keyboard navigation
  • +
+

SCSS Compilation Fixes

+
    +
  • Issue: SCSS compilation errors due to $accent_color variable conflicts with CSS custom properties
  • +
  • Solution: Replaced $accent_color with direct color values (#007bff) in affected files:
  • +
  • src/components/Trendminer/ExploreTrendsSidebar.scss
  • +
  • src/components/Trendminer/LatestTransactionsCarousel.scss
  • +
  • src/views/Trendminer/TokenDetails.scss
  • +
  • Result: Eliminated SCSS compilation errors and improved build stability
  • +
+

�� Mobile Navigation

+

Enhanced Mobile Navigation Component

+
    +
  • Location: src/components/layout/MobileNavigation.tsx
  • +
  • Features:
  • +
  • Full-screen overlay navigation menu
  • +
  • Touch-friendly navigation items (56px minimum height)
  • +
  • Smooth animations and transitions
  • +
  • Theme toggle integration
  • +
  • Search functionality integration
  • +
  • Better visual feedback on touch
  • +
+

Mobile Navigation Styles

+
    +
  • Location: src/components/layout/MobileNavigation.scss
  • +
  • Improvements:
  • +
  • Better touch targets (44px minimum)
  • +
  • Improved visual hierarchy
  • +
  • Smooth transitions and animations
  • +
  • Better contrast and readability
  • +
  • Responsive design for different screen sizes
  • +
+

🎨 Mobile-Optimized Components

+

1. MobileInput Component

+
    +
  • Location: src/components/MobileInput.tsx & src/components/MobileInput.scss
  • +
  • Features:
  • +
  • Touch-friendly input fields (48px minimum height)
  • +
  • Prevents zoom on iOS (16px font size)
  • +
  • Better focus states and visual feedback
  • +
  • Icon support (left/right icons)
  • +
  • Error and helper text display
  • +
  • Multiple variants (default, filled, outlined)
  • +
  • Multiple sizes (small, medium, large)
  • +
+

2. MobileCard Component

+
    +
  • Location: src/components/MobileCard.tsx & src/components/MobileCard.scss
  • +
  • Features:
  • +
  • Touch-friendly clickable cards
  • +
  • Loading states with skeleton animations
  • +
  • Multiple variants (default, elevated, outlined, filled)
  • +
  • Responsive padding options
  • +
  • Smooth hover and active states
  • +
  • Better visual hierarchy
  • +
+

3. Enhanced AeButton Component

+
    +
  • Location: src/components/AeButton.scss
  • +
  • Improvements:
  • +
  • Larger touch targets (48px minimum)
  • +
  • Better visual feedback on touch
  • +
  • Multiple size variants (small, medium, large)
  • +
  • Block variant for full-width buttons
  • +
  • Icon-only variant
  • +
  • Improved hover and active states
  • +
+

🏗️ Layout Optimizations

+

Shell Layout

+
    +
  • Location: src/components/layout/Shell.scss
  • +
  • Improvements:
  • +
  • Mobile-first responsive grid
  • +
  • Better spacing for mobile devices
  • +
  • Improved sidebar handling on mobile
  • +
  • Account for mobile navigation height
  • +
+

App Container

+
    +
  • Location: src/App.tsx & src/styles/mobile-optimizations.scss
  • +
  • Improvements:
  • +
  • Mobile-optimized container structure
  • +
  • Better responsive breakpoints
  • +
  • Improved loading states
  • +
+

🎨 Base Styles & Mixins

+

Enhanced Base Styles

+
    +
  • Location: src/styles/base.scss
  • +
  • Improvements:
  • +
  • Touch-friendly minimum sizes (44px)
  • +
  • Better viewport handling
  • +
  • Improved typography for mobile
  • +
  • Better form controls
  • +
  • Performance optimizations
  • +
+

Comprehensive Mixins

+
    +
  • Location: src/styles/mixins.scss
  • +
  • New Mixins:
  • +
  • mobile-small (≤480px)
  • +
  • mobile-large (≤1024px)
  • +
  • tablet (769px-1024px)
  • +
  • desktop (≥1025px)
  • +
  • mobile-first - Mobile-first responsive approach
  • +
  • touch-spacing - Touch-friendly spacing
  • +
  • mobile-grid - Mobile-optimized grid layouts
  • +
  • mobile-flex - Mobile-optimized flex layouts
  • +
+

📱 Mobile-Specific Optimizations

+

App-Level Mobile Styles

+
    +
  • Location: src/styles/mobile-optimizations.scss
  • +
  • Features:
  • +
  • Mobile-optimized containers and grids
  • +
  • Touch-friendly forms and buttons
  • +
  • Mobile-specific navigation styles
  • +
  • Optimized modals and overlays
  • +
  • Better loading and empty states
  • +
  • Performance optimizations
  • +
  • Accessibility improvements
  • +
+

Top Navigation

+
    +
  • Location: src/components/layout/TopNav.scss
  • +
  • Improvements:
  • +
  • Hidden on mobile (uses mobile navigation instead)
  • +
  • Better responsive design for tablet/desktop
  • +
  • Improved visual hierarchy
  • +
+

🚀 Performance Optimizations

+

Mobile Performance

+
    +
  • Reduced animation durations for better performance
  • +
  • Optimized scrolling with -webkit-overflow-scrolling: touch
  • +
  • Respect for prefers-reduced-motion user preference
  • +
  • Better touch feedback with touch-action: manipulation
  • +
  • Optimized loading states and skeleton animations
  • +
+

Build Optimizations

+
    +
  • Mobile-first CSS architecture
  • +
  • Efficient responsive breakpoints
  • +
  • Optimized bundle sizes for mobile
  • +
+

🎯 Touch-Friendly Features

+

Minimum Touch Targets

+
    +
  • All interactive elements: 44px minimum (48px on mobile)
  • +
  • Buttons and links: 48px minimum height/width
  • +
  • Form inputs: 48px minimum height
  • +
  • Navigation items: 56px minimum height
  • +
+

Touch Feedback

+
    +
  • Visual feedback on touch/active states
  • +
  • Smooth transitions and animations
  • +
  • Better hover states for touch devices
  • +
  • Improved focus indicators
  • +
+

📐 Responsive Design

+

Breakpoints

+
    +
  • Mobile Small: ≤480px
  • +
  • Mobile: ≤768px
  • +
  • Tablet: 769px-1024px
  • +
  • Desktop: ≥1025px
  • +
+

Mobile-First Approach

+
    +
  • Styles start with mobile as the base
  • +
  • Progressive enhancement for larger screens
  • +
  • Efficient CSS with minimal overrides
  • +
+

🎨 Visual Improvements

+

Mobile Typography

+
    +
  • Optimized font sizes for mobile readability
  • +
  • Better line heights and spacing
  • +
  • Improved contrast ratios
  • +
+

Mobile Spacing

+
    +
  • Consistent 16px base spacing
  • +
  • Reduced to 12px on small mobile devices
  • +
  • Better visual hierarchy
  • +
+

Mobile Colors & Themes

+
    +
  • Optimized for both light and dark themes
  • +
  • Better contrast for mobile viewing
  • +
  • Improved accessibility
  • +
+

🔧 Usage Examples

+

Using Mobile Components

+
// Mobile-optimized input
+<MobileInput
+  label="Amount"
+  placeholder="Enter amount"
+  variant="filled"
+  size="large"
+  leftIcon={<IconWallet />}
+/>
+
+// Mobile-optimized card
+<MobileCard
+  variant="elevated"
+  padding="medium"
+  clickable
+  onClick={handleClick}
+>
+  <h3>Card Title</h3>
+  <p>Card content</p>
+</MobileCard>
+
+// Mobile-optimized button
+<AeButton
+  className="large block"
+  green
+  onClick={handleAction}
+>
+  Connect Wallet
+</AeButton>
+
+

Using Mobile Classes

+
// Mobile-optimized layout
+<div className="mobile-container">
+  <div className="mobile-grid">
+    <div className="mobile-card">Content</div>
+  </div>
+
+  <form className="mobile-form">
+    <div className="form-group">
+      <MobileInput label="Field" />
+    </div>
+
+    <div className="mobile-button-group">
+      <AeButton>Cancel</AeButton>
+      <AeButton green>Submit</AeButton>
+    </div>
+  </form>
+</div>
+
+

🧪 Testing

+

Mobile Testing Checklist

+
    +
  • [ ] Touch targets are at least 44px (48px preferred)
  • +
  • [ ] No horizontal scrolling on mobile
  • +
  • [ ] Forms work well on mobile keyboards
  • +
  • [ ] Navigation is intuitive on mobile
  • +
  • [ ] Loading states are smooth
  • +
  • [ ] Animations are performant
  • +
  • [ ] Text is readable on small screens
  • +
  • [ ] Buttons are easy to tap
  • +
  • [ ] Modals work well on mobile
  • +
  • [ ] Search functionality is mobile-friendly
  • +
+

Browser Testing

+
    +
  • iOS Safari
  • +
  • Android Chrome
  • +
  • Mobile Firefox
  • +
  • Mobile Edge
  • +
+

📈 Performance Metrics

+

Mobile Performance Targets

+
    +
  • First Contentful Paint: < 1.5s
  • +
  • Largest Contentful Paint: < 2.5s
  • +
  • Cumulative Layout Shift: < 0.1
  • +
  • First Input Delay: < 100ms
  • +
+

Bundle Size Optimizations

+
    +
  • Mobile-first CSS architecture
  • +
  • Efficient responsive breakpoints
  • +
  • Optimized component imports
  • +
  • Lazy loading for non-critical components
  • +
+

🚀 Trendminer Mobile Optimizations

+

Optimized Trendminer Components

+

1. ExploreTrendsSidebar Component

+
    +
  • Location: src/components/Trendminer/ExploreTrendsSidebar.tsx & src/components/Trendminer/ExploreTrendsSidebar.scss
  • +
  • Mobile Improvements:
  • +
  • Responsive layout with mobile-first design
  • +
  • Touch-friendly trend item cards with 44px minimum touch targets
  • +
  • Mobile-optimized search input with 16px font size (prevents iOS zoom)
  • +
  • Collapsible configuration panel for better mobile UX
  • +
  • Improved visual hierarchy and spacing for mobile screens
  • +
  • Smooth animations and transitions optimized for mobile performance
  • +
+

2. LatestTransactionsCarousel Component

+
    +
  • Location: src/components/Trendminer/LatestTransactionsCarousel.tsx & src/components/Trendminer/LatestTransactionsCarousel.scss
  • +
  • Mobile Improvements:
  • +
  • Responsive carousel with mobile-optimized card sizes
  • +
  • Touch-friendly transaction cards with proper spacing
  • +
  • Smooth scrolling animations with reduced motion support
  • +
  • Mobile-specific gradient overlays for better visual feedback
  • +
  • Optimized typography and spacing for small screens
  • +
  • Pause animation on hover for better mobile interaction
  • +
+

3. TokenDetails Component

+
    +
  • Location: src/views/Trendminer/TokenDetails.tsx & src/views/Trendminer/TokenDetails.scss
  • +
  • Mobile Improvements:
  • +
  • Mobile-first responsive layout with stacked sections
  • +
  • Touch-friendly action buttons with 44px minimum height
  • +
  • Mobile-optimized chart controls with larger touch targets
  • +
  • Responsive tab navigation with improved mobile UX
  • +
  • Mobile-specific loading and error states
  • +
  • Optimized typography and spacing for mobile readability
  • +
  • Collapsible configuration sections for better mobile organization
  • +
+

4. TrendCloud Component

+
    +
  • Location: src/views/Trendminer/TrendCloud.tsx & src/views/Trendminer/TrendCloud.scss
  • +
  • Mobile Improvements:
  • +
  • Responsive word cloud with mobile-optimized sizing
  • +
  • Touch-friendly configuration controls with proper spacing
  • +
  • Mobile-specific grid layout for trend items
  • +
  • Collapsible configuration panel to save mobile screen space
  • +
  • Optimized hover tooltips for touch devices
  • +
  • Mobile-optimized search and filter controls
  • +
  • Responsive chart containers with proper mobile scaling
  • +
+

Mobile-Specific Features

+

Touch-Friendly Interactions

+
    +
  • All interactive elements have minimum 44px touch targets
  • +
  • Improved touch feedback with visual state changes
  • +
  • Optimized button sizes and spacing for mobile fingers
  • +
  • Better hover states that work well on touch devices
  • +
+

Mobile Performance Optimizations

+
    +
  • Reduced animation durations for better mobile performance
  • +
  • Optimized loading states with mobile-specific spinners
  • +
  • Efficient responsive breakpoints to minimize CSS overhead
  • +
  • Touch-optimized scrolling with -webkit-overflow-scrolling: touch
  • +
+

Mobile Navigation Improvements

+
    +
  • Mobile-optimized tab navigation with larger touch targets
  • +
  • Responsive grid layouts that adapt to mobile screen sizes
  • +
  • Collapsible sections to maximize mobile screen real estate
  • +
  • Better visual hierarchy for mobile scanning
  • +
+

Mobile-Specific Styling

+
    +
  • 16px font sizes on inputs to prevent iOS zoom
  • +
  • Mobile-optimized spacing and padding throughout
  • +
  • Better contrast ratios for mobile viewing conditions
  • +
  • Responsive typography that scales appropriately
  • +
+

Mobile Testing Checklist for Trendminer

+
    +
  • [ ] All trend items are easily tappable on mobile
  • +
  • [ ] Carousel animations are smooth on mobile devices
  • +
  • [ ] Token details page is fully responsive
  • +
  • [ ] Word cloud is usable on touch devices
  • +
  • [ ] Configuration panels work well on mobile
  • +
  • [ ] Search and filter controls are mobile-friendly
  • +
  • [ ] Loading states are optimized for mobile
  • +
  • [ ] Error handling works well on mobile devices
  • +
  • [ ] Charts and visualizations are mobile-responsive
  • +
  • [ ] Navigation between sections is intuitive on mobile
  • +
+

🚀 DEX Mobile Optimizations

+

Enhanced DEX Components

+
    +
  • Location: src/views/Dex.tsx & src/views/Dex.scss
  • +
  • Features:
  • +
  • Mobile-optimized layout with responsive design
  • +
  • Touch-friendly form inputs (48px minimum height)
  • +
  • Mobile-specific token selector with modal interface
  • +
  • Responsive DEX tabs with proper touch targets
  • +
  • Mobile-optimized buttons and interactive elements
  • +
  • Better spacing and typography for mobile devices
  • +
+

DEX Mobile Components

+
    +
  • MobileDexCard: src/components/dex/MobileDexCard.tsx
  • +
  • Touch-friendly card component with loading states
  • +
  • Multiple variants and responsive padding
  • +
  • +

    Skeleton loading animations

    +
  • +
  • +

    MobileTokenSelector: src/components/dex/MobileTokenSelector.tsx

    +
  • +
  • Mobile-optimized token selection modal
  • +
  • Touch-friendly search interface
  • +
  • +

    Proper keyboard handling and accessibility

    +
  • +
  • +

    MobileDexInput: src/components/dex/MobileDexInput.tsx

    +
  • +
  • Mobile-optimized input fields
  • +
  • Prevents zoom on iOS (16px font size)
  • +
  • +

    Better focus states and validation

    +
  • +
  • +

    MobileDexButton: src/components/dex/MobileDexButton.tsx

    +
  • +
  • Touch-friendly buttons with loading states
  • +
  • Multiple variants and sizes
  • +
  • Proper touch feedback
  • +
+

DEX Mobile Features

+
    +
  • Responsive Layout: Adapts to different screen sizes
  • +
  • Touch-Friendly: All interactive elements meet 44px minimum
  • +
  • Mobile Navigation: Optimized tabs and navigation
  • +
  • Form Optimization: Better mobile keyboard handling
  • +
  • Loading States: Smooth loading indicators
  • +
  • Error Handling: Mobile-friendly error messages
  • +
+

🔄 Future Enhancements

+

Planned Mobile Improvements

+
    +
  • [ ] PWA (Progressive Web App) features
  • +
  • [ ] Offline functionality
  • +
  • [ ] Mobile-specific gestures
  • +
  • [ ] Enhanced mobile animations
  • +
  • [ ] Mobile-specific error handling
  • +
  • [ ] Better mobile keyboard handling
  • +
  • [ ] Mobile-specific accessibility features
  • +
  • [ ] DEX-specific mobile optimizations
  • +
  • [ ] Swipe gestures for token switching
  • +
  • [ ] Mobile-optimized price charts
  • +
  • [ ] Touch-friendly slippage adjustment
  • +
  • [ ] Mobile-specific transaction confirmations
  • +
+

📚 Resources

+

Mobile Development Best Practices

+ +

Testing Tools

+
    +
  • Chrome DevTools Mobile Emulation
  • +
  • BrowserStack Mobile Testing
  • +
  • Lighthouse Mobile Audits
  • +
  • WebPageTest Mobile Testing
  • +
+
+

Note: All mobile optimizations are designed to work seamlessly with the existing desktop experience while providing an enhanced mobile experience. The mobile-first approach ensures that the application performs well across all device sizes.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/POOL_EXPLORE_REFACTORING_SUMMARY/index.html b/site/_archive/POOL_EXPLORE_REFACTORING_SUMMARY/index.html new file mode 100644 index 000000000..a68788649 --- /dev/null +++ b/site/_archive/POOL_EXPLORE_REFACTORING_SUMMARY/index.html @@ -0,0 +1,1250 @@ + + + + + + + + + + + + + + + + + + + + + Pool & Explore Refactoring Complete! 🎉 - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Pool & Explore Refactoring Complete! 🎉

+

Overview

+

Successfully refactored both Pool and Explore components from monolithic structures into modular, maintainable architectures with improved layouts and enhanced functionality.

+

What Was Accomplished

+

Pool Component Refactoring

+

Archived: src/views/Pool.tsxsrc/archive/Pool.original.tsx
+New: src/views/PoolRefactored.tsxsrc/views/Pool.tsx

+

New Architecture Created:

+
src/components/pool/
+├── core/                    # Core components
+│   ├── LiquidityPositionCard.tsx
+│   └── AddLiquidityForm.tsx
+├── hooks/                   # Custom hooks
+│   ├── useLiquidityPositions.ts
+│   └── useAddLiquidity.ts
+├── types/                   # TypeScript definitions
+│   └── pool.ts
+└── index.ts                 # Clean exports
+
+

Key Improvements:

+
    +
  1. Enhanced Layout: Modern card-based design with stats overview
  2. +
  3. Tab Navigation: Separate views for positions and add liquidity
  4. +
  5. Stats Dashboard: Total positions, value, and fees earned
  6. +
  7. Better UX: Empty states with clear call-to-actions
  8. +
  9. Modular Components: Reusable position cards and forms
  10. +
  11. Type Safety: Comprehensive TypeScript interfaces
  12. +
+

Explore Component Refactoring

+

Archived: src/views/Explore.tsxsrc/archive/Explore.original.tsx
+New: src/views/ExploreRefactored.tsxsrc/views/Explore.tsx

+

New Architecture Created:

+
src/components/explore/
+├── core/                    # Core components
+│   └── TokenTable.tsx
+├── hooks/                   # Custom hooks
+│   ├── useTokenList.ts
+│   ├── usePairList.ts
+│   └── useTransactionList.ts
+├── types/                   # TypeScript definitions
+│   └── explore.ts
+└── index.ts                 # Clean exports
+
+

Key Improvements:

+
    +
  1. Enhanced Layout: Modern header with clear navigation
  2. +
  3. Improved Tables: Better styling and responsive design
  4. +
  5. Advanced Filtering: Sort and search functionality
  6. +
  7. Transaction Tracking: Color-coded transaction types
  8. +
  9. Better Navigation: Direct links to swap/add liquidity
  10. +
  11. Modular Hooks: Separated data fetching logic
  12. +
+

New Features & Enhancements

+

🏊‍♂️ Pool Features

+
    +
  • Stats Overview: Real-time position statistics
  • +
  • Tab Navigation: Seamless switching between views
  • +
  • Position Cards: Rich display of liquidity information
  • +
  • Quick Actions: Easy access to common operations
  • +
  • Empty States: Helpful guidance for new users
  • +
  • Add Liquidity Form: Integrated form with preview
  • +
+

🔍 Explore Features

+
    +
  • Enhanced Tables: Better responsive design
  • +
  • Advanced Sorting: Multiple sort options per table
  • +
  • Real-time Search: Instant filtering
  • +
  • Transaction Types: Visual indicators for different operations
  • +
  • Direct Actions: One-click access to swap/add liquidity
  • +
  • Better Navigation: Improved user flow
  • +
+

Technical Improvements

+

🏗️ Architecture

+
    +
  • Separation of Concerns: Logic separated into custom hooks
  • +
  • Reusable Components: Modular, testable components
  • +
  • Type Safety: Comprehensive TypeScript coverage
  • +
  • Clean Exports: Organized index files
  • +
+

🎨 UI/UX

+
    +
  • Modern Design: Consistent with DEX component styling
  • +
  • Responsive Layout: Works on all screen sizes
  • +
  • Better Typography: Improved readability
  • +
  • Color Coding: Visual hierarchy and status indicators
  • +
  • Loading States: Better user feedback
  • +
+

Performance

+
    +
  • Optimized Hooks: Efficient data fetching and caching
  • +
  • Memoized Components: Reduced unnecessary re-renders
  • +
  • Lazy Loading: Components load only when needed
  • +
+

File Structure

+
src/
+├── components/
+│   ├── pool/                # Pool components
+│   │   ├── core/
+│   │   ├── hooks/
+│   │   ├── types/
+│   │   └── index.ts
+│   └── explore/             # Explore components
+│       ├── core/
+│       ├── hooks/
+│       ├── types/
+│       └── index.ts
+├── views/
+│   ├── Pool.tsx            # New refactored Pool
+│   └── Explore.tsx         # New refactored Explore
+└── archive/
+    ├── Pool.original.tsx   # Archived original
+    └── Explore.original.tsx # Archived original
+
+

Benefits Achieved

+

🔧 Maintainability

+
    +
  • Modular Code: Easy to modify individual components
  • +
  • Clear Structure: Logical organization of files
  • +
  • Type Safety: Reduced runtime errors
  • +
  • Documentation: Self-documenting component structure
  • +
+

🚀 Scalability

+
    +
  • Reusable Components: Can be used across the app
  • +
  • Extensible Hooks: Easy to add new functionality
  • +
  • Clean APIs: Well-defined interfaces
  • +
+

🎯 User Experience

+
    +
  • Better Navigation: Intuitive user flow
  • +
  • Faster Loading: Optimized data fetching
  • +
  • Responsive Design: Works on all devices
  • +
  • Visual Feedback: Clear status indicators
  • +
+

Next Steps

+

🧪 Testing

+
    +
  • [ ] Add unit tests for new components
  • +
  • [ ] Add integration tests for hooks
  • +
  • [ ] Test responsive design on mobile
  • +
+

🔄 Integration

+
    +
  • [ ] Update routing if needed
  • +
  • [ ] Test with real data
  • +
  • [ ] Performance monitoring
  • +
+

📈 Enhancements

+
    +
  • [ ] Add more pool analytics
  • +
  • [ ] Implement real-time updates
  • +
  • [ ] Add more transaction filters
  • +
  • [ ] Enhanced mobile experience
  • +
+

Migration Status

+

Pool Component: Fully migrated and active
+✅ Explore Component: Fully migrated and active
+✅ Archives: Original files safely stored
+✅ Routing: Automatically uses new components

+

The refactored Pool and Explore components are now fully functional and ready for production use! 🚀

+
+

This refactoring follows the same successful pattern used for the DEX component, ensuring consistency across the entire application architecture.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/SHADCN_MIGRATION_GUIDE/index.html b/site/_archive/SHADCN_MIGRATION_GUIDE/index.html new file mode 100644 index 000000000..2d07304c7 --- /dev/null +++ b/site/_archive/SHADCN_MIGRATION_GUIDE/index.html @@ -0,0 +1,1429 @@ + + + + + + + + + + + + + + + + + + + + + Shadcn/UI Migration Guide - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + +

Shadcn/UI Migration Guide

+

Overview

+

This project has been successfully migrated to use shadcn/ui components with Tailwind CSS while maintaining the existing design system and visual identity. The migration preserves all the "Gen Z Ultra Modern" styling with neon colors, glassmorphism effects, and gradient backgrounds.

+

What's Been Completed ✅

+

1. Installation & Configuration

+
    +
  • ✅ Installed shadcn/ui with proper configuration
  • +
  • ✅ Configured Tailwind CSS v4 with custom design system integration
  • +
  • ✅ Set up path aliases (@/*) for clean imports
  • +
  • ✅ Added required dependencies: class-variance-authority, @radix-ui/react-slot, tailwindcss-animate
  • +
+

2. Design System Integration

+
    +
  • ✅ Mapped existing CSS variables to Tailwind colors
  • +
  • ✅ Preserved all neon colors (--neon-pink, --neon-teal, etc.)
  • +
  • ✅ Maintained glassmorphism effects and gradients
  • +
  • ✅ Kept existing animations and transitions
  • +
  • ✅ Integrated with existing theme system (light/dark modes)
  • +
+

3. Custom Components Created

+
    +
  • AeButton: Enhanced shadcn button with all existing variants
  • +
  • AeCard: Custom card component with glassmorphism variants
  • +
  • AeDropdownMenu: Styled dropdown menu components
  • +
  • Form Components: Input, Label, Textarea, Select with design system styling
  • +
+

4. Component Migrations

+
    +
  • HeaderWalletButton: Fully migrated to use shadcn components
  • +
  • AeButton: Wrapper component maintaining backward compatibility
  • +
  • Demo Component: Created comprehensive showcase
  • +
+

5. Build System

+
    +
  • ✅ Build process working correctly
  • +
  • ✅ All TypeScript types properly configured
  • +
  • ✅ SCSS integration maintained for existing styles
  • +
+

File Structure

+
src/
+├── components/
+│   ├── ui/                    # New shadcn components
+│   │   ├── ae-button.tsx      # Custom button with design system
+│   │   ├── ae-card.tsx        # Custom card with variants
+│   │   ├── ae-dropdown-menu.tsx # Styled dropdown components
+│   │   ├── button.tsx         # Base shadcn button
+│   │   ├── card.tsx           # Base shadcn card
+│   │   ├── dropdown-menu.tsx  # Base shadcn dropdown
+│   │   ├── input.tsx          # Form input
+│   │   ├── label.tsx          # Form label
+│   │   ├── textarea.tsx       # Form textarea
+│   │   ├── select.tsx         # Form select
+│   │   ├── badge.tsx          # Badge component
+│   │   ├── avatar.tsx         # Avatar component
+│   │   └── separator.tsx      # Separator component
+│   ├── AeButton.tsx           # Legacy wrapper (backward compatible)
+│   ├── ShadcnDemo.tsx         # Migration showcase
+│   └── layout/app-header/
+│       └── HeaderWalletButton.tsx # Migrated component
+├── lib/
+│   └── utils.ts               # shadcn utility functions
+├── styles/
+│   ├── tailwind.css           # Tailwind with design system
+│   ├── base.scss              # Updated with Tailwind import
+│   └── variables.scss         # Existing design variables
+└── components.json            # shadcn configuration
+
+

Design System Mapping

+

Colors

+
/* Existing CSS Variables → Tailwind Classes */
+--primary-color  bg-primary, text-primary
+--accent-color  bg-accent, text-accent
+--neon-pink  bg-neon-pink, text-neon-pink
+--neon-teal  bg-neon-teal, text-neon-teal
+--neon-blue  bg-neon-blue, text-neon-blue
+--background-color  bg-background
+--standard-font-color  text-foreground
+--light-font-color  text-muted-foreground
+
+

Effects

+
/* Gradients */
+bg-primary-gradient    /* var(--primary-gradient) */
+bg-secondary-gradient  /* var(--secondary-gradient) */
+bg-card-gradient       /* var(--card-gradient) */
+bg-button-gradient     /* var(--button-gradient) */
+
+/* Shadows */
+shadow-glow           /* var(--glow-shadow) */
+shadow-card           /* var(--card-shadow) */
+shadow-button         /* var(--button-shadow) */
+
+/* Glassmorphism */
+bg-glass-bg           /* var(--glass-bg) */
+border-glass-border   /* var(--glass-border) */
+backdrop-blur-glass   /* 10px blur */
+
+

Component Usage Examples

+

AeButton (Enhanced shadcn Button)

+
import { AeButton } from '@/components/ui/ae-button';
+
+// All existing variants work
+<AeButton variant="primary">Primary</AeButton>
+<AeButton variant="accent">Accent</AeButton>
+<AeButton variant="success">Success</AeButton>
+<AeButton variant="ghost">Ghost</AeButton>
+
+// New features
+<AeButton loading>Loading</AeButton>
+<AeButton glow>Glow Effect</AeButton>
+<AeButton fullWidth>Full Width</AeButton>
+
+

AeCard (Enhanced shadcn Card)

+
import { AeCard, AeCardHeader, AeCardTitle, AeCardContent } from '@/components/ui/ae-card';
+
+<AeCard variant="glass">
+  <AeCardHeader>
+    <AeCardTitle>Glass Card</AeCardTitle>
+  </AeCardHeader>
+  <AeCardContent>
+    Content with glassmorphism
+  </AeCardContent>
+</AeCard>
+
+ +
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/ae-dropdown-menu';
+
+<DropdownMenu>
+  <DropdownMenuTrigger asChild>
+    <AeButton variant="ghost">Open Menu</AeButton>
+  </DropdownMenuTrigger>
+  <DropdownMenuContent>
+    <DropdownMenuItem>Item 1</DropdownMenuItem>
+    <DropdownMenuItem>Item 2</DropdownMenuItem>
+  </DropdownMenuContent>
+</DropdownMenu>
+
+

Migration Strategy for Remaining Components

+

1. Identify Components to Migrate

+
    +
  • Form components (inputs, selects, etc.)
  • +
  • Modal components
  • +
  • Navigation components
  • +
  • Data display components (tables, lists)
  • +
+

2. Migration Steps

+
    +
  1. Analyze existing component: Understand current styling and behavior
  2. +
  3. Find shadcn equivalent: Check if shadcn has a similar component
  4. +
  5. Create custom variant: Extend shadcn component with design system
  6. +
  7. Update imports: Replace old component with new one
  8. +
  9. Test thoroughly: Ensure all functionality works
  10. +
+

3. Backward Compatibility

+
    +
  • Keep existing component files as wrappers initially
  • +
  • Gradually migrate usage to new components
  • +
  • Remove old components after full migration
  • +
+

Benefits Achieved

+

1. Consistency

+
    +
  • Unified component API across the application
  • +
  • Consistent styling patterns
  • +
  • Better maintainability
  • +
+

2. Performance

+
    +
  • Smaller bundle sizes with tree-shaking
  • +
  • Better CSS optimization with Tailwind
  • +
  • Reduced runtime style calculations
  • +
+

3. Developer Experience

+
    +
  • Better TypeScript support
  • +
  • IntelliSense for component props
  • +
  • Easier theming and customization
  • +
+

4. Design System

+
    +
  • Preserved all existing visual identity
  • +
  • Enhanced with modern component patterns
  • +
  • Better responsive design support
  • +
+

Next Steps

+

Immediate (High Priority)

+
    +
  1. Migrate form components: Input, Select, Textarea, etc.
  2. +
  3. Migrate modal components: Dialog, AlertDialog, etc.
  4. +
  5. Migrate navigation components: Tabs, NavigationMenu, etc.
  6. +
+

Medium Priority

+
    +
  1. Migrate data display components: Table, List, etc.
  2. +
  3. Migrate feedback components: Toast, Alert, etc.
  4. +
  5. Optimize bundle size: Remove unused SCSS files
  6. +
+

Long Term

+
    +
  1. Remove legacy components: Clean up old SCSS files
  2. +
  3. Performance optimization: Code splitting, lazy loading
  4. +
  5. Documentation: Create component documentation
  6. +
+

Testing

+

Build Test

+
npm run build  # ✅ Successful
+
+

Development Test

+
npm run dev    # Test in development mode
+
+

Component Test

+
    +
  • Visit /shadcn-demo route to see all migrated components
  • +
  • Test responsive design on different screen sizes
  • +
  • Verify theme switching (light/dark modes)
  • +
+

Troubleshooting

+

Common Issues

+
    +
  1. Import errors: Ensure path aliases are configured correctly
  2. +
  3. Styling conflicts: Check Tailwind CSS import order
  4. +
  5. Type errors: Verify component prop types match
  6. +
+

Solutions

+
    +
  1. Clear cache: rm -rf node_modules/.vite
  2. +
  3. Reinstall dependencies: npm install
  4. +
  5. Check TypeScript config: Ensure path mapping is correct
  6. +
+

Conclusion

+

The migration to shadcn/ui has been successful while maintaining the existing design system. The project now benefits from: +- Modern component architecture +- Better TypeScript support +- Improved maintainability +- Preserved visual identity +- Enhanced developer experience

+

The foundation is now in place for continued migration of remaining components and future enhancements.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/SOCIAL_LAYOUT_TODO/index.html b/site/_archive/SOCIAL_LAYOUT_TODO/index.html new file mode 100644 index 000000000..d1cb6433a --- /dev/null +++ b/site/_archive/SOCIAL_LAYOUT_TODO/index.html @@ -0,0 +1,1064 @@ + + + + + + + + + + + + + + + + + + + + + SOCIAL LAYOUT TODO - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

SOCIAL LAYOUT TODO

+ +

Three‑Column Social Layout – TODO & Notes

+

This document tracks the migration to a modern three‑column social layout (left navigation, centered feed, right rail). Use this as the single source of truth for remaining work and decisions.

+

Goals

+
    +
  • Consistent shell with sticky rails and a focused, readable center column
  • +
  • Clean navigation on desktop; streamlined bottom navigation on mobile
  • +
  • Fast, accessible, and keyboard‑friendly experience
  • +
+

Status snapshot

+
    +
  • [x] 3‑column Shell supporting left and right rails
  • +
  • [x] LeftNav with logo, nested items, wallet button pinned to bottom
  • +
  • [x] Social routes wrapped with new shell (FeedList, PostDetail, UserProfile, TipDetail)
  • +
  • [x] Right rail hooked up and sticky; moved footer into the right column
  • +
+

Open tasks

+

Shell, header, and spacing

+
    +
  • [ ] Finalize sticky offset relative to header (choose top-16 or add center pt-16) and apply consistently to both rails
  • +
  • [ ] Add a shell prop for stickyTop to avoid hard‑coding offset values
  • +
  • [ ] Audit global footer placement (right rail vs full‑width page footer) and unify across non‑social sections
  • +
+

Center column and feed

+
    +
  • [ ] Normalize center width to ~640–700px for all social views
  • +
  • [ ] Add a lightweight “What’s happening?” composer at the top of the feed (desktop + mobile)
  • +
  • [ ] Add “new posts” toast when older content is shown and fresh content arrives
  • +
  • [ ] Virtualize long feeds (windowed list) and incremental media loading
  • +
+

Left navigation

+
    +
  • [ ] Expand navigation config for sections with children; keep single source of truth used by header and left nav
  • +
  • [ ] Add active/hover states parity and keyboard focus styles
  • +
  • [ ] Optional: compact icon‑only mode on narrower screens
  • +
+

Right rail

+
    +
  • [ ] Defer heavy widgets with IntersectionObserver
  • +
  • [ ] Convert data fetching to suspense‑friendly boundaries where useful
  • +
  • [ ] Provide per‑page toggles (e.g., hide trends on detail pages if desired)
  • +
+

Mobile and tablet

+
    +
  • [ ] Ensure rails are hidden < lg; keep bottom navigation for primary actions
  • +
  • [ ] Provide dedicated screens for right‑rail widgets on mobile (e.g., price/trends)
  • +
  • [ ] Review tap targets (44px), scroll restoration, and pull‑to‑refresh feel
  • +
+

Performance

+
    +
  • [ ] Code‑split large view chunks and right‑rail widgets
  • +
  • [ ] Memoize expensive components; avoid unnecessary gradients/filters offscreen
  • +
  • [ ] Add image lazy‑loading and responsive sources where relevant
  • +
+

Accessibility and keyboard

+
    +
  • [ ] Keyboard nav shortcuts: j/k next/prev item, / focus search, Cmd/Ctrl+Enter to post
  • +
  • [ ] ARIA live regions for toasts and live updates
  • +
  • [ ] Ensure focus outlines and tab order are consistent across rails and feed
  • +
+

Styling and tokens

+
    +
  • [ ] Consolidate widths and breakpoints in tailwind.config.js if needed
  • +
  • [ ] Retire unused rules from Shell.scss that are superseded by utility classes
  • +
  • [ ] Verify dark‑mode tokens match the new layout (borders, surface translucency)
  • +
+

QA and rollout

+
    +
  • [ ] Convert remaining social pages to the new shell (search, trending landing if desired)
  • +
  • [ ] Audit CLS/LCP; verify no header/rail jank on resize
  • +
  • [ ] Add basic unit/integration tests for layout wrappers
  • +
+

Useful entry points

+
    +
  • src/components/layout/Shell.tsx – three‑column shell
  • +
  • src/components/layout/LeftNav.tsx – desktop left navigation
  • +
  • src/components/layout/RightRail.tsx – right rail widgets
  • +
  • Social views: src/features/social/views/*
  • +
  • Header: src/components/layout/app-header/*
  • +
+

How to work on it

+

1) Start dev server: npm run dev +2) Edit the shell or rails, then verify on desktop (≥1024px) and mobile (<1024px) +3) Keep center column width consistent across FeedList, PostDetail, and UserProfile

+

Decisions log

+
    +
  • Rails are sticky; footer is currently rendered inside the right rail. Revisit if a global footer is preferred across all sections.
  • +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/TEST_MIGRATION_SUMMARY/index.html b/site/_archive/TEST_MIGRATION_SUMMARY/index.html new file mode 100644 index 000000000..489a1aba4 --- /dev/null +++ b/site/_archive/TEST_MIGRATION_SUMMARY/index.html @@ -0,0 +1,1244 @@ + + + + + + + + + + + + + + + + + + + + + DEX Test Migration Complete! ✅ - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

DEX Test Migration Complete! ✅

+

What Was Accomplished

+

Successfully migrated the DEX component tests from the old monolithic structure to work with the new refactored modular architecture.

+

Test Results

+

All Tests Passing (10/10)

+
    +
  1. loads tokens and computes quote on input - ✅ Working
  2. +
  3. quotes exact-out by filling amountOut and toggling exact output - ✅ Working
  4. +
  5. displays swap form with proper structure - ✅ Working
  6. +
  7. shows connect wallet button when no wallet is connected - ✅ Working
  8. +
  9. shows swap button when wallet is connected - ✅ Working
  10. +
  11. displays all DEX tabs - ✅ Working
  12. +
  13. displays specialized widgets - ✅ Working
  14. +
  15. handles token selection - ✅ Working
  16. +
  17. displays amount inputs with proper labels - ✅ Working
  18. +
  19. shows price impact when available - ✅ Working
  20. +
+

Key Changes Made

+

🔧 Updated Element Selectors

+
    +
  • Old: document.getElementById('dex-amount-in')
  • +
  • +

    New: screen.getByLabelText('amount-from')

    +
  • +
  • +

    Old: document.getElementById('dex-amount-out')

    +
  • +
  • New: screen.getByLabelText('amount-to')
  • +
+

🔧 Updated Button Selectors

+
    +
  • Old: screen.findByRole('button', { name: /swap/i })
  • +
  • New: screen.findAllByRole('button', { name: /swap/i })[0] (handles multiple swap buttons)
  • +
+

🔧 Added New Component Tests

+
    +
  • DEX Tabs: Tests for Dex, Pool, Explore, Add tokens tabs
  • +
  • Specialized Widgets: Tests for Ethxit and EthBridge widgets
  • +
  • Token Selection: Tests for combobox functionality
  • +
  • Input Validation: Tests for amount input attributes and behavior
  • +
+

🔧 Simplified Complex Tests

+
    +
  • Removed: Complex swap execution tests that required extensive mocking
  • +
  • Kept: Core functionality tests that verify the component structure and basic interactions
  • +
  • Added: New tests for the modular architecture
  • +
+

Test Coverage

+

Core Functionality

+
    +
  • Token loading and display
  • +
  • Quote calculation (exact-in and exact-out)
  • +
  • Amount input handling
  • +
  • Token selection
  • +
  • Wallet connection state
  • +
+

Component Structure

+
    +
  • DEX header and title
  • +
  • Swap form elements
  • +
  • Settings and configuration
  • +
  • Specialized widgets
  • +
  • Navigation tabs
  • +
+

User Interactions

+
    +
  • Input field interactions
  • +
  • Token selector interactions
  • +
  • Exact output toggle
  • +
  • Button states (enabled/disabled)
  • +
+

Technical Improvements

+

🚀 Better Test Stability

+
    +
  • Uses screen.getByLabelText() instead of getElementById() for more reliable element selection
  • +
  • Handles multiple elements with similar names (e.g., multiple swap buttons)
  • +
  • More flexible text matching for dynamic content
  • +
+

🚀 Enhanced Mocking

+
    +
  • Added toast provider mocking
  • +
  • Improved SDK mocking for the new component structure
  • +
  • Better error handling in test setup
  • +
+

🚀 Comprehensive Coverage

+
    +
  • Tests both connected and disconnected wallet states
  • +
  • Verifies all major UI components are present
  • +
  • Tests the new modular widget architecture
  • +
+

Files Modified

+

📝 Updated Files

+
    +
  • src/views/__tests__/Dex.test.tsx - Completely refactored for new architecture
  • +
+

📝 Test Dependencies

+
    +
  • All tests now work with the new src/components/dex/ structure
  • +
  • Compatible with the refactored hooks and components
  • +
  • Uses the new TypeScript types
  • +
+

Benefits Achieved

+

Maintainability

+
    +
  • Tests are now easier to understand and maintain
  • +
  • Better separation of concerns in test structure
  • +
  • More focused test cases
  • +
+

Reliability

+
    +
  • More stable element selection
  • +
  • Better handling of async operations
  • +
  • Improved error handling
  • +
+

Coverage

+
    +
  • Tests cover the new modular architecture
  • +
  • Verifies all major components are working
  • +
  • Tests both simple and complex interactions
  • +
+

Next Steps

+

🔄 Future Test Enhancements

+
    +
  1. Integration Tests: Add tests for the complete swap flow
  2. +
  3. Error Handling: Test error states and edge cases
  4. +
  5. Performance Tests: Test component performance with large token lists
  6. +
  7. Accessibility Tests: Add tests for accessibility features
  8. +
  9. Widget-Specific Tests: Add detailed tests for individual widgets
  10. +
+

🔄 Advanced Testing

+
    +
  1. E2E Tests: Add end-to-end tests for complete user workflows
  2. +
  3. Visual Regression Tests: Test UI consistency
  4. +
  5. Load Testing: Test performance under load
  6. +
+

Conclusion

+

The test migration has been successfully completed! All 10 tests are passing, providing comprehensive coverage of the new refactored DEX component architecture. The tests are now more maintainable, reliable, and provide better coverage of the modular component structure.

+

The refactored DEX component is now fully tested and ready for production use! 🚀

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/TRENDING_MOBILE_OPTIMIZATIONS/index.html b/site/_archive/TRENDING_MOBILE_OPTIMIZATIONS/index.html new file mode 100644 index 000000000..c292b057d --- /dev/null +++ b/site/_archive/TRENDING_MOBILE_OPTIMIZATIONS/index.html @@ -0,0 +1,1492 @@ + + + + + + + + + + + + + + + + + + + + + Trending Page Mobile Optimizations - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Trending Page Mobile Optimizations

+

Overview

+

This document outlines the comprehensive mobile optimizations implemented for the /trending page to provide an excellent user experience on mobile devices.

+

Key Improvements

+

1. Responsive Layout System

+
    +
  • Mobile Detection: Added window resize listener to detect mobile devices (≤768px)
  • +
  • Conditional Rendering: Separate mobile and desktop layouts for optimal UX
  • +
  • Touch-Friendly Interface: All interactive elements sized for touch input
  • +
+

2. Mobile-Optimized Components

+

MobileTrendingLayout

+
    +
  • Full-screen mobile layout with proper header structure
  • +
  • Responsive padding and spacing
  • +
  • Optimized typography for mobile screens
  • +
+

MobileTrendingBanner

+
    +
  • Stacked layout for mobile (text → stats → actions)
  • +
  • Responsive button grid that adapts to screen size
  • +
  • Touch-friendly button sizes (44px minimum height)
  • +
+

MobileTrendingControls

+
    +
  • Full-width search input with clear button
  • +
  • Vertical filter layout for better mobile UX
  • +
  • Touch-friendly timeframe selector buttons
  • +
  • Responsive form controls
  • +
+

MobileTrendingTokenCard

+
    +
  • Card-based layout instead of table rows
  • +
  • Optimized information hierarchy
  • +
  • Touch-friendly click targets
  • +
  • Integrated mini charts with proper sizing
  • +
+

MobileTrendingTagCard

+
    +
  • Compact tag display with clear actions
  • +
  • Conditional rendering for tokenized vs non-tokenized tags
  • +
  • Touch-friendly buttons with proper spacing
  • +
+

MobileTrendingPagination

+
    +
  • Mobile-optimized pagination controls
  • +
  • Touch-friendly page indicators
  • +
  • Responsive button layout
  • +
  • Loading state indicators
  • +
+

3. Performance Optimizations

+

Reduced Motion

+
    +
  • Faster animations on mobile (0.2s vs 0.3s)
  • +
  • Respects prefers-reduced-motion user preference
  • +
  • Optimized transitions for better performance
  • +
+

Touch Scrolling

+
    +
  • -webkit-overflow-scrolling: touch for smooth scrolling
  • +
  • Optimized scroll behavior for mobile devices
  • +
+

Loading States

+
    +
  • Skeleton loading animations
  • +
  • Shimmer effects for better perceived performance
  • +
  • Proper loading indicators with spinners
  • +
+

4. Visual Design Improvements

+

Typography

+
    +
  • Responsive font sizes (smaller on mobile)
  • +
  • Improved line heights for readability
  • +
  • Optimized font weights for mobile screens
  • +
+

Spacing & Layout

+
    +
  • Consistent 16px/12px padding system
  • +
  • Proper touch target spacing (minimum 44px)
  • +
  • Optimized margins and gaps for mobile
  • +
+

Color & Contrast

+
    +
  • Proper dark/light theme support
  • +
  • High contrast ratios for accessibility
  • +
  • Consistent color usage across components
  • +
+

5. User Experience Enhancements

+

Error Handling

+
    +
  • Mobile-optimized error states
  • +
  • Clear error messages with icons
  • +
  • Actionable error recovery options
  • +
+

Empty States

+
    +
  • Engaging empty state designs
  • +
  • Helpful messaging for different scenarios
  • +
  • Clear call-to-action buttons
  • +
+

Search & Filtering

+
    +
  • Full-width search input
  • +
  • Clear search functionality
  • +
  • Intuitive filter controls
  • +
  • Real-time search feedback
  • +
+

Technical Implementation

+

Component Structure

+
src/components/Trendminer/
+├── MobileTrendingLayout.tsx
+├── MobileTrendingLayout.scss
+├── MobileTrendingBanner.tsx
+├── MobileTrendingBanner.scss
+├── MobileTrendingControls.tsx
+├── MobileTrendingControls.scss
+├── MobileTrendingTagCard.tsx
+├── MobileTrendingTagCard.scss
+├── MobileTrendingPagination.tsx
+└── MobileTrendingPagination.scss
+
+

Responsive Breakpoints

+
    +
  • Mobile Small: ≤480px
  • +
  • Mobile: ≤768px
  • +
  • Tablet: 769px - 1024px
  • +
  • Desktop: ≥1025px
  • +
+

CSS Architecture

+
    +
  • Uses SCSS mixins for consistent responsive design
  • +
  • BEM methodology for component styling
  • +
  • CSS custom properties for theming
  • +
  • Mobile-first responsive approach
  • +
+

Accessibility Features

+

Touch Accessibility

+
    +
  • Minimum 44px touch targets
  • +
  • Proper touch feedback (active states)
  • +
  • Adequate spacing between interactive elements
  • +
+

Visual Accessibility

+
    +
  • High contrast ratios
  • +
  • Clear visual hierarchy
  • +
  • Proper focus indicators
  • +
  • Readable font sizes
  • +
+

Screen Reader Support

+
    +
  • Proper ARIA labels
  • +
  • Semantic HTML structure
  • +
  • Clear button and link text
  • +
+

Browser Compatibility

+

Supported Features

+
    +
  • CSS Grid and Flexbox
  • +
  • CSS Custom Properties
  • +
  • Touch events
  • +
  • Viewport units
  • +
  • Modern CSS animations
  • +
+

Fallbacks

+
    +
  • Graceful degradation for older browsers
  • +
  • Progressive enhancement approach
  • +
  • Feature detection for advanced features
  • +
+

Performance Metrics

+

Optimizations

+
    +
  • Reduced DOM complexity on mobile
  • +
  • Optimized re-renders with React
  • +
  • Efficient CSS selectors
  • +
  • Minimal JavaScript overhead
  • +
+

Loading Performance

+
    +
  • Lazy loading of non-critical components
  • +
  • Optimized bundle splitting
  • +
  • Efficient API calls
  • +
  • Proper caching strategies
  • +
+

Testing Considerations

+

Device Testing

+
    +
  • Test on various mobile devices
  • +
  • Different screen sizes and resolutions
  • +
  • Various touch input methods
  • +
  • Different browsers and versions
  • +
+

User Testing

+
    +
  • Touch interaction testing
  • +
  • Navigation flow validation
  • +
  • Performance testing on slower devices
  • +
  • Accessibility testing with screen readers
  • +
+

Future Enhancements

+

Potential Improvements

+
    +
  • Pull-to-refresh functionality
  • +
  • Infinite scroll for token lists
  • +
  • Offline support with service workers
  • +
  • Advanced search filters
  • +
  • Customizable mobile layouts
  • +
+

Analytics Integration

+
    +
  • Mobile-specific event tracking
  • +
  • Performance monitoring
  • +
  • User behavior analysis
  • +
  • A/B testing capabilities
  • +
+

Conclusion

+

The mobile optimization of the trending page provides a comprehensive, touch-friendly experience that maintains the functionality of the desktop version while being optimized for mobile devices. The implementation follows modern mobile design patterns and ensures excellent performance across all device types.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/backend-integration/index.html b/site/_archive/backend-integration/index.html new file mode 100644 index 000000000..8cb33b281 --- /dev/null +++ b/site/_archive/backend-integration/index.html @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + Backend Integration for App Extensions - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Backend Integration for App Extensions

+

This guide explains how to integrate an extension with a backend service. For Social Superhero, we reference the superhero-api repository.

+

When to use a backend

+
    +
  • Indexing historical data and building feeds
  • +
  • Search and rich queries
  • +
  • WebSocket push updates
  • +
  • Off-chain validation
  • +
+

Setup superhero-api

+
    +
  • Repo: https://github.com/superhero-com/superhero-api
  • +
  • Quick start (Docker): see repository README
  • +
  • Configure ENV in frontend: VITE_EXT_<YOUR_EXT>_API_URL
  • +
+

Client in extension

+

Create a small client under src/plugins/<id>/client/backend.ts and read the base URL from VITE_EXT_<ID>_API_URL or runtime __SUPERCONFIG__.

+

Patterns

+
    +
  • Pagination tokens instead of page numbers
  • +
  • Idempotent POSTs, signed requests where needed
  • +
  • WebSocket channels for live updates
  • +
+

Error handling

+
    +
  • Map HTTP errors to user-friendly messages
  • +
  • Retry with backoff
  • +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/build-governance-poll-extension/index.html b/site/_archive/build-governance-poll-extension/index.html new file mode 100644 index 000000000..f6aa36de5 --- /dev/null +++ b/site/_archive/build-governance-poll-extension/index.html @@ -0,0 +1,1026 @@ + + + + + + + + + + + + + + + + + + + + + How to build a Superhero App Extension (Polls example) - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

How to build a Superhero App Extension (Polls example)

+

This guide shows how to build a full Superhero App Extension using the Governance Polls example. You will add a feed plugin that renders polls, wire voting/revoking via Sophia contracts, add a composer attachment, integrate the Governance backend, and test locally. Follow the steps in order.

+

See the Extensions overview for capabilities, loading modes, configuration and CI checks.

+

Prerequisites

+
    +
  • Node 18+, pnpm
  • +
  • Funded Aeternity testnet account (for signing votes)
  • +
  • Superhero dev environment and wallet
  • +
  • Endpoints (confirm or adjust in runtime config):
  • +
  • NODE_URL, MIDDLEWARE_URL, AE_COMPILER_URL
  • +
  • GOVERNANCE_API_URL
  • +
+

1) Project setup

+
    +
  1. Clone the repo and install dependencies.
  2. +
  3. Start the dev server.
  4. +
  5. Review runtime config (public/superconfig.json or window.__SUPERCONFIG__) and ensure Governance and MDW endpoints are set.
  6. +
+

2) Scaffold an extension

+

Use the scaffold script to generate a minimal plugin. +

pnpm run ext:scaffold governance-polls
+
+This creates src/plugins/governance-polls/index.tsx and src/plugins/governance-polls/ui/App.tsx.

+

3) Add capabilities and registration

+

In src/plugins/governance-polls/index.tsx: +- Set capabilities: ['feed', 'composer'] +- Register attachments: () => [pollAttachmentSpec] +- Optionally add a route and menu for discovery

+

4) Implement the feed plugin

+
    +
  • Create a PollCreatedEntryData type and a mapper adaptPollToEntry.
  • +
  • Implement fetchPage(page) that:
  • +
  • Queries open and closed polls via GovernanceApi.getPollOrdering(false/true)
  • +
  • For each poll, fetch creation time via MDW (contract create tx) and set createdAt on entries
  • +
  • Sort entries by createdAt descending
  • +
  • Render entries using PollCreatedCard and FeedRenderer.
  • +
+

5) Wire voting flows (Sophia contract integration)

+
    +
  • Use useAeSdk() to get sdk and ensure wallet is connected before voting.
  • +
  • Load the poll contract with GovernancePollACI.json and call vote(option).
  • +
  • Optimistically update UI (increment selected option, update totals) and refresh from backend.
  • +
  • Implement revoke_vote() similarly and update UI accordingly.
  • +
+

6) Add composer attachment (create polls)

+
    +
  • Import pollAttachmentSpec from features/social/feed-plugins/poll-attachment and register via attachments: () => [pollAttachmentSpec].
  • +
  • This surfaces a poll creation UI in the composer toolbar; after posting, it runs its onAfterPost hook.
  • +
+

7) Local registration (dev only)

+
    +
  • Temporarily import your new plugin in src/plugins/local.ts and add to the localPlugins array for local testing.
  • +
  • Do not commit this if you don’t want it enabled by default; guard with an env flag if needed.
  • +
+

8) Testing

+
    +
  • Load the app, ensure feed renders poll cards.
  • +
  • Click an option to vote; confirm signing flow and UI updates.
  • +
  • Revoke vote; confirm UI updates and backend reflects the change after refresh.
  • +
+

9) Backend integration notes

+
    +
  • Governance endpoints supply ordering, overviews and accept event submissions to speed up cache updates.
  • +
  • You can change the base URL via runtime config. For advanced cases, add a tiny client wrapper (src/plugins/governance-polls/client/backend.ts).
  • +
  • Consider WebSocket updates or polling intervals for faster UI convergence.
  • +
+

10) Troubleshooting

+
    +
  • Wallet not connected: ensure useWalletConnect flow and wallet pairing works
  • +
  • Compiler/runtime mismatch: confirm AE_COMPILER_URL and SDK versions
  • +
  • ACI mismatch or wrong contract address: recompile and point to correct address
  • +
  • MDW differences: different deployments may expose slightly different transaction shapes; handle both micro_time and block_time
  • +
+

11) Submission checklist

+
    +
  • Extension compiles and passes pnpm run ext:check
  • +
  • Polls render; voting flows work
  • +
  • Composer poll attachment shows and works
  • +
+

12) Submit your extension (Fork + PR)

+

To contribute your extension to Superhero: +1. Fork the repository to your GitHub account. +2. Create a feature branch (e.g., feat/polls-extension). +3. Add your plugin module under src/plugins/<your-id>/ and any supporting files. +4. Ensure local testing is done via src/plugins/local.ts (do not auto-register in production by default). +5. Run pnpm run ext:check and fix any validation issues. +6. Open a Pull Request from your fork/branch to this repository with a brief description and screenshots. +7. Our CI will run the extension checks; address any feedback from reviewers.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/build-nft-marketplace-extension/index.html b/site/_archive/build-nft-marketplace-extension/index.html new file mode 100644 index 000000000..487fd1631 --- /dev/null +++ b/site/_archive/build-nft-marketplace-extension/index.html @@ -0,0 +1,930 @@ + + + + + + + + + + + + + + + + + + + + + Tutorial: Build an NFT Marketplace App Extension - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Tutorial: Build an NFT Marketplace App Extension

+

This guide walks you through creating a full-stack extension that adds an /nft route and integrates with contracts and an optional backend.

+

Prerequisites

+
    +
  • Node 18+, pnpm
  • +
  • Funded Aeternity testnet account
  • +
  • Superhero dev env running
  • +
  • ENV: VITE_EXT_NFT_API_URL (optional)
  • +
+

1) Scaffold

+
pnpm run ext:scaffold nft-marketplace
+
+

2) Register route & menu

+

See src/plugins/nft-marketplace/index.tsx.

+

3) UI stub

+

Edit src/plugins/nft-marketplace/ui/MarketApp.tsx to render listings.

+

4) Contracts

+
    +
  • Write contracts/Marketplace.aes
  • +
  • Compile & export ACI via a script
  • +
  • Load with createContractLoader(sdk)
  • +
+

5) Backend integration (optional)

+
    +
  • Add client/backend.ts and point to VITE_EXT_NFT_API_URL
  • +
  • Reference backend repo: https://github.com/superhero-com/superhero-api
  • +
+

6) Test & commit

+
    +
  • Run app, navigate to /nft
  • +
  • Conventional commits after each step
  • +
+

Troubleshooting

+
    +
  • Compiler mismatch → update compiler URL
  • +
  • Missing ACI → recompile contract
  • +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/_archive/components-tailwind-migration/index.html b/site/_archive/components-tailwind-migration/index.html new file mode 100644 index 000000000..12df3e03e --- /dev/null +++ b/site/_archive/components-tailwind-migration/index.html @@ -0,0 +1,1288 @@ + + + + + + + + + + + + + + + + + + + + + Components Tailwind/Shadcn Migration Status - Superhero Hackathon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Components Tailwind/Shadcn Migration Status

+

✅ Layout Components Migration Complete!

+

Status: All layout components have been successfully migrated to Tailwind CSS v3.4.17 (ShadCN compatible)

+

Key Changes Made: +- Downgraded from Tailwind CSS v4.1.12 (beta) to v3.4.17 (stable) for better ShadCN compatibility +- Updated Tailwind config to use ES module syntax with proper plugin imports
+- Fixed PostCSS config to use CommonJS (.cjs extension) for ES module compatibility +- Resolved all conflicting Tailwind class warnings +- Maintained original styling using CSS variables and hybrid approach where needed +- All components now build and run successfully with proper Tailwind styles loading

+

Migration Status Legend

+
    +
  • Migrated: Component fully converted to Tailwind/Shadcn
  • +
  • 🔄 In Progress: Currently being migrated
  • +
  • Pending: Not yet started
  • +
  • 🚫 Skip: Component doesn't need migration (utilities, contexts, etc.)
  • +
+

Components List

+

Core UI Components

+
    +
  • components/ui/ae-button.tsx - Custom shadcn button
  • +
  • components/ui/ae-card.tsx - Custom shadcn card
  • +
  • components/ui/ae-dropdown-menu.tsx - Custom shadcn dropdown
  • +
  • components/ui/button.tsx - Base shadcn button
  • +
  • components/ui/card.tsx - Base shadcn card
  • +
  • components/ui/dropdown-menu.tsx - Base shadcn dropdown
  • +
  • components/ui/input.tsx - Base shadcn input
  • +
  • components/ui/label.tsx - Base shadcn label
  • +
  • components/ui/textarea.tsx - Base shadcn textarea
  • +
  • components/ui/select.tsx - Base shadcn select
  • +
  • components/ui/separator.tsx - Base shadcn separator
  • +
  • components/ui/avatar.tsx - Base shadcn avatar
  • +
  • components/ui/badge.tsx - Base shadcn badge
  • +
+

Layout Components

+
    +
  • components/layout/app-header/HeaderWalletButton.tsx - Migrated to shadcn
  • +
  • components/layout/app-header/WebAppHeader.tsx - Migrated to Tailwind CSS
  • +
  • components/layout/app-header/MobileAppHeader.tsx - Migrated to Tailwind CSS
  • +
  • components/layout/app-header/AppHeader.tsx - Migrated to Tailwind CSS
  • +
  • components/layout/app-header/MobileNavigation.tsx - Migrated to Tailwind CSS
  • +
  • components/layout/RightRail.tsx - Migrated to Tailwind CSS
  • +
  • components/layout/LeftRail.tsx - Migrated to Tailwind CSS
  • +
  • components/layout/FooterSection.tsx - Migrated to Tailwind CSS
  • +
  • components/layout/Shell.tsx - Migrated to Tailwind CSS
  • +
+

Button Components

+
    +
  • components/AeButton.tsx - Wrapper for backward compatibility
  • +
  • components/WalletConnectBtn.tsx - Uses MiniWalletInfo and ConnectWalletButton
  • +
  • components/ConnectWalletButton.tsx - Migrated to AeButton
  • +
+

Form & Input Components

+
    +
  • components/SearchInput.tsx - Migrated to Input component with Tailwind
  • +
  • components/MobileInput.tsx - Migrated to Tailwind with responsive design and mobile optimizations
  • +
+

Card Components

+
    +
  • components/SwapCard.tsx - Migrated to Tailwind with modern tab interface and glassmorphism styling
  • +
  • components/TransactionCard.tsx - Migrated to AeCard with Badge components
  • +
  • components/MobileCard.tsx - Migrated to Tailwind with variant support and loading states
  • +
+ +
    +
  • components/modals/UserPopup.tsx - Migrated to shadcn Dialog with Avatar component and profile display
  • +
  • components/modals/TransactionConfirmModal.tsx - Migrated to shadcn Dialog with modern wallet confirmation UI
  • +
  • components/modals/PostModal.tsx - Migrated to shadcn Dialog with Input and Label components
  • +
  • components/modals/TokenSelect.tsx - Migrated to shadcn Dialog with token list and gradient avatars
  • +
  • components/modals/FeedItemMenu.tsx - Migrated to shadcn DropdownMenu with nested report dialog
  • +
  • components/modals/CookiesDialog.tsx - Migrated simple modal with modern styling
  • +
  • components/ModalProvider.tsx - Migrated with glassmorphism modal styling and backdrop blur
  • +
+

Display Components

+
    +
  • components/AddressAvatar.tsx - Migrated with glassmorphism avatar styling and fallback states
  • +
  • components/AddressChip.tsx - Migrated to Badge with glassmorphism
  • +
  • components/UserBadge.tsx - Migrated to AeCard with hover popover
  • +
  • components/TokenChip.tsx - Migrated to Badge with loading states
  • +
  • components/AeAmount.tsx - Migrated to Tailwind with font-mono styling
  • +
  • components/FiatValue.tsx - Migrated to Tailwind with muted foreground
  • +
  • components/MiniWalletInfo.tsx - Migrated to Tailwind classes
  • +
  • components/Spinner.tsx - Migrated to Tailwind with purple accent animation
  • +
  • components/CommentList.tsx - Migrated with modern card layout and avatar styling
  • +
+

DEX Components

+
    +
  • components/dex/core/SwapForm.tsx - Migrated with glassmorphism card, gradient buttons, and modern styling
  • +
  • components/dex/core/TokenInput.tsx - Migrated with AeCard, glassmorphism, and AeButton controls
  • +
  • components/dex/core/TokenSelector.tsx - Migrated with modern dialog styling and glassmorphism effects
  • +
  • components/dex/core/SwapConfirmation.tsx - Migrated with modern dialog styling and gradient effects
  • +
  • components/dex/core/SwapRouteInfo.tsx - Migrated to AeCard with Badge components for route display
  • +
  • components/dex/core/SwapTabSwitcher.tsx - Migrated to modern tab interface with glassmorphism
  • +
  • components/dex/core/LiquiditySuccessNotification.tsx - Migrated with animated success states and progress indicators
  • +
  • components/dex/DexSettings.tsx - Migrated to clean form styling with focus states
  • +
  • components/dex/widgets/NewAccountEducation.tsx - Migrated with vibrant gradients and animated elements
  • +
  • components/dex/supporting/RecentActivity.tsx - Migrated with glassmorphism cards and removed SCSS dependency
  • +
+

Social Components

+
    +
  • features/social/components/PostContent.tsx - Migrated with responsive media grids
  • +
  • features/social/components/FeedItem.tsx - Migrated to AeCard with glassmorphism
  • +
  • features/social/components/CreatePost.tsx - Migrated with glassmorphism design, responsive layout, and modern form styling
  • +
  • features/social/components/SortControls.tsx - Migrated to modern pill-style buttons
  • +
  • features/social/components/PostCommentsList.tsx - Migrated with loading/error states
  • +
  • features/social/components/EmptyState.tsx - Migrated to AeCard with icons
  • +
  • features/social/components/CommentItem.tsx - Migrated with nested reply structure
  • +
  • features/social/components/CommentForm.tsx - Migrated to AeCard with Textarea
  • +
+

Trendminer Components

+
    +
  • components/Trendminer/TokenChat.tsx - Migrated with modern chat interface and loading states
  • +
  • components/Trendminer/MobileTest.tsx - Migrated debug component with conditional styling
  • +
  • components/Trendminer/TvCandles.tsx - Migrated with Tailwind styling
  • +
  • components/Trendminer/MobileTrendingTagCard.tsx - Migrated with glassmorphism card styling and responsive design
  • +
  • components/Trendminer/TokenMiniChart.tsx - Migrated with loading state styling
  • +
  • components/Trendminer/LatestTransactionsCarousel.tsx - Migrated with infinite scroll animation and hover effects
  • +
  • components/Trendminer/Sparkline.tsx - Pure SVG component, no migration needed
  • +
  • components/Trendminer/ExploreTrendsSidebar.tsx - Migrated with responsive layout and modern card design
  • +
  • components/Trendminer/MobileTrendingBanner.tsx - Migrated with gradient backgrounds and responsive button layout
  • +
  • components/Trendminer/TrendingSidebar.tsx - Migrated with glassmorphism styling and gradient text effects
  • +
  • components/Trendminer/MobileTrendingControls.tsx - Migrated with modern form controls and filter interface
  • +
+

Feature Components

+
    +
  • features/dex/components/AddLiquidityForm.tsx
  • +
  • features/dex/components/RemoveLiquidityForm.tsx
  • +
  • features/dex/components/LiquidityPreview.tsx
  • +
  • features/dex/components/LiquidityConfirmation.tsx
  • +
  • features/dex/components/LiquidityPositionCard.tsx
  • +
  • features/dex/components/DexSettings.tsx
  • +
  • features/dex/components/charts/PoolCandlestickChart.tsx - Migrated to AeCard with Tailwind classes
  • +
  • features/dex/components/charts/TokenPricePerformance.tsx - Migrated to AeCard with proper Tailwind utilities
  • +
  • features/dex/components/charts/TokenPricePerformanceExample.tsx - Updated styling to use Tailwind classes
  • +
  • features/bridge/components/EthBridgeWidget.tsx - Migrated to AeCard with comprehensive Tailwind styling
  • +
+

Utility Components (Skip Migration)

+
    +
  • 🚫 components/ErrorBoundary.tsx - Error boundary utility
  • +
  • 🚫 components/ShadcnDemo.tsx - Demo component
  • +
  • 🚫 context/AeSdkProvider.tsx - Context provider
  • +
  • 🚫 features/dex/context/PoolProvider.tsx - Context provider
  • +
+

View Components (Lower Priority)

+
    +
  • views/UserProfile.tsx - Migrated with glassmorphism profile card and responsive design
  • +
  • views/PoolDetail.tsx - Migrated with glassmorphism cards and responsive grid layouts
  • +
  • views/TokenDetail.tsx - Migrated with glassmorphism cards and responsive stats grid
  • +
  • views/TxQueue.tsx - Simple component migrated to Tailwind classes
  • +
  • views/Swap.tsx - Migrated with glassmorphism input cards and enhanced form styling
  • +
  • views/Governance.tsx - Migrated with comprehensive Tailwind styling and modern glassmorphism design
  • +
  • views/ExploreRefactored.tsx - Migrated with modern tab navigation and responsive layouts
  • +
  • views/Dex.tsx - Migrated with gradient headers and clean spacing
  • +
  • views/AddTokens.tsx - Migrated with enhanced table styling and status badges
  • +
  • views/Explore.tsx - Migrated with modern tab navigation and table styling (duplicate of ExploreRefactored)
  • +
  • views/TipDetail.tsx - Migrated with modern comment system and responsive layout
  • +
  • views/Landing.tsx - Migrated with modern hero sections, glassmorphism cards and responsive design
  • +
  • views/Trending.tsx - Migrated with glassmorphism cards, modern table design and responsive layout
  • +
  • views/FAQ.tsx - Migrated with modern card layouts and interactive accordion
  • +
  • views/Privacy.tsx - Simple page migrated to Tailwind typography
  • +
  • views/Tracing.tsx - Debug page migrated with terminal-style code display
  • +
  • views/Conference.tsx - Video conference iframe migrated with enhanced styling
  • +
  • views/Terms.tsx - Legal page migrated to Tailwind typography
  • +
+

Trendminer Views

+
    +
  • views/Trendminer/TradeCard.tsx - Migrated with glassmorphism styling and modern form controls
  • +
  • views/Trendminer/Invite.tsx - Migrated with comprehensive Tailwind styling, glassmorphism cards and responsive design
  • +
  • views/Trendminer/Daos.tsx - Migrated with glassmorphism cards, responsive grid layout and modern controls
  • +
  • views/Trendminer/Dao.tsx - Migrated with glassmorphism styling and modern form controls
  • +
  • views/Trendminer/CreateToken.tsx - Migrated with modern input styling and responsive layout
  • +
  • views/Trendminer/TokenDetails.tsx - Migrated with comprehensive mobile-optimized layout and glassmorphism design
  • +
  • views/Trendminer/Accounts.tsx - Migrated with modern table styling and responsive layout
  • +
  • views/Trendminer/TrendCloud.tsx - Migrated with modern header controls and responsive design
  • +
  • views/Trendminer/TrendCloudVisx.tsx - Migrated with glassmorphism styling and modern color palette
  • +
  • views/Trendminer/AccountDetails.tsx - Migrated with responsive grid layout and glassmorphism cards
  • +
+

Feature Views

+
    +
  • features/social/views/FeedList.tsx
  • +
  • features/social/views/PostDetail.tsx
  • +
  • features/dex/views/DexSwap.tsx
  • +
  • features/dex/views/Pool.tsx
  • +
  • features/dex/views/DexExploreTokens.tsx
  • +
  • features/dex/views/DexExplorePools.tsx
  • +
  • features/dex/views/DexExploreTransactions.tsx
  • +
  • features/dex/views/DexWrap.tsx
  • +
  • features/dex/views/DexBridge.tsx
  • +
  • features/dex/layouts/DexLayout.tsx
  • +
+

Root Components

+
    +
  • 🚫 App.tsx - Root app component
  • +
  • 🚫 main.tsx - App entry point
  • +
  • 🚫 routes.tsx - Routing configuration
  • +
+

Migration Priority

+

Phase 1: Core Components (High Priority)

+
    +
  1. Button components (WalletConnectBtn, ConnectWalletButton)
  2. +
  3. Input components (SearchInput)
  4. +
  5. Display components (AddressChip, TokenChip, UserBadge)
  6. +
  7. Card components (SwapCard, TransactionCard)
  8. +
+

Phase 2: Layout & Navigation

+
    +
  1. Header components
  2. +
  3. Navigation components
  4. +
  5. Layout shells
  6. +
+

Phase 3: Feature Components

+
    +
  1. DEX components
  2. +
  3. Social components
  4. +
  5. Modal components
  6. +
+

Phase 4: Views & Pages

+
    +
  1. Core views
  2. +
  3. Feature views
  4. +
  5. Trendminer views
  6. +
+

Migration Notes

+
    +
  • Focus on components with SCSS files first
  • +
  • Maintain backward compatibility during migration
  • +
  • Test each component after migration
  • +
  • Update imports gradually
  • +
  • Remove old SCSS files after successful migration
  • +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/site/assets/images/favicon.png b/site/assets/images/favicon.png new file mode 100644 index 000000000..1cf13b9f9 Binary files /dev/null and b/site/assets/images/favicon.png differ diff --git a/site/assets/javascripts/bundle.f55a23d4.min.js b/site/assets/javascripts/bundle.f55a23d4.min.js new file mode 100644 index 000000000..01a46ad8b --- /dev/null +++ b/site/assets/javascripts/bundle.f55a23d4.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Vi=Object.getOwnPropertyDescriptor;var Di=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,zi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Ni=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Di(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Vi(t,n))||o.enumerable});return e};var Lt=(e,t,r)=>(r=e!=null?Wi(zi(e)):{},Ni(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,(function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function ee(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,ee())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var qr=xr((dy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Rt=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Rt=="object"?Rt.ClipboardJS=r():t.ClipboardJS=r()})(Rt,function(){return(function(){var e={686:(function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(D){try{return document.execCommand(D)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(D){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=D,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var V=f()(F);return u("copy"),F.remove(),V},ee=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=ee;function k(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(D)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,V=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:V});if(Y)return F==="cut"?y(Y):J(Y,{container:V})},qe=ft;function Fe(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(D)}function ki(D,A){if(!(D instanceof A))throw new TypeError("Cannot call a class as a function")}function no(D,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof V.action=="function"?V.action:this.defaultAction,this.target=typeof V.target=="function"?V.target:this.defaultTarget,this.text=typeof V.text=="function"?V.text:this.defaultText,this.container=Fe(V.container)==="object"?V.container:document.body}},{key:"listenClick",value:function(V){var Y=this;this.listener=c()(V,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(V){var Y=V.delegateTarget||V.currentTarget,$e=this.action(Y)||"copy",Wt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Wt?"success":"error",{action:$e,text:Wt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(V){return vr("action",V)}},{key:"defaultTarget",value:function(V){var Y=vr("target",V);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(V){return vr("text",V)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(V){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(V,Y)}},{key:"cut",value:function(V){return y(V)}},{key:"isSupported",value:function(){var V=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof V=="string"?[V]:V,$e=!!document.queryCommandSupported;return Y.forEach(function(Wt){$e=$e&&!!document.queryCommandSupported(Wt)}),$e}}]),M})(s()),Ui=Fi}),828:(function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a}),438:(function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p}),879:(function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}}),370:(function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p}),817:(function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n}),279:(function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function z(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],z(i)),z(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var Tr=Ue.EMPTY;function Nt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t})(j);var To=(function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t})(g);var _r=(function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(g);var _t={now:function(){return(_t.delegate||Date).now()},delegate:void 0};var At=(function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=_t);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t})(gt);var Lo=(function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(yt);var kr=new Lo(Oo);var Mo=(function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&o===r._scheduled&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t})(gt);var _o=(function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o;r?o=r.id:(o=this._scheduled,this._scheduled=void 0);var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t})(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Kt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Kt(Hr(e))?e.pop():void 0}function Yt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function Bt(e){return H(e==null?void 0:e.then)}function Gt(e){return H(e[bt])}function Jt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Xt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Zt=Zi();function er(e){return H(e==null?void 0:e[Zt])}function tr(e){return fo(this,arguments,function(){var r,o,n,i;return Dt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function rr(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Gt(e))return ea(e);if(xt(e))return ta(e);if(Bt(e))return ra(e);if(Jt(e))return Ao(e);if(er(e))return oa(e);if(rr(e))return na(e)}throw Xt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?Ve(t):Qo(function(){return new nr}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},ee=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;ee(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(ee,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(ee,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function Ht(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?kt(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function wt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?wt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function Tt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function De(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>De(e)),Q(De(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function ze(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return ze(e).pipe(m(({y:r})=>{let o=ce(e),n=Tt(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function Ne(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function $t(e){let t=matchMedia(e);return ir(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function zr(e,t){return e.pipe(v(r=>r?t():S))}function Nr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return Nr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return Nr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return Nr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return N([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(te("size")),n=N([o,r]).pipe(m(()=>De(e)));return N([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),St=JSON.parse(Ca.textContent);St.base=`${new URL(St.base,ye())}`;function xe(){return St}function B(e){return St.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?St.translations[e].replace("#",t.toString()):St.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Pt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Lt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=N([et(e),Ht(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(ze),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>N([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(kt(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());N([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>Ht(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return N([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),N([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>N([tn(e),ze(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Va(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Va(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Lt(Br());var Da=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function za(e){return ge(e).pipe(m(({width:t})=>({scrollable:Tt(e).width>t})),te("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Da++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),za(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function Na(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),Na(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?wt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Vn=x("table");function Dn(e){return e.replaceWith(Vn),Vn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function zn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));N([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=De(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),N([ze(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=Tt(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.offsetWidth&&c.autoplay?c.play().catch(()=>{}):c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function Nn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Dn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>zn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?ze(o):I({x:0,y:0}),i=O(et(t),Ht(t)).pipe(K());return N([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=De(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Pt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=Ne("search");return N([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>N([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(te("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:n>0&&o>=n}}),te("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),te("bottom"))));return N([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=$t("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Lt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(te("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),te("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(te("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Lt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function It(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),Ne("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),N([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var s;let i=new URL(t.base),a=__md_get("__outdated",sessionStorage,i);if(a===null){a=!0;let p=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(p)||(p=[p]);e:for(let c of p)for(let l of n.aliases.concat(n.version))if(new RegExp(c,"i").test(l)){a=!1;break e}__md_set("__outdated",a,sessionStorage,i)}if(a)for(let p of ae("outdated"))p.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),Ne("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(It)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return N([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));N([t.pipe(Ae(It)),r],(i,a)=>a).pipe(te("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(te("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);Ne("search").subscribe(l=>{s.setAttribute("role",l?"list":"presentation"),s.hidden=!l}),o.pipe(re(r),Wr(t.pipe(Ae(It)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Dr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return N([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return N([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=De(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),Ve({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Ve({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),Ve({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),Ve({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Ve({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),te("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(te("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(te("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),te("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return N([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),te("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Vr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){N([Ne("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?wt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ft=sn(),Ot=ln(Ft),to=an(),Oe=gn(),hr=$t("(min-width: 60em)"),Mi=$t("(min-width: 76.25em)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ft,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ft,Ot).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),jt=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Ot})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>Nn(e,{viewport$:Oe,target$:Ot,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ft}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:jt})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?zr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:jt})):zr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:jt}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:jt,target$:Ot})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:jt,target$:Ot})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ft;window.target$=Ot;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.f55a23d4.min.js.map + diff --git a/site/assets/javascripts/bundle.f55a23d4.min.js.map b/site/assets/javascripts/bundle.f55a23d4.min.js.map new file mode 100644 index 000000000..e3de73ff9 --- /dev/null +++ b/site/assets/javascripts/bundle.f55a23d4.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2025 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 60em)\")\nconst screen$ = watchMedia(\"(min-width: 76.25em)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n */\nexport class Subscription implements SubscriptionLike {\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param value The `next` value.\n */\n next(value: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param err The `error` exception.\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as ((value: T) => void) | undefined,\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent.\n * @param subscriber The stopped subscriber.\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @param subscribe The function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @param subscribe the subscriber function to be passed to the Observable constructor\n * @return A new observable.\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @param operator the operator defining the operation to take on the observable\n * @return A new observable with the Operator applied.\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param observerOrNext Either an {@link Observer} with some or all callback methods,\n * or the `next` handler that is called for each value emitted from the subscribed Observable.\n * @param error A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param complete A handler for a terminal event resulting from successful completion.\n * @return A subscription reference to the registered handlers.\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next A handler for each value emitted by the observable.\n * @return A promise that either resolves on observable completion or\n * rejects with the handled error.\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @return This instance of the observable.\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n *\n * @return The Observable result of all the operators having been called\n * in the order they were passed in.\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return Observable that this Subject casts to.\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param _bufferSize The size of the buffer to replay on subscription\n * @param _windowTime The amount of time the buffered items will stay buffered\n * @param _timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param state Some contextual data that the `work` function uses when called by the\n * Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is implicit\n * and defined by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param work A function representing a task, or some unit of work to be\n * executed by the Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is\n * implicit and defined by the Scheduler itself.\n * @param state Some contextual data that the `work` function uses when called\n * by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && id === scheduler._scheduled && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n let flushId;\n if (action) {\n flushId = action.id;\n } else {\n flushId = this._scheduled;\n this._scheduled = undefined;\n }\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an