diff --git a/src/core/directives/directives.module.ts b/src/core/directives/directives.module.ts
index 6225f56a1..f26fc2ab2 100644
--- a/src/core/directives/directives.module.ts
+++ b/src/core/directives/directives.module.ts
@@ -35,6 +35,7 @@ import { CoreContentDirective } from './content';
import { CoreUpdateNonReactiveAttributesDirective } from './update-non-reactive-attributes';
import { CoreUserTourDirective } from './user-tour';
import { CoreIonDatetimeDirective } from './datetime';
+import { CoreReadingModeDirective } from './reading-mode';
@NgModule({
declarations: [
@@ -59,6 +60,7 @@ import { CoreIonDatetimeDirective } from './datetime';
CoreUpdateNonReactiveAttributesDirective,
CoreUserTourDirective,
CoreIonDatetimeDirective,
+ CoreReadingModeDirective,
],
exports: [
CoreAutoFocusDirective,
@@ -82,6 +84,7 @@ import { CoreIonDatetimeDirective } from './datetime';
CoreUpdateNonReactiveAttributesDirective,
CoreUserTourDirective,
CoreIonDatetimeDirective,
+ CoreReadingModeDirective,
],
})
export class CoreDirectivesModule {}
diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts
index 800756f0a..93379a63e 100644
--- a/src/core/directives/format-text.ts
+++ b/src/core/directives/format-text.ts
@@ -67,7 +67,7 @@ import { CorePromiseUtils } from '@singletons/promise-utils';
* Please use this directive if your text needs to be filtered or it can contain links or media (images, audio, video).
*
* Example usage:
- *
+ *
*/
@Directive({
selector: 'core-format-text',
diff --git a/src/core/directives/reading-mode.ts b/src/core/directives/reading-mode.ts
new file mode 100644
index 000000000..0534e601c
--- /dev/null
+++ b/src/core/directives/reading-mode.ts
@@ -0,0 +1,202 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+ AfterViewInit,
+ Directive,
+ ElementRef,
+ OnDestroy,
+} from '@angular/core';
+
+import { Translate } from '@singletons';
+import { CoreIcons } from '@singletons/icons';
+import { CoreDom } from '@singletons/dom';
+import { CoreWait } from '@singletons/wait';
+import { CoreCancellablePromise } from '@classes/cancellable-promise';
+import { CoreModals } from '@services/modals';
+import { CoreViewer } from '@features/viewer/services/viewer';
+
+/**
+ * Directive to add the reading mode to the selected html tag.
+ *
+ * Example usage:
+ *
+ */
+@Directive({
+ selector: '[core-reading-mode]',
+})
+export class CoreReadingModeDirective implements AfterViewInit, OnDestroy {
+
+ protected element: HTMLElement;
+ protected viewportPromise?: CoreCancellablePromise
;
+ protected disabledStyles: HTMLStyleElement[] = [];
+ protected hiddenElements: HTMLElement[] = [];
+ protected renamedStyles: HTMLElement[] = [];
+ protected enabled = false;
+ protected contentEl?: HTMLIonContentElement;
+
+ constructor(
+ element: ElementRef,
+ ) {
+ this.element = element.nativeElement;
+ this.viewportPromise = CoreDom.waitToBeInViewport(this.element);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async ngAfterViewInit(): Promise {
+ await this.viewportPromise;
+ await CoreWait.nextTick();
+ this.addTextViewerButton();
+ }
+
+ /**
+ * Add text viewer button to enable the reading mode.
+ */
+ protected async addTextViewerButton(): Promise {
+ const page = CoreDom.closest(this.element, '.ion-page');
+ this.contentEl = page?.querySelector('ion-content') ?? undefined;
+
+ const toolbar = page?.querySelector('ion-header ion-toolbar ion-buttons[slot="end"]');
+
+ if (!toolbar || toolbar.querySelector('.core-text-viewer-button')) {
+ return;
+ }
+
+ this.contentEl?.classList.add('core-reading-mode-content');
+
+ const label = Translate.instant('core.viewer.enterreadingmode');
+ const button = document.createElement('ion-button');
+
+ button.classList.add('core-text-viewer-button');
+ button.setAttribute('aria-label', label);
+ button.setAttribute('fill', 'clear');
+
+ const iconName = 'book-open-reader';
+ const src = CoreIcons.getIconSrc('font-awesome', 'solid', iconName);
+ // Add an ion-icon item to apply the right styles, but the ion-icon component won't be executed.
+ button.innerHTML = ``;
+ toolbar.appendChild(button);
+
+ button.addEventListener('click', (e: Event) => {
+ if (!this.element.innerHTML) {
+ return;
+ }
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (!this.enabled) {
+ this.enterReadingMode();
+ } else {
+ this.showReadingSettings();
+ }
+
+ });
+ }
+
+ /**
+ * Enters the reading mode.
+ */
+ protected async enterReadingMode(): Promise {
+ this.enabled = true;
+ CoreViewer.loadReadingModeSettings();
+
+ document.body.classList.add('core-reading-mode-enabled');
+
+ // Disable all styles in element.
+ this.disabledStyles = Array.from(this.element.querySelectorAll('style:not(disabled)'));
+ this.disabledStyles.forEach((style) => {
+ style.disabled = true;
+ });
+
+ // Rename style attributes on DOM elements.
+ this.renamedStyles = Array.from(this.element.querySelectorAll('*[style]'));
+ this.renamedStyles.forEach((element: HTMLElement) => {
+ this.renamedStyles.push(element);
+ element.setAttribute('data-original-style', element.getAttribute('style') || '');
+ element.removeAttribute('style');
+ });
+
+ // Navigate to parent hidding all other elements.
+ let currentChild = this.element;
+ let parent = currentChild.parentElement;
+ while (parent && parent.tagName.toLowerCase() !== 'ion-content') {
+ Array.from(parent.children).forEach((child: HTMLElement) => {
+ if (child !== currentChild && child.tagName.toLowerCase() !== 'swiper-slide') {
+ this.hiddenElements.push(child);
+ child.classList.add('hide-on-reading-mode');
+ }
+ });
+
+ currentChild = parent;
+ parent = currentChild.parentElement;
+ }
+ }
+
+ /**
+ * Disable the reading mode.
+ */
+ protected async disableReadingMode(): Promise {
+ this.enabled = false;
+ document.body.classList.remove('core-reading-mode-enabled');
+
+ // Enable all styles in element.
+ this.disabledStyles.forEach((style) => {
+ style.disabled = false;
+ });
+ this.disabledStyles = [];
+
+ // Rename style attributes on DOM elements.
+ this.renamedStyles.forEach((element) => {
+ element.setAttribute('style', element.getAttribute('data-original-style') || '');
+ element.removeAttribute('data-original-style');
+ });
+ this.renamedStyles = [];
+
+ this.hiddenElements.forEach((element) => {
+ element.classList.remove('hide-on-reading-mode');
+ });
+ this.hiddenElements = [];
+ }
+
+ /**
+ * Show the reading settings.
+ */
+ protected async showReadingSettings(): Promise {
+ const { CoreReadingModeSettingsModalComponent } =
+ await import('@features/viewer/components/reading-mode-settings/reading-mode-settings');
+
+ const exit = await CoreModals.openModal({
+ component: CoreReadingModeSettingsModalComponent,
+ initialBreakpoint: 1,
+ breakpoints: [0, 1],
+ cssClass: 'core-modal-auto-height',
+ });
+
+ if (exit) {
+ this.disableReadingMode();
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ ngOnDestroy(): void {
+ this.disableReadingMode();
+ this.viewportPromise?.cancel();
+ }
+
+}
diff --git a/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.html b/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.html
new file mode 100644
index 000000000..db01839d5
--- /dev/null
+++ b/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.settings.fontsize' | translate }}
+
+ {{settings.zoom}}% @if (defaultZoom) { {{ 'core.viewer.default' | translate }} }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Theme
+
+
+
+ @for (theme of themes; track $index; let last = $last;) {
+
+
+ Aa
{{ 'core.viewer.readingtheme'+theme | translate }}
+
+
+ }
+
+
+
+ {{ 'core.viewer.showmedia' | translate }}
+
+
+
+
+ {{ 'core.viewer.exitreadingmode' | translate }}
+
+
diff --git a/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.scss b/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.scss
new file mode 100644
index 000000000..93cf3faf0
--- /dev/null
+++ b/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.scss
@@ -0,0 +1,55 @@
+@use "theme/globals" as *;
+
+ion-button ion-icon.zoom-decrease {
+ font-size: 1.5em;
+}
+
+ion-button ion-icon.zoom-increase {
+ font-size: 2em;
+}
+
+ion-radio.reading-theme {
+
+ &::part(label) {
+ margin: 0px;
+ display: inline-flex;
+ align-items: center;
+ }
+
+ .preview {
+ width: 40px;
+ height: 40px;
+ border-radius: 100%;
+ @include margin(4px, 16px, 4px, 2px);
+ text-align: center;
+ line-height: 40px;
+ font-size: #{dynamic-font(16px)};
+ font-weight: bold;
+ border: 1px solid var(--stroke);
+
+ &.auto {
+ background: linear-gradient(to right, #{$background-color-dark} 50%, #{$background-color} 50%);
+ color: #{$text-color};
+ &::first-letter {
+ color: #{$text-color-dark};
+ }
+ }
+ &.light {
+ background-color: #{$background-color};
+ color: #{$text-color};
+ }
+ &.dark {
+ background-color: #{$background-color-dark};
+ color: #{$text-color-dark};
+ }
+ &.sepia {
+ background-color: var(--core-reading-mode-sepia-background);
+ color: var(--core-reading-mode-sepia-text-color);
+
+ }
+ &.hcm {
+ background-color: black;
+ color: white;
+ }
+ }
+}
diff --git a/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.ts b/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.ts
new file mode 100644
index 000000000..3bf67c5c4
--- /dev/null
+++ b/src/core/features/viewer/components/reading-mode-settings/reading-mode-settings.ts
@@ -0,0 +1,95 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { CoreSharedModule } from '@/core/shared.module';
+import { Component, OnInit } from '@angular/core';
+import {
+ CORE_READING_MODE_DEFAULT_SETTINGS,
+ CoreViewerReadingModeThemes,
+ CoreViewerReadingModeThemesType,
+} from '@features/viewer/constants';
+import { CoreViewer } from '@features/viewer/services/viewer';
+
+import { ModalController } from '@singletons';
+import { CoreMath } from '@singletons/math';
+
+/**
+ * Component to display a text modal.
+ */
+@Component({
+ selector: 'core-reading-mode-settings-modal',
+ templateUrl: 'reading-mode-settings.html',
+ styleUrl: 'reading-mode-settings.scss',
+ standalone: true,
+ imports: [
+ CoreSharedModule,
+ ],
+})
+export class CoreReadingModeSettingsModalComponent implements OnInit {
+
+ readonly MAX_TEXT_SIZE_ZOOM = 200;
+ readonly MIN_TEXT_SIZE_ZOOM = 75;
+ readonly TEXT_SIZE_ZOOM_STEP = 25;
+
+ settings = CORE_READING_MODE_DEFAULT_SETTINGS;
+
+ defaultZoom = true;
+
+ themes: CoreViewerReadingModeThemesType[] = Object.values(CoreViewerReadingModeThemes);
+
+ /**
+ * @inheritdoc
+ */
+ async ngOnInit(): Promise {
+ this.settings = await CoreViewer.getReadingModeSettings();
+ }
+
+ /**
+ * Close modal.
+ */
+ closeModal(): void {
+ ModalController.dismiss();
+ }
+
+ /**
+ * Close modal.
+ */
+ exit(): void {
+ ModalController.dismiss(true);
+ }
+
+ /**
+ * Change text size zoom.
+ *
+ * @param newTextSizeZoom New text size zoom.
+ */
+ changeTextSizeZoom(newTextSizeZoom: number): void {
+ this.settings.zoom = CoreMath.clamp(
+ newTextSizeZoom,
+ this.MIN_TEXT_SIZE_ZOOM,
+ this.MAX_TEXT_SIZE_ZOOM,
+ );
+
+ this.defaultZoom = this.settings.zoom === 100;
+ this.onSettingChange();
+ }
+
+ /**
+ * Save settings on any change.
+ */
+ onSettingChange(): void {
+ CoreViewer.setReadingModeSettings(this.settings);
+ }
+
+}
diff --git a/src/core/features/viewer/constants.ts b/src/core/features/viewer/constants.ts
new file mode 100644
index 000000000..3b56833d1
--- /dev/null
+++ b/src/core/features/viewer/constants.ts
@@ -0,0 +1,33 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { CoreViewerReadingModeSettings } from './services/viewer';
+
+export const CORE_READING_MODE_SETTINGS = 'CoreReadingModeSettings';
+
+export const CoreViewerReadingModeThemes = {
+ AUTO: 'auto', // eslint-disable-line @typescript-eslint/naming-convention
+ LIGHT: 'light', // eslint-disable-line @typescript-eslint/naming-convention
+ DARK: 'dark', // eslint-disable-line @typescript-eslint/naming-convention
+ SEPIA: 'sepia', // eslint-disable-line @typescript-eslint/naming-convention
+ HCM: 'hcm', // eslint-disable-line @typescript-eslint/naming-convention
+} as const;
+
+export type CoreViewerReadingModeThemesType = typeof CoreViewerReadingModeThemes[keyof typeof CoreViewerReadingModeThemes];
+
+export const CORE_READING_MODE_DEFAULT_SETTINGS: CoreViewerReadingModeSettings = {
+ zoom: 100,
+ showMultimedia: false,
+ theme: CoreViewerReadingModeThemes.HCM,
+};
diff --git a/src/core/features/viewer/lang.json b/src/core/features/viewer/lang.json
new file mode 100644
index 000000000..ab263e5ab
--- /dev/null
+++ b/src/core/features/viewer/lang.json
@@ -0,0 +1,14 @@
+{
+ "decreasetextsize": "Decrease text size",
+ "default": "(Default)",
+ "enterreadingmode": "Enter reading mode",
+ "exitreadingmode": "Exit reading mode",
+ "increasetextsize": "Increase text size",
+ "openreadingmodesettings": "Open reading mode settings",
+ "readingthemeauto": "Match app",
+ "readingthemedark": "Dark",
+ "readingthemehcm": "High contrast",
+ "readingthemelight": "Light",
+ "readingthemesepia": "Sepia",
+ "showmedia": "Show images and media"
+}
diff --git a/src/core/features/viewer/services/viewer.ts b/src/core/features/viewer/services/viewer.ts
index 98b38f676..9efcfc277 100644
--- a/src/core/features/viewer/services/viewer.ts
+++ b/src/core/features/viewer/services/viewer.ts
@@ -15,10 +15,17 @@
import { ContextLevel } from '@/core/constants';
import { Injectable } from '@angular/core';
import { ModalOptions } from '@ionic/angular';
+import { CoreConfig } from '@services/config';
import { CoreModals } from '@services/modals';
import { CoreNavigator } from '@services/navigator';
import { CoreWSFile } from '@services/ws';
import { makeSingleton } from '@singletons';
+import {
+ CORE_READING_MODE_SETTINGS,
+ CoreViewerReadingModeThemes,
+ CoreViewerReadingModeThemesType,
+ CORE_READING_MODE_DEFAULT_SETTINGS,
+} from '../constants';
/**
* Viewer services.
@@ -97,6 +104,49 @@ export class CoreViewerService {
await CoreNavigator.navigateToSitePath('viewer/iframe', { params: { title, url, autoLogin } });
}
+ /**
+ * Get reading mode settings.
+ *
+ * @returns Reading mode settings.
+ */
+ async getReadingModeSettings(): Promise {
+ return CoreConfig.getJSON(CORE_READING_MODE_SETTINGS, CORE_READING_MODE_DEFAULT_SETTINGS);
+ }
+
+ /**
+ * Load and apply reading mode settings.
+ */
+ async loadReadingModeSettings(): Promise {
+ const settings = await this.getReadingModeSettings();
+
+ this.applyReadingModeSettings(settings);
+ }
+
+ /**
+ * Apply the reading mode settings to the DOM.
+ *
+ * @param settings Settings to apply.
+ */
+ protected applyReadingModeSettings(settings: CoreViewerReadingModeSettings): void {
+ document.body.style.setProperty('--reading-mode-zoom', settings.zoom + '%');
+ Object.values(CoreViewerReadingModeThemes).forEach((theme) => {
+ document.body.classList.remove(`core-reading-mode-theme-${theme}`);
+ });
+ document.body.classList.add(`core-reading-mode-theme-${settings.theme}`);
+ document.body.classList.toggle('core-reading-mode-multimedia-hidden', !settings.showMultimedia);
+ }
+
+ /**
+ * Save reading mode settings.
+ *
+ * @param settings Settings to save.
+ */
+ async setReadingModeSettings(settings: CoreViewerReadingModeSettings): Promise {
+ await CoreConfig.setJSON(CORE_READING_MODE_SETTINGS, settings);
+
+ this.applyReadingModeSettings(settings);
+ }
+
}
export const CoreViewer = makeSingleton(CoreViewerService);
@@ -114,3 +164,9 @@ export type CoreViewerTextOptions = {
displayCopyButton?: boolean; // Whether to display a button to copy the text.
modalOptions?: Partial; // Modal options.
};
+
+export type CoreViewerReadingModeSettings = {
+ zoom: number; // Zoom level.
+ showMultimedia: boolean; // Show images and multimedia.
+ theme: CoreViewerReadingModeThemesType; // Theme to use.
+};
diff --git a/src/core/services/app.ts b/src/core/services/app.ts
index 5da40dbb9..c8c4165ee 100644
--- a/src/core/services/app.ts
+++ b/src/core/services/app.ts
@@ -346,7 +346,8 @@ export class CoreAppProvider {
*/
setSystemUIColors(): void {
this.setStatusBarColor();
- this.setAndroidNavigationBarColor(); }
+ this.setAndroidNavigationBarColor();
+ }
/**
* Set StatusBar color depending on platform.
diff --git a/src/core/services/config.ts b/src/core/services/config.ts
index aa27a80f2..53aa6b4f5 100644
--- a/src/core/services/config.ts
+++ b/src/core/services/config.ts
@@ -24,6 +24,7 @@ import { CoreDatabaseTable } from '@classes/database/database-table';
import { asyncInstance } from '../utils/async-instance';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreBrowser } from '@singletons/browser';
+import { CoreText } from '@singletons/text';
declare module '@singletons/events' {
@@ -118,6 +119,30 @@ export class CoreConfigProvider {
}
}
+ /**
+ * Get an app setting with json format
+ *
+ * @param name The config name.
+ * @param defaultValue Default value to use if the entry is not found.
+ * @returns Resolves upon success along with the config data. Reject on failure.
+ */
+ async getJSON(name: string, defaultValue?: T): Promise {
+ try {
+ const configString = await CoreConfig.get(name);
+ if (!configString) {
+ throw new Error('Config not found');
+ }
+
+ return CoreText.parseJSON(configString, defaultValue);
+ } catch (error) {
+ if (defaultValue !== undefined) {
+ return defaultValue;
+ }
+
+ throw error;
+ }
+ }
+
/**
* Get an app setting directly from the database, without using any optimizations..
*
@@ -152,12 +177,21 @@ export class CoreConfigProvider {
*
* @param name The config name.
* @param value The config value. Can only store number or strings.
- * @returns Promise resolved when done.
*/
async set(name: string, value: number | string): Promise {
await this.table.insert({ name, value });
}
+ /**
+ * Set an app setting with json format.
+ *
+ * @param name The config name.
+ * @param value The config value. Can only store objects.
+ */
+ async setJSON(name: string, value: unknown): Promise {
+ await this.set(name, JSON.stringify(value));
+ }
+
/**
* Update config with the given values.
*
diff --git a/src/core/tests/behat/snapshots/fontawesome-icons-are-correctly-shown-in-the-app-view-fontawesome-icons-in-the-app_7.png b/src/core/tests/behat/snapshots/fontawesome-icons-are-correctly-shown-in-the-app-view-fontawesome-icons-in-the-app_7.png
index ce2463119..703afebe6 100644
Binary files a/src/core/tests/behat/snapshots/fontawesome-icons-are-correctly-shown-in-the-app-view-fontawesome-icons-in-the-app_7.png and b/src/core/tests/behat/snapshots/fontawesome-icons-are-correctly-shown-in-the-app-view-fontawesome-icons-in-the-app_7.png differ
diff --git a/src/theme/components/ion-button.scss b/src/theme/components/ion-button.scss
index 690456df3..0a22be6cb 100644
--- a/src/theme/components/ion-button.scss
+++ b/src/theme/components/ion-button.scss
@@ -105,6 +105,7 @@ ion-button {
}
ion-button,
+ion-button.button, // Add specificity
ion-fab-button,
button,
[role="button"] {
diff --git a/src/theme/components/ion-modal.scss b/src/theme/components/ion-modal.scss
index 93edc24e7..852ac0384 100644
--- a/src/theme/components/ion-modal.scss
+++ b/src/theme/components/ion-modal.scss
@@ -122,4 +122,21 @@ ion-modal {
justify-content: space-between;
}
}
+
+ &.core-modal-auto-height {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+
+ &::part(content) {
+ position: relative;
+ display: block;
+ contain: content;
+ }
+
+ .inner-content {
+ max-height: 80vh;
+ overflow: auto;
+ }
+ }
}
diff --git a/src/theme/components/reading-mode.scss b/src/theme/components/reading-mode.scss
new file mode 100644
index 000000000..ed4ef820e
--- /dev/null
+++ b/src/theme/components/reading-mode.scss
@@ -0,0 +1,57 @@
+
+html {
+ --core-reading-mode-sepia-background: #f4ecd8;
+ --core-reading-mode-sepia-text-color: #5b4636;
+}
+
+body.core-reading-mode-enabled {
+ .core-text-viewer-button {
+ --core-header-buttons-background: var(--stroke);
+ }
+
+ &.core-reading-mode-theme-light {
+ --reading-mode-background: #{$background-color};
+ --reading-mode-text-color: #{$text-color};
+ }
+
+ &.core-reading-mode-theme-dark {
+ --reading-mode-background: #{$background-color-dark};
+ --reading-mode-text-color: #{$text-color-dark};
+ }
+
+ &.core-reading-mode-theme-sepia {
+ --reading-mode-background: var(--core-reading-mode-sepia-background);
+ --reading-mode-text-color: var(--core-reading-mode-sepia-text-color);
+ }
+
+ &.core-reading-mode-theme-hcm {
+ --reading-mode-background: #000000;
+ --reading-mode-text-color: #ffffff;
+ }
+
+ &.core-reading-mode-multimedia-hidden {
+ ion-content.core-reading-mode-content {
+ img, video, iframe {
+ display: none !important;
+ }
+ }
+ }
+
+ ion-content.core-reading-mode-content {
+ --background: var(--reading-mode-background, --ion-background-color);
+ background: var(--background);
+
+ [core-reading-mode] {
+ zoom: var(--reading-mode-zoom, 1);
+ &> * {
+ --text-color: var(--reading-mode-text-color, --text-color);
+ --color: var(--reading-mode-text-color, --text-color);
+ color: var(--text-color);
+ }
+ }
+
+ .hide-on-reading-mode {
+ display: none !important;
+ }
+ }
+}
diff --git a/src/theme/theme.scss b/src/theme/theme.scss
index e35ae118a..23181639d 100644
--- a/src/theme/theme.scss
+++ b/src/theme/theme.scss
@@ -21,6 +21,7 @@
@import "components/collapsible-header.scss";
@import "components/collapsible-item.scss";
@import "components/error-accordion.scss";
+@import "components/reading-mode.scss";
@import "components/format-text.scss";
@import "components/iframe.scss";
@import "components/mod-label.scss";