From 7b62c2e166e81c024347c2759b090a794b0f19c7 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 21 Mar 2026 17:57:04 +0100 Subject: [PATCH] Refactor how NickelMenu is set up --- .github/workflows/build.yml | 9 +- .gitignore | 2 +- README.md | 34 ++- koreader/setup.sh | 9 +- nickelmenu/setup.sh | 23 -- nixpacks.toml | 4 +- readerly/setup.sh | 24 ++ serve-locally.sh | 5 + tests/e2e/helpers/assets.js | 2 +- tests/e2e/integration.spec.js | 6 +- web/src/index.html | 44 +-- web/src/js/app.js | 109 ++++--- web/src/js/nickelmenu.js | 277 ------------------ web/src/js/strings.js | 7 - .../nickelmenu/features/custom-menu/.cog.png | Bin 0 -> 3028 bytes .../nickelmenu/features/custom-menu/index.js | 16 + web/src/nickelmenu/features/custom-menu/items | 39 +++ .../custom-menu/scripts/legibility_status.sh | 25 ++ .../scripts/toggle_wk_rendering.sh | 25 ++ web/src/nickelmenu/features/koreader/index.js | 42 +++ .../features/readerly-fonts/index.js | 27 ++ .../nickelmenu/features/screensaver/index.js | 12 + .../nickelmenu/features/screensaver/moon.png | Bin 0 -> 108482 bytes .../features/simplify-home/index.js | 15 + .../features/simplify-tabs/index.js | 21 ++ web/src/nickelmenu/installer.js | 180 ++++++++++++ web/src/nickelmenu/kobo-config.zip | Bin 0 -> 588104 bytes 27 files changed, 553 insertions(+), 404 deletions(-) create mode 100755 readerly/setup.sh delete mode 100644 web/src/js/nickelmenu.js create mode 100755 web/src/nickelmenu/features/custom-menu/.cog.png create mode 100644 web/src/nickelmenu/features/custom-menu/index.js create mode 100644 web/src/nickelmenu/features/custom-menu/items create mode 100755 web/src/nickelmenu/features/custom-menu/scripts/legibility_status.sh create mode 100644 web/src/nickelmenu/features/custom-menu/scripts/toggle_wk_rendering.sh create mode 100644 web/src/nickelmenu/features/koreader/index.js create mode 100644 web/src/nickelmenu/features/readerly-fonts/index.js create mode 100644 web/src/nickelmenu/features/screensaver/index.js create mode 100755 web/src/nickelmenu/features/screensaver/moon.png create mode 100644 web/src/nickelmenu/features/simplify-home/index.js create mode 100644 web/src/nickelmenu/features/simplify-tabs/index.js create mode 100644 web/src/nickelmenu/installer.js create mode 100644 web/src/nickelmenu/kobo-config.zip diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ef1365..8debb6d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,9 @@ jobs: with: node-version: '22' + - name: Install jq + run: sudo apt-get install -y jq + - name: Clone kobopatch source run: | cd kobopatch-wasm @@ -56,7 +59,7 @@ jobs: run: | if [[ "${{ github.ref }}" == refs/tags/* ]]; then echo "run=true" >> "$GITHUB_OUTPUT" - elif git diff --name-only HEAD~1 HEAD | grep -qE '^(kobopatch-wasm/|web/|tests/|nickelmenu/|koreader/)'; then + elif git diff --name-only HEAD~1 HEAD | grep -qE '^(kobopatch-wasm/|web/|tests/|nickelmenu/|koreader/|readerly/)'; then echo "run=true" >> "$GITHUB_OUTPUT" else echo "run=false" >> "$GITHUB_OUTPUT" @@ -82,6 +85,10 @@ jobs: if: steps.check-e2e.outputs.run == 'true' && env.GITEA_ACTIONS != 'true' run: koreader/setup.sh + - name: Set up Readerly assets + if: steps.check-e2e.outputs.run == 'true' && env.GITEA_ACTIONS != 'true' + run: readerly/setup.sh + - name: Full integration test (Playwright) if: steps.check-e2e.outputs.run == 'true' && env.GITEA_ACTIONS != 'true' run: | diff --git a/.gitignore b/.gitignore index 53a6e31..205195e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,8 @@ kobopatch-wasm/wasm_exec.js # Generated files in src (written by build scripts, regenerated on demand) web/src/js/wasm_exec.js web/src/nickelmenu/NickelMenu.zip -web/src/nickelmenu/kobo-config.zip web/src/koreader/ +web/src/readerly/ # Build output web/dist/ diff --git a/README.md b/README.md index b8bd8d3..71c4143 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,14 @@ A web application for customising Kobo e-readers. It supports two modes: - These changes are wiped when system updates are released. Requires re-patching when system updates are installed. - Gives you a lot of customization options, but not all of them may work correctly. +## Prerequisites + +- [Node.js](https://nodejs.org/) (includes npm) +- [jq](https://jqlang.github.io/jq/) — `brew install jq` / `apt install jq` +- [Git](https://git-scm.com/) + +Go is required for the WASM build but downloaded automatically if not installed. + ## How it works The app uses the **Filesystem Access API** (Chromium) to interface with connected Kobo devices, or falls back to manual model/software version selection with a downloadable ZIP on other browsers. @@ -44,7 +52,7 @@ web/ app.js # ES module entry point: step navigation, flow orchestration kobo-device.js # KoboModels, KoboDevice class kobo-software-urls.js # Fetches download URLs from JSON, getSoftwareUrl, getDevicesForVersion - nickelmenu.js # NickelMenuInstaller: downloads/bundles NickelMenu + config + nickelmenu/ # NickelMenu feature modules + installer orchestrator patch-ui.js # PatchUI: loads patches, parses YAML, renders toggle UI patch-runner.js # KoboPatchRunner: spawns Web Worker per build patch-worker.js # Web Worker: loads WASM, runs patchFirmware() @@ -54,9 +62,8 @@ web/ index.json # Available patch manifest downloads.json # Firmware download URLs by version/serial (may be auto-generated) patches_*.zip # Patch files per firmware version - nickelmenu/ # NickelMenu assets (generated by nickelmenu/setup.sh, gitignored) - NickelMenu.zip - kobo-config.zip + nickelmenu/ # NickelMenu assets (NickelMenu.zip generated by nickelmenu/setup.sh, gitignored) + readerly/ # Readerly font assets (generated by readerly/setup.sh, gitignored) koreader/ # KOReader assets (generated by koreader/setup.sh, gitignored) koreader-kobo.zip release.json @@ -64,12 +71,15 @@ web/ dist/ # Build output (gitignored, fully regenerable) bundle.js # esbuild output (minified, content-hashed) index.html # Generated with cache-busted references - css/ favicon/ patches/ nickelmenu/ koreader/ wasm/ js/wasm_exec.js + css/ favicon/ patches/ nickelmenu/ readerly/ koreader/ wasm/ js/wasm_exec.js build.mjs # esbuild build script + asset copy package.json # esbuild, jszip nickelmenu/ - setup.sh # Downloads NickelMenu.zip and bundles kobo-config.zip + setup.sh # Downloads NickelMenu.zip + +readerly/ + setup.sh # Downloads latest Readerly fonts from GitHub releases koreader/ setup.sh # Downloads latest KOReader release for Kobo @@ -122,7 +132,15 @@ cd kobopatch-wasm nickelmenu/setup.sh ``` -This downloads `NickelMenu.zip` and clones/updates the [kobo-config](https://github.com/nicoverbruggen/kobo-config) repo to bundle `kobo-config.zip` into `web/src/nickelmenu/`. +This downloads `NickelMenu.zip` into `web/src/nickelmenu/`. + +## Setting up Readerly font assets + +```bash +readerly/setup.sh +``` + +This downloads the latest [Readerly](https://github.com/nicoverbruggen/readerly) font release (`KF_Readerly.zip`) into `web/src/readerly/`. The fonts are served from the app's own domain and downloaded by the browser at install time. ## Setting up KOReader assets @@ -159,7 +177,7 @@ npm run dev # dev server with watch mode on :8889 This serves the app at `http://localhost:8888`. The script automatically: -1. Sets up NickelMenu assets if missing (`web/src/nickelmenu/`) +1. Sets up NickelMenu, KOReader, and Readerly assets if missing 2. Builds the JS bundle (`web/dist/bundle.js`) 3. Builds the WASM binary if missing (`web/dist/wasm/kobopatch.wasm`) diff --git a/koreader/setup.sh b/koreader/setup.sh index 423667e..3620fd3 100755 --- a/koreader/setup.sh +++ b/koreader/setup.sh @@ -8,8 +8,13 @@ mkdir -p "$PUBLIC_DIR" echo "Fetching latest KOReader release info..." RELEASE_JSON=$(curl -fsSL https://api.github.com/repos/koreader/koreader/releases/latest) -VERSION=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'])") -DOWNLOAD_URL=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; assets=json.load(sys.stdin)['assets']; print(next(a['browser_download_url'] for a in assets if 'koreader-kobo-' in a['name'] and a['name'].endswith('.zip')))") +VERSION=$(echo "$RELEASE_JSON" | jq -r '.tag_name') +DOWNLOAD_URL=$(echo "$RELEASE_JSON" | jq -r '.assets[] | select(.name | test("koreader-kobo-.*\\.zip$")) | .browser_download_url') + +if [ -z "$VERSION" ] || [ "$VERSION" = "null" ] || [ -z "$DOWNLOAD_URL" ] || [ "$DOWNLOAD_URL" = "null" ]; then + echo "Error: Could not find KOReader Kobo release" + exit 1 +fi echo "Downloading KOReader $VERSION..." curl -fL --progress-bar -o "$PUBLIC_DIR/koreader-kobo.zip" "$DOWNLOAD_URL" diff --git a/nickelmenu/setup.sh b/nickelmenu/setup.sh index 648ad07..22dca33 100755 --- a/nickelmenu/setup.sh +++ b/nickelmenu/setup.sh @@ -12,28 +12,5 @@ echo "Downloading NickelMenu.zip..." curl -fSL -o "$PUBLIC_DIR/NickelMenu.zip" "$NICKELMENU_URL" echo " -> $(du -h "$PUBLIC_DIR/NickelMenu.zip" | cut -f1)" -# --- kobo-config --- -KOBO_CONFIG_DIR="$SCRIPT_DIR/kobo-config" -if [ -d "$KOBO_CONFIG_DIR" ]; then - echo "Updating kobo-config..." - cd "$KOBO_CONFIG_DIR" - git pull -else - echo "Cloning kobo-config..." - git clone https://github.com/nicoverbruggen/kobo-config.git "$KOBO_CONFIG_DIR" -fi - -# Copy the relevant assets into a zip for the web app. -# Includes: .adds/, .kobo/screensaver/, fonts/ -echo "Bundling kobo-config.zip..." -cd "$KOBO_CONFIG_DIR" -zip -r "$PUBLIC_DIR/kobo-config.zip" \ - .adds/ \ - .kobo/screensaver/ \ - fonts/ \ - -x "*.DS_Store" - -echo " -> $(du -h "$PUBLIC_DIR/kobo-config.zip" | cut -f1)" - echo "" echo "Done. Assets written to: $PUBLIC_DIR" diff --git a/nixpacks.toml b/nixpacks.toml index 8c3cd58..1f75b0d 100644 --- a/nixpacks.toml +++ b/nixpacks.toml @@ -1,7 +1,7 @@ providers = ["node"] [phases.setup] -nixPkgs = ["git", "curl", "zip", "gnutar", "nginx", "nodejs", "gettext"] +nixPkgs = ["git", "curl", "zip", "gnutar", "nginx", "nodejs", "gettext", "jq"] paths = ["/usr/local/go/bin"] [phases.build] @@ -9,6 +9,8 @@ cmds = [ "cd kobopatch-wasm && bash setup.sh", "cd kobopatch-wasm && bash build.sh", "cd nickelmenu && bash setup.sh", + "cd koreader && bash setup.sh", + "cd readerly && bash setup.sh", "cd web && npm install && npm run build", ] diff --git a/readerly/setup.sh b/readerly/setup.sh new file mode 100755 index 0000000..f54613e --- /dev/null +++ b/readerly/setup.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PUBLIC_DIR="$SCRIPT_DIR/../web/src/readerly" + +mkdir -p "$PUBLIC_DIR" + +# Get latest release download URL for KF_Readerly.zip +echo "Fetching latest Readerly release..." +DOWNLOAD_URL=$(curl -fsSL https://api.github.com/repos/nicoverbruggen/readerly/releases/latest \ + | jq -r '.assets[] | select(.name == "KF_Readerly.zip") | .browser_download_url') + +if [ -z "$DOWNLOAD_URL" ] || [ "$DOWNLOAD_URL" = "null" ]; then + echo "Error: Could not find KF_Readerly.zip in latest release" + exit 1 +fi + +echo "Downloading KF_Readerly.zip..." +curl -fSL -o "$PUBLIC_DIR/KF_Readerly.zip" "$DOWNLOAD_URL" +echo " -> $(du -h "$PUBLIC_DIR/KF_Readerly.zip" | cut -f1)" + +echo "" +echo "Done. Assets written to: $PUBLIC_DIR" diff --git a/serve-locally.sh b/serve-locally.sh index 2a3703a..c806338 100755 --- a/serve-locally.sh +++ b/serve-locally.sh @@ -22,6 +22,11 @@ if [ ! -f "$SRC_DIR/koreader/koreader-kobo.zip" ]; then "$SCRIPT_DIR/koreader/setup.sh" fi +if [ ! -f "$SRC_DIR/readerly/KF_Readerly.zip" ]; then + echo "Readerly font assets not found, downloading..." + "$SCRIPT_DIR/readerly/setup.sh" +fi + echo "Building JS bundle..." cd "$WEB_DIR" npm install --silent diff --git a/tests/e2e/helpers/assets.js b/tests/e2e/helpers/assets.js index 53fa713..68f5dcd 100644 --- a/tests/e2e/helpers/assets.js +++ b/tests/e2e/helpers/assets.js @@ -4,7 +4,7 @@ const { WEBROOT, WEBROOT_FIRMWARE, FIRMWARE_PATH } = require('./paths'); function hasNickelMenuAssets() { return fs.existsSync(path.join(WEBROOT, 'nickelmenu', 'NickelMenu.zip')) - && fs.existsSync(path.join(WEBROOT, 'nickelmenu', 'kobo-config.zip')); + && fs.existsSync(path.join(WEBROOT, 'nickelmenu', 'features', 'custom-menu', 'items')); } function hasKoreaderAssets() { diff --git a/tests/e2e/integration.spec.js b/tests/e2e/integration.spec.js index 201c02f..ac59a2d 100644 --- a/tests/e2e/integration.spec.js +++ b/tests/e2e/integration.spec.js @@ -39,7 +39,7 @@ test.describe('NickelMenu', () => { await expect(page.locator('#nm-config-options')).not.toBeHidden(); // Verify default checkbox states - await expect(page.locator('input[name="nm-cfg-fonts"]')).toBeChecked(); + await expect(page.locator('input[name="nm-cfg-readerly-fonts"]')).toBeChecked(); await expect(page.locator('input[name="nm-cfg-screensaver"]')).not.toBeChecked(); await expect(page.locator('input[name="nm-cfg-simplify-tabs"]')).not.toBeChecked(); await expect(page.locator('input[name="nm-cfg-simplify-home"]')).not.toBeChecked(); @@ -256,8 +256,8 @@ test.describe('NickelMenu', () => { await expect(page.locator('#step-nm-review')).not.toBeHidden(); await expect(page.locator('#nm-review-list')).toContainText('NickelMenu'); await expect(page.locator('#nm-review-list')).toContainText('Readerly fonts'); - await expect(page.locator('#nm-review-list')).toContainText('Simplified tab menu'); - await expect(page.locator('#nm-review-list')).toContainText('Simplified homescreen'); + await expect(page.locator('#nm-review-list')).toContainText('Hide certain navigation tabs'); + await expect(page.locator('#nm-review-list')).toContainText('Hide certain home screen elements'); // Both buttons visible when device is connected await expect(page.locator('#btn-nm-write')).toBeVisible(); diff --git a/web/src/index.html b/web/src/index.html index d1ae406..9e8707b 100644 --- a/web/src/index.html +++ b/web/src/index.html @@ -177,49 +177,7 @@