Skip to content

Initial setup for HTML Article Renderer plugin #13274

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 2, 2025

Conversation

rtibbles
Copy link
Member

@rtibbles rtibbles commented Mar 28, 2025

Summary

  • Adds a SafeHTML functional component for rendering sanitized HTML
  • Adds a new SafeHTML Viewer plugin that uses this component to load HTML content from a new KPUB file format

References

This will require learningequality/le-utils#163 to be merged and a new version released, then for the version update to be propagated here, in order for this to be fully functional.

Reviewer guidance

To test this, the new plugin needs to be explicitly enabled using the command line:

kolibri plugin enable kolibri.plugins.safe_html5_viewer

Then you can import the Kolibri QA Channel, specifically the All About Hummingbirds HTML5 resource, which uses this new renderer.

Summary by CodeRabbit

  • New Features

    • Introduced a new Safe HTML5 Viewer for securely displaying HTML5 content within the application.
    • Added a new content renderer module and Vue component for loading and rendering zipped HTML5 apps safely.
    • Implemented a functional component to sanitize and render HTML content using enhanced security measures.
  • Enhancements

    • Expanded supported file types and content presets for improved consistency and validation.
    • Updated styles for rendered HTML content for better readability.
  • Chores

    • Added new dependencies and updated existing ones to support new features.

Copy link
Contributor

github-actions bot commented Mar 29, 2025

@rtibbles rtibbles force-pushed the html_article_renderer branch from 0f72a24 to 9da0943 Compare March 30, 2025 19:53
@github-actions github-actions bot added DEV: renderers HTML5 apps, videos, exercises, etc. DEV: backend Python, databases, networking, filesystem... DEV: frontend labels Mar 30, 2025
@rtibbles rtibbles force-pushed the html_article_renderer branch 3 times, most recently from f2c2546 to dd41f25 Compare April 1, 2025 16:27
@rtibbles rtibbles force-pushed the html_article_renderer branch from dd41f25 to bda0c45 Compare April 16, 2025 23:20
@rtibbles rtibbles marked this pull request as ready for review April 16, 2025 23:24
@rtibbles rtibbles force-pushed the html_article_renderer branch from bda0c45 to 563befa Compare April 19, 2025 06:18
@rtibbles rtibbles force-pushed the html_article_renderer branch from 563befa to 970b811 Compare May 5, 2025 15:39
Copy link
Contributor

coderabbitai bot commented May 5, 2025

Walkthrough

This update introduces a new internal plugin, safe_html5_viewer, to the Kolibri platform. The plugin adds support for safely rendering zipped HTML5 content using a Vue-based renderer that sanitizes HTML with DOMPurify and tracks user progress. The update includes new frontend components, a build configuration, and plugin registration files. Additionally, a database migration expands and standardizes allowed values for file presets and extensions in the content models. The le-utils dependency is updated, and a new dependency on dompurify is added for secure HTML sanitization.

Changes

