From 2f6c7293cba381994a921b018b56aab52535e217 Mon Sep 17 00:00:00 2001 From: erseco Date: Sun, 12 Jul 2026 17:04:52 +0100 Subject: [PATCH 1/3] Add Nextcloud Playground preview with ready-to-use demo Add `blueprint.json`, a GitHub workflow, and README section so the EPUB Viewer can be tried instantly in the browser via Nextcloud Playground (https://ateeducacion.github.io/nextcloud-playground/). - blueprint.json: installs `epubviewer` (pointing at canonical devnoname120/epubviewer for rewrite compatibility from forks), seeds a couple of sample EPUBs under Files, and lands on the Files view. - .github/workflows/playground-preview.yml: on push to master keeps a rolling `playground` release asset; on PRs builds the app, publishes a per-PR preview asset, rewrites the blueprint to target the PR zip via the CORS proxy, and posts a sticky "Preview this PR in the Nextcloud Playground" comment with a one-click link. Mirrors the pattern used in nextcloud-exelearning and the Moodle equivalent. - README.md: adds top badge + "Try in Nextcloud Playground" section documenting the zero-setup path and automatic PR previews. The static blueprint always targets the parent repository (devnoname120/epubviewer) so the URL-rewrite logic in the workflow (and future shared action) works correctly from forks and when this is later proposed upstream. See https://github.com/erseco/moodle-mod_questionnaire/pull/1 for the equivalent Moodle change. --- .github/workflows/playground-preview.yml | 167 +++++++++++++++++++++++ README.md | 12 ++ blueprint.json | 43 ++++++ 3 files changed, 222 insertions(+) create mode 100644 .github/workflows/playground-preview.yml create mode 100644 blueprint.json diff --git a/.github/workflows/playground-preview.yml b/.github/workflows/playground-preview.yml new file mode 100644 index 0000000..02c8382 --- /dev/null +++ b/.github/workflows/playground-preview.yml @@ -0,0 +1,167 @@ +name: Playground preview + +# Builds the app (after `npm run build` so the Vite bundles are present) into a +# ZIP that Nextcloud Playground can install via the blueprint `installApp` step. +# +# - push to master -> refresh the rolling `playground` prerelease asset +# (epubviewer.zip) that blueprint.json points at, so the +# README badge always boots the latest master. +# - pull_request -> publish a per-PR prerelease (playground-pr-) and post +# a one-click playground link as a sticky PR comment. +# - PR closed -> delete that per-PR preview release and its tag. + +on: + push: + branches: [master] + pull_request: + types: [opened, synchronize, reopened, closed] + +concurrency: + group: playground-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: write + +env: + PLAYGROUND_URL: https://ateeducacion.github.io/nextcloud-playground/ + +jobs: + build: + # Build on push to master, and on open/sync/reopen of PRs from this repo. + # Fork PRs run with a read-only token (no release write), so skip them. + if: >- + github.event_name == 'push' || + (github.event.action != 'closed' && + github.event.pull_request.head.repo.full_name == github.repository) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Resolve preview identity + id: id + run: | + set -euo pipefail + if [ "${{ github.event_name }}" = "push" ]; then + echo "tag=playground" >> "$GITHUB_OUTPUT" + echo "version=playground" >> "$GITHUB_OUTPUT" + else + echo "tag=playground-pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + echo "version=pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + fi + + - name: Build the app (Vite bundles + everything needed for viewer) + run: npm run build + + - name: Build the playground ZIP + run: | + set -euo pipefail + mkdir -p build/artifacts + # Zip the tree after build. Leading "epubviewer/" folder is stripped by installApp. + # Exclude only heavy/unneeded dev directories. + (cd .. && zip -qr epubviewer/build/artifacts/epubviewer.zip epubviewer \ + -x "epubviewer/node_modules/*" \ + -x "epubviewer/.git/*" \ + -x "epubviewer/build/*" \ + -x "epubviewer/tests/*" \ + -x "epubviewer/screenshots/*" \ + -x "epubviewer/.github/*") + ls -lh build/artifacts/epubviewer.zip + # Also place a copy at root for the release upload step (matching action-gh-release usage) + cp build/artifacts/epubviewer.zip epubviewer.zip + + - name: Publish epubviewer.zip to the preview release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.id.outputs.tag }} + name: ${{ steps.id.outputs.tag }} + prerelease: true + make_latest: false + files: epubviewer.zip + fail_on_unmatched_files: true + body: > + Automated Nextcloud Playground preview build (not a real release). + Installed in the browser via the blueprint `installApp` step. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Rewrite installApp URL + fixture raw URLs to point at this PR's release / commit + # then inline the whole blueprint as base64 (?blueprint=) so the link works + # without hosting per-PR blueprint files. We go through the CORS proxy because + # GitHub release assets are served from Azure without ACAO headers. + - name: Build PR playground link + if: github.event_name == 'pull_request' + id: link + run: | + set -euo pipefail + asset="https://github-proxy.exelearning.dev/?repo=${{ github.repository }}&release=${{ steps.id.outputs.tag }}&asset=epubviewer.zip" + jq --arg url "$asset" '(.steps[] | select(.step == "installApp")).url = $url' blueprint.json > blueprint.pr.json + # Point sample fixtures at this PR's commit so added/changed samples are visible in the preview. + sed -i "s#/devnoname120/epubviewer/master/tests/fixtures#/devnoname120/epubviewer/${{ github.event.pull_request.head.sha }}/tests/fixtures#g" blueprint.pr.json + # Also support the fork owner in case the static blueprint used it temporarily + sed -i "s#/erseco/epubviewer/master/tests/fixtures#/devnoname120/epubviewer/${{ github.event.pull_request.head.sha }}/tests/fixtures#g" blueprint.pr.json + b64=$(base64 -w0 blueprint.pr.json | tr '+/' '-_' | tr -d '=') + echo "url=${PLAYGROUND_URL}?blueprint=${b64}" >> "$GITHUB_OUTPUT" + + - name: Upsert PR comment + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + env: + PREVIEW_URL: ${{ steps.link.outputs.url }} + with: + script: | + const marker = ''; + const url = process.env.PREVIEW_URL; + const button = 'https://raw.githubusercontent.com/ateeducacion/nextcloud-playground/refs/heads/main/assets/playground-preview-button.svg'; + const body = [ + marker, + '### Preview this PR in the Nextcloud Playground', + '', + `Open this PR in the Nextcloud Playground`, + '', + "A fresh Nextcloud boots in your browser with this branch's `epubviewer` app installed and enabled (log in as `admin` / `admin`). " + + 'Sample EPUB files are seeded under `epub-samples/` in Files — click one to open the viewer and test the reader (EPUB, night mode, search, etc.).', + '', + 'The preview reflects the exact code and any new fixtures from this PR.', + ].join('\n'); + const { owner, repo } = context.repo; + const issue_number = context.payload.pull_request.number; + const comments = await github.paginate(github.rest.issues.listComments, { + owner, repo, issue_number, + }); + const existing = comments.find((c) => c.body && c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number, body }); + } + + cleanup: + if: >- + github.event_name == 'pull_request' && + github.event.action == 'closed' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Delete the per-PR preview release and tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + tag="playground-pr-${{ github.event.pull_request.number }}" + gh release delete "$tag" --cleanup-tag --yes || echo "No release $tag to delete." diff --git a/README.md b/README.md index 408eb6c..079fd56 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ## epubviewer +Open in Nextcloud Playground + Reader is an ebook reader based on pure javascript renderers. It works for publications formatted according to the following standards: @@ -23,6 +25,16 @@ quality, a set of image enhancement filters are provided. Comic pages may use JPEG, PNG, GIF, or WebP. TIFF pages are not supported. +## Try in Nextcloud Playground + +Click the badge above (or [this link](https://ateeducacion.github.io/nextcloud-playground/?blueprint-url=https://raw.githubusercontent.com/devnoname120/epubviewer/refs/heads/master/blueprint.json)) to open a full Nextcloud instance running entirely in your browser (via WebAssembly). It comes with the EPUB Viewer app pre-installed, admin logged in, and a few sample EPUB files ready in Files. + +No local setup or Docker required. The instance is provisioned from [`blueprint.json`](blueprint.json) at the repository root using the [`installApp`](https://github.com/ateeducacion/nextcloud-playground/blob/main/docs/blueprint-json.md#installapp) step. + +Every pull request automatically receives a "Preview this PR in the Nextcloud Playground" comment with a one-click link built from that branch's code (see [`.github/workflows/playground-preview.yml`](.github/workflows/playground-preview.yml)). Reviewers can test changes live with zero installation. + +The preview button and automation are modeled after the [Moodle Playground PR preview flow](https://github.com/erseco/moodle-mod_questionnaire/pull/1) and the approach used in sibling Nextcloud apps. + # Development ## GitHub codespaces diff --git a/blueprint.json b/blueprint.json new file mode 100644 index 0000000..1677583 --- /dev/null +++ b/blueprint.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://raw.githubusercontent.com/ateeducacion/nextcloud-playground/main/assets/blueprints/blueprint-schema.json", + "meta": { + "title": "EPUB Viewer Playground", + "author": "epubviewer", + "description": "A fresh Nextcloud in the browser with the EPUB Viewer app installed. Browse sample EPUB, PDF and CBR files directly in Files." + }, + "landingPage": "/index.php/apps/files/", + "siteOptions": { + "title": "Nextcloud + EPUB Viewer", + "locale": "en", + "timezone": "UTC" + }, + "admin": { + "username": "admin", + "password": "admin", + "email": "admin@example.com" + }, + "steps": [ + { "step": "disableApp", "app": "firstrunwizard" }, + { + "step": "installApp", + "appId": "epubviewer", + "url": "https://github-proxy.exelearning.dev/?repo=devnoname120/epubviewer&release=playground&asset=epubviewer.zip" + }, + { + "step": "writeFile", + "path": "data/admin/files/epub-samples/epub2-metadata-cover.epub", + "url": "https://raw.githubusercontent.com/devnoname120/epubviewer/master/tests/fixtures/epub/epub2-metadata-cover.epub" + }, + { + "step": "writeFile", + "path": "data/admin/files/epub-samples/epub3-cover-image-properties.epub", + "url": "https://raw.githubusercontent.com/devnoname120/epubviewer/master/tests/fixtures/epub/epub3-cover-image-properties.epub" + }, + { + "step": "writeFile", + "path": "data/admin/files/epub-samples/no-cover.epub", + "url": "https://raw.githubusercontent.com/devnoname120/epubviewer/master/tests/fixtures/epub/no-cover.epub" + }, + { "step": "runOcc", "args": ["files:scan", "admin"] } + ] +} From 64749bc4e4c8ec6c6264afdf826bdc61a8475503 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 12 Jul 2026 22:03:11 +0200 Subject: [PATCH 2/3] Declutter the README --- README.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/README.md b/README.md index 079fd56..a7b168d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -## epubviewer - -Open in Nextcloud Playground +## epubviewer    Open in Nextcloud Playground Reader is an ebook reader based on pure javascript renderers. It works for publications formatted according to the following standards: @@ -25,16 +23,6 @@ quality, a set of image enhancement filters are provided. Comic pages may use JPEG, PNG, GIF, or WebP. TIFF pages are not supported. -## Try in Nextcloud Playground - -Click the badge above (or [this link](https://ateeducacion.github.io/nextcloud-playground/?blueprint-url=https://raw.githubusercontent.com/devnoname120/epubviewer/refs/heads/master/blueprint.json)) to open a full Nextcloud instance running entirely in your browser (via WebAssembly). It comes with the EPUB Viewer app pre-installed, admin logged in, and a few sample EPUB files ready in Files. - -No local setup or Docker required. The instance is provisioned from [`blueprint.json`](blueprint.json) at the repository root using the [`installApp`](https://github.com/ateeducacion/nextcloud-playground/blob/main/docs/blueprint-json.md#installapp) step. - -Every pull request automatically receives a "Preview this PR in the Nextcloud Playground" comment with a one-click link built from that branch's code (see [`.github/workflows/playground-preview.yml`](.github/workflows/playground-preview.yml)). Reviewers can test changes live with zero installation. - -The preview button and automation are modeled after the [Moodle Playground PR preview flow](https://github.com/erseco/moodle-mod_questionnaire/pull/1) and the approach used in sibling Nextcloud apps. - # Development ## GitHub codespaces From 6f3ae5a42edfee4a5f4a936a3ae0b6752929c0b0 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 12 Jul 2026 22:51:35 +0200 Subject: [PATCH 3/3] Delete .github/workflows/playground-preview.yml --- .github/workflows/playground-preview.yml | 167 ----------------------- 1 file changed, 167 deletions(-) delete mode 100644 .github/workflows/playground-preview.yml diff --git a/.github/workflows/playground-preview.yml b/.github/workflows/playground-preview.yml deleted file mode 100644 index 02c8382..0000000 --- a/.github/workflows/playground-preview.yml +++ /dev/null @@ -1,167 +0,0 @@ -name: Playground preview - -# Builds the app (after `npm run build` so the Vite bundles are present) into a -# ZIP that Nextcloud Playground can install via the blueprint `installApp` step. -# -# - push to master -> refresh the rolling `playground` prerelease asset -# (epubviewer.zip) that blueprint.json points at, so the -# README badge always boots the latest master. -# - pull_request -> publish a per-PR prerelease (playground-pr-) and post -# a one-click playground link as a sticky PR comment. -# - PR closed -> delete that per-PR preview release and its tag. - -on: - push: - branches: [master] - pull_request: - types: [opened, synchronize, reopened, closed] - -concurrency: - group: playground-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -permissions: - contents: write - pull-requests: write - -env: - PLAYGROUND_URL: https://ateeducacion.github.io/nextcloud-playground/ - -jobs: - build: - # Build on push to master, and on open/sync/reopen of PRs from this repo. - # Fork PRs run with a read-only token (no release write), so skip them. - if: >- - github.event_name == 'push' || - (github.event.action != 'closed' && - github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Resolve preview identity - id: id - run: | - set -euo pipefail - if [ "${{ github.event_name }}" = "push" ]; then - echo "tag=playground" >> "$GITHUB_OUTPUT" - echo "version=playground" >> "$GITHUB_OUTPUT" - else - echo "tag=playground-pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" - echo "version=pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" - fi - - - name: Build the app (Vite bundles + everything needed for viewer) - run: npm run build - - - name: Build the playground ZIP - run: | - set -euo pipefail - mkdir -p build/artifacts - # Zip the tree after build. Leading "epubviewer/" folder is stripped by installApp. - # Exclude only heavy/unneeded dev directories. - (cd .. && zip -qr epubviewer/build/artifacts/epubviewer.zip epubviewer \ - -x "epubviewer/node_modules/*" \ - -x "epubviewer/.git/*" \ - -x "epubviewer/build/*" \ - -x "epubviewer/tests/*" \ - -x "epubviewer/screenshots/*" \ - -x "epubviewer/.github/*") - ls -lh build/artifacts/epubviewer.zip - # Also place a copy at root for the release upload step (matching action-gh-release usage) - cp build/artifacts/epubviewer.zip epubviewer.zip - - - name: Publish epubviewer.zip to the preview release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.id.outputs.tag }} - name: ${{ steps.id.outputs.tag }} - prerelease: true - make_latest: false - files: epubviewer.zip - fail_on_unmatched_files: true - body: > - Automated Nextcloud Playground preview build (not a real release). - Installed in the browser via the blueprint `installApp` step. - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Rewrite installApp URL + fixture raw URLs to point at this PR's release / commit - # then inline the whole blueprint as base64 (?blueprint=) so the link works - # without hosting per-PR blueprint files. We go through the CORS proxy because - # GitHub release assets are served from Azure without ACAO headers. - - name: Build PR playground link - if: github.event_name == 'pull_request' - id: link - run: | - set -euo pipefail - asset="https://github-proxy.exelearning.dev/?repo=${{ github.repository }}&release=${{ steps.id.outputs.tag }}&asset=epubviewer.zip" - jq --arg url "$asset" '(.steps[] | select(.step == "installApp")).url = $url' blueprint.json > blueprint.pr.json - # Point sample fixtures at this PR's commit so added/changed samples are visible in the preview. - sed -i "s#/devnoname120/epubviewer/master/tests/fixtures#/devnoname120/epubviewer/${{ github.event.pull_request.head.sha }}/tests/fixtures#g" blueprint.pr.json - # Also support the fork owner in case the static blueprint used it temporarily - sed -i "s#/erseco/epubviewer/master/tests/fixtures#/devnoname120/epubviewer/${{ github.event.pull_request.head.sha }}/tests/fixtures#g" blueprint.pr.json - b64=$(base64 -w0 blueprint.pr.json | tr '+/' '-_' | tr -d '=') - echo "url=${PLAYGROUND_URL}?blueprint=${b64}" >> "$GITHUB_OUTPUT" - - - name: Upsert PR comment - if: github.event_name == 'pull_request' - uses: actions/github-script@v7 - env: - PREVIEW_URL: ${{ steps.link.outputs.url }} - with: - script: | - const marker = ''; - const url = process.env.PREVIEW_URL; - const button = 'https://raw.githubusercontent.com/ateeducacion/nextcloud-playground/refs/heads/main/assets/playground-preview-button.svg'; - const body = [ - marker, - '### Preview this PR in the Nextcloud Playground', - '', - `Open this PR in the Nextcloud Playground`, - '', - "A fresh Nextcloud boots in your browser with this branch's `epubviewer` app installed and enabled (log in as `admin` / `admin`). " + - 'Sample EPUB files are seeded under `epub-samples/` in Files — click one to open the viewer and test the reader (EPUB, night mode, search, etc.).', - '', - 'The preview reflects the exact code and any new fixtures from this PR.', - ].join('\n'); - const { owner, repo } = context.repo; - const issue_number = context.payload.pull_request.number; - const comments = await github.paginate(github.rest.issues.listComments, { - owner, repo, issue_number, - }); - const existing = comments.find((c) => c.body && c.body.includes(marker)); - if (existing) { - await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); - } else { - await github.rest.issues.createComment({ owner, repo, issue_number, body }); - } - - cleanup: - if: >- - github.event_name == 'pull_request' && - github.event.action == 'closed' && - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Delete the per-PR preview release and tag - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - tag="playground-pr-${{ github.event.pull_request.number }}" - gh release delete "$tag" --cleanup-tag --yes || echo "No release $tag to delete."