commit ab81a74ed84fb11a1b0f22726c8c7c1243496b6e Author: admin Date: Wed May 28 09:47:43 2025 -0600 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..793b3ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/configs/code-server/.local/* +!/configs/code-server/.local/.gitkeep + +/configs/code-server/.config/* +!/configs/code-server/.config/.gitkeep + +.env +.env* \ No newline at end of file diff --git a/Dockerfile.code-server b/Dockerfile.code-server new file mode 100644 index 0000000..b922a07 --- /dev/null +++ b/Dockerfile.code-server @@ -0,0 +1,84 @@ +FROM codercom/code-server:latest + +USER root + +# Install Python and dependencies +RUN apt-get update && apt-get install -y \ + python3 \ + python3-pip \ + python3-venv \ + python3-full \ + pipx \ + # Dependencies for CairoSVG and Pillow (PIL) + libcairo2-dev \ + libfreetype6-dev \ + libffi-dev \ + libjpeg-dev \ + libpng-dev \ + libz-dev \ + python3-dev \ + pkg-config \ + # Additional dependencies for advanced image processing + libwebp-dev \ + libtiff5-dev \ + libopenjp2-7-dev \ + liblcms2-dev \ + libxml2-dev \ + libxslt1-dev \ + # PDF generation dependencies + weasyprint \ + fonts-roboto \ + # Git for git-based plugins + git \ + # For lxml + zlib1g-dev \ + # Required for some plugins + build-essential \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Switch to non-root user (coder) +USER coder + +# Set up a virtual environment for mkdocs +RUN mkdir -p /home/coder/.venv +RUN python3 -m venv /home/coder/.venv/mkdocs + +# Install mkdocs-material in the virtual environment with all extras +RUN /home/coder/.venv/mkdocs/bin/pip install "mkdocs-material[imaging,recommended,git]" + +# Install additional useful MkDocs plugins +RUN /home/coder/.venv/mkdocs/bin/pip install \ + mkdocs-minify-plugin \ + mkdocs-git-revision-date-localized-plugin \ + mkdocs-glightbox \ + mkdocs-redirects \ + mkdocs-awesome-pages-plugin \ + mkdocs-blog-plugin \ + mkdocs-rss-plugin \ + mkdocs-meta-descriptions-plugin \ + mkdocs-swagger-ui-tag \ + mkdocs-macros-plugin \ + mkdocs-material-extensions \ + mkdocs-section-index \ + mkdocs-table-reader-plugin \ + mkdocs-pdf-export-plugin \ + mkdocs-mermaid2-plugin \ + pymdown-extensions \ + pygments \ + pillow \ + cairosvg + +# Add the virtual environment bin to PATH +ENV PATH="/home/coder/.venv/mkdocs/bin:${PATH}" + +# Add shell configuration to activate the virtual environment in .bashrc +RUN echo 'export PATH="/home/coder/.venv/mkdocs/bin:$PATH"' >> ~/.bashrc +RUN echo 'export PATH="/home/coder/.local/bin:$PATH"' >> ~/.bashrc + +# Create a convenience script to simplify running mkdocs commands +RUN mkdir -p /home/coder/.local/bin \ + && echo '#!/bin/bash\ncd /home/coder/mkdocs\nmkdocs "$@"' > /home/coder/.local/bin/run-mkdocs \ + && chmod +x /home/coder/.local/bin/run-mkdocs + +WORKDIR /home/coder diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbe88fd --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# Changemaker Lite + +Changemaker Lite is a streamlined documentation and development platform featuring essential self-hosted services for creating, managing, and automating content workflows. + +## Features + +- **Homepage**: Modern dashboard for accessing all services +- **Code Server**: VS Code in your browser for remote development +- **MkDocs Material**: Beautiful documentation with live preview +- **Static Site Server**: High-performance hosting for built sites +- **Listmonk**: Self-hosted newsletter and email campaign management +- **PostgreSQL**: Reliable database backend +- **n8n**: Workflow automation and service integration +- **NocoDB**: No-code database platform and smart spreadsheet interface +- **Silex**: Visual website editor for drag-and-drop web design + +## Quick Start + +```bash +# Clone the repository +git clone https://gitea.bnkops.com/admin/Changemaker.git +cd changemaker.lite + +# Configure environment (creates .env file) +./config.sh + +# Start all services +docker compose up -d +``` + +## Service Access + +After starting, access services at: + +- **Homepage Dashboard**: http://localhost:3010 +- **Documentation (Dev)**: http://localhost:4000 +- **Documentation (Built)**: http://localhost:4001 +- **Code Server**: http://localhost:8888 +- **Silex Editor**: http://localhost:6805 +- **Listmonk**: http://localhost:9000 +- **n8n**: http://localhost:5678 +- **NocoDB**: http://localhost:8090 + +## Documentation + +Complete documentation is available in the MkDocs site, including: + +- Service configuration guides +- Integration examples +- Workflow automation tutorials +- Troubleshooting guides + +Visit http://localhost:4000 after starting services to access the full documentation. \ No newline at end of file diff --git a/add-cname-records.sh b/add-cname-records.sh new file mode 100755 index 0000000..1b7aef2 --- /dev/null +++ b/add-cname-records.sh @@ -0,0 +1,302 @@ +#!/bin/bash +echo "#############################################################" +echo "# " +echo "# DNS Setup for Changemaker.lite Services " +echo "# " +echo "# This script will ADD DNS records for your services. " +echo "# Existing DNS records will NOT be deleted. " +echo "# " +echo "#############################################################" +echo "" +echo "-------------------------------------------------------------" +echo "Cloudflare Credentials Required" +echo "Please ensure your .env file contains the following variables:" +echo " CF_API_TOKEN=your_cloudflare_api_token" +echo " CF_ZONE_ID=your_cloudflare_zone_id" +echo " CF_TUNNEL_ID=your_cloudflared_tunnel_id" +echo " CF_DOMAIN=yourdomain.com" +echo "" +echo "You can find these values in your Cloudflare dashboard:" +echo " - API Token: https://dash.cloudflare.com/profile/api-tokens (Create a token with Zone:DNS:Edit and Access:Apps:Edit permissions for your domain)" +echo " - Zone ID: On your domain's overview page" +echo " - Tunnel ID: In the Zero Trust dashboard under Access > Tunnels" +echo " - Domain: The domain you want to use for your services" +echo "" +echo "-------------------------------------------------------------" +echo "" +read -p "Type 'y' to continue or any other key to abort: " consent +if [[ "$consent" != "y" && "$consent" != "Y" ]]; then + echo "Aborted by user." + exit 1 +fi + +# Source environment variables from the .env file in the same directory +ENV_FILE="$(dirname "$0")/.env" +if [ -f "$ENV_FILE" ]; then + export $(grep -v '^#' "$ENV_FILE" | xargs) +else + echo "Error: .env file not found at $ENV_FILE" + exit 1 +fi + +# Check if required Cloudflare variables are set +if [ -z "$CF_API_TOKEN" ] || [ -z "$CF_ZONE_ID" ] || [ -z "$CF_TUNNEL_ID" ] || [ -z "$CF_DOMAIN" ]; then + echo "Error: One or more required Cloudflare environment variables (CF_API_TOKEN, CF_ZONE_ID, CF_TUNNEL_ID, CF_DOMAIN) are not set in $ENV_FILE." + exit 1 +fi + +# Check if jq is installed +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed. Please install jq to continue." + echo "On Debian/Ubuntu: sudo apt-get install jq" + echo "On RHEL/CentOS: sudo yum install jq" + exit 1 +fi + +# Array of subdomains that need DNS records +SUBDOMAINS=( + "homepage" + "excalidraw" + "listmonk" + "monica" + "flatnotes" + "code-server" + "ollama" + "open-webui" + "gitea" + "mini-qr" + "ferdium" + "answer" + "nocodb" + "n8n" + "convertx" + "rocket" + "live" + "vw" +) + +# Function to check if DNS record already exists +record_exists() { + local subdomain=$1 + local records=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=$subdomain.$CF_DOMAIN" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json") + + local count=$(echo $records | jq -r '.result | length') + [ "$count" -gt 0 ] +} + +# Add CNAME records for each subdomain (only if they don't exist) +echo "Adding DNS records for services..." + +for subdomain in "${SUBDOMAINS[@]}"; do + if record_exists "$subdomain"; then + echo "DNS record for $subdomain.$CF_DOMAIN already exists, skipping..." + else + echo "Adding CNAME record for $subdomain.$CF_DOMAIN..." + + response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{ + \"type\": \"CNAME\", + \"name\": \"$subdomain\", + \"content\": \"$CF_TUNNEL_ID.cfargotunnel.com\", + \"ttl\": 1, + \"proxied\": true + }") + + success=$(echo $response | jq -r '.success') + if [ "$success" == "true" ]; then + echo "✓ Successfully added CNAME record for $subdomain.$CF_DOMAIN" + else + echo "✗ Failed to add CNAME record for $subdomain.$CF_DOMAIN" + echo "Error: $(echo $response | jq -r '.errors[0].message')" + fi + fi +done + +# Add root domain record if it doesn't exist +if record_exists "@"; then + echo "Root domain DNS record already exists, skipping..." +else + echo "Adding root domain CNAME record..." + response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{ + \"type\": \"CNAME\", + \"name\": \"@\", + \"content\": \"$CF_TUNNEL_ID.cfargotunnel.com\", + \"ttl\": 1, + \"proxied\": true + }") + + success=$(echo $response | jq -r '.success') + if [ "$success" == "true" ]; then + echo "✓ Successfully added root domain CNAME record" + else + echo "✗ Failed to add root domain CNAME record" + echo "Error: $(echo $response | jq -r '.errors[0].message')" + fi +fi + +echo "" +echo "DNS records setup complete!" +echo "" + +# Prompt for admin email for secured services +echo "-------------------------------------------------------------" +echo "Setting up Cloudflare Access Protection" +echo "-------------------------------------------------------------" +echo "" +echo "The following services will be protected with authentication:" +echo " - homepage.$CF_DOMAIN" +echo " - code-server.$CF_DOMAIN" +echo " - live.$CF_DOMAIN" +echo "" +echo "Please enter the admin email address that should have access:" +read ADMIN_EMAIL + +# Validate email format +if [[ ! "$ADMIN_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then + echo "Error: Invalid email format. Please provide a valid email address." + exit 1 +fi + +# Services that require authentication +PROTECTED_SERVICES=("homepage" "code-server" "live") + +# Services that should have bypass policies (public access) +BYPASS_SERVICES=("excalidraw" "listmonk" "monica" "flatnotes" "ollama" "open-webui" "gitea" "mini-qr" "ferdium" "answer" "nocodb" "n8n" "convertx" "rocket" "vw") + +# Function to create access application with email authentication +create_protected_app() { + local service=$1 + echo "Setting up authentication for $service.$CF_DOMAIN..." + + app_response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{ + \"name\": \"$service $CF_DOMAIN\", + \"domain\": \"$service.$CF_DOMAIN\", + \"type\": \"self_hosted\", + \"session_duration\": \"24h\", + \"app_launcher_visible\": true, + \"skip_interstitial\": true + }") + + app_id=$(echo $app_response | jq -r '.result.id') + + if [ -z "$app_id" ] || [ "$app_id" == "null" ]; then + echo "✗ Error creating access application for $service" + return 1 + fi + + echo "✓ Created access application for $service (ID: $app_id)" + + # Create authentication policy + policy_response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps/$app_id/policies" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{ + \"name\": \"Admin Access\", + \"decision\": \"allow\", + \"include\": [{ + \"email\": { + \"email\": \"$ADMIN_EMAIL\" + } + }], + \"require\": [], + \"exclude\": [] + }") + + policy_success=$(echo $policy_response | jq -r '.success') + + if [ "$policy_success" == "true" ]; then + echo "✓ Created authentication policy for $service" + else + echo "✗ Failed to create authentication policy for $service" + fi +} + +# Function to create bypass application (public access) +create_bypass_app() { + local service=$1 + echo "Setting up public access for $service.$CF_DOMAIN..." + + app_response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{ + \"name\": \"$service $CF_DOMAIN\", + \"domain\": \"$service.$CF_DOMAIN\", + \"type\": \"self_hosted\", + \"session_duration\": \"24h\", + \"app_launcher_visible\": false, + \"skip_interstitial\": true + }") + + app_id=$(echo $app_response | jq -r '.result.id') + + if [ -z "$app_id" ] || [ "$app_id" == "null" ]; then + echo "✗ Error creating access application for $service" + return 1 + fi + + echo "✓ Created access application for $service (ID: $app_id)" + + # Create bypass policy + policy_response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps/$app_id/policies" \ + -H "Authorization: Bearer $CF_API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{ + \"name\": \"Public Access\", + \"decision\": \"bypass\", + \"include\": [{ + \"everyone\": {} + }], + \"require\": [], + \"exclude\": [] + }") + + policy_success=$(echo $policy_response | jq -r '.success') + + if [ "$policy_success" == "true" ]; then + echo "✓ Created public access policy for $service" + else + echo "✗ Failed to create public access policy for $service" + fi +} + +echo "Creating Cloudflare Access applications..." +echo "" + +# Create protected applications +for service in "${PROTECTED_SERVICES[@]}"; do + create_protected_app "$service" + echo "" +done + +# Create bypass applications for public services +for service in "${BYPASS_SERVICES[@]}"; do + create_bypass_app "$service" + echo "" +done + +echo "-------------------------------------------------------------" +echo "Setup Complete!" +echo "-------------------------------------------------------------" +echo "" +echo "Protected services (require authentication with $ADMIN_EMAIL):" +for service in "${PROTECTED_SERVICES[@]}"; do + echo " - https://$service.$CF_DOMAIN" +done +echo "" +echo "Public services (no authentication required):" +for service in "${BYPASS_SERVICES[@]}"; do + echo " - https://$service.$CF_DOMAIN" +done +echo "" +echo "All services should be accessible through your Cloudflare tunnel." diff --git a/config.sh b/config.sh new file mode 100755 index 0000000..ad4c949 --- /dev/null +++ b/config.sh @@ -0,0 +1,562 @@ +#!/bin/bash + +cat << "EOF" + ██████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ███████╗ + ██╔════╝██║ ██║██╔══██╗████╗ ██║██╔════╝ ██╔════╝ + ██║ ███████║███████║██╔██╗ ██║██║ ███╗█████╗ + ██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██╔══╝ + ╚██████╗██║ ██║██║ ██║██║ ╚████║╚██████╔╝███████╗ + ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝ + + ███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗ + ████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗ + ██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝ + ██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗ + ██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║ + ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ + Configuration Wizard +EOF + +# Get the absolute path of the script directory +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ENV_FILE="$SCRIPT_DIR/.env" +MKDOCS_YML="$SCRIPT_DIR/mkdocs/docs/mkdocs.yml" + +echo "Looking for .env file at: $ENV_FILE" + +# Function to generate a random secure password +generate_password() { + local length=${1:-16} + openssl rand -base64 48 | tr -dc 'a-zA-Z0-9!@#$%^&*()-_=+' | head -c "$length" +} + +# Function to safely update environment variables in .env file +update_env_var() { + local key=$1 + local value=$2 + local escaped_value=$(echo "$value" | sed 's/[\/&]/\\&/g') + + if grep -q "^$key=" "$ENV_FILE"; then + sed -i "s/^$key=.*/$key=$escaped_value/" "$ENV_FILE" + echo "Updated $key in .env file" + else + echo "$key=$escaped_value" >> "$ENV_FILE" + echo "Added $key to .env file" + fi +} + +# Function to create a timestamped backup of the .env file +backup_env_file() { + if [ -f "$ENV_FILE" ]; then + local timestamp=$(date +"%Y%m%d_%H%M%S") + local backup_file="$ENV_FILE.backup_$timestamp" + + echo "Creating backup of current .env file to: $backup_file" + if cp "$ENV_FILE" "$backup_file"; then + echo "Backup created successfully!" + return 0 + else + echo "Failed to create backup file. Proceeding with caution..." + return 1 + fi + fi +} + +# Function to initialize the .env file with default values +initialize_env_file() { + echo "Initializing new .env file with default values..." + + cat > "$ENV_FILE" << EOL +# Never share this file publicly. It contains sensitive information. +# This file is used to configure various applications and services. +# Generated by Changemaker Config Wizard on $(date) + +# User and Group Configuration +USER_NAME=coder +USER_ID=1000 +GROUP_ID=1000 + +# Port Configuration +CODE_SERVER_PORT=8888 +LISTMONK_PORT=9000 +LISTMONK_DB_PORT=5432 +MKDOCS_PORT=4000 +MKDOCS_SITE_SERVER_PORT=4001 +N8N_PORT=5678 +NOCODB_PORT=8090 +HOMEPAGE_PORT=3010 +SILEX_PORT=6805 + +# Domain Configuration +BASE_DOMAIN=https://changeme.org +DOMAIN=changeme.org +LISTMONK_HOSTNAME=listmonk.changeme.org +N8N_HOST=n8n.changeme.org +SILEX_HOST=silex.changeme.org + +# Cloudflare Configuration +CF_API_TOKEN=your_cloudflare_api_token +CF_ZONE_ID=your_cloudflare_zone_id +CF_TUNNEL_ID=your_cloudflared_tunnel_id +CF_DOMAIN=changeme.org + +# Database Configuration (PostgreSQL for Listmonk) +POSTGRES_USER=listmonk +POSTGRES_PASSWORD=changeMe +POSTGRES_DB=listmonk + +# Listmonk Admin Configuration +LISTMONK_ADMIN_USER=admin +LISTMONK_ADMIN_PASSWORD=changeMe + +# N8N Configuration +N8N_USER_EMAIL=admin@example.com +N8N_USER_PASSWORD=changeMe +N8N_ENCRYPTION_KEY=changeMe +GENERIC_TIMEZONE=UTC + +# Nocodb Configuration +NOCODB_PORT=8090 +NOCODB_JWT_SECRET=changeMe +NOCODB_DB_NAME=nocodb +NOCODB_DB_USER=noco +NOCODB_DB_PASSWORD=changeMe + +# Listmonk SMTP Configuration +LISTMONK_SMTP_HOST=smtp.example.com +LISTMONK_SMTP_PORT=587 +LISTMONK_SMTP_AUTH_PROTOCOL=plain +LISTMONK_SMTP_USERNAME=your-smtp-username +LISTMONK_SMTP_PASSWORD=your-smtp-password +LISTMONK_SMTP_HELLO_HOSTNAME=listmonk.changeme.org +LISTMONK_SMTP_TLS_ENABLED=true +LISTMONK_SMTP_TLS_SKIP_VERIFY=false +LISTMONK_SMTP_MAX_CONNS=10 +LISTMONK_SMTP_MAX_MSG_RETRIES=2 +LISTMONK_SMTP_IDLE_TIMEOUT=10s +LISTMONK_SMTP_WAIT_TIMEOUT=5s +LISTMONK_SMTP_EMAIL_HEADERS=List-Unsubscribe-Post=List-Unsubscribe=One-Click +EOL + + echo "New .env file created with default values." +} + +# Function to update the site_url in mkdocs.yml +update_mkdocs_yml() { + local new_domain=$1 + + if [ ! -f "$MKDOCS_YML" ]; then + echo "Warning: mkdocs.yml not found at $MKDOCS_YML" + return 1 + fi + + echo "Updating site_url in mkdocs.yml..." + + # Create a backup of the mkdocs.yml file + local timestamp=$(date +"%Y%m%d_%H%M%S") + local backup_file="${MKDOCS_YML}.backup_${timestamp}" + cp "$MKDOCS_YML" "$backup_file" + echo "Created backup of mkdocs.yml at $backup_file" + + # Update the site_url value + sed -i "s|^site_url:.*|site_url: https://$new_domain|" "$MKDOCS_YML" + + if grep -q "site_url: https://$new_domain" "$MKDOCS_YML"; then + echo "Updated site_url in mkdocs.yml to: https://$new_domain" + return 0 + else + echo "Warning: Failed to update site_url in mkdocs.yml" + return 1 + fi +} + +# Function to check if a port is in use +check_port() { + local port=$1 + if command -v ss >/dev/null 2>&1; then + ss -tuln | grep -q ":$port " + elif command -v netstat >/dev/null 2>&1; then + netstat -tuln | grep -q ":$port " + else + # Fallback to lsof if available + if command -v lsof >/dev/null 2>&1; then + lsof -i ":$port" >/dev/null 2>&1 + else + echo "Warning: Cannot check port availability. Please ensure ports are free manually." + return 1 + fi + fi +} + +# Function to check all service ports for conflicts +check_port_conflicts() { + echo "Checking for port conflicts..." + + local ports_to_check=( + "${CODE_SERVER_PORT:-8888}:Code Server" + "${LISTMONK_PORT:-9000}:Listmonk" + "${LISTMONK_DB_PORT:-5432}:Listmonk Database" + "${MKDOCS_PORT:-4000}:MkDocs" + "${MKDOCS_SITE_SERVER_PORT:-4001}:MkDocs Site Server" + "${N8N_PORT:-5678}:N8N" + "${NOCODB_PORT:-8090}:NocoDB" + "${HOMEPAGE_PORT:-3010}:Homepage" + "${SILEX_PORT:-6805}:Silex" + ) + + local conflicts_found=false + + for port_info in "${ports_to_check[@]}"; do + local port=$(echo "$port_info" | cut -d: -f1) + local service=$(echo "$port_info" | cut -d: -f2) + + if check_port "$port"; then + echo "⚠️ Port conflict detected: $port is already in use (assigned to $service)" + conflicts_found=true + else + echo "✅ Port $port is available for $service" + fi + done + + if [ "$conflicts_found" = true ]; then + echo "" + echo "Port conflicts detected! Please choose alternative ports or stop conflicting services." + read -p "Do you want to configure alternative ports? [Y/n]: " configure_ports + + if [[ "$configure_ports" =~ ^[Nn]$ ]]; then + echo "Configuration cancelled. Please resolve port conflicts and try again." + exit 1 + else + configure_alternative_ports + fi + else + echo "✅ All ports are available!" + fi +} + +# Function to configure alternative ports +configure_alternative_ports() { + echo "" + echo "---- Port Configuration ----" + + # Code Server + if check_port "${CODE_SERVER_PORT:-8888}"; then + read -p "Enter alternative port for Code Server [current: ${CODE_SERVER_PORT:-8888}]: " new_code_port + if [ ! -z "$new_code_port" ]; then + update_env_var "CODE_SERVER_PORT" "$new_code_port" + fi + fi + + # Listmonk + if check_port "${LISTMONK_PORT:-9000}"; then + read -p "Enter alternative port for Listmonk [current: ${LISTMONK_PORT:-9000}]: " new_listmonk_port + if [ ! -z "$new_listmonk_port" ]; then + update_env_var "LISTMONK_PORT" "$new_listmonk_port" + fi + fi + + # Listmonk DB + if check_port "${LISTMONK_DB_PORT:-5432}"; then + read -p "Enter alternative port for Listmonk Database [current: ${LISTMONK_DB_PORT:-5432}]: " new_db_port + if [ ! -z "$new_db_port" ]; then + update_env_var "LISTMONK_DB_PORT" "$new_db_port" + fi + fi + + # MkDocs + if check_port "${MKDOCS_PORT:-4000}"; then + read -p "Enter alternative port for MkDocs [current: ${MKDOCS_PORT:-4000}]: " new_mkdocs_port + if [ ! -z "$new_mkdocs_port" ]; then + update_env_var "MKDOCS_PORT" "$new_mkdocs_port" + fi + fi + + # MkDocs Site Server + if check_port "${MKDOCS_SITE_SERVER_PORT:-4001}"; then + read -p "Enter alternative port for MkDocs Site Server [current: ${MKDOCS_SITE_SERVER_PORT:-4001}]: " new_site_port + if [ ! -z "$new_site_port" ]; then + update_env_var "MKDOCS_SITE_SERVER_PORT" "$new_site_port" + fi + fi + + # N8N + if check_port "${N8N_PORT:-5678}"; then + read -p "Enter alternative port for N8N [current: ${N8N_PORT:-5678}]: " new_n8n_port + if [ ! -z "$new_n8n_port" ]; then + update_env_var "N8N_PORT" "$new_n8n_port" + fi + fi + + # NocoDB + if check_port "${NOCODB_PORT:-8090}"; then + read -p "Enter alternative port for NocoDB [current: ${NOCODB_PORT:-8090}]: " new_nocodb_port + if [ ! -z "$new_nocodb_port" ]; then + update_env_var "NOCODB_PORT" "$new_nocodb_port" + fi + fi + + # Homepage + if check_port "${HOMEPAGE_PORT:-3010}"; then + read -p "Enter alternative port for Homepage [current: ${HOMEPAGE_PORT:-3010}]: " new_homepage_port + if [ ! -z "$new_homepage_port" ]; then + update_env_var "HOMEPAGE_PORT" "$new_homepage_port" + fi + fi + + # Silex + if check_port "${SILEX_PORT:-6805}"; then + read -p "Enter alternative port for Silex [current: ${SILEX_PORT:-6805}]: " new_silex_port + if [ ! -z "$new_silex_port" ]; then + update_env_var "SILEX_PORT" "$new_silex_port" + fi + fi + + echo "Port configuration completed." +} + +# Initialize a new .env file if it doesn't exist +if [ ! -f "$ENV_FILE" ]; then + echo "No .env file found. Creating a new one from scratch." + touch "$ENV_FILE" + initialize_env_file +else + echo "Found existing .env file. Will update values." + backup_env_file +fi + +echo -e "\n\nWelcome to Changemaker Config!\n" +echo "This script will help you configure your .env file for Changemaker." +echo "Please provide the following information:" + +# Domain configuration +read -p "Enter your domain name (without protocol, e.g., example.com): " domain_name + +if [ -z "$domain_name" ]; then + echo "Domain name cannot be empty. Using default: changeme.org" + domain_name="changeme.org" +fi + +echo -e "\nUpdating domain settings in .env file..." + +# Update main domain settings +update_env_var "DOMAIN" "$domain_name" +update_env_var "BASE_DOMAIN" "https://$domain_name" +update_env_var "LISTMONK_HOSTNAME" "listmonk.$domain_name" +update_env_var "N8N_HOST" "n8n.$domain_name" +update_env_var "SILEX_HOST" "silex.$domain_name" +update_env_var "CF_DOMAIN" "$domain_name" + +echo "Domain settings updated successfully!" + +# Cloudflare Configuration +echo -e "\n---- Cloudflare Configuration ----" +echo "To use the DNS setup script, you'll need Cloudflare credentials." +echo "You can find these values in your Cloudflare dashboard:" +echo " - API Token: https://dash.cloudflare.com/profile/api-tokens" +echo " (Create a token with Zone:DNS:Edit and Access:Apps:Edit permissions)" +echo " - Zone ID: On your domain's overview page" +echo " - Tunnel ID: In the Zero Trust dashboard under Access > Tunnels" +echo "" + +read -p "Do you want to configure Cloudflare settings now? [Y/n]: " configure_cf + +if [[ ! "$configure_cf" =~ ^[Nn]$ ]]; then + echo "" + echo "Please enter your Cloudflare credentials:" + + # CF API Token + read -p "Enter your Cloudflare API Token: " cf_api_token + if [ ! -z "$cf_api_token" ]; then + # Basic validation for API token format + if [[ "$cf_api_token" =~ ^[A-Za-z0-9_-]{40}$ ]]; then + update_env_var "CF_API_TOKEN" "$cf_api_token" + echo "✅ Cloudflare API Token updated" + else + echo "⚠️ Warning: API Token format seems incorrect (should be 40 characters)" + update_env_var "CF_API_TOKEN" "$cf_api_token" + fi + else + echo "⚠️ Cloudflare API Token left unchanged" + fi + + # CF Zone ID + read -p "Enter your Cloudflare Zone ID: " cf_zone_id + if [ ! -z "$cf_zone_id" ]; then + # Basic validation for Zone ID format + if [[ "$cf_zone_id" =~ ^[a-f0-9]{32}$ ]]; then + update_env_var "CF_ZONE_ID" "$cf_zone_id" + echo "✅ Cloudflare Zone ID updated" + else + echo "⚠️ Warning: Zone ID format seems incorrect (should be 32 hex characters)" + update_env_var "CF_ZONE_ID" "$cf_zone_id" + fi + else + echo "⚠️ Cloudflare Zone ID left unchanged" + fi + + # CF Tunnel ID + read -p "Enter your Cloudflare Tunnel ID: " cf_tunnel_id + if [ ! -z "$cf_tunnel_id" ]; then + # Basic validation for Tunnel ID format (UUID) + if [[ "$cf_tunnel_id" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$ ]]; then + update_env_var "CF_TUNNEL_ID" "$cf_tunnel_id" + echo "✅ Cloudflare Tunnel ID updated" + else + echo "⚠️ Warning: Tunnel ID format seems incorrect (should be UUID format)" + update_env_var "CF_TUNNEL_ID" "$cf_tunnel_id" + fi + else + echo "⚠️ Cloudflare Tunnel ID left unchanged" + fi + + echo "" + echo "Cloudflare configuration completed!" + echo "You can now run './add-cname-records.sh' to set up DNS records." +else + echo "Skipping Cloudflare configuration. You can run this script again later to configure it." +fi + +# Update the site_url in mkdocs.yml +echo -e "\nUpdating site_url in mkdocs.yml..." +update_mkdocs_yml "$domain_name" + +# Check for port conflicts +echo -e "\n---- Checking Port Availability ----" +check_port_conflicts + +# Listmonk Admin Credentials configuration +echo -e "\n---- Listmonk Admin Credentials ----" +read -p "Enter Listmonk admin username [default: admin]: " listmonk_user +read -sp "Enter Listmonk admin password [default: strongpassword]: " listmonk_password +echo # Add new line after password input + +if [ -z "$listmonk_user" ]; then + listmonk_user="admin" +fi + +if [ -z "$listmonk_password" ]; then + listmonk_password="strongpassword" +fi + +update_env_var "LISTMONK_ADMIN_USER" "$listmonk_user" +update_env_var "LISTMONK_ADMIN_PASSWORD" "$listmonk_password" + +echo "Listmonk admin credentials updated." + +# N8N User Credentials configuration +echo -e "\n---- N8N Admin Credentials ----" +read -p "Enter N8N admin email [default: admin@example.com]: " n8n_email +read -sp "Enter N8N admin password [default: changeMe]: " n8n_password +echo # Add new line after password input + +if [ -z "$n8n_email" ]; then + n8n_email="admin@example.com" +fi + +if [ -z "$n8n_password" ]; then + n8n_password="changeMe" +fi + +update_env_var "N8N_USER_EMAIL" "$n8n_email" +update_env_var "N8N_USER_PASSWORD" "$n8n_password" + +echo "N8N admin credentials updated." + +# SMTP Configuration +echo -e "\n---- SMTP Configuration ----" +echo "Configure SMTP settings for sending emails through Listmonk." +read -p "Do you want to configure SMTP settings now? [Y/n]: " configure_smtp + +if [[ ! "$configure_smtp" =~ ^[Nn]$ ]]; then + echo "" + echo "Please enter your SMTP server details:" + + read -p "SMTP Host (e.g., smtp.gmail.com, smtp.sendgrid.net): " smtp_host + read -p "SMTP Port [default: 587]: " smtp_port + read -p "SMTP Username/Email: " smtp_username + read -sp "SMTP Password: " smtp_password + echo + read -p "Hello Hostname [default: ${LISTMONK_HOSTNAME:-listmonk.changeme.org}]: " smtp_hello + read -p "Enable TLS? [Y/n]: " smtp_tls + + # Set defaults + if [ -z "$smtp_port" ]; then + smtp_port="587" + fi + + if [ -z "$smtp_hello" ]; then + smtp_hello="${LISTMONK_HOSTNAME:-listmonk.changeme.org}" + fi + + if [[ "$smtp_tls" =~ ^[Nn]$ ]]; then + smtp_tls_enabled="false" + else + smtp_tls_enabled="true" + fi + + # Update SMTP settings in .env + if [ ! -z "$smtp_host" ]; then + update_env_var "LISTMONK_SMTP_HOST" "$smtp_host" + fi + + update_env_var "LISTMONK_SMTP_PORT" "$smtp_port" + + if [ ! -z "$smtp_username" ]; then + update_env_var "LISTMONK_SMTP_USERNAME" "$smtp_username" + fi + + if [ ! -z "$smtp_password" ]; then + update_env_var "LISTMONK_SMTP_PASSWORD" "$smtp_password" + fi + + update_env_var "LISTMONK_SMTP_HELLO_HOSTNAME" "$smtp_hello" + update_env_var "LISTMONK_SMTP_TLS_ENABLED" "$smtp_tls_enabled" + update_env_var "LISTMONK_SMTP_AUTH_PROTOCOL" "plain" + update_env_var "LISTMONK_SMTP_TLS_SKIP_VERIFY" "false" + update_env_var "LISTMONK_SMTP_MAX_CONNS" "10" + update_env_var "LISTMONK_SMTP_MAX_MSG_RETRIES" "2" + update_env_var "LISTMONK_SMTP_IDLE_TIMEOUT" "10s" + update_env_var "LISTMONK_SMTP_WAIT_TIMEOUT" "5s" + + echo "✅ SMTP configuration completed!" +else + echo "Skipping SMTP configuration. You can configure this later in the Listmonk admin interface." +fi + +# Generate secure passwords for database and encryption +echo -e "\n---- Generating Secure Passwords ----" +echo "Generating secure passwords for database and encryption keys..." + +# Generate and update database password +postgres_password=$(generate_password 20) +update_env_var "POSTGRES_PASSWORD" "$postgres_password" + +# Generate and update N8N encryption key +n8n_encryption_key=$(generate_password 32) +update_env_var "N8N_ENCRYPTION_KEY" "$n8n_encryption_key" + +# Generate and update NocoDB passwords +nocodb_jwt_secret=$(generate_password 32) +update_env_var "NOCODB_JWT_SECRET" "$nocodb_jwt_secret" + +nocodb_db_password=$(generate_password 20) +update_env_var "NOCODB_DB_PASSWORD" "$nocodb_db_password" + +echo "Secure passwords generated and updated." + +echo -e "\n✅ Configuration completed successfully!" +echo "Your .env file has been configured with:" +echo "- Domain: $domain_name" +echo "- Listmonk Admin: $listmonk_user" +echo "- N8N Admin Email: $n8n_email" +echo "- Secure random passwords for database, encryption, and NocoDB" +if [[ ! "$configure_cf" =~ ^[Nn]$ ]]; then + echo "- Cloudflare credentials for DNS management" +fi +echo -e "\nYour .env file is located at: $ENV_FILE" +echo "A backup of your original .env file was created before modifications." +echo -e "\nNext steps:" +echo "1. Run 'docker-compose up -d' to start your services" +if [[ ! "$configure_cf" =~ ^[Nn]$ ]]; then + echo "2. Run './add-cname-records.sh' to set up DNS records and access policies" +fi diff --git a/configs/code-server/.config/.gitkeep b/configs/code-server/.config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/configs/code-server/.local/.gitkeep b/configs/code-server/.local/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/configs/homepage/bookmarks.yaml b/configs/homepage/bookmarks.yaml new file mode 100755 index 0000000..04959f8 --- /dev/null +++ b/configs/homepage/bookmarks.yaml @@ -0,0 +1,50 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/configs/bookmarks + +- Documentation: + - Code Server: + - abbr: CS + href: https://github.com/coder/code-server + - MkDocs Material: + - abbr: MD + href: https://squidfunk.github.io/mkdocs-material/ + - Homepage: + - abbr: HP + href: https://gethomepage.dev/ + - Silex: + - abbr: SX + href: https://www.silex.me/ + +- Services: + - Listmonk: + - abbr: LM + href: https://listmonk.app/docs/ + - NocoDB: + - abbr: NC + href: https://docs.nocodb.com/ + - n8n: + - abbr: N8 + href: https://docs.n8n.io/ + - PostgreSQL: + - abbr: PG + href: https://www.postgresql.org/docs/ + +- Resources: + - Docker: + - abbr: DC + href: https://docs.docker.com/ + - Docker Compose: + - abbr: DCC + href: https://docs.docker.com/compose/ + - Nginx: + - abbr: NG + href: https://nginx.org/en/docs/ + +- Development: + - GitHub: + - abbr: GH + href: https://github.com/ + - Stack Overflow: + - abbr: SO + href: https://stackoverflow.com/ diff --git a/configs/homepage/custom.css b/configs/homepage/custom.css new file mode 100755 index 0000000..e69de29 diff --git a/configs/homepage/custom.js b/configs/homepage/custom.js new file mode 100755 index 0000000..e69de29 diff --git a/configs/homepage/docker.yaml b/configs/homepage/docker.yaml new file mode 100755 index 0000000..1916e18 --- /dev/null +++ b/configs/homepage/docker.yaml @@ -0,0 +1,6 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/configs/docker/ + +my-docker: + socket: /var/run/docker.sock diff --git a/configs/homepage/kubernetes.yaml b/configs/homepage/kubernetes.yaml new file mode 100755 index 0000000..aca6e82 --- /dev/null +++ b/configs/homepage/kubernetes.yaml @@ -0,0 +1,2 @@ +--- +# sample kubernetes config diff --git a/configs/homepage/logs/homepage.log b/configs/homepage/logs/homepage.log new file mode 100644 index 0000000..0de4c20 --- /dev/null +++ b/configs/homepage/logs/homepage.log @@ -0,0 +1,192 @@ +[2025-05-28T14:48:05.173Z] error: Error getting services from Docker server 'my-docker': [Error: connect EACCES /var/run/docker.sock] { + errno: -13, + code: 'EACCES', + syscall: 'connect', + address: '/var/run/docker.sock' +} +[2025-05-28T14:48:05.221Z] error: Error getting services from Docker server 'my-docker': [Error: connect EACCES /var/run/docker.sock] { + errno: -13, + code: 'EACCES', + syscall: 'connect', + address: '/var/run/docker.sock' +} +[2025-05-28T14:48:05.445Z] error: Error getting services from Docker server 'my-docker': [Error: connect EACCES /var/run/docker.sock] { + errno: -13, + code: 'EACCES', + syscall: 'connect', + address: '/var/run/docker.sock' +} +[2025-05-28T14:48:05.568Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.585Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.585Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.586Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.586Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.587Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.601Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.601Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.602Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.602Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.602Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.603Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.622Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.622Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.623Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.623Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.623Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:48:05.624Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.099Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.111Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.116Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.128Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.129Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.137Z] error: Error getting services from Docker server 'my-docker': [Error: connect EACCES /var/run/docker.sock] { + errno: -13, + code: 'EACCES', + syscall: 'connect', + address: '/var/run/docker.sock' +} +[2025-05-28T14:56:53.138Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.158Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.159Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.160Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.176Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.177Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.177Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.178Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.188Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.189Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.190Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.201Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:53.202Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.868Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.876Z] error: Error getting services from Docker server 'my-docker': [Error: connect EACCES /var/run/docker.sock] { + errno: -13, + code: 'EACCES', + syscall: 'connect', + address: '/var/run/docker.sock' +} +[2025-05-28T14:56:56.877Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.878Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.890Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.891Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.891Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.891Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.896Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.906Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.907Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.907Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.908Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.919Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.920Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.920Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.922Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.936Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) +[2025-05-28T14:56:56.936Z] error: Error: connect EACCES /var/run/docker.sock + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1636:16) + at PipeConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) diff --git a/configs/homepage/services.yaml b/configs/homepage/services.yaml new file mode 100644 index 0000000..ce97f59 --- /dev/null +++ b/configs/homepage/services.yaml @@ -0,0 +1,78 @@ +--- +# For public access, replace "http://localhost" with your domain (e.g., "https://changeme.org") + +- Essential Tools: + - Code Server: + href: "http://localhost:8888" + # href: "https://changeme.org:8888" # Uncomment for public access + description: VS Code in the browser + icon: mdi-code-braces + widget: + type: docker + container: code-server-changemaker + server: my-docker + - Listmonk: + href: "http://localhost:9000" + # href: "https://changeme.org:9000" # Uncomment for public access + description: Newsletter & mailing list manager + icon: mdi-email-newsletter + widget: + type: docker + container: listmonk_app + server: my-docker + - NocoDB: + href: "http://localhost:8090" + # href: "https://changeme.org:8090" # Uncomment for public access + description: No-code database platform + icon: mdi-database + widget: + type: docker + container: nocodb + server: my-docker + +- Content & Documentation: + - MkDocs (Live): + href: "http://localhost:4000" + # href: "https://changeme.org:4000" # Uncomment for public access + description: Live documentation server with hot reload + icon: mdi-book-open-page-variant + widget: + type: docker + container: mkdocs-changemaker + server: my-docker + - Static Site: + href: "http://localhost:4001" + # href: "https://changeme.org:4001" # Uncomment for public access + description: Built documentation hosting + icon: mdi-web + widget: + type: docker + container: mkdocs-site-server-changemaker + server: my-docker + +- Automation & Infrastructure: + - n8n: + href: "http://localhost:5678" + # href: "https://changeme.org:5678" # Uncomment for public access + description: Workflow automation platform + icon: mdi-workflow + widget: + type: docker + container: n8n-changemaker + server: my-docker + - PostgreSQL (Listmonk): + href: "#" + description: Database for Listmonk + icon: mdi-database-outline + widget: + type: docker + container: listmonk-db + server: my-docker + - PostgreSQL (NocoDB): + href: "#" + description: Database for NocoDB + icon: mdi-database-outline + widget: + type: docker + container: root_db + server: my-docker diff --git a/configs/homepage/settings.yaml b/configs/homepage/settings.yaml new file mode 100755 index 0000000..3ed96b3 --- /dev/null +++ b/configs/homepage/settings.yaml @@ -0,0 +1,41 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/configs/settings/ + +title: Changemaker Lite +description: Self-hosted platform for documentation, development, and automation +theme: dark # or light +color: purple + +background: + image: /images/background.png + blur: xs # sm, "", md, xl... see https://tailwindcss.com/docs/backdrop-blur + saturate: 100 # 0, 50, 100... see https://tailwindcss.com/docs/backdrop-saturate + brightness: 75 # 0, 50, 75... see https://tailwindcss.com/docs/backdrop-brightness + opacity: 50 # 0-100 + +cardBlur: xl # xs, md, +headerStyle: boxed + +layout: + style: columns + columns: 3 + +quicklaunch: + searchDescriptions: true + hideInternetSearch: true + showSearchSuggestions: true + hideVisitURL: true + provider: duckduckgo + +showStats: true + +bookmarks: + showCategories: true + showIcons: true + target: _blank + columns: 3 + pinned: + - Essential Tools:Code Server + - Content & Documentation:MkDocs (Live) + - Automation & Infrastructure:n8n \ No newline at end of file diff --git a/configs/homepage/widgets.yaml b/configs/homepage/widgets.yaml new file mode 100644 index 0000000..0c2fc24 --- /dev/null +++ b/configs/homepage/widgets.yaml @@ -0,0 +1,23 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/configs/info-widgets/ + +- resources: + cpu: true + memory: true + disk: / + +- greeting: + text_size: xl + text: "Welcome to Changemaker Lite" + +- datetime: + text_size: xl + format: + dateStyle: short + timeStyle: short + hour12: true + +- search: + provider: duckduckgo + target: _blank diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8299831 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,200 @@ +services: + code-server: + build: + context: . + dockerfile: Dockerfile.code-server + container_name: code-server-changemaker + environment: + - DOCKER_USER=${USER_NAME:-coder} + - DEFAULT_WORKSPACE=/home/coder/mkdocs/ + user: "${USER_ID:-1000}:${GROUP_ID:-1000}" + volumes: + - ./configs/code-server/.config:/home/coder/.config + - ./configs/code-server/.local:/home/coder/.local + - ./mkdocs:/home/coder/mkdocs/ + ports: + - "${CODE_SERVER_PORT:-8888}:8080" + restart: unless-stopped + networks: + - changemaker + + listmonk-app: + image: listmonk/listmonk:latest + container_name: listmonk_app + restart: unless-stopped + ports: + - "${LISTMONK_PORT:-9000}:9000" + networks: + - changemaker + hostname: ${LISTMONK_HOSTNAME} + depends_on: + - listmonk-db + command: [sh, -c, "./listmonk --install --idempotent --yes --config '' && ./listmonk --upgrade --yes --config '' && ./listmonk --config ''"] + environment: + LISTMONK_app__address: 0.0.0.0:9000 + LISTMONK_db__user: ${POSTGRES_USER} + LISTMONK_db__password: ${POSTGRES_PASSWORD} + LISTMONK_db__database: ${POSTGRES_DB} + LISTMONK_db__host: listmonk-db + LISTMONK_db__port: 5432 + LISTMONK_db__ssl_mode: disable + LISTMONK_db__max_open: 25 + LISTMONK_db__max_idle: 25 + LISTMONK_db__max_lifetime: 300s + TZ: Etc/UTC + LISTMONK_ADMIN_USER: ${LISTMONK_ADMIN_USER:-} + LISTMONK_ADMIN_PASSWORD: ${LISTMONK_ADMIN_PASSWORD:-} + # SMTP Configuration + LISTMONK_smtp__host: ${LISTMONK_SMTP_HOST:-} + LISTMONK_smtp__port: ${LISTMONK_SMTP_PORT:-587} + LISTMONK_smtp__auth_protocol: ${LISTMONK_SMTP_AUTH_PROTOCOL:-plain} + LISTMONK_smtp__username: ${LISTMONK_SMTP_USERNAME:-} + LISTMONK_smtp__password: ${LISTMONK_SMTP_PASSWORD:-} + LISTMONK_smtp__hello_hostname: ${LISTMONK_SMTP_HELLO_HOSTNAME:-} + LISTMONK_smtp__tls_enabled: ${LISTMONK_SMTP_TLS_ENABLED:-true} + LISTMONK_smtp__tls_skip_verify: ${LISTMONK_SMTP_TLS_SKIP_VERIFY:-false} + LISTMONK_smtp__max_conns: ${LISTMONK_SMTP_MAX_CONNS:-10} + LISTMONK_smtp__max_msg_retries: ${LISTMONK_SMTP_MAX_MSG_RETRIES:-2} + LISTMONK_smtp__idle_timeout: ${LISTMONK_SMTP_IDLE_TIMEOUT:-10s} + LISTMONK_smtp__wait_timeout: ${LISTMONK_SMTP_WAIT_TIMEOUT:-5s} + LISTMONK_smtp__email_headers: ${LISTMONK_SMTP_EMAIL_HEADERS:-} + volumes: + - ./assets/uploads:/listmonk/uploads:rw + + listmonk-db: + image: postgres:17-alpine + container_name: listmonk-db + restart: unless-stopped + ports: + - "${LISTMONK_DB_PORT:-5432}:5432" + networks: + - changemaker + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + interval: 10s + timeout: 5s + retries: 6 + volumes: + - type: volume + source: listmonk-data + target: /var/lib/postgresql/data + + mkdocs: + image: squidfunk/mkdocs-material + container_name: mkdocs-changemaker + volumes: + - ./mkdocs:/docs:rw + - ./assets/images:/docs/assets/images:rw + user: "${USER_ID:-1000}:${GROUP_ID:-1000}" + ports: + - "${MKDOCS_PORT:-4000}:8000" + environment: + - SITE_URL=${BASE_DOMAIN:-https://changeme.org} + command: serve --dev-addr=0.0.0.0:8000 --watch-theme --livereload + networks: + - changemaker + restart: unless-stopped + + mkdocs-site-server: + image: lscr.io/linuxserver/nginx:latest + container_name: mkdocs-site-server-changemaker + environment: + - PUID=${USER_ID:-1000} # Uses USER_ID from your .env file, defaults to 1000 + - PGID=${GROUP_ID:-1000} # Uses GROUP_ID from your .env file, defaults to 1000 + - TZ=Etc/UTC + volumes: + - ./mkdocs/site:/config/www # Mounts your static site to Nginx's web root + ports: + - "${MKDOCS_SITE_SERVER_PORT:-4001}:80" # Exposes Nginx's port 80 to host port 4001 + restart: unless-stopped + networks: + - changemaker + + n8n: + image: docker.n8n.io/n8nio/n8n + container_name: n8n-changemaker + restart: unless-stopped + ports: + - "${N8N_PORT:-5678}:5678" + environment: + - N8N_HOST=${N8N_HOST:-n8n.${DOMAIN}} + - N8N_PORT=5678 + - N8N_PROTOCOL=https + - NODE_ENV=production + - WEBHOOK_URL=https://${N8N_HOST:-n8n.${DOMAIN}}/ + - GENERIC_TIMEZONE=${GENERIC_TIMEZONE:-UTC} + - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY:-changeMe} + - N8N_USER_MANAGEMENT_DISABLED=false + - N8N_DEFAULT_USER_EMAIL=${N8N_USER_EMAIL:-admin@example.com} + - N8N_DEFAULT_USER_PASSWORD=${N8N_USER_PASSWORD:-changeMe} + volumes: + - n8n_data:/home/node/.n8n + - ./local-files:/files + networks: + - changemaker + + nocodb: + depends_on: + root_db: + condition: service_healthy + environment: + NC_DB: "pg://root_db:5432?u=postgres&p=password&d=root_db" + image: "nocodb/nocodb:latest" + ports: + - "${NOCODB_PORT:-8090}:8080" + restart: always + volumes: + - "nc_data:/usr/app/data" + networks: + - changemaker + root_db: + environment: + POSTGRES_DB: root_db + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + healthcheck: + interval: 10s + retries: 10 + test: "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\"" + timeout: 2s + image: postgres:16.6 + restart: always + volumes: + - "db_data:/var/lib/postgresql/data" + networks: + - changemaker + + # Homepage App + homepage-changemaker: + image: ghcr.io/gethomepage/homepage:latest + container_name: homepage-changemaker + ports: + - "${HOMEPAGE_PORT:-3010}:3000" + volumes: + - ./configs/homepage:/app/config + - ./assets/icons:/app/public/icons + - ./assets/images:/app/public/images + - /var/run/docker.sock:/var/run/docker.sock + environment: + - PUID=${USER_ID:-1000} + - PGID=${DOCKER_GROUP_ID:-984} + - TZ=Etc/UTC + - HOMEPAGE_ALLOWED_HOSTS=* + - HOMEPAGE_VAR_BASE_URL=${HOMEPAGE_VAR_BASE_URL:-http://localhost} + restart: unless-stopped + networks: + - changemaker + +networks: + changemaker: + driver: bridge + +volumes: + listmonk-data: + n8n_data: + nc_data: + db_data: \ No newline at end of file diff --git a/mkdocs/.cache/plugin/social/0ee943db87dfa39f1d8b8882384425da.png b/mkdocs/.cache/plugin/social/0ee943db87dfa39f1d8b8882384425da.png new file mode 100644 index 0000000..5263f14 Binary files /dev/null and b/mkdocs/.cache/plugin/social/0ee943db87dfa39f1d8b8882384425da.png differ diff --git a/mkdocs/.cache/plugin/social/13b7450908684a452205d94a249e3e4d.png b/mkdocs/.cache/plugin/social/13b7450908684a452205d94a249e3e4d.png new file mode 100644 index 0000000..237bb71 Binary files /dev/null and b/mkdocs/.cache/plugin/social/13b7450908684a452205d94a249e3e4d.png differ diff --git a/mkdocs/.cache/plugin/social/1ba54b15aedf0426dba781e37512dce1.png b/mkdocs/.cache/plugin/social/1ba54b15aedf0426dba781e37512dce1.png new file mode 100644 index 0000000..420d8e2 Binary files /dev/null and b/mkdocs/.cache/plugin/social/1ba54b15aedf0426dba781e37512dce1.png differ diff --git a/mkdocs/.cache/plugin/social/3a2fd3f826d2a906ed92c3970b25cb7d.png b/mkdocs/.cache/plugin/social/3a2fd3f826d2a906ed92c3970b25cb7d.png new file mode 100644 index 0000000..4971e61 Binary files /dev/null and b/mkdocs/.cache/plugin/social/3a2fd3f826d2a906ed92c3970b25cb7d.png differ diff --git a/mkdocs/.cache/plugin/social/431ab4f4662987971222ad7fc4583df8.png b/mkdocs/.cache/plugin/social/431ab4f4662987971222ad7fc4583df8.png new file mode 100644 index 0000000..1b798d8 Binary files /dev/null and b/mkdocs/.cache/plugin/social/431ab4f4662987971222ad7fc4583df8.png differ diff --git a/mkdocs/.cache/plugin/social/668c246629c618cf956e7cea78d4037d.png b/mkdocs/.cache/plugin/social/668c246629c618cf956e7cea78d4037d.png new file mode 100644 index 0000000..42681cc Binary files /dev/null and b/mkdocs/.cache/plugin/social/668c246629c618cf956e7cea78d4037d.png differ diff --git a/mkdocs/.cache/plugin/social/721bd151e1ec0d1e48006634b8ff2e38.png b/mkdocs/.cache/plugin/social/721bd151e1ec0d1e48006634b8ff2e38.png new file mode 100644 index 0000000..321a1b9 Binary files /dev/null and b/mkdocs/.cache/plugin/social/721bd151e1ec0d1e48006634b8ff2e38.png differ diff --git a/mkdocs/.cache/plugin/social/740e1015f23d88c791e7cd6c726e1081.png b/mkdocs/.cache/plugin/social/740e1015f23d88c791e7cd6c726e1081.png new file mode 100644 index 0000000..d10dcd9 Binary files /dev/null and b/mkdocs/.cache/plugin/social/740e1015f23d88c791e7cd6c726e1081.png differ diff --git a/mkdocs/.cache/plugin/social/83db91be2c8770ec6e0cad47805127ef.png b/mkdocs/.cache/plugin/social/83db91be2c8770ec6e0cad47805127ef.png new file mode 100644 index 0000000..6120273 Binary files /dev/null and b/mkdocs/.cache/plugin/social/83db91be2c8770ec6e0cad47805127ef.png differ diff --git a/mkdocs/.cache/plugin/social/96a422da08dcb28205d2e584a7c620ca.png b/mkdocs/.cache/plugin/social/96a422da08dcb28205d2e584a7c620ca.png new file mode 100644 index 0000000..c6dd85d Binary files /dev/null and b/mkdocs/.cache/plugin/social/96a422da08dcb28205d2e584a7c620ca.png differ diff --git a/mkdocs/.cache/plugin/social/9fdd03d6602a201cef87a201f27fc715.png b/mkdocs/.cache/plugin/social/9fdd03d6602a201cef87a201f27fc715.png new file mode 100644 index 0000000..ea5947b Binary files /dev/null and b/mkdocs/.cache/plugin/social/9fdd03d6602a201cef87a201f27fc715.png differ diff --git a/mkdocs/.cache/plugin/social/a5775bbacaf158405defcb68416ea8bd.png b/mkdocs/.cache/plugin/social/a5775bbacaf158405defcb68416ea8bd.png new file mode 100644 index 0000000..9615bd6 Binary files /dev/null and b/mkdocs/.cache/plugin/social/a5775bbacaf158405defcb68416ea8bd.png differ diff --git a/mkdocs/.cache/plugin/social/aa0707be07fb7b02ab3372711d954d83.png b/mkdocs/.cache/plugin/social/aa0707be07fb7b02ab3372711d954d83.png new file mode 100644 index 0000000..1a02082 Binary files /dev/null and b/mkdocs/.cache/plugin/social/aa0707be07fb7b02ab3372711d954d83.png differ diff --git a/mkdocs/.cache/plugin/social/af754735b566f6d264e3f21b80a2d139.png b/mkdocs/.cache/plugin/social/af754735b566f6d264e3f21b80a2d139.png new file mode 100644 index 0000000..8bc5d08 Binary files /dev/null and b/mkdocs/.cache/plugin/social/af754735b566f6d264e3f21b80a2d139.png differ diff --git a/mkdocs/.cache/plugin/social/ca7a35746e5b24c6195659a5ce3f5a0d.png b/mkdocs/.cache/plugin/social/ca7a35746e5b24c6195659a5ce3f5a0d.png new file mode 100644 index 0000000..f46cc75 Binary files /dev/null and b/mkdocs/.cache/plugin/social/ca7a35746e5b24c6195659a5ce3f5a0d.png differ diff --git a/mkdocs/.cache/plugin/social/d988b15f28c148e32971e4f12f707389.png b/mkdocs/.cache/plugin/social/d988b15f28c148e32971e4f12f707389.png new file mode 100644 index 0000000..91be978 Binary files /dev/null and b/mkdocs/.cache/plugin/social/d988b15f28c148e32971e4f12f707389.png differ diff --git a/mkdocs/.cache/plugin/social/fb97f0bfe74bea4659ea90c0a851c2ec.png b/mkdocs/.cache/plugin/social/fb97f0bfe74bea4659ea90c0a851c2ec.png new file mode 100644 index 0000000..9be948c Binary files /dev/null and b/mkdocs/.cache/plugin/social/fb97f0bfe74bea4659ea90c0a851c2ec.png differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Black Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Black Italic.ttf new file mode 100644 index 0000000..c71c549 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Black Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Black.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Black.ttf new file mode 100644 index 0000000..d51221a Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Black.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Bold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Bold Italic.ttf new file mode 100644 index 0000000..f73d681 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Bold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Bold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Bold.ttf new file mode 100644 index 0000000..9d7cf22 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Bold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Black Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Black Italic.ttf new file mode 100644 index 0000000..0c31e9f Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Black Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Black.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Black.ttf new file mode 100644 index 0000000..7529d1b Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Black.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Bold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Bold Italic.ttf new file mode 100644 index 0000000..d269187 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Bold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Bold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Bold.ttf new file mode 100644 index 0000000..c3ccd49 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Bold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraBold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraBold Italic.ttf new file mode 100644 index 0000000..aeff7c2 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraBold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraBold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraBold.ttf new file mode 100644 index 0000000..782442a Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraBold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraLight Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraLight Italic.ttf new file mode 100644 index 0000000..0f6fe70 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraLight Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraLight.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraLight.ttf new file mode 100644 index 0000000..16a1560 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed ExtraLight.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Italic.ttf new file mode 100644 index 0000000..3b387eb Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Light Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Light Italic.ttf new file mode 100644 index 0000000..9f623e0 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Light Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Light.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Light.ttf new file mode 100644 index 0000000..e70c357 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Light.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Medium Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Medium Italic.ttf new file mode 100644 index 0000000..80ff64e Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Medium Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Medium.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Medium.ttf new file mode 100644 index 0000000..dd2842b Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Medium.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Regular.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Regular.ttf new file mode 100644 index 0000000..5af42d4 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Regular.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed SemiBold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed SemiBold Italic.ttf new file mode 100644 index 0000000..6cb4656 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed SemiBold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed SemiBold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed SemiBold.ttf new file mode 100644 index 0000000..4297f17 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed SemiBold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Thin Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Thin Italic.ttf new file mode 100644 index 0000000..e58e966 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Thin Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Thin.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Thin.ttf new file mode 100644 index 0000000..1ccebcc Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Condensed Thin.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraBold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraBold Italic.ttf new file mode 100644 index 0000000..a5536f5 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraBold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraBold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraBold.ttf new file mode 100644 index 0000000..7092a88 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraBold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraLight Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraLight Italic.ttf new file mode 100644 index 0000000..23dbbef Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraLight Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraLight.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraLight.ttf new file mode 100644 index 0000000..75608c6 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/ExtraLight.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Italic.ttf new file mode 100644 index 0000000..978e53a Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Light Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Light Italic.ttf new file mode 100644 index 0000000..a6e5047 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Light Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Light.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Light.ttf new file mode 100644 index 0000000..6fcd5f9 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Light.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Medium Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Medium Italic.ttf new file mode 100644 index 0000000..ef9ed1b Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Medium Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Medium.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Medium.ttf new file mode 100644 index 0000000..d629e98 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Medium.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Regular.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Regular.ttf new file mode 100644 index 0000000..bba55f6 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Regular.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiBold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiBold Italic.ttf new file mode 100644 index 0000000..132cca1 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiBold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiBold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiBold.ttf new file mode 100644 index 0000000..3f34834 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiBold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Black Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Black Italic.ttf new file mode 100644 index 0000000..19a5096 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Black Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Black.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Black.ttf new file mode 100644 index 0000000..8eedb64 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Black.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Bold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Bold Italic.ttf new file mode 100644 index 0000000..8604aee Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Bold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Bold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Bold.ttf new file mode 100644 index 0000000..98d7b0d Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Bold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraBold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraBold Italic.ttf new file mode 100644 index 0000000..b40ce77 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraBold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraBold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraBold.ttf new file mode 100644 index 0000000..36423c3 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraBold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraLight Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraLight Italic.ttf new file mode 100644 index 0000000..929a093 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraLight Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraLight.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraLight.ttf new file mode 100644 index 0000000..e1c25a0 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed ExtraLight.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Italic.ttf new file mode 100644 index 0000000..23454ff Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Light Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Light Italic.ttf new file mode 100644 index 0000000..c096473 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Light Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Light.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Light.ttf new file mode 100644 index 0000000..b9aedcd Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Light.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Medium Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Medium Italic.ttf new file mode 100644 index 0000000..ab34b70 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Medium Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Medium.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Medium.ttf new file mode 100644 index 0000000..e9c34d6 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Medium.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Regular.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Regular.ttf new file mode 100644 index 0000000..36109ba Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Regular.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed SemiBold Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed SemiBold Italic.ttf new file mode 100644 index 0000000..e88bc4a Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed SemiBold Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed SemiBold.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed SemiBold.ttf new file mode 100644 index 0000000..6d10b33 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed SemiBold.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Thin Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Thin Italic.ttf new file mode 100644 index 0000000..81afeea Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Thin Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Thin.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Thin.ttf new file mode 100644 index 0000000..8ed8d79 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/SemiCondensed Thin.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Thin Italic.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Thin Italic.ttf new file mode 100644 index 0000000..0381198 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Thin Italic.ttf differ diff --git a/mkdocs/.cache/plugin/social/fonts/Roboto/Thin.ttf b/mkdocs/.cache/plugin/social/fonts/Roboto/Thin.ttf new file mode 100644 index 0000000..6ee97b8 Binary files /dev/null and b/mkdocs/.cache/plugin/social/fonts/Roboto/Thin.ttf differ diff --git a/mkdocs/docs/blog/index.md b/mkdocs/docs/blog/index.md new file mode 100644 index 0000000..e69de29 diff --git a/mkdocs/docs/getting-started.md b/mkdocs/docs/getting-started.md new file mode 100644 index 0000000..ef456d9 --- /dev/null +++ b/mkdocs/docs/getting-started.md @@ -0,0 +1,231 @@ +# Getting Started + +Welcome to Changemaker Lite! This guide will help you get up and running quickly. + +## Prerequisites + +Before starting, ensure you have: + +- [Docker](https://docs.docker.com/get-docker/) installed +- [Docker Compose](https://docs.docker.com/compose/install/) installed +- Git for cloning the repository +- Basic familiarity with terminal/command line + +## Installation + +### 1. Clone Repository + +```bash +git clone https://gitea.bnkops.com/admin/Changemaker.git +cd changemaker.lite +``` + +### 2. Configuration + +Run the configuration script to set up environment variables: + +```bash +./config.sh +``` + +This creates a `.env` file with default settings. You can edit this file to customize: +- Service ports +- Database credentials +- User/group IDs +- Domain settings + +### 3. Start Services + +Launch all services with Docker Compose: + +```bash +docker compose up -d +``` + +Wait a few minutes for all services to start, especially on first run as Docker images need to be downloaded. + +### 4. Verify Installation + +Check that services are running: + +```bash +docker compose ps +``` + +All services should show as "Up" status. + +## First Steps + +### Access the Dashboard + +Start with the **Homepage Dashboard**: http://localhost:3010 +- Central hub for all services +- Live status monitoring +- Quick access to all applications + +### Access Documentation + +1. **Development Server**: http://localhost:4000 + - Live preview with auto-reload + - Used while writing documentation + +2. **Production Server**: http://localhost:4001 + - Serves built static site + - Optimized for performance + +### Start Coding + +1. Open **Code Server**: http://localhost:8888 +2. Navigate to `/home/coder/mkdocs/` workspace +3. Edit documentation files in `docs/` directory +4. See changes instantly in development server + +### Set Up Email Campaigns + +1. Access **Listmonk**: http://localhost:9000 +2. Log in with admin credentials (set in config) +3. Configure SMTP settings +4. Create your first mailing list + +### Create Workflows + +1. Open **n8n**: http://localhost:5678 +2. Log in with admin credentials +3. Create your first workflow +4. Connect different services together + +### Manage Data with NocoDB + +1. Access **NocoDB**: http://localhost:8090 +2. Complete the initial setup +3. Create your first project +4. Import data or create new tables + +### Access All Services via Homepage + +1. Open **Homepage**: http://localhost:3010 +2. View all services in one dashboard +3. Monitor service status +4. Quick access to all applications + +## Configuration Details + +### Environment Variables + +Key settings in `.env` file: + +```env +# Service Ports +HOMEPAGE_PORT=3010 +CODE_SERVER_PORT=8888 +MKDOCS_PORT=4000 +MKDOCS_SITE_SERVER_PORT=4001 +LISTMONK_PORT=9000 +N8N_PORT=5678 +NOCODB_PORT=8090 + +# Database +POSTGRES_USER=listmonk +POSTGRES_PASSWORD=your_secure_password +POSTGRES_DB=listmonk + +# User/Group IDs (match your system) +USER_ID=1000 +GROUP_ID=1000 +``` + +### Volume Mounts + +Important directories: + +- `./mkdocs/`: Documentation source files +- `./configs/`: Service configurations +- `./assets/`: Shared assets and uploads +- Docker volumes for persistent data + +## Common Tasks + +### Writing Documentation + +1. Edit files in `mkdocs/docs/` using Code Server +2. Preview changes at http://localhost:4000 +3. Build static site: `docker exec mkdocs-changemaker mkdocs build` +4. View built site at http://localhost:4001 + +### Managing Services + +```bash +# View logs +docker compose logs [service-name] + +# Restart service +docker compose restart [service-name] + +# Stop all services +docker compose down + +# Update services +docker compose pull && docker compose up -d +``` + +### Backup Important Data + +- Export n8n workflows +- Backup PostgreSQL database +- Version control your documentation files +- Save service configurations + +## Troubleshooting + +### Port Conflicts + +If ports are already in use, edit `.env` file: + +```env +CODE_SERVER_PORT=8889 # Change from 8888 +MKDOCS_PORT=4002 # Change from 4000 +``` + +Then restart: `docker compose down && docker compose up -d` + +### Permission Issues + +Ensure correct user/group IDs in `.env`: + +```bash +# Get your user ID +id -u + +# Get your group ID +id -g +``` + +Update `.env` with these values and restart services. + +### Service Won't Start + +Check logs for specific errors: + +```bash +docker compose logs [service-name] +``` + +Common issues: +- Port conflicts +- Permission problems +- Missing environment variables +- Network connectivity + +## Next Steps + +- Explore the [Services](services/index.md) documentation +- Set up your first [n8n workflow](services/n8n.md) +- Configure [Listmonk](services/listmonk.md) for email campaigns +- Customize your [MkDocs](services/mkdocs.md) theme and content + +## Getting Help + +- Check service-specific documentation +- Review Docker container logs +- Verify environment configuration +- Test network connectivity between services diff --git a/mkdocs/docs/index.md b/mkdocs/docs/index.md new file mode 100644 index 0000000..d802c02 --- /dev/null +++ b/mkdocs/docs/index.md @@ -0,0 +1,60 @@ +# Welcome to Changemaker Lite + +A streamlined, self-hosted platform for documentation and development. + +## Quick Start + +Get up and running in minutes: + +```bash +# Clone the repository +git clone https://gitea.bnkops.com/admin/Changemaker.git +cd changemaker.lite + +# Configure environment +./config.sh + +# Start all services +docker compose up -d +``` + +## Services + +Changemaker Lite includes these essential services: + +- **[Homepage](services/homepage.md)** (Port 3010) - Service dashboard and monitoring +- **[Code Server](services/code-server.md)** (Port 8888) - VS Code in your browser +- **[MkDocs](services/mkdocs.md)** (Port 4000) - Documentation with live preview +- **[Static Server](services/static-server.md)** (Port 4001) - Built site hosting +- **[Waypoint Editor](services/waypoint-editor.md)** (Port 3020) - Visual page editor +- **[Listmonk](services/listmonk.md)** (Port 9000) - Newsletter management +- **[PostgreSQL](services/postgresql.md)** (Port 5432) - Database backend +- **[n8n](services/n8n.md)** (Port 5678) - Workflow automation +- **[NocoDB](services/nocodb.md)** (Port 8090) - No-code database platform + +## Getting Started + +1. **Documentation**: Start writing in [Code Server](http://localhost:8888) +2. **Preview**: See live changes at [MkDocs](http://localhost:4000) +3. **Production**: View built site at [Static Server](http://localhost:4001) +4. **Email**: Set up campaigns with [Listmonk](http://localhost:9000) +5. **Automation**: Create workflows in [n8n](http://localhost:5678) + +## Project Structure + +``` +changemaker.lite/ +├── docker-compose.yml # Service definitions +├── config.sh # Setup script +├── mkdocs/ # Documentation source +│ ├── docs/ # Markdown files +│ └── mkdocs.yml # Configuration +├── configs/ # Service configurations +└── assets/ # Shared assets +``` + +## Learn More + +- [Services Overview](services/index.md) - Detailed service documentation +- [Blog](blog/index.md) - Updates and tutorials +- [GitHub Repository](https://gitea.bnkops.com/admin/Changemaker) - Source code and issues diff --git a/mkdocs/docs/overrides/home.html b/mkdocs/docs/overrides/home.html new file mode 100644 index 0000000..e69de29 diff --git a/mkdocs/docs/overrides/main.html b/mkdocs/docs/overrides/main.html new file mode 100644 index 0000000..81a640e --- /dev/null +++ b/mkdocs/docs/overrides/main.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block extrahead %} +{% endblock %} + +{% block announce %} + +Changemaker Archive. Learn more +{% endblock %} diff --git a/mkdocs/docs/services/code-server.md b/mkdocs/docs/services/code-server.md new file mode 100644 index 0000000..82bd67c --- /dev/null +++ b/mkdocs/docs/services/code-server.md @@ -0,0 +1,59 @@ +# Code Server + +Visual Studio Code in your browser for remote development. + +## Overview + +Code Server provides a full Visual Studio Code experience in your web browser, allowing you to develop from any device. It runs on your server and provides access to your development environment through a web interface. + +## Features + +- Full VS Code experience in the browser +- Extensions support +- Terminal access +- Git integration +- File editing and management +- Multi-language support + +## Access + +- **Default Port**: 8888 +- **URL**: `http://localhost:8888` +- **Default Workspace**: `/home/coder/mkdocs/` + +## Configuration + +### Environment Variables + +- `DOCKER_USER`: The user to run code-server as (default: `coder`) +- `DEFAULT_WORKSPACE`: Default workspace directory +- `USER_ID`: User ID for file permissions +- `GROUP_ID`: Group ID for file permissions + +### Volumes + +- `./configs/code-server/.config`: VS Code configuration +- `./configs/code-server/.local`: Local data +- `./mkdocs`: Main workspace directory + +## Usage + +1. Access Code Server at `http://localhost:8888` +2. Open the `/home/coder/mkdocs/` workspace +3. Start editing your documentation files +4. Install extensions as needed +5. Use the integrated terminal for commands + +## Useful Extensions + +Consider installing these extensions for better documentation work: + +- Markdown All in One +- Material Design Icons +- GitLens +- Docker +- YAML + +## Official Documentation + +For more detailed information, visit the [official Code Server documentation](https://coder.com/docs/code-server). diff --git a/mkdocs/docs/services/homepage.md b/mkdocs/docs/services/homepage.md new file mode 100644 index 0000000..c597aa0 --- /dev/null +++ b/mkdocs/docs/services/homepage.md @@ -0,0 +1,199 @@ +# Homepage + +Modern dashboard for accessing all your self-hosted services. + +## Overview + +Homepage is a modern, fully static, fast, secure fully configurable application dashboard with integrations for over 100 services. It provides a beautiful and customizable interface to access all your Changemaker Lite services from a single location. + +## Features + +- **Service Dashboard**: Central hub for all your applications +- **Docker Integration**: Automatic service discovery and monitoring +- **Customizable Layout**: Flexible grid-based layout system +- **Service Widgets**: Live status and metrics for services +- **Quick Search**: Fast navigation with built-in search +- **Bookmarks**: Organize frequently used links +- **Dark/Light Themes**: Multiple color schemes available +- **Responsive Design**: Works on desktop and mobile devices + +## Access + +- **Default Port**: 3010 +- **URL**: `http://localhost:3010` +- **Configuration**: YAML-based configuration files + +## Configuration + +### Environment Variables + +- `HOMEPAGE_PORT`: External port mapping (default: 3010) +- `PUID`: User ID for file permissions (default: 1000) +- `PGID`: Group ID for file permissions (default: 1000) +- `TZ`: Timezone setting (default: Etc/UTC) +- `HOMEPAGE_ALLOWED_HOSTS`: Allowed hosts for the dashboard + +### Configuration Files + +Homepage uses YAML configuration files located in `./configs/homepage/`: + +- `settings.yaml`: Global settings and theme configuration +- `services.yaml`: Service definitions and widgets +- `bookmarks.yaml`: Bookmark categories and links +- `widgets.yaml`: Dashboard widgets configuration +- `docker.yaml`: Docker integration settings + +### Volumes + +- `./configs/homepage:/app/config`: Configuration files +- `./assets/icons:/app/public/icons`: Custom service icons +- `./assets/images:/app/public/images`: Background images and assets +- `/var/run/docker.sock:/var/run/docker.sock`: Docker socket for container monitoring + +## Changemaker Lite Services + +Homepage is pre-configured with all Changemaker Lite services: + +### Essential Tools +- **Code Server** (Port 8888): VS Code in the browser +- **Listmonk** (Port 9000): Newsletter & mailing list manager +- **NocoDB** (Port 8090): No-code database platform + +### Content & Documentation +- **MkDocs** (Port 4000): Live documentation server +- **Static Site** (Port 4001): Built documentation hosting + +### Automation & Data +- **n8n** (Port 5678): Workflow automation platform +- **PostgreSQL** (Port 5432): Database backends + +## Customization + +### Adding Custom Services + +Edit `configs/homepage/services.yaml` to add new services: + +```yaml +- Custom Category: + - My Service: + href: http://localhost:8080 + description: Custom service description + icon: mdi-application + widget: + type: ping + url: http://localhost:8080 +``` + +### Custom Icons + +Add custom icons to `./assets/icons/` directory and reference them in services.yaml: + +```yaml +icon: /icons/my-custom-icon.png +``` + +### Themes and Styling + +Modify `configs/homepage/settings.yaml` to customize appearance: + +```yaml +theme: dark # or light +color: purple # slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose +``` + +### Widgets + +Enable live monitoring widgets in `configs/homepage/services.yaml`: + +```yaml +- Service Name: + widget: + type: docker + container: container-name + server: my-docker +``` + +## Service Monitoring + +Homepage can display real-time status information for your services: + +- **Docker Integration**: Container status and resource usage +- **HTTP Ping**: Service availability monitoring +- **Custom APIs**: Integration with service-specific APIs + +## Docker Integration + +Homepage monitors Docker containers automatically when configured: + +1. Ensure Docker socket is mounted (`/var/run/docker.sock`) +2. Configure container mappings in `docker.yaml` +3. Add widget configurations to `services.yaml` + +## Security Considerations + +- Homepage runs with limited privileges +- Configuration files should have appropriate permissions +- Consider network isolation for production deployments +- Use HTTPS for external access +- Regularly update the Homepage image + +## Troubleshooting + +### Common Issues + +**Configuration not loading**: Check YAML syntax in configuration files +```bash +docker logs homepage-changemaker +``` + +**Icons not displaying**: Verify icon paths and file permissions +```bash +ls -la ./assets/icons/ +``` + +**Services not reachable**: Verify network connectivity between containers +```bash +docker exec homepage-changemaker ping service-name +``` + +**Widget data not updating**: Check Docker socket permissions and container access +```bash +docker exec homepage-changemaker ls -la /var/run/docker.sock +``` + +## Configuration Examples + +### Basic Service Widget + +```yaml +- Code Server: + href: http://localhost:8888 + description: VS Code in the browser + icon: code-server + widget: + type: docker + container: code-server-changemaker +``` + +### Custom Dashboard Layout + +```yaml +# settings.yaml +layout: + style: columns + columns: 3 + +# Responsive breakpoints +responsive: + mobile: 1 + tablet: 2 + desktop: 3 +``` + +## Official Documentation + +For comprehensive configuration guides and advanced features: +- [Homepage Documentation](https://gethomepage.dev/) +- [GitHub Repository](https://github.com/gethomepage/homepage) +- [Configuration Examples](https://gethomepage.dev/configs/) +- [Widget Integrations](https://gethomepage.dev/widgets/) diff --git a/mkdocs/docs/services/index.md b/mkdocs/docs/services/index.md new file mode 100644 index 0000000..85135b2 --- /dev/null +++ b/mkdocs/docs/services/index.md @@ -0,0 +1,175 @@ +# Services + +Changemaker Lite includes several powerful services that work together to provide a complete documentation and development platform. Each service is containerized and can be accessed through its dedicated port. + +## Available Services + +### [Code Server](code-server.md) +**Port: 8888** | Visual Studio Code in your browser for remote development +- Full IDE experience +- Extensions support +- Git integration +- Terminal access + +### [Listmonk](listmonk.md) +**Port: 9000** | Self-hosted newsletter and mailing list manager +- Email campaigns +- Subscriber management +- Analytics +- Template system + +### [PostgreSQL](postgresql.md) +**Port: 5432** | Reliable database backend +- Data persistence for Listmonk +- ACID compliance +- High performance +- Backup and restore capabilities + +### [MkDocs Material](mkdocs.md) +**Port: 4000** | Documentation site generator with live preview +- Material Design theme +- Live reload +- Search functionality +- Markdown support + +### [Static Site Server](static-server.md) +**Port: 4001** | Nginx-powered static site hosting +- High-performance serving +- Built documentation hosting +- Caching and compression +- Security headers + +### [Silex](silex.md) +**Port: 6805** | Visual website editor +- Drag-and-drop web design +- Filesystem-based storage +- Template editing for MkDocs +- No-code HTML creation + +### [n8n](n8n.md) +**Port: 5678** | Workflow automation tool +- Visual workflow editor +- 400+ integrations +- Custom code execution +- Webhook support + +### [NocoDB](nocodb.md) +**Port: 8090** | No-code database platform +- Smart spreadsheet interface +- Form builder and API generation +- Real-time collaboration +- Multi-database support + +### [Homepage](homepage.md) +**Port: 3010** | Modern dashboard for all services +- Service dashboard and monitoring +- Docker integration +- Customizable layout +- Quick search and bookmarks + +## Service Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Homepage │ │ Code Server │ │ MkDocs │ +│ :3010 │ │ :8888 │ │ :4000 │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Static Server │ │ Listmonk │ │ n8n │ +│ :4001 │ │ :9000 │ │ :5678 │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ NocoDB │ │ PostgreSQL │ │ PostgreSQL │ +│ :8090 │ │ (listmonk-db) │ │ (root_db) │ +└─────────────────┘ │ :5432 │ │ :5432 │ + │ └─────────────────┘ └─────────────────┘ + └──────────────────────────────────────────────────────┘ +``` + +## Getting Started + +1. **Start all services**: `docker compose up -d` +2. **Check service status**: `docker compose ps` +3. **View logs**: `docker compose logs [service-name]` +4. **Stop services**: `docker compose down` + +## Service Dependencies + +- **Listmonk** depends on **PostgreSQL** (listmonk-db) +- **NocoDB** depends on **PostgreSQL** (root_db) +- **Static Server** serves content built by **MkDocs** +- **n8n** can integrate with all other services +- All services share the `changemaker` network + +## Environment Configuration + +Services are configured through environment variables in your `.env` file: + +```env +# Port configurations +CODE_SERVER_PORT=8888 +LISTMONK_PORT=9000 +LISTMONK_DB_PORT=5432 +MKDOCS_PORT=4000 +MKDOCS_SITE_SERVER_PORT=4001 +N8N_PORT=5678 + +# User and group IDs +USER_ID=1000 +GROUP_ID=1000 + +# Database configuration +POSTGRES_USER=listmonk +POSTGRES_PASSWORD=your_password +POSTGRES_DB=listmonk + +# n8n configuration +N8N_ENCRYPTION_KEY=your_encryption_key +N8N_USER_EMAIL=admin@example.com +N8N_USER_PASSWORD=your_password +``` + +## Monitoring and Maintenance + +### Health Checks +```bash +# Check all services +docker compose ps + +# Check specific service logs +docker compose logs listmonk-app +docker compose logs code-server +``` + +### Updates +```bash +# Pull latest images +docker compose pull + +# Restart with new images +docker compose down && docker compose up -d +``` + +### Backups +- **PostgreSQL**: Regular database backups +- **n8n**: Export workflows and credentials +- **Code Server**: Backup configuration and workspace +- **MkDocs**: Version control your documentation + +## Troubleshooting + +### Common Issues + +1. **Port Conflicts**: Ensure ports are not used by other applications +2. **Permission Issues**: Check `USER_ID` and `GROUP_ID` settings +3. **Network Issues**: Verify services can communicate through the `changemaker` network +4. **Data Persistence**: Ensure volumes are properly mounted + +### Getting Help + +- Check individual service documentation +- Review container logs for error messages +- Verify environment variable configuration +- Test network connectivity between services diff --git a/mkdocs/docs/services/listmonk.md b/mkdocs/docs/services/listmonk.md new file mode 100644 index 0000000..e0d51c5 --- /dev/null +++ b/mkdocs/docs/services/listmonk.md @@ -0,0 +1,66 @@ +# Listmonk + +Self-hosted newsletter and mailing list manager. + +## Overview + +Listmonk is a modern, feature-rich newsletter and mailing list manager designed for high performance and easy management. It provides a complete solution for email campaigns, subscriber management, and analytics. + +## Features + +- Newsletter and email campaign management +- Subscriber list management +- Template system with HTML/markdown support +- Campaign analytics and tracking +- API for integration +- Multi-list support +- Bounce handling +- Privacy-focused design + +## Access + +- **Default Port**: 9000 +- **URL**: `http://localhost:9000` +- **Admin User**: Set via `LISTMONK_ADMIN_USER` environment variable +- **Admin Password**: Set via `LISTMONK_ADMIN_PASSWORD` environment variable + +## Configuration + +### Environment Variables + +- `LISTMONK_ADMIN_USER`: Admin username +- `LISTMONK_ADMIN_PASSWORD`: Admin password +- `POSTGRES_USER`: Database username +- `POSTGRES_PASSWORD`: Database password +- `POSTGRES_DB`: Database name + +### Database + +Listmonk uses PostgreSQL as its backend database. The database is automatically configured through the docker-compose setup. + +### Uploads + +- Upload directory: `./assets/uploads` +- Used for media files, templates, and attachments + +## Getting Started + +1. Access Listmonk at `http://localhost:9000` +2. Log in with your admin credentials +3. Set up your first mailing list +4. Configure SMTP settings for sending emails +5. Import subscribers or create subscription forms +6. Create your first campaign + +## Important Notes + +- Configure SMTP settings before sending emails +- Set up proper domain authentication (SPF, DKIM) for better deliverability +- Regularly backup your subscriber data and campaigns +- Monitor bounce rates and maintain list hygiene + +## Official Documentation + +For comprehensive guides and API documentation, visit: +- [Listmonk Documentation](https://listmonk.app/docs/) +- [GitHub Repository](https://github.com/knadh/listmonk) diff --git a/mkdocs/docs/services/mkdocs.md b/mkdocs/docs/services/mkdocs.md new file mode 100644 index 0000000..c7a397a --- /dev/null +++ b/mkdocs/docs/services/mkdocs.md @@ -0,0 +1,124 @@ +# MkDocs Material + +Modern documentation site generator with live preview. + +## Overview + +MkDocs Material is a powerful documentation framework built on top of MkDocs, providing a beautiful Material Design theme and advanced features for creating professional documentation sites. + +## Features + +- Material Design theme +- Live preview during development +- Search functionality +- Navigation and organization +- Code syntax highlighting +- Mathematical expressions support +- Responsive design +- Customizable themes and colors + +## Access + +- **Development Port**: 4000 +- **Development URL**: `http://localhost:4000` +- **Live Reload**: Automatically refreshes on file changes + +## Configuration + +### Main Configuration + +Configuration is managed through `mkdocs.yml` in the project root. + +### Volumes + +- `./mkdocs`: Documentation source files +- `./assets/images`: Shared images directory + +### Environment Variables + +- `SITE_URL`: Base domain for the site +- `USER_ID`: User ID for file permissions +- `GROUP_ID`: Group ID for file permissions + +## Directory Structure + +``` +mkdocs/ +├── mkdocs.yml # Configuration file +├── docs/ # Documentation source +│ ├── index.md # Homepage +│ ├── services/ # Service documentation +│ ├── blog/ # Blog posts +│ └── overrides/ # Template overrides +└── site/ # Built static site +``` + +## Writing Documentation + +### Markdown Basics + +- Use standard Markdown syntax +- Support for tables, code blocks, and links +- Mathematical expressions with MathJax +- Admonitions for notes and warnings + +### Example Page + +```markdown +# Page Title + +This is a sample documentation page. + +## Section + +Content goes here with **bold** and *italic* text. + +### Code Example + +```python +def hello_world(): + print("Hello, World!") +``` + +!!! note + This is an informational note. +``` + +## Building and Deployment + +### Development + +The development server runs automatically with live reload. + +### Building Static Site + +```bash +docker exec mkdocs-changemaker mkdocs build +``` + +The built site will be available in the `mkdocs/site/` directory. + +## Customization + +### Themes and Colors + +Customize appearance in `mkdocs.yml`: + +```yaml +theme: + name: material + palette: + primary: blue + accent: indigo +``` + +### Custom CSS + +Add custom styles in `docs/stylesheets/extra.css`. + +## Official Documentation + +For comprehensive MkDocs Material documentation: +- [MkDocs Material](https://squidfunk.github.io/mkdocs-material/) +- [MkDocs Documentation](https://www.mkdocs.org/) +- [Markdown Guide](https://www.markdownguide.org/) diff --git a/mkdocs/docs/services/n8n.md b/mkdocs/docs/services/n8n.md new file mode 100644 index 0000000..07da533 --- /dev/null +++ b/mkdocs/docs/services/n8n.md @@ -0,0 +1,145 @@ +# n8n + +Workflow automation tool for connecting services and automating tasks. + +## Overview + +n8n is a powerful workflow automation tool that allows you to connect various apps and services together. It provides a visual interface for creating automated workflows, making it easy to integrate different systems and automate repetitive tasks. + +## Features + +- Visual workflow editor +- 400+ integrations +- Custom code execution (JavaScript/Python) +- Webhook support +- Scheduled workflows +- Error handling and retries +- User management +- API access +- Self-hosted and privacy-focused + +## Access + +- **Default Port**: 5678 +- **URL**: `http://localhost:5678` +- **Default User Email**: Set via `N8N_DEFAULT_USER_EMAIL` +- **Default User Password**: Set via `N8N_DEFAULT_USER_PASSWORD` + +## Configuration + +### Environment Variables + +- `N8N_HOST`: Hostname for n8n (default: `n8n.${DOMAIN}`) +- `N8N_PORT`: Internal port (5678) +- `N8N_PROTOCOL`: Protocol for webhooks (https) +- `NODE_ENV`: Environment (production) +- `WEBHOOK_URL`: Base URL for webhooks +- `GENERIC_TIMEZONE`: Timezone setting +- `N8N_ENCRYPTION_KEY`: Encryption key for credentials +- `N8N_USER_MANAGEMENT_DISABLED`: Enable/disable user management +- `N8N_DEFAULT_USER_EMAIL`: Default admin email +- `N8N_DEFAULT_USER_PASSWORD`: Default admin password + +### Volumes + +- `n8n_data`: Persistent data storage +- `./local-files`: Local file access for workflows + +## Getting Started + +1. Access n8n at `http://localhost:5678` +2. Log in with your admin credentials +3. Create your first workflow +4. Add nodes for different services +5. Configure connections between nodes +6. Test and activate your workflow + +## Common Use Cases + +### Documentation Automation +- Auto-generate documentation from code comments +- Sync documentation between different platforms +- Notify team when documentation is updated + +### Email Campaign Integration +- Connect Listmonk with external data sources +- Automate subscriber management +- Trigger campaigns based on events + +### Database Management with NocoDB +- Sync data between NocoDB and external APIs +- Automate data entry and validation +- Create backup workflows for database content +- Generate reports from NocoDB data + +### Development Workflows +- Auto-deploy documentation on git push +- Sync code changes with documentation +- Backup automation + +### Data Processing +- Process CSV files and import to databases +- Transform data between different formats +- Schedule regular data updates + +## Example Workflows + +### Simple Webhook to Email +``` +Webhook → Email +``` + +### Scheduled Documentation Backup +``` +Schedule → Read Files → Compress → Upload to Storage +``` + +### Git Integration +``` +Git Webhook → Process Changes → Update Documentation → Notify Team +``` + +## Security Considerations + +- Use strong encryption keys +- Secure webhook URLs +- Regularly update credentials +- Monitor workflow executions +- Implement proper error handling + +## Integration with Other Services + +n8n can integrate with all services in your Changemaker Lite setup: +- **Listmonk**: Manage subscribers and campaigns +- **PostgreSQL**: Read/write database operations +- **Code Server**: File operations and git integration +- **MkDocs**: Documentation generation and updates + +## Troubleshooting + +### Common Issues + +- **Workflow Execution Errors**: Check node configurations and credentials +- **Webhook Issues**: Verify URLs and authentication +- **Connection Problems**: Check network connectivity between services + +### Debugging + +```bash +# Check container logs +docker logs n8n-changemaker + +# Access container shell +docker exec -it n8n-changemaker sh + +# Check workflow executions in the UI +# Visit http://localhost:5678 → Executions +``` + +## Official Documentation + +For comprehensive n8n documentation: +- [n8n Documentation](https://docs.n8n.io/) +- [Community Workflows](https://n8n.io/workflows/) +- [Node Reference](https://docs.n8n.io/integrations/builtin/) +- [GitHub Repository](https://github.com/n8n-io/n8n) diff --git a/mkdocs/docs/services/nocodb.md b/mkdocs/docs/services/nocodb.md new file mode 100644 index 0000000..a1ca8d4 --- /dev/null +++ b/mkdocs/docs/services/nocodb.md @@ -0,0 +1,153 @@ +# NocoDB + +No-code database platform that turns any database into a smart spreadsheet. + +## Overview + +NocoDB is an open-source no-code platform that transforms any database into a smart spreadsheet interface. It provides a user-friendly way to manage data, create forms, build APIs, and collaborate on database operations without requiring extensive technical knowledge. + +## Features + +- **Smart Spreadsheet Interface**: Transform databases into intuitive spreadsheets +- **Form Builder**: Create custom forms for data entry +- **API Generation**: Auto-generated REST APIs for all tables +- **Collaboration**: Real-time collaboration with team members +- **Access Control**: Role-based permissions and sharing +- **Data Visualization**: Charts and dashboard creation +- **Webhooks**: Integration with external services +- **Import/Export**: Support for CSV, Excel, and other formats +- **Multi-Database Support**: Works with PostgreSQL, MySQL, SQLite, and more + +## Access + +- **Default Port**: 8090 +- **URL**: `http://localhost:8090` +- **Database**: PostgreSQL (dedicated `root_db` instance) + +## Configuration + +### Environment Variables + +- `NOCODB_PORT`: External port mapping (default: 8090) +- `NC_DB`: Database connection string for PostgreSQL backend + +### Database Backend + +NocoDB uses a dedicated PostgreSQL instance (`root_db`) with the following configuration: +- **Database Name**: `root_db` +- **Username**: `postgres` +- **Password**: `password` +- **Host**: `root_db` (internal container name) + +### Volumes + +- `nc_data`: Application data and configuration storage +- `db_data`: PostgreSQL database files + +## Getting Started + +1. **Access NocoDB**: Navigate to `http://localhost:8090` +2. **Initial Setup**: Complete the onboarding process +3. **Create Project**: Start with a new project or connect existing databases +4. **Add Tables**: Import data or create new tables +5. **Configure Views**: Set up different views (Grid, Form, Gallery, etc.) +6. **Set Permissions**: Configure user access and sharing settings + +## Common Use Cases + +### Content Management +- Create content databases for blogs and websites +- Manage product catalogs and inventories +- Track customer information and interactions + +### Project Management +- Task and project tracking systems +- Team collaboration workspaces +- Resource and timeline management + +### Data Collection +- Custom forms for surveys and feedback +- Event registration and management +- Lead capture and CRM systems + +### Integration with Other Services + +NocoDB can integrate well with other Changemaker Lite services: + +- **n8n Integration**: Use NocoDB as a data source/destination in automation workflows +- **Listmonk Integration**: Manage subscriber lists and campaign data +- **Documentation**: Store and manage documentation metadata + +## API Usage + +NocoDB automatically generates REST APIs for all your tables: + +```bash +# Get all records from a table +GET http://localhost:8090/api/v1/db/data/v1/{project}/table/{table} + +# Create a new record +POST http://localhost:8090/api/v1/db/data/v1/{project}/table/{table} + +# Update a record +PATCH http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}/{id} +``` + +## Backup and Data Management + +### Database Backup + +Since NocoDB uses PostgreSQL, you can backup the database: + +```bash +# Backup NocoDB database +docker exec root_db pg_dump -U postgres root_db > nocodb_backup.sql + +# Restore from backup +docker exec -i root_db psql -U postgres root_db < nocodb_backup.sql +``` + +### Application Data + +Application settings and metadata are stored in the `nc_data` volume. + +## Security Considerations + +- Change default database credentials in production +- Configure proper access controls within NocoDB +- Use HTTPS for production deployments +- Regularly backup both database and application data +- Monitor access logs and user activities + +## Performance Tips + +- Regular database maintenance and optimization +- Monitor memory usage for large datasets +- Use appropriate indexing for frequently queried fields +- Consider database connection pooling for high-traffic scenarios + +## Troubleshooting + +### Common Issues + +**Service won't start**: Check if the PostgreSQL database is healthy +```bash +docker logs root_db +``` + +**Database connection errors**: Verify database credentials and network connectivity +```bash +docker exec nocodb nc_data nc +``` + +**Performance issues**: Monitor resource usage and optimize queries +```bash +docker stats nocodb root_db +``` + +## Official Documentation + +For comprehensive guides and advanced features: +- [NocoDB Documentation](https://docs.nocodb.com/) +- [GitHub Repository](https://github.com/nocodb/nocodb) +- [Community Forum](https://community.nocodb.com/) \ No newline at end of file diff --git a/mkdocs/docs/services/postgresql.md b/mkdocs/docs/services/postgresql.md new file mode 100644 index 0000000..bc55fb2 --- /dev/null +++ b/mkdocs/docs/services/postgresql.md @@ -0,0 +1,90 @@ +# PostgreSQL Database + +Reliable database backend for applications. + +## Overview + +PostgreSQL is a powerful, open-source relational database system. In Changemaker Lite, it serves as the backend database for Listmonk and can be used by other applications requiring persistent data storage. + +## Features + +- ACID compliance +- Advanced SQL features +- JSON/JSONB support +- Full-text search +- Extensibility +- High performance +- Reliability and data integrity + +## Access + +- **Default Port**: 5432 +- **Host**: `listmonk-db` (internal container name) +- **Database**: Set via `POSTGRES_DB` environment variable +- **Username**: Set via `POSTGRES_USER` environment variable +- **Password**: Set via `POSTGRES_PASSWORD` environment variable + +## Configuration + +### Environment Variables + +- `POSTGRES_USER`: Database username +- `POSTGRES_PASSWORD`: Database password +- `POSTGRES_DB`: Database name + +### Health Checks + +The PostgreSQL container includes health checks to ensure the database is ready before dependent services start. + +### Data Persistence + +Database data is stored in a Docker volume (`listmonk-data`) to ensure persistence across container restarts. + +## Connecting to the Database + +### From Host Machine + +You can connect to PostgreSQL from your host machine using: + +```bash +psql -h localhost -p 5432 -U [username] -d [database] +``` + +### From Other Containers + +Other containers can connect using the internal hostname `listmonk-db` on port 5432. + +## Backup and Restore + +### Backup + +```bash +docker exec listmonk-db pg_dump -U [username] [database] > backup.sql +``` + +### Restore + +```bash +docker exec -i listmonk-db psql -U [username] [database] < backup.sql +``` + +## Monitoring + +Monitor database health and performance through: +- Container logs: `docker logs listmonk-db` +- Database metrics and queries +- Connection monitoring + +## Security Considerations + +- Use strong passwords +- Regularly update PostgreSQL version +- Monitor access logs +- Implement regular backups +- Consider network isolation + +## Official Documentation + +For comprehensive PostgreSQL documentation: +- [PostgreSQL Documentation](https://www.postgresql.org/docs/) +- [Docker PostgreSQL Image](https://hub.docker.com/_/postgres) diff --git a/mkdocs/docs/services/silex.md b/mkdocs/docs/services/silex.md new file mode 100644 index 0000000..b80c6df --- /dev/null +++ b/mkdocs/docs/services/silex.md @@ -0,0 +1,112 @@ +# Silex - Visual Website Editor + +Silex is a streamlined visual website editor that allows you to create and edit HTML pages with a drag-and-drop interface. + +## Overview + +This Changemaker setup uses the minimal `silexlabs/silex` image, which provides a focused, single-site editing experience. It's particularly useful for editing the `home.html` and `main.html` templates in your MkDocs overrides. + +## Access + +- **Default Port**: 6805 +- **URL**: `http://localhost:6805` +- **Container**: `silex-changemaker` + +## Configuration + +### Environment Variables + +- `SILEX_PORT`: External port mapping (default: 6805) +- `SILEX_HOST`: Hostname for Silex (default: `silex.${DOMAIN}`) + +### Volume Mounts + +Silex uses the following directories for persistence: + +- **Storage**: `./silex/storage` - Site data and configurations +- **Hosting**: `./silex/hosting` - Published files +- **HTML Files**: `./mkdocs/docs/overrides` - Direct access to edit `home.html` and `main.html` + +## Usage + +### Getting Started + +1. Access Silex at `http://localhost:6805` +2. Create a new site or open an existing project +3. Use the visual editor to design your pages +4. Save your work to the mounted directories + +### Editing MkDocs Templates + +Silex is configured to have access to your MkDocs override templates: + +- `home.html` - Custom home page template +- `main.html` - Main template overrides + +You can edit these files directly through Silex's visual interface. + +### Features + +- **Drag-and-Drop Interface**: Visual page building +- **Filesystem Storage**: Simple file-based persistence +- **Single Site Focus**: Streamlined for focused editing +- **Responsive Design**: Mobile-friendly editing + +## File Structure + +``` +silex/ +├── storage/ # Site configurations and data +├── hosting/ # Published website files +└── html/ # Direct access to MkDocs overrides + ├── home.html + └── main.html +``` + +## Tips + +1. **Backup Important Files**: Always backup your templates before making changes +2. **Test Changes**: Preview your changes in MkDocs before publishing +3. **Use Version Control**: Commit your changes to git regularly +4. **Responsive Design**: Test your designs on different screen sizes + +## Troubleshooting + +### Port Conflicts + +If port 6805 is already in use, you can change it in your `.env` file: + +```bash +SILEX_PORT=8080 +``` + +### File Permissions + +If you encounter permission issues, ensure the mounted directories have the correct ownership: + +```bash +sudo chown -R 1000:1000 ./silex/ +``` + +### Container Issues + +To restart the Silex container: + +```bash +docker-compose restart silex +``` + +## Integration with MkDocs + +Silex is particularly useful for editing MkDocs template overrides. After making changes in Silex: + +1. Check your changes in the MkDocs live preview (`http://localhost:4000`) +2. Build the static site if everything looks good +3. Commit your changes to version control + +## Resources + +- [Silex Official Website](https://www.silex.me/) +- [Silex Documentation](https://docs.silex.me/) +- [Silex GitHub Repository (minimal version)](https://github.com/silexlabs/silex-lib) +- [Docker Hub - Silex Minimal](https://hub.docker.com/r/silexlabs/silex) diff --git a/mkdocs/docs/services/static-server.md b/mkdocs/docs/services/static-server.md new file mode 100644 index 0000000..ecc517d --- /dev/null +++ b/mkdocs/docs/services/static-server.md @@ -0,0 +1,100 @@ +# Static Site Server + +Nginx-powered static site server for hosting built documentation and websites. + +## Overview + +The Static Site Server uses Nginx to serve your built documentation and static websites. It's configured to serve the built MkDocs site and other static content with high performance and reliability. + +## Features + +- High-performance static file serving +- Automatic index file handling +- Gzip compression +- Caching headers +- Security headers +- Custom error pages +- URL rewriting support + +## Access + +- **Default Port**: 4001 +- **URL**: `http://localhost:4001` +- **Document Root**: `/config/www` (mounted from `./mkdocs/site`) + +## Configuration + +### Environment Variables + +- `PUID`: User ID for file permissions (default: 1000) +- `PGID`: Group ID for file permissions (default: 1000) +- `TZ`: Timezone setting (default: Etc/UTC) + +### Volumes + +- `./mkdocs/site:/config/www`: Static site files +- Built MkDocs site is automatically served + +## Usage + +1. Build your MkDocs site: `docker exec mkdocs-changemaker mkdocs build` +2. The built site is automatically available at `http://localhost:4001` +3. Any files in `./mkdocs/site/` will be served statically + +## File Structure + +``` +mkdocs/site/ # Served at / +├── index.html # Homepage +├── assets/ # CSS, JS, images +├── services/ # Service documentation +└── search/ # Search functionality +``` + +## Performance Features + +- **Gzip Compression**: Automatic compression for text files +- **Browser Caching**: Optimized cache headers +- **Fast Static Serving**: Nginx optimized for static content +- **Security Headers**: Basic security header configuration + +## Custom Configuration + +For advanced Nginx configuration, you can: +1. Create custom Nginx config files +2. Mount them as volumes +3. Restart the container + +## Monitoring + +Monitor the static site server through: +- Container logs: `docker logs mkdocs-site-server-changemaker` +- Access logs for traffic analysis +- Performance metrics + +## Troubleshooting + +### Common Issues + +- **404 Errors**: Ensure MkDocs site is built and files exist in `./mkdocs/site/` +- **Permission Issues**: Check `PUID` and `PGID` settings +- **File Not Found**: Verify file paths and case sensitivity + +### Debugging + +```bash +# Check container logs +docker logs mkdocs-site-server-changemaker + +# Verify files are present +docker exec mkdocs-site-server-changemaker ls -la /config/www + +# Test file serving +curl -I http://localhost:4001 +``` + +## Official Documentation + +For more information about the underlying Nginx server: +- [LinuxServer.io Nginx](https://docs.linuxserver.io/images/docker-nginx) +- [Nginx Documentation](https://nginx.org/en/docs/) diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml new file mode 100644 index 0000000..5c2ec6c --- /dev/null +++ b/mkdocs/mkdocs.yml @@ -0,0 +1,66 @@ +site_name: Changemaker Lite Documentation +site_description: Self-hosted platform for documentation and development +site_url: https://changeme.org +site_author: Bunker Ops +docs_dir: docs +site_dir: site + +# Theme +theme: + name: material + custom_dir: docs/overrides + palette: + scheme: slate + primary: deep purple + accent: amber + features: + - navigation.tracking + - navigation.indexes + - navigation.collapse + - navigation.path + - content.code.copy + - navigation.top + - navigation.tabs # Added for top-level navigation tabs + +extra_css: + - stylesheets/extra.css +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.superfences + - admonition + - pymdownx.details + - attr_list + - md_in_html + - pymdownx.emoji # Simplified emoji config + - footnotes + - toc: + permalink: true + # The specific slugify line was removed to avoid previous tool error, + # you may need to add back your preferred slugify option: + # slugify: !!python/name:pymdownx.slugs.uslugify + +copyright: Copyright © 2024 The Bunker Operations - Built with Change Maker + +# Plugins +plugins: + - social + - search + - blog + # - tags # Consider adding if you use tags for your blog or docs + +# Navigation +nav: + - Home: index.md + - Getting Started: getting-started.md + - Services: + - Overview: services/index.md + - Homepage: services/homepage.md + - Code Server: services/code-server.md + - MkDocs Material: services/mkdocs.md + - Static Site Server: services/static-server.md + - Listmonk: services/listmonk.md + - PostgreSQL: services/postgresql.md + - n8n: services/n8n.md + - NocoDB: services/nocodb.md + - Blog: blog/index.md diff --git a/mkdocs/site/404.html b/mkdocs/site/404.html new file mode 100644 index 0000000..cf8baae --- /dev/null +++ b/mkdocs/site/404.html @@ -0,0 +1,708 @@ + + + + + + + + + + + + + + + + + + + + + + + Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/assets/images/favicon.png b/mkdocs/site/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/mkdocs/site/assets/images/favicon.png differ diff --git a/mkdocs/site/assets/images/social/blog/index.png b/mkdocs/site/assets/images/social/blog/index.png new file mode 100644 index 0000000..f46cc75 Binary files /dev/null and b/mkdocs/site/assets/images/social/blog/index.png differ diff --git a/mkdocs/site/assets/images/social/getting-started.png b/mkdocs/site/assets/images/social/getting-started.png new file mode 100644 index 0000000..91be978 Binary files /dev/null and b/mkdocs/site/assets/images/social/getting-started.png differ diff --git a/mkdocs/site/assets/images/social/index.png b/mkdocs/site/assets/images/social/index.png new file mode 100644 index 0000000..42681cc Binary files /dev/null and b/mkdocs/site/assets/images/social/index.png differ diff --git a/mkdocs/site/assets/images/social/services/code-server.png b/mkdocs/site/assets/images/social/services/code-server.png new file mode 100644 index 0000000..ea5947b Binary files /dev/null and b/mkdocs/site/assets/images/social/services/code-server.png differ diff --git a/mkdocs/site/assets/images/social/services/homepage.png b/mkdocs/site/assets/images/social/services/homepage.png new file mode 100644 index 0000000..5263f14 Binary files /dev/null and b/mkdocs/site/assets/images/social/services/homepage.png differ diff --git a/mkdocs/site/assets/images/social/services/index.png b/mkdocs/site/assets/images/social/services/index.png new file mode 100644 index 0000000..321a1b9 Binary files /dev/null and b/mkdocs/site/assets/images/social/services/index.png differ diff --git a/mkdocs/site/assets/images/social/services/listmonk.png b/mkdocs/site/assets/images/social/services/listmonk.png new file mode 100644 index 0000000..6120273 Binary files /dev/null and b/mkdocs/site/assets/images/social/services/listmonk.png differ diff --git a/mkdocs/site/assets/images/social/services/mkdocs.png b/mkdocs/site/assets/images/social/services/mkdocs.png new file mode 100644 index 0000000..9be948c Binary files /dev/null and b/mkdocs/site/assets/images/social/services/mkdocs.png differ diff --git a/mkdocs/site/assets/images/social/services/n8n.png b/mkdocs/site/assets/images/social/services/n8n.png new file mode 100644 index 0000000..d10dcd9 Binary files /dev/null and b/mkdocs/site/assets/images/social/services/n8n.png differ diff --git a/mkdocs/site/assets/images/social/services/nocodb.png b/mkdocs/site/assets/images/social/services/nocodb.png new file mode 100644 index 0000000..1a02082 Binary files /dev/null and b/mkdocs/site/assets/images/social/services/nocodb.png differ diff --git a/mkdocs/site/assets/images/social/services/postgresql.png b/mkdocs/site/assets/images/social/services/postgresql.png new file mode 100644 index 0000000..4971e61 Binary files /dev/null and b/mkdocs/site/assets/images/social/services/postgresql.png differ diff --git a/mkdocs/site/assets/images/social/services/static-server.png b/mkdocs/site/assets/images/social/services/static-server.png new file mode 100644 index 0000000..c6dd85d Binary files /dev/null and b/mkdocs/site/assets/images/social/services/static-server.png differ diff --git a/mkdocs/site/assets/javascripts/bundle.13a4f30d.min.js b/mkdocs/site/assets/javascripts/bundle.13a4f30d.min.js new file mode 100644 index 0000000..c31fa1a --- /dev/null +++ b/mkdocs/site/assets/javascripts/bundle.13a4f30d.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);stroke-width:.05rem}.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.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: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," ").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")),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: 960px)"),Mi=$t("(min-width: 1220px)"),_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.13a4f30d.min.js.map + diff --git a/mkdocs/site/assets/javascripts/bundle.13a4f30d.min.js.map b/mkdocs/site/assets/javascripts/bundle.13a4f30d.min.js.map new file mode 100644 index 0000000..8941cb8 --- /dev/null +++ b/mkdocs/site/assets/javascripts/bundle.13a4f30d.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: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\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 + + +
+ +
+