File(s) Change Summary
kolibri/__init__.py Added "kolibri.plugins.safe_html5_viewer" to INTERNAL_PLUGINS.
kolibri/core/content/migrations/0040_html_article_constants.py Migration: Expanded and standardized choices for preset and extension fields in content models.
kolibri/plugins/safe_html5_viewer/assets/src/module.js Added SafeHTML5Module class extending ContentRendererModule and exported its instance.
kolibri/plugins/safe_html5_viewer/assets/src/views/SafeHtml5RendererIndex.vue Introduced SafeHtml5RendererIndex Vue component for safely rendering zipped HTML5 content and tracking progress.
kolibri/plugins/safe_html5_viewer/buildConfig.js Added build configuration for the plugin, specifying bundle ID and entry point.
kolibri/plugins/safe_html5_viewer/kolibri_plugin.py Registered new plugin (SafeHTML5AppPlugin) and content renderer hook (SafeHTML5AppAsset).
packages/kolibri-common/components/SafeHTML/index.js Added functional Vue component SafeHTML for sanitized HTML rendering using DOMPurify.
packages/kolibri-common/components/SafeHTML/style.scss Added styles for .safe-html class, targeting direct child h1 elements.
packages/kolibri-common/package.json Added dompurify dependency.
requirements/base.txt Updated le-utils version from 0.2.9 to 0.2.10.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SafeHtml5RendererIndex (Vue)
    participant kolibri-zip
    participant SafeHTML (Vue)

    User ->> SafeHtml5RendererIndex (Vue): Mount component with storageUrl
    SafeHtml5RendererIndex (Vue) ->> kolibri-zip: Open zip archive from storageUrl
    kolibri-zip -->> SafeHtml5RendererIndex (Vue): Return zip contents
    SafeHtml5RendererIndex (Vue) ->> SafeHtml5RendererIndex (Vue): Extract and read entry HTML file
    SafeHtml5RendererIndex (Vue) ->> SafeHTML (Vue): Pass HTML content
    SafeHTML (Vue) ->> SafeHTML (Vue): Sanitize HTML with DOMPurify
    SafeHTML (Vue) -->> SafeHtml5RendererIndex (Vue): Render sanitized HTML
    SafeHtml5RendererIndex (Vue) -->> User: Display HTML content
    SafeHtml5RendererIndex (Vue) ->> SafeHtml5RendererIndex (Vue): Poll for progress
    SafeHtml5RendererIndex (Vue) -->> User: Emit progress events (startTracking, updateProgress, finished, stopTracking)
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (5)
packages/kolibri-common/components/SafeHTML/index.js (2)

32-44: Consider preserving existing class attributes instead of overwriting them.

Currently, line 38 sets the class attribute to 'safe-html', which will overwrite any existing classes on elements in the sanitized HTML. Consider preserving existing classes:

- attributes.class = 'safe-html';
+ attributes.class = (attributes.class ? attributes.class + ' safe-html' : 'safe-html');

26-31: Add error handling for HTML parsing.

The component doesn't include error handling if parseFromString fails or returns invalid content. Consider adding try/catch blocks or validation of the parsed document.

- const doc = parser.parseFromString(sanitizedHTML, 'text/html');
+ let doc;
+ try {
+   doc = parser.parseFromString(sanitizedHTML, 'text/html');
+   // Verify parsing was successful
+   if (doc.body === null) {
+     return [];
+   }
+ } catch (e) {
+   console.error('Error parsing HTML:', e);
+   return [];
+ }
kolibri/plugins/safe_html5_viewer/assets/src/views/SafeHtml5RendererIndex.vue (3)

80-84: Improve polling mechanism by adding a property for polling interval.

The polling interval is hardcoded to 5000ms. It would be better to define this as a property to make it configurable.

+ computed: {
+   // Other computed properties...
+   pollingInterval() {
+     return (this.options && this.options.pollingInterval) || 5000;
+   },
+ },

  methods: {
    // Other methods...
    pollProgress() {
      this.timeout = setTimeout(() => {
        this.recordProgress();
-     }, 5000);
+     }, this.pollingInterval);
    },
  },

49-56: Initialize startTime in the created lifecycle hook.

If you implement the durationBasedProgress as suggested, you should initialize the startTime in the created hook rather than in data.

  async created() {
+   this.startTime = Date.now();
    try {
      const storageUrl = this.defaultFile.storage_url;
      const zipFile = new ZipFile(storageUrl);
      const entryHtmlFile = await zipFile.file(this.entry);
      this.html = entryHtmlFile.toString();
      this.loading = false;
      this.$emit('startTracking');
      this.pollProgress();
    } catch (error) {
      console.error('Error loading HTML content:', error);
      this.loading = false;
    }
  },

4-12: Consider adding accessibility attributes to the loader.

For better accessibility, you might want to add an aria-label to the loader.

  <KCircularLoader
    v-if="loading || !html"
    :delay="false"
    class="loader"
