Add HTML5 article rendering with embedded content viewers#14548
Add HTML5 article rendering with embedded content viewers#14548rtibbles wants to merge 5 commits into
Conversation
Build Artifacts
Smoke test screenshot |
4735d6d to
f00e02e
Compare
npm Package VersionsWarning The following packages have changed files but no version bump:
If these changes affect published code, consider bumping the version. |
d597e47 to
5da5ce9
Compare
rtibbles
left a comment
There was a problem hiding this comment.
Some things I need to update before undrafting.
| import useContentViewer, { contentViewerProps } from 'kolibri/composables/useContentViewer'; | ||
| import useContentViewer from 'kolibri/composables/useContentViewer'; | ||
|
|
||
| In order to log data about users viewing content, the component should emit ``startTracking``, ``updateProgress``, and ``stopTracking`` events, using the Vue ``$emit`` method. ``startTracking`` and ``stopTracking`` are emitted without any arguments, whereas ``updateProgress`` should be emitted with a single value between 0 and 1 representing the current proportion of progress on the content. |
There was a problem hiding this comment.
Question for reviewers - should I move the entire public API surface into the composable? Just make methods for startTracking, stopTracking and updateProgress and skip the emit altogether?
| allow_object_tag = False | ||
|
|
||
| @classmethod | ||
| def all_css_selectors(cls): |
There was a problem hiding this comment.
Using css_selectors allows for specific content viewers to only render certain DOM nodes if they have particular attributes present, for example.
| from le_utils.constants.file_formats import BLOOMPUB | ||
| from le_utils.constants.file_formats import H5P | ||
| from le_utils.constants.file_formats import HTML5 | ||
| from le_utils.constants.file_formats import HTML5_ARTICLE |
There was a problem hiding this comment.
Need to add this to allow larger media files to be loaded via the zip content backend from kpub files.
| :style="{ width: iframeWidth }" | ||
| @changeFullscreen="isInFullscreen = $event" | ||
| > | ||
| <div |
There was a problem hiding this comment.
We had copied this across HTML5, EPUB, PDF, and Bloompub - rule of three more than applied!
| class DocumentEPUBRenderAsset(content_hooks.ContentRendererHook): | ||
| bundle_id = "main" | ||
| presets = (format_presets.EPUB,) | ||
| allow_object_tag = True |
There was a problem hiding this comment.
We use this as a shorthand to let the <object> tag be used for these kinds of files (but we use the preset logic to work out which files in the object tag they should render and generate CSS selectors accordingly).
5da5ce9 to
fdb1287
Compare
fdb1287 to
e307c3c
Compare
…nents to use the viewer props, and instead direct all data via the composable api.
…ction Replace the Vuex store module with composables for instance-specific state management. Extract VideoPlayer and implement AudioPlayer with custom transport controls, sticky player, and inline transcript. Add useMediaProgress composable for shared progress tracking logic.
ContentViewer now generates unique viewer IDs and appends them to all interaction events, allowing parent components to track which embedded viewer emitted each event. SafeHtml5RendererIndex uses this to aggregate progress from multiple embedded viewers (e.g., videos in HTML5 content), combining scroll-based progress with embedded viewer progress using dynamic weighting.
Replace v-bind="$attrs" with explicit class="safe-html" and remove inheritAttrs: false, reducing the component API surface area. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
e307c3c to
4eb097a
Compare
Summary
Refactors the content viewer architecture to support rendering HTML content directly in the DOM (rather than exclusively in sandboxed iframes), and migrates the MediaPlayer from Vuex to composables.
Key changes:
useMediaPlayercomposableViewerToolbarcomponent replaces per-viewer toolbar implementations (HTML5, PDF, EPUB)References
Reviewer guidance
To test: load any HTML5 content, PDF, EPUB, video, or audio content and verify it renders and plays correctly. For HTML5 content with embedded media, use the Kolibri QA Channel under HTML5 > HTML5 Article.
HTML5 Articles with embedded content use a blended progress model: 50% from scroll progress, 50% from embedded content completion. The 50/50 split is arbitrary and may need tuning — worth scrutinizing whether this feels right in practice.
Implementation involved judgment calls that may deviate from the design specs — worth comparing against the original designs during review.
Risky areas:
packages/kolibri/internal/pluginMediator.js— viewer registration now supports DOM element viewers alongside preset-based viewers; this is the core extension pointpackages/kolibri/components/internal/ContentViewer/index.js— major rewrite from a simple wrapper to a render-function component withprovide/injectcontext, viewer ID tracking, and DOM element file extractionkolibri/core/content/hooks.py— newcss_selectorsandallow_object_tagonContentRendererHook; theall_css_selectorsclassmethod uses module-level cachingStandalone players (desktop)
Embedded in HTML5 article (desktop, 1280px)
Embedded video player
Screencast.From.2026-04-07.19-26-04.mp4
Sticky audio player
Screencast.From.2026-04-07.19-24-22.mp4
Embedded in HTML5 article (mobile, 412px)
AI usage
Implementation was done collaboratively with Claude Code (Opus). I directed the architecture and made design decisions; Claude helped with implementation, test writing, and iterating on the embedded content rendering approach. All code was reviewed and verified against a running dev server.