Quick Start

+

Familiar with the terminal, git, and Docker? Get Changemaker up and running in minutes:

+
+
# Clone the repository
+git clone https://gitea.bnkops.com/admin/Changemaker.git
+cd changemaker.lite
+
+ +
+
# Run the config.sh script
+./config.sh
+
+ +
+
# Start all services
+docker compose up -d
+
+ +

That's it! After services start (which may take a few minutes on first run), visit the individual service ports to get started.

+

Access MkDocs (Documentation)

+ Detailed Installation Guide +
+ +
+

What is Changemaker Lite?

+

Changemaker Lite is a streamlined version focused on documentation and development tools. It provides essential services for creating and managing content, including documentation sites, newsletters, automation workflows, and development environments.

+
+ +
+

Why Changemaker Lite?

+

Changemaker Lite offers a minimal but powerful self-hosted solution for individuals and small teams who need reliable documentation and development tools without the complexity of larger platforms. Perfect for personal projects, small campaigns, or development workflows.

+
+ +
+

Core Applications & Services

+

Changemaker Lite comes with essential self-hosted tools for documentation, development, and automation. Click on any app to learn more about its features and how to use it.

+
+
+
+

dashboardHomepage

+

Modern dashboard for accessing and monitoring all your self-hosted services from one place.

