diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 0a3a28e..ab31660 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -4,13 +4,14 @@ on: push: tags: - "desktop-v*" + workflow_dispatch: permissions: contents: write jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repository @@ -21,13 +22,21 @@ jobs: with: node-version: "lts/*" + - name: Inject version from tag + working-directory: desktop-app + run: | + VERSION="${GITHUB_REF_NAME#desktop-v}" + jq --arg v "$VERSION" '.version = $v' neutralino.config.json > tmp.json \ + && mv tmp.json neutralino.config.json + echo "::notice::Building version $VERSION" + - name: Setup Neutralinojs binaries working-directory: desktop-app run: npm run setup - name: Build all binaries (embedded + portable) working-directory: desktop-app - run: npm run build:all + run: npm run build - name: Stage release assets working-directory: desktop-app @@ -63,6 +72,7 @@ jobs: --exclude='desktop-app/bin' \ --exclude='desktop-app/node_modules' \ --exclude='desktop-app/output' \ + --exclude="desktop-app/$STAGING" \ --exclude='.git' \ desktop-app/ cd desktop-app @@ -74,6 +84,6 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - name: "Markdown Viewer Desktop ${{ github.ref_name }}" + name: "${{ github.ref_name }}" generate_release_notes: true files: desktop-app/release-assets/* diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 6a5177a..a6d8f22 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -2,9 +2,13 @@ name: Build and Push Docker Image on: push: - branches: [ main ] + branches: [main] + paths-ignore: + - "desktop-app/**" pull_request: - branches: [ main ] + branches: [main] + paths-ignore: + - "desktop-app/**" env: REGISTRY: ghcr.io @@ -18,37 +22,36 @@ jobs: packages: write steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=sha,prefix=sha- - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52ab34d --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Salesforce +.sfdx/ + +# Node +node_modules/ +dist/ +*.log +npm-debug.log* + +# Generated desktop resources (rebuilt by prepare.js) +desktop-app/resources/ +desktop-app/bin/ + +# Vite cache +.vite/ + +# OS generated files +.DS_Store +Thumbs.db + +# Editor +.vscode/ +.idea/ +.vercel/* +!.vercel/project.json +.env*.local + +# Claude Code +.claude/ diff --git a/.vercel/project.json b/.vercel/project.json new file mode 100644 index 0000000..17c8445 --- /dev/null +++ b/.vercel/project.json @@ -0,0 +1 @@ +{"projectId":"prj_caH2tkxw3dHNcLO0DzCbVMNObzA3","orgId":"team_lzDr5cAAkpQFExaMSnzJ5zvc","projectName":"markdown-viewer"} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e2314f0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,94 @@ +# Markdown Viewer — Agent Instructions + +A vanilla JS web app + Neutralinojs desktop app for rendering Markdown. Uses Vite for bundling. + +## Build & Dev + +```bash +# Install dependencies +npm install + +# Web dev server (http://localhost:5173) +npm run dev + +# Build web app to dist/ +npm run build + +# Desktop setup + dev +npm run desktop:install # Install desktop-app dependencies (once) +npm run desktop:prepare # Build + copy to resources/ +npm run desktop:dev # Run desktop app + +# Desktop build (Windows/Linux/macOS) +npm run desktop:build +``` + +No tests, no linter. + +## Architecture + +``` +web/ # Core app — the single source of truth + index.html # UI markup, MathJax CDN tag only + styles.css # CSS custom properties; [data-theme="dark"] for dark mode + src/ # ES modules + main.js # Entry point + event wiring + CSS imports + state.js # Shared mutable state + constants + dom.js # DOM element references (initDom pattern) + preprocessors.js # Markdown transformers (ADO TOC, callouts) + mermaid-utils.js # Mermaid init / zoom / drag / export + scroll-sync.js # Anchor cache + scroll interpolation + render.js # Markdown render pipeline + stats + import-export.js # File open/save, SharePoint/ADO import + pdf-export.js # PDF page-break + canvas logic + view-mode.js # View mode toggle + resize divider +vite.config.js # Vite build configuration +dist/ # Vite output (gitignored) +desktop-app/ # Thin Neutralinojs wrapper + prepare.js # Copies dist/ → resources/, injects Neutralino SDK + resources/js/desktop-main.js # Desktop-only: tray menu, window close +``` + +**Shared code model**: `desktop-app/prepare.js` copies Vite's `dist/` output at build time. Edit `web/src/` for any logic or UI change; never edit `desktop-app/resources/` directly. + +## Key Conventions + +- **ES modules** — Application logic is split across `web/src/` modules. Shared state in `state.js`, DOM refs in `dom.js`. +- **npm dependencies** — marked, highlight.js, mermaid, bootstrap, etc. are npm packages bundled by Vite. +- **MathJax on CDN** — Only exception; config-based loading is simpler than bundling. +- **Debouncing**: render is debounced 100 ms (`RENDER_DELAY`); scroll sync is debounced 10 ms. +- **View modes**: `'editor'`, `'split'`, `'preview'` — managed in `state.currentViewMode`. +- **Versioning**: Desktop uses CalVer `YYYY.M.P` (e.g., 2026.2.0), tagged as `desktop-vYYYY.M.P`. +- **Mobile breakpoint**: 768 px. Separate mobile hamburger menu; desktop has a toolbar. + +## Module Dependencies (no cycles) + +``` +preprocessors.js ← no internal deps +state.js ← no internal deps +dom.js ← no internal deps + +mermaid-utils.js ← state, dom +scroll-sync.js ← state, dom +view-mode.js ← state, dom, scroll-sync +render.js ← state, dom, preprocessors, mermaid-utils, scroll-sync +import-export.js ← state, dom, render +pdf-export.js ← state, dom, preprocessors + +main.js ← all of the above +``` + +## Deployment + +| Target | Trigger | Notes | +|--------|---------|-------| +| Vercel (web) | Push to `main` | Runs `npm run build`, serves `dist/` | +| GHCR Docker | Push to `main` | See `Dockerfile` / `docker-compose.yml` | +| GitHub Release | Tag `desktop-v*` | `.github/workflows/build-desktop.yml` | + +## Pitfalls + +- Neutralinojs binaries are gitignored (`desktop-app/bin/`). Run setup before first desktop build. +- macOS tray menu is intentionally disabled (upstream Neutralinojs bug #615). +- Security headers are set in both `vercel.json` and `Dockerfile` — keep them in sync when adding new CSP directives. +- Vite pre-bundles mermaid and dayjs to handle CommonJS interop — see `optimizeDeps` in `vite.config.js`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4d75389 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,80 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +# Web development +npm run dev # Vite dev server at http://localhost:5173 +npm run build # Vite production build → dist/ +npm run preview # Preview production build locally + +# Desktop development +npm run desktop:prepare # Build + copy dist/ into desktop-app/resources/ +npm run desktop:dev # Run Neutralinojs app with hot-reload +npm run desktop:build # Build cross-platform binaries (Windows .exe, Linux/macOS .tar.gz) +npm run desktop:install # Install desktop-app dependencies (run once after clone) +``` + +There is no test framework, linter, or TypeScript compiler in this project. + +## Architecture + +This is a **vanilla JS markdown editor** targeting two platforms from a single shared codebase: + +- **`web/`** — The canonical app (HTML + CSS + ES modules), deployed to Vercel +- **`desktop-app/`** — Neutralinojs wrapper; `prepare.js` copies `dist/` into `resources/` and injects the Neutralinojs SDK script tag into `index.html` at build time + +### Shared-code model + +`desktop-app/resources/` is never edited directly — it is always regenerated from `dist/` (Vite output) by `prepare.js`. All feature work happens in `web/src/`. + +### ES Modules structure (`web/src/`) + +Application logic is split into ES modules: + +| Module | Purpose | +|--------|---------| +| `main.js` | Entry point, event wiring, CSS imports | +| `state.js` | Shared mutable state + constants | +| `dom.js` | DOM element references (`initDom()` pattern) | +| `preprocessors.js` | Pure markdown transformers (ADO TOC, callouts) | +| `mermaid-utils.js` | Mermaid init / zoom / drag / export | +| `scroll-sync.js` | Anchor cache + interpolation + scroll handlers | +| `render.js` | Markdown render pipeline + document stats | +| `import-export.js` | File open/save, SharePoint/ADO import, HTML export | +| `pdf-export.js` | PDF page-break + canvas logic | +| `view-mode.js` | View mode toggle + resize divider + pane widths | + +Key subsystems: + +- **Rendering pipeline**: marked → highlight.js (syntax) + MathJax (LaTeX) + Mermaid (diagrams) + JoyPixels (emoji) → DOMPurify sanitize → inject into preview pane. Render is debounced 100 ms. +- **View modes**: `'editor'` / `'split'` / `'preview'` toggled via toolbar buttons (desktop) or hamburger menu (mobile). +- **Scroll sync** (split view): Anchor-based line-position cache + piecewise-linear interpolation maps editor scroll position to preview position. Sync is debounced 10 ms. +- **Export**: PDF via jsPDF + html2canvas; HTML/Markdown raw download via file-saver. +- **Platform detection**: Checks for `window.NL_VERSION` to enable desktop-only features (file open/save via Neutralino FS API, system tray via `desktop-app/resources/js/desktop-main.js`). + +### Dependencies + +Runtime dependencies are npm packages bundled by Vite: +- marked, highlight.js, mermaid, dompurify, file-saver, jspdf, html2canvas +- bootstrap, bootstrap-icons, github-markdown-css, emoji-toolkit + +**MathJax** remains on CDN (config-based loading is simpler than bundling). + +### Build tool + +**Vite** handles development server, ES module bundling, and production builds. Config in `vite.config.js`. + +### Deployment + +| Target | Trigger | Config | +|--------|---------|--------| +| Vercel (web) | Push to `main` | `vercel.json` | +| Docker | Push to `main` | `Dockerfile`, nginx security headers | +| GitHub Release (desktop) | Tag `desktop-v*` (CalVer, e.g. `desktop-v2026.2.0`) | `.github/workflows/build-desktop.yml` | + +### Theming + +CSS custom properties (`--bg-color`, `--text-color`, etc.) define the palette. The `[data-theme="dark"]` attribute on `` switches themes; the preference is persisted in `localStorage`. diff --git a/README.md b/README.md index 8008159..beae75c 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,12 @@ License + ## 🚀 Overview Markdown Viewer is a professional, full-featured Markdown editor and preview application that runs entirely in your browser. It provides a GitHub-style rendering experience with a clean split-screen interface, allowing you to write Markdown on one side and instantly preview the formatted output on the other. + ## ✨ Features - **GitHub-style Markdown rendering** - See your Markdown exactly as it would appear on GitHub @@ -56,6 +58,24 @@ Markdown Viewer is a professional, full-featured Markdown editor and preview app 5. **Toggle Dark Mode** - Click the moon icon to switch between light and dark themes 6. **Toggle Sync Scrolling** - Enable/disable synchronized scrolling between panels +## 💻 Run With npm + +From the project root: + +```bash +npm install +npm run dev +``` + +This starts a local server at `http://localhost:5173`. + +Desktop app shortcuts are also available from the root: + +```bash +npm run desktop:install +npm run desktop:dev +``` + ### Mermaid Diagram Toolbar When a Mermaid diagram is rendered, hover over it to reveal a small toolbar with the following actions: diff --git a/desktop-app/.dockerignore b/desktop-app/.dockerignore index 28f2c2a..6be7bc7 100644 --- a/desktop-app/.dockerignore +++ b/desktop-app/.dockerignore @@ -1,5 +1,6 @@ # Build-generated resources resources/js/script.js +resources/js/neutralino* resources/styles.css resources/assets/ resources/index.html diff --git a/desktop-app/.gitignore b/desktop-app/.gitignore index 4c77b9d..9fc6d7b 100644 --- a/desktop-app/.gitignore +++ b/desktop-app/.gitignore @@ -5,17 +5,16 @@ node_modules/ .lite_workspace.lua # Neutralinojs binaries and builds -/bin -/dist - -# Neutralinojs client (minified) -neutralino.js +bin/ +dist/ # Build-generated resources (copied from root by prepare.js) -/resources/js/script.js -/resources/styles.css -/resources/assets/ -/resources/index.html +resources/js/script.js +resources/js/neutralino* +resources/styles.css +resources/assets/ +resources/index.html + # Neutralinojs related files .storage @@ -25,4 +24,4 @@ neutralino.js .tmp # Docker build output -/output \ No newline at end of file +output/ \ No newline at end of file diff --git a/desktop-app/Dockerfile b/desktop-app/Dockerfile index 9c5f12d..1f613b1 100644 --- a/desktop-app/Dockerfile +++ b/desktop-app/Dockerfile @@ -13,7 +13,7 @@ COPY . . WORKDIR /app/desktop-app # Setup (download binaries + prepare resources) and build all variants -RUN npm run build:all +RUN npm run build # Final stage: Export the dist artifacts FROM alpine:latest diff --git a/desktop-app/README.md b/desktop-app/README.md index 210d881..a925842 100644 --- a/desktop-app/README.md +++ b/desktop-app/README.md @@ -11,9 +11,8 @@ Neutralinojs platform binaries are managed by `setup-binaries.js`, which downloa Desktop-only files (not generated): - `resources/js/main.js` — Neutralinojs lifecycle, tray menu, window events -- `resources/js/neutralino.js` — Neutralinojs client library - `neutralino.config.json` — App configuration -- `setup-binaries.js` — Idempotent binary setup (downloads on first use) +- `setup-binaries.js` — Idempotent binary setup (downloads on first use or updates if `cli.binaryVersion` changes) ## Development @@ -45,7 +44,7 @@ For more information, see the [Neutralinojs documentation](https://neutralino.js ### Building the app -**Default** — Single-file executables with embedded resources: +**Default** — Single-file executables with embedded resources + release ZIP bundle with separate `resources.neu` file: ```bash npm run build @@ -57,10 +56,10 @@ npm run build npm run build:portable ``` -**Both** — Build embedded + portable in one step: +**Embedded** — Single-file executables with embedded resources: ```bash -npm run build:all +npm run build:embedded ``` Build output is placed in `dist/`. @@ -79,7 +78,31 @@ Build artifacts will be output to `desktop-app/output/`. ## Releases -Prebuilt binaries are automatically built and published as GitHub Releases when a tag matching `desktop-v*` is pushed (e.g., `desktop-v1.0.0`). See [`.github/workflows/desktop-build.yml`](../.github/workflows/desktop-build.yml). +Prebuilt binaries are automatically built and published as GitHub Releases when a tag matching `desktop-v*` is pushed (e.g., `desktop-v2026.2.0`). See [`.github/workflows/desktop-build.yml`](../.github/workflows/desktop-build.yml). + +### Versioning + +The Git tag is the **single source of truth** for the release version, using CalVer (Calendar Versioning) format `desktop-vYYYY.M.P`; + +- `YYYY` = Year +- `M` = Month +- `P` = Patch (Defaults to 0, bumped if new release occurs same month) + +The CI workflow extracts the version from the tag (e.g., `desktop-v2026.2.0` → `2026.2.0`) and injects it into `neutralino.config.json` at build time. `package.json` carries a placeholder version (`0.0.0-dev`) since this is *not* an npm package. + +To create a release, you can use the utility script `tag.sh` to calculate the next [lightweight tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging): + +```bash +./tag.sh # Calculates the next tag based on the current date, latest tag, and commit SHA +``` + +or run the following commands, replacing `` with the desired version (e.g., `2026.2.1`): + +```bash +git tag desktop-v && git push origin desktop-v +``` + +### Release assets Each release includes: diff --git a/desktop-app/neutralino.config.json b/desktop-app/neutralino.config.json index cc55a4e..a2c7fca 100644 --- a/desktop-app/neutralino.config.json +++ b/desktop-app/neutralino.config.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/neutralinojs/neutralinojs/main/schemas/neutralino.config.schema.json", - "applicationId": "js.neutralino.sample", - "version": "1.0.0", + "applicationId": "js.markdownviewer.desktop", + "version": "2026.2.0", "defaultMode": "window", "port": 0, "documentRoot": "/resources/", @@ -13,7 +13,7 @@ "enabled": true, "writeToLogFile": true }, - "nativeAllowList": ["app.*", "os.*", "filesystem.readFile", "debug.log"], + "nativeAllowList": ["app.*", "os.*", "debug.log"], "globalVariables": {}, "modes": { "window": { diff --git a/desktop-app/package-lock.json b/desktop-app/package-lock.json index c95ab6d..e9d5d81 100644 --- a/desktop-app/package-lock.json +++ b/desktop-app/package-lock.json @@ -1,12 +1,13 @@ { "name": "markdown-viewer-desktop", - "version": "1.0.0", + "version": "0.0.0-dev", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "markdown-viewer-desktop", - "version": "1.0.0" + "version": "0.0.0-dev", + "license": "MIT" } } } diff --git a/desktop-app/package.json b/desktop-app/package.json index b02da6d..5ba9ba3 100644 --- a/desktop-app/package.json +++ b/desktop-app/package.json @@ -1,17 +1,29 @@ { "name": "markdown-viewer-desktop", - "version": "1.0.0", + "author": "ramezio", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/ramezio/markdown-viewer-fork.git" + }, + "contributors": [ + "ramezio", + "JBroeren", + "ThisIs-Developer" + ], + "version": "0.0.0-dev", "private": true, - "description": "Neutralinojs desktop port of Markdown Viewer", + "description": "Neutralinojs desktop port of Markdown Viewer (https://github.com/ThisIs-Developer/markdown-viewer)", "scripts": { "setup": "node setup-binaries.js", "postsetup": "node prepare.js", + "clean": "npx -y rimraf bin dist node_modules .tmp .neutralinojs.log resources/js/script.js resources/styles.css resources/assets resources/index.html resources/js/neutralino.js resources/js/neutralino.d.ts", "predev": "npm run setup", - "dev": "npx -y @neutralinojs/neu@11.7.0 run", + "dev": "npx -y @neutralinojs/neu@11.7.1 run", "prebuild": "npm run setup", - "build": "npx -y @neutralinojs/neu@11.7.0 build --embed-resources", - "build:portable": "npx -y @neutralinojs/neu@11.7.0 build --release", - "build:all": "npm run build && npm run build:portable" + "build": "npx -y @neutralinojs/neu@11.7.1 build --embed-resources --release", + "build:portable": "npm run setup && npx -y @neutralinojs/neu@11.7.1 build --release", + "build:embedded": "npm run setup && npx -y @neutralinojs/neu@11.7.1 build --embed-resources" }, "dependencies": {} } diff --git a/desktop-app/prepare.js b/desktop-app/prepare.js index 2e03333..ff560e8 100644 --- a/desktop-app/prepare.js +++ b/desktop-app/prepare.js @@ -1,86 +1,83 @@ -#!/usr/bin/env node - -/** - * prepare.js — Build script for the Neutralinojs desktop app. - * - * Copies shared browser-version files (script.js, styles.css, assets/) - * from the repo root into desktop-app/resources/, and generates a - * Neutralinojs-compatible index.html from the root index.html by - * injecting the required Neutralinojs script tags and wrapper elements. - * - * Run from the desktop-app/ directory: - * node prepare.js - */ - -const fs = require("fs"); -const path = require("path"); - -const ROOT_DIR = path.resolve(__dirname, ".."); -const RESOURCES_DIR = path.resolve(__dirname, "resources"); - -/** @section Copy shared files */ - -/** - * Recursively copy a directory, creating target dirs as needed. - */ -function copyDirSync(src, dest) { - fs.mkdirSync(dest, { recursive: true }); - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { - const srcPath = path.join(src, entry.name); - const destPath = path.join(dest, entry.name); - if (entry.isDirectory()) { - copyDirSync(srcPath, destPath); - } else { - fs.copyFileSync(srcPath, destPath); - } - } -} - -/** script.js → resources/js/script.js */ -const jsDest = path.join(RESOURCES_DIR, "js"); -fs.mkdirSync(jsDest, { recursive: true }); -fs.copyFileSync( - path.join(ROOT_DIR, "script.js"), - path.join(jsDest, "script.js"), -); -console.log("✓ Copied script.js → resources/js/script.js"); - -/** styles.css → resources/styles.css */ -fs.copyFileSync( - path.join(ROOT_DIR, "styles.css"), - path.join(RESOURCES_DIR, "styles.css"), -); -console.log("✓ Copied styles.css → resources/styles.css"); - -/** assets/ → resources/assets/ */ -copyDirSync(path.join(ROOT_DIR, "assets"), path.join(RESOURCES_DIR, "assets")); -console.log("✓ Copied assets/ → resources/assets/"); - -/** @section Generate index.html with Neutralinojs injections */ - -let html = fs.readFileSync(path.join(ROOT_DIR, "index.html"), "utf-8"); - -/** Fix relative asset paths → absolute (Neutralinojs documentRoot is /resources/) */ -html = html.replace(/href="assets\//g, 'href="/assets/'); -html = html.replace(/href="styles\.css"/g, 'href="/styles.css"'); -/** Replace root script.js tag with neutralino.js + main.js + script.js under /js/ */ -html = html.replace( - /\n \n ', -); - -/** Inject Neutralinojs app-info element after .app-container */ -html = html.replace( - '
', - `
-
-
-
`, -); - -fs.writeFileSync(path.join(RESOURCES_DIR, "index.html"), html, "utf-8"); -console.log( - "✓ Generated resources/index.html (Neutralinojs injections applied)", -); - -console.log("\nDone! Run `npm run dev` to start the desktop app."); +#!/usr/bin/env node + +/** + * prepare.js — Build script for the Neutralinojs desktop app. + * + * Copies the Vite build output (dist/) into desktop-app/resources/ and + * generates a Neutralinojs-compatible index.html by injecting the required + * Neutralinojs script tags. + * + * Must be run after `npm run build` (vite build) from the repo root. + * The desktop:prepare npm script does both steps in sequence. + */ + +const fs = require("fs"); +const path = require("path"); + +const DIST_DIR = path.resolve(__dirname, "../dist"); +const RESOURCES_DIR = path.resolve(__dirname, "resources"); + +if (!fs.existsSync(DIST_DIR)) { + console.error("✗ dist/ not found — run `npm run build` first"); + process.exit(1); +} + +/** Recursively copy a directory, creating target dirs as needed. */ +function copyDirSync(src, dest) { + fs.mkdirSync(dest, { recursive: true }); + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + if (entry.isDirectory()) { + copyDirSync(srcPath, destPath); + } else { + fs.copyFileSync(srcPath, destPath); + } + } +} + +/** Copy entire dist/ → resources/ */ +copyDirSync(DIST_DIR, RESOURCES_DIR); +console.log("✓ Copied dist/ → resources/"); + +/** @section Generate index.html with Neutralinojs injections */ + +const indexPath = path.join(RESOURCES_DIR, "index.html"); +let html = fs.readFileSync(indexPath, "utf-8"); +const originalHtml = html; + +/** + * Vite emits: + * Inject neutralino.js + desktop-main.js before it. + */ +const moduleScriptRegex = /(]*type="module"[^>]*src="\/js\/index\.js"[^>]*><\/script>)/; + +if (!moduleScriptRegex.test(html)) { + console.error("✗ Could not find Vite module script tag (/js/index.js) in resources/index.html"); + process.exit(1); +} + +html = html.replace( + moduleScriptRegex, + '\n \n $1', +); + +if (html === originalHtml) { + console.error("✗ No prepare.js transformations were applied"); + process.exit(1); +} + +fs.writeFileSync(indexPath, html, "utf-8"); +console.log("✓ Generated resources/index.html (Neutralinojs injections applied)"); + +/** @section Copy desktop-specific source files */ +const desktopSrcDir = path.resolve(__dirname, "src"); +const desktopMainSrc = path.join(desktopSrcDir, "desktop-main.js"); +const desktopMainDest = path.join(RESOURCES_DIR, "js", "desktop-main.js"); + +if (fs.existsSync(desktopMainSrc)) { + fs.copyFileSync(desktopMainSrc, desktopMainDest); + console.log("✓ Copied desktop-main.js to resources/js/"); +} + +console.log("\nDone! Run `npm run desktop:dev` to start the desktop app."); diff --git a/desktop-app/resources/assets/Black and Beige Simple Coming Soon Banner.png b/desktop-app/resources/assets/Black and Beige Simple Coming Soon Banner.png deleted file mode 100644 index 8ec1fc1..0000000 Binary files a/desktop-app/resources/assets/Black and Beige Simple Coming Soon Banner.png and /dev/null differ diff --git a/desktop-app/resources/assets/code.png b/desktop-app/resources/assets/code.png deleted file mode 100644 index 9473cb1..0000000 Binary files a/desktop-app/resources/assets/code.png and /dev/null differ diff --git a/desktop-app/resources/assets/github.png b/desktop-app/resources/assets/github.png deleted file mode 100644 index 0c2ee50..0000000 Binary files a/desktop-app/resources/assets/github.png and /dev/null differ diff --git a/desktop-app/resources/assets/icon.jpg b/desktop-app/resources/assets/icon.jpg deleted file mode 100644 index cdb8b4a..0000000 Binary files a/desktop-app/resources/assets/icon.jpg and /dev/null differ diff --git a/desktop-app/resources/assets/live-peview.gif b/desktop-app/resources/assets/live-peview.gif deleted file mode 100644 index 56edb86..0000000 Binary files a/desktop-app/resources/assets/live-peview.gif and /dev/null differ diff --git a/desktop-app/resources/assets/mathexp.png b/desktop-app/resources/assets/mathexp.png deleted file mode 100644 index 4731f6f..0000000 Binary files a/desktop-app/resources/assets/mathexp.png and /dev/null differ diff --git a/desktop-app/resources/assets/mermaid.png b/desktop-app/resources/assets/mermaid.png deleted file mode 100644 index 16323a4..0000000 Binary files a/desktop-app/resources/assets/mermaid.png and /dev/null differ diff --git a/desktop-app/resources/assets/table.png b/desktop-app/resources/assets/table.png deleted file mode 100644 index e0cca14..0000000 Binary files a/desktop-app/resources/assets/table.png and /dev/null differ diff --git a/desktop-app/resources/js/main.js b/desktop-app/resources/js/main.js deleted file mode 100644 index 70773eb..0000000 --- a/desktop-app/resources/js/main.js +++ /dev/null @@ -1,127 +0,0 @@ -// This is just a sample app. You can structure your Neutralinojs app code as you wish. -// This example app is written with vanilla JavaScript and HTML. -// Feel free to use any frontend framework you like :) -// See more details: https://neutralino.js.org/docs/how-to/use-a-frontend-library - -/* - Function to display information about the Neutralino app. - This function updates the content of the 'info' element in the HTML - with details regarding the running Neutralino application, including - its ID, port, operating system, and version information. -*/ -function showInfo() { - return ` - ${NL_APPID} is running on port ${NL_PORT} inside ${NL_OS} -

- server: v${NL_VERSION} . client: v${NL_CVERSION} - `; -} - -/* - Function to open the official Neutralino documentation in the default web browser. -*/ -function openDocs() { - Neutralino.os.open("https://neutralino.js.org/docs"); -} - -/* - Function to open a tutorial video on Neutralino's official YouTube channel in the default web browser. -*/ -function openTutorial() { - Neutralino.os.open("https://www.youtube.com/c/CodeZri"); -} - -/* - Function to set up a system tray menu with options specific to the window mode. - This function checks if the application is running in window mode, and if so, - it defines the tray menu items and sets up the tray accordingly. -*/ -function setTray() { - // Tray menu is only available in window mode - if (NL_MODE != "window") { - console.log("INFO: Tray menu is only available in the window mode."); - return; - } - - // Define tray menu items - let tray = { - icon: "/resources/icons/trayIcon.png", - menuItems: [ - { id: "VERSION", text: "Get version" }, - { id: "SEP", text: "-" }, - { id: "QUIT", text: "Quit" }, - ], - }; - - // Set the tray menu - Neutralino.os.setTray(tray); -} - -/* - Function to handle click events on the tray menu items. - This function performs different actions based on the clicked item's ID, - such as displaying version information or exiting the application. -*/ -function onTrayMenuItemClicked(event) { - switch (event.detail.id) { - case "VERSION": - // Display version information - Neutralino.os.showMessageBox( - "Version information", - `Neutralinojs server: v${NL_VERSION} | Neutralinojs client: v${NL_CVERSION}`, - ); - break; - case "QUIT": - // Exit the application - Neutralino.app.exit(); - break; - } -} - -/* - Function to handle the window close event by gracefully exiting the Neutralino application. -*/ -function onWindowClose() { - Neutralino.app.exit(); -} - -// Initialize Neutralino -Neutralino.init(); - -// Register event listeners -Neutralino.events.on("trayMenuItemClicked", onTrayMenuItemClicked); -Neutralino.events.on("windowClose", onWindowClose); - -// Conditional initialization: Set up system tray if not running on macOS -if (NL_OS != "Darwin") { - // TODO: Fix https://github.com/neutralinojs/neutralinojs/issues/615 - setTray(); -} - -// Open file passed as command-line argument (e.g. when double-clicking a .md file) -(async function loadInitialFile() { - const args = Array.isArray(NL_ARGS) ? NL_ARGS : (() => { try { return JSON.parse(NL_ARGS); } catch(e) { return []; } })(); - const filePath = args.find(a => typeof a === 'string' && /\.(md|markdown)$/i.test(a)); - if (!filePath) return; - - try { - const content = await Neutralino.filesystem.readFile(filePath); - - function applyContent() { - const editor = document.getElementById('markdown-editor'); - const dropzone = document.getElementById('dropzone'); - if (!editor) return; - editor.value = content; - editor.dispatchEvent(new Event('input')); - if (dropzone) dropzone.style.display = 'none'; - } - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', applyContent); - } else { - setTimeout(applyContent, 0); - } - } catch (e) { - console.warn('Could not open initial file:', e); - } -})(); diff --git a/desktop-app/resources/js/neutralino.d.ts b/desktop-app/resources/js/neutralino.d.ts deleted file mode 100644 index 09d69e6..0000000 --- a/desktop-app/resources/js/neutralino.d.ts +++ /dev/null @@ -1,531 +0,0 @@ -export declare enum LoggerType { - WARNING = "WARNING", - ERROR = "ERROR", - INFO = "INFO" -} -export declare enum Icon { - WARNING = "WARNING", - ERROR = "ERROR", - INFO = "INFO", - QUESTION = "QUESTION" -} -export declare enum MessageBoxChoice { - OK = "OK", - OK_CANCEL = "OK_CANCEL", - YES_NO = "YES_NO", - YES_NO_CANCEL = "YES_NO_CANCEL", - RETRY_CANCEL = "RETRY_CANCEL", - ABORT_RETRY_IGNORE = "ABORT_RETRY_IGNORE" -} -export declare enum ClipboardFormat { - unknown = "unknown", - text = "text", - image = "image" -} -export declare enum Mode { - window = "window", - browser = "browser", - cloud = "cloud", - chrome = "chrome" -} -export declare enum OperatingSystem { - Linux = "Linux", - Windows = "Windows", - Darwin = "Darwin", - FreeBSD = "FreeBSD", - Unknown = "Unknown" -} -export declare enum Architecture { - x64 = "x64", - arm = "arm", - itanium = "itanium", - ia32 = "ia32", - unknown = "unknown" -} -export interface DirectoryEntry { - entry: string; - path: string; - type: string; -} -export interface FileReaderOptions { - pos: number; - size: number; -} -export interface DirectoryReaderOptions { - recursive: boolean; -} -export interface OpenedFile { - id: number; - eof: boolean; - pos: number; - lastRead: number; -} -export interface Stats { - size: number; - isFile: boolean; - isDirectory: boolean; - createdAt: number; - modifiedAt: number; -} -export interface Watcher { - id: number; - path: string; -} -export interface CopyOptions { - recursive: boolean; - overwrite: boolean; - skip: boolean; -} -export interface PathParts { - rootName: string; - rootDirectory: string; - rootPath: string; - relativePath: string; - parentPath: string; - filename: string; - stem: string; - extension: string; -} -interface Permissions$1 { - all: boolean; - ownerAll: boolean; - ownerRead: boolean; - ownerWrite: boolean; - ownerExec: boolean; - groupAll: boolean; - groupRead: boolean; - groupWrite: boolean; - groupExec: boolean; - othersAll: boolean; - othersRead: boolean; - othersWrite: boolean; - othersExec: boolean; -} -export type PermissionsMode = "ADD" | "REPLACE" | "REMOVE"; -declare function createDirectory(path: string): Promise; -declare function remove(path: string): Promise; -declare function writeFile(path: string, data: string): Promise; -declare function appendFile(path: string, data: string): Promise; -declare function writeBinaryFile(path: string, data: ArrayBuffer): Promise; -declare function appendBinaryFile(path: string, data: ArrayBuffer): Promise; -declare function readFile(path: string, options?: FileReaderOptions): Promise; -declare function readBinaryFile(path: string, options?: FileReaderOptions): Promise; -declare function openFile(path: string): Promise; -declare function createWatcher(path: string): Promise; -declare function removeWatcher(id: number): Promise; -declare function getWatchers(): Promise; -declare function updateOpenedFile(id: number, event: string, data?: any): Promise; -declare function getOpenedFileInfo(id: number): Promise; -declare function readDirectory(path: string, options?: DirectoryReaderOptions): Promise; -declare function copy(source: string, destination: string, options?: CopyOptions): Promise; -declare function move(source: string, destination: string): Promise; -declare function getStats(path: string): Promise; -declare function getAbsolutePath(path: string): Promise; -declare function getRelativePath(path: string, base?: string): Promise; -declare function getPathParts(path: string): Promise; -declare function getPermissions(path: string): Promise; -declare function setPermissions(path: string, permissions: Permissions$1, mode: PermissionsMode): Promise; -declare function getJoinedPath(...paths: string[]): Promise; -declare function getNormalizedPath(path: string): Promise; -declare function getUnnormalizedPath(path: string): Promise; -export interface ExecCommandOptions { - stdIn?: string; - background?: boolean; - cwd?: string; -} -export interface ExecCommandResult { - pid: number; - stdOut: string; - stdErr: string; - exitCode: number; -} -export interface SpawnedProcess { - id: number; - pid: number; -} -export interface SpawnedProcessOptions { - cwd?: string; - envs?: Record; -} -export interface Envs { - [key: string]: string; -} -export interface OpenDialogOptions { - multiSelections?: boolean; - filters?: Filter[]; - defaultPath?: string; -} -export interface FolderDialogOptions { - defaultPath?: string; -} -export interface SaveDialogOptions { - forceOverwrite?: boolean; - filters?: Filter[]; - defaultPath?: string; -} -export interface Filter { - name: string; - extensions: string[]; -} -export interface TrayOptions { - icon: string; - menuItems: TrayMenuItem[]; -} -export interface TrayMenuItem { - id?: string; - text: string; - isDisabled?: boolean; - isChecked?: boolean; -} -export type KnownPath = "config" | "data" | "cache" | "documents" | "pictures" | "music" | "video" | "downloads" | "savedGames1" | "savedGames2" | "temp"; -declare function execCommand(command: string, options?: ExecCommandOptions): Promise; -declare function spawnProcess(command: string, options?: SpawnedProcessOptions): Promise; -declare function updateSpawnedProcess(id: number, event: string, data?: any): Promise; -declare function getSpawnedProcesses(): Promise; -declare function getEnv(key: string): Promise; -declare function getEnvs(): Promise; -declare function showOpenDialog(title?: string, options?: OpenDialogOptions): Promise; -declare function showFolderDialog(title?: string, options?: FolderDialogOptions): Promise; -declare function showSaveDialog(title?: string, options?: SaveDialogOptions): Promise; -declare function showNotification(title: string, content: string, icon?: Icon): Promise; -declare function showMessageBox(title: string, content: string, choice?: MessageBoxChoice, icon?: Icon): Promise; -declare function setTray(options: TrayOptions): Promise; -declare function open$1(url: string): Promise; -declare function getPath(name: KnownPath): Promise; -export interface MemoryInfo { - physical: { - total: number; - available: number; - }; - virtual: { - total: number; - available: number; - }; -} -export interface KernelInfo { - variant: string; - version: string; -} -export interface OSInfo { - name: string; - description: string; - version: string; -} -export interface CPUInfo { - vendor: string; - model: string; - frequency: number; - architecture: string; - logicalThreads: number; - physicalCores: number; - physicalUnits: number; -} -export interface Display { - id: number; - resolution: Resolution; - dpi: number; - bpp: number; - refreshRate: number; -} -export interface Resolution { - width: number; - height: number; -} -export interface MousePosition { - x: number; - y: number; -} -declare function getMemoryInfo(): Promise; -declare function getArch(): Promise; -declare function getKernelInfo(): Promise; -declare function getOSInfo(): Promise; -declare function getCPUInfo(): Promise; -declare function getDisplays(): Promise; -declare function getMousePosition(): Promise; -declare function setData(key: string, data: string | null): Promise; -declare function getData(key: string): Promise; -declare function removeData(key: string): Promise; -declare function getKeys(): Promise; -declare function clear(): Promise; -declare function log(message: string, type?: LoggerType): Promise; -export interface OpenActionOptions { - url: string; -} -export interface RestartOptions { - args: string; -} -declare function exit(code?: number): Promise; -declare function killProcess(): Promise; -declare function restartProcess(options?: RestartOptions): Promise; -declare function getConfig(): Promise; -declare function broadcast(event: string, data?: any): Promise; -declare function readProcessInput(readAll?: boolean): Promise; -declare function writeProcessOutput(data: string): Promise; -declare function writeProcessError(data: string): Promise; -export interface WindowOptions extends WindowSizeOptions, WindowPosOptions { - title?: string; - icon?: string; - fullScreen?: boolean; - alwaysOnTop?: boolean; - enableInspector?: boolean; - borderless?: boolean; - maximize?: boolean; - hidden?: boolean; - maximizable?: boolean; - useSavedState?: boolean; - exitProcessOnClose?: boolean; - extendUserAgentWith?: string; - injectGlobals?: boolean; - injectClientLibrary?: boolean; - injectScript?: string; - processArgs?: string; -} -export interface WindowSizeOptions { - width?: number; - height?: number; - minWidth?: number; - minHeight?: number; - maxWidth?: number; - maxHeight?: number; - resizable?: boolean; -} -export interface WindowPosOptions { - x?: number; - y?: number; - center?: boolean; -} -export interface WindowMenu extends Array { -} -export interface WindowMenuItem { - id?: string; - text: string; - action?: string; - shortcut?: string; - isDisabled?: boolean; - isChecked?: boolean; - menuItems?: WindowMenuItem[]; -} -declare function setTitle(title: string): Promise; -declare function getTitle(): Promise; -declare function maximize(): Promise; -declare function unmaximize(): Promise; -declare function isMaximized(): Promise; -declare function minimize(): Promise; -declare function unminimize(): Promise; -declare function isMinimized(): Promise; -declare function setFullScreen(): Promise; -declare function exitFullScreen(): Promise; -declare function isFullScreen(): Promise; -declare function show(): Promise; -declare function hide(): Promise; -declare function isVisible(): Promise; -declare function focus$1(): Promise; -declare function setIcon(icon: string): Promise; -declare function move$1(x: number, y: number): Promise; -declare function center(): Promise; -declare function beginDrag(screenX?: number, screenY?: number): Promise; -declare function setDraggableRegion(DOMElementOrId: string | HTMLElement, options?: { - exclude?: Array; -}): Promise<{ - success: true; - message: string; - exclusions: { - add(elements: Array): void; - remove(elements: Array): void; - removeAll(): void; - }; -}>; -declare function unsetDraggableRegion(DOMElementOrId: string | HTMLElement): Promise<{ - success: true; - message: string; -}>; -declare function setSize(options: WindowSizeOptions): Promise; -declare function getSize(): Promise; -declare function getPosition(): Promise; -declare function setAlwaysOnTop(onTop: boolean): Promise; -declare function setBorderless(borderless: boolean): Promise; -declare function create(url: string, options?: WindowOptions): Promise; -declare function snapshot(path: string): Promise; -declare function setMainMenu(options: WindowMenu): Promise; -declare function print$1(): Promise; -interface Response$1 { - success: boolean; - message: string; -} -export type Builtin = "ready" | "trayMenuItemClicked" | "windowClose" | "serverOffline" | "clientConnect" | "clientDisconnect" | "appClientConnect" | "appClientDisconnect" | "extClientConnect" | "extClientDisconnect" | "extensionReady" | "neuDev_reloadApp"; -declare function on(event: string, handler: (ev: CustomEvent) => void): Promise; -declare function off(event: string, handler: (ev: CustomEvent) => void): Promise; -declare function dispatch(event: string, data?: any): Promise; -declare function broadcast$1(event: string, data?: any): Promise; -export interface ExtensionStats { - loaded: string[]; - connected: string[]; -} -declare function dispatch$1(extensionId: string, event: string, data?: any): Promise; -declare function broadcast$2(event: string, data?: any): Promise; -declare function getStats$1(): Promise; -export interface Manifest { - applicationId: string; - version: string; - resourcesURL: string; -} -declare function checkForUpdates(url: string): Promise; -declare function install(): Promise; -export interface ClipboardImage { - width: number; - height: number; - bpp: number; - bpr: number; - redMask: number; - greenMask: number; - blueMask: number; - redShift: number; - greenShift: number; - blueShift: number; - data: ArrayBuffer; -} -declare function getFormat(): Promise; -declare function readText(): Promise; -declare function readImage(format?: string): Promise; -declare function writeText(data: string): Promise; -declare function writeImage(image: ClipboardImage): Promise; -declare function readHTML(): Promise; -declare function writeHTML(data: string): Promise; -declare function clear$1(): Promise; -interface Stats$1 { - size: number; - isFile: boolean; - isDirectory: boolean; -} -declare function getFiles(): Promise; -declare function getStats$2(path: string): Promise; -declare function extractFile(path: string, destination: string): Promise; -declare function extractDirectory(path: string, destination: string): Promise; -declare function readFile$1(path: string): Promise; -declare function readBinaryFile$1(path: string): Promise; -declare function mount(path: string, target: string): Promise; -declare function unmount(path: string): Promise; -declare function getMounts(): Promise>; -declare function getMethods(): Promise; -export interface InitOptions { - exportCustomMethods?: boolean; -} -export declare function init(options?: InitOptions): void; -export type ErrorCode = "NE_FS_DIRCRER" | "NE_FS_RMDIRER" | "NE_FS_FILRDER" | "NE_FS_FILWRER" | "NE_FS_FILRMER" | "NE_FS_NOPATHE" | "NE_FS_COPYFER" | "NE_FS_MOVEFER" | "NE_OS_INVMSGA" | "NE_OS_INVKNPT" | "NE_ST_INVSTKY" | "NE_ST_STKEYWE" | "NE_RT_INVTOKN" | "NE_RT_NATPRME" | "NE_RT_APIPRME" | "NE_RT_NATRTER" | "NE_RT_NATNTIM" | "NE_CL_NSEROFF" | "NE_EX_EXTNOTC" | "NE_UP_CUPDMER" | "NE_UP_CUPDERR" | "NE_UP_UPDNOUF" | "NE_UP_UPDINER"; -interface Error$1 { - code: ErrorCode; - message: string; -} -declare global { - interface Window { - /** Mode of the application: window, browser, cloud, or chrome */ - NL_MODE: Mode; - /** Application port */ - NL_PORT: number; - /** Command-line arguments */ - NL_ARGS: string[]; - /** Basic authentication token */ - NL_TOKEN: string; - /** Neutralinojs client version */ - NL_CVERSION: string; - /** Application identifier */ - NL_APPID: string; - /** Application version */ - NL_APPVERSION: string; - /** Application path */ - NL_PATH: string; - /** Application data path */ - NL_DATAPATH: string; - /** Returns true if extensions are enabled */ - NL_EXTENABLED: boolean; - /** Returns true if the client library is injected */ - NL_GINJECTED: boolean; - /** Returns true if globals are injected */ - NL_CINJECTED: boolean; - /** Operating system name: Linux, Windows, Darwin, FreeBSD, or Uknown */ - NL_OS: OperatingSystem; - /** CPU architecture: x64, arm, itanium, ia32, or unknown */ - NL_ARCH: Architecture; - /** Neutralinojs server version */ - NL_VERSION: string; - /** Current working directory */ - NL_CWD: string; - /** Identifier of the current process */ - NL_PID: string; - /** Source of application resources: bundle or directory */ - NL_RESMODE: string; - /** Release commit of the client library */ - NL_CCOMMIT: string; - /** An array of custom methods */ - NL_CMETHODS: string[]; - } - /** Neutralino global object for custom methods **/ - const Neutralino: any; -} - -declare namespace custom { - export { getMethods }; -} -declare namespace filesystem { - export { appendBinaryFile, appendFile, copy, createDirectory, createWatcher, getAbsolutePath, getJoinedPath, getNormalizedPath, getOpenedFileInfo, getPathParts, getPermissions, getRelativePath, getStats, getUnnormalizedPath, getWatchers, move, openFile, readBinaryFile, readDirectory, readFile, remove, removeWatcher, setPermissions, updateOpenedFile, writeBinaryFile, writeFile }; -} -declare namespace os { - export { execCommand, getEnv, getEnvs, getPath, getSpawnedProcesses, open$1 as open, setTray, showFolderDialog, showMessageBox, showNotification, showOpenDialog, showSaveDialog, spawnProcess, updateSpawnedProcess }; -} -declare namespace computer { - export { getArch, getCPUInfo, getDisplays, getKernelInfo, getMemoryInfo, getMousePosition, getOSInfo }; -} -declare namespace storage { - export { clear, getData, getKeys, removeData, setData }; -} -declare namespace debug { - export { log }; -} -declare namespace app { - export { broadcast, exit, getConfig, killProcess, readProcessInput, restartProcess, writeProcessError, writeProcessOutput }; -} -declare namespace window$1 { - export { beginDrag, center, create, exitFullScreen, focus$1 as focus, getPosition, getSize, getTitle, hide, isFullScreen, isMaximized, isMinimized, isVisible, maximize, minimize, move$1 as move, print$1 as print, setAlwaysOnTop, setBorderless, setDraggableRegion, setFullScreen, setIcon, setMainMenu, setSize, setTitle, show, snapshot, unmaximize, unminimize, unsetDraggableRegion }; -} -declare namespace events { - export { broadcast$1 as broadcast, dispatch, off, on }; -} -declare namespace extensions { - export { broadcast$2 as broadcast, dispatch$1 as dispatch, getStats$1 as getStats }; -} -declare namespace updater { - export { checkForUpdates, install }; -} -declare namespace clipboard { - export { clear$1 as clear, getFormat, readHTML, readImage, readText, writeHTML, writeImage, writeText }; -} -declare namespace resources { - export { extractDirectory, extractFile, getFiles, getStats$2 as getStats, readBinaryFile$1 as readBinaryFile, readFile$1 as readFile }; -} -declare namespace server { - export { getMounts, mount, unmount }; -} - -export { - Error$1 as Error, - Permissions$1 as Permissions, - Response$1 as Response, - app, - clipboard, - computer, - custom, - debug, - events, - extensions, - filesystem, - os, - resources, - server, - storage, - updater, - window$1 as window, -}; - -export as namespace Neutralino; - -export {}; diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js deleted file mode 100644 index 7ee9126..0000000 --- a/desktop-app/resources/js/script.js +++ /dev/null @@ -1,2835 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - let markdownRenderTimeout = null; - const RENDER_DELAY = 100; - let syncScrollingEnabled = true; - let isEditorScrolling = false; - let isPreviewScrolling = false; - let scrollSyncTimeout = null; - const SCROLL_SYNC_DELAY = 10; - - // View Mode State - Story 1.1 - let currentViewMode = 'split'; // 'editor', 'split', or 'preview' - - const markdownEditor = document.getElementById("markdown-editor"); - const markdownPreview = document.getElementById("markdown-preview"); - const themeToggle = document.getElementById("theme-toggle"); - const importFromFileButton = document.getElementById("import-from-file"); - const importFromGithubButton = document.getElementById("import-from-github"); - const fileInput = document.getElementById("file-input"); - const exportMd = document.getElementById("export-md"); - const exportHtml = document.getElementById("export-html"); - const exportPdf = document.getElementById("export-pdf"); - const copyMarkdownButton = document.getElementById("copy-markdown-button"); - const dropzone = document.getElementById("dropzone"); - const closeDropzoneBtn = document.getElementById("close-dropzone"); - const toggleSyncButton = document.getElementById("toggle-sync"); - const editorPane = document.getElementById("markdown-editor"); - const previewPane = document.querySelector(".preview-pane"); - const readingTimeElement = document.getElementById("reading-time"); - const wordCountElement = document.getElementById("word-count"); - const charCountElement = document.getElementById("char-count"); - - // View Mode Elements - Story 1.1 - const contentContainer = document.querySelector(".content-container"); - const viewModeButtons = document.querySelectorAll(".view-mode-btn"); - - // Mobile View Mode Elements - Story 1.4 - const mobileViewModeButtons = document.querySelectorAll(".mobile-view-mode-btn"); - - // Resize Divider Elements - Story 1.3 - const resizeDivider = document.querySelector(".resize-divider"); - const editorPaneElement = document.querySelector(".editor-pane"); - const previewPaneElement = document.querySelector(".preview-pane"); - let isResizing = false; - let editorWidthPercent = 50; // Default 50% - const MIN_PANE_PERCENT = 20; // Minimum 20% width - - const mobileMenuToggle = document.getElementById("mobile-menu-toggle"); - const mobileMenuPanel = document.getElementById("mobile-menu-panel"); - const mobileMenuOverlay = document.getElementById("mobile-menu-overlay"); - const mobileCloseMenu = document.getElementById("close-mobile-menu"); - const mobileReadingTime = document.getElementById("mobile-reading-time"); - const mobileWordCount = document.getElementById("mobile-word-count"); - const mobileCharCount = document.getElementById("mobile-char-count"); - const mobileToggleSync = document.getElementById("mobile-toggle-sync"); - const mobileImportBtn = document.getElementById("mobile-import-button"); - const mobileImportGithubBtn = document.getElementById("mobile-import-github-button"); - const mobileExportMd = document.getElementById("mobile-export-md"); - const mobileExportHtml = document.getElementById("mobile-export-html"); - const mobileExportPdf = document.getElementById("mobile-export-pdf"); - const mobileCopyMarkdown = document.getElementById("mobile-copy-markdown"); - const mobileThemeToggle = document.getElementById("mobile-theme-toggle"); - const shareButton = document.getElementById("share-button"); - const mobileShareButton = document.getElementById("mobile-share-button"); - const githubImportModal = document.getElementById("github-import-modal"); - const githubImportTitle = document.getElementById("github-import-title"); - const githubImportUrlInput = document.getElementById("github-import-url"); - const githubImportFileSelect = document.getElementById("github-import-file-select"); - const githubImportError = document.getElementById("github-import-error"); - const githubImportCancelBtn = document.getElementById("github-import-cancel"); - const githubImportSubmitBtn = document.getElementById("github-import-submit"); - - // Check dark mode preference first for proper initialization - const prefersDarkMode = - window.matchMedia && - window.matchMedia("(prefers-color-scheme: dark)").matches; - - document.documentElement.setAttribute( - "data-theme", - prefersDarkMode ? "dark" : "light" - ); - - themeToggle.innerHTML = prefersDarkMode - ? '' - : ''; - - const initMermaid = () => { - const currentTheme = document.documentElement.getAttribute("data-theme"); - const mermaidTheme = currentTheme === "dark" ? "dark" : "default"; - - mermaid.initialize({ - startOnLoad: false, - theme: mermaidTheme, - securityLevel: 'loose', - flowchart: { useMaxWidth: true, htmlLabels: true }, - fontSize: 16 - }); - }; - - try { - initMermaid(); - } catch (e) { - console.warn("Mermaid initialization failed:", e); - } - - const markedOptions = { - gfm: true, - breaks: false, - pedantic: false, - sanitize: false, - smartypants: false, - xhtml: false, - headerIds: true, - mangle: false, - }; - - const renderer = new marked.Renderer(); - renderer.code = function (code, language) { - if (language === 'mermaid') { - const uniqueId = 'mermaid-diagram-' + Math.random().toString(36).substr(2, 9); - return `
${code}
`; - } - - const validLanguage = hljs.getLanguage(language) ? language : "plaintext"; - const highlightedCode = hljs.highlight(code, { - language: validLanguage, - }).value; - return `
${highlightedCode}
`; - }; - - marked.setOptions({ - ...markedOptions, - renderer: renderer, - }); - - const sampleMarkdown = `# Welcome to Markdown Viewer - -## ✨ Key Features -- **Live Preview** with GitHub styling -- **Smart Import/Export** (MD, HTML, PDF) -- **Mermaid Diagrams** for visual documentation -- **LaTeX Math Support** for scientific notation -- **Emoji Support** 😄 👍 🎉 - -## 💻 Code with Syntax Highlighting -\`\`\`javascript - function renderMarkdown() { - const markdown = markdownEditor.value; - const html = marked.parse(markdown); - const sanitizedHtml = DOMPurify.sanitize(html); - markdownPreview.innerHTML = sanitizedHtml; - - // Syntax highlighting is handled automatically - // during the parsing phase by the marked renderer. - // Themes are applied instantly via CSS variables. - } -\`\`\` - -## 🧮 Mathematical Expressions -Write complex formulas with LaTeX syntax: - -Inline equation: $$E = mc^2$$ - -Display equations: -$$\\frac{\\partial f}{\\partial x} = \\lim_{h \\to 0} \\frac{f(x+h) - f(x)}{h}$$ - -$$\\sum_{i=1}^{n} i^2 = \\frac{n(n+1)(2n+1)}{6}$$ - -## 📊 Mermaid Diagrams -Create powerful visualizations directly in markdown: - -\`\`\`mermaid -flowchart LR - A[Start] --> B{Is it working?} - B -->|Yes| C[Great!] - B -->|No| D[Debug] - C --> E[Deploy] - D --> B -\`\`\` - -### Sequence Diagram Example -\`\`\`mermaid -sequenceDiagram - User->>Editor: Type markdown - Editor->>Preview: Render content - User->>Editor: Make changes - Editor->>Preview: Update rendering - User->>Export: Save as PDF -\`\`\` - -## 📋 Task Management -- [x] Create responsive layout -- [x] Implement live preview with GitHub styling -- [x] Add syntax highlighting for code blocks -- [x] Support math expressions with LaTeX -- [x] Enable mermaid diagrams - -## 🆚 Feature Comparison - -| Feature | Markdown Viewer (Ours) | Other Markdown Editors | -|:-------------------------|:----------------------:|:-----------------------:| -| Live Preview | ✅ GitHub-Styled | ✅ | -| Sync Scrolling | ✅ Two-way | 🔄 Partial/None | -| Mermaid Support | ✅ | ❌/Limited | -| LaTeX Math Rendering | ✅ | ❌/Limited | - -### 📝 Multi-row Headers Support - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Document TypeSupport
Markdown Viewer (Ours)Other Markdown Editors
Technical DocsFull + DiagramsLimited/Basic
Research NotesFull + MathPartial
Developer GuidesFull + Export OptionsBasic
- -## 📝 Text Formatting Examples - -### Text Formatting - -Text can be formatted in various ways for ~~strikethrough~~, **bold**, *italic*, or ***bold italic***. - -For highlighting important information, use highlighted text or add underlines where appropriate. - -### Superscript and Subscript - -Chemical formulas: H2O, CO2 -Mathematical notation: x2, e - -### Keyboard Keys - -Press Ctrl + B for bold text. - -### Abbreviations - -GUI -API - -### Text Alignment - -
-Centered text for headings or important notices -
- -
-Right-aligned text (for dates, signatures, etc.) -
- -### **Lists** - -Create bullet points: -* Item 1 -* Item 2 - * Nested item - * Nested further - -### **Links and Images** - -Add a [link](https://github.com/ThisIs-Developer/Markdown-Viewer) to important resources. - -Embed an image: -![Markdown Logo](https://markdownviewer.pages.dev/assets/icon.jpg) - -### **Blockquotes** - -Quote someone famous: -> "The best way to predict the future is to invent it." - Alan Kay - ---- - -## 🛡️ Security Note - -This is a fully client-side application. Your content never leaves your browser and stays secure on your device.`; - - markdownEditor.value = sampleMarkdown; - - // ======================================== - // DOCUMENT TABS & SESSION MANAGEMENT - // ======================================== - - const STORAGE_KEY = 'markdownViewerTabs'; - const ACTIVE_TAB_KEY = 'markdownViewerActiveTab'; - const UNTITLED_COUNTER_KEY = 'markdownViewerUntitledCounter'; - let tabs = []; - let activeTabId = null; - let draggedTabId = null; - let saveTabStateTimeout = null; - let untitledCounter = 0; - - function loadTabsFromStorage() { - try { - return JSON.parse(localStorage.getItem(STORAGE_KEY)) || []; - } catch (e) { - return []; - } - } - - function saveTabsToStorage(tabsArr) { - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(tabsArr)); - } catch (e) { - console.warn('Failed to save tabs to localStorage:', e); - } - } - - function loadActiveTabId() { - return localStorage.getItem(ACTIVE_TAB_KEY); - } - - function saveActiveTabId(id) { - localStorage.setItem(ACTIVE_TAB_KEY, id); - } - - function loadUntitledCounter() { - return parseInt(localStorage.getItem(UNTITLED_COUNTER_KEY) || '0', 10); - } - - function saveUntitledCounter(val) { - localStorage.setItem(UNTITLED_COUNTER_KEY, String(val)); - } - - function nextUntitledTitle() { - untitledCounter += 1; - saveUntitledCounter(untitledCounter); - return 'Untitled ' + untitledCounter; - } - - function createTab(content, title, viewMode) { - if (content === undefined) content = ''; - if (title === undefined) title = null; - if (viewMode === undefined) viewMode = 'split'; - return { - id: 'tab_' + Date.now() + '_' + Math.random().toString(36).substring(2, 8), - title: title || 'Untitled', - content: content, - scrollPos: 0, - viewMode: viewMode, - createdAt: Date.now() - }; - } - - function renderTabBar(tabsArr, currentActiveTabId) { - const tabList = document.getElementById('tab-list'); - if (!tabList) return; - tabList.innerHTML = ''; - tabsArr.forEach(function(tab) { - const item = document.createElement('div'); - item.className = 'tab-item' + (tab.id === currentActiveTabId ? ' active' : ''); - item.setAttribute('data-tab-id', tab.id); - item.setAttribute('role', 'tab'); - item.setAttribute('aria-selected', tab.id === currentActiveTabId ? 'true' : 'false'); - item.setAttribute('draggable', 'true'); - - const titleSpan = document.createElement('span'); - titleSpan.className = 'tab-title'; - titleSpan.textContent = tab.title || 'Untitled'; - titleSpan.title = tab.title || 'Untitled'; - - // Three-dot menu button - const menuBtn = document.createElement('button'); - menuBtn.className = 'tab-menu-btn'; - menuBtn.setAttribute('aria-label', 'File options'); - menuBtn.title = 'File options'; - menuBtn.innerHTML = '⋯'; - - // Dropdown - const dropdown = document.createElement('div'); - dropdown.className = 'tab-menu-dropdown'; - dropdown.innerHTML = - '' + - '' + - ''; - - menuBtn.appendChild(dropdown); - - menuBtn.addEventListener('click', function(e) { - e.stopPropagation(); - // Close all other open dropdowns first - document.querySelectorAll('.tab-menu-btn.open').forEach(function(btn) { - if (btn !== menuBtn) btn.classList.remove('open'); - }); - menuBtn.classList.toggle('open'); - // Position the dropdown relative to the viewport so it escapes the - // overflow scroll container on .tab-list - if (menuBtn.classList.contains('open')) { - var rect = menuBtn.getBoundingClientRect(); - dropdown.style.top = (rect.bottom + 4) + 'px'; - dropdown.style.right = (window.innerWidth - rect.right) + 'px'; - dropdown.style.left = 'auto'; - } - }); - - dropdown.querySelectorAll('.tab-menu-item').forEach(function(actionBtn) { - actionBtn.addEventListener('click', function(e) { - e.stopPropagation(); - menuBtn.classList.remove('open'); - const action = actionBtn.getAttribute('data-action'); - if (action === 'rename') renameTab(tab.id); - else if (action === 'duplicate') duplicateTab(tab.id); - else if (action === 'delete') deleteTab(tab.id); - }); - }); - - item.appendChild(titleSpan); - item.appendChild(menuBtn); - - item.addEventListener('click', function() { - switchTab(tab.id); - }); - - item.addEventListener('dragstart', function() { - draggedTabId = tab.id; - setTimeout(function() { item.classList.add('dragging'); }, 0); - }); - - item.addEventListener('dragend', function() { - item.classList.remove('dragging'); - draggedTabId = null; - }); - - item.addEventListener('dragover', function(e) { - e.preventDefault(); - item.classList.add('drag-over'); - }); - - item.addEventListener('dragleave', function() { - item.classList.remove('drag-over'); - }); - - item.addEventListener('drop', function(e) { - e.preventDefault(); - item.classList.remove('drag-over'); - if (!draggedTabId || draggedTabId === tab.id) return; - const fromIdx = tabs.findIndex(function(t) { return t.id === draggedTabId; }); - const toIdx = tabs.findIndex(function(t) { return t.id === tab.id; }); - if (fromIdx === -1 || toIdx === -1) return; - const moved = tabs.splice(fromIdx, 1)[0]; - tabs.splice(toIdx, 0, moved); - saveTabsToStorage(tabs); - renderTabBar(tabs, activeTabId); - }); - - tabList.appendChild(item); - }); - - // "+ Create" button at end of tab list - const newBtn = document.createElement('button'); - newBtn.className = 'tab-new-btn'; - newBtn.title = 'New Tab (Ctrl+T)'; - newBtn.setAttribute('aria-label', 'Open new tab'); - newBtn.innerHTML = ''; - newBtn.addEventListener('click', function() { newTab(); }); - tabList.appendChild(newBtn); - - // Auto-scroll active tab into view - const activeItem = tabList.querySelector('.tab-item.active'); - if (activeItem) { - activeItem.scrollIntoView({ block: 'nearest', inline: 'nearest' }); - } - - renderMobileTabList(tabsArr, currentActiveTabId); - } - - function renderMobileTabList(tabsArr, currentActiveTabId) { - const mobileTabList = document.getElementById('mobile-tab-list'); - if (!mobileTabList) return; - mobileTabList.innerHTML = ''; - tabsArr.forEach(function(tab) { - const item = document.createElement('div'); - item.className = 'mobile-tab-item' + (tab.id === currentActiveTabId ? ' active' : ''); - item.setAttribute('role', 'tab'); - item.setAttribute('aria-selected', tab.id === currentActiveTabId ? 'true' : 'false'); - item.setAttribute('data-tab-id', tab.id); - - const titleSpan = document.createElement('span'); - titleSpan.className = 'mobile-tab-title'; - titleSpan.textContent = tab.title || 'Untitled'; - titleSpan.title = tab.title || 'Untitled'; - - // Three-dot menu button (same as desktop) - const menuBtn = document.createElement('button'); - menuBtn.className = 'tab-menu-btn'; - menuBtn.setAttribute('aria-label', 'File options'); - menuBtn.title = 'File options'; - menuBtn.innerHTML = '⋯'; - - // Dropdown (same as desktop) - const dropdown = document.createElement('div'); - dropdown.className = 'tab-menu-dropdown'; - dropdown.innerHTML = - '' + - '' + - ''; - - menuBtn.appendChild(dropdown); - - menuBtn.addEventListener('click', function(e) { - e.stopPropagation(); - document.querySelectorAll('.tab-menu-btn.open').forEach(function(btn) { - if (btn !== menuBtn) btn.classList.remove('open'); - }); - menuBtn.classList.toggle('open'); - if (menuBtn.classList.contains('open')) { - const rect = menuBtn.getBoundingClientRect(); - dropdown.style.top = (rect.bottom + 4) + 'px'; - dropdown.style.right = (window.innerWidth - rect.right) + 'px'; - dropdown.style.left = 'auto'; - } - }); - - dropdown.querySelectorAll('.tab-menu-item').forEach(function(actionBtn) { - actionBtn.addEventListener('click', function(e) { - e.stopPropagation(); - menuBtn.classList.remove('open'); - const action = actionBtn.getAttribute('data-action'); - if (action === 'rename') { - closeMobileMenu(); - renameTab(tab.id); - } else if (action === 'duplicate') { - duplicateTab(tab.id); - closeMobileMenu(); - } else if (action === 'delete') { - deleteTab(tab.id); - } - }); - }); - - item.appendChild(titleSpan); - item.appendChild(menuBtn); - - item.addEventListener('click', function() { - switchTab(tab.id); - closeMobileMenu(); - }); - - mobileTabList.appendChild(item); - }); - } - - // Close any open tab dropdown when clicking elsewhere in the document - document.addEventListener('click', function() { - document.querySelectorAll('.tab-menu-btn.open').forEach(function(btn) { - btn.classList.remove('open'); - }); - }); - - function saveCurrentTabState() { - const tab = tabs.find(function(t) { return t.id === activeTabId; }); - if (!tab) return; - tab.content = markdownEditor.value; - tab.scrollPos = markdownEditor.scrollTop; - tab.viewMode = currentViewMode || 'split'; - saveTabsToStorage(tabs); - } - - function restoreViewMode(mode) { - currentViewMode = null; - setViewMode(mode || 'split'); - } - - function switchTab(tabId) { - if (tabId === activeTabId) return; - saveCurrentTabState(); - activeTabId = tabId; - saveActiveTabId(activeTabId); - const tab = tabs.find(function(t) { return t.id === tabId; }); - if (!tab) return; - markdownEditor.value = tab.content; - restoreViewMode(tab.viewMode); - renderMarkdown(); - requestAnimationFrame(function() { - markdownEditor.scrollTop = tab.scrollPos || 0; - }); - renderTabBar(tabs, activeTabId); - } - - function newTab(content, title) { - if (content === undefined) content = ''; - if (tabs.length >= 20) { - alert('Maximum of 20 tabs reached. Please close an existing tab to open a new one.'); - return; - } - if (!title) title = nextUntitledTitle(); - const tab = createTab(content, title); - tabs.push(tab); - switchTab(tab.id); - markdownEditor.focus(); - } - - function closeTab(tabId) { - const idx = tabs.findIndex(function(t) { return t.id === tabId; }); - if (idx === -1) return; - tabs.splice(idx, 1); - if (tabs.length === 0) { - // Auto-create new "Untitled" when last tab is deleted - const newT = createTab('', nextUntitledTitle()); - tabs.push(newT); - activeTabId = newT.id; - saveActiveTabId(activeTabId); - markdownEditor.value = ''; - restoreViewMode('split'); - renderMarkdown(); - } else if (activeTabId === tabId) { - const newIdx = Math.max(0, idx - 1); - activeTabId = tabs[newIdx].id; - saveActiveTabId(activeTabId); - const newActiveTab = tabs[newIdx]; - markdownEditor.value = newActiveTab.content; - restoreViewMode(newActiveTab.viewMode); - renderMarkdown(); - requestAnimationFrame(function() { - markdownEditor.scrollTop = newActiveTab.scrollPos || 0; - }); - } - saveTabsToStorage(tabs); - renderTabBar(tabs, activeTabId); - } - - function deleteTab(tabId) { - closeTab(tabId); - } - - function renameTab(tabId) { - const tab = tabs.find(function(t) { return t.id === tabId; }); - if (!tab) return; - const modal = document.getElementById('rename-modal'); - const input = document.getElementById('rename-modal-input'); - const confirmBtn = document.getElementById('rename-modal-confirm'); - const cancelBtn = document.getElementById('rename-modal-cancel'); - if (!modal || !input) return; - input.value = tab.title; - modal.style.display = 'flex'; - input.focus(); - input.select(); - - function doRename() { - const newName = input.value.trim(); - if (newName) { - tab.title = newName; - saveTabsToStorage(tabs); - renderTabBar(tabs, activeTabId); - } - modal.style.display = 'none'; - cleanup(); - } - - function cleanup() { - confirmBtn.removeEventListener('click', doRename); - cancelBtn.removeEventListener('click', doCancel); - input.removeEventListener('keydown', onKey); - } - - function doCancel() { - modal.style.display = 'none'; - cleanup(); - } - - function onKey(e) { - if (e.key === 'Enter') doRename(); - else if (e.key === 'Escape') doCancel(); - } - - confirmBtn.addEventListener('click', doRename); - cancelBtn.addEventListener('click', doCancel); - input.addEventListener('keydown', onKey); - } - - function duplicateTab(tabId) { - const tab = tabs.find(function(t) { return t.id === tabId; }); - if (!tab) return; - if (tabs.length >= 20) { - alert('Maximum of 20 tabs reached. Please close an existing tab to open a new one.'); - return; - } - saveCurrentTabState(); - const dupTitle = tab.title + ' (copy)'; - const dup = createTab(tab.content, dupTitle, tab.viewMode); - const idx = tabs.findIndex(function(t) { return t.id === tabId; }); - tabs.splice(idx + 1, 0, dup); - switchTab(dup.id); - } - - function resetAllTabs() { - const modal = document.getElementById('reset-confirm-modal'); - const confirmBtn = document.getElementById('reset-modal-confirm'); - const cancelBtn = document.getElementById('reset-modal-cancel'); - if (!modal) return; - modal.style.display = 'flex'; - - function doReset() { - modal.style.display = 'none'; - cleanup(); - tabs = []; - untitledCounter = 0; - saveUntitledCounter(0); - const welcome = createTab(sampleMarkdown, 'Welcome to Markdown'); - tabs.push(welcome); - activeTabId = welcome.id; - saveActiveTabId(activeTabId); - saveTabsToStorage(tabs); - markdownEditor.value = sampleMarkdown; - restoreViewMode('split'); - renderMarkdown(); - renderTabBar(tabs, activeTabId); - } - - function doCancel() { - modal.style.display = 'none'; - cleanup(); - } - - function cleanup() { - confirmBtn.removeEventListener('click', doReset); - cancelBtn.removeEventListener('click', doCancel); - } - - confirmBtn.addEventListener('click', doReset); - cancelBtn.addEventListener('click', doCancel); - } - - function initTabs() { - untitledCounter = loadUntitledCounter(); - tabs = loadTabsFromStorage(); - activeTabId = loadActiveTabId(); - if (tabs.length === 0) { - const tab = createTab(sampleMarkdown, 'Welcome to Markdown'); - tabs.push(tab); - activeTabId = tab.id; - saveTabsToStorage(tabs); - saveActiveTabId(activeTabId); - } else if (!tabs.find(function(t) { return t.id === activeTabId; })) { - activeTabId = tabs[0].id; - saveActiveTabId(activeTabId); - } - const activeTab = tabs.find(function(t) { return t.id === activeTabId; }); - markdownEditor.value = activeTab.content; - restoreViewMode(activeTab.viewMode); - renderMarkdown(); - requestAnimationFrame(function() { - markdownEditor.scrollTop = activeTab.scrollPos || 0; - }); - renderTabBar(tabs, activeTabId); - } - - function renderMarkdown() { - try { - const markdown = markdownEditor.value; - const html = marked.parse(markdown); - const sanitizedHtml = DOMPurify.sanitize(html, { - ADD_TAGS: ['mjx-container'], - ADD_ATTR: ['id', 'class', 'style'] - }); - markdownPreview.innerHTML = sanitizedHtml; - - processEmojis(markdownPreview); - - // Reinitialize mermaid with current theme before rendering diagrams - initMermaid(); - - try { - const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); - if (mermaidNodes.length > 0) { - Promise.resolve(mermaid.init(undefined, mermaidNodes)) - .then(() => addMermaidToolbars()) - .catch((e) => { - console.warn("Mermaid rendering failed:", e); - addMermaidToolbars(); - }); - } - } catch (e) { - console.warn("Mermaid rendering failed:", e); - } - - if (window.MathJax) { - try { - MathJax.typesetPromise([markdownPreview]).catch((err) => { - console.warn('MathJax typesetting failed:', err); - }); - } catch (e) { - console.warn("MathJax rendering failed:", e); - } - } - - updateDocumentStats(); - } catch (e) { - console.error("Markdown rendering failed:", e); - markdownPreview.innerHTML = `
- Error rendering markdown: ${e.message} -
-
${markdownEditor.value}
`; - } - } - - function importMarkdownFile(file) { - const reader = new FileReader(); - reader.onload = function(e) { - newTab(e.target.result, file.name.replace(/\.md$/i, '')); - dropzone.style.display = "none"; - }; - reader.readAsText(file); - } - - function isMarkdownPath(path) { - return /\.(md|markdown)$/i.test(path || ""); - } - const MAX_GITHUB_FILES_SHOWN = 30; - - function getFileName(path) { - return (path || "").split("/").pop() || "document.md"; - } - - function buildRawGitHubUrl(owner, repo, ref, filePath) { - const encodedPath = filePath - .split("/") - .map((part) => encodeURIComponent(part)) - .join("/"); - return `https://raw.githubusercontent.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${encodeURIComponent(ref)}/${encodedPath}`; - } - - async function fetchGitHubJson(url) { - const response = await fetch(url, { - headers: { - Accept: "application/vnd.github+json" - } - }); - if (!response.ok) { - throw new Error(`GitHub API request failed (${response.status})`); - } - return response.json(); - } - - async function fetchTextContent(url) { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to fetch file (${response.status})`); - } - return response.text(); - } - - function parseGitHubImportUrl(input) { - let parsedUrl; - try { - parsedUrl = new URL((input || "").trim()); - } catch (_) { - return null; - } - - const host = parsedUrl.hostname.replace(/^www\./, ""); - const segments = parsedUrl.pathname.split("/").filter(Boolean); - - if (host === "raw.githubusercontent.com") { - if (segments.length < 5) return null; - const [owner, repo, ref, ...rest] = segments; - const filePath = rest.join("/"); - return { owner, repo, ref, type: "file", filePath }; - } - - if (host !== "github.com" || segments.length < 2) return null; - - const owner = segments[0]; - const repo = segments[1].replace(/\.git$/i, ""); - if (segments.length === 2) { - return { owner, repo, type: "repo" }; - } - - const mode = segments[2]; - if (mode === "blob" && segments.length >= 5) { - return { - owner, - repo, - type: "file", - ref: segments[3], - filePath: segments.slice(4).join("/") - }; - } - - if (mode === "tree" && segments.length >= 4) { - return { - owner, - repo, - type: "tree", - ref: segments[3], - basePath: segments.slice(4).join("/") - }; - } - - return { owner, repo, type: "repo" }; - } - - async function getDefaultBranch(owner, repo) { - const repoInfo = await fetchGitHubJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`); - return repoInfo.default_branch; - } - - async function listMarkdownFiles(owner, repo, ref, basePath) { - const treeResponse = await fetchGitHubJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/trees/${encodeURIComponent(ref)}?recursive=1`); - const normalizedBasePath = (basePath || "").replace(/^\/+|\/+$/g, ""); - - return (treeResponse.tree || []) - .filter((entry) => entry.type === "blob" && isMarkdownPath(entry.path)) - .filter((entry) => !normalizedBasePath || entry.path === normalizedBasePath || entry.path.startsWith(normalizedBasePath + "/")) - .map((entry) => entry.path) - .sort((a, b) => a.localeCompare(b)); - } - - function setGitHubImportLoading(isLoading) { - if (!githubImportSubmitBtn) return; - if (isLoading) { - githubImportSubmitBtn.dataset.loadingText = githubImportSubmitBtn.textContent; - githubImportSubmitBtn.textContent = "Importing..."; - } else if (githubImportSubmitBtn.dataset.loadingText) { - githubImportSubmitBtn.textContent = githubImportSubmitBtn.dataset.loadingText; - delete githubImportSubmitBtn.dataset.loadingText; - } - } - - function setGitHubImportMessage(message, options = {}) { - if (!githubImportError) return; - const { isError = true } = options; - githubImportError.classList.toggle("is-info", !isError); - if (!message) { - githubImportError.textContent = ""; - githubImportError.style.display = "none"; - return; - } - githubImportError.textContent = message; - githubImportError.style.display = "block"; - } - - function resetGitHubImportModal() { - if (!githubImportUrlInput || !githubImportFileSelect || !githubImportSubmitBtn) return; - if (githubImportTitle) { - githubImportTitle.textContent = "Import Markdown from GitHub"; - } - githubImportUrlInput.value = ""; - githubImportUrlInput.style.display = "block"; - githubImportUrlInput.disabled = false; - githubImportFileSelect.innerHTML = ""; - githubImportFileSelect.style.display = "none"; - githubImportFileSelect.disabled = false; - githubImportSubmitBtn.dataset.step = "url"; - delete githubImportSubmitBtn.dataset.owner; - delete githubImportSubmitBtn.dataset.repo; - delete githubImportSubmitBtn.dataset.ref; - githubImportSubmitBtn.textContent = "Import"; - setGitHubImportMessage(""); - } - - function openGitHubImportModal() { - if (!githubImportModal || !githubImportUrlInput || !githubImportSubmitBtn) return; - resetGitHubImportModal(); - githubImportModal.style.display = "flex"; - githubImportUrlInput.focus(); - } - - function closeGitHubImportModal() { - if (!githubImportModal) return; - githubImportModal.style.display = "none"; - resetGitHubImportModal(); - } - - async function handleGitHubImportSubmit() { - if (!githubImportSubmitBtn || !githubImportUrlInput || !githubImportFileSelect) return; - const setGitHubImportDialogDisabled = (disabled) => { - githubImportSubmitBtn.disabled = disabled; - if (githubImportCancelBtn) { - githubImportCancelBtn.disabled = disabled; - } - }; - const step = githubImportSubmitBtn.dataset.step || "url"; - if (step === "select") { - const selectedPath = githubImportFileSelect.value; - const owner = githubImportSubmitBtn.dataset.owner; - const repo = githubImportSubmitBtn.dataset.repo; - const ref = githubImportSubmitBtn.dataset.ref; - if (!owner || !repo || !ref || !selectedPath) { - setGitHubImportMessage("Please select a file to import."); - return; - } - setGitHubImportLoading(true); - setGitHubImportDialogDisabled(true); - try { - const markdown = await fetchTextContent(buildRawGitHubUrl(owner, repo, ref, selectedPath)); - newTab(markdown, getFileName(selectedPath).replace(/\.(md|markdown)$/i, "")); - closeGitHubImportModal(); - } catch (error) { - console.error("GitHub import failed:", error); - setGitHubImportMessage("GitHub import failed: " + error.message); - } finally { - setGitHubImportDialogDisabled(false); - setGitHubImportLoading(false); - } - return; - } - - const urlInput = githubImportUrlInput.value.trim(); - if (!urlInput) { - setGitHubImportMessage("Please enter a GitHub URL."); - return; - } - - const parsed = parseGitHubImportUrl(urlInput); - if (!parsed || !parsed.owner || !parsed.repo) { - setGitHubImportMessage("Please enter a valid GitHub URL."); - return; - } - - setGitHubImportMessage(""); - setGitHubImportLoading(true); - setGitHubImportDialogDisabled(true); - try { - if (parsed.type === "file") { - if (!isMarkdownPath(parsed.filePath)) { - throw new Error("The provided URL does not point to a Markdown file."); - } - const markdown = await fetchTextContent(buildRawGitHubUrl(parsed.owner, parsed.repo, parsed.ref, parsed.filePath)); - newTab(markdown, getFileName(parsed.filePath).replace(/\.(md|markdown)$/i, "")); - closeGitHubImportModal(); - return; - } - - const ref = parsed.ref || await getDefaultBranch(parsed.owner, parsed.repo); - const files = await listMarkdownFiles(parsed.owner, parsed.repo, ref, parsed.basePath || ""); - - if (!files.length) { - setGitHubImportMessage("No Markdown files were found at that GitHub location."); - return; - } - - const shownFiles = files.slice(0, MAX_GITHUB_FILES_SHOWN); - if (files.length === 1) { - const targetPath = files[0]; - const markdown = await fetchTextContent(buildRawGitHubUrl(parsed.owner, parsed.repo, ref, targetPath)); - newTab(markdown, getFileName(targetPath).replace(/\.(md|markdown)$/i, "")); - closeGitHubImportModal(); - return; - } - - githubImportUrlInput.style.display = "none"; - githubImportFileSelect.style.display = "block"; - githubImportFileSelect.innerHTML = ""; - shownFiles.forEach((filePath) => { - const option = document.createElement("option"); - option.value = filePath; - option.textContent = filePath; - githubImportFileSelect.appendChild(option); - }); - if (files.length > MAX_GITHUB_FILES_SHOWN) { - setGitHubImportMessage(`Showing first ${MAX_GITHUB_FILES_SHOWN} of ${files.length} Markdown files.`, { isError: false }); - } else { - setGitHubImportMessage(""); - } - if (githubImportTitle) { - githubImportTitle.textContent = "Select a Markdown file to import"; - } - githubImportSubmitBtn.dataset.step = "select"; - githubImportSubmitBtn.dataset.owner = parsed.owner; - githubImportSubmitBtn.dataset.repo = parsed.repo; - githubImportSubmitBtn.dataset.ref = ref; - githubImportSubmitBtn.textContent = "Import Selected"; - } catch (error) { - console.error("GitHub import failed:", error); - setGitHubImportMessage("GitHub import failed: " + error.message); - } finally { - setGitHubImportDialogDisabled(false); - setGitHubImportLoading(false); - } - } - - function processEmojis(element) { - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - null, - false - ); - - const textNodes = []; - let node; - while ((node = walker.nextNode())) { - let parent = node.parentNode; - let isInCode = false; - while (parent && parent !== element) { - if (parent.tagName === 'PRE' || parent.tagName === 'CODE') { - isInCode = true; - break; - } - parent = parent.parentNode; - } - - if (!isInCode && node.nodeValue.includes(':')) { - textNodes.push(node); - } - } - - textNodes.forEach(textNode => { - const text = textNode.nodeValue; - const emojiRegex = /:([\w+-]+):/g; - - let match; - let lastIndex = 0; - let result = ''; - let hasEmoji = false; - - while ((match = emojiRegex.exec(text)) !== null) { - const shortcode = match[1]; - const emoji = joypixels.shortnameToUnicode(`:${shortcode}:`); - - if (emoji !== `:${shortcode}:`) { // If conversion was successful - hasEmoji = true; - result += text.substring(lastIndex, match.index) + emoji; - lastIndex = emojiRegex.lastIndex; - } else { - result += text.substring(lastIndex, emojiRegex.lastIndex); - lastIndex = emojiRegex.lastIndex; - } - } - - if (hasEmoji) { - result += text.substring(lastIndex); - const span = document.createElement('span'); - span.innerHTML = result; - textNode.parentNode.replaceChild(span, textNode); - } - }); - } - - function debouncedRender() { - clearTimeout(markdownRenderTimeout); - markdownRenderTimeout = setTimeout(renderMarkdown, RENDER_DELAY); - } - - function updateDocumentStats() { - const text = markdownEditor.value; - - const charCount = text.length; - charCountElement.textContent = charCount.toLocaleString(); - - const wordCount = text.trim() === "" ? 0 : text.trim().split(/\s+/).length; - wordCountElement.textContent = wordCount.toLocaleString(); - - const readingTimeMinutes = Math.ceil(wordCount / 200); - readingTimeElement.textContent = readingTimeMinutes; - } - - function syncEditorToPreview() { - if (!syncScrollingEnabled || isPreviewScrolling) return; - - isEditorScrolling = true; - clearTimeout(scrollSyncTimeout); - - scrollSyncTimeout = setTimeout(() => { - const editorScrollRatio = - editorPane.scrollTop / - (editorPane.scrollHeight - editorPane.clientHeight); - const previewScrollPosition = - (previewPane.scrollHeight - previewPane.clientHeight) * - editorScrollRatio; - - if (!isNaN(previewScrollPosition) && isFinite(previewScrollPosition)) { - previewPane.scrollTop = previewScrollPosition; - } - - setTimeout(() => { - isEditorScrolling = false; - }, 50); - }, SCROLL_SYNC_DELAY); - } - - function syncPreviewToEditor() { - if (!syncScrollingEnabled || isEditorScrolling) return; - - isPreviewScrolling = true; - clearTimeout(scrollSyncTimeout); - - scrollSyncTimeout = setTimeout(() => { - const previewScrollRatio = - previewPane.scrollTop / - (previewPane.scrollHeight - previewPane.clientHeight); - const editorScrollPosition = - (editorPane.scrollHeight - editorPane.clientHeight) * - previewScrollRatio; - - if (!isNaN(editorScrollPosition) && isFinite(editorScrollPosition)) { - editorPane.scrollTop = editorScrollPosition; - } - - setTimeout(() => { - isPreviewScrolling = false; - }, 50); - }, SCROLL_SYNC_DELAY); - } - - function toggleSyncScrolling() { - syncScrollingEnabled = !syncScrollingEnabled; - if (syncScrollingEnabled) { - toggleSyncButton.innerHTML = ' Sync Off'; - toggleSyncButton.classList.add("sync-disabled"); - toggleSyncButton.classList.remove("sync-enabled"); - toggleSyncButton.classList.add("border-primary"); - } else { - toggleSyncButton.innerHTML = ' Sync On'; - toggleSyncButton.classList.add("sync-enabled"); - toggleSyncButton.classList.remove("sync-disabled"); - toggleSyncButton.classList.remove("border-primary"); - } - } - - // View Mode Functions - Story 1.1 & 1.2 - function setViewMode(mode) { - if (mode === currentViewMode) return; - - const previousMode = currentViewMode; - currentViewMode = mode; - - // Update content container class - contentContainer.classList.remove('view-editor-only', 'view-preview-only', 'view-split'); - contentContainer.classList.add('view-' + (mode === 'editor' ? 'editor-only' : mode === 'preview' ? 'preview-only' : 'split')); - - // Update button active states (desktop) - viewModeButtons.forEach(btn => { - const btnMode = btn.getAttribute('data-mode'); - if (btnMode === mode) { - btn.classList.add('active'); - btn.setAttribute('aria-pressed', 'true'); - } else { - btn.classList.remove('active'); - btn.setAttribute('aria-pressed', 'false'); - } - }); - - // Story 1.4: Update mobile button active states - mobileViewModeButtons.forEach(btn => { - const btnMode = btn.getAttribute('data-mode'); - if (btnMode === mode) { - btn.classList.add('active'); - btn.setAttribute('aria-pressed', 'true'); - } else { - btn.classList.remove('active'); - btn.setAttribute('aria-pressed', 'false'); - } - }); - - // Story 1.2: Show/hide sync toggle based on view mode - updateSyncToggleVisibility(mode); - - // Story 1.3: Handle pane widths when switching modes - if (mode === 'split') { - // Restore preserved pane widths when entering split mode - applyPaneWidths(); - } else if (previousMode === 'split') { - // Reset pane widths when leaving split mode - resetPaneWidths(); - } - - // Re-render markdown when switching to a view that includes preview - if (mode === 'split' || mode === 'preview') { - renderMarkdown(); - } - } - - // Story 1.2: Update sync toggle visibility - function updateSyncToggleVisibility(mode) { - const isSplitView = mode === 'split'; - - // Desktop sync toggle - if (toggleSyncButton) { - toggleSyncButton.style.display = isSplitView ? '' : 'none'; - toggleSyncButton.setAttribute('aria-hidden', !isSplitView); - } - - // Mobile sync toggle - if (mobileToggleSync) { - mobileToggleSync.style.display = isSplitView ? '' : 'none'; - mobileToggleSync.setAttribute('aria-hidden', !isSplitView); - } - } - - // Story 1.3: Resize Divider Functions - function initResizer() { - if (!resizeDivider) return; - - resizeDivider.addEventListener('mousedown', startResize); - document.addEventListener('mousemove', handleResize); - document.addEventListener('mouseup', stopResize); - - // Touch support for tablets (though disabled via CSS, keeping for future) - resizeDivider.addEventListener('touchstart', startResizeTouch); - document.addEventListener('touchmove', handleResizeTouch); - document.addEventListener('touchend', stopResize); - } - - function startResize(e) { - if (currentViewMode !== 'split') return; - e.preventDefault(); - isResizing = true; - resizeDivider.classList.add('dragging'); - document.body.classList.add('resizing'); - } - - function startResizeTouch(e) { - if (currentViewMode !== 'split') return; - e.preventDefault(); - isResizing = true; - resizeDivider.classList.add('dragging'); - document.body.classList.add('resizing'); - } - - function handleResize(e) { - if (!isResizing) return; - - const containerRect = contentContainer.getBoundingClientRect(); - const containerWidth = containerRect.width; - const mouseX = e.clientX - containerRect.left; - - // Calculate percentage - let newEditorPercent = (mouseX / containerWidth) * 100; - - // Enforce minimum pane widths - newEditorPercent = Math.max(MIN_PANE_PERCENT, Math.min(100 - MIN_PANE_PERCENT, newEditorPercent)); - - editorWidthPercent = newEditorPercent; - applyPaneWidths(); - } - - function handleResizeTouch(e) { - if (!isResizing || !e.touches[0]) return; - - const containerRect = contentContainer.getBoundingClientRect(); - const containerWidth = containerRect.width; - const touchX = e.touches[0].clientX - containerRect.left; - - let newEditorPercent = (touchX / containerWidth) * 100; - newEditorPercent = Math.max(MIN_PANE_PERCENT, Math.min(100 - MIN_PANE_PERCENT, newEditorPercent)); - - editorWidthPercent = newEditorPercent; - applyPaneWidths(); - } - - function stopResize() { - if (!isResizing) return; - isResizing = false; - resizeDivider.classList.remove('dragging'); - document.body.classList.remove('resizing'); - } - - function applyPaneWidths() { - if (currentViewMode !== 'split') return; - - const previewPercent = 100 - editorWidthPercent; - editorPaneElement.style.flex = `0 0 calc(${editorWidthPercent}% - 4px)`; - previewPaneElement.style.flex = `0 0 calc(${previewPercent}% - 4px)`; - } - - function resetPaneWidths() { - editorPaneElement.style.flex = ''; - previewPaneElement.style.flex = ''; - } - - function openMobileMenu() { - mobileMenuPanel.classList.add("active"); - mobileMenuOverlay.classList.add("active"); - } - function closeMobileMenu() { - mobileMenuPanel.classList.remove("active"); - mobileMenuOverlay.classList.remove("active"); - } - mobileMenuToggle.addEventListener("click", openMobileMenu); - mobileCloseMenu.addEventListener("click", closeMobileMenu); - mobileMenuOverlay.addEventListener("click", closeMobileMenu); - - function updateMobileStats() { - mobileCharCount.textContent = charCountElement.textContent; - mobileWordCount.textContent = wordCountElement.textContent; - mobileReadingTime.textContent = readingTimeElement.textContent; - } - - const origUpdateStats = updateDocumentStats; - updateDocumentStats = function() { - origUpdateStats(); - updateMobileStats(); - }; - - mobileToggleSync.addEventListener("click", () => { - toggleSyncScrolling(); - if (syncScrollingEnabled) { - mobileToggleSync.innerHTML = ' Sync Off'; - mobileToggleSync.classList.add("sync-disabled"); - mobileToggleSync.classList.remove("sync-enabled"); - mobileToggleSync.classList.add("border-primary"); - } else { - mobileToggleSync.innerHTML = ' Sync On'; - mobileToggleSync.classList.add("sync-enabled"); - mobileToggleSync.classList.remove("sync-disabled"); - mobileToggleSync.classList.remove("border-primary"); - } - }); - mobileImportBtn.addEventListener("click", () => fileInput.click()); - mobileImportGithubBtn.addEventListener("click", () => { - closeMobileMenu(); - openGitHubImportModal(); - }); - mobileExportMd.addEventListener("click", () => exportMd.click()); - mobileExportHtml.addEventListener("click", () => exportHtml.click()); - mobileExportPdf.addEventListener("click", () => exportPdf.click()); - mobileCopyMarkdown.addEventListener("click", () => copyMarkdownButton.click()); - mobileThemeToggle.addEventListener("click", () => { - themeToggle.click(); - mobileThemeToggle.innerHTML = themeToggle.innerHTML + " Toggle Dark Mode"; - }); - - const mobileNewTabBtn = document.getElementById("mobile-new-tab-btn"); - if (mobileNewTabBtn) { - mobileNewTabBtn.addEventListener("click", function() { - newTab(); - closeMobileMenu(); - }); - } - - const mobileTabResetBtn = document.getElementById("mobile-tab-reset-btn"); - if (mobileTabResetBtn) { - mobileTabResetBtn.addEventListener("click", function() { - closeMobileMenu(); - resetAllTabs(); - }); - } - - initTabs(); - updateMobileStats(); - - // Initialize resizer - Story 1.3 - initResizer(); - - // View Mode Button Event Listeners - Story 1.1 - viewModeButtons.forEach(btn => { - btn.addEventListener('click', function() { - const mode = this.getAttribute('data-mode'); - setViewMode(mode); - saveCurrentTabState(); - }); - }); - - // Story 1.4: Mobile View Mode Button Event Listeners - mobileViewModeButtons.forEach(btn => { - btn.addEventListener('click', function() { - const mode = this.getAttribute('data-mode'); - setViewMode(mode); - saveCurrentTabState(); - closeMobileMenu(); - }); - }); - - markdownEditor.addEventListener("input", function() { - debouncedRender(); - clearTimeout(saveTabStateTimeout); - saveTabStateTimeout = setTimeout(saveCurrentTabState, 500); - }); - - // Tab key handler to insert indentation instead of moving focus - markdownEditor.addEventListener("keydown", function(e) { - if (e.key === 'Tab') { - e.preventDefault(); - - const start = this.selectionStart; - const end = this.selectionEnd; - const value = this.value; - - // Insert 2 spaces - const indent = ' '; // 2 spaces - - // Update textarea value - this.value = value.substring(0, start) + indent + value.substring(end); - - // Update cursor position - this.selectionStart = this.selectionEnd = start + indent.length; - - // Trigger input event to update preview - this.dispatchEvent(new Event('input')); - } - }); - - editorPane.addEventListener("scroll", syncEditorToPreview); - previewPane.addEventListener("scroll", syncPreviewToEditor); - toggleSyncButton.addEventListener("click", toggleSyncScrolling); - themeToggle.addEventListener("click", function () { - const theme = - document.documentElement.getAttribute("data-theme") === "dark" - ? "light" - : "dark"; - document.documentElement.setAttribute("data-theme", theme); - - if (theme === "dark") { - themeToggle.innerHTML = ''; - } else { - themeToggle.innerHTML = ''; - } - - renderMarkdown(); - }); - - if (importFromFileButton) { - importFromFileButton.addEventListener("click", function (e) { - e.preventDefault(); - fileInput.click(); - }); - } - - if (importFromGithubButton) { - importFromGithubButton.addEventListener("click", function (e) { - e.preventDefault(); - openGitHubImportModal(); - }); - } - - if (githubImportSubmitBtn) { - githubImportSubmitBtn.addEventListener("click", handleGitHubImportSubmit); - } - if (githubImportCancelBtn) { - githubImportCancelBtn.addEventListener("click", closeGitHubImportModal); - } - const handleGitHubImportInputKeydown = function(e) { - if (e.key === "Enter") { - e.preventDefault(); - handleGitHubImportSubmit(); - } else if (e.key === "Escape") { - closeGitHubImportModal(); - } - }; - if (githubImportUrlInput) { - githubImportUrlInput.addEventListener("keydown", handleGitHubImportInputKeydown); - } - if (githubImportFileSelect) { - githubImportFileSelect.addEventListener("keydown", handleGitHubImportInputKeydown); - } - - fileInput.addEventListener("change", function (e) { - const file = e.target.files[0]; - if (file) { - importMarkdownFile(file); - } - this.value = ""; - }); - - exportMd.addEventListener("click", function () { - try { - const blob = new Blob([markdownEditor.value], { - type: "text/markdown;charset=utf-8", - }); - saveAs(blob, "document.md"); - } catch (e) { - console.error("Export failed:", e); - alert("Export failed: " + e.message); - } - }); - - exportHtml.addEventListener("click", function () { - try { - const markdown = markdownEditor.value; - const html = marked.parse(markdown); - const sanitizedHtml = DOMPurify.sanitize(html, { - ADD_TAGS: ['mjx-container'], - ADD_ATTR: ['id', 'class', 'style'] - }); - const isDarkTheme = - document.documentElement.getAttribute("data-theme") === "dark"; - const cssTheme = isDarkTheme - ? "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.3.0/github-markdown-dark.min.css" - : "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.3.0/github-markdown.min.css"; - const fullHtml = ` - - - - - Markdown Export - - - - -
- ${sanitizedHtml} -
- -`; - const blob = new Blob([fullHtml], { type: "text/html;charset=utf-8" }); - saveAs(blob, "document.html"); - } catch (e) { - console.error("HTML export failed:", e); - alert("HTML export failed: " + e.message); - } - }); - - // ============================================ - // Page-Break Detection Functions (Story 1.1) - // ============================================ - - // Page configuration constants for A4 PDF export - const PAGE_CONFIG = { - a4Width: 210, // mm - a4Height: 297, // mm - margin: 15, // mm each side - contentWidth: 180, // 210 - 30 (margins) - contentHeight: 267, // 297 - 30 (margins) - windowWidth: 1000, // html2canvas config - scale: 2 // html2canvas scale factor - }; - - /** - * Task 1: Identifies all graphic elements that may need page-break handling - * @param {HTMLElement} container - The container element to search within - * @returns {Array} Array of {element, type} objects - */ - function identifyGraphicElements(container) { - const graphics = []; - - // Query for images - container.querySelectorAll('img').forEach(el => { - graphics.push({ element: el, type: 'img' }); - }); - - // Query for SVGs (Mermaid diagrams) - container.querySelectorAll('svg').forEach(el => { - graphics.push({ element: el, type: 'svg' }); - }); - - // Query for pre elements (code blocks) - container.querySelectorAll('pre').forEach(el => { - graphics.push({ element: el, type: 'pre' }); - }); - - // Query for tables - container.querySelectorAll('table').forEach(el => { - graphics.push({ element: el, type: 'table' }); - }); - - return graphics; - } - - /** - * Task 2: Calculates element positions relative to the container - * @param {Array} elements - Array of {element, type} objects - * @param {HTMLElement} container - The container element - * @returns {Array} Array with position data added - */ - function calculateElementPositions(elements, container) { - const containerRect = container.getBoundingClientRect(); - - return elements.map(item => { - const rect = item.element.getBoundingClientRect(); - const top = rect.top - containerRect.top; - const height = rect.height; - const bottom = top + height; - - return { - element: item.element, - type: item.type, - top: top, - height: height, - bottom: bottom - }; - }); - } - - /** - * Task 3: Calculates page boundary positions - * @param {number} totalHeight - Total height of content in pixels - * @param {number} elementWidth - Actual width of the rendered element in pixels - * @param {Object} pageConfig - Page configuration object - * @returns {Array} Array of y-coordinates where pages end - */ - function calculatePageBoundaries(totalHeight, elementWidth, pageConfig) { - // Calculate pixel height per page based on the element's actual width - // This must match how PDF pagination will split the canvas - // The aspect ratio of content area determines page height relative to width - const aspectRatio = pageConfig.contentHeight / pageConfig.contentWidth; - const pageHeightPx = elementWidth * aspectRatio; - - const boundaries = []; - let y = pageHeightPx; - - while (y < totalHeight) { - boundaries.push(y); - y += pageHeightPx; - } - - return { boundaries, pageHeightPx }; - } - - /** - * Task 4: Detects which elements would be split across page boundaries - * @param {Array} elements - Array of elements with position data - * @param {Array} pageBoundaries - Array of page break y-coordinates - * @returns {Array} Array of split elements with additional split info - */ - function detectSplitElements(elements, pageBoundaries) { - // Handle edge case: empty elements array - if (!elements || elements.length === 0) { - return []; - } - - // Handle edge case: no page boundaries (single page) - if (!pageBoundaries || pageBoundaries.length === 0) { - return []; - } - - const splitElements = []; - - for (const item of elements) { - // Find which page the element starts on - let startPage = 0; - for (let i = 0; i < pageBoundaries.length; i++) { - if (item.top >= pageBoundaries[i]) { - startPage = i + 1; - } else { - break; - } - } - - // Find which page the element ends on - let endPage = 0; - for (let i = 0; i < pageBoundaries.length; i++) { - if (item.bottom > pageBoundaries[i]) { - endPage = i + 1; - } else { - break; - } - } - - // Element is split if it spans multiple pages - if (endPage > startPage) { - // Calculate overflow amount (how much crosses into next page) - const boundaryY = pageBoundaries[startPage] || pageBoundaries[0]; - const overflowAmount = item.bottom - boundaryY; - - splitElements.push({ - element: item.element, - type: item.type, - top: item.top, - height: item.height, - splitPageIndex: startPage, - overflowAmount: overflowAmount - }); - } - } - - return splitElements; - } - - /** - * Task 5: Main entry point for analyzing graphics for page breaks - * @param {HTMLElement} tempElement - The rendered content container - * @returns {Object} Analysis result with totalElements, splitElements, pageCount - */ - function analyzeGraphicsForPageBreaks(tempElement) { - try { - // Step 1: Identify all graphic elements - const graphics = identifyGraphicElements(tempElement); - console.log('Step 1 - Graphics found:', graphics.length, graphics.map(g => g.type)); - - // Step 2: Calculate positions for each element - const elementsWithPositions = calculateElementPositions(graphics, tempElement); - console.log('Step 2 - Element positions:', elementsWithPositions.map(e => ({ - type: e.type, - top: Math.round(e.top), - height: Math.round(e.height), - bottom: Math.round(e.bottom) - }))); - - // Step 3: Calculate page boundaries using the element's ACTUAL width - const totalHeight = tempElement.scrollHeight; - const elementWidth = tempElement.offsetWidth; - const { boundaries: pageBoundaries, pageHeightPx } = calculatePageBoundaries( - totalHeight, - elementWidth, - PAGE_CONFIG - ); - - console.log('Step 3 - Page boundaries:', { - elementWidth, - totalHeight, - pageHeightPx: Math.round(pageHeightPx), - boundaries: pageBoundaries.map(b => Math.round(b)) - }); - - // Step 4: Detect split elements - const splitElements = detectSplitElements(elementsWithPositions, pageBoundaries); - console.log('Step 4 - Split elements detected:', splitElements.length); - - // Calculate page count - const pageCount = pageBoundaries.length + 1; - - return { - totalElements: graphics.length, - splitElements: splitElements, - pageCount: pageCount, - pageBoundaries: pageBoundaries, - pageHeightPx: pageHeightPx - }; - } catch (error) { - console.error('Page-break analysis failed:', error); - return { - totalElements: 0, - splitElements: [], - pageCount: 1, - pageBoundaries: [], - pageHeightPx: 0 - }; - } - } - - // ============================================ - // End Page-Break Detection Functions - // ============================================ - - // ============================================ - // Page-Break Insertion Functions (Story 1.2) - // ============================================ - - // Threshold for whitespace optimization (30% of page height) - const PAGE_BREAK_THRESHOLD = 0.3; - - /** - * Task 3: Categorizes split elements by whether they fit on a single page - * @param {Array} splitElements - Array of split elements from detection - * @param {number} pageHeightPx - Page height in pixels - * @returns {Object} { fittingElements, oversizedElements } - */ - function categorizeBySize(splitElements, pageHeightPx) { - const fittingElements = []; - const oversizedElements = []; - - for (const item of splitElements) { - if (item.height <= pageHeightPx) { - fittingElements.push(item); - } else { - oversizedElements.push(item); - } - } - - return { fittingElements, oversizedElements }; - } - - /** - * Task 1: Inserts page breaks by adjusting margins for fitting elements - * @param {Array} fittingElements - Elements that fit on a single page - * @param {number} pageHeightPx - Page height in pixels - */ - function insertPageBreaks(fittingElements, pageHeightPx) { - for (const item of fittingElements) { - // Calculate where the current page ends - const currentPageBottom = (item.splitPageIndex + 1) * pageHeightPx; - - // Calculate remaining space on current page - const remainingSpace = currentPageBottom - item.top; - const remainingRatio = remainingSpace / pageHeightPx; - - console.log('Processing split element:', { - type: item.type, - top: Math.round(item.top), - height: Math.round(item.height), - splitPageIndex: item.splitPageIndex, - currentPageBottom: Math.round(currentPageBottom), - remainingSpace: Math.round(remainingSpace), - remainingRatio: remainingRatio.toFixed(2) - }); - - // Task 4: Whitespace optimization - // If remaining space is more than threshold and element almost fits, skip - // (Will be handled by Story 1.3 scaling instead) - if (remainingRatio > PAGE_BREAK_THRESHOLD) { - const scaledHeight = item.height * 0.9; // 90% scale - if (scaledHeight <= remainingSpace) { - console.log(' -> Skipping (can fit with 90% scaling)'); - continue; - } - } - - // Calculate margin needed to push element to next page - const marginNeeded = currentPageBottom - item.top + 5; // 5px buffer - - console.log(' -> Applying marginTop:', marginNeeded, 'px'); - - // Determine which element to apply margin to - // For SVG elements (Mermaid diagrams), apply to parent container for proper layout - let targetElement = item.element; - if (item.type === 'svg' && item.element.parentElement) { - targetElement = item.element.parentElement; - console.log(' -> Using parent element:', targetElement.tagName, targetElement.className); - } - - // Apply margin to push element to next page - const currentMargin = parseFloat(targetElement.style.marginTop) || 0; - targetElement.style.marginTop = `${currentMargin + marginNeeded}px`; - - console.log(' -> Element after margin:', targetElement.tagName, 'marginTop =', targetElement.style.marginTop); - } - } - - /** - * Task 2: Applies page breaks with cascading adjustment handling - * @param {HTMLElement} tempElement - The rendered content container - * @param {Object} pageConfig - Page configuration object (unused, kept for API compatibility) - * @param {number} maxIterations - Maximum iterations to prevent infinite loops - * @returns {Object} Final analysis result - */ - function applyPageBreaksWithCascade(tempElement, pageConfig, maxIterations = 10) { - let iteration = 0; - let analysis; - let previousSplitCount = -1; - - do { - // Re-analyze after each adjustment - analysis = analyzeGraphicsForPageBreaks(tempElement); - - // Use pageHeightPx from analysis (calculated from actual element width) - const pageHeightPx = analysis.pageHeightPx; - - // Categorize elements by size - const { fittingElements, oversizedElements } = categorizeBySize( - analysis.splitElements, - pageHeightPx - ); - - // Store oversized elements for Story 1.3 - analysis.oversizedElements = oversizedElements; - - // If no fitting elements need adjustment, we're done - if (fittingElements.length === 0) { - break; - } - - // Check if we're making progress (prevent infinite loops) - if (fittingElements.length === previousSplitCount) { - console.warn('Page-break adjustment not making progress, stopping'); - break; - } - previousSplitCount = fittingElements.length; - - // Apply page breaks to fitting elements - insertPageBreaks(fittingElements, pageHeightPx); - iteration++; - - } while (iteration < maxIterations); - - if (iteration >= maxIterations) { - console.warn('Page-break stabilization reached max iterations:', maxIterations); - } - - console.log('Page-break cascade complete:', { - iterations: iteration, - finalSplitCount: analysis.splitElements.length, - oversizedCount: analysis.oversizedElements ? analysis.oversizedElements.length : 0 - }); - - return analysis; - } - - // ============================================ - // End Page-Break Insertion Functions - // ============================================ - - // ============================================ - // Oversized Graphics Scaling Functions (Story 1.3) - // ============================================ - - // Minimum scale factor to maintain readability (50%) - const MIN_SCALE_FACTOR = 0.5; - - /** - * Task 1 & 2: Calculates scale factor with minimum enforcement - * @param {number} elementHeight - Original height of element in pixels - * @param {number} availableHeight - Available page height in pixels - * @param {number} buffer - Small buffer to prevent edge overflow - * @returns {Object} { scaleFactor, wasClampedToMin } - */ - function calculateScaleFactor(elementHeight, availableHeight, buffer = 5) { - const targetHeight = availableHeight - buffer; - let scaleFactor = targetHeight / elementHeight; - let wasClampedToMin = false; - - // Enforce minimum scale for readability - if (scaleFactor < MIN_SCALE_FACTOR) { - console.warn( - `Warning: Large graphic requires ${(scaleFactor * 100).toFixed(0)}% scaling. ` + - `Clamping to minimum ${MIN_SCALE_FACTOR * 100}%. Content may be cut off.` - ); - scaleFactor = MIN_SCALE_FACTOR; - wasClampedToMin = true; - } - - return { scaleFactor, wasClampedToMin }; - } - - /** - * Task 3: Applies CSS transform scaling to an element - * @param {HTMLElement} element - The element to scale - * @param {number} scaleFactor - Scale factor (0.5 = 50%) - * @param {string} elementType - Type of element (svg, pre, img, table) - */ - function applyGraphicScaling(element, scaleFactor, elementType) { - // Get original dimensions before transform - const originalHeight = element.offsetHeight; - - // Task 4: Handle SVG elements (Mermaid diagrams) - if (elementType === 'svg') { - // Remove max-width constraint that may interfere - element.style.maxWidth = 'none'; - } - - // Apply CSS transform - element.style.transform = `scale(${scaleFactor})`; - element.style.transformOrigin = 'top left'; - - // Calculate margin adjustment to collapse visual space - const scaledHeight = originalHeight * scaleFactor; - const marginAdjustment = originalHeight - scaledHeight; - - // Apply negative margin to pull subsequent content up - element.style.marginBottom = `-${marginAdjustment}px`; - } - - /** - * Task 6: Handles all oversized elements by applying appropriate scaling - * @param {Array} oversizedElements - Array of oversized element data - * @param {number} pageHeightPx - Page height in pixels - */ - function handleOversizedElements(oversizedElements, pageHeightPx) { - if (!oversizedElements || oversizedElements.length === 0) { - return; - } - - let scaledCount = 0; - let clampedCount = 0; - - for (const item of oversizedElements) { - // Calculate required scale factor - const { scaleFactor, wasClampedToMin } = calculateScaleFactor( - item.height, - pageHeightPx - ); - - // Apply scaling to the element - applyGraphicScaling(item.element, scaleFactor, item.type); - - scaledCount++; - if (wasClampedToMin) { - clampedCount++; - } - } - - console.log('Oversized graphics scaling complete:', { - totalScaled: scaledCount, - clampedToMinimum: clampedCount - }); - } - - // ============================================ - // End Oversized Graphics Scaling Functions - // ============================================ - - exportPdf.addEventListener("click", async function () { - try { - const originalText = exportPdf.innerHTML; - exportPdf.innerHTML = ' Generating...'; - exportPdf.disabled = true; - - const progressContainer = document.createElement('div'); - progressContainer.style.position = 'fixed'; - progressContainer.style.top = '50%'; - progressContainer.style.left = '50%'; - progressContainer.style.transform = 'translate(-50%, -50%)'; - progressContainer.style.padding = '15px 20px'; - progressContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; - progressContainer.style.color = 'white'; - progressContainer.style.borderRadius = '5px'; - progressContainer.style.zIndex = '9999'; - progressContainer.style.textAlign = 'center'; - - const statusText = document.createElement('div'); - statusText.textContent = 'Generating PDF...'; - progressContainer.appendChild(statusText); - document.body.appendChild(progressContainer); - - const markdown = markdownEditor.value; - const html = marked.parse(markdown); - const sanitizedHtml = DOMPurify.sanitize(html, { - ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath'], - ADD_ATTR: ['id', 'class', 'style', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start'] - }); - - const tempElement = document.createElement("div"); - tempElement.className = "markdown-body pdf-export"; - tempElement.innerHTML = sanitizedHtml; - tempElement.style.padding = "20px"; - tempElement.style.width = "210mm"; - tempElement.style.margin = "0 auto"; - tempElement.style.fontSize = "14px"; - tempElement.style.position = "fixed"; - tempElement.style.left = "-9999px"; - tempElement.style.top = "0"; - - const currentTheme = document.documentElement.getAttribute("data-theme"); - tempElement.style.backgroundColor = currentTheme === "dark" ? "#0d1117" : "#ffffff"; - tempElement.style.color = currentTheme === "dark" ? "#c9d1d9" : "#24292e"; - - document.body.appendChild(tempElement); - - await new Promise(resolve => setTimeout(resolve, 200)); - - try { - await mermaid.run({ - nodes: tempElement.querySelectorAll('.mermaid'), - suppressErrors: true - }); - } catch (mermaidError) { - console.warn("Mermaid rendering issue:", mermaidError); - } - - if (window.MathJax) { - try { - await MathJax.typesetPromise([tempElement]); - } catch (mathJaxError) { - console.warn("MathJax rendering issue:", mathJaxError); - } - - // Hide MathJax assistive elements that cause duplicate text in PDF - // These are screen reader elements that html2canvas captures as visible - // Use multiple CSS properties to ensure html2canvas doesn't render them - const assistiveElements = tempElement.querySelectorAll('mjx-assistive-mml'); - assistiveElements.forEach(el => { - el.style.display = 'none'; - el.style.visibility = 'hidden'; - el.style.position = 'absolute'; - el.style.width = '0'; - el.style.height = '0'; - el.style.overflow = 'hidden'; - el.remove(); // Remove entirely from DOM - }); - - // Also hide any MathJax script elements that might contain source - const mathScripts = tempElement.querySelectorAll('script[type*="math"], script[type*="tex"]'); - mathScripts.forEach(el => el.remove()); - } - - await new Promise(resolve => setTimeout(resolve, 500)); - - // Analyze and apply page-breaks for graphics (Story 1.1 + 1.2) - const pageBreakAnalysis = applyPageBreaksWithCascade(tempElement, PAGE_CONFIG); - - // Scale oversized graphics that can't fit on a single page (Story 1.3) - if (pageBreakAnalysis.oversizedElements && pageBreakAnalysis.pageHeightPx) { - handleOversizedElements(pageBreakAnalysis.oversizedElements, pageBreakAnalysis.pageHeightPx); - } - - const pdfOptions = { - orientation: 'portrait', - unit: 'mm', - format: 'a4', - compress: true, - hotfixes: ["px_scaling"] - }; - - const pdf = new jspdf.jsPDF(pdfOptions); - const pageWidth = pdf.internal.pageSize.getWidth(); - const pageHeight = pdf.internal.pageSize.getHeight(); - const margin = 15; - const contentWidth = pageWidth - (margin * 2); - - const canvas = await html2canvas(tempElement, { - scale: 2, - useCORS: true, - allowTaint: true, - logging: false, - windowWidth: 1000, - windowHeight: tempElement.scrollHeight - }); - - const scaleFactor = canvas.width / contentWidth; - const imgHeight = canvas.height / scaleFactor; - const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2)); - - for (let page = 0; page < pagesCount; page++) { - if (page > 0) pdf.addPage(); - - const sourceY = page * (pageHeight - margin * 2) * scaleFactor; - const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor); - const destHeight = sourceHeight / scaleFactor; - - const pageCanvas = document.createElement('canvas'); - pageCanvas.width = canvas.width; - pageCanvas.height = sourceHeight; - - const ctx = pageCanvas.getContext('2d'); - ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight); - - const imgData = pageCanvas.toDataURL('image/png'); - pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight); - } - - pdf.save("document.pdf"); - - statusText.textContent = 'Download successful!'; - setTimeout(() => { - document.body.removeChild(progressContainer); - }, 1500); - - document.body.removeChild(tempElement); - exportPdf.innerHTML = originalText; - exportPdf.disabled = false; - - } catch (error) { - console.error("PDF export failed:", error); - alert("PDF export failed: " + error.message); - exportPdf.innerHTML = ' Export'; - exportPdf.disabled = false; - - const progressContainer = document.querySelector('div[style*="Preparing PDF"]'); - if (progressContainer) { - document.body.removeChild(progressContainer); - } - } - }); - - copyMarkdownButton.addEventListener("click", function () { - try { - const markdownText = markdownEditor.value; - copyToClipboard(markdownText); - } catch (e) { - console.error("Copy failed:", e); - alert("Failed to copy Markdown: " + e.message); - } - }); - - async function copyToClipboard(text) { - try { - if (navigator.clipboard && window.isSecureContext) { - await navigator.clipboard.writeText(text); - showCopiedMessage(); - } else { - const textArea = document.createElement("textarea"); - textArea.value = text; - textArea.style.position = "fixed"; - textArea.style.opacity = "0"; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - const successful = document.execCommand("copy"); - document.body.removeChild(textArea); - if (successful) { - showCopiedMessage(); - } else { - throw new Error("Copy command was unsuccessful"); - } - } - } catch (err) { - console.error("Copy failed:", err); - alert("Failed to copy HTML: " + err.message); - } - } - - function showCopiedMessage() { - const originalText = copyMarkdownButton.innerHTML; - copyMarkdownButton.innerHTML = ' Copied!'; - - setTimeout(() => { - copyMarkdownButton.innerHTML = originalText; - }, 2000); - } - - // ============================================ - // Share via URL (pako compression + base64url) - // ============================================ - - const MAX_SHARE_URL_LENGTH = 32000; - - function encodeMarkdownForShare(text) { - const compressed = pako.deflate(new TextEncoder().encode(text)); - const chunkSize = 0x8000; - let binary = ''; - for (let i = 0; i < compressed.length; i += chunkSize) { - binary += String.fromCharCode.apply(null, compressed.subarray(i, i + chunkSize)); - } - return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); - } - - function decodeMarkdownFromShare(encoded) { - const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/'); - const binary = atob(base64); - const bytes = Uint8Array.from(binary, c => c.charCodeAt(0)); - return new TextDecoder().decode(pako.inflate(bytes)); - } - - function copyShareUrl(btn) { - const markdownText = markdownEditor.value; - let encoded; - try { - encoded = encodeMarkdownForShare(markdownText); - } catch (e) { - console.error("Share encoding failed:", e); - alert("Failed to encode content for sharing: " + e.message); - return; - } - - const shareUrl = window.location.origin + window.location.pathname + '#share=' + encoded; - const tooLarge = shareUrl.length > MAX_SHARE_URL_LENGTH; - - const originalHTML = btn.innerHTML; - const copiedHTML = ' Copied!'; - - function onCopied() { - if (!tooLarge) { - window.location.hash = 'share=' + encoded; - } - btn.innerHTML = copiedHTML; - setTimeout(() => { btn.innerHTML = originalHTML; }, 2000); - } - - if (navigator.clipboard && window.isSecureContext) { - navigator.clipboard.writeText(shareUrl).then(onCopied).catch(() => { - // clipboard.writeText failed; nothing further to do in secure context - }); - } else { - try { - const tempInput = document.createElement("textarea"); - tempInput.value = shareUrl; - document.body.appendChild(tempInput); - tempInput.select(); - document.execCommand("copy"); - document.body.removeChild(tempInput); - onCopied(); - } catch (_) { - // copy failed silently - } - } - } - - shareButton.addEventListener("click", function () { copyShareUrl(shareButton); }); - mobileShareButton.addEventListener("click", function () { copyShareUrl(mobileShareButton); }); - - function loadFromShareHash() { - if (typeof pako === 'undefined') return; - const hash = window.location.hash; - if (!hash.startsWith('#share=')) return; - const encoded = hash.slice('#share='.length); - if (!encoded) return; - try { - const decoded = decodeMarkdownFromShare(encoded); - markdownEditor.value = decoded; - renderMarkdown(); - saveCurrentTabState(); - } catch (e) { - console.error("Failed to load shared content:", e); - alert("The shared URL could not be decoded. It may be corrupted or incomplete."); - } - } - - loadFromShareHash(); - - const dropEvents = ["dragenter", "dragover", "dragleave", "drop"]; - - dropEvents.forEach((eventName) => { - dropzone.addEventListener(eventName, preventDefaults, false); - document.body.addEventListener(eventName, preventDefaults, false); - }); - - function preventDefaults(e) { - e.preventDefault(); - e.stopPropagation(); - } - - ["dragenter", "dragover"].forEach((eventName) => { - dropzone.addEventListener(eventName, highlight, false); - }); - - ["dragleave", "drop"].forEach((eventName) => { - dropzone.addEventListener(eventName, unhighlight, false); - }); - - function highlight() { - dropzone.classList.add("active"); - } - - function unhighlight() { - dropzone.classList.remove("active"); - } - - dropzone.addEventListener("drop", handleDrop, false); - dropzone.addEventListener("click", function (e) { - if (e.target !== closeDropzoneBtn && !closeDropzoneBtn.contains(e.target)) { - fileInput.click(); - } - }); - closeDropzoneBtn.addEventListener("click", function(e) { - e.stopPropagation(); - dropzone.style.display = "none"; - }); - - function handleDrop(e) { - const dt = e.dataTransfer; - const files = dt.files; - if (files.length) { - const file = files[0]; - const isMarkdownFile = - file.type === "text/markdown" || - file.name.endsWith(".md") || - file.name.endsWith(".markdown"); - if (isMarkdownFile) { - importMarkdownFile(file); - } else { - alert("Please upload a Markdown file (.md or .markdown)"); - } - } - } - - document.addEventListener("keydown", function (e) { - if ((e.ctrlKey || e.metaKey) && e.key === "s") { - e.preventDefault(); - exportMd.click(); - } - if ((e.ctrlKey || e.metaKey) && e.key === "c") { - const activeEl = document.activeElement; - const isTextControl = activeEl && (activeEl.tagName === "TEXTAREA" || activeEl.tagName === "INPUT"); - const hasSelection = window.getSelection && window.getSelection().toString().trim().length > 0; - if (!isTextControl && !hasSelection) { - e.preventDefault(); - copyMarkdownButton.click(); - } - } - // Story 1.2: Only allow sync toggle shortcut when in split view - if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "S") { - e.preventDefault(); - if (currentViewMode === 'split') { - toggleSyncScrolling(); - } - } - // New tab - if ((e.ctrlKey || e.metaKey) && e.key === "t") { - e.preventDefault(); - newTab(); - } - // Close tab - if ((e.ctrlKey || e.metaKey) && e.key === "w") { - e.preventDefault(); - closeTab(activeTabId); - } - // Close Mermaid zoom modal with Escape - if (e.key === "Escape") { - closeMermaidModal(); - } - }); - - document.getElementById('tab-reset-btn').addEventListener('click', function() { - resetAllTabs(); - }); - - // ======================================== - // MERMAID DIAGRAM TOOLBAR - // ======================================== - - /** - * Serialises an SVG element to a data URL suitable for use as an image source. - * Inline styles and dimensions are preserved so the PNG matches the rendered diagram. - */ - function svgToDataUrl(svgEl) { - const clone = svgEl.cloneNode(true); - // Ensure explicit width/height so the canvas has the right dimensions - const bbox = svgEl.getBoundingClientRect(); - if (!clone.getAttribute('width')) clone.setAttribute('width', Math.round(bbox.width)); - if (!clone.getAttribute('height')) clone.setAttribute('height', Math.round(bbox.height)); - const serialized = new XMLSerializer().serializeToString(clone); - return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(serialized); - } - - /** - * Renders an SVG element onto a canvas and resolves with the canvas. - */ - function svgToCanvas(svgEl) { - return new Promise((resolve, reject) => { - const bbox = svgEl.getBoundingClientRect(); - const scale = window.devicePixelRatio || 1; - const width = Math.max(Math.round(bbox.width), 1); - const height = Math.max(Math.round(bbox.height), 1); - - const canvas = document.createElement('canvas'); - canvas.width = width * scale; - canvas.height = height * scale; - const ctx = canvas.getContext('2d'); - ctx.scale(scale, scale); - - // Fill background matching current theme using the CSS variable value - const bgColor = getComputedStyle(document.documentElement) - .getPropertyValue('--bg-color').trim() || '#ffffff'; - ctx.fillStyle = bgColor; - ctx.fillRect(0, 0, width, height); - - const img = new Image(); - img.onload = () => { ctx.drawImage(img, 0, 0, width, height); resolve(canvas); }; - img.onerror = reject; - img.src = svgToDataUrl(svgEl); - }); - } - - /** Downloads the diagram in the given container as a PNG file. */ - async function downloadMermaidPng(container, btn) { - const svgEl = container.querySelector('svg'); - if (!svgEl) return; - const original = btn.innerHTML; - btn.innerHTML = ''; - try { - const canvas = await svgToCanvas(svgEl); - canvas.toBlob(blob => { - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `diagram-${Date.now()}.png`; - a.click(); - URL.revokeObjectURL(url); - btn.innerHTML = ''; - setTimeout(() => { btn.innerHTML = original; }, 1500); - }, 'image/png'); - } catch (e) { - console.error('Mermaid PNG export failed:', e); - btn.innerHTML = original; - } - } - - /** Copies the diagram in the given container as a PNG image to the clipboard. */ - async function copyMermaidImage(container, btn) { - const svgEl = container.querySelector('svg'); - if (!svgEl) return; - const original = btn.innerHTML; - btn.innerHTML = ''; - try { - const canvas = await svgToCanvas(svgEl); - canvas.toBlob(async blob => { - try { - await navigator.clipboard.write([ - new ClipboardItem({ 'image/png': blob }) - ]); - btn.innerHTML = ' Copied!'; - } catch (clipErr) { - console.error('Clipboard write failed:', clipErr); - btn.innerHTML = ''; - } - setTimeout(() => { btn.innerHTML = original; }, 1800); - }, 'image/png'); - } catch (e) { - console.error('Mermaid copy failed:', e); - btn.innerHTML = original; - } - } - - /** Downloads the SVG source of a diagram. */ - function downloadMermaidSvg(container, btn) { - const svgEl = container.querySelector('svg'); - if (!svgEl) return; - const clone = svgEl.cloneNode(true); - const serialized = new XMLSerializer().serializeToString(clone); - const blob = new Blob([serialized], { type: 'image/svg+xml' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `diagram-${Date.now()}.svg`; - a.click(); - URL.revokeObjectURL(url); - const original = btn.innerHTML; - btn.innerHTML = ''; - setTimeout(() => { btn.innerHTML = original; }, 1500); - } - - // ---- Zoom modal state ---- - let modalZoomScale = 1; - let modalPanX = 0; - let modalPanY = 0; - let modalIsDragging = false; - let modalDragStart = { x: 0, y: 0 }; - let modalCurrentSvgEl = null; - - const mermaidZoomModal = document.getElementById('mermaid-zoom-modal'); - const mermaidModalDiagram = document.getElementById('mermaid-modal-diagram'); - - function applyModalTransform() { - if (modalCurrentSvgEl) { - modalCurrentSvgEl.style.transform = - `translate(${modalPanX}px, ${modalPanY}px) scale(${modalZoomScale})`; - } - } - - function closeMermaidModal() { - if (!mermaidZoomModal.classList.contains('active')) return; - mermaidZoomModal.classList.remove('active'); - mermaidModalDiagram.innerHTML = ''; - modalCurrentSvgEl = null; - modalZoomScale = 1; - modalPanX = 0; - modalPanY = 0; - } - - /** Opens the zoom modal with the SVG from the given container. */ - function openMermaidZoomModal(container) { - const svgEl = container.querySelector('svg'); - if (!svgEl) return; - - mermaidModalDiagram.innerHTML = ''; - modalZoomScale = 1; - modalPanX = 0; - modalPanY = 0; - - const svgClone = svgEl.cloneNode(true); - // Remove fixed dimensions so it sizes naturally inside the modal - svgClone.removeAttribute('width'); - svgClone.removeAttribute('height'); - svgClone.style.width = 'auto'; - svgClone.style.height = 'auto'; - svgClone.style.maxWidth = '80vw'; - svgClone.style.maxHeight = '60vh'; - svgClone.style.transformOrigin = 'center'; - mermaidModalDiagram.appendChild(svgClone); - modalCurrentSvgEl = svgClone; - - mermaidZoomModal.classList.add('active'); - } - - // Modal close button - document.getElementById('mermaid-modal-close').addEventListener('click', closeMermaidModal); - // Click backdrop to close - mermaidZoomModal.addEventListener('click', function(e) { - if (e.target === mermaidZoomModal) closeMermaidModal(); - }); - - // Zoom controls - document.getElementById('mermaid-modal-zoom-in').addEventListener('click', () => { - modalZoomScale = Math.min(modalZoomScale + 0.25, 10); - applyModalTransform(); - }); - document.getElementById('mermaid-modal-zoom-out').addEventListener('click', () => { - modalZoomScale = Math.max(modalZoomScale - 0.25, 0.1); - applyModalTransform(); - }); - document.getElementById('mermaid-modal-zoom-reset').addEventListener('click', () => { - modalZoomScale = 1; modalPanX = 0; modalPanY = 0; - applyModalTransform(); - }); - - // Mouse-wheel zoom inside modal - mermaidModalDiagram.addEventListener('wheel', function(e) { - e.preventDefault(); - const delta = e.deltaY < 0 ? 0.15 : -0.15; - modalZoomScale = Math.min(Math.max(modalZoomScale + delta, 0.1), 10); - applyModalTransform(); - }, { passive: false }); - - // Drag to pan inside modal - mermaidModalDiagram.addEventListener('mousedown', function(e) { - modalIsDragging = true; - modalDragStart = { x: e.clientX - modalPanX, y: e.clientY - modalPanY }; - mermaidModalDiagram.classList.add('dragging'); - }); - document.addEventListener('mousemove', function(e) { - if (!modalIsDragging) return; - modalPanX = e.clientX - modalDragStart.x; - modalPanY = e.clientY - modalDragStart.y; - applyModalTransform(); - }); - document.addEventListener('mouseup', function() { - if (modalIsDragging) { - modalIsDragging = false; - mermaidModalDiagram.classList.remove('dragging'); - } - }); - - // Modal download buttons (operate on the currently displayed SVG) - document.getElementById('mermaid-modal-download-png').addEventListener('click', async function() { - if (!modalCurrentSvgEl) return; - const btn = this; - const original = btn.innerHTML; - btn.innerHTML = ''; - try { - // Use the original SVG (with dimensions) for proper PNG rendering - const canvas = await svgToCanvas(modalCurrentSvgEl); - canvas.toBlob(blob => { - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; a.download = `diagram-${Date.now()}.png`; a.click(); - URL.revokeObjectURL(url); - btn.innerHTML = ''; - setTimeout(() => { btn.innerHTML = original; }, 1500); - }, 'image/png'); - } catch (e) { - console.error('Modal PNG export failed:', e); - btn.innerHTML = original; - } - }); - - document.getElementById('mermaid-modal-copy').addEventListener('click', async function() { - if (!modalCurrentSvgEl) return; - const btn = this; - const original = btn.innerHTML; - btn.innerHTML = ''; - try { - const canvas = await svgToCanvas(modalCurrentSvgEl); - canvas.toBlob(async blob => { - try { - await navigator.clipboard.write([ - new ClipboardItem({ 'image/png': blob }) - ]); - btn.innerHTML = ' Copied!'; - } catch (clipErr) { - console.error('Clipboard write failed:', clipErr); - btn.innerHTML = ''; - } - setTimeout(() => { btn.innerHTML = original; }, 1800); - }, 'image/png'); - } catch (e) { - console.error('Modal copy failed:', e); - btn.innerHTML = original; - } - }); - - document.getElementById('mermaid-modal-download-svg').addEventListener('click', function() { - if (!modalCurrentSvgEl) return; - const serialized = new XMLSerializer().serializeToString(modalCurrentSvgEl); - const blob = new Blob([serialized], { type: 'image/svg+xml' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; a.download = `diagram-${Date.now()}.svg`; a.click(); - URL.revokeObjectURL(url); - }); - - /** - * Adds the hover toolbar to every rendered Mermaid container. - * Safe to call multiple times – existing toolbars are not duplicated. - */ - function addMermaidToolbars() { - markdownPreview.querySelectorAll('.mermaid-container').forEach(container => { - if (container.querySelector('.mermaid-toolbar')) return; // already added - const svgEl = container.querySelector('svg'); - if (!svgEl) return; // diagram not yet rendered - - const toolbar = document.createElement('div'); - toolbar.className = 'mermaid-toolbar'; - toolbar.setAttribute('aria-label', 'Diagram actions'); - - const btnZoom = document.createElement('button'); - btnZoom.className = 'mermaid-toolbar-btn'; - btnZoom.title = 'Zoom diagram'; - btnZoom.setAttribute('aria-label', 'Zoom diagram'); - btnZoom.innerHTML = ''; - btnZoom.addEventListener('click', () => openMermaidZoomModal(container)); - - const btnPng = document.createElement('button'); - btnPng.className = 'mermaid-toolbar-btn'; - btnPng.title = 'Download PNG'; - btnPng.setAttribute('aria-label', 'Download PNG'); - btnPng.innerHTML = ' PNG'; - btnPng.addEventListener('click', () => downloadMermaidPng(container, btnPng)); - - const btnCopy = document.createElement('button'); - btnCopy.className = 'mermaid-toolbar-btn'; - btnCopy.title = 'Copy image to clipboard'; - btnCopy.setAttribute('aria-label', 'Copy image to clipboard'); - btnCopy.innerHTML = ' Copy'; - btnCopy.addEventListener('click', () => copyMermaidImage(container, btnCopy)); - - const btnSvg = document.createElement('button'); - btnSvg.className = 'mermaid-toolbar-btn'; - btnSvg.title = 'Download SVG'; - btnSvg.setAttribute('aria-label', 'Download SVG'); - btnSvg.innerHTML = ' SVG'; - btnSvg.addEventListener('click', () => downloadMermaidSvg(container, btnSvg)); - - toolbar.appendChild(btnZoom); - toolbar.appendChild(btnCopy); - toolbar.appendChild(btnPng); - toolbar.appendChild(btnSvg); - container.appendChild(toolbar); - }); - } -}); diff --git a/desktop-app/setup-binaries.js b/desktop-app/setup-binaries.js index ac6816c..38ac8c5 100644 --- a/desktop-app/setup-binaries.js +++ b/desktop-app/setup-binaries.js @@ -23,7 +23,7 @@ const BIN_DIR = path.resolve(__dirname, "bin"); const VERSION_MARKER = path.join(BIN_DIR, ".version"); /** Neu CLI package — same version used across all npm scripts */ -const NEU_CLI = "@neutralinojs/neu@11.7.0"; +const NEU_CLI = "@neutralinojs/neu@11.7.1"; const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8")); const expectedVersion = config.cli.binaryVersion; diff --git a/desktop-app/src/desktop-main.js b/desktop-app/src/desktop-main.js new file mode 100644 index 0000000..7e11bc7 --- /dev/null +++ b/desktop-app/src/desktop-main.js @@ -0,0 +1,66 @@ +/* + Function to set up a system tray menu with options specific to the window mode. + This function checks if the application is running in window mode, and if so, + it defines the tray menu items and sets up the tray accordingly. +*/ +function setTray() { + // Tray menu is only available in window mode + if (NL_MODE != "window") { + console.log("INFO: Tray menu is only available in the window mode."); + return; + } + + // Define tray menu items + let tray = { + icon: "/resources/assets/icon.jpg", + menuItems: [ + { id: "VERSION", text: "Get version" }, + { id: "SEP", text: "-" }, + { id: "QUIT", text: "Quit" }, + ], + }; + + // Set the tray menu + Neutralino.os.setTray(tray); +} + +/* + Function to handle click events on the tray menu items. + This function performs different actions based on the clicked item's ID, + such as displaying version information or exiting the application. +*/ +function onTrayMenuItemClicked(event) { + switch (event.detail.id) { + case "VERSION": + // Display version information + Neutralino.os.showMessageBox( + "Version information", + `Neutralinojs server: v${NL_VERSION}\nNeutralinojs client: v${NL_CVERSION}\nOS Name: ${NL_OS}\nArchitecture: ${NL_ARCH}\nApplication ID: ${NL_APPID}\nApplication Version: ${NL_APPVERSION}\nPort: ${NL_PORT}\nMode: ${NL_MODE}\nNeutralinojs server: v${NL_VERSION}\nNeutralinojs client: v${NL_CVERSION}\nCurrent working directory: ${NL_CWD}\nApplication path: ${NL_PATH}\nApplication data path: ${NL_DATAPATH}\nCommand-line arguments: ${NL_ARGS}\nProcess ID: ${NL_PID}\nResource mode: ${NL_RESMODE}\nExtensions enabled: ${NL_EXTENABLED}\nFramework binary's release commit hash: ${NL_COMMIT}\nClient library's release commit hash: ${NL_CCOMMIT}\nCustom method identifiers: ${NL_CMETHODS}\nInitial window state was loaded from the saved configuration: ${NL_WSAVSTLOADED}\nUser System Locale: ${NL_LOCALE}\nData passed during the framework binary compilation via the NEU_COMPILATION_DATA definition in the BuildZri configuration file: ${NL_COMPDATA}`, + ); + break; + case "QUIT": + // Exit the application + Neutralino.app.exit(); + break; + } +} + +/* + Function to handle the window close event by gracefully exiting the Neutralino application. +*/ +function onWindowClose() { + Neutralino.app.exit(); +} + +// Initialize Neutralino +Neutralino.init(); + +// Register event listeners +Neutralino.events.on("trayMenuItemClicked", onTrayMenuItemClicked); +Neutralino.events.on("windowClose", onWindowClose); + +// Conditional initialization: Set up system tray if not running on macOS +if (NL_OS != "Darwin") { + // TODO: Fix https://github.com/neutralinojs/neutralinojs/issues/615 + setTray(); +} diff --git a/desktop-app/tag.sh b/desktop-app/tag.sh new file mode 100644 index 0000000..c8ea173 --- /dev/null +++ b/desktop-app/tag.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -euo pipefail + +echo "" +echo "@tag.sh - Utility script to calculate the next tag for the desktop app" +echo "---" + +DEFAULT_VERSION="$(date +"%Y.%-m.0")" +DEFAULT_TAG_NAME="desktop-v$DEFAULT_VERSION" + +# Get the latest tag for the current branch and prune deleted tags +TAG_NAME=$(git fetch --tags --prune --prune-tags && git tag -l --contains HEAD | tail -n1) + +# If no tag is found, create one using CalVer (Calendar Versioning) +if [ -z "$TAG_NAME" ]; then + echo "[WARNING] No tag found, creating one using CalVer (Calendar Versioning)" + # Use CalVer (Calendar Versioning) + # format: YYYY.M.P + # YYYY = Year, M = Month, P = Patch (Defaults to 0 if not specified) + # Example: 2026.2.0 + TAG_NAME="$DEFAULT_TAG_NAME" + +else # If a tag is found, determine the next tag + # Remove "desktop-v" prefix + TAG_NAME=${TAG_NAME#desktop-v} + + # Check if not from current month or year + if [ "$(echo "$TAG_NAME" | awk -F. '{print $2}')" != "$(date +"%-m")" ] || [ "$(echo "$TAG_NAME" | awk -F. '{print $1}')" != "$(date +"%Y")" ]; then + # Reset patch to 0 and set YYYY.M to current date + TAG_NAME="$DEFAULT_VERSION" + else + # Same month & year => only increment the patch number + TAG_NAME=$(echo "$TAG_NAME" | awk -F. '{$NF = $NF + 1; OFS="."; print}') + fi + # Add "desktop-v" prefix back + TAG_NAME="desktop-v$TAG_NAME" +fi + +# Get the current short commit-hash +COMMIT_HASH=$(git show -s --format=%h) + +# Print the tag and commit-hash +echo "TAG "$'\t'""$'\t '" | COMMIT" +echo "----------------- | --------" +echo "$TAG_NAME | $COMMIT_HASH" +echo "" +echo "To create and push the tag, run:" +echo "git tag \"$TAG_NAME\" && git push origin \"$TAG_NAME\"" diff --git a/index.html b/index.html deleted file mode 100644 index 550fee4..0000000 --- a/index.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Markdown Viewer - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-

Markdown Viewer

- - - -
-
- 0 Min Read -
-
- 0 Words -
-
- 0 Chars -
-
-
- - -
- - - -
- - -
- - - - - - - - - - - -
- - -
- - -
-
-
Menu
- -
- - -
-
- Documents - -
-
- -
- -
- - -
- - - -
- -
-
- 0 Min Read -
-
- 0 Words -
-
- 0 Chars -
-
- -
- - - - - - - - - - - - - - - - - -
-
- -
-
-
-
- - -
-
- -
- - - - - - - - - - -
- -

Drop your Markdown file here or click to browse

-
- -
-
- -
- - -
-
-
-
-
- - - - - - - - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6af3b8e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2693 @@ +{ + "name": "markdown-viewer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "markdown-viewer", + "version": "1.0.0", + "dependencies": { + "@popperjs/core": "^2.11.8", + "bootstrap": "^5.3.8", + "bootstrap-icons": "^1.13.1", + "dompurify": "^3.3.3", + "emoji-toolkit": "^9.0.1", + "file-saver": "^2.0.5", + "github-markdown-css": "^5.9.0", + "highlight.js": "^11.11.1", + "html2canvas": "^1.4.1", + "jspdf": "^2.5.1", + "marked": "^16.0.0", + "mermaid": "^11.4.1" + }, + "devDependencies": { + "vite": "^6.3.5" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz", + "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "12.0.0", + "@chevrotain/types": "12.0.0" + } + }, + "node_modules/@chevrotain/gast": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-12.0.0.tgz", + "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "12.0.0" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz", + "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-12.0.0.tgz", + "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-12.0.0.tgz", + "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==", + "license": "Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.1.tgz", + "integrity": "sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.2" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz", + "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==", + "license": "MIT", + "dependencies": { + "langium": "^4.0.0" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz", + "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT" + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/chevrotain": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz", + "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "12.0.0", + "@chevrotain/gast": "12.0.0", + "@chevrotain/regexp-to-ast": "12.0.0", + "@chevrotain/types": "12.0.0", + "@chevrotain/utils": "12.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.4.3.tgz", + "integrity": "sha512-2X4mkroolSMKqW+H22pyPMUVDqYZzPhephTmg/NODKb1IGYPHfxfhcW0EjS7wcPJNbze2i4vBWT7zT5FKF2lrQ==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.18.1" + }, + "peerDependencies": { + "chevrotain": "^12.0.0" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/cytoscape": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.3.tgz", + "integrity": "sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dompurify": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz", + "integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/emoji-toolkit": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/emoji-toolkit/-/emoji-toolkit-9.0.1.tgz", + "integrity": "sha512-sMMNqKNLVHXJfIKoPbrRJwtYuysVNC9GlKetr72zE3SSVbHqoeDLWVrxP0uM0AE0qvdl3hbUk+tJhhwXZrDHaw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/github-markdown-css": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.9.0.tgz", + "integrity": "sha512-tmT5sY+zvg2302XLYEfH2mtkViIM1SWf2nvYoF5N1ZsO0V6B2qZTiw3GOzw4vpjLygK/KG35qRlPFweHqfzz5w==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/jspdf": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz", + "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.5.4", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jspdf/node_modules/dompurify": { + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz", + "integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true + }, + "node_modules/katex": { + "version": "0.16.45", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz", + "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/langium": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.2.tgz", + "integrity": "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==", + "license": "MIT", + "dependencies": { + "@chevrotain/regexp-to-ast": "~12.0.0", + "chevrotain": "~12.0.0", + "chevrotain-allstar": "~0.4.1", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.1.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mermaid": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz", + "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.0", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "lodash-es": "^4.17.23", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e78aeac --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "markdown-viewer", + "version": "1.0.0", + "private": true, + "description": "Markdown Viewer web app with root npm scripts", + "scripts": { + "build": "vite build", + "dev": "vite", + "preview": "vite preview", + "start": "npm run dev", + "desktop:install": "npm --prefix desktop-app install", + "desktop:prepare": "npm run build && node desktop-app/prepare.js", + "desktop:dev": "npm --prefix desktop-app run dev", + "desktop:build": "npm --prefix desktop-app run build", + "skills:install": "node packages/claude-skills/install.js", + "skills:list": "node packages/claude-skills/install.js --list" + }, + "dependencies": { + "@popperjs/core": "^2.11.8", + "bootstrap": "^5.3.8", + "bootstrap-icons": "^1.13.1", + "dompurify": "^3.3.3", + "emoji-toolkit": "^9.0.1", + "file-saver": "^2.0.5", + "github-markdown-css": "^5.9.0", + "highlight.js": "^11.11.1", + "html2canvas": "^1.4.1", + "jspdf": "^2.5.1", + "marked": "^16.0.0", + "mermaid": "^11.4.1" + }, + "devDependencies": { + "vite": "^6.3.5" + } +} diff --git a/packages/claude-skills/commands/add-feature.md b/packages/claude-skills/commands/add-feature.md new file mode 100644 index 0000000..6a268f6 --- /dev/null +++ b/packages/claude-skills/commands/add-feature.md @@ -0,0 +1,19 @@ +Implement the following new feature in the Markdown Viewer: $ARGUMENTS + +## Rules to follow + +1. **All logic goes in `web/script.js`** inside the existing `DOMContentLoaded` closure. No new files, no modules, no global variables. +2. **If a UI element is needed**, add it to both: + - The desktop/tablet toolbar in `web/index.html` (`.toolbar` div and/or `.view-mode-group`) + - The mobile menu panel (`#mobile-menu-panel`) so mobile users get the same feature +3. **DOM references** belong at the top of the closure with the other `const` declarations (lines ~13–70 of script.js). +4. **Do not npm-install libraries.** If a library is needed, load it from CDN via a ` - - - - + + - - - - - - - -
-
-
-
-
+

Markdown Viewer

- +
@@ -100,22 +74,29 @@

Markdown Viewer

+ + - + - + - - - + @@ -151,22 +128,6 @@
Menu
- -
-
- Documents - -
-
- -
- -
-
- - - - + + + + + + + + + - - @@ -239,51 +214,6 @@
Menu
- -
-
- -
- - - - - - - - - -
- -