+   aria-label="Loading content"
  />
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08d6a2e and 970b811.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • kolibri/__init__.py (1 hunks)
  • kolibri/core/content/migrations/0040_html_article_constants.py (1 hunks)
  • kolibri/plugins/safe_html5_viewer/assets/src/module.js (1 hunks)
  • kolibri/plugins/safe_html5_viewer/assets/src/views/SafeHtml5RendererIndex.vue (1 hunks)
  • kolibri/plugins/safe_html5_viewer/buildConfig.js (1 hunks)
  • kolibri/plugins/safe_html5_viewer/kolibri_plugin.py (1 hunks)
  • packages/kolibri-common/components/SafeHTML/index.js (1 hunks)
  • packages/kolibri-common/components/SafeHTML/style.scss (1 hunks)
  • packages/kolibri-common/package.json (1 hunks)
  • requirements/base.txt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (25)
  • GitHub Check: Morango Integration Tests for currently supported Python versions (3.13)
  • GitHub Check: Morango Integration Tests for currently supported Python versions (3.12)
  • GitHub Check: Morango Integration Tests for EOL Python versions (3.8)
  • GitHub Check: Morango Integration Tests for currently supported Python versions (3.11)
  • GitHub Check: Morango Integration Tests for EOL Python versions (3.7)
  • GitHub Check: Licenses check
  • GitHub Check: Morango Integration Tests for currently supported Python versions (3.10)
  • GitHub Check: Morango Integration Tests for EOL Python versions (3.6)
  • GitHub Check: Morango Integration Tests for currently supported Python versions (3.9)
  • GitHub Check: Python unit tests for currently supported Python versions (3.13)
  • GitHub Check: Python unit tests for currently supported Python versions (3.12)
  • GitHub Check: No zombies
  • GitHub Check: Morango Integration Tests with PostgreSQL
  • GitHub Check: Python unit tests for EOL Python versions (3.8)
  • GitHub Check: Python unit tests for currently supported Python versions (3.11)
  • GitHub Check: Python unit tests on Windows Server (3.8)
  • GitHub Check: Python unit tests for EOL Python versions (3.7)
  • GitHub Check: Python unit tests for currently supported Python versions (3.10)
  • GitHub Check: Python unit tests for EOL Python versions (3.6)
  • GitHub Check: Python unit tests for currently supported Python versions (3.9)
  • GitHub Check: Python postgres unit tests
  • GitHub Check: Frontend tests
  • GitHub Check: Python unit tests on Mac OS (3.10)
  • GitHub Check: All file linting
  • GitHub Check: Build WHL file / Build WHL
🔇 Additional comments (10)
packages/kolibri-common/package.json (1)

13-13: Verify DOMPurify dependency and lockfile updates.

Ensure that adding "dompurify": "^3.2.4" is intentional and that your lockfile (yarn.lock/package-lock.json) has been updated accordingly. Also confirm that the installed version of DOMPurify is compatible with your Vue version and the build pipeline.

requirements/base.txt (1)

13-13:

✅ Verification successful

Confirm le-utils version availability.

Verify that le-utils==0.2.10 has been published on PyPI and matches the new content format presets required by the plugin. You can run:

and check that "0.2.10" appears in the list.


🏁 Script executed:

#!/bin/bash
curl -s https://pypi.org/pypi/le-utils/json | jq '.releases | keys'

Length of output: 1195


le-utils==0.2.10 is available on PyPI
Verified via curl -s https://pypi.org/pypi/le-utils/json | jq '.releases | keys'—“0.2.10” is listed.
No changes needed.

kolibri/__init__.py (1)

43-45:

✅ Verification successful

Approve plugin registration; verify file presence.

The entry "kolibri.plugins.safe_html5_viewer" is correctly appended to INTERNAL_PLUGINS. Ensure that kolibri/plugins/safe_html5_viewer/kolibri_plugin.py exists and exports the expected plugin class.

Run:

to confirm the plugin file is present.


🏁 Script executed:

#!/bin/bash
fd kolibri_plugin.py kolibri/plugins/safe_html5_viewer

