MOBILE-3063 reading-mode: Add reading mode to glossary entries

main
Pau Ferrer Ocaña 2024-12-16 14:11:07 +01:00
parent a3c16af58d
commit e6733bbd65
3 changed files with 83 additions and 68 deletions

View File

@ -8,6 +8,7 @@
<core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId" [courseId]="courseId" /> <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId" [courseId]="courseId" />
</h1> </h1>
</ion-title> </ion-title>
<ion-buttons slot="end" />
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content [core-swipe-navigation]="entries" class="limited-width"> <ion-content [core-swipe-navigation]="entries" class="limited-width">
@ -23,72 +24,77 @@
<ion-label>{{ 'core.hasdatatosync' | translate: { $a: 'addon.mod_glossary.entry' | translate } }}</ion-label> <ion-label>{{ 'core.hasdatatosync' | translate: { $a: 'addon.mod_glossary.entry' | translate } }}</ion-label>
</ion-item> </ion-item>
</ion-card> </ion-card>
<ion-item class="ion-text-wrap" *ngIf="showAuthor"> <div core-reading-mode>
<core-user-avatar [user]="entry" slot="start" /> <ion-item class="ion-text-wrap" *ngIf="showAuthor">
<ion-label> <core-user-avatar [user]="entry" slot="start" />
<h2> <ion-label>
<core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId" <h2>
[courseId]="courseId" /> <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId"
</h2> [courseId]="courseId" />
<p *ngIf="onlineEntry">{{ onlineEntry.userfullname }}</p> </h2>
</ion-label> <p *ngIf="onlineEntry">{{ onlineEntry.userfullname }}</p>
<ion-note slot="end" *ngIf="showDate && onlineEntry">{{ onlineEntry.timemodified | coreDateDayOrTime }}</ion-note> </ion-label>
</ion-item> <ion-note slot="end" *ngIf="showDate && onlineEntry">{{ onlineEntry.timemodified | coreDateDayOrTime }}</ion-note>
<ion-item class="ion-text-wrap" *ngIf="!showAuthor"> </ion-item>
<ion-label> <ion-item class="ion-text-wrap" *ngIf="!showAuthor">
<p class="item-heading"> <ion-label>
<core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId" /> <p class="item-heading">
</p> <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId" />
</ion-label> </p>
<ion-note slot="end" *ngIf="showDate && onlineEntry">{{ onlineEntry.timemodified | coreDateDayOrTime }}</ion-note> </ion-label>
</ion-item> <ion-note slot="end" *ngIf="showDate && onlineEntry">{{ onlineEntry.timemodified | coreDateDayOrTime }}</ion-note>
<ion-item class="ion-text-wrap"> </ion-item>
<ion-label> <ion-item class="ion-text-wrap">
<core-format-text [component]="component" [componentId]="componentId" [text]="entry.definition" contextLevel="module" <ion-label>
[contextInstanceId]="componentId" [courseId]="courseId" /> <core-format-text [component]="component" [componentId]="componentId" [text]="entry.definition"
</ion-label> contextLevel="module" [contextInstanceId]="componentId" [courseId]="courseId" />
</ion-item> </ion-label>
<div *ngIf="onlineEntry && onlineEntry.attachment"> </ion-item>
<core-file *ngFor="let file of onlineEntry.attachments" [file]="file" [component]="component" [componentId]="componentId" /> <div *ngIf="onlineEntry && onlineEntry.attachment">
</div> <core-file *ngFor="let file of onlineEntry.attachments" [file]="file" [component]="component"
<div *ngIf="offlineEntry && offlineEntry.attachments"> [componentId]="componentId" />
<core-file *ngFor="let file of offlineEntry.attachments.online" [file]="file" [component]="component"
[componentId]="componentId" />
</div>
<div *ngIf="offlineEntry && offlineEntryFiles">
<core-local-file *ngFor="let file of offlineEntryFiles" [file]="file" />
</div>
<ion-item class="ion-text-wrap" *ngIf="onlineEntry && tagsEnabled && entry && onlineEntry.tags && onlineEntry.tags.length > 0">
<ion-label>
<div slot="start">{{ 'core.tag.tags' | translate }}:</div>
<core-tag-list [tags]="onlineEntry.tags" />
</ion-label>
</ion-item>
<ion-item *ngIf="canDelete || canEdit">
<div slot="end">
<ion-button *ngIf="canDelete" fill="clear" color="danger" (click)="deleteEntry()"
[ariaLabel]="'addon.mod_glossary.deleteentry' | translate">
<ion-icon slot="icon-only" name="fas-trash" aria-hidden="true" />
</ion-button>
<ion-button *ngIf="canEdit" fill="clear" (click)="editEntry()" [ariaLabel]="'addon.mod_glossary.editentry' | translate">
<ion-icon slot="icon-only" name="fas-pen" aria-hidden="true" />
</ion-button>
</div> </div>
</ion-item> <div *ngIf="offlineEntry && offlineEntry.attachments">
<ion-item class="ion-text-wrap" *ngIf="onlineEntry && !onlineEntry.approved"> <core-file *ngFor="let file of offlineEntry.attachments.online" [file]="file" [component]="component"
<ion-label> [componentId]="componentId" />
<p><em>{{ 'addon.mod_glossary.entrypendingapproval' | translate }}</em></p> </div>
</ion-label> <div *ngIf="offlineEntry && offlineEntryFiles">
</ion-item> <core-local-file *ngFor="let file of offlineEntryFiles" [file]="file" />
<core-comments *ngIf="glossary && glossary.allowcomments && onlineEntry && onlineEntry.id > 0 && commentsEnabled" </div>
contextLevel="module" [instanceId]="glossary.coursemodule" component="mod_glossary" [itemId]="onlineEntry.id" <ion-item class="ion-text-wrap"
area="glossary_entry" [courseId]="glossary.course" [showItem]="true" /> *ngIf="onlineEntry && tagsEnabled && entry && onlineEntry.tags && onlineEntry.tags.length > 0">
<core-rating-rate *ngIf="glossary && ratingInfo && onlineEntry" [ratingInfo]="ratingInfo" contextLevel="module" <ion-label>
[instanceId]="glossary.coursemodule" [itemId]="onlineEntry.id" [itemSetId]="0" [courseId]="glossary.course" <div slot="start">{{ 'core.tag.tags' | translate }}:</div>
[aggregateMethod]="glossary.assessed" [scaleId]="glossary.scale" [userId]="entry.userid" (onUpdate)="ratingUpdated()" /> <core-tag-list [tags]="onlineEntry.tags" />
<core-rating-aggregate *ngIf="glossary && ratingInfo && onlineEntry" [ratingInfo]="ratingInfo" contextLevel="module" </ion-label>
[instanceId]="glossary.coursemodule" [itemId]="onlineEntry.id" [courseId]="glossary.course" </ion-item>
[aggregateMethod]="glossary.assessed" [scaleId]="glossary.scale" /> <ion-item *ngIf="canDelete || canEdit">
<div slot="end">
<ion-button *ngIf="canDelete" fill="clear" color="danger" (click)="deleteEntry()"
[ariaLabel]="'addon.mod_glossary.deleteentry' | translate">
<ion-icon slot="icon-only" name="fas-trash" aria-hidden="true" />
</ion-button>
<ion-button *ngIf="canEdit" fill="clear" (click)="editEntry()"
[ariaLabel]="'addon.mod_glossary.editentry' | translate">
<ion-icon slot="icon-only" name="fas-pen" aria-hidden="true" />
</ion-button>
</div>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="onlineEntry && !onlineEntry.approved">
<ion-label>
<p><em>{{ 'addon.mod_glossary.entrypendingapproval' | translate }}</em></p>
</ion-label>
</ion-item>
<core-comments *ngIf="glossary && glossary.allowcomments && onlineEntry && onlineEntry.id > 0 && commentsEnabled"
contextLevel="module" [instanceId]="glossary.coursemodule" component="mod_glossary" [itemId]="onlineEntry.id"
area="glossary_entry" [courseId]="glossary.course" [showItem]="true" />
<core-rating-rate *ngIf="glossary && ratingInfo && onlineEntry" [ratingInfo]="ratingInfo" contextLevel="module"
[instanceId]="glossary.coursemodule" [itemId]="onlineEntry.id" [itemSetId]="0" [courseId]="glossary.course"
[aggregateMethod]="glossary.assessed" [scaleId]="glossary.scale" [userId]="entry.userid" (onUpdate)="ratingUpdated()" />
<core-rating-aggregate *ngIf="glossary && ratingInfo && onlineEntry" [ratingInfo]="ratingInfo" contextLevel="module"
[instanceId]="glossary.coursemodule" [itemId]="onlineEntry.id" [courseId]="glossary.course"
[aggregateMethod]="glossary.assessed" [scaleId]="glossary.scale" />
</div>
</ng-container> </ng-container>
<ion-card *ngIf="!entry" class="core-warning-card"> <ion-card *ngIf="!entry" class="core-warning-card">

