diff --git a/kolibri/plugins/safe_html5_viewer/assets/__tests__/SafeHtml5RendererIndex.spec.js b/kolibri/plugins/safe_html5_viewer/assets/__tests__/SafeHtml5RendererIndex.spec.js
index 32722888095..6859dee4683 100644
--- a/kolibri/plugins/safe_html5_viewer/assets/__tests__/SafeHtml5RendererIndex.spec.js
+++ b/kolibri/plugins/safe_html5_viewer/assets/__tests__/SafeHtml5RendererIndex.spec.js
@@ -92,34 +92,6 @@ describe('SafeHtml5RendererIndex', () => {
});
});
- describe('windowSizeClass pass-through', () => {
- test("the 'small-window' class is applied to table caption when innerWidth <= 600", async () => {
- Object.defineProperty(window, 'innerWidth', {
- writable: true,
- configurable: true,
- value: 500,
- });
- renderComponent();
- await waitFor(() => {
- const caption = screen.getByText('Mocked 3-column Table');
- expect(caption).toHaveClass('small-window');
- });
- });
-
- test("the 'small-window' class is not applied to table caption when innerWidth > 600", async () => {
- Object.defineProperty(window, 'innerWidth', {
- writable: true,
- configurable: true,
- value: 800,
- });
- renderComponent();
- await waitFor(() => {
- const caption = screen.getByText('Mocked 3-column Table');
- expect(caption).not.toHaveClass('small-window');
- });
- });
- });
-
describe('progress tracking', () => {
test('emits startTracking on created', async () => {
const { emitted } = renderComponent();
diff --git a/kolibri/plugins/safe_html5_viewer/assets/src/views/SafeHtml5RendererIndex.vue b/kolibri/plugins/safe_html5_viewer/assets/src/views/SafeHtml5RendererIndex.vue
index 086147b77b2..abfd1be5a75 100644
--- a/kolibri/plugins/safe_html5_viewer/assets/src/views/SafeHtml5RendererIndex.vue
+++ b/kolibri/plugins/safe_html5_viewer/assets/src/views/SafeHtml5RendererIndex.vue
@@ -15,12 +15,7 @@
role="region"
:aria-label="$tr('articleContent')"
>
-
+
@@ -32,8 +27,6 @@
import ZipFile from 'kolibri-zip';
import SafeHTML from 'kolibri-common/components/SafeHTML';
import useContentViewer, { contentViewerProps } from 'kolibri/composables/useContentViewer';
- import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
- import { computed } from 'vue';
export default {
name: 'SafeHtml5RendererIndex',
@@ -41,17 +34,12 @@
SafeHTML,
},
setup(props, context) {
- const { windowIsSmall } = useKResponsiveWindow();
- const windowSizeClass = computed(() => {
- return windowIsSmall.value ? 'small-window' : '';
- });
const { defaultFile, forceDurationBasedProgress, durationBasedProgress } = useContentViewer(
props,
context,
{ defaultDuration: 300 },
);
return {
- windowSizeClass,
defaultFile,
forceDurationBasedProgress,
durationBasedProgress,
diff --git a/packages/kolibri-common/components/SafeHTML/SafeHtmlTable.js b/packages/kolibri-common/components/SafeHTML/SafeHtmlTable.js
deleted file mode 100644
index 3ef2d9fc074..00000000000
--- a/packages/kolibri-common/components/SafeHTML/SafeHtmlTable.js
+++ /dev/null
@@ -1,54 +0,0 @@
-export default {
- name: 'SafeHtmlTable',
- functional: true,
- props: {
- node: { required: true },
- attributes: { type: Object, required: true },
- tableCounter: { type: Number, required: true },
- windowSizeClass: { type: String, default: '' },
- mapNode: { type: Function, required: true },
- mapChildren: { type: Function, required: true },
- },
- render(h, context) {
- const { node, attributes, tableCounter, windowSizeClass, mapNode, mapChildren } = context.props;
- const captionId = `table-caption-${tableCounter}`;
-
- const children = [];
- for (const childNode of node.childNodes) {
- if (
- childNode.nodeType === Node.ELEMENT_NODE &&
- childNode.tagName.toLowerCase() === 'caption'
- ) {
- const captionAttrs = {};
- for (const attr of childNode.attributes) {
- captionAttrs[attr.name] = attr.value;
- }
- captionAttrs.id = captionId;
- captionAttrs.class = windowSizeClass ? `safe-html ${windowSizeClass}` : 'safe-html';
- children.push(h('caption', { attrs: captionAttrs }, mapChildren(childNode.childNodes)));
- } else {
- children.push(mapNode(childNode));
- }
- }
-
- const firstRow = node.querySelector && node.querySelector('tr');
- const colCount = firstRow ? firstRow.children.length : 0;
- let tableWidth = '640px';
- if (colCount > 3) {
- tableWidth = `${colCount * 200}px`;
- }
-
- return h(
- 'div',
- {
- class: 'table-container',
- attrs: {
- role: 'region',
- 'aria-labelledby': captionId,
- 'data-testid': 'table-container',
- },
- },
- [h('table', { attrs: attributes, style: { width: tableWidth } }, children)],
- );
- },
-};
diff --git a/packages/kolibri-common/components/SafeHTML/SafeHtmlTable.vue b/packages/kolibri-common/components/SafeHTML/SafeHtmlTable.vue
new file mode 100644
index 00000000000..4595f448498
--- /dev/null
+++ b/packages/kolibri-common/components/SafeHTML/SafeHtmlTable.vue
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/kolibri-common/components/SafeHTML/__tests__/SafeHtmlTable.spec.js b/packages/kolibri-common/components/SafeHTML/__tests__/SafeHtmlTable.spec.js
index 66f149d0611..7fd274b8eb3 100644
--- a/packages/kolibri-common/components/SafeHTML/__tests__/SafeHtmlTable.spec.js
+++ b/packages/kolibri-common/components/SafeHTML/__tests__/SafeHtmlTable.spec.js
@@ -1,12 +1,10 @@
import { render, screen } from '@testing-library/vue';
-import { h } from 'vue';
-import SafeHtmlTable from '../SafeHtmlTable.js';
+import SafeHtmlTable from '../SafeHtmlTable.vue';
// Create a table element with m rows and n columns
const createSampleNode = (m, n) => {
const table = document.createElement('table');
- // Caption
const caption = document.createElement('caption');
caption.textContent = 'Sample Caption';
table.appendChild(caption);
@@ -15,7 +13,6 @@ const createSampleNode = (m, n) => {
return table;
}
- // Thead (row 1)
const thead = document.createElement('thead');
const theadRow = document.createElement('tr');
for (let col = 1; col <= n; col++) {
@@ -57,27 +54,14 @@ const createSampleNode = (m, n) => {
};
const sampleAttributes = { class: 'safe-html' };
-const sampleTableCounter = 6;
-const sampleWindowSizeClass = 'small-window';
-
-const mapNode = jest.fn(node => {
- if (node.nodeType === Node.ELEMENT_NODE) {
- return h(node.tagName.toLowerCase(), { attrs: sampleAttributes }, mapChildren(node.childNodes));
- } else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') {
- return node.textContent;
- }
- return null;
-});
-
-const mapChildren = jest.fn(childNodes => Array.from(childNodes).map(mapNode).filter(Boolean));
+const mapNode = jest.fn(() => null);
+const mapChildren = jest.fn(() => []);
const renderComponent = (m, n) => {
return render(SafeHtmlTable, {
props: {
node: createSampleNode(m, n),
attributes: sampleAttributes,
- tableCounter: sampleTableCounter,
- windowSizeClass: sampleWindowSizeClass,
mapNode,
mapChildren,
},
@@ -98,46 +82,10 @@ describe('SafeHtmlTable', () => {
expect(screen.getByRole('table')).toBeInTheDocument();
});
- test('renders the caption', () => {
- expect(screen.getByText('Sample Caption')).toBeInTheDocument();
- });
- });
-
- describe('class and a11y attributes', () => {
- beforeEach(() => {
- renderComponent(3, 3);
- });
-
- test('table has safe-html class', () => {
- expect(screen.getByRole('table')).toHaveClass('safe-html');
- });
-
- test('caption has safe-html and small-window classes', () => {
- expect(screen.getByText('Sample Caption')).toHaveClass('safe-html', 'small-window');
- });
-
- test('non-caption child nodes have safe-html class', () => {
- const rowGroups = screen.getAllByRole('rowgroup'); // thead, tbody, tfoot
- rowGroups.forEach(rowGroup => expect(rowGroup).toHaveClass('safe-html'));
-
- const trs = screen.getAllByRole('row');
- trs.forEach(tr => expect(tr).toHaveClass('safe-html'));
-
- const ths = screen.getAllByRole('columnheader');
- ths.forEach(th => expect(th).toHaveClass('safe-html'));
- const tds = screen.getAllByRole('cell');
- tds.forEach(td => expect(td).toHaveClass('safe-html'));
- });
-
- test('caption has correct id', () => {
- expect(screen.getByText('Sample Caption')).toHaveAttribute('id', 'table-caption-6');
- });
-
- test('container div has correct aria-labelledby', () => {
- expect(screen.getByTestId('table-container')).toHaveAttribute(
- 'aria-labelledby',
- 'table-caption-6',
- );
+ test('renders a caption element', () => {
+ const table = screen.getByRole('table');
+ const caption = table.querySelector('caption');
+ expect(caption).not.toBeNull();
});
});
diff --git a/packages/kolibri-common/components/SafeHTML/index.js b/packages/kolibri-common/components/SafeHTML/index.js
index 797fb5769ff..ed1f2c808ec 100644
--- a/packages/kolibri-common/components/SafeHTML/index.js
+++ b/packages/kolibri-common/components/SafeHTML/index.js
@@ -1,7 +1,7 @@
import DOMPurify from 'dompurify';
import kebabCase from 'lodash/kebabCase';
import './style.scss';
-import SafeHtmlTable from './SafeHtmlTable.js';
+import SafeHtmlTable from './SafeHtmlTable.vue';
import SafeHtmlImage from './SafeHtmlImage.vue';
const ALLOWED_URI_REGEXP = /^(?:(?:blob:https?|data):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
@@ -46,7 +46,7 @@ export function createSafeHTML(customComponents = {}) {
},
RETURN_DOM_FRAGMENT: true,
});
- let tableCounter = 0;
+
function mapNode(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
const tagName = node.tagName.toLowerCase();
@@ -88,17 +88,11 @@ export function createSafeHTML(customComponents = {}) {
attributes[attr.name] = attr.value;
}
attributes.class = attributes.class ? `${attributes.class} safe-html` : 'safe-html';
-
if (tagName === 'table') {
- tableCounter += 1;
return h(SafeHtmlTable, {
props: {
node,
attributes,
- tableCounter,
- windowSizeClass: context.props.styleOverrides
- ? context.props.styleOverrides.windowSizeClass
- : '',
mapNode,
mapChildren,
},