Length of output: 108


🏁 Script executed:

#!/bin/bash
grep -R "class " -n kolibri/plugins/safe_html5_viewer/kolibri_plugin.py

Length of output: 183


Plugin registration verified

  • kolibri/plugins/safe_html5_viewer/kolibri_plugin.py exists.
  • Defines class SafeHTML5AppPlugin(KolibriPluginBase), confirming the expected plugin export.

No further changes required.

kolibri/plugins/safe_html5_viewer/buildConfig.js (1)

1-6:

❓ Verification inconclusive

Validate build configuration.

The bundle_id and entry look correct for bundling your plugin’s assets. Please verify that your build system picks up this config and that any required loaders (e.g., for Vue SFCs) are registered elsewhere in the project.

You can confirm the bundle reference with:


🏁 Script executed:

#!/bin/bash
rg "bundle_id: 'main'" -n kolibri/plugins/safe_html5_viewer

Length of output: 139


Verify webpack integration and loader configuration

  • Confirm that kolibri/plugins/safe_html5_viewer/buildConfig.js is picked up by your project’s main webpack setup (e.g. in webpack.config.js or the plugin loader).
  • Ensure the entry: './assets/src/module.js' path resolves correctly at build time.
  • If you’re bundling Vue Single-File Components, verify that a rule for .vue files (using vue-loader) is registered in your webpack rules.
kolibri/plugins/safe_html5_viewer/assets/src/module.js (1)

1-12: Implementation looks good and follows the Kolibri renderer module pattern.

The module structure correctly extends ContentRendererModule, provides the appropriate renderer component, and exports an instance following Kolibri's conventions.

kolibri/plugins/safe_html5_viewer/kolibri_plugin.py (1)

1-15: Plugin structure and hook registration look correct.

The plugin class and content renderer hook are properly defined following Kolibri's plugin architecture. The hook correctly associates the renderer with the KPUB_ZIP preset content type.

packages/kolibri-common/components/SafeHTML/index.js (2)

4-15: Good security configuration for DOMPurify.

The configuration correctly restricts potentially dangerous elements like style tags and attributes, which helps prevent XSS attacks.


4-4:

❓ Verification inconclusive

Verify if the URI restriction is intentionally strict.

The current regex only allows blob URLs. Is this intentional? If the component needs to support other URL schemes (http, https, data), the regex should be expanded.


🏁 Script executed:

#!/bin/bash
# Check where this component is used to understand URL requirements
echo "Searching for imports or uses of SafeHTML component:"
rg -A 3 "SafeHTML" --type js --type vue

echo "\nSearching for potential URI usage patterns in HTML content:"
rg -A 2 "src=|href=" --type html

Length of output: 4336


I need to inspect the SafeHTML implementation to confirm which schemes are allowed versus how it’s used elsewhere. Let me pull in the relevant portion of packages/kolibri-common/components/SafeHTML/index.js.


Let’s pull in the SafeHTML implementation so we can see how the ALLOWED_URI_REGEXP is used and whether other schemes are intentionally excluded:


🏁 Script executed:

#!/bin/bash
# Show the regex definition and its surrounding logic:
rg -C5 "const ALLOWED_URI_REGEXP" -n packages/kolibri-common/components/SafeHTML/index.js

# Show all references to ALLOWED_URI_REGEXP in that file:
rg -C5 "ALLOWED_URI_REGEXP" -n packages/kolibri-common/components/SafeHTML/index.js

# Find all imports/usages of SafeHTML across JS/TS files:
rg -n "SafeHTML" --glob "*.{js,ts}" .

Length of output: 1320


Confirm blob-only URI restriction

