Skip to content

Add HTML5 article rendering with embedded content viewers#14548

Draft
rtibbles wants to merge 6 commits intolearningequality:developfrom
rtibbles:html_viewer_injection
Draft

Add HTML5 article rendering with embedded content viewers#14548
rtibbles wants to merge 6 commits intolearningequality:developfrom
rtibbles:html_viewer_injection

Conversation

@rtibbles
Copy link
Copy Markdown
Member

@rtibbles rtibbles commented Apr 7, 2026

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:

  • Content viewer composable API refactored so viewer components receive data via composables instead of props
  • New DOM element-based rendering path alongside existing iframe rendering
  • MediaPlayer Vuex module replaced with useMediaPlayer composable
  • VideoPlayer extracted from MediaPlayerIndex; new AudioPlayer with custom controls and inline transcript
  • Shared ViewerToolbar component replaces per-viewer toolbar implementations (HTML5, PDF, EPUB)
  • SafeHtml5Renderer updated to render HTML content with embedded media support and progress tracking

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 point
  • packages/kolibri/components/internal/ContentViewer/index.js — major rewrite from a simple wrapper to a render-function component with provide/inject context, viewer ID tracking, and DOM element file extraction
  • kolibri/core/content/hooks.py — new css_selectors and allow_object_tag on ContentRendererHook; the all_css_selectors classmethod uses module-level caching

Screenshots (desktop, 1280px)

State Screenshot
Article text rendering Article top
Embedded audio players Audio players
Embedded PDF viewers PDF viewers

Embedded video player

Screencast.From.2026-04-07.19-26-04.mp4

Sticky audio player

Screencast.From.2026-04-07.19-24-22.mp4

Screenshots (mobile, 412px)

State Screenshot
Article text Mobile article
Embedded audio Mobile audio
Embedded video Mobile video
Embedded PDF Mobile PDF
Embedded EPUB Mobile EPUB

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.

@github-actions github-actions bot added DEV: renderers HTML5 apps, videos, exercises, etc. DEV: backend Python, databases, networking, filesystem... APP: Learn Re: Learn App (content, quizzes, lessons, etc.) APP: Coach Re: Coach App (lessons, quizzes, groups, reports, etc.) DEV: frontend SIZE: very large labels Apr 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

@rtibbles rtibbles force-pushed the html_viewer_injection branch 7 times, most recently from 26cacd8 to 4735d6d Compare April 9, 2026 00:51
rtibbles and others added 6 commits April 13, 2026 16:16
…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>
@rtibbles rtibbles force-pushed the html_viewer_injection branch from 4735d6d to f00e02e Compare April 14, 2026 00:01
@github-actions
Copy link
Copy Markdown
Contributor

npm Package Versions

Warning

The following packages have changed files but no version bump:

Package Version Changed files
kolibri 0.18.0 14

If these changes affect published code, consider bumping the version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

APP: Coach Re: Coach App (lessons, quizzes, groups, reports, etc.) APP: Learn Re: Learn App (content, quizzes, lessons, etc.) DEV: backend Python, databases, networking, filesystem... DEV: frontend DEV: renderers HTML5 apps, videos, exercises, etc. SIZE: very large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate MediaPlayer from Vuex module to composable Allow HTML5 articles and general inclusion of rich text in HTML in content rendering

1 participant