+
+ Learn More +
+ +
+
+

codeCode Server

+

Visual Studio Code in your browser. Develop and edit code from any device with full IDE features.

+
+ Learn More +
+ +
+
+

mailListmonk

+

Self-hosted newsletter and mailing list manager for full control over email campaigns.

+
+ Learn More +
+ +
+
+

storagePostgreSQL

+

Reliable database backend for Listmonk and other applications requiring persistent data storage.

+
+ Learn More +
+ +
+
+

descriptionMkDocs Material

+

Modern documentation site generator with live preview and beautiful Material Design theme.

+
+ Learn More +
+ +
+
+

webStatic Site Server

+

Nginx-powered static site server for hosting your built documentation and websites.

+
+ Learn More +
+ +
+
+

hubn8n

+

Workflow automation tool for connecting services and automating repetitive tasks.

+
+ Learn More +
+ +
+
+

grid_viewNocoDB

+

No-code database platform that turns any database into a smart spreadsheet interface.

+
+ Learn More +
+
+
+ +
+

Development Pathway

+

Changemaker Lite is continuously evolving. Here are some identified goals for development:

+
    +
  • Enhanced service integrations and documentation.
  • +
  • Improved automation workflows with n8n examples.
  • +
  • Better asset management across services.
  • +
  • Comprehensive training materials and guides.
  • +