The ALLOWED_URI_REGEXP is currently set to /^blob:https?:/i, which only permits blob:http: and blob:https: URLs. If you intend for SafeHTML to support other schemes (e.g. http://, https://, data:), please extend this regex accordingly.

• packages/kolibri-common/components/SafeHTML/index.js:4

kolibri/core/content/migrations/0040_html_article_constants.py (1)

1-85: Migration correctly expands content type presets and file extensions.

The migration properly adds support for the KPUB format while maintaining existing content types. The structure follows Django migration best practices.

kolibri/plugins/safe_html5_viewer/assets/src/views/SafeHtml5RendererIndex.vue (1)

93-97: LGTM! The loader positioning is appropriately centered.

The CSS for positioning the loader is correctly implemented using absolute positioning with calculated offsets.

Comment on lines +69 to +73
} else {
// TODO: Handle progress tracking based on how
// much of the article has been scrolled through
progress = this.scrollBasedProgress;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement scroll-based progress tracking.

There's a TODO comment about scroll-based progress tracking, but it's currently hardcoded to 0.5.

Consider implementing a proper scroll-based progress calculation:

- // TODO: Handle progress tracking based on how
- // much of the article has been scrolled through
- progress = this.scrollBasedProgress;
+ progress = this.calculateScrollProgress();

And add a method to calculate the scroll progress:

calculateScrollProgress() {
  if (!this.$el) return 0.5;
  
  const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
  if (scrollHeight <= 0) return 1;
  
  const scrollPosition = window.scrollY;
  return Math.min(scrollPosition / scrollHeight, 1);
}

Don't forget to add a scroll event listener in the mounted hook:

mounted() {
  window.addEventListener('scroll', this.handleScroll);
},
beforeDestroy() {
  if (this.timeout) {
    clearTimeout(this.timeout);
  }
  window.removeEventListener('scroll', this.handleScroll);
  this.$emit('stopTracking');
},
methods: {
  handleScroll() {
    // Optionally debounce this for performance
    this.currentScrollProgress = this.calculateScrollProgress();
  },
  // other methods...
}

Comment on lines +49 to +56
async created() {
const storageUrl = this.defaultFile.storage_url;
const zipFile = new ZipFile(storageUrl);
const entryHtmlFile = await zipFile.file(this.entry);
this.html = entryHtmlFile.toString();
this.loading = false;
this.$emit('startTracking');
this.pollProgress();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for zip file operations.

The code currently doesn't handle potential errors when loading the zip file or extracting the HTML file. If the zip file can't be found or doesn't contain the specified entry, this will result in unhandled promise rejections.

Consider adding try/catch blocks for error handling:

  async created() {
+   try {
      const storageUrl = this.defaultFile.storage_url;
      const zipFile = new ZipFile(storageUrl);
      const entryHtmlFile = await zipFile.file(this.entry);
      this.html = entryHtmlFile.toString();
      this.loading = false;
      this.$emit('startTracking');
      this.pollProgress();
+   } catch (error) {
+     console.error('Error loading HTML content:', error);
+     this.loading = false;
+     // Consider adding user-facing error state
+   }
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async created() {
const storageUrl = this.defaultFile.storage_url;
const zipFile = new ZipFile(storageUrl);
const entryHtmlFile = await zipFile.file(this.entry);
this.html = entryHtmlFile.toString();
this.loading = false;
this.$emit('startTracking');
this.pollProgress();
async created() {
try {
const storageUrl = this.defaultFile.storage_url;
const zipFile = new ZipFile(storageUrl);
const entryHtmlFile = await zipFile.file(this.entry);
this.html = entryHtmlFile.toString();
this.loading = false;
this.$emit('startTracking');
this.pollProgress();
} catch (error) {
console.error('Error loading HTML content:', error);
this.loading = false;
// Consider adding user-facing error state
}
},

Comment on lines +65 to +73
recordProgress() {
let progress;
if (this.forceDurationBasedProgress) {
progress = this.durationBasedProgress;
} else {
// TODO: Handle progress tracking based on how
// much of the article has been scrolled through
progress = this.scrollBasedProgress;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement the missing progress tracking variables.

The recordProgress method uses this.forceDurationBasedProgress and this.durationBasedProgress which are not defined in the component.

You need to define these variables either as props or computed properties:

  computed: {
    /**
     * @public
     * Note: the default duration historically for HTML5 Apps has been 5 min
     */
    defaultDuration() {
      return 300;
    },
    entry() {
      return (this.options && this.options.entry) || 'index.html';
    },
    scrollBasedProgress() {
      return 0.5;
    },
+   forceDurationBasedProgress() {
+     return this.options && this.options.forceDurationBasedProgress;
+   },
+   durationBasedProgress() {
+     // Calculate progress based on time elapsed
+     const elapsedTime = (Date.now() - this.startTime) / 1000;
+     return Math.min(elapsedTime / this.defaultDuration, 1);
+   },
  },
+ data() {
+   return {
+     loading: true,
+     html: null,
+     startTime: Date.now(),
+   };
+ },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
recordProgress() {
let progress;
if (this.forceDurationBasedProgress) {
progress = this.durationBasedProgress;
} else {
// TODO: Handle progress tracking based on how
// much of the article has been scrolled through
progress = this.scrollBasedProgress;
}
<script>
export default {
data() {
return {
loading: true,
html: null,
startTime: Date.now(),
};
},
computed: {
/**
* @public
* Note: the default duration historically for HTML5 Apps has been 5 min
*/
defaultDuration() {
return 300;
},
entry() {
return (this.options && this.options.entry) || 'index.html';
},
scrollBasedProgress() {
return 0.5;
},
forceDurationBasedProgress() {
return this.options && this.options.forceDurationBasedProgress;
},
durationBasedProgress() {
// Calculate progress based on time elapsed
const elapsedTime = (Date.now() - this.startTime) / 1000;
return Math.min(elapsedTime / this.defaultDuration, 1);
},
},
methods: {
recordProgress() {
let progress;
if (this.forceDurationBasedProgress) {
progress = this.durationBasedProgress;
} else {
// TODO: Handle progress tracking based on how
// much of the article has been scrolled through
progress = this.scrollBasedProgress;
}
// …rest of recordProgress…
},
// …other methods…
},
};
</script>

Comment on lines +34 to +44
computed: {
/**
* @public
* Note: the default duration historically for HTML5 Apps has been 5 min
*/
defaultDuration() {
return 300;
},
entry() {
return (this.options && this.options.entry) || 'index.html';
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Define missing props used in component.

The component uses this.defaultFile and this.options which are not defined as props in the component.

Add the missing props to ensure the component works correctly:

  export default {
    name: 'SafeHtml5RendererIndex',
    components: {
      SafeHTML,
    },
+   props: {
+     defaultFile: {
+       type: Object,
+       required: true,
+     },
+     options: {
+       type: Object,
+       default: () => ({}),
+     },
+   },
    data() {
      return {
        loading: true,
        html: null,
      };
    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
computed: {
/**
* @public
* Note: the default duration historically for HTML5 Apps has been 5 min
*/
defaultDuration() {
return 300;
},
entry() {
return (this.options && this.options.entry) || 'index.html';
},
export default {
name: 'SafeHtml5RendererIndex',
components: {
SafeHTML,
},
props: {
defaultFile: {
type: Object,
required: true,
},
options: {
type: Object,
default: () => ({}),
},
},
data() {
return {
loading: true,
html: null,
};
},
computed: {
/**
* @public
* Note: the default duration historically for HTML5 Apps has been 5 min
*/
defaultDuration() {
return 300;
},
entry() {
return (this.options && this.options.entry) || 'index.html';
},
// …other computed props
},
// …rest of component
};

Copy link
Member

@marcellamaki marcellamaki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code read through + manual confirmation checks out

@rtibbles rtibbles merged commit 38b4b4c into learningequality:develop Jun 2, 2025
58 checks passed
@rtibbles rtibbles deleted the html_article_renderer branch June 2, 2025 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DEV: backend Python, databases, networking, filesystem... DEV: frontend DEV: renderers HTML5 apps, videos, exercises, etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants