Compare commits
1 Commits
fd8f0b74aa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53796f3b03 |
@@ -8,3 +8,7 @@ __pycache__
|
|||||||
.mypy_cache
|
.mypy_cache
|
||||||
README.md
|
README.md
|
||||||
build.sh
|
build.sh
|
||||||
|
|
||||||
|
# Rebuilt fresh in the frontend stage; never copy a local build into the image.
|
||||||
|
frontend/node_modules
|
||||||
|
static/vendor
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,3 +7,7 @@ __pycache__/
|
|||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# Frontend vendor bundle is produced at Docker build time (frontend/build.mjs).
|
||||||
|
node_modules/
|
||||||
|
static/vendor/
|
||||||
|
|||||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -7,6 +7,17 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.5.0] - 2026-07-05
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **All browser dependencies are now vendored into the image — nothing loads from a CDN at runtime.** A new `frontend/` esbuild step (run in a Node stage of the Dockerfile) bundles CodeMirror into a single self-contained `static/vendor/codemirror.js` and copies cropperjs + Tesseract.js (engine, wasm core, and the Dutch `nld` language pack) into `static/vendor/`. The page imports only from `/static/vendor/…`. Google Fonts (Material Symbols, Cinzel) is the one remaining external `<link>`; it degrades gracefully and isn't in the failing path.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Empty language and model dropdowns whenever esm.sh was degraded. The editor's top-level `import`s pulled `@codemirror/*` from esm.sh, whose transitive graph contained an **unpinned** `@codemirror/view@^6.27.0` range URL. During esm.sh's storage outage that range returned an error body as `text/plain`, so the browser blocked the module for a disallowed MIME type — and a single failed top-level import aborts the whole `<script type="module">`, so `fillLangs()`/`loadModels()` never ran despite a healthy backend. Vendoring removes every runtime CDN import, eliminating the failure mode entirely.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Vim editing mode (`@replit/codemirror-vim`) and its `Vim.mapCommand` H/L bindings. The document-level Shift-H/L focus cycling across the dropdowns/buttons is retained. One fewer dependency to vendor.
|
||||||
|
|
||||||
## [0.3.0] - 2026-06-09
|
## [0.3.0] - 2026-06-09
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
13
Dockerfile
13
Dockerfile
@@ -1,5 +1,17 @@
|
|||||||
# syntax=docker/dockerfile:1.7
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
# Frontend stage: vendor all browser dependencies (CodeMirror, cropperjs,
|
||||||
|
# Tesseract.js + wasm core + the Dutch language pack) into static/vendor so the
|
||||||
|
# running container serves everything locally — no esm.sh / CDN at runtime.
|
||||||
|
# Pinned to the build platform: the output is architecture-neutral browser
|
||||||
|
# assets, so there's no need to re-run it under emulation for each target arch.
|
||||||
|
FROM --platform=$BUILDPLATFORM node:22-slim AS frontend
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
COPY frontend/package.json frontend/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY frontend/ ./
|
||||||
|
RUN node build.mjs
|
||||||
|
|
||||||
# Build stage: install deps into a venv with uv.
|
# Build stage: install deps into a venv with uv.
|
||||||
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder
|
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder
|
||||||
|
|
||||||
@@ -22,6 +34,7 @@ WORKDIR /app
|
|||||||
COPY --from=builder /app/.venv /app/.venv
|
COPY --from=builder /app/.venv /app/.venv
|
||||||
COPY app.py ./
|
COPY app.py ./
|
||||||
COPY static ./static
|
COPY static ./static
|
||||||
|
COPY --from=frontend /app/static/vendor ./static/vendor
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
|
|||||||
77
frontend/build.mjs
Normal file
77
frontend/build.mjs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Vendors finrod's browser dependencies into ../static/vendor/ so the running
|
||||||
|
// container serves everything locally — no esm.sh / CDN dependency at runtime.
|
||||||
|
//
|
||||||
|
// node build.mjs
|
||||||
|
//
|
||||||
|
// Outputs:
|
||||||
|
// static/vendor/codemirror.js — esbuild bundle (view+state+commands)
|
||||||
|
// static/vendor/cropper.esm.js — cropperjs ESM (dependency-free)
|
||||||
|
// static/vendor/cropper.min.css — cropperjs stylesheet
|
||||||
|
// static/vendor/tesseract/tesseract.esm.min.js — Tesseract.js main thread
|
||||||
|
// static/vendor/tesseract/worker.min.js — Tesseract.js worker
|
||||||
|
// static/vendor/tesseract/tesseract-core*.{js,wasm} — wasm OCR core
|
||||||
|
// static/vendor/tesseract/lang/<lang>.traineddata.gz — default language packs
|
||||||
|
import * as esbuild from "esbuild";
|
||||||
|
import { createRequire } from "node:module";
|
||||||
|
import { cp, mkdir, rm, readdir, writeFile } from "node:fs/promises";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const OUT = join(here, "..", "static", "vendor");
|
||||||
|
|
||||||
|
// Only Dutch is baked in — finrod's real use is OCR'ing Dutch source text.
|
||||||
|
// Any other OCR language falls back to on-demand download from Tesseract's
|
||||||
|
// default CDN. These packs are served locally but only fetched by the browser
|
||||||
|
// when OCR is actually used (the OCR code path dynamic-imports Tesseract).
|
||||||
|
const LANGS = ["nld"];
|
||||||
|
const TESSDATA_BASE = "https://tessdata.projectnaptha.com/4.0.0";
|
||||||
|
|
||||||
|
const pkgDir = (name) => dirname(require.resolve(`${name}/package.json`));
|
||||||
|
|
||||||
|
await rm(OUT, { recursive: true, force: true });
|
||||||
|
await mkdir(join(OUT, "tesseract", "lang"), { recursive: true });
|
||||||
|
|
||||||
|
// 1. CodeMirror — bundle the whole graph into one ESM file.
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: [join(here, "src", "codemirror.js")],
|
||||||
|
bundle: true,
|
||||||
|
format: "esm",
|
||||||
|
minify: true,
|
||||||
|
legalComments: "none",
|
||||||
|
outfile: join(OUT, "codemirror.js"),
|
||||||
|
});
|
||||||
|
console.log("✓ codemirror.js");
|
||||||
|
|
||||||
|
// 2. cropperjs — ESM build + CSS, both dependency-free, just copy.
|
||||||
|
const cropper = pkgDir("cropperjs");
|
||||||
|
await cp(join(cropper, "dist", "cropper.esm.js"), join(OUT, "cropper.esm.js"));
|
||||||
|
await cp(join(cropper, "dist", "cropper.min.css"), join(OUT, "cropper.min.css"));
|
||||||
|
console.log("✓ cropperjs");
|
||||||
|
|
||||||
|
// 3. Tesseract.js — ship the prebuilt worker + wasm core (do NOT bundle; it
|
||||||
|
// spawns a Web Worker and streams wasm, which expects real dist files).
|
||||||
|
const tess = pkgDir("tesseract.js");
|
||||||
|
await cp(join(tess, "dist", "tesseract.esm.min.js"), join(OUT, "tesseract", "tesseract.esm.min.js"));
|
||||||
|
await cp(join(tess, "dist", "worker.min.js"), join(OUT, "tesseract", "worker.min.js"));
|
||||||
|
|
||||||
|
const core = pkgDir("tesseract.js-core");
|
||||||
|
for (const f of await readdir(core)) {
|
||||||
|
if (f.endsWith(".js") || f.endsWith(".wasm")) {
|
||||||
|
await cp(join(core, f), join(OUT, "tesseract", f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("✓ tesseract engine + core");
|
||||||
|
|
||||||
|
// Language packs — fetched at build time so the runtime image is self-contained.
|
||||||
|
for (const lang of LANGS) {
|
||||||
|
const url = `${TESSDATA_BASE}/${lang}.traineddata.gz`;
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
|
||||||
|
const buf = Buffer.from(await res.arrayBuffer());
|
||||||
|
await writeFile(join(OUT, "tesseract", "lang", `${lang}.traineddata.gz`), buf);
|
||||||
|
console.log(`✓ tessdata ${lang} (${(buf.length / 1e6).toFixed(1)} MB)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("vendor build complete →", OUT);
|
||||||
702
frontend/package-lock.json
generated
Normal file
702
frontend/package-lock.json
generated
Normal file
@@ -0,0 +1,702 @@
|
|||||||
|
{
|
||||||
|
"name": "finrod-frontend",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "finrod-frontend",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "6.7.1",
|
||||||
|
"@codemirror/state": "6.5.0",
|
||||||
|
"@codemirror/view": "6.36.1",
|
||||||
|
"cropperjs": "1.6.2",
|
||||||
|
"tesseract.js": "5.1.1",
|
||||||
|
"tesseract.js-core": "5.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "0.24.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands": {
|
||||||
|
"version": "6.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz",
|
||||||
|
"integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.4.0",
|
||||||
|
"@codemirror/view": "^6.27.0",
|
||||||
|
"@lezer/common": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language": {
|
||||||
|
"version": "6.12.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz",
|
||||||
|
"integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.5.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.36.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.1.tgz",
|
||||||
|
"integrity": "sha512-miD1nyT4m4uopZaDdO2uXU/LLHliKNYL9kB1C1wJHrunHLm/rpkb5QVSokqgw9hFqEZakrdlb/VGWX8aYZTslQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/common": {
|
||||||
|
"version": "1.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz",
|
||||||
|
"integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz",
|
||||||
|
"integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/bmp-js": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/cropperjs": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.2.tgz",
|
||||||
|
"integrity": "sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
|
||||||
|
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.24.2",
|
||||||
|
"@esbuild/android-arm": "0.24.2",
|
||||||
|
"@esbuild/android-arm64": "0.24.2",
|
||||||
|
"@esbuild/android-x64": "0.24.2",
|
||||||
|
"@esbuild/darwin-arm64": "0.24.2",
|
||||||
|
"@esbuild/darwin-x64": "0.24.2",
|
||||||
|
"@esbuild/freebsd-arm64": "0.24.2",
|
||||||
|
"@esbuild/freebsd-x64": "0.24.2",
|
||||||
|
"@esbuild/linux-arm": "0.24.2",
|
||||||
|
"@esbuild/linux-arm64": "0.24.2",
|
||||||
|
"@esbuild/linux-ia32": "0.24.2",
|
||||||
|
"@esbuild/linux-loong64": "0.24.2",
|
||||||
|
"@esbuild/linux-mips64el": "0.24.2",
|
||||||
|
"@esbuild/linux-ppc64": "0.24.2",
|
||||||
|
"@esbuild/linux-riscv64": "0.24.2",
|
||||||
|
"@esbuild/linux-s390x": "0.24.2",
|
||||||
|
"@esbuild/linux-x64": "0.24.2",
|
||||||
|
"@esbuild/netbsd-arm64": "0.24.2",
|
||||||
|
"@esbuild/netbsd-x64": "0.24.2",
|
||||||
|
"@esbuild/openbsd-arm64": "0.24.2",
|
||||||
|
"@esbuild/openbsd-x64": "0.24.2",
|
||||||
|
"@esbuild/sunos-x64": "0.24.2",
|
||||||
|
"@esbuild/win32-arm64": "0.24.2",
|
||||||
|
"@esbuild/win32-ia32": "0.24.2",
|
||||||
|
"@esbuild/win32-x64": "0.24.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/idb-keyval": {
|
||||||
|
"version": "6.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.6.tgz",
|
||||||
|
"integrity": "sha512-FY64UEhw+5liMzMQ1R9Mw6AF0+wyBrg1CIA1z4CjI/EvT5ty/SvQcWZgd8s9sgaNhX10Y8UzScTh89tEAls5nA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/is-electron": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/is-url": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/opencollective-postinstall": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"opencollective-postinstall": "index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/tesseract.js": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-lzVl/Ar3P3zhpUT31NjqeCo1f+D5+YfpZ5J62eo2S14QNVOmHBTtbchHm/YAbOOOzCegFnKf4B3Qih9LuldcYQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"bmp-js": "^0.1.0",
|
||||||
|
"idb-keyval": "^6.2.0",
|
||||||
|
"is-electron": "^2.2.2",
|
||||||
|
"is-url": "^1.2.4",
|
||||||
|
"node-fetch": "^2.6.9",
|
||||||
|
"opencollective-postinstall": "^2.0.3",
|
||||||
|
"regenerator-runtime": "^0.13.3",
|
||||||
|
"tesseract.js-core": "^5.1.1",
|
||||||
|
"wasm-feature-detect": "^1.2.11",
|
||||||
|
"zlibjs": "^0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tesseract.js-core": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/wasm-feature-detect": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zlibjs": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
frontend/package.json
Normal file
20
frontend/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "finrod-frontend",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"description": "Vendors finrod's browser dependencies into static/vendor so nothing loads from a CDN at runtime.",
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.mjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "6.7.1",
|
||||||
|
"@codemirror/state": "6.5.0",
|
||||||
|
"@codemirror/view": "6.36.1",
|
||||||
|
"cropperjs": "1.6.2",
|
||||||
|
"tesseract.js": "5.1.1",
|
||||||
|
"tesseract.js-core": "5.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "0.24.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
frontend/src/codemirror.js
Normal file
6
frontend/src/codemirror.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Single entry point re-exporting every CodeMirror symbol index.html needs.
|
||||||
|
// esbuild bundles this (and the whole transitive @codemirror/@lezer graph)
|
||||||
|
// into one self-contained ESM file — no runtime CDN, no semver-range URLs.
|
||||||
|
export { EditorView, keymap, placeholder } from "@codemirror/view";
|
||||||
|
export { EditorState } from "@codemirror/state";
|
||||||
|
export { history, defaultKeymap, historyKeymap } from "@codemirror/commands";
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "finrod"
|
name = "finrod"
|
||||||
version = "0.3.0"
|
version = "0.5.0"
|
||||||
description = "Translator UI backed by Ollama"
|
description = "Translator UI backed by Ollama"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -474,13 +474,6 @@
|
|||||||
}
|
}
|
||||||
.cm-scroller { padding: 0.6rem 0.75rem; }
|
.cm-scroller { padding: 0.6rem 0.75rem; }
|
||||||
.cm-content { caret-color: var(--accent); }
|
.cm-content { caret-color: var(--accent); }
|
||||||
/* vim's command-line / status hint inherits readable colors */
|
|
||||||
.cm-vim-panel {
|
|
||||||
background: var(--panel);
|
|
||||||
color: var(--fg);
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -550,10 +543,11 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { EditorView, keymap, placeholder } from "https://esm.sh/@codemirror/view@6";
|
import {
|
||||||
import { EditorState } from "https://esm.sh/@codemirror/state@6";
|
EditorView, keymap, placeholder,
|
||||||
import { history, defaultKeymap, historyKeymap } from "https://esm.sh/@codemirror/commands@6";
|
EditorState,
|
||||||
import { vim, Vim } from "https://esm.sh/@replit/codemirror-vim@6";
|
history, defaultKeymap, historyKeymap,
|
||||||
|
} from "/static/vendor/codemirror.js";
|
||||||
|
|
||||||
const LANGUAGES = [
|
const LANGUAGES = [
|
||||||
"English", "Portuguese (Brazil)", "Portuguese (Portugal)", "Spanish",
|
"English", "Portuguese (Brazil)", "Portuguese (Portugal)", "Spanish",
|
||||||
@@ -576,7 +570,7 @@
|
|||||||
const scanInput = document.getElementById("scan-input");
|
const scanInput = document.getElementById("scan-input");
|
||||||
const scanStatus = document.getElementById("scan-status");
|
const scanStatus = document.getElementById("scan-status");
|
||||||
|
|
||||||
// --- OCR via Tesseract.js (lazy-loaded on first use from esm.sh) --------
|
// --- OCR via Tesseract.js (vendored; lazy-loaded on first use) ----------
|
||||||
// Tesseract language codes are ISO 639-2 — map from our "From" dropdown.
|
// Tesseract language codes are ISO 639-2 — map from our "From" dropdown.
|
||||||
const TESS_LANGS = {
|
const TESS_LANGS = {
|
||||||
"English": "eng",
|
"English": "eng",
|
||||||
@@ -618,7 +612,7 @@
|
|||||||
scanStatus.classList.toggle("error", error);
|
scanStatus.classList.toggle("error", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crop + rotate modal. Lazy-loads cropperjs + its CSS from esm.sh on
|
// Crop + rotate modal. Lazy-loads the vendored cropperjs + its CSS on
|
||||||
// first use. Resolves with a Blob (cropped, rotation baked in) or null
|
// first use. Resolves with a Blob (cropped, rotation baked in) or null
|
||||||
// if the user cancelled.
|
// if the user cancelled.
|
||||||
let cropperCssLoaded = false;
|
let cropperCssLoaded = false;
|
||||||
@@ -626,11 +620,11 @@
|
|||||||
if (!cropperCssLoaded) {
|
if (!cropperCssLoaded) {
|
||||||
const link = document.createElement("link");
|
const link = document.createElement("link");
|
||||||
link.rel = "stylesheet";
|
link.rel = "stylesheet";
|
||||||
link.href = "https://esm.sh/cropperjs@1/dist/cropper.min.css";
|
link.href = "/static/vendor/cropper.min.css";
|
||||||
document.head.appendChild(link);
|
document.head.appendChild(link);
|
||||||
cropperCssLoaded = true;
|
cropperCssLoaded = true;
|
||||||
}
|
}
|
||||||
const { default: Cropper } = await import("https://esm.sh/cropperjs@1");
|
const { default: Cropper } = await import("/static/vendor/cropper.esm.js");
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const backdrop = document.createElement("div");
|
const backdrop = document.createElement("div");
|
||||||
@@ -850,10 +844,15 @@
|
|||||||
setScanStatus(`Preprocessed ${Math.max(w, h)}px (binarised)`);
|
setScanStatus(`Preprocessed ${Math.max(w, h)}px (binarised)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazy import — first scan triggers the ~5 MB JS + language pack
|
// Lazy import — the vendored engine, wasm core, and language pack are
|
||||||
// download; cached by the browser after that.
|
// only fetched from the container on first scan, then browser-cached.
|
||||||
const { createWorker } = await import("https://esm.sh/tesseract.js@5");
|
// Everything is served locally (workerPath/corePath/langPath) so OCR
|
||||||
|
// never depends on a CDN. Only the Dutch pack (nld) is bundled.
|
||||||
|
const { createWorker } = await import("/static/vendor/tesseract/tesseract.esm.min.js");
|
||||||
const worker = await createWorker(lang, 1, {
|
const worker = await createWorker(lang, 1, {
|
||||||
|
workerPath: "/static/vendor/tesseract/worker.min.js",
|
||||||
|
corePath: "/static/vendor/tesseract",
|
||||||
|
langPath: "/static/vendor/tesseract/lang",
|
||||||
logger: (m) => {
|
logger: (m) => {
|
||||||
if (m.status === "recognizing text") {
|
if (m.status === "recognizing text") {
|
||||||
setScanStatus(`OCR ${Math.round(m.progress * 100)}%`);
|
setScanStatus(`OCR ${Math.round(m.progress * 100)}%`);
|
||||||
@@ -975,18 +974,13 @@
|
|||||||
if (file) runOCR(file);
|
if (file) runOCR(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- CodeMirror editor (vim mode on real keyboards only) ----------------
|
// --- CodeMirror editor --------------------------------------------------
|
||||||
const isCoarsePointer = window.matchMedia("(pointer: coarse)").matches;
|
const isCoarsePointer = window.matchMedia("(pointer: coarse)").matches;
|
||||||
let typingTimer;
|
let typingTimer;
|
||||||
const editor = new EditorView({
|
const editor = new EditorView({
|
||||||
parent: document.getElementById("input"),
|
parent: document.getElementById("input"),
|
||||||
state: EditorState.create({
|
state: EditorState.create({
|
||||||
extensions: [
|
extensions: [
|
||||||
// vim() MUST be first per @replit/codemirror-vim docs so its keymap
|
|
||||||
// takes precedence. Skipped on touch devices — modal editing without
|
|
||||||
// a hardware Esc key on a virtual keyboard is unusable, and IME
|
|
||||||
// composition fights the vim keymap.
|
|
||||||
...(isCoarsePointer ? [] : [vim()]),
|
|
||||||
history(),
|
history(),
|
||||||
keymap.of([
|
keymap.of([
|
||||||
{ key: "Mod-Enter", run: () => { translate(); return true; } },
|
{ key: "Mod-Enter", run: () => { translate(); return true; } },
|
||||||
@@ -1214,10 +1208,8 @@
|
|||||||
goBtn.addEventListener("click", translate);
|
goBtn.addEventListener("click", translate);
|
||||||
|
|
||||||
// Shift-H / Shift-L cycle focus across controls (vim-style Shift-Tab /
|
// Shift-H / Shift-L cycle focus across controls (vim-style Shift-Tab /
|
||||||
// Tab). Two paths kept in sync:
|
// Tab), for focus on a select / button. Inside the editor they type
|
||||||
// 1. Document keydown listener — fires when focus is on a select / button
|
// normally.
|
||||||
// 2. Vim normal-mode mapping (below) — fires when focus is in the editor
|
|
||||||
// Together they make Shift-H/L work everywhere, including escaping the editor.
|
|
||||||
const focusables = [
|
const focusables = [
|
||||||
{ el: sourceSel, focus: () => sourceSel.focus() },
|
{ el: sourceSel, focus: () => sourceSel.focus() },
|
||||||
{ el: targetSel, focus: () => targetSel.focus() },
|
{ el: targetSel, focus: () => targetSel.focus() },
|
||||||
@@ -1239,23 +1231,12 @@
|
|||||||
if (!e.shiftKey) return;
|
if (!e.shiftKey) return;
|
||||||
const k = e.key.toLowerCase();
|
const k = e.key.toLowerCase();
|
||||||
if (k !== "h" && k !== "l") return;
|
if (k !== "h" && k !== "l") return;
|
||||||
// Inside the editor, vim's mapCommand handles H/L (see below). Letting
|
// Inside the editor, let H/L type normally.
|
||||||
// the event bubble here would double-jump.
|
|
||||||
if (editor.dom.contains(document.activeElement)) return;
|
if (editor.dom.contains(document.activeElement)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
moveFocus(k === "l" ? 1 : -1);
|
moveFocus(k === "l" ? 1 : -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Override vim's H/L (normally "viewport top/bottom") so they exit the
|
|
||||||
// editor to the previous/next control, matching the Shift-H/L contract
|
|
||||||
// the document handler uses elsewhere.
|
|
||||||
if (!isCoarsePointer) {
|
|
||||||
Vim.defineAction("finrodFocusPrev", () => moveFocus(-1));
|
|
||||||
Vim.defineAction("finrodFocusNext", () => moveFocus(1));
|
|
||||||
Vim.mapCommand("H", "action", "finrodFocusPrev", {}, { context: "normal" });
|
|
||||||
Vim.mapCommand("L", "action", "finrodFocusNext", {}, { context: "normal" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Side drawer (Settings) wiring — opens from the top-left hamburger.
|
// Side drawer (Settings) wiring — opens from the top-left hamburger.
|
||||||
const drawerEl = document.getElementById("drawer");
|
const drawerEl = document.getElementById("drawer");
|
||||||
const drawerBackdrop = document.getElementById("drawer-backdrop");
|
const drawerBackdrop = document.getElementById("drawer-backdrop");
|
||||||
|
|||||||
Reference in New Issue
Block a user