+

Stay tuned for updates and new features!

+
+ +{% endblock %} + +{% block tabs %} + {{ super() }} +{% endblock %} \ No newline at end of file diff --git a/mkdocs/site/overrides/main.html b/mkdocs/site/overrides/main.html new file mode 100644 index 0000000..81a640e --- /dev/null +++ b/mkdocs/site/overrides/main.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block extrahead %} +{% endblock %} + +{% block announce %} + +Changemaker Archive. Learn more +{% endblock %} diff --git a/mkdocs/site/search/search_index.json b/mkdocs/site/search/search_index.json new file mode 100644 index 0000000..fcf115e --- /dev/null +++ b/mkdocs/site/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to Changemaker Lite","text":"

A streamlined, self-hosted platform for documentation and development.

"},{"location":"#quick-start","title":"Quick Start","text":"

Get up and running in minutes:

# Clone the repository\ngit clone https://gitea.bnkops.com/admin/Changemaker.git\ncd changemaker.lite\n\n# Configure environment\n./config.sh\n\n# Start all services\ndocker compose up -d\n
"},{"location":"#services","title":"Services","text":"

Changemaker Lite includes these essential services:

  • Homepage (Port 3010) - Service dashboard and monitoring
  • Code Server (Port 8888) - VS Code in your browser
  • MkDocs (Port 4000) - Documentation with live preview
  • Static Server (Port 4001) - Built site hosting
  • Listmonk (Port 9000) - Newsletter management
  • PostgreSQL (Port 5432) - Database backend
  • n8n (Port 5678) - Workflow automation
  • NocoDB (Port 8090) - No-code database platform
"},{"location":"#getting-started","title":"Getting Started","text":"
  1. Documentation: Start writing in Code Server
  2. Preview: See live changes at MkDocs
  3. Production: View built site at Static Server
  4. Email: Set up campaigns with Listmonk
  5. Automation: Create workflows in n8n
"},{"location":"#project-structure","title":"Project Structure","text":"
changemaker.lite/\n\u251c\u2500\u2500 docker-compose.yml    # Service definitions\n\u251c\u2500\u2500 config.sh            # Setup script\n\u251c\u2500\u2500 mkdocs/              # Documentation source\n\u2502   \u251c\u2500\u2500 docs/            # Markdown files\n\u2502   \u2514\u2500\u2500 mkdocs.yml       # Configuration\n\u251c\u2500\u2500 configs/             # Service configurations\n\u2514\u2500\u2500 assets/              # Shared assets\n
"},{"location":"#learn-more","title":"Learn More","text":"
  • Services Overview - Detailed service documentation
  • Blog - Updates and tutorials
  • GitHub Repository - Source code and issues
"},{"location":"getting-started/","title":"Getting Started","text":"

Welcome to Changemaker Lite! This guide will help you get up and running quickly.

"},{"location":"getting-started/#prerequisites","title":"Prerequisites","text":"

Before starting, ensure you have:

  • Docker installed
  • Docker Compose installed
  • Git for cloning the repository
  • Basic familiarity with terminal/command line
"},{"location":"getting-started/#installation","title":"Installation","text":""},{"location":"getting-started/#1-clone-repository","title":"1. Clone Repository","text":"
git clone https://gitea.bnkops.com/admin/Changemaker.git\ncd changemaker.lite\n
"},{"location":"getting-started/#2-configuration","title":"2. Configuration","text":"

Run the configuration script to set up environment variables:

./config.sh\n

This creates a .env file with default settings. You can edit this file to customize: - Service ports - Database credentials - User/group IDs - Domain settings

"},{"location":"getting-started/#3-start-services","title":"3. Start Services","text":"

Launch all services with Docker Compose:

docker compose up -d\n

Wait a few minutes for all services to start, especially on first run as Docker images need to be downloaded.

"},{"location":"getting-started/#4-verify-installation","title":"4. Verify Installation","text":"

Check that services are running:

docker compose ps\n

All services should show as \"Up\" status.

"},{"location":"getting-started/#first-steps","title":"First Steps","text":""},{"location":"getting-started/#access-documentation","title":"Access Documentation","text":"
  1. Development Server: http://localhost:4000
  2. Live preview with auto-reload
  3. Used while writing documentation

  4. Production Server: http://localhost:4001

  5. Serves built static site
  6. Optimized for performance
"},{"location":"getting-started/#start-coding","title":"Start Coding","text":"
  1. Open Code Server: http://localhost:8888
  2. Navigate to /home/coder/mkdocs/ workspace
  3. Edit documentation files in docs/ directory
  4. See changes instantly in development server
"},{"location":"getting-started/#set-up-email-campaigns","title":"Set Up Email Campaigns","text":"
  1. Access Listmonk: http://localhost:9000
  2. Log in with admin credentials (set in config)
  3. Configure SMTP settings
  4. Create your first mailing list
"},{"location":"getting-started/#create-workflows","title":"Create Workflows","text":"
  1. Open n8n: http://localhost:5678
  2. Log in with admin credentials
  3. Create your first workflow
  4. Connect different services together
"},{"location":"getting-started/#manage-data-with-nocodb","title":"Manage Data with NocoDB","text":"
  1. Access NocoDB: http://localhost:8090
  2. Complete the initial setup
  3. Create your first project
  4. Import data or create new tables
"},{"location":"getting-started/#access-all-services-via-homepage","title":"Access All Services via Homepage","text":"
  1. Open Homepage: http://localhost:3010
  2. View all services in one dashboard
  3. Monitor service status
  4. Quick access to all applications
"},{"location":"getting-started/#configuration-details","title":"Configuration Details","text":""},{"location":"getting-started/#environment-variables","title":"Environment Variables","text":"

Key settings in .env file:

# Service Ports\nHOMEPAGE_PORT=3010\nCODE_SERVER_PORT=8888\nMKDOCS_PORT=4000\nMKDOCS_SITE_SERVER_PORT=4001\nLISTMONK_PORT=9000\nN8N_PORT=5678\nNOCODB_PORT=8090\n\n# Database\nPOSTGRES_USER=listmonk\nPOSTGRES_PASSWORD=your_secure_password\nPOSTGRES_DB=listmonk\n\n# User/Group IDs (match your system)\nUSER_ID=1000\nGROUP_ID=1000\n
"},{"location":"getting-started/#volume-mounts","title":"Volume Mounts","text":"

Important directories:

  • ./mkdocs/: Documentation source files
  • ./configs/: Service configurations
  • ./assets/: Shared assets and uploads
  • Docker volumes for persistent data
"},{"location":"getting-started/#common-tasks","title":"Common Tasks","text":""},{"location":"getting-started/#writing-documentation","title":"Writing Documentation","text":"
  1. Edit files in mkdocs/docs/ using Code Server
  2. Preview changes at http://localhost:4000
  3. Build static site: docker exec mkdocs-changemaker mkdocs build
  4. View built site at http://localhost:4001
"},{"location":"getting-started/#managing-services","title":"Managing Services","text":"
# View logs\ndocker compose logs [service-name]\n\n# Restart service\ndocker compose restart [service-name]\n\n# Stop all services\ndocker compose down\n\n# Update services\ndocker compose pull && docker compose up -d\n
"},{"location":"getting-started/#backup-important-data","title":"Backup Important Data","text":"
  • Export n8n workflows
  • Backup PostgreSQL database
  • Version control your documentation files
  • Save service configurations
"},{"location":"getting-started/#troubleshooting","title":"Troubleshooting","text":""},{"location":"getting-started/#port-conflicts","title":"Port Conflicts","text":"

If ports are already in use, edit .env file:

CODE_SERVER_PORT=8889  # Change from 8888\nMKDOCS_PORT=4002       # Change from 4000\n

Then restart: docker compose down && docker compose up -d

"},{"location":"getting-started/#permission-issues","title":"Permission Issues","text":"

Ensure correct user/group IDs in .env:

# Get your user ID\nid -u\n\n# Get your group ID  \nid -g\n

Update .env with these values and restart services.

"},{"location":"getting-started/#service-wont-start","title":"Service Won't Start","text":"

Check logs for specific errors:

docker compose logs [service-name]\n

Common issues: - Port conflicts - Permission problems - Missing environment variables - Network connectivity

"},{"location":"getting-started/#next-steps","title":"Next Steps","text":"
  • Explore the Services documentation
  • Set up your first n8n workflow
  • Configure Listmonk for email campaigns
  • Customize your MkDocs theme and content
"},{"location":"getting-started/#getting-help","title":"Getting Help","text":"
  • Check service-specific documentation
  • Review Docker container logs
  • Verify environment configuration
  • Test network connectivity between services
"},{"location":"services/","title":"Services","text":"

Changemaker Lite includes several powerful services that work together to provide a complete documentation and development platform. Each service is containerized and can be accessed through its dedicated port.

"},{"location":"services/#available-services","title":"Available Services","text":""},{"location":"services/#code-server","title":"Code Server","text":"

Port: 8888 | Visual Studio Code in your browser for remote development - Full IDE experience - Extensions support - Git integration - Terminal access

"},{"location":"services/#listmonk","title":"Listmonk","text":"

Port: 9000 | Self-hosted newsletter and mailing list manager - Email campaigns - Subscriber management - Analytics - Template system

"},{"location":"services/#postgresql","title":"PostgreSQL","text":"

Port: 5432 | Reliable database backend - Data persistence for Listmonk - ACID compliance - High performance - Backup and restore capabilities

"},{"location":"services/#mkdocs-material","title":"MkDocs Material","text":"

Port: 4000 | Documentation site generator with live preview - Material Design theme - Live reload - Search functionality - Markdown support

"},{"location":"services/#static-site-server","title":"Static Site Server","text":"

Port: 4001 | Nginx-powered static site hosting - High-performance serving - Built documentation hosting - Caching and compression - Security headers

"},{"location":"services/#n8n","title":"n8n","text":"

Port: 5678 | Workflow automation tool - Visual workflow editor - 400+ integrations - Custom code execution - Webhook support

"},{"location":"services/#nocodb","title":"NocoDB","text":"

Port: 8090 | No-code database platform - Smart spreadsheet interface - Form builder and API generation - Real-time collaboration - Multi-database support

"},{"location":"services/#homepage","title":"Homepage","text":"

Port: 3010 | Modern dashboard for all services - Service dashboard and monitoring - Docker integration - Customizable layout - Quick search and bookmarks

"},{"location":"services/#service-architecture","title":"Service Architecture","text":"
\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502   Homepage      \u2502    \u2502   Code Server   \u2502    \u2502     MkDocs      \u2502\n\u2502     :3010       \u2502    \u2502     :8888       \u2502    \u2502     :4000       \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Static Server   \u2502    \u2502    Listmonk     \u2502    \u2502      n8n        \u2502\n\u2502     :4001       \u2502    \u2502     :9000       \u2502    \u2502     :5678       \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502     NocoDB      \u2502    \u2502 PostgreSQL      \u2502    \u2502 PostgreSQL      \u2502\n\u2502     :8090       \u2502    \u2502 (listmonk-db)   \u2502    \u2502 (root_db)       \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2502     :5432       \u2502    \u2502     :5432       \u2502\n         \u2502              \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n         \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n
"},{"location":"services/#getting-started","title":"Getting Started","text":"
  1. Start all services: docker compose up -d
  2. Check service status: docker compose ps
  3. View logs: docker compose logs [service-name]
  4. Stop services: docker compose down
"},{"location":"services/#service-dependencies","title":"Service Dependencies","text":"
  • Listmonk depends on PostgreSQL (listmonk-db)
  • NocoDB depends on PostgreSQL (root_db)
  • Static Server serves content built by MkDocs
  • n8n can integrate with all other services
  • All services share the changemaker network
"},{"location":"services/#environment-configuration","title":"Environment Configuration","text":"

Services are configured through environment variables in your .env file:

# Port configurations\nCODE_SERVER_PORT=8888\nLISTMONK_PORT=9000\nLISTMONK_DB_PORT=5432\nMKDOCS_PORT=4000\nMKDOCS_SITE_SERVER_PORT=4001\nN8N_PORT=5678\n\n# User and group IDs\nUSER_ID=1000\nGROUP_ID=1000\n\n# Database configuration\nPOSTGRES_USER=listmonk\nPOSTGRES_PASSWORD=your_password\nPOSTGRES_DB=listmonk\n\n# n8n configuration\nN8N_ENCRYPTION_KEY=your_encryption_key\nN8N_USER_EMAIL=admin@example.com\nN8N_USER_PASSWORD=your_password\n
"},{"location":"services/#monitoring-and-maintenance","title":"Monitoring and Maintenance","text":""},{"location":"services/#health-checks","title":"Health Checks","text":"
# Check all services\ndocker compose ps\n\n# Check specific service logs\ndocker compose logs listmonk-app\ndocker compose logs code-server\n
"},{"location":"services/#updates","title":"Updates","text":"
# Pull latest images\ndocker compose pull\n\n# Restart with new images\ndocker compose down && docker compose up -d\n
"},{"location":"services/#backups","title":"Backups","text":"
  • PostgreSQL: Regular database backups
  • n8n: Export workflows and credentials
  • Code Server: Backup configuration and workspace
  • MkDocs: Version control your documentation
"},{"location":"services/#troubleshooting","title":"Troubleshooting","text":""},{"location":"services/#common-issues","title":"Common Issues","text":"
  1. Port Conflicts: Ensure ports are not used by other applications
  2. Permission Issues: Check USER_ID and GROUP_ID settings
  3. Network Issues: Verify services can communicate through the changemaker network
  4. Data Persistence: Ensure volumes are properly mounted
"},{"location":"services/#getting-help","title":"Getting Help","text":"
  • Check individual service documentation
  • Review container logs for error messages
  • Verify environment variable configuration
  • Test network connectivity between services
"},{"location":"services/code-server/","title":"Code Server","text":"

Visual Studio Code in your browser for remote development.

"},{"location":"services/code-server/#overview","title":"Overview","text":"

Code Server provides a full Visual Studio Code experience in your web browser, allowing you to develop from any device. It runs on your server and provides access to your development environment through a web interface.

"},{"location":"services/code-server/#features","title":"Features","text":"
  • Full VS Code experience in the browser
  • Extensions support
  • Terminal access
  • Git integration
  • File editing and management
  • Multi-language support
"},{"location":"services/code-server/#access","title":"Access","text":"
  • Default Port: 8888
  • URL: http://localhost:8888
  • Default Workspace: /home/coder/mkdocs/
"},{"location":"services/code-server/#configuration","title":"Configuration","text":""},{"location":"services/code-server/#environment-variables","title":"Environment Variables","text":"
  • DOCKER_USER: The user to run code-server as (default: coder)
  • DEFAULT_WORKSPACE: Default workspace directory
  • USER_ID: User ID for file permissions
  • GROUP_ID: Group ID for file permissions
"},{"location":"services/code-server/#volumes","title":"Volumes","text":"
  • ./configs/code-server/.config: VS Code configuration
  • ./configs/code-server/.local: Local data
  • ./mkdocs: Main workspace directory
"},{"location":"services/code-server/#usage","title":"Usage","text":"
  1. Access Code Server at http://localhost:8888
  2. Open the /home/coder/mkdocs/ workspace
  3. Start editing your documentation files
  4. Install extensions as needed
  5. Use the integrated terminal for commands
"},{"location":"services/code-server/#useful-extensions","title":"Useful Extensions","text":"

Consider installing these extensions for better documentation work:

  • Markdown All in One
  • Material Design Icons
  • GitLens
  • Docker
  • YAML
"},{"location":"services/code-server/#official-documentation","title":"Official Documentation","text":"

For more detailed information, visit the official Code Server documentation.

"},{"location":"services/homepage/","title":"Homepage","text":"

Modern dashboard for accessing all your self-hosted services.

"},{"location":"services/homepage/#overview","title":"Overview","text":"

Homepage is a modern, fully static, fast, secure fully configurable application dashboard with integrations for over 100 services. It provides a beautiful and customizable interface to access all your Changemaker Lite services from a single location.

"},{"location":"services/homepage/#features","title":"Features","text":"
  • Service Dashboard: Central hub for all your applications
  • Docker Integration: Automatic service discovery and monitoring
  • Customizable Layout: Flexible grid-based layout system
  • Service Widgets: Live status and metrics for services
  • Quick Search: Fast navigation with built-in search
  • Bookmarks: Organize frequently used links
  • Dark/Light Themes: Multiple color schemes available
  • Responsive Design: Works on desktop and mobile devices
"},{"location":"services/homepage/#access","title":"Access","text":"
  • Default Port: 3010
  • URL: http://localhost:3010
  • Configuration: YAML-based configuration files
"},{"location":"services/homepage/#configuration","title":"Configuration","text":""},{"location":"services/homepage/#environment-variables","title":"Environment Variables","text":"
  • HOMEPAGE_PORT: External port mapping (default: 3010)
  • PUID: User ID for file permissions (default: 1000)
  • PGID: Group ID for file permissions (default: 1000)
  • TZ: Timezone setting (default: Etc/UTC)
  • HOMEPAGE_ALLOWED_HOSTS: Allowed hosts for the dashboard
"},{"location":"services/homepage/#configuration-files","title":"Configuration Files","text":"

Homepage uses YAML configuration files located in ./configs/homepage/:

  • settings.yaml: Global settings and theme configuration
  • services.yaml: Service definitions and widgets
  • bookmarks.yaml: Bookmark categories and links
  • widgets.yaml: Dashboard widgets configuration
  • docker.yaml: Docker integration settings
"},{"location":"services/homepage/#volumes","title":"Volumes","text":"
  • ./configs/homepage:/app/config: Configuration files
  • ./assets/icons:/app/public/icons: Custom service icons
  • ./assets/images:/app/public/images: Background images and assets
  • /var/run/docker.sock:/var/run/docker.sock: Docker socket for container monitoring
"},{"location":"services/homepage/#changemaker-lite-services","title":"Changemaker Lite Services","text":"

Homepage is pre-configured with all Changemaker Lite services:

"},{"location":"services/homepage/#essential-tools","title":"Essential Tools","text":"
  • Code Server (Port 8888): VS Code in the browser
  • Listmonk (Port 9000): Newsletter & mailing list manager
  • NocoDB (Port 8090): No-code database platform
"},{"location":"services/homepage/#content-documentation","title":"Content & Documentation","text":"
  • MkDocs (Port 4000): Live documentation server
  • Static Site (Port 4001): Built documentation hosting
"},{"location":"services/homepage/#automation-data","title":"Automation & Data","text":"
  • n8n (Port 5678): Workflow automation platform
  • PostgreSQL (Port 5432): Database backends
"},{"location":"services/homepage/#customization","title":"Customization","text":""},{"location":"services/homepage/#adding-custom-services","title":"Adding Custom Services","text":"

Edit configs/homepage/services.yaml to add new services:

- Custom Category:\n    - My Service:\n        href: http://localhost:8080\n        description: Custom service description\n        icon: mdi-application\n        widget:\n          type: ping\n          url: http://localhost:8080\n
"},{"location":"services/homepage/#custom-icons","title":"Custom Icons","text":"

Add custom icons to ./assets/icons/ directory and reference them in services.yaml:

icon: /icons/my-custom-icon.png\n
"},{"location":"services/homepage/#themes-and-styling","title":"Themes and Styling","text":"

Modify configs/homepage/settings.yaml to customize appearance:

theme: dark  # or light\ncolor: purple  # slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose\n
"},{"location":"services/homepage/#widgets","title":"Widgets","text":"

Enable live monitoring widgets in configs/homepage/services.yaml:

- Service Name:\n    widget:\n      type: docker\n      container: container-name\n      server: my-docker\n
"},{"location":"services/homepage/#service-monitoring","title":"Service Monitoring","text":"

Homepage can display real-time status information for your services:

  • Docker Integration: Container status and resource usage
  • HTTP Ping: Service availability monitoring
  • Custom APIs: Integration with service-specific APIs
"},{"location":"services/homepage/#docker-integration","title":"Docker Integration","text":"

Homepage monitors Docker containers automatically when configured:

  1. Ensure Docker socket is mounted (/var/run/docker.sock)
  2. Configure container mappings in docker.yaml
  3. Add widget configurations to services.yaml
"},{"location":"services/homepage/#security-considerations","title":"Security Considerations","text":"
  • Homepage runs with limited privileges
  • Configuration files should have appropriate permissions
  • Consider network isolation for production deployments
  • Use HTTPS for external access
  • Regularly update the Homepage image
"},{"location":"services/homepage/#troubleshooting","title":"Troubleshooting","text":""},{"location":"services/homepage/#common-issues","title":"Common Issues","text":"

Configuration not loading: Check YAML syntax in configuration files

docker logs homepage-changemaker\n

Icons not displaying: Verify icon paths and file permissions

ls -la ./assets/icons/\n

Services not reachable: Verify network connectivity between containers

docker exec homepage-changemaker ping service-name\n

Widget data not updating: Check Docker socket permissions and container access

docker exec homepage-changemaker ls -la /var/run/docker.sock\n

"},{"location":"services/homepage/#configuration-examples","title":"Configuration Examples","text":""},{"location":"services/homepage/#basic-service-widget","title":"Basic Service Widget","text":"
- Code Server:\n    href: http://localhost:8888\n    description: VS Code in the browser\n    icon: code-server\n    widget:\n      type: docker\n      container: code-server-changemaker\n
"},{"location":"services/homepage/#custom-dashboard-layout","title":"Custom Dashboard Layout","text":"
# settings.yaml\nlayout:\n  style: columns\n  columns: 3\n\n# Responsive breakpoints\nresponsive:\n  mobile: 1\n  tablet: 2\n  desktop: 3\n
"},{"location":"services/homepage/#official-documentation","title":"Official Documentation","text":"

For comprehensive configuration guides and advanced features: - Homepage Documentation - GitHub Repository - Configuration Examples - Widget Integrations

"},{"location":"services/listmonk/","title":"Listmonk","text":"

Self-hosted newsletter and mailing list manager.

"},{"location":"services/listmonk/#overview","title":"Overview","text":"

Listmonk is a modern, feature-rich newsletter and mailing list manager designed for high performance and easy management. It provides a complete solution for email campaigns, subscriber management, and analytics.

"},{"location":"services/listmonk/#features","title":"Features","text":"
  • Newsletter and email campaign management
  • Subscriber list management
  • Template system with HTML/markdown support
  • Campaign analytics and tracking
  • API for integration
  • Multi-list support
  • Bounce handling
  • Privacy-focused design
"},{"location":"services/listmonk/#access","title":"Access","text":"
  • Default Port: 9000
  • URL: http://localhost:9000
  • Admin User: Set via LISTMONK_ADMIN_USER environment variable
  • Admin Password: Set via LISTMONK_ADMIN_PASSWORD environment variable
"},{"location":"services/listmonk/#configuration","title":"Configuration","text":""},{"location":"services/listmonk/#environment-variables","title":"Environment Variables","text":"
  • LISTMONK_ADMIN_USER: Admin username
  • LISTMONK_ADMIN_PASSWORD: Admin password
  • POSTGRES_USER: Database username
  • POSTGRES_PASSWORD: Database password
  • POSTGRES_DB: Database name
"},{"location":"services/listmonk/#database","title":"Database","text":"

Listmonk uses PostgreSQL as its backend database. The database is automatically configured through the docker-compose setup.

"},{"location":"services/listmonk/#uploads","title":"Uploads","text":"
  • Upload directory: ./assets/uploads
  • Used for media files, templates, and attachments
"},{"location":"services/listmonk/#getting-started","title":"Getting Started","text":"
  1. Access Listmonk at http://localhost:9000
  2. Log in with your admin credentials
  3. Set up your first mailing list
  4. Configure SMTP settings for sending emails
  5. Import subscribers or create subscription forms
  6. Create your first campaign
"},{"location":"services/listmonk/#important-notes","title":"Important Notes","text":"
  • Configure SMTP settings before sending emails
  • Set up proper domain authentication (SPF, DKIM) for better deliverability
  • Regularly backup your subscriber data and campaigns
  • Monitor bounce rates and maintain list hygiene
"},{"location":"services/listmonk/#official-documentation","title":"Official Documentation","text":"

For comprehensive guides and API documentation, visit: - Listmonk Documentation - GitHub Repository

"},{"location":"services/mkdocs/","title":"MkDocs Material","text":"

Modern documentation site generator with live preview.

"},{"location":"services/mkdocs/#overview","title":"Overview","text":"

MkDocs Material is a powerful documentation framework built on top of MkDocs, providing a beautiful Material Design theme and advanced features for creating professional documentation sites.

"},{"location":"services/mkdocs/#features","title":"Features","text":"
  • Material Design theme
  • Live preview during development
  • Search functionality
  • Navigation and organization
  • Code syntax highlighting
  • Mathematical expressions support
  • Responsive design
  • Customizable themes and colors
"},{"location":"services/mkdocs/#access","title":"Access","text":"
  • Development Port: 4000
  • Development URL: http://localhost:4000
  • Live Reload: Automatically refreshes on file changes
"},{"location":"services/mkdocs/#configuration","title":"Configuration","text":""},{"location":"services/mkdocs/#main-configuration","title":"Main Configuration","text":"

Configuration is managed through mkdocs.yml in the project root.

"},{"location":"services/mkdocs/#volumes","title":"Volumes","text":"
  • ./mkdocs: Documentation source files
  • ./assets/images: Shared images directory
"},{"location":"services/mkdocs/#environment-variables","title":"Environment Variables","text":"
  • SITE_URL: Base domain for the site
  • USER_ID: User ID for file permissions
  • GROUP_ID: Group ID for file permissions
"},{"location":"services/mkdocs/#directory-structure","title":"Directory Structure","text":"
mkdocs/\n\u251c\u2500\u2500 mkdocs.yml          # Configuration file\n\u251c\u2500\u2500 docs/               # Documentation source\n\u2502   \u251c\u2500\u2500 index.md       # Homepage\n\u2502   \u251c\u2500\u2500 services/      # Service documentation\n\u2502   \u251c\u2500\u2500 blog/          # Blog posts\n\u2502   \u2514\u2500\u2500 overrides/     # Template overrides\n\u2514\u2500\u2500 site/              # Built static site\n
"},{"location":"services/mkdocs/#writing-documentation","title":"Writing Documentation","text":""},{"location":"services/mkdocs/#markdown-basics","title":"Markdown Basics","text":"
  • Use standard Markdown syntax
  • Support for tables, code blocks, and links
  • Mathematical expressions with MathJax
  • Admonitions for notes and warnings
"},{"location":"services/mkdocs/#example-page","title":"Example Page","text":"
# Page Title\n\nThis is a sample documentation page.\n\n## Section\n\nContent goes here with **bold** and *italic* text.\n\n### Code Example\n\n```python\ndef hello_world():\n    print(\"Hello, World!\")\n

Note

This is an informational note.

## Building and Deployment\n\n### Development\n\nThe development server runs automatically with live reload.\n\n### Building Static Site\n\n```bash\ndocker exec mkdocs-changemaker mkdocs build\n

The built site will be available in the mkdocs/site/ directory.

"},{"location":"services/mkdocs/#customization","title":"Customization","text":""},{"location":"services/mkdocs/#themes-and-colors","title":"Themes and Colors","text":"

Customize appearance in mkdocs.yml:

theme:\n  name: material\n  palette:\n    primary: blue\n    accent: indigo\n
"},{"location":"services/mkdocs/#custom-css","title":"Custom CSS","text":"

Add custom styles in docs/stylesheets/extra.css.

"},{"location":"services/mkdocs/#official-documentation","title":"Official Documentation","text":"

For comprehensive MkDocs Material documentation: - MkDocs Material - MkDocs Documentation - Markdown Guide

"},{"location":"services/n8n/","title":"n8n","text":"

Workflow automation tool for connecting services and automating tasks.

"},{"location":"services/n8n/#overview","title":"Overview","text":"

n8n is a powerful workflow automation tool that allows you to connect various apps and services together. It provides a visual interface for creating automated workflows, making it easy to integrate different systems and automate repetitive tasks.

"},{"location":"services/n8n/#features","title":"Features","text":"
  • Visual workflow editor
  • 400+ integrations
  • Custom code execution (JavaScript/Python)
  • Webhook support
  • Scheduled workflows
  • Error handling and retries
  • User management
  • API access
  • Self-hosted and privacy-focused
"},{"location":"services/n8n/#access","title":"Access","text":"
  • Default Port: 5678
  • URL: http://localhost:5678
  • Default User Email: Set via N8N_DEFAULT_USER_EMAIL
  • Default User Password: Set via N8N_DEFAULT_USER_PASSWORD
"},{"location":"services/n8n/#configuration","title":"Configuration","text":""},{"location":"services/n8n/#environment-variables","title":"Environment Variables","text":"
  • N8N_HOST: Hostname for n8n (default: n8n.${DOMAIN})
  • N8N_PORT: Internal port (5678)
  • N8N_PROTOCOL: Protocol for webhooks (https)
  • NODE_ENV: Environment (production)
  • WEBHOOK_URL: Base URL for webhooks
  • GENERIC_TIMEZONE: Timezone setting
  • N8N_ENCRYPTION_KEY: Encryption key for credentials
  • N8N_USER_MANAGEMENT_DISABLED: Enable/disable user management
  • N8N_DEFAULT_USER_EMAIL: Default admin email
  • N8N_DEFAULT_USER_PASSWORD: Default admin password
"},{"location":"services/n8n/#volumes","title":"Volumes","text":"
  • n8n_data: Persistent data storage
  • ./local-files: Local file access for workflows
"},{"location":"services/n8n/#getting-started","title":"Getting Started","text":"
  1. Access n8n at http://localhost:5678
  2. Log in with your admin credentials
  3. Create your first workflow
  4. Add nodes for different services
  5. Configure connections between nodes
  6. Test and activate your workflow
"},{"location":"services/n8n/#common-use-cases","title":"Common Use Cases","text":""},{"location":"services/n8n/#documentation-automation","title":"Documentation Automation","text":"
  • Auto-generate documentation from code comments
  • Sync documentation between different platforms
  • Notify team when documentation is updated
"},{"location":"services/n8n/#email-campaign-integration","title":"Email Campaign Integration","text":"
  • Connect Listmonk with external data sources
  • Automate subscriber management
  • Trigger campaigns based on events
"},{"location":"services/n8n/#database-management-with-nocodb","title":"Database Management with NocoDB","text":"
  • Sync data between NocoDB and external APIs
  • Automate data entry and validation
  • Create backup workflows for database content
  • Generate reports from NocoDB data
"},{"location":"services/n8n/#development-workflows","title":"Development Workflows","text":"
  • Auto-deploy documentation on git push
  • Sync code changes with documentation
  • Backup automation
"},{"location":"services/n8n/#data-processing","title":"Data Processing","text":"
  • Process CSV files and import to databases
  • Transform data between different formats
  • Schedule regular data updates
"},{"location":"services/n8n/#example-workflows","title":"Example Workflows","text":""},{"location":"services/n8n/#simple-webhook-to-email","title":"Simple Webhook to Email","text":"
Webhook \u2192 Email\n
"},{"location":"services/n8n/#scheduled-documentation-backup","title":"Scheduled Documentation Backup","text":"
Schedule \u2192 Read Files \u2192 Compress \u2192 Upload to Storage\n
"},{"location":"services/n8n/#git-integration","title":"Git Integration","text":"
Git Webhook \u2192 Process Changes \u2192 Update Documentation \u2192 Notify Team\n
"},{"location":"services/n8n/#security-considerations","title":"Security Considerations","text":"
  • Use strong encryption keys
  • Secure webhook URLs
  • Regularly update credentials
  • Monitor workflow executions
  • Implement proper error handling
"},{"location":"services/n8n/#integration-with-other-services","title":"Integration with Other Services","text":"

n8n can integrate with all services in your Changemaker Lite setup: - Listmonk: Manage subscribers and campaigns - PostgreSQL: Read/write database operations - Code Server: File operations and git integration - MkDocs: Documentation generation and updates

"},{"location":"services/n8n/#troubleshooting","title":"Troubleshooting","text":""},{"location":"services/n8n/#common-issues","title":"Common Issues","text":"
  • Workflow Execution Errors: Check node configurations and credentials
  • Webhook Issues: Verify URLs and authentication
  • Connection Problems: Check network connectivity between services
"},{"location":"services/n8n/#debugging","title":"Debugging","text":"
# Check container logs\ndocker logs n8n-changemaker\n\n# Access container shell\ndocker exec -it n8n-changemaker sh\n\n# Check workflow executions in the UI\n# Visit http://localhost:5678 \u2192 Executions\n
"},{"location":"services/n8n/#official-documentation","title":"Official Documentation","text":"

For comprehensive n8n documentation: - n8n Documentation - Community Workflows - Node Reference - GitHub Repository

"},{"location":"services/nocodb/","title":"NocoDB","text":"

No-code database platform that turns any database into a smart spreadsheet.

"},{"location":"services/nocodb/#overview","title":"Overview","text":"

NocoDB is an open-source no-code platform that transforms any database into a smart spreadsheet interface. It provides a user-friendly way to manage data, create forms, build APIs, and collaborate on database operations without requiring extensive technical knowledge.

"},{"location":"services/nocodb/#features","title":"Features","text":"
  • Smart Spreadsheet Interface: Transform databases into intuitive spreadsheets
  • Form Builder: Create custom forms for data entry
  • API Generation: Auto-generated REST APIs for all tables
  • Collaboration: Real-time collaboration with team members
  • Access Control: Role-based permissions and sharing
  • Data Visualization: Charts and dashboard creation
  • Webhooks: Integration with external services
  • Import/Export: Support for CSV, Excel, and other formats
  • Multi-Database Support: Works with PostgreSQL, MySQL, SQLite, and more
"},{"location":"services/nocodb/#access","title":"Access","text":"
  • Default Port: 8090
  • URL: http://localhost:8090
  • Database: PostgreSQL (dedicated root_db instance)
"},{"location":"services/nocodb/#configuration","title":"Configuration","text":""},{"location":"services/nocodb/#environment-variables","title":"Environment Variables","text":"
  • NOCODB_PORT: External port mapping (default: 8090)
  • NC_DB: Database connection string for PostgreSQL backend
"},{"location":"services/nocodb/#database-backend","title":"Database Backend","text":"

NocoDB uses a dedicated PostgreSQL instance (root_db) with the following configuration: - Database Name: root_db - Username: postgres - Password: password - Host: root_db (internal container name)

"},{"location":"services/nocodb/#volumes","title":"Volumes","text":"
  • nc_data: Application data and configuration storage
  • db_data: PostgreSQL database files
"},{"location":"services/nocodb/#getting-started","title":"Getting Started","text":"
  1. Access NocoDB: Navigate to http://localhost:8090
  2. Initial Setup: Complete the onboarding process
  3. Create Project: Start with a new project or connect existing databases
  4. Add Tables: Import data or create new tables
  5. Configure Views: Set up different views (Grid, Form, Gallery, etc.)
  6. Set Permissions: Configure user access and sharing settings
"},{"location":"services/nocodb/#common-use-cases","title":"Common Use Cases","text":""},{"location":"services/nocodb/#content-management","title":"Content Management","text":"
  • Create content databases for blogs and websites
  • Manage product catalogs and inventories
  • Track customer information and interactions
"},{"location":"services/nocodb/#project-management","title":"Project Management","text":"
  • Task and project tracking systems
  • Team collaboration workspaces
  • Resource and timeline management
"},{"location":"services/nocodb/#data-collection","title":"Data Collection","text":"
  • Custom forms for surveys and feedback
  • Event registration and management
  • Lead capture and CRM systems
"},{"location":"services/nocodb/#integration-with-other-services","title":"Integration with Other Services","text":"

NocoDB can integrate well with other Changemaker Lite services:

  • n8n Integration: Use NocoDB as a data source/destination in automation workflows
  • Listmonk Integration: Manage subscriber lists and campaign data
  • Documentation: Store and manage documentation metadata
"},{"location":"services/nocodb/#api-usage","title":"API Usage","text":"

NocoDB automatically generates REST APIs for all your tables:

# Get all records from a table\nGET http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}\n\n# Create a new record\nPOST http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}\n\n# Update a record\nPATCH http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}/{id}\n
"},{"location":"services/nocodb/#backup-and-data-management","title":"Backup and Data Management","text":""},{"location":"services/nocodb/#database-backup","title":"Database Backup","text":"

Since NocoDB uses PostgreSQL, you can backup the database:

# Backup NocoDB database\ndocker exec root_db pg_dump -U postgres root_db > nocodb_backup.sql\n\n# Restore from backup\ndocker exec -i root_db psql -U postgres root_db < nocodb_backup.sql\n
"},{"location":"services/nocodb/#application-data","title":"Application Data","text":"

Application settings and metadata are stored in the nc_data volume.

"},{"location":"services/nocodb/#security-considerations","title":"Security Considerations","text":"
  • Change default database credentials in production
  • Configure proper access controls within NocoDB
  • Use HTTPS for production deployments
  • Regularly backup both database and application data
  • Monitor access logs and user activities
"},{"location":"services/nocodb/#performance-tips","title":"Performance Tips","text":"
  • Regular database maintenance and optimization
  • Monitor memory usage for large datasets
  • Use appropriate indexing for frequently queried fields
  • Consider database connection pooling for high-traffic scenarios
"},{"location":"services/nocodb/#troubleshooting","title":"Troubleshooting","text":""},{"location":"services/nocodb/#common-issues","title":"Common Issues","text":"

Service won't start: Check if the PostgreSQL database is healthy

docker logs root_db\n

Database connection errors: Verify database credentials and network connectivity

docker exec nocodb nc_data nc\n

Performance issues: Monitor resource usage and optimize queries

docker stats nocodb root_db\n

"},{"location":"services/nocodb/#official-documentation","title":"Official Documentation","text":"

For comprehensive guides and advanced features: - NocoDB Documentation - GitHub Repository - Community Forum

"},{"location":"services/postgresql/","title":"PostgreSQL Database","text":"

Reliable database backend for applications.

"},{"location":"services/postgresql/#overview","title":"Overview","text":"

PostgreSQL is a powerful, open-source relational database system. In Changemaker Lite, it serves as the backend database for Listmonk and can be used by other applications requiring persistent data storage.

"},{"location":"services/postgresql/#features","title":"Features","text":"
  • ACID compliance
  • Advanced SQL features
  • JSON/JSONB support
  • Full-text search
  • Extensibility
  • High performance
  • Reliability and data integrity
"},{"location":"services/postgresql/#access","title":"Access","text":"
  • Default Port: 5432
  • Host: listmonk-db (internal container name)
  • Database: Set via POSTGRES_DB environment variable
  • Username: Set via POSTGRES_USER environment variable
  • Password: Set via POSTGRES_PASSWORD environment variable
"},{"location":"services/postgresql/#configuration","title":"Configuration","text":""},{"location":"services/postgresql/#environment-variables","title":"Environment Variables","text":"
  • POSTGRES_USER: Database username
  • POSTGRES_PASSWORD: Database password
  • POSTGRES_DB: Database name
"},{"location":"services/postgresql/#health-checks","title":"Health Checks","text":"

The PostgreSQL container includes health checks to ensure the database is ready before dependent services start.

"},{"location":"services/postgresql/#data-persistence","title":"Data Persistence","text":"

Database data is stored in a Docker volume (listmonk-data) to ensure persistence across container restarts.

"},{"location":"services/postgresql/#connecting-to-the-database","title":"Connecting to the Database","text":""},{"location":"services/postgresql/#from-host-machine","title":"From Host Machine","text":"

You can connect to PostgreSQL from your host machine using:

psql -h localhost -p 5432 -U [username] -d [database]\n
"},{"location":"services/postgresql/#from-other-containers","title":"From Other Containers","text":"

Other containers can connect using the internal hostname listmonk-db on port 5432.

"},{"location":"services/postgresql/#backup-and-restore","title":"Backup and Restore","text":""},{"location":"services/postgresql/#backup","title":"Backup","text":"
docker exec listmonk-db pg_dump -U [username] [database] > backup.sql\n
"},{"location":"services/postgresql/#restore","title":"Restore","text":"
docker exec -i listmonk-db psql -U [username] [database] < backup.sql\n
"},{"location":"services/postgresql/#monitoring","title":"Monitoring","text":"

Monitor database health and performance through: - Container logs: docker logs listmonk-db - Database metrics and queries - Connection monitoring

"},{"location":"services/postgresql/#security-considerations","title":"Security Considerations","text":"
  • Use strong passwords
  • Regularly update PostgreSQL version
  • Monitor access logs
  • Implement regular backups
  • Consider network isolation
"},{"location":"services/postgresql/#official-documentation","title":"Official Documentation","text":"

For comprehensive PostgreSQL documentation: - PostgreSQL Documentation - Docker PostgreSQL Image

"},{"location":"services/static-server/","title":"Static Site Server","text":"

Nginx-powered static site server for hosting built documentation and websites.

"},{"location":"services/static-server/#overview","title":"Overview","text":"

The Static Site Server uses Nginx to serve your built documentation and static websites. It's configured to serve the built MkDocs site and other static content with high performance and reliability.

"},{"location":"services/static-server/#features","title":"Features","text":"
  • High-performance static file serving
  • Automatic index file handling
  • Gzip compression
  • Caching headers
  • Security headers
  • Custom error pages
  • URL rewriting support
"},{"location":"services/static-server/#access","title":"Access","text":"
  • Default Port: 4001
  • URL: http://localhost:4001
  • Document Root: /config/www (mounted from ./mkdocs/site)
"},{"location":"services/static-server/#configuration","title":"Configuration","text":""},{"location":"services/static-server/#environment-variables","title":"Environment Variables","text":"
  • PUID: User ID for file permissions (default: 1000)
  • PGID: Group ID for file permissions (default: 1000)
  • TZ: Timezone setting (default: Etc/UTC)
"},{"location":"services/static-server/#volumes","title":"Volumes","text":"
  • ./mkdocs/site:/config/www: Static site files
  • Built MkDocs site is automatically served
"},{"location":"services/static-server/#usage","title":"Usage","text":"
  1. Build your MkDocs site: docker exec mkdocs-changemaker mkdocs build
  2. The built site is automatically available at http://localhost:4001
  3. Any files in ./mkdocs/site/ will be served statically
"},{"location":"services/static-server/#file-structure","title":"File Structure","text":"
mkdocs/site/           # Served at /\n\u251c\u2500\u2500 index.html         # Homepage\n\u251c\u2500\u2500 assets/           # CSS, JS, images\n\u251c\u2500\u2500 services/         # Service documentation\n\u2514\u2500\u2500 search/           # Search functionality\n
"},{"location":"services/static-server/#performance-features","title":"Performance Features","text":"
  • Gzip Compression: Automatic compression for text files
  • Browser Caching: Optimized cache headers
  • Fast Static Serving: Nginx optimized for static content
  • Security Headers: Basic security header configuration
"},{"location":"services/static-server/#custom-configuration","title":"Custom Configuration","text":"

For advanced Nginx configuration, you can: 1. Create custom Nginx config files 2. Mount them as volumes 3. Restart the container

"},{"location":"services/static-server/#monitoring","title":"Monitoring","text":"

Monitor the static site server through: - Container logs: docker logs mkdocs-site-server-changemaker - Access logs for traffic analysis - Performance metrics

"},{"location":"services/static-server/#troubleshooting","title":"Troubleshooting","text":""},{"location":"services/static-server/#common-issues","title":"Common Issues","text":"
  • 404 Errors: Ensure MkDocs site is built and files exist in ./mkdocs/site/
  • Permission Issues: Check PUID and PGID settings
  • File Not Found: Verify file paths and case sensitivity
"},{"location":"services/static-server/#debugging","title":"Debugging","text":"
# Check container logs\ndocker logs mkdocs-site-server-changemaker\n\n# Verify files are present\ndocker exec mkdocs-site-server-changemaker ls -la /config/www\n\n# Test file serving\ncurl -I http://localhost:4001\n
"},{"location":"services/static-server/#official-documentation","title":"Official Documentation","text":"

For more information about the underlying Nginx server: - LinuxServer.io Nginx - Nginx Documentation

"}]} \ No newline at end of file diff --git a/mkdocs/site/services/code-server/index.html b/mkdocs/site/services/code-server/index.html new file mode 100644 index 0000000..d402c3e --- /dev/null +++ b/mkdocs/site/services/code-server/index.html @@ -0,0 +1,1048 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code Server - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + +

Code Server

+

Visual Studio Code in your browser for remote development.

+

Overview

+

Code Server provides a full Visual Studio Code experience in your web browser, allowing you to develop from any device. It runs on your server and provides access to your development environment through a web interface.

+

Features

+
    +
  • Full VS Code experience in the browser
  • +
  • Extensions support
  • +
  • Terminal access
  • +
  • Git integration
  • +
  • File editing and management
  • +
  • Multi-language support
  • +
+

Access

+
    +
  • Default Port: 8888
  • +
  • URL: http://localhost:8888
  • +
  • Default Workspace: /home/coder/mkdocs/
  • +
+

Configuration

+

Environment Variables

+
    +
  • DOCKER_USER: The user to run code-server as (default: coder)
  • +
  • DEFAULT_WORKSPACE: Default workspace directory
  • +
  • USER_ID: User ID for file permissions
  • +
  • GROUP_ID: Group ID for file permissions
  • +
+

Volumes

+
    +
  • ./configs/code-server/.config: VS Code configuration
  • +
  • ./configs/code-server/.local: Local data
  • +
  • ./mkdocs: Main workspace directory
  • +
+

Usage

+
    +
  1. Access Code Server at http://localhost:8888
  2. +
  3. Open the /home/coder/mkdocs/ workspace
  4. +
  5. Start editing your documentation files
  6. +
  7. Install extensions as needed
  8. +
  9. Use the integrated terminal for commands
  10. +
+

Useful Extensions

+

Consider installing these extensions for better documentation work:

+
    +
  • Markdown All in One
  • +
  • Material Design Icons
  • +
  • GitLens
  • +
  • Docker
  • +
  • YAML
  • +
+

Official Documentation

+

For more detailed information, visit the official Code Server documentation.

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/services/homepage/index.html b/mkdocs/site/services/homepage/index.html new file mode 100644 index 0000000..e22be91 --- /dev/null +++ b/mkdocs/site/services/homepage/index.html @@ -0,0 +1,1489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Homepage - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + +

Homepage

+

Modern dashboard for accessing all your self-hosted services.

+

Overview

+

Homepage is a modern, fully static, fast, secure fully configurable application dashboard with integrations for over 100 services. It provides a beautiful and customizable interface to access all your Changemaker Lite services from a single location.

+

Features

+
    +
  • Service Dashboard: Central hub for all your applications
  • +
  • Docker Integration: Automatic service discovery and monitoring
  • +
  • Customizable Layout: Flexible grid-based layout system
  • +
  • Service Widgets: Live status and metrics for services
  • +
  • Quick Search: Fast navigation with built-in search
  • +
  • Bookmarks: Organize frequently used links
  • +
  • Dark/Light Themes: Multiple color schemes available
  • +
  • Responsive Design: Works on desktop and mobile devices
  • +
+

Access

+
    +
  • Default Port: 3010
  • +
  • URL: http://localhost:3010
  • +
  • Configuration: YAML-based configuration files
  • +
+

Configuration

+

Environment Variables

+
    +
  • HOMEPAGE_PORT: External port mapping (default: 3010)
  • +
  • PUID: User ID for file permissions (default: 1000)
  • +
  • PGID: Group ID for file permissions (default: 1000)
  • +
  • TZ: Timezone setting (default: Etc/UTC)
  • +
  • HOMEPAGE_ALLOWED_HOSTS: Allowed hosts for the dashboard
  • +
+

Configuration Files

+

Homepage uses YAML configuration files located in ./configs/homepage/:

+
    +
  • settings.yaml: Global settings and theme configuration
  • +
  • services.yaml: Service definitions and widgets
  • +
  • bookmarks.yaml: Bookmark categories and links
  • +
  • widgets.yaml: Dashboard widgets configuration
  • +
  • docker.yaml: Docker integration settings
  • +
+

Volumes

+
    +
  • ./configs/homepage:/app/config: Configuration files
  • +
  • ./assets/icons:/app/public/icons: Custom service icons
  • +
  • ./assets/images:/app/public/images: Background images and assets
  • +
  • /var/run/docker.sock:/var/run/docker.sock: Docker socket for container monitoring
  • +
+

Changemaker Lite Services

+

Homepage is pre-configured with all Changemaker Lite services:

+

Essential Tools

+
    +
  • Code Server (Port 8888): VS Code in the browser
  • +
  • Listmonk (Port 9000): Newsletter & mailing list manager
  • +
  • NocoDB (Port 8090): No-code database platform
  • +
+

Content & Documentation

+
    +
  • MkDocs (Port 4000): Live documentation server
  • +
  • Static Site (Port 4001): Built documentation hosting
  • +
+

Automation & Data

+
    +
  • n8n (Port 5678): Workflow automation platform
  • +
  • PostgreSQL (Port 5432): Database backends
  • +
+

Customization

+

Adding Custom Services

+

Edit configs/homepage/services.yaml to add new services:

+
- Custom Category:
+    - My Service:
+        href: http://localhost:8080
+        description: Custom service description
+        icon: mdi-application
+        widget:
+          type: ping
+          url: http://localhost:8080
+
+

Custom Icons

+

Add custom icons to ./assets/icons/ directory and reference them in services.yaml:

+
icon: /icons/my-custom-icon.png
+
+

Themes and Styling

+

Modify configs/homepage/settings.yaml to customize appearance:

+
theme: dark  # or light
+color: purple  # slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
+
+

Widgets

+

Enable live monitoring widgets in configs/homepage/services.yaml:

+
- Service Name:
+    widget:
+      type: docker
+      container: container-name
+      server: my-docker
+
+

Service Monitoring

+

Homepage can display real-time status information for your services:

+
    +
  • Docker Integration: Container status and resource usage
  • +
  • HTTP Ping: Service availability monitoring
  • +
  • Custom APIs: Integration with service-specific APIs
  • +
+

Docker Integration

+

Homepage monitors Docker containers automatically when configured:

+
    +
  1. Ensure Docker socket is mounted (/var/run/docker.sock)
  2. +
  3. Configure container mappings in docker.yaml
  4. +
  5. Add widget configurations to services.yaml
  6. +
+

Security Considerations

+
    +
  • Homepage runs with limited privileges
  • +
  • Configuration files should have appropriate permissions
  • +
  • Consider network isolation for production deployments
  • +
  • Use HTTPS for external access
  • +
  • Regularly update the Homepage image
  • +
+

Troubleshooting

+

Common Issues

+

Configuration not loading: Check YAML syntax in configuration files +

docker logs homepage-changemaker
+

+

Icons not displaying: Verify icon paths and file permissions +

ls -la ./assets/icons/
+

+

Services not reachable: Verify network connectivity between containers +

docker exec homepage-changemaker ping service-name
+

+

Widget data not updating: Check Docker socket permissions and container access +

docker exec homepage-changemaker ls -la /var/run/docker.sock
+

+

Configuration Examples

+

Basic Service Widget

+
- Code Server:
+    href: http://localhost:8888
+    description: VS Code in the browser
+    icon: code-server
+    widget:
+      type: docker
+      container: code-server-changemaker
+
+

Custom Dashboard Layout

+
# settings.yaml
+layout:
+  style: columns
+  columns: 3
+
+# Responsive breakpoints
+responsive:
+  mobile: 1
+  tablet: 2
+  desktop: 3
+
+

Official Documentation

+

For comprehensive configuration guides and advanced features: +- Homepage Documentation +- GitHub Repository +- Configuration Examples +- Widget Integrations

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/services/index.html b/mkdocs/site/services/index.html new file mode 100644 index 0000000..a5411d3 --- /dev/null +++ b/mkdocs/site/services/index.html @@ -0,0 +1,1124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Overview - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + +

Services

+

Changemaker Lite includes several powerful services that work together to provide a complete documentation and development platform. Each service is containerized and can be accessed through its dedicated port.

+

Available Services

+

Code Server

+

Port: 8888 | Visual Studio Code in your browser for remote development +- Full IDE experience +- Extensions support +- Git integration +- Terminal access

+

Listmonk

+

Port: 9000 | Self-hosted newsletter and mailing list manager +- Email campaigns +- Subscriber management +- Analytics +- Template system

+

PostgreSQL

+

Port: 5432 | Reliable database backend +- Data persistence for Listmonk +- ACID compliance +- High performance +- Backup and restore capabilities

+

MkDocs Material

+

Port: 4000 | Documentation site generator with live preview +- Material Design theme +- Live reload +- Search functionality +- Markdown support

+

Static Site Server

+

Port: 4001 | Nginx-powered static site hosting +- High-performance serving +- Built documentation hosting +- Caching and compression +- Security headers

+

n8n

+

Port: 5678 | Workflow automation tool +- Visual workflow editor +- 400+ integrations +- Custom code execution +- Webhook support

+

NocoDB

+

Port: 8090 | No-code database platform +- Smart spreadsheet interface +- Form builder and API generation +- Real-time collaboration +- Multi-database support

+

Homepage

+

Port: 3010 | Modern dashboard for all services +- Service dashboard and monitoring +- Docker integration +- Customizable layout +- Quick search and bookmarks

+

Service Architecture

+
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
+│   Homepage      │    │   Code Server   │    │     MkDocs      │
+│     :3010       │    │     :8888       │    │     :4000       │
+└─────────────────┘    └─────────────────┘    └─────────────────┘
+
+┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
+│ Static Server   │    │    Listmonk     │    │      n8n        │
+│     :4001       │    │     :9000       │    │     :5678       │
+└─────────────────┘    └─────────────────┘    └─────────────────┘
+
+┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
+│     NocoDB      │    │ PostgreSQL      │    │ PostgreSQL      │
+│     :8090       │    │ (listmonk-db)   │    │ (root_db)       │
+└─────────────────┘    │     :5432       │    │     :5432       │
+         │              └─────────────────┘    └─────────────────┘
+         └──────────────────────────────────────────────────────┘
+
+

Getting Started

+
    +
  1. Start all services: docker compose up -d
  2. +
  3. Check service status: docker compose ps
  4. +
  5. View logs: docker compose logs [service-name]
  6. +
  7. Stop services: docker compose down
  8. +
+

Service Dependencies

+
    +
  • Listmonk depends on PostgreSQL (listmonk-db)
  • +
  • NocoDB depends on PostgreSQL (root_db)
  • +
  • Static Server serves content built by MkDocs
  • +
  • n8n can integrate with all other services
  • +
  • All services share the changemaker network
  • +
+

Environment Configuration

+

Services are configured through environment variables in your .env file:

+
# Port configurations
+CODE_SERVER_PORT=8888
+LISTMONK_PORT=9000
+LISTMONK_DB_PORT=5432
+MKDOCS_PORT=4000
+MKDOCS_SITE_SERVER_PORT=4001
+N8N_PORT=5678
+
+# User and group IDs
+USER_ID=1000
+GROUP_ID=1000
+
+# Database configuration
+POSTGRES_USER=listmonk
+POSTGRES_PASSWORD=your_password
+POSTGRES_DB=listmonk
+
+# n8n configuration
+N8N_ENCRYPTION_KEY=your_encryption_key
+N8N_USER_EMAIL=admin@example.com
+N8N_USER_PASSWORD=your_password
+
+

Monitoring and Maintenance

+

Health Checks

+
# Check all services
+docker compose ps
+
+# Check specific service logs
+docker compose logs listmonk-app
+docker compose logs code-server
+
+

Updates

+
# Pull latest images
+docker compose pull
+
+# Restart with new images
+docker compose down && docker compose up -d
+
+

Backups

+
    +
  • PostgreSQL: Regular database backups
  • +
  • n8n: Export workflows and credentials
  • +
  • Code Server: Backup configuration and workspace
  • +
  • MkDocs: Version control your documentation
  • +
+

Troubleshooting

+

Common Issues

+
    +
  1. Port Conflicts: Ensure ports are not used by other applications
  2. +
  3. Permission Issues: Check USER_ID and GROUP_ID settings
  4. +
  5. Network Issues: Verify services can communicate through the changemaker network
  6. +
  7. Data Persistence: Ensure volumes are properly mounted
  8. +
+

Getting Help

+
    +
  • Check individual service documentation
  • +
  • Review container logs for error messages
  • +
  • Verify environment variable configuration
  • +
  • Test network connectivity between services
  • +
+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/services/listmonk/index.html b/mkdocs/site/services/listmonk/index.html new file mode 100644 index 0000000..79d9963 --- /dev/null +++ b/mkdocs/site/services/listmonk/index.html @@ -0,0 +1,1072 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Listmonk - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + +

Listmonk

+

Self-hosted newsletter and mailing list manager.

+

Overview

+

Listmonk is a modern, feature-rich newsletter and mailing list manager designed for high performance and easy management. It provides a complete solution for email campaigns, subscriber management, and analytics.

+

Features

+
    +
  • Newsletter and email campaign management
  • +
  • Subscriber list management
  • +
  • Template system with HTML/markdown support
  • +
  • Campaign analytics and tracking
  • +
  • API for integration
  • +
  • Multi-list support
  • +
  • Bounce handling
  • +
  • Privacy-focused design
  • +
+

Access

+
    +
  • Default Port: 9000
  • +
  • URL: http://localhost:9000
  • +
  • Admin User: Set via LISTMONK_ADMIN_USER environment variable
  • +
  • Admin Password: Set via LISTMONK_ADMIN_PASSWORD environment variable
  • +
+

Configuration

+

Environment Variables

+
    +
  • LISTMONK_ADMIN_USER: Admin username
  • +
  • LISTMONK_ADMIN_PASSWORD: Admin password
  • +
  • POSTGRES_USER: Database username
  • +
  • POSTGRES_PASSWORD: Database password
  • +
  • POSTGRES_DB: Database name
  • +
+

Database

+

Listmonk uses PostgreSQL as its backend database. The database is automatically configured through the docker-compose setup.

+

Uploads

+
    +
  • Upload directory: ./assets/uploads
  • +
  • Used for media files, templates, and attachments
  • +
+

Getting Started

+
    +
  1. Access Listmonk at http://localhost:9000
  2. +
  3. Log in with your admin credentials
  4. +
  5. Set up your first mailing list
  6. +
  7. Configure SMTP settings for sending emails
  8. +
  9. Import subscribers or create subscription forms
  10. +
  11. Create your first campaign
  12. +
+

Important Notes

+
    +
  • Configure SMTP settings before sending emails
  • +
  • Set up proper domain authentication (SPF, DKIM) for better deliverability
  • +
  • Regularly backup your subscriber data and campaigns
  • +
  • Monitor bounce rates and maintain list hygiene
  • +
+

Official Documentation

+

For comprehensive guides and API documentation, visit: +- Listmonk Documentation +- GitHub Repository

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/services/mkdocs/index.html b/mkdocs/site/services/mkdocs/index.html new file mode 100644 index 0000000..09bfde3 --- /dev/null +++ b/mkdocs/site/services/mkdocs/index.html @@ -0,0 +1,1228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MkDocs Material - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + +

MkDocs Material

+

Modern documentation site generator with live preview.

+

Overview

+

MkDocs Material is a powerful documentation framework built on top of MkDocs, providing a beautiful Material Design theme and advanced features for creating professional documentation sites.

+

Features

+
    +
  • Material Design theme
  • +
  • Live preview during development
  • +
  • Search functionality
  • +
  • Navigation and organization
  • +
  • Code syntax highlighting
  • +
  • Mathematical expressions support
  • +
  • Responsive design
  • +
  • Customizable themes and colors
  • +
+

Access

+
    +
  • Development Port: 4000
  • +
  • Development URL: http://localhost:4000
  • +
  • Live Reload: Automatically refreshes on file changes
  • +
+

Configuration

+

Main Configuration

+

Configuration is managed through mkdocs.yml in the project root.

+

Volumes

+
    +
  • ./mkdocs: Documentation source files
  • +
  • ./assets/images: Shared images directory
  • +
+

Environment Variables

+
    +
  • SITE_URL: Base domain for the site
  • +
  • USER_ID: User ID for file permissions
  • +
  • GROUP_ID: Group ID for file permissions
  • +
+

Directory Structure

+
mkdocs/
+├── mkdocs.yml          # Configuration file
+├── docs/               # Documentation source
+│   ├── index.md       # Homepage
+│   ├── services/      # Service documentation
+│   ├── blog/          # Blog posts
+│   └── overrides/     # Template overrides
+└── site/              # Built static site
+
+

Writing Documentation

+

Markdown Basics

+
    +
  • Use standard Markdown syntax
  • +
  • Support for tables, code blocks, and links
  • +
  • Mathematical expressions with MathJax
  • +
  • Admonitions for notes and warnings
  • +
+

Example Page

+
# Page Title
+
+This is a sample documentation page.
+
+## Section
+
+Content goes here with **bold** and *italic* text.
+
+### Code Example
+
+```python
+def hello_world():
+    print("Hello, World!")
+
+
+

Note

+

This is an informational note.

+
+
## Building and Deployment
+
+### Development
+
+The development server runs automatically with live reload.
+
+### Building Static Site
+
+```bash
+docker exec mkdocs-changemaker mkdocs build
+
+

The built site will be available in the mkdocs/site/ directory.

+

Customization

+

Themes and Colors

+

Customize appearance in mkdocs.yml:

+
theme:
+  name: material
+  palette:
+    primary: blue
+    accent: indigo
+
+

Custom CSS

+

Add custom styles in docs/stylesheets/extra.css.

+

Official Documentation

+

For comprehensive MkDocs Material documentation: +- MkDocs Material +- MkDocs Documentation +- Markdown Guide

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/services/n8n/index.html b/mkdocs/site/services/n8n/index.html new file mode 100644 index 0000000..c1eeab5 --- /dev/null +++ b/mkdocs/site/services/n8n/index.html @@ -0,0 +1,1414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + n8n - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + +

n8n

+

Workflow automation tool for connecting services and automating tasks.

+

Overview

+

n8n is a powerful workflow automation tool that allows you to connect various apps and services together. It provides a visual interface for creating automated workflows, making it easy to integrate different systems and automate repetitive tasks.

+

Features

+
    +
  • Visual workflow editor
  • +
  • 400+ integrations
  • +
  • Custom code execution (JavaScript/Python)
  • +
  • Webhook support
  • +
  • Scheduled workflows
  • +
  • Error handling and retries
  • +
  • User management
  • +
  • API access
  • +
  • Self-hosted and privacy-focused
  • +
+

Access

+
    +
  • Default Port: 5678
  • +
  • URL: http://localhost:5678
  • +
  • Default User Email: Set via N8N_DEFAULT_USER_EMAIL
  • +
  • Default User Password: Set via N8N_DEFAULT_USER_PASSWORD
  • +
+

Configuration

+

Environment Variables

+
    +
  • N8N_HOST: Hostname for n8n (default: n8n.${DOMAIN})
  • +
  • N8N_PORT: Internal port (5678)
  • +
  • N8N_PROTOCOL: Protocol for webhooks (https)
  • +
  • NODE_ENV: Environment (production)
  • +
  • WEBHOOK_URL: Base URL for webhooks
  • +
  • GENERIC_TIMEZONE: Timezone setting
  • +
  • N8N_ENCRYPTION_KEY: Encryption key for credentials
  • +
  • N8N_USER_MANAGEMENT_DISABLED: Enable/disable user management
  • +
  • N8N_DEFAULT_USER_EMAIL: Default admin email
  • +
  • N8N_DEFAULT_USER_PASSWORD: Default admin password
  • +
+

Volumes

+
    +
  • n8n_data: Persistent data storage
  • +
  • ./local-files: Local file access for workflows
  • +
+

Getting Started

+
    +
  1. Access n8n at http://localhost:5678
  2. +
  3. Log in with your admin credentials
  4. +
  5. Create your first workflow
  6. +
  7. Add nodes for different services
  8. +
  9. Configure connections between nodes
  10. +
  11. Test and activate your workflow
  12. +
+

Common Use Cases

+

Documentation Automation

+
    +
  • Auto-generate documentation from code comments
  • +
  • Sync documentation between different platforms
  • +
  • Notify team when documentation is updated
  • +
+

Email Campaign Integration

+
    +
  • Connect Listmonk with external data sources
  • +
  • Automate subscriber management
  • +
  • Trigger campaigns based on events
  • +
+

Database Management with NocoDB

+
    +
  • Sync data between NocoDB and external APIs
  • +
  • Automate data entry and validation
  • +
  • Create backup workflows for database content
  • +
  • Generate reports from NocoDB data
  • +
+

Development Workflows

+
    +
  • Auto-deploy documentation on git push
  • +
  • Sync code changes with documentation
  • +
  • Backup automation
  • +
+

Data Processing

+
    +
  • Process CSV files and import to databases
  • +
  • Transform data between different formats
  • +
  • Schedule regular data updates
  • +
+

Example Workflows

+

Simple Webhook to Email

+
Webhook → Email
+
+

Scheduled Documentation Backup

+
Schedule → Read Files → Compress → Upload to Storage
+
+

Git Integration

+
Git Webhook → Process Changes → Update Documentation → Notify Team
+
+

Security Considerations

+
    +
  • Use strong encryption keys
  • +
  • Secure webhook URLs
  • +
  • Regularly update credentials
  • +
  • Monitor workflow executions
  • +
  • Implement proper error handling
  • +
+

Integration with Other Services

+

n8n can integrate with all services in your Changemaker Lite setup: +- Listmonk: Manage subscribers and campaigns +- PostgreSQL: Read/write database operations +- Code Server: File operations and git integration +- MkDocs: Documentation generation and updates

+

Troubleshooting

+

Common Issues

+
    +
  • Workflow Execution Errors: Check node configurations and credentials
  • +
  • Webhook Issues: Verify URLs and authentication
  • +
  • Connection Problems: Check network connectivity between services
  • +
+

Debugging

+
# Check container logs
+docker logs n8n-changemaker
+
+# Access container shell
+docker exec -it n8n-changemaker sh
+
+# Check workflow executions in the UI
+# Visit http://localhost:5678 → Executions
+
+

Official Documentation

+

For comprehensive n8n documentation: +- n8n Documentation +- Community Workflows +- Node Reference +- GitHub Repository

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/services/nocodb/index.html b/mkdocs/site/services/nocodb/index.html new file mode 100644 index 0000000..484b187 --- /dev/null +++ b/mkdocs/site/services/nocodb/index.html @@ -0,0 +1,1393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NocoDB - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + +

NocoDB

+

No-code database platform that turns any database into a smart spreadsheet.

+

Overview

+

NocoDB is an open-source no-code platform that transforms any database into a smart spreadsheet interface. It provides a user-friendly way to manage data, create forms, build APIs, and collaborate on database operations without requiring extensive technical knowledge.

+

Features

+
    +
  • Smart Spreadsheet Interface: Transform databases into intuitive spreadsheets
  • +
  • Form Builder: Create custom forms for data entry
  • +
  • API Generation: Auto-generated REST APIs for all tables
  • +
  • Collaboration: Real-time collaboration with team members
  • +
  • Access Control: Role-based permissions and sharing
  • +
  • Data Visualization: Charts and dashboard creation
  • +
  • Webhooks: Integration with external services
  • +
  • Import/Export: Support for CSV, Excel, and other formats
  • +
  • Multi-Database Support: Works with PostgreSQL, MySQL, SQLite, and more
  • +
+

Access

+
    +
  • Default Port: 8090
  • +
  • URL: http://localhost:8090
  • +
  • Database: PostgreSQL (dedicated root_db instance)
  • +
+

Configuration

+

Environment Variables

+
    +
  • NOCODB_PORT: External port mapping (default: 8090)
  • +
  • NC_DB: Database connection string for PostgreSQL backend
  • +
+

Database Backend

+

NocoDB uses a dedicated PostgreSQL instance (root_db) with the following configuration: +- Database Name: root_db +- Username: postgres +- Password: password +- Host: root_db (internal container name)

+

Volumes

+
    +
  • nc_data: Application data and configuration storage
  • +
  • db_data: PostgreSQL database files
  • +
+

Getting Started

+
    +
  1. Access NocoDB: Navigate to http://localhost:8090
  2. +
  3. Initial Setup: Complete the onboarding process
  4. +
  5. Create Project: Start with a new project or connect existing databases
  6. +
  7. Add Tables: Import data or create new tables
  8. +
  9. Configure Views: Set up different views (Grid, Form, Gallery, etc.)
  10. +
  11. Set Permissions: Configure user access and sharing settings
  12. +
+

Common Use Cases

+

Content Management

+
    +
  • Create content databases for blogs and websites
  • +
  • Manage product catalogs and inventories
  • +
  • Track customer information and interactions
  • +
+

Project Management

+
    +
  • Task and project tracking systems
  • +
  • Team collaboration workspaces
  • +
  • Resource and timeline management
  • +
+

Data Collection

+
    +
  • Custom forms for surveys and feedback
  • +
  • Event registration and management
  • +
  • Lead capture and CRM systems
  • +
+

Integration with Other Services

+

NocoDB can integrate well with other Changemaker Lite services:

+
    +
  • n8n Integration: Use NocoDB as a data source/destination in automation workflows
  • +
  • Listmonk Integration: Manage subscriber lists and campaign data
  • +
  • Documentation: Store and manage documentation metadata
  • +
+

API Usage

+

NocoDB automatically generates REST APIs for all your tables:

+
# Get all records from a table
+GET http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}
+
+# Create a new record
+POST http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}
+
+# Update a record
+PATCH http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}/{id}
+
+

Backup and Data Management

+

Database Backup

+

Since NocoDB uses PostgreSQL, you can backup the database:

+
# Backup NocoDB database
+docker exec root_db pg_dump -U postgres root_db > nocodb_backup.sql
+
+# Restore from backup
+docker exec -i root_db psql -U postgres root_db < nocodb_backup.sql
+
+

Application Data

+

Application settings and metadata are stored in the nc_data volume.

+

Security Considerations

+
    +
  • Change default database credentials in production
  • +
  • Configure proper access controls within NocoDB
  • +
  • Use HTTPS for production deployments
  • +
  • Regularly backup both database and application data
  • +
  • Monitor access logs and user activities
  • +
+

Performance Tips

+
    +
  • Regular database maintenance and optimization
  • +
  • Monitor memory usage for large datasets
  • +
  • Use appropriate indexing for frequently queried fields
  • +
  • Consider database connection pooling for high-traffic scenarios
  • +
+

Troubleshooting

+

Common Issues

+

Service won't start: Check if the PostgreSQL database is healthy +

docker logs root_db
+

+

Database connection errors: Verify database credentials and network connectivity +

docker exec nocodb nc_data nc
+

+

Performance issues: Monitor resource usage and optimize queries +

docker stats nocodb root_db
+

+

Official Documentation

+

For comprehensive guides and advanced features: +- NocoDB Documentation +- GitHub Repository +- Community Forum

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/services/postgresql/index.html b/mkdocs/site/services/postgresql/index.html new file mode 100644 index 0000000..74f2abe --- /dev/null +++ b/mkdocs/site/services/postgresql/index.html @@ -0,0 +1,1210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PostgreSQL - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + +

PostgreSQL Database

+

Reliable database backend for applications.

+

Overview

+

PostgreSQL is a powerful, open-source relational database system. In Changemaker Lite, it serves as the backend database for Listmonk and can be used by other applications requiring persistent data storage.

+

Features

+
    +
  • ACID compliance
  • +
  • Advanced SQL features
  • +
  • JSON/JSONB support
  • +
  • Full-text search
  • +
  • Extensibility
  • +
  • High performance
  • +
  • Reliability and data integrity
  • +
+

Access

+
    +
  • Default Port: 5432
  • +
  • Host: listmonk-db (internal container name)
  • +
  • Database: Set via POSTGRES_DB environment variable
  • +
  • Username: Set via POSTGRES_USER environment variable
  • +
  • Password: Set via POSTGRES_PASSWORD environment variable
  • +
+

Configuration

+

Environment Variables

+
    +
  • POSTGRES_USER: Database username
  • +
  • POSTGRES_PASSWORD: Database password
  • +
  • POSTGRES_DB: Database name
  • +
+

Health Checks

+

The PostgreSQL container includes health checks to ensure the database is ready before dependent services start.

+

Data Persistence

+

Database data is stored in a Docker volume (listmonk-data) to ensure persistence across container restarts.

+

Connecting to the Database

+

From Host Machine

+

You can connect to PostgreSQL from your host machine using:

+
psql -h localhost -p 5432 -U [username] -d [database]
+
+

From Other Containers

+

Other containers can connect using the internal hostname listmonk-db on port 5432.

+

Backup and Restore

+

Backup

+
docker exec listmonk-db pg_dump -U [username] [database] > backup.sql
+
+

Restore

+
docker exec -i listmonk-db psql -U [username] [database] < backup.sql
+
+

Monitoring

+

Monitor database health and performance through: +- Container logs: docker logs listmonk-db +- Database metrics and queries +- Connection monitoring

+

Security Considerations

+
    +
  • Use strong passwords
  • +
  • Regularly update PostgreSQL version
  • +
  • Monitor access logs
  • +
  • Implement regular backups
  • +
  • Consider network isolation
  • +
+

Official Documentation

+

For comprehensive PostgreSQL documentation: +- PostgreSQL Documentation +- Docker PostgreSQL Image

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/services/static-server/index.html b/mkdocs/site/services/static-server/index.html new file mode 100644 index 0000000..2500f7d --- /dev/null +++ b/mkdocs/site/services/static-server/index.html @@ -0,0 +1,1199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Static Site Server - Changemaker Lite Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + +

Static Site Server

+

Nginx-powered static site server for hosting built documentation and websites.

+

Overview

+

The Static Site Server uses Nginx to serve your built documentation and static websites. It's configured to serve the built MkDocs site and other static content with high performance and reliability.

+

Features

+
    +
  • High-performance static file serving
  • +
  • Automatic index file handling
  • +
  • Gzip compression
  • +
  • Caching headers
  • +
  • Security headers
  • +
  • Custom error pages
  • +
  • URL rewriting support
  • +
+

Access

+
    +
  • Default Port: 4001
  • +
  • URL: http://localhost:4001
  • +
  • Document Root: /config/www (mounted from ./mkdocs/site)
  • +
+

Configuration

+

Environment Variables

+
    +
  • PUID: User ID for file permissions (default: 1000)
  • +
  • PGID: Group ID for file permissions (default: 1000)
  • +
  • TZ: Timezone setting (default: Etc/UTC)
  • +
+

Volumes

+
    +
  • ./mkdocs/site:/config/www: Static site files
  • +
  • Built MkDocs site is automatically served
  • +
+

Usage

+
    +
  1. Build your MkDocs site: docker exec mkdocs-changemaker mkdocs build
  2. +
  3. The built site is automatically available at http://localhost:4001
  4. +
  5. Any files in ./mkdocs/site/ will be served statically
  6. +
+

File Structure

+
mkdocs/site/           # Served at /
+├── index.html         # Homepage
+├── assets/           # CSS, JS, images
+├── services/         # Service documentation
+└── search/           # Search functionality
+
+

Performance Features

+
    +
  • Gzip Compression: Automatic compression for text files
  • +
  • Browser Caching: Optimized cache headers
  • +
  • Fast Static Serving: Nginx optimized for static content
  • +
  • Security Headers: Basic security header configuration
  • +
+

Custom Configuration

+

For advanced Nginx configuration, you can: +1. Create custom Nginx config files +2. Mount them as volumes +3. Restart the container

+

Monitoring

+

Monitor the static site server through: +- Container logs: docker logs mkdocs-site-server-changemaker +- Access logs for traffic analysis +- Performance metrics

+

Troubleshooting

+

Common Issues

+
    +
  • 404 Errors: Ensure MkDocs site is built and files exist in ./mkdocs/site/
  • +
  • Permission Issues: Check PUID and PGID settings
  • +
  • File Not Found: Verify file paths and case sensitivity
  • +
+

Debugging

+
# Check container logs
+docker logs mkdocs-site-server-changemaker
+
+# Verify files are present
+docker exec mkdocs-site-server-changemaker ls -la /config/www
+
+# Test file serving
+curl -I http://localhost:4001
+
+

Official Documentation

+

For more information about the underlying Nginx server: +- LinuxServer.io Nginx +- Nginx Documentation

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs/site/sitemap.xml b/mkdocs/site/sitemap.xml new file mode 100644 index 0000000..675303b --- /dev/null +++ b/mkdocs/site/sitemap.xml @@ -0,0 +1,51 @@ + + + + https://changeme.org/ + 2025-05-26 + + + https://changeme.org/getting-started/ + 2025-05-26 + + + https://changeme.org/blog/ + 2025-05-26 + + + https://changeme.org/services/ + 2025-05-26 + + + https://changeme.org/services/code-server/ + 2025-05-26 + + + https://changeme.org/services/homepage/ + 2025-05-26 + + + https://changeme.org/services/listmonk/ + 2025-05-26 + + + https://changeme.org/services/mkdocs/ + 2025-05-26 + + + https://changeme.org/services/n8n/ + 2025-05-26 + + + https://changeme.org/services/nocodb/ + 2025-05-26 + + + https://changeme.org/services/postgresql/ + 2025-05-26 + + + https://changeme.org/services/static-server/ + 2025-05-26 + + \ No newline at end of file diff --git a/mkdocs/site/sitemap.xml.gz b/mkdocs/site/sitemap.xml.gz new file mode 100644 index 0000000..72009b8 Binary files /dev/null and b/mkdocs/site/sitemap.xml.gz differ