View File

@ -28,6 +28,7 @@ import { CoreModals } from '@services/modals';
import { CoreViewer } from '@features/viewer/services/viewer'; import { CoreViewer } from '@features/viewer/services/viewer';
import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CoreDirectivesRegistry } from '@singletons/directives-registry';
import { CoreCollapsibleHeaderDirective } from './collapsible-header'; import { CoreCollapsibleHeaderDirective } from './collapsible-header';
import { CoreLogger } from '@singletons/logger';
/** /**
* Directive to add the reading mode to the selected html tag. * Directive to add the reading mode to the selected html tag.
@ -48,6 +49,7 @@ export class CoreReadingModeDirective implements AfterViewInit, OnDestroy {
protected enabled = false; protected enabled = false;
protected contentEl?: HTMLIonContentElement; protected contentEl?: HTMLIonContentElement;
protected header?: CoreCollapsibleHeaderDirective; protected header?: CoreCollapsibleHeaderDirective;
protected logger = CoreLogger.getInstance('CoreReadingModeDirective');
constructor( constructor(
element: ElementRef, element: ElementRef,
@ -72,9 +74,14 @@ export class CoreReadingModeDirective implements AfterViewInit, OnDestroy {
const page = CoreDom.closest(this.element, '.ion-page'); const page = CoreDom.closest(this.element, '.ion-page');
this.contentEl = page?.querySelector('ion-content') ?? undefined; this.contentEl = page?.querySelector('ion-content') ?? undefined;
const toolbar = page?.querySelector('ion-header ion-toolbar ion-buttons[slot="end"]'); const buttonsContainer = page?.querySelector<HTMLIonButtonsElement>('ion-header ion-toolbar ion-buttons[slot="end"]');
if (!toolbar || toolbar.querySelector('.core-text-viewer-button')) { if (!buttonsContainer) {
this.logger.warn('The header was not found, or it didn\'t have any ion-buttons on slot end.');
return;
}
if (buttonsContainer.querySelector('.core-text-viewer-button')) {
return; return;
} }
@ -96,7 +103,7 @@ export class CoreReadingModeDirective implements AfterViewInit, OnDestroy {
const src = CoreIcons.getIconSrc('font-awesome', 'solid', iconName); 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. // Add an ion-icon item to apply the right styles, but the ion-icon component won't be executed.
button.innerHTML = `<ion-icon name="fas-${iconName}" aria-hidden="true" src="${src}"></ion-icon>`; button.innerHTML = `<ion-icon name="fas-${iconName}" aria-hidden="true" src="${src}"></ion-icon>`;
toolbar.appendChild(button); buttonsContainer.appendChild(button);
button.addEventListener('click', (e: Event) => { button.addEventListener('click', (e: Event) => {
if (!this.element.innerHTML) { if (!this.element.innerHTML) {

View File

@ -44,8 +44,10 @@ body.core-reading-mode-enabled {
[core-reading-mode] { [core-reading-mode] {
zoom: var(--reading-mode-zoom, 1); zoom: var(--reading-mode-zoom, 1);
&> * { &> * {
--ion-item-background: var(--reading-mode-background, --ion-background-color);
--text-color: var(--reading-mode-text-color, --text-color); --text-color: var(--reading-mode-text-color, --text-color);
--color: var(--reading-mode-text-color, --text-color); --color: var(--reading-mode-text-color, --text-color);
--subdued-text-color: var(--text-color);
color: var(--text-color); color: var(--text-color);
} }
} }