.. _frontend: ******** Frontend ******** Django Cast ships a small JavaScript layer that powers interactive features: an audio player, an image gallery modal, and AJAX-based comments. Assets are built with `Vite `_ and integrated into Django templates via `django-vite `_. Page-level interactivity (pagination, gallery navigation) uses `HTMX `_. .. _frontend_architecture: Architecture Overview ===================== The JavaScript source lives in ``javascript/src/`` and is organized into three independent entry points: ``src/gallery/image-gallery-bs4.ts`` A custom element (````) that provides Bootstrap-modal gallery navigation with keyboard support. Used by the **bootstrap4** theme. ``src/audio/podlove-player.ts`` A custom element (````) that lazy-loads the `Podlove Web Player 5 `_ for audio playback. Used by all themes. ``src/comments/ajaxcomments.ts`` An IIFE script that intercepts comment form submissions and posts them via ``fetch``, with preview support, threaded replies, and error display. Loaded on post detail pages when comments are enabled. The **plain** theme does not use the ```` web component for galleries. Instead, it renders a pure HTMX-driven gallery modal (see :ref:`htmx_gallery_modal`). .. _frontend_pagination: Pagination ========== The blog index page comes with pagination support. You can set the number of posts per page using the ``POST_LIST_PAGINATION`` setting. If there are more than 3 pages, there will be a "..." in the pagination. If there are more than 10 pages, there will be two "..." in the pagination. .. _frontend_web_components: Web Components ============== .. _podlove_player_component: ```` -------------------- A custom HTML element that wraps the Podlove Web Player 5. It handles lazy loading, dark mode detection, and an optional click-to-load facade. Template Usage ^^^^^^^^^^^^^^ The element is rendered by the shared template ``cast/audio/audio.html``: .. code-block:: html+django {% if podlove_load_mode == "facade" %} {% endif %} Data Attributes ^^^^^^^^^^^^^^^ ``data-url`` (required) The API endpoint returning the Podlove episode JSON for this audio file. ``data-config`` URL for the player configuration (theme colors, fonts). Defaults to ``/api/audios/player_config/``. You can customize the theme per template base directory using the ``CAST_PODLOVE_PLAYER_THEMES`` setting. ``data-embed`` URL of the Podlove Web Player embed script. Defaults to the CDN version at ``https://cdn.podlove.org/web-player/5.x/embed.js``. Django Cast ships a local copy at ``cast/js/web-player/embed.5.js``. ``data-template`` Optional Podlove template name passed through to the player initialization. ``data-load-mode`` Controls how the player is initialized. Two values are supported: - ``"click"`` — displays a "Load player" button; the player loads only when clicked. Used on **list pages** to avoid loading multiple heavy player instances at once. - ``"facade"`` — the server renders a static preview (cover art, title, play button) inside the element. The player auto-initializes via ``IntersectionObserver`` and is injected into the existing container. Used on **post detail pages**. If omitted, the player auto-initializes with a plain placeholder. Initialization Behavior ^^^^^^^^^^^^^^^^^^^^^^^ **Auto-init** (no ``data-load-mode``, or ``data-load-mode="facade"``): 1. The element waits for the page ``load`` event. 2. A shared ``IntersectionObserver`` (with a 200 px root margin) watches the element. 3. When the element enters the viewport, initialization is scheduled via ``requestIdleCallback``. 4. The embed script is loaded (once, shared across all players on the page), then the Podlove player is created inside the element. In facade mode, the server-rendered preview (cover art, title, decorative play button and progress bar) is visible while the player loads. The JS skips creating its own placeholder when it detects a ``.podlove-player-container`` already present inside the element. The player ``
`` is appended into the existing container; the facade markup remains in the DOM unless hidden by CSS or overwritten by the player iframe. **Click-to-load** (``data-load-mode="click"``): 1. A placeholder container with a "Load player" button is rendered immediately. 2. Clicking the button bypasses the page-load wait and IntersectionObserver steps — it directly schedules initialization via ``requestIdleCallback``, then loads the embed script and creates the player. 3. On failure, an error message is shown and the button text changes to "Try again". Dark Mode ^^^^^^^^^ The player automatically detects dark mode by checking (in order): 1. ``data-bs-theme`` on ```` 2. ``data-theme`` on ```` 3. ``data-bs-theme`` on ```` 4. ``data-theme`` on ```` 5. ``prefers-color-scheme: dark`` media query When dark mode is detected, ``?color_scheme=dark`` is appended to the config URL so the server can return appropriate theme tokens. .. _image_gallery_component: ```` ----------------------- A custom HTML element that manages Bootstrap-modal image galleries with keyboard navigation. Used by the **bootstrap4** theme. Template Usage ^^^^^^^^^^^^^^ The element wraps gallery thumbnails and an associated Bootstrap modal. Each thumbnail is an ```` tag with a nested ```` and ````. The ```` carries data attributes that define the modal-size image sources and navigation links. .. code-block:: html+django Data Attributes ^^^^^^^^^^^^^^^ On ```` (AVIF variant for the modal): ``data-modal-src``, ``data-modal-srcset``, ``data-modal-sizes`` The AVIF image sources to use in the modal ```` element. On ```` (JPEG fallback and navigation): ``data-fullsrc`` The JPEG URL for the modal ```` ``src``. Note: the current template sets ``data-modal-src`` on ```` instead; the JS reads ``data-fullsrc``, so the modal ``src`` falls back to the placeholder while ``srcset`` (from ``data-modal-srcset``) provides the actual image. ``data-modal-srcset``, ``data-modal-sizes`` The JPEG srcset and sizes for the modal ````. ``data-modal-width``, ``data-modal-height`` Used to set the ``aspect-ratio`` CSS property on the modal image, preventing layout shift while the full-size image loads. ``data-prev``, ``data-next`` The ``id`` of the previous/next thumbnail ```` element, or ``"false"`` if at the boundary. These drive the Prev/Next navigation. ``data-full`` On the parent ```` tag. The original full-resolution image URL, used as the ``href`` on the modal image link. Keyboard Navigation ^^^^^^^^^^^^^^^^^^^ When the modal is open: - **ArrowLeft** navigates to the previous image - **ArrowRight** navigates to the next image - **Escape** closes the modal Loading States ^^^^^^^^^^^^^^ When navigating between images, a spinner overlay is shown while the new full-size image loads. A sequence counter ensures that only the most recent navigation request updates the display, preventing race conditions with slow-loading images. Bootstrap Compatibility ^^^^^^^^^^^^^^^^^^^^^^^ The component supports three Bootstrap modal APIs, tried in order: 1. **jQuery plugin** (``$(modal).modal("show")``) — legacy Bootstrap 4 with jQuery 2. **Bootstrap JS global** (``new bootstrap.Modal(el)``) — Bootstrap 4.6.2 UMD build 3. **CSS-only fallback** — manually toggles ``show`` class and ``display`` style when no Bootstrap JS is available .. _ajax_comments_component: AJAX Comments ------------- The comments script (``ajaxcomments.ts``) provides client-side comment posting without full page reloads. It is built as a standalone IIFE and loaded via a `` {% endif %} The script works on HTMX-navigated pages because it re-wraps forms on each submit, reply, and cancel interaction (not via HTMX lifecycle events). This means forms inserted after the initial page load are handled correctly. .. _frontend_vite_build: Vite Build Setup ================ JavaScript assets are built with Vite. There are two separate build configurations in the ``javascript/`` directory. Main Build (Gallery + Podlove Player) -------------------------------------- Configuration file: ``javascript/vite.config.ts`` .. code-block:: text Entry points: src/gallery/image-gallery-bs4.ts → main-.js src/audio/podlove-player.ts → podlovePlayer-.js Output: javascript/dist/ Format: ES modules (default Vite/Rollup output) Target: ES2015 ``npm run build`` writes to ``javascript/dist/``. The ``just js-build-vite`` command handles the full pipeline: it runs the Vite build, moves the manifest file from the ``.vite/`` subdirectory, and copies the output to ``src/cast/static/cast/vite/`` where Django's static files system can serve it. Templates include these assets using ``django-vite`` template tags: .. code-block:: html+django {% load django_vite %} {% vite_hmr_client app="cast" %} {% vite_asset 'src/gallery/image-gallery-bs4.ts' app="cast" %} {% vite_asset 'src/audio/podlove-player.ts' app="cast" %} In development (``DJANGO_VITE["cast"]["dev_mode"] = True``), the template tags point to the Vite dev server on port 5173. In production, they resolve asset paths from the manifest file. Comments Build -------------- Configuration file: ``javascript/vite.comments.config.ts`` .. code-block:: text Entry point: src/comments/ajaxcomments.ts → ajaxcomments.js Output: src/cast/static/fluent_comments/js/ Format: IIFE (immediately invoked function expression) Target: ES2015 The comments build outputs directly into the static files directory. It uses IIFE format (not ES modules) so it works as a simple ``