Merge pull request #2803 from crazyserver/MOBILE-3320

Mobile 3320
main
Dani Palou 2021-06-01 14:48:44 +02:00 committed by GitHub
commit f2c19b0a6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 620 additions and 415 deletions

View File

@ -18,7 +18,7 @@
<ion-list *ngIf="!badges.empty" class="ion-no-margin"> <ion-list *ngIf="!badges.empty" class="ion-no-margin">
<ion-item button class="ion-text-wrap" *ngFor="let badge of badges.items" [attr.aria-label]="badge.name" <ion-item button class="ion-text-wrap" *ngFor="let badge of badges.items" [attr.aria-label]="badge.name"
(click)="badges.select(badge)" [attr.aria-current]="badges.getItemAriaCurrent(badge)"> (click)="badges.select(badge)" [attr.aria-current]="badges.getItemAriaCurrent(badge)" detail="true">
<ion-avatar slot="start"> <ion-avatar slot="start">
<img [src]="badge.badgeurl" [alt]="badge.name" core-external-content> <img [src]="badge.badgeurl" [alt]="badge.name" core-external-content>
</ion-avatar> </ion-avatar>

View File

@ -3,8 +3,8 @@
<h2>{{ 'addon.block_activitymodules.pluginname' | translate }}</h2> <h2>{{ 'addon.block_activitymodules.pluginname' | translate }}</h2>
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="margin">
<ion-item class="ion-text-wrap item-media" *ngFor="let entry of entries" detail="false" button <ion-item class="ion-text-wrap item-media" *ngFor="let entry of entries" detail="true" button
(click)="gotoCoureListModType(entry)"> (click)="gotoCoureListModType(entry)">
<img slot="start" [src]="entry.icon" alt="" role="presentation" class="core-module-icon"> <img slot="start" [src]="entry.icon" alt="" role="presentation" class="core-module-icon">
<ion-label>{{ entry.name }}</ion-label> <ion-label>{{ entry.name }}</ion-label>

View File

@ -36,7 +36,7 @@
</core-context-menu-item> </core-context-menu-item>
</core-context-menu> </core-context-menu>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="margin">
<div class="safe-padding-horizontal" [hidden]="showFilter || !showSelectorFilter"> <div class="safe-padding-horizontal" [hidden]="showFilter || !showSelectorFilter">
<!-- "Time" selector. --> <!-- "Time" selector. -->
<core-combobox [label]="'core.show' | translate" [selection]="selectedFilter" (onChange)="selectedChanged($event)"> <core-combobox [label]="'core.show' | translate" [selection]="selectedFilter" (onChange)="selectedChanged($event)">

View File

@ -20,7 +20,7 @@
</core-horizontal-scroll-controls> </core-horizontal-scroll-controls>
</div> </div>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false" class="safe-area-page"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="safe-area-page margin">
<core-empty-box *ngIf="courses.length == 0" image="assets/img/icons/courses.svg" inline="true" <core-empty-box *ngIf="courses.length == 0" image="assets/img/icons/courses.svg" inline="true"
[message]="'addon.block_recentlyaccessedcourses.nocourses' | translate"></core-empty-box> [message]="'addon.block_recentlyaccessedcourses.nocourses' | translate"></core-empty-box>
<!-- List of courses. --> <!-- List of courses. -->

View File

@ -5,7 +5,7 @@
</core-horizontal-scroll-controls> </core-horizontal-scroll-controls>
</div> </div>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false" class="safe-area-page"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="safe-area-page margin">
<div <div
[id]="scrollElementId" [id]="scrollElementId"
[hidden]="!items || items.length === 0" [hidden]="!items || items.length === 0"

View File

@ -3,7 +3,7 @@
<h2>{{ 'addon.block_sitemainmenu.pluginname' | translate }}</h2> <h2>{{ 'addon.block_sitemainmenu.pluginname' | translate }}</h2>
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="margin">
<ng-container *ngIf="mainMenuBlock"> <ng-container *ngIf="mainMenuBlock">
<ion-item class="ion-text-wrap" *ngIf="mainMenuBlock.summary"> <ion-item class="ion-text-wrap" *ngIf="mainMenuBlock.summary">
<ion-label> <ion-label>

View File

@ -20,7 +20,7 @@
</core-horizontal-scroll-controls> </core-horizontal-scroll-controls>
</div> </div>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false" class="safe-area-page"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="safe-area-page margin">
<core-empty-box *ngIf="courses.length == 0" image="assets/img/icons/courses.svg" inline="true" <core-empty-box *ngIf="courses.length == 0" image="assets/img/icons/courses.svg" inline="true"
[message]="'addon.block_starredcourses.nocourses' | translate"></core-empty-box> [message]="'addon.block_starredcourses.nocourses' | translate"></core-empty-box>
<!-- List of courses. --> <!-- List of courses. -->

View File

@ -9,7 +9,7 @@
</core-context-menu-item> </core-context-menu-item>
</core-context-menu> </core-context-menu>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="margin">
<div class="safe-padding-horizontal"> <div class="safe-padding-horizontal">
<core-combobox [selection]="filter" (onChange)="switchFilter($event)"> <core-combobox [selection]="filter" (onChange)="switchFilter($event)">
<ion-select-option class="ion-text-wrap" value="all"> <ion-select-option class="ion-text-wrap" value="all">
@ -35,12 +35,12 @@
</ion-select-option> </ion-select-option>
</core-combobox> </core-combobox>
</div> </div>
<core-loading [hideUntil]="timeline.loaded" [hidden]="sort != 'sortbydates'" [fullscreen]="false"> <core-loading [hideUntil]="timeline.loaded" [hidden]="sort != 'sortbydates'" [fullscreen]="false" class="margin">
<addon-block-timeline-events [events]="timeline.events" showCourse="true" [canLoadMore]="timeline.canLoadMore" <addon-block-timeline-events [events]="timeline.events" showCourse="true" [canLoadMore]="timeline.canLoadMore"
(loadMore)="loadMoreTimeline()" [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events> (loadMore)="loadMoreTimeline()" [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events>
</core-loading> </core-loading>
<core-loading [hideUntil]="timelineCourses.loaded" [hidden]="sort != 'sortbycourses'" <core-loading [hideUntil]="timelineCourses.loaded" [hidden]="sort != 'sortbycourses'"
[fullscreen]="false" class="safe-area-page"> [fullscreen]="false" class="safe-area-page margin">
<ion-grid class="ion-no-padding"> <ion-grid class="ion-no-padding">
<ion-row class="ion-no-padding"> <ion-row class="ion-no-padding">
<ion-col *ngFor="let course of timelineCourses.courses" class="ion-no-padding" size="12" size-md="6"> <ion-col *ngFor="let course of timelineCourses.courses" class="ion-no-padding" size="12" size-md="6">

View File

@ -54,22 +54,18 @@
<core-tag-list [tags]="entry.tags"></core-tag-list> <core-tag-list [tags]="entry.tags"></core-tag-list>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item *ngIf="commentsEnabled" detail="true"> <core-comments *ngIf="commentsEnabled" [component]="this.component" [itemId]="entry.id" area="format_blog"
<ion-label> [instanceId]="entry.userid" contextLevel="user" [showItem]="true">
<core-comments [component]="this.component" [itemId]="entry.id" area="format_blog" </core-comments>
[instanceId]="entry.userid" contextLevel="user">
</core-comments>
</ion-label>
</ion-item>
<core-file *ngFor="let file of entry.attachmentfiles" [file]="file" [component]="this.component" <core-file *ngFor="let file of entry.attachmentfiles" [file]="file" [component]="this.component"
[componentId]="entry.id"> [componentId]="entry.id">
</core-file> </core-file>
<ion-item *ngIf="entry.uniquehash" [href]="entry.uniquehash" core-link> <ion-item *ngIf="entry.uniquehash" [href]="entry.uniquehash" core-link detail="true">
<ion-label>{{ 'addon.blog.linktooriginalentry' | translate }}</ion-label> <ion-label>{{ 'addon.blog.linktooriginalentry' | translate }}</ion-label>
</ion-item> </ion-item>
</ion-card-content> </ion-card-content>
<ion-row class="ion-text-center"> <ion-row class="ion-text-center" *ngIf="entry.lastmodified > entry.created">
<ion-col *ngIf="entry.lastmodified > entry.created"> <ion-col>
<ion-note> <ion-note>
<ion-icon name="fas-clock" <ion-icon name="fas-clock"
[attr.aria-label]="'core.lastmodified' | translate"></ion-icon> {{entry.lastmodified | coreTimeAgo}} [attr.aria-label]="'core.lastmodified' | translate"></ion-icon> {{entry.lastmodified | coreTimeAgo}}

View File

@ -5,7 +5,7 @@
<ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin"> <ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin">
<ng-container *ngFor="let event of filteredEvents"> <ng-container *ngFor="let event of filteredEvents">
<ion-item class="ion-text-wrap addon-calendar-event" [attr.aria-label]="event.name" (click)="eventClicked(event)" button <ion-item class="ion-text-wrap addon-calendar-event" [attr.aria-label]="event.name" (click)="eventClicked(event)" button
[ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" detail="true">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon" alt="" <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon" alt=""
role="presentation"> role="presentation">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true"> <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true">

View File

@ -60,7 +60,7 @@
<ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin"> <ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin">
<ng-container *ngFor="let event of filteredEvents"> <ng-container *ngFor="let event of filteredEvents">
<ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name" (click)="gotoEvent(event.id)" <ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name" (click)="gotoEvent(event.id)"
[class.item-dimmed]="event.ispast" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button> [class.item-dimmed]="event.ispast" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button detail="true">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon" alt="" <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon" alt=""
role="presentation"> role="presentation">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true"> <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true">

View File

@ -101,7 +101,7 @@
</ng-container> </ng-container>
<!-- Advanced options. --> <!-- Advanced options. -->
<ion-item button class="ion-text-wrap core-expandable divider" (click)="toggleAdvanced()"> <ion-item button class="ion-text-wrap core-expandable divider" (click)="toggleAdvanced()" detail="false">
<ion-icon *ngIf="!advanced" name="fas-caret-right" slot="start" aria-hidden="true"></ion-icon> <ion-icon *ngIf="!advanced" name="fas-caret-right" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="advanced" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon> <ion-icon *ngIf="advanced" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
@ -138,7 +138,7 @@
<ion-radio slot="end" value="0"></ion-radio> <ion-radio slot="end" value="0"></ion-radio>
<ion-label>{{ 'addon.calendar.durationnone' | translate }}</ion-label> <ion-label>{{ 'addon.calendar.durationnone' | translate }}</ion-label>
</ion-item> </ion-item>
<ion-item button (click)="selectDuration('1')"> <ion-item button (click)="selectDuration('1')" detail="false">
<ion-radio slot="end" value="1"></ion-radio> <ion-radio slot="end" value="1"></ion-radio>
<ion-label>{{ 'addon.calendar.durationuntil' | translate }}</ion-label> <ion-label>{{ 'addon.calendar.durationuntil' | translate }}</ion-label>
<ion-datetime formControlName="timedurationuntil" [max]="maxDate" [min]="minDate" <ion-datetime formControlName="timedurationuntil" [max]="maxDate" [min]="minDate"
@ -146,7 +146,7 @@
[displayFormat]="dateFormat" [disabled]="form.controls.duration.value != 1"> [displayFormat]="dateFormat" [disabled]="form.controls.duration.value != 1">
</ion-datetime> </ion-datetime>
</ion-item> </ion-item>
<ion-item button (click)="selectDuration('2')"> <ion-item button (click)="selectDuration('2')" detail="false">
<ion-radio slot="end" value="2"></ion-radio> <ion-radio slot="end" value="2"></ion-radio>
<ion-label>{{ 'addon.calendar.durationminutes' | translate }}</ion-label> <ion-label>{{ 'addon.calendar.durationminutes' | translate }}</ion-label>
<ion-input type="number" name="timedurationminutes" slot="end" <ion-input type="number" name="timedurationminutes" slot="end"

View File

@ -166,12 +166,13 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.notificationsEnabled = CoreLocalNotifications.isAvailable(); this.notificationsEnabled = CoreLocalNotifications.isAvailable();
this.loadUpcoming = !!CoreNavigator.getRouteBooleanParam('upcoming');
this.showCalendar = !this.loadUpcoming;
this.route.queryParams.subscribe(async () => { this.route.queryParams.subscribe(async () => {
this.filter.courseId = CoreNavigator.getRouteNumberParam('courseId'); this.filter.courseId = CoreNavigator.getRouteNumberParam('courseId');
this.year = CoreNavigator.getRouteNumberParam('year'); this.year = CoreNavigator.getRouteNumberParam('year');
this.month = CoreNavigator.getRouteNumberParam('month'); this.month = CoreNavigator.getRouteNumberParam('month');
this.loadUpcoming = !!CoreNavigator.getRouteBooleanParam('upcoming');
this.showCalendar = !this.loadUpcoming;
this.filter.filtered = !!this.filter.courseId; this.filter.filtered = !!this.filter.courseId;
this.fetchData(true, false); this.fetchData(true, false);

View File

@ -44,7 +44,7 @@
</ion-item-divider> </ion-item-divider>
<ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name" (click)="gotoEvent(event.id)" <ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name" (click)="gotoEvent(event.id)"
[attr.aria-current]="event.id == eventId ? 'page' : 'false'" [attr.aria-current]="event.id == eventId ? 'page' : 'false'"
[ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button> [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button detail="true">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon" alt="" <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon" alt=""
role="presentation"> role="presentation">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start"

View File

@ -15,7 +15,7 @@
<ion-list> <ion-list>
<ion-item class="ion-text-wrap" *ngFor="let competency of competencies.items" <ion-item class="ion-text-wrap" *ngFor="let competency of competencies.items"
[attr.aria-label]="competency.competency.shortname" (click)="competencies.select(competency)" [attr.aria-label]="competency.competency.shortname" (click)="competencies.select(competency)"
[attr.aria-current]="competencies.getItemAriaCurrent(competency)" button> [attr.aria-current]="competencies.getItemAriaCurrent(competency)" button detail="true">
<ion-label> <ion-label>
<p class="item-heading">{{ competency.competency.shortname }} <em>{{competency.competency.idnumber}}</em></p> <p class="item-heading">{{ competency.competency.shortname }} <em>{{competency.competency.idnumber}}</em></p>
</ion-label> </ion-label>

View File

@ -28,47 +28,50 @@
</core-format-text> </core-format-text>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap only-links">
<ion-label> <ion-label>
<strong>{{ 'addon.competency.path' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
<a *ngIf="competency.competency.comppath.showlinks" <p>
[href]="competency.competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' + <a *ngIf="competency.competency.comppath.showlinks"
competency.competency.comppath.framework.id + '&pagecontextid=' + [href]="competency.competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' +
competency.competency.comppath.pagecontextid" competency.competency.comppath.framework.id + '&pagecontextid=' +
core-link [title]="competency.competency.comppath.framework.name"> competency.competency.comppath.pagecontextid"
{{ competency.competency.comppath.framework.name }} core-link>
</a> {{ competency.competency.comppath.framework.name }}
<ng-container *ngIf="!competency.competency.comppath.showlinks">
{{ competency.competency.comppath.framework.name }}
</ng-container>
&nbsp;/&nbsp;
<span *ngFor="let ancestor of competency.competency.comppath.ancestors">
<a *ngIf="competency.competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)">
{{ ancestor.name }}
</a> </a>
<ng-container *ngIf="!competency.competency.comppath.showlinks">{{ ancestor.name }}</ng-container> <ng-container *ngIf="!competency.competency.comppath.showlinks">
<ng-container *ngIf="!ancestor.last">&nbsp;/&nbsp;</ng-container> {{ competency.competency.comppath.framework.name }}
</span> </ng-container>
&nbsp;/&nbsp;
<ng-container *ngFor="let ancestor of competency.competency.comppath.ancestors">
<button *ngIf="competency.competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)"
class="as-link">
{{ ancestor.name }}
</button>
<ng-container *ngIf="!competency.competency.comppath.showlinks">{{ ancestor.name }}</ng-container>
<ng-container *ngIf="!ancestor.last">&nbsp;/&nbsp;</ng-container>
</ng-container>
</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<strong>{{ 'addon.competency.crossreferencedcompetencies' | translate }}</strong>: <p class="item-heading">{{ 'addon.competency.crossreferencedcompetencies' | translate }}</p>
<div *ngIf="!competency.competency.hasrelatedcompetencies"> <p *ngIf="!competency.competency.hasrelatedcompetencies">
{{ 'addon.competency.nocrossreferencedcompetencies' | translate }} {{ 'addon.competency.nocrossreferencedcompetencies' | translate }}
</div> </p>
<div *ngIf="competency.competency.hasrelatedcompetencies"> <ng-container *ngIf="competency.competency.hasrelatedcompetencies">
<p *ngFor="let relatedcomp of competency.competency.relatedcompetencies"> <p *ngFor="let relatedcomp of competency.competency.relatedcompetencies">
<a (click)="openCompetencySummary(relatedcomp.id)"> <button (click)="openCompetencySummary(relatedcomp.id)" class="as-link">
{{ relatedcomp.shortname }} - {{ relatedcomp.idnumber }} {{ relatedcomp.shortname }} - {{ relatedcomp.idnumber }}
</a> </button>
</p> </p>
</div> </ng-container>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="coursemodules"> <ion-item class="ion-text-wrap" *ngIf="coursemodules">
<ion-label> <ion-label>
<strong>{{ 'addon.competency.activities' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.activities' | translate }}</p>
<p *ngIf="coursemodules.length == 0"> <p *ngIf="coursemodules.length == 0">
{{ 'addon.competency.noactivities' | translate }} {{ 'addon.competency.noactivities' | translate }}
</p> </p>
@ -87,13 +90,13 @@
<ng-container *ngIf="userCompetency"> <ng-container *ngIf="userCompetency">
<ion-item class="ion-text-wrap" *ngIf="competency.usercompetency && competency.usercompetency!.status"> <ion-item class="ion-text-wrap" *ngIf="competency.usercompetency && competency.usercompetency!.status">
<ion-label> <ion-label>
<strong>{{ 'addon.competency.reviewstatus' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.reviewstatus' | translate }}</p>
{{ competency.usercompetency!.statusname }} <p>{{ competency.usercompetency!.statusname }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<strong>{{ 'addon.competency.proficient' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.proficient' | translate }}</p>
</ion-label> </ion-label>
<ion-badge slot="end" color="success" *ngIf="userCompetency.proficiency"> <ion-badge slot="end" color="success" *ngIf="userCompetency.proficiency">
{{ 'core.yes' | translate }} {{ 'core.yes' | translate }}
@ -104,7 +107,7 @@
</ion-item> </ion-item>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<strong>{{ 'addon.competency.rating' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.rating' | translate }}</p>
</ion-label> </ion-label>
<ion-badge color="dark" slot="end">{{ userCompetency.gradename }}</ion-badge> <ion-badge color="dark" slot="end">{{ userCompetency.gradename }}</ion-badge>
</ion-item> </ion-item>

View File

@ -23,11 +23,15 @@
</ion-item> </ion-item>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<strong>{{ 'addon.competency.path' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
{{ competency.comppath.framework.name }} <p>{{ competency.comppath.framework.name }}
<span *ngFor="let ancestor of competency.comppath.ancestors"> <ng-container *ngFor="let ancestor of competency.comppath.ancestors">
&nbsp;/&nbsp;<a (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a> &nbsp;/&nbsp;
</span> <button class="as-link" (click)="openCompetencySummary(ancestor.id)">
{{ ancestor.name }}
</button>
</ng-container>
</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
</ion-card> </ion-card>

View File

@ -34,11 +34,11 @@
<ion-item class="ion-text-wrap" <ion-item class="ion-text-wrap"
*ngIf="competencies.statistics.canmanagecoursecompetencies && competencies.statistics.leastproficientcount > 0"> *ngIf="competencies.statistics.canmanagecoursecompetencies && competencies.statistics.leastproficientcount > 0">
<ion-label> <ion-label>
<strong>{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}</strong>: <p class="item-heading">{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}</p>
<p *ngFor="let comp of competencies.statistics.leastproficient"> <p *ngFor="let comp of competencies.statistics.leastproficient">
<a (click)="openCompetencySummary(comp.id)"> <button class="as-link" (click)="openCompetencySummary(comp.id)">
{{ comp.shortname }} - {{ comp.idnumber }} {{ comp.shortname }} - {{ comp.idnumber }}
</a> </button>
</p> </p>
</ion-label> </ion-label>
</ion-item> </ion-item>
@ -63,7 +63,7 @@
[attr.aria-label]="competency.competency.shortname" detail="true" button> [attr.aria-label]="competency.competency.shortname" detail="true" button>
<ion-label> <ion-label>
<p class="item-heading"> <p class="item-heading">
<strong>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></strong> {{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em>
</p> </p>
</ion-label> </ion-label>
<ion-badge slot="end" *ngIf="competency.usercompetencycourse && competency.usercompetencycourse.gradename" <ion-badge slot="end" *ngIf="competency.usercompetencycourse && competency.usercompetencycourse.gradename"
@ -79,33 +79,36 @@
</core-format-text> </core-format-text>
</p> </p>
<div> <div>
<strong>{{ 'addon.competency.path' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
<a *ngIf="competency.comppath.showlinks" <p>
[href]="competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' + <a *ngIf="competency.comppath.showlinks"
competency.comppath.framework.id + '&pagecontextid=' + competency.comppath.pagecontextid" [href]="competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' +
core-link [title]="competency.comppath.framework.name"> competency.comppath.framework.id + '&pagecontextid=' + competency.comppath.pagecontextid"
{{ competency.comppath.framework.name }} core-link [title]="competency.comppath.framework.name">
</a> {{ competency.comppath.framework.name }}
<ng-container *ngIf="!competency.comppath.showlinks">
{{ competency.comppath.framework.name }}
</ng-container>
&nbsp;/&nbsp;
<span *ngFor="let ancestor of competency.comppath.ancestors">
<a *ngIf="competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)">
{{ ancestor.name }}
</a> </a>
<ng-container *ngIf="!competency.comppath.showlinks">{{ ancestor.name }}</ng-container> <ng-container *ngIf="!competency.comppath.showlinks">
<ng-container *ngIf="!ancestor.last">&nbsp;/&nbsp;</ng-container> {{ competency.comppath.framework.name }}
</span> </ng-container>
&nbsp;/&nbsp;
<ng-container *ngFor="let ancestor of competency.comppath.ancestors">
<button class="as-link" *ngIf="competency.comppath.showlinks"
(click)="openCompetencySummary(ancestor.id)">
{{ ancestor.name }}
</button>
<ng-container *ngIf="!competency.comppath.showlinks">{{ ancestor.name }}</ng-container>
<ng-container *ngIf="!ancestor.last">&nbsp;/&nbsp;</ng-container>
</ng-container>
</p>
</div> </div>
<div *ngIf="competencies.statistics.canmanagecoursecompetencies"> <div *ngIf="competencies.statistics.canmanagecoursecompetencies">
<strong>{{ 'addon.competency.uponcoursecompletion' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.uponcoursecompletion' | translate }}</p>
<ng-container *ngFor="let ruleoutcome of competency.ruleoutcomeoptions"> <ng-container *ngFor="let ruleoutcome of competency.ruleoutcomeoptions">
<span *ngIf="ruleoutcome.selected">{{ ruleoutcome.text }}</span> <span *ngIf="ruleoutcome.selected">{{ ruleoutcome.text }}</span>
</ng-container> </ng-container>
</div> </div>
<div> <div>
<strong>{{ 'addon.competency.activities' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.activities' | translate }}</p>
<p *ngIf="competency.coursemodules.length == 0"> <p *ngIf="competency.coursemodules.length == 0">
{{ 'addon.competency.noactivities' | translate }} {{ 'addon.competency.noactivities' | translate }}
</p> </p>
@ -121,7 +124,7 @@
</ion-item> </ion-item>
</div> </div>
<div *ngIf="competency.plans"> <div *ngIf="competency.plans">
<strong>{{ 'addon.competency.userplans' | translate }}</strong> <p class="item-heading">{{ 'addon.competency.userplans' | translate }}</p>
<p *ngIf="competency.plans.length == 0"> <p *ngIf="competency.plans.length == 0">
{{ 'addon.competency.nouserplanswithcompetency' | translate }} {{ 'addon.competency.nouserplanswithcompetency' | translate }}
</p> </p>

View File

@ -23,37 +23,35 @@
<ion-list> <ion-list>
<ion-item class="ion-text-wrap" *ngIf="plan.plan.description" lines="none"> <ion-item class="ion-text-wrap" *ngIf="plan.plan.description" lines="none">
<ion-label> <ion-label>
<core-format-text [text]="plan.plan.description" contextLevel="user" <p>
[contextInstanceId]="plan.plan.userid"> <core-format-text [text]="plan.plan.description" contextLevel="user"
</core-format-text> [contextInstanceId]="plan.plan.userid">
</core-format-text>
</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap" lines="none">
<ion-label> <ion-label>
<p> <p class="item-heading">{{ 'addon.competency.status' | translate }}</p>
<strong>{{ 'addon.competency.status' | translate }}</strong>: {{ plan.plan.statusname }} <p>{{ plan.plan.statusname }}</p>
</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="plan.plan.duedate > 0" lines="none"> <ion-item class="ion-text-wrap" *ngIf="plan.plan.duedate > 0" lines="none">
<ion-label> <ion-label>
<p> <p class="item-heading">{{ 'addon.competency.duedate' | translate }}</p>
<strong>{{ 'addon.competency.duedate' | translate }}</strong>: <p>{{ plan.plan.duedate * 1000 | coreFormatDate }}</p>
{{ plan.plan.duedate * 1000 | coreFormatDate }}
</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="plan.plan.template" lines="none"> <ion-item class="ion-text-wrap" *ngIf="plan.plan.template" lines="none">
<ion-label> <ion-label>
<p> <p class="item-heading">{{ 'addon.competency.template' | translate }}</p>
<strong>{{ 'addon.competency.template' | translate }}</strong>: {{ plan.plan.template.shortname }} <p>{{ plan.plan.template.shortname }}</p>
</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap" lines="none">
<ion-label> <ion-label id="addon-competency-plan-{{plan.plan.id}}-progress">
<p id="addon-competency-plan-{{plan.plan.id}}-progress"> <p class="item-heading">{{ 'addon.competency.progress' | translate }}</p>
<strong>{{ 'addon.competency.progress' | translate }}</strong>: <p>
{{ 'addon.competency.xcompetenciesproficientoutofy' | translate: {{ 'addon.competency.xcompetenciesproficientoutofy' | translate:
{$a: {x: plan.proficientcompetencycount, y: plan.competencycount} } }} {$a: {x: plan.proficientcompetencycount, y: plan.competencycount} } }}
</p> </p>

View File

@ -17,7 +17,7 @@
</core-empty-box> </core-empty-box>
<ion-list *ngIf="!plans.empty" class="ion-no-margin"> <ion-list *ngIf="!plans.empty" class="ion-no-margin">
<ion-item class="ion-text-wrap" *ngFor="let plan of plans.items" [attr.aria-label]="plan.name" <ion-item class="ion-text-wrap" *ngFor="let plan of plans.items" [attr.aria-label]="plan.name"
(click)="plans.select(plan)" [attr.aria-current]="plans.getItemAriaCurrent(plan)" button> (click)="plans.select(plan)" [attr.aria-current]="plans.getItemAriaCurrent(plan)" button detail="true">
<ion-label> <ion-label>
<p class="item-heading">{{ plan.name }}</p> <p class="item-heading">{{ plan.name }}</p>
<p *ngIf="plan.duedate > 0"> <p *ngIf="plan.duedate > 0">

View File

@ -8,10 +8,9 @@
alt="" onError="this.src='assets/img/group-avatar.png'" core-external-content role="presentation" alt="" onError="this.src='assets/img/group-avatar.png'" core-external-content role="presentation"
[siteId]="siteId || null"> [siteId]="siteId || null">
<core-user-avatar *ngIf="loaded && otherMember" class="core-bar-button-image" [user]="otherMember" <core-user-avatar *ngIf="loaded && otherMember" class="core-bar-button-image" [user]="otherMember"
[linkProfile]="false" [checkOnline]="otherMember.showonlinestatus" (click)="showInfo && viewInfo()"> [linkProfile]="false" [checkOnline]="otherMember.showonlinestatus">
</core-user-avatar> </core-user-avatar>
<core-format-text [text]="title" contextLevel="system" [contextInstanceId]="0" <core-format-text [text]="title" contextLevel="system" [contextInstanceId]="0"></core-format-text>
(click)="showInfo && !isGroup && viewInfo()"></core-format-text>
<ion-icon *ngIf="conversation && conversation.isfavourite" name="fas-star" <ion-icon *ngIf="conversation && conversation.isfavourite" name="fas-star"
[attr.aria-label]="'core.favourites' | translate"> [attr.aria-label]="'core.favourites' | translate">
</ion-icon> </ion-icon>
@ -93,12 +92,16 @@
[@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'"> [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
<ion-label> <ion-label>
<!-- User data. --> <!-- User data. -->
<div class="item-heading addon-message-user" [attr.aria-label]="message.useridfrom == currentUserId ? <div *ngIf="message.showUserData" class="item-heading addon-message-user">
('addon.messages.you' | translate) : members[message.useridfrom].fullname">
<core-user-avatar slot="start" [user]="members[message.useridfrom]" [linkProfile]="false" <core-user-avatar slot="start" [user]="members[message.useridfrom]" [linkProfile]="false"
*ngIf="message.showUserData"></core-user-avatar> aria-hidden="true">
</core-user-avatar>
<div *ngIf="message.showUserData">{{ members[message.useridfrom].fullname }}</div> {{ members[message.useridfrom].fullname }}
</div>
<div *ngIf="!message.showUserData" class="sr-only">
{{ message.useridfrom == currentUserId
? ('addon.messages.you' | translate)
: members[message.useridfrom].fullname }}
</div> </div>
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. --> <!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->
@ -128,7 +131,7 @@
<!-- Scroll bottom. --> <!-- Scroll bottom. -->
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="loaded && newMessages > 0"> <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="loaded && newMessages > 0">
<ion-fab-button size="small" (click)="scrollToFirstUnreadMessage()" color="light" <ion-fab-button size="small" (click)="scrollToFirstUnreadMessage()" color="light"
[attr.aria-label]="'addon.messages.newmessages' | translate"> [attr.aria-label]="'addon.messages.newmessages' | translate">
<ion-icon name="fas-arrow-down" aria-hidden="true"></ion-icon> <ion-icon name="fas-arrow-down" aria-hidden="true"></ion-icon>
<span class="core-discussion-messages-badge">{{ newMessages }}</span> <span class="core-discussion-messages-badge">{{ newMessages }}</span>
</ion-fab-button> </ion-fab-button>

View File

@ -250,6 +250,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
this.title = user.fullname; this.title = user.fullname;
} }
this.conversationImage = user.profileimageurl; this.conversationImage = user.profileimageurl;
this.members[user.id] = <AddonMessagesConversationMember>user;
return; return;
}).catch(() => { }).catch(() => {
@ -1302,7 +1303,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
async viewInfo(): Promise<void> { async viewInfo(): Promise<void> {
if (this.isGroup) { if (this.isGroup) {
// Display the group information. // Display the group information.
const userId = await CoreDomUtils.openModal<number>({ const userId = await CoreDomUtils.openSideModal<number>({
component: AddonMessagesConversationInfoComponent, component: AddonMessagesConversationInfoComponent,
componentProps: { componentProps: {
conversationId: this.conversationId, conversationId: this.conversationId,

View File

@ -19,11 +19,11 @@
<core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch()" <core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch()"
[placeholder]=" 'addon.messages.message' | translate" autocorrect="off" spellcheck="false" lengthCheck="2" [placeholder]=" 'addon.messages.message' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
[disabled]="!loaded" searchArea="AddonMessagesDiscussions"></core-search-box> [disabled]="!loaded" searchArea="AddonMessagesDiscussions" [autoFocus]="false"></core-search-box>
<core-loading [hideUntil]="loaded" [message]="loadingMessage"> <core-loading [hideUntil]="loaded" [message]="loadingMessage">
<ion-list class="ion-no-margin"> <ion-list class="ion-no-margin">
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts()" <ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts()"
[attr.aria-label]="'addon.messages.contacts' | translate" detail="true" button> [attr.aria-label]="'addon.messages.contacts' | translate" detail="true" button>
@ -40,7 +40,7 @@
</ion-item-divider> </ion-item-divider>
<ion-item class="ion-text-wrap addon-message-discussion" *ngFor="let result of search.results" button <ion-item class="ion-text-wrap addon-message-discussion" *ngFor="let result of search.results" button
[attr.aria-label]="result.fullname" (click)="gotoDiscussion(result.userid, result.messageid)" [attr.aria-label]="result.fullname" (click)="gotoDiscussion(result.userid, result.messageid)"
[attr.aria-current]="result.userid == discussionUserId ? 'page' : 'false'"> [attr.aria-current]="result.userid == discussionUserId ? 'page' : 'false'" detail="false">
<core-user-avatar [user]="result" slot="start" [checkOnline]="result.showonlinestatus"></core-user-avatar> <core-user-avatar [user]="result" slot="start" [checkOnline]="result.showonlinestatus"></core-user-avatar>
<ion-label> <ion-label>
<p class="item-heading">{{ result.fullname }}</p> <p class="item-heading">{{ result.fullname }}</p>

View File

@ -40,7 +40,8 @@
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120" <core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" (click)="expandDescription($event)"> contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"
(onClick)="expandDescription($event)">
</core-format-text> </core-format-text>
</ion-label> </ion-label>
</ion-item> </ion-item>

View File

@ -1,4 +1,4 @@
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" class="margin">
<!-- User and status of the submission. --> <!-- User and status of the submission. -->
<ion-item class="ion-text-wrap" *ngIf="!blindMarking && user" core-user-link [userId]="submitId" [courseId]="courseId" <ion-item class="ion-text-wrap" *ngIf="!blindMarking && user" core-user-link [userId]="submitId" [courseId]="courseId"

View File

@ -40,7 +40,7 @@
<!-- List of submissions. --> <!-- List of submissions. -->
<ng-container *ngFor="let submission of submissions.items"> <ng-container *ngFor="let submission of submissions.items">
<ion-item class="ion-text-wrap" (click)="submissions.select(submission)" button <ion-item class="ion-text-wrap" (click)="submissions.select(submission)" button
[attr.aria-current]="submissions.getItemAriaCurrent(submission)"> [attr.aria-current]="submissions.getItemAriaCurrent(submission)" detail="true">
<core-user-avatar [user]="submission" [linkProfile]="false" slot="start"></core-user-avatar> <core-user-avatar [user]="submission" [linkProfile]="false" slot="start"></core-user-avatar>
<ion-label> <ion-label>
<p class="item-heading" *ngIf="submission.userfullname">{{submission.userfullname}}</p> <p class="item-heading" *ngIf="submission.userfullname">{{submission.userfullname}}</p>

View File

@ -1,4 +1,4 @@
<ion-item *ngIf="commentsEnabled" class="ion-text-wrap" (click)="showComments($event)" detail="false" button> <ion-item *ngIf="commentsEnabled" class="ion-text-wrap" (click)="showComments($event)" detail="true" button>
<ion-label> <ion-label>
<h2>{{plugin.name}}</h2> <h2>{{plugin.name}}</h2>
<core-comments contextLevel="module" [instanceId]="assign.cmid" component="assignsubmission_comments" <core-comments contextLevel="module" [instanceId]="assign.cmid" component="assignsubmission_comments"

View File

@ -13,11 +13,11 @@
<ion-list> <ion-list>
<ion-item class="ion-text-wrap" *ngFor="let chapter of chapters" (click)="loadChapter(chapter.id)" <ion-item class="ion-text-wrap" *ngFor="let chapter of chapters" (click)="loadChapter(chapter.id)"
[attr.aria-current]="selected == chapter.id ? 'page' : 'false'" button [attr.aria-current]="selected == chapter.id ? 'page' : 'false'" button
[class.item-dimmed]="chapter.hidden"> [class.item-dimmed]="chapter.hidden" detail="false">
<ion-label> <ion-label>
<p [class.ion-padding-left]="addPadding && chapter.level == 1 ? true : null"> <p [class.ion-padding-left]="addPadding && chapter.level == 1 ? true : null" class="item-heading">
<span *ngIf="showNumbers" class="addon-mod-book-number">{{chapter.indexNumber}}</span> <span *ngIf="showNumbers" class="addon-mod-book-number">{{chapter.indexNumber}}&nbsp;</span>
<span *ngIf="showBullets" class="addon-mod-book-bullet">&bull;</span> <span *ngIf="showBullets" class="addon-mod-book-bullet">&bull;&nbsp;</span>
<core-format-text [text]="chapter.title" contextLevel="module" [contextInstanceId]="moduleId" <core-format-text [text]="chapter.title" contextLevel="module" [contextInstanceId]="moduleId"
[courseId]="courseId"> [courseId]="courseId">
</core-format-text> </core-format-text>

View File

@ -30,7 +30,7 @@
<ion-toggle [(ngModel)]="showAll" (ionChange)="fetchSessions(true)"></ion-toggle> <ion-toggle [(ngModel)]="showAll" (ionChange)="fetchSessions(true)"></ion-toggle>
</ion-item> </ion-item>
<ion-card *ngFor="let session of sessions.items" (click)="sessions.select(session)" <ion-card *ngFor="let session of sessions.items" (click)="sessions.select(session)" button
[attr.aria-current]="sessions.getItemAriaCurrent(session)" [attr.aria-current]="sessions.getItemAriaCurrent(session)"
[class.addon-mod-chat-session-show-more]="session.sessionusers.length < session.allsessionusers.length"> [class.addon-mod-chat-session-show-more]="session.sessionusers.length < session.allsessionusers.length">
@ -47,11 +47,10 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
</ion-card-content> </ion-card-content>
<div *ngIf="session.sessionusers.length < session.allsessionusers.length"> <ion-button *ngIf="session.sessionusers.length < session.allsessionusers.length" fill="clear" expand="block"
<ion-button fill="clear" (click)="showMoreUsers(session, $event)"> (click)="showMoreUsers(session, $event)">
{{ 'core.showmore' | translate }} {{ 'core.showmore' | translate }}
</ion-button> </ion-button>
</div>
</ion-card> </ion-card>
<core-empty-box *ngIf="sessions.empty" icon="far-comments" [message]="'addon.mod_chat.nosessionsfound' | translate"> <core-empty-box *ngIf="sessions.empty" icon="far-comments" [message]="'addon.mod_chat.nosessionsfound' | translate">

View File

@ -1,5 +1,4 @@
<core-dynamic-component [component]="fieldComponent" [data]="pluginData"> <core-dynamic-component [component]="fieldComponent" [data]="pluginData">
<!-- This content will be replaced by the component if found. --> <!-- This content will be replaced by the component if found. -->
<core-loading [hideUntil]="fieldLoaded"> <core-loading [hideUntil]="fieldLoaded" [fullscreen]="false"></core-loading>
</core-loading>
</core-dynamic-component> </core-dynamic-component>

View File

@ -103,16 +103,18 @@
<!-- Reset search. --> <!-- Reset search. -->
<ng-container *ngIf="search.searching && !isEmpty"> <ng-container *ngIf="search.searching && !isEmpty">
<ion-item *ngIf="!foundRecordsTranslationData"> <ion-item (click)="searchReset()" button detail="false">
<ion-label> <ion-label color="primary">
<a (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</a> {{ 'addon.mod_data.resetsettings' | translate}}
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-card class="core-success-card" *ngIf="foundRecordsTranslationData" (click)="searchReset()"> <ion-card class="core-success-card" *ngIf="foundRecordsTranslationData">
<ion-item><ion-label> <ion-item (click)="searchReset()" button detail="false">
<p [innerHTML]="'addon.mod_data.foundrecords' | translate:{$a: foundRecordsTranslationData}"></p> <ion-label>
</ion-label></ion-item> <p [innerHTML]="'addon.mod_data.foundrecords' | translate:{$a: foundRecordsTranslationData}"></p>
</ion-label>
</ion-item>
</ion-card> </ion-card>
</ng-container> </ng-container>
@ -144,7 +146,7 @@
<core-empty-box *ngIf="isEmpty && search.searching" icon="fas-database" [message]="'addon.mod_data.nomatch' | translate" <core-empty-box *ngIf="isEmpty && search.searching" icon="fas-database" [message]="'addon.mod_data.nomatch' | translate"
class="core-empty-box-clickable"> class="core-empty-box-clickable">
<a (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</a> <button class="as-link" (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</button>
</core-empty-box> </core-empty-box>
</core-loading> </core-loading>

View File

@ -48,6 +48,8 @@ import { AddonModDataPrefetchHandler } from '../../services/handlers/prefetch';
import { AddonModDataComponentsCompileModule } from '../components-compile.module'; import { AddonModDataComponentsCompileModule } from '../components-compile.module';
import { AddonModDataSearchComponent } from '../search/search'; import { AddonModDataSearchComponent } from '../search/search';
const contentToken = '<!-- CORE-DATABASE-CONTENT-GOES-HERE -->';
/** /**
* Component that displays a data index page. * Component that displays a data index page.
*/ */
@ -318,12 +320,22 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
if (!this.isEmpty) { if (!this.isEmpty) {
this.entries = (entries.offlineEntries || []).concat(entries.entries); this.entries = (entries.offlineEntries || []).concat(entries.entries);
let entriesHTML = AddonModDataHelper.getTemplate( let headerAndFooter = AddonModDataHelper.getTemplate(
this.database!, this.database!,
AddonModDataTemplateType.LIST_HEADER, AddonModDataTemplateType.LIST_HEADER,
this.fieldsArray, this.fieldsArray,
); );
headerAndFooter += contentToken;
headerAndFooter += AddonModDataHelper.getTemplate(
this.database!,
AddonModDataTemplateType.LIST_FOOTER,
this.fieldsArray,
);
headerAndFooter = CoreDomUtils.fixHtml(headerAndFooter);
// Get first entry from the whole list. // Get first entry from the whole list.
if (!this.search.searching || !this.firstEntry) { if (!this.search.searching || !this.firstEntry) {
this.firstEntry = this.entries[0].id; this.firstEntry = this.entries[0].id;
@ -331,6 +343,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
const template = AddonModDataHelper.getTemplate(this.database!, AddonModDataTemplateType.LIST, this.fieldsArray); const template = AddonModDataHelper.getTemplate(this.database!, AddonModDataTemplateType.LIST, this.fieldsArray);
let entriesHTML = '';
const entriesById: Record<number, AddonModDataEntry> = {}; const entriesById: Record<number, AddonModDataEntry> = {};
this.entries.forEach((entry, index) => { this.entries.forEach((entry, index) => {
entriesById[entry.id] = entry; entriesById[entry.id] = entry;
@ -349,9 +363,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
actions, actions,
); );
}); });
entriesHTML += AddonModDataHelper.getTemplate(this.database!, AddonModDataTemplateType.LIST_FOOTER, this.fieldsArray);
this.entriesRendered = CoreDomUtils.fixHtml(entriesHTML); this.entriesRendered = headerAndFooter.replace(contentToken, entriesHTML);
// Pass the input data to the component. // Pass the input data to the component.
this.jsData = { this.jsData = {

View File

@ -12,6 +12,10 @@ $grid-column-paddings: (
xl: var(--ion-grid-column-padding-xl, $grid-column-padding) xl: var(--ion-grid-column-padding-xl, $grid-column-padding)
) !default; ) !default;
:host {
--border-color: var(--gray);
}
.addon-data-contents { .addon-data-contents {
overflow: visible; overflow: visible;
white-space: normal; white-space: normal;
@ -19,9 +23,7 @@ $grid-column-paddings: (
padding: 16px; padding: 16px;
background-color: var(--ion-item-background); background-color: var(--ion-item-background);
border-width: 1px 0; border-bottom: 1px solid var(--border-color);
border-style: solid;
border-color: var(--gray-dark);
::ng-deep { ::ng-deep {
table, tbody { table, tbody {

View File

@ -14,9 +14,9 @@
<ion-input type="text" [formControlName]="'f_'+field.id" [placeholder]="field.name"></ion-input> <ion-input type="text" [formControlName]="'f_'+field.id" [placeholder]="field.name"></ion-input>
</span> </span>
<span *ngIf="listMode && imageUrl" (click)="gotoEntry.emit(entryId)"> <button class="as-link" *ngIf="listMode && imageUrl" (click)="navigateEntry()">
<img [src]="imageUrl" [alt]="title" class="core-media-adapt-width listMode_picture" core-external-content/> <img [src]="imageUrl" [alt]="title" class="core-media-adapt-width listMode_picture" core-external-content/>
</span> </button>
<img *ngIf="showMode && imageUrl" [src]="imageUrl" [alt]="title" class="core-media-adapt-width listMode_picture" <img *ngIf="showMode && imageUrl" [src]="imageUrl" [alt]="title" class="core-media-adapt-width listMode_picture"
[attr.width]="width" [attr.height]="height" core-external-content/> [attr.width]="width" [attr.height]="height" core-external-content/>

View File

@ -138,4 +138,11 @@ export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginCo
} }
} }
/**
* Navigate to the entry.
*/
navigateEntry(): void {
this.gotoEntry.emit(this.entryId);
}
} }

View File

@ -37,7 +37,7 @@
</ion-select> </ion-select>
</ion-item> </ion-item>
<div class="addon-data-contents addon-data-entries-{{database.id}}" *ngIf="database && entry"> <div class="addon-data-contents addon-data-entry addon-data-entries-{{database.id}}" *ngIf="database && entry">
<core-style [css]="database.csstemplate" prefix=".addon-data-entries-{{database.id}}"></core-style> <core-style [css]="database.csstemplate" prefix=".addon-data-entries-{{database.id}}"></core-style>
<core-compile-html [text]="entryHtml" [jsData]="jsData" [extraImports]="extraImports" <core-compile-html [text]="entryHtml" [jsData]="jsData" [extraImports]="extraImports"
@ -54,13 +54,10 @@
[scaleId]="database.scale"> [scaleId]="database.scale">
</core-rating-aggregate> </core-rating-aggregate>
<ion-item *ngIf="database && database.comments && entry && entry.id > 0 && commentsEnabled"> <core-comments *ngIf="database && database.comments && entry && entry.id > 0 && commentsEnabled"
<ion-label> contextLevel="module" [instanceId]="database.coursemodule" component="mod_data" [itemId]="entry.id"
<core-comments contextLevel="module" [instanceId]="database.coursemodule" component="mod_data" [itemId]="entry.id" area="database_entry" [courseId]="courseId" (onLoading)="setLoadingComments($event)" [showItem]="true">
area="database_entry" [displaySpinner]="false" [courseId]="courseId" (onLoading)="setLoadingComments($event)"> </core-comments>
</core-comments>
</ion-label>
</ion-item>
<ion-grid *ngIf="hasPrevious || hasNext"> <ion-grid *ngIf="hasPrevious || hasNext">
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">

View File

@ -658,7 +658,7 @@ export class AddonModDataHelperProvider {
// Add core-link directive to links. // Add core-link directive to links.
template = template.replace( template = template.replace(
/<a ([^>]*href="[^>]*)>/ig, /<a ([^>]*href="[^>]*)>/ig,
(match, attributes) => '<a core-link capture="true" ' + attributes + '>', (match, attributes) => '<button class="as-link" core-link capture="true" ' + attributes + '>',
); );
return template; return template;

View File

@ -1,36 +1,38 @@
<ion-item class="ion-text-wrap" (click)="setLockState(true)" *ngIf="discussion.canlock && !discussion.locked"> <ion-item button class="ion-text-wrap" (click)="setLockState(true)" *ngIf="discussion.canlock && !discussion.locked" detail="false">
<ion-icon name="fa-lock" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fa-lock" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.mod_forum.lockdiscussion' | translate }}</h2> <p class="item-heading">{{ 'addon.mod_forum.lockdiscussion' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" (click)="setLockState(false)" *ngIf="discussion.canlock && discussion.locked"> <ion-item button class="ion-text-wrap" (click)="setLockState(false)" *ngIf="discussion.canlock && discussion.locked" detail="false">
<ion-icon name="fa-unlock" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fa-unlock" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.mod_forum.unlockdiscussion' | translate }}</h2> <p class="item-heading">{{ 'addon.mod_forum.unlockdiscussion' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" (click)="setPinState(true)" *ngIf="canPin && !discussion.pinned"> <ion-item button class="ion-text-wrap" (click)="setPinState(true)" *ngIf="canPin && !discussion.pinned" detail="false">
<ion-icon name="fa-map-pin" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fa-map-pin" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.mod_forum.pindiscussion' | translate }}</h2> <p class="item-heading">{{ 'addon.mod_forum.pindiscussion' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" (click)="setPinState(false)" *ngIf="canPin && discussion.pinned"> <ion-item button class="ion-text-wrap" (click)="setPinState(false)" *ngIf="canPin && discussion.pinned" detail="false">
<ion-icon name="fa-map-pin" slot="start" class="icon-slash" aria-hidden="true"></ion-icon> <ion-icon name="fa-map-pin" slot="start" class="icon-slash" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.mod_forum.unpindiscussion' | translate }}</h2> <p class="item-heading">{{ 'addon.mod_forum.unpindiscussion' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" (click)="toggleFavouriteState(true)" *ngIf="discussion.canfavourite && !discussion.starred"> <ion-item button class="ion-text-wrap" (click)="toggleFavouriteState(true)" *ngIf="discussion.canfavourite && !discussion.starred"
detail="false">
<ion-icon name="fa-star" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fa-star" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.mod_forum.addtofavourites' | translate }}</h2> <p class="item-heading">{{ 'addon.mod_forum.addtofavourites' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" (click)="toggleFavouriteState(false)" *ngIf="discussion.canfavourite && discussion.starred"> <ion-item button class="ion-text-wrap" (click)="toggleFavouriteState(false)" *ngIf="discussion.canfavourite && discussion.starred"
detail="false">
<ion-icon name="fa-star" slot="start" class="icon-slash" aria-hidden="true"></ion-icon> <ion-icon name="fa-star" slot="start" class="icon-slash" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.mod_forum.removefromfavourites' | translate }}</h2> <p class="item-heading">{{ 'addon.mod_forum.removefromfavourites' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>

View File

@ -23,7 +23,7 @@
(action)="prefetch($event)"> (action)="prefetch($event)">
</core-context-menu-item> </core-context-menu-item>
<core-context-menu-item *ngIf="size" <core-context-menu-item *ngIf="size"
iconDescription="cube" iconAction="trash" iconDescription="fas-archive" iconAction="trash"
[priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}" [closeOnClick]="false" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}" [closeOnClick]="false"
(action)="removeFiles($event)"> (action)="removeFiles($event)">
</core-context-menu-item> </core-context-menu-item>

View File

@ -1,26 +1,26 @@
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="margin">
<ion-item class="ion-text-wrap" (click)="editPost()" *ngIf="offlinePost || (canEdit && isOnline)"> <ion-item button class="ion-text-wrap" (click)="editPost()" *ngIf="offlinePost || (canEdit && isOnline)" detail="false">
<ion-icon name="fas-pen" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-pen" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'addon.mod_forum.edit' | translate }}</h2> <p class="item-heading">{{ 'addon.mod_forum.edit' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" (click)="deletePost()" *ngIf="offlinePost || (canDelete && isOnline)"> <ion-item button class="ion-text-wrap" (click)="deletePost()" *ngIf="offlinePost || (canDelete && isOnline)" detail="false">
<ion-icon name="fas-trash" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-trash" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2 *ngIf="!offlinePost">{{ 'addon.mod_forum.delete' | translate }}</h2> <p class="item-heading" *ngIf="!offlinePost">{{ 'addon.mod_forum.delete' | translate }}</p>
<h2 *ngIf="offlinePost">{{ 'core.discard' | translate }}</h2> <p class="item-heading" *ngIf="offlinePost">{{ 'core.discard' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" (click)="dismiss()" *ngIf="wordCount"> <ion-item class="ion-text-wrap" *ngIf="wordCount">
<ion-label> <ion-label>
<h2>{{ 'core.numwords' | translate: {'$a': wordCount} }}</h2> <p class="item-heading">{{ 'core.numwords' | translate: {'$a': wordCount} }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" [href]="url" *ngIf="url" core-link capture="false"> <ion-item class="ion-text-wrap" [href]="url" *ngIf="url" core-link capture="false" button detail="false">
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'core.openinbrowser' | translate }}</h2> <p class="item-heading">{{ 'core.openinbrowser' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
</core-loading> </core-loading>

View File

@ -58,13 +58,10 @@
<ion-item class="ion-text-wrap" *ngIf="!entry.approved"> <ion-item class="ion-text-wrap" *ngIf="!entry.approved">
<ion-label><p><em>{{ 'addon.mod_glossary.entrypendingapproval' | translate }}</em></p></ion-label> <ion-label><p><em>{{ 'addon.mod_glossary.entrypendingapproval' | translate }}</em></p></ion-label>
</ion-item> </ion-item>
<ion-item *ngIf="glossary && glossary.allowcomments && entry && entry.id > 0 && commentsEnabled"> <core-comments *ngIf="glossary && glossary.allowcomments && entry && entry.id > 0 && commentsEnabled"
<ion-label> contextLevel="module" [instanceId]="glossary.coursemodule" component="mod_glossary"
<core-comments contextLevel="module" [instanceId]="glossary.coursemodule" component="mod_glossary" [itemId]="entry.id" area="glossary_entry" [courseId]="glossary.course" [showItem]="true">
[itemId]="entry.id" area="glossary_entry" [courseId]="glossary.course"> </core-comments>
</core-comments>
</ion-label>
</ion-item>
<core-rating-rate *ngIf="glossary && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" <core-rating-rate *ngIf="glossary && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module"
[instanceId]="glossary.coursemodule" [itemId]="entry.id" [itemSetId]="0" [courseId]="glossary.course" [instanceId]="glossary.coursemodule" [itemId]="entry.id" [itemSetId]="0" [courseId]="glossary.course"
[aggregateMethod]="glossary.assessed" [scaleId]="glossary.scale" [userId]="entry.userid" [aggregateMethod]="glossary.assessed" [scaleId]="glossary.scale" [userId]="entry.userid"

View File

@ -63,7 +63,7 @@
<ion-label> <ion-label>
<h2>{{ 'addon.mod_h5pactivity.outcome' | translate }}</h2> <h2>{{ 'addon.mod_h5pactivity.outcome' | translate }}</h2>
<p *ngIf="attempt.success !== null && attempt.success" > <p *ngIf="attempt.success !== null && attempt.success" >
<ion-icon name="fa-check-circle" aria-hidden="true"></ion-icon> <ion-icon name="fas-check-circle" aria-hidden="true"></ion-icon>
{{ 'addon.mod_h5pactivity.attempt_success_pass' | translate }} {{ 'addon.mod_h5pactivity.attempt_success_pass' | translate }}
</p> </p>
<p *ngIf="attempt.success !== null && !attempt.success" > <p *ngIf="attempt.success !== null && !attempt.success" >
@ -166,12 +166,12 @@
<!-- Template to render an answer. --> <!-- Template to render an answer. -->
<ng-template #answerTemplate let-answer="answer"> <ng-template #answerTemplate let-answer="answer">
<p *ngIf="answer.correct"> <p *ngIf="answer.correct">
<ion-icon name="fa-check" [attr.aria-label]="'addon.mod_h5pactivity.answer_correct' | translate" color="success"> <ion-icon name="fas-check" [attr.aria-label]="'addon.mod_h5pactivity.answer_correct' | translate" color="success">
</ion-icon> </ion-icon>
{{ answer.answer }} {{ answer.answer }}
</p> </p>
<p *ngIf="answer.incorrect"> <p *ngIf="answer.incorrect">
<ion-icon name="fa-remove" [attr.aria-label]="'addon.mod_h5pactivity.answer_incorrect' | translate" color="danger"> <ion-icon name="fas-times" [attr.aria-label]="'addon.mod_h5pactivity.answer_incorrect' | translate" color="danger">
</ion-icon> </ion-icon>
{{ answer.answer }} {{ answer.answer }}
</p> </p>
@ -179,15 +179,15 @@
{{ answer.answer }} {{ answer.answer }}
</p> </p>
<p *ngIf="answer.checked"> <p *ngIf="answer.checked">
<ion-icon name="fa-check-circle" [attr.aria-label]="'addon.mod_h5pactivity.answer_checked' | translate"> <ion-icon name="fas-check-circle" [attr.aria-label]="'addon.mod_h5pactivity.answer_checked' | translate">
</ion-icon> </ion-icon>
</p> </p>
<p *ngIf="answer.pass"> <p *ngIf="answer.pass">
<ion-icon name="fa-check" [attr.aria-label]="'addon.mod_h5pactivity.answer_pass' | translate" color="success"> <ion-icon name="fas-check" [attr.aria-label]="'addon.mod_h5pactivity.answer_pass' | translate" color="success">
</ion-icon> </ion-icon>
</p> </p>
<p *ngIf="answer.fail"> <p *ngIf="answer.fail">
<ion-icon name="fa-remove" [attr.aria-label]="'addon.mod_h5pactivity.answer_fail' | translate" color="danger"> <ion-icon name="fas-times" [attr.aria-label]="'addon.mod_h5pactivity.answer_fail' | translate" color="danger">
</ion-icon> </ion-icon>
</p> </p>
</ng-template> </ng-template>

View File

@ -99,7 +99,7 @@
[alt]="'addon.mod_h5pactivity.attempt_completion_no' | translate"> [alt]="'addon.mod_h5pactivity.attempt_completion_no' | translate">
</ion-col> </ion-col>
<ion-col class="ion-text-center addon-mod_h5pactivity-table-success-col"> <ion-col class="ion-text-center addon-mod_h5pactivity-table-success-col">
<ion-icon *ngIf="attempt.success !== null && attempt.success" name="fa-check-circle" <ion-icon *ngIf="attempt.success !== null && attempt.success" name="fas-check-circle"
[attr.aria-label]="'addon.mod_h5pactivity.attempt_success_pass' | translate"> [attr.aria-label]="'addon.mod_h5pactivity.attempt_success_pass' | translate">
</ion-icon> </ion-icon>
<ion-icon *ngIf="attempt.success !== null && !attempt.success" name="far-circle" <ion-icon *ngIf="attempt.success !== null && !attempt.success" name="far-circle"

View File

@ -1,6 +1,6 @@
<!-- Buttons to add to the header. --> <!-- Buttons to add to the header. -->
<core-navbar-buttons slot="end"> <core-navbar-buttons slot="end">
<ion-button (click)="showToc()" aria-haspopup="true" [attr.aria-label]="'addon.mod_imscp.toc' | translate"> <ion-button *ngIf="loaded" (click)="showToc()" aria-haspopup="true" [attr.aria-label]="'addon.mod_imscp.toc' | translate">
<ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
<core-context-menu> <core-context-menu>

View File

@ -12,9 +12,12 @@
<nav> <nav>
<ion-list> <ion-list>
<ion-item *ngFor="let item of items" (click)="loadItem(item.href)" <ion-item *ngFor="let item of items" (click)="loadItem(item.href)"
[attr.aria-current]="selected == item.href ? 'page' : 'false'" button> [attr.aria-current]="selected == item.href ? 'page' : 'false'" button detail="false">
<ion-label [class.core-bold]="!item.href"> <ion-label [class.core-bold]="!item.href">
<span class="ion-padding-left" *ngFor="let i of getNumberForPadding(item.level)"></span>{{item.title}} <p class="item-heading">
<span class="ion-padding-left" *ngFor="let i of getNumberForPadding(item.level)"></span>
{{item.title}}
</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
</ion-list> </ion-list>

View File

@ -296,7 +296,7 @@
</ion-card-header> </ion-card-header>
<ion-item class="ion-text-wrap" *ngFor="let student of overview.students" button <ion-item class="ion-text-wrap" *ngFor="let student of overview.students" button
(click)="openRetake(student.id)"> (click)="openRetake(student.id)" detail="true">
<core-user-avatar [user]="student" slot="start" [userId]="student.id" [courseId]="courseId"> <core-user-avatar [user]="student" slot="start" [userId]="student.id" [courseId]="courseId">
</core-user-avatar> </core-user-avatar>
<ion-label> <ion-label>

View File

@ -27,7 +27,7 @@
<ion-item button class="ion-text-wrap {{question.stateClass}}" *ngFor="let question of navigation" <ion-item button class="ion-text-wrap {{question.stateClass}}" *ngFor="let question of navigation"
[attr.aria-current]="!summaryShown && currentPage == question.page ? 'page' : 'false'" [attr.aria-current]="!summaryShown && currentPage == question.page ? 'page' : 'false'"
(click)="loadPage(question.page, question.slot)" detail="true"> (click)="loadPage(question.page, question.slot)" detail="false">
<ion-label> <ion-label>
<span *ngIf="question.number">{{ 'core.question.questionno' | translate:{$a: question.number} }}</span> <span *ngIf="question.number">{{ 'core.question.questionno' | translate:{$a: question.number} }}</span>

View File

@ -158,11 +158,11 @@
<ion-icon *ngIf="sco.icon" [name]="sco.icon.icon" [attr.aria-label]="sco.icon.description" <ion-icon *ngIf="sco.icon" [name]="sco.icon.icon" [attr.aria-label]="sco.icon.description"
slot="start"> slot="start">
</ion-icon> </ion-icon>
<a *ngIf="sco.prereq && sco.launch" (click)="open($event, false, sco.id)" tappable="true"> <button class="as-link" *ngIf="sco.prereq && sco.launch" (click)="open($event, false, sco.id)">
<core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id" <core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="courseId"> [courseId]="courseId">
</core-format-text> </core-format-text>
</a> </button>
<span *ngIf="!sco.prereq || !sco.launch"> <span *ngIf="!sco.prereq || !sco.launch">
<core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id" <core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="courseId"> [courseId]="courseId">

View File

@ -12,7 +12,7 @@
<nav> <nav>
<ion-list> <ion-list>
<!-- Go to "home". --> <!-- Go to "home". -->
<ion-item class="ion-text-wrap" *ngIf="homeView" (click)="goToWikiHome()" button> <ion-item class="ion-text-wrap" *ngIf="homeView" (click)="goToWikiHome()" button detail="true">
<ion-icon name="fas-home" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-home" slot="start" aria-hidden="true"></ion-icon>
<ion-label>{{ 'addon.mod_wiki.gowikihome' | translate }}</ion-label> <ion-label>{{ 'addon.mod_wiki.gowikihome' | translate }}</ion-label>
</ion-item> </ion-item>
@ -21,7 +21,7 @@
<ion-label><h2>{{ letter.label }}</h2></ion-label> <ion-label><h2>{{ letter.label }}</h2></ion-label>
</ion-item-divider> </ion-item-divider>
<ion-item class="ion-text-wrap" *ngFor="let page of letter.pages" (click)="goToPage(page)" <ion-item class="ion-text-wrap" *ngFor="let page of letter.pages" (click)="goToPage(page)"
[attr.aria-current]="selectedTitle == page.title ? 'page' : 'false'" button> [attr.aria-current]="selectedTitle == page.title ? 'page' : 'false'" button detail="false">
<ion-icon name="fas-home" slot="start" *ngIf="page.firstpage" aria-hidden="true"></ion-icon> <ion-icon name="fas-home" slot="start" *ngIf="page.firstpage" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<core-format-text [text]="page.title" contextLevel="module" [contextInstanceId]="moduleId" <core-format-text [text]="page.title" contextLevel="module" [contextInstanceId]="moduleId"

View File

@ -21,7 +21,7 @@ import { HttpClient, HttpClientModule } from '@angular/common/http';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { IonicModule, IonicRouteStrategy, iosTransitionAnimation } from '@ionic/angular';
import { CoreModule } from '@/core/core.module'; import { CoreModule } from '@/core/core.module';
import { AddonsModule } from '@/addons/addons.module'; import { AddonsModule } from '@/addons/addons.module';
@ -42,7 +42,11 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
imports: [ imports: [
BrowserModule, BrowserModule,
BrowserAnimationsModule, BrowserAnimationsModule,
IonicModule.forRoot(), IonicModule.forRoot(
{
navAnimation: iosTransitionAnimation,
},
),
HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content. HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
TranslateModule.forRoot({ TranslateModule.forRoot({
loader: { loader: {

View File

@ -0,0 +1,58 @@
// (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 { createAnimation } from '@ionic/angular';
import { Animation } from '@ionic/core';
import { Platform } from '@singletons';
/**
* Sliding transition for lateral modals.
*/
export function CoreModalLateralTransitionEnter(baseEl: HTMLElement): Animation {
const OFF_RIGHT = Platform.isRTL ? '-100%' : '100%';
const backdropAnimation = createAnimation()
.addElement(baseEl.querySelector('ion-backdrop')!)
.fromTo('opacity', 0.01, 0.4);
const wrapperAnimation = createAnimation()
.addElement(baseEl.querySelector('.modal-wrapper')!)
.fromTo('transform', 'translateX(' + OFF_RIGHT + ')', 'translateX(0)')
.fromTo('opacity', 0.8, 1);
return createAnimation()
.addElement(baseEl)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.duration(300)
.addAnimation([backdropAnimation, wrapperAnimation]);
}
export function CoreModalLateralTransitionLeave(baseEl: HTMLElement): Animation {
const OFF_RIGHT = Platform.isRTL ? '-100%' : '100%';
const backdropAnimation = createAnimation()
.addElement(baseEl.querySelector('ion-backdrop')!)
.fromTo('opacity', 0.4, 0.0);
const wrapperAnimation = createAnimation()
.addElement(baseEl.querySelector('.modal-wrapper')!)
.beforeStyles({ opacity: 1 })
.fromTo('transform', 'translateX(0)', 'translateX(' + OFF_RIGHT + ')');
return createAnimation()
.addElement(baseEl)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.duration(300)
.addAnimation([backdropAnimation, wrapperAnimation]);
}

View File

@ -84,6 +84,7 @@ ion-button {
text-transform: none; text-transform: none;
font-weight: 400; font-weight: 400;
font-size: 16px; font-size: 16px;
line-height: 20px;
} }
.select-text { .select-text {

View File

@ -1,5 +1,11 @@
:host { @import "~theme/globals";
ion-list {
padding: 0; ion-list {
} padding: 0;
}
ion-icon[slot=start] {
@include margin-horizontal(0, 10px);
width: 0.8em;
height: 0.8em;
} }

View File

@ -7,7 +7,9 @@
[detail]="(item.href && !item.iconAction) || null" role="menuitem" [button]="(item.href && !item.iconAction)"> [detail]="(item.href && !item.iconAction) || null" role="menuitem" [button]="(item.href && !item.iconAction)">
<ion-icon *ngIf="item.iconDescription" [name]="item.iconDescription" [attr.aria-label]="item.ariaDescription" slot="start"> <ion-icon *ngIf="item.iconDescription" [name]="item.iconDescription" [attr.aria-label]="item.ariaDescription" slot="start">
</ion-icon> </ion-icon>
<ion-label><core-format-text [clean]="true" [text]="item.content" [filter]="false"></core-format-text></ion-label> <ion-label>
<p class="item-heading"><core-format-text [clean]="true" [text]="item.content" [filter]="false"></core-format-text></p>
</ion-label>
<ion-icon *ngIf="(item.href || item.action) && item.iconAction && item.iconAction != 'spinner'" [name]="item.iconAction" <ion-icon *ngIf="(item.href || item.action) && item.iconAction && item.iconAction != 'spinner'" [name]="item.iconAction"
[class.icon-slash]="item.iconSlash" slot="end"> [class.icon-slash]="item.iconSlash" slot="end">
</ion-icon> </ion-icon>

View File

@ -1,8 +1,6 @@
<div class="core-loading-container" *ngIf="!hideUntil" role="status" [@coreShowHideAnimation]> <div class="core-loading-container" *ngIf="!hideUntil" role="status" [@coreShowHideAnimation]>
<span class="core-loading-spinner"> <ion-spinner color="primary"></ion-spinner>
<ion-spinner color="primary"></ion-spinner> <p class="core-loading-message" *ngIf="message" role="status">{{message}}</p>
<p class="core-loading-message" *ngIf="message" role="status">{{message}}</p>
</span>
</div> </div>
<div #content class="core-loading-content" [id]="uniqueId" [attr.aria-busy]="hideUntil" [@coreShowHideAnimation]> <div #content class="core-loading-content" [id]="uniqueId" [attr.aria-busy]="hideUntil" [@coreShowHideAnimation]>
<ng-content *ngIf="hideUntil"> <ng-content *ngIf="hideUntil">

View File

@ -4,35 +4,44 @@
--loading-background: var(--ion-background-color); --loading-background: var(--ion-background-color);
--loading-spinner: var(--ion-color-primary); --loading-spinner: var(--ion-color-primary);
--loading-text-color: var(--ion-text-color); --loading-text-color: var(--ion-text-color);
--loading-inline-margin: 0;
--loading-inline-min-height: 28px;
position: static; position: static;
color: var(--loading-text-color); color: var(--loading-text-color);
&.margin {
--loading-inline-margin: 10px;
}
&.core-loading-loaded {
--loading-inline-margin: 0;
--loading-inline-min-height: 0;
}
ion-spinner { ion-spinner {
--color: var(--loading-spinner); --color: var(--loading-spinner);
color: var(--color); color: var(--color);
} }
> .core-loading-container { .core-loading-container {
position: absolute; position: absolute;
@include position(0, 0, 0, 0); @include position(0, 0, 0, 0);
display: table; display: flex;
height: 100%; height: 100%;
width: 100%; width: 100%;
text-align: center; justify-content: center;
clear: both; align-items: center;
flex-direction: column;
z-index: 3; z-index: 3;
margin: 0; margin: 0;
padding: 10px 0 0 0; padding: 0;
background-color: var(--loading-background); background-color: var(--loading-background);
-webkit-transition: all 200ms ease-in-out; @include core-transition(all, 200ms);
transition: all 200ms ease-in-out; }
.core-loading-spinner { .core-loading-message {
display: table-cell; @include margin(10px, 0, 0, 0);
text-align: center;
vertical-align: middle;
}
} }
.core-loading-content { .core-loading-content {
@ -48,21 +57,26 @@
} }
&.core-loading-inline { &.core-loading-inline {
display: block; --loading-background: transparent;
.core-loading-container {
padding-top: 20px;
position: relative;
}
}
&.core-loading-loaded.core-loading-inline {
position: relative; position: relative;
min-height: 102px; display: block;
min-height: var(--loading-inline-min-height);
margin-top: var(--loading-inline-margin);
margin-bottom: var(--loading-inline-margin);
.core-loading-message {
@include margin(0, 0, 0, 10px);
}
.core-loading-container { .core-loading-container {
padding-top: 10px; flex-direction: row;
position: absolute;
} }
} }
} }
:host-context(ion-item) {
&.core-loading-inline {
position: static;
display: block;
}
}

View File

@ -2,7 +2,7 @@
<ion-tab-bar slot="top" class="core-tabs-bar" [hidden]="!tabs || numTabsShown <= 1" #tabBar> <ion-tab-bar slot="top" class="core-tabs-bar" [hidden]="!tabs || numTabsShown <= 1" #tabBar>
<ion-spinner *ngIf="!hideUntil"></ion-spinner> <ion-spinner *ngIf="!hideUntil"></ion-spinner>
<ion-row *ngIf="hideUntil"> <ion-row *ngIf="hideUntil">
<ion-col class="col-with-arrow ion-no-padding" (click)="slidePrev()" size="1"> <ion-col class="col-with-arrow ion-no-padding" (click)="slidePrev()" size="1" [class.clickable]="showPrevButton">
<ion-icon *ngIf="showPrevButton" name="fas-chevron-left" [attr.aria-label]="'core.previous' | translate"></ion-icon> <ion-icon *ngIf="showPrevButton" name="fas-chevron-left" [attr.aria-label]="'core.previous' | translate"></ion-icon>
</ion-col> </ion-col>
<ion-col class="ion-no-padding" size="10"> <ion-col class="ion-no-padding" size="10">
@ -41,7 +41,7 @@
</ng-container> </ng-container>
</ion-slides> </ion-slides>
</ion-col> </ion-col>
<ion-col class="col-with-arrow ion-no-padding" (click)="slideNext()" size="1"> <ion-col class="col-with-arrow ion-no-padding" (click)="slideNext()" size="1" [class.clickable]="showNextButton">
<ion-icon *ngIf="showNextButton" name="fas-chevron-right" [attr.aria-label]="'core.next' | translate"></ion-icon> <ion-icon *ngIf="showNextButton" name="fas-chevron-right" [attr.aria-label]="'core.next' | translate"></ion-icon>
</ion-col> </ion-col>
</ion-row> </ion-row>

View File

@ -1,7 +1,7 @@
<ion-tab-bar slot="top" class="core-tabs-bar" [hidden]="!tabs || numTabsShown <= 1" #tabBar> <ion-tab-bar slot="top" class="core-tabs-bar" [hidden]="!tabs || numTabsShown <= 1" #tabBar>
<ion-spinner *ngIf="!hideUntil"></ion-spinner> <ion-spinner *ngIf="!hideUntil"></ion-spinner>
<ion-row *ngIf="hideUntil"> <ion-row *ngIf="hideUntil">
<ion-col class="col-with-arrow ion-no-padding" (click)="slidePrev()" size="1"> <ion-col class="col-with-arrow ion-no-padding" (click)="slidePrev()" size="1" [class.clickable]="showPrevButton">
<ion-icon *ngIf="showPrevButton" name="fas-chevron-left" [attr.aria-label]="'core.previous' | translate"></ion-icon> <ion-icon *ngIf="showPrevButton" name="fas-chevron-left" [attr.aria-label]="'core.previous' | translate"></ion-icon>
</ion-col> </ion-col>
<ion-col class="ion-no-padding" size="10"> <ion-col class="ion-no-padding" size="10">
@ -39,7 +39,7 @@
</ng-container> </ng-container>
</ion-slides> </ion-slides>
</ion-col> </ion-col>
<ion-col class="col-with-arrow ion-no-padding" (click)="slideNext()" size="1"> <ion-col class="col-with-arrow ion-no-padding" (click)="slideNext()" size="1" [class.clickable]="showNextButton">
<ion-icon *ngIf="showNextButton" name="fas-chevron-right" [attr.aria-label]="'core.next' | translate"></ion-icon> <ion-icon *ngIf="showNextButton" name="fas-chevron-right" [attr.aria-label]="'core.next' | translate"></ion-icon>
</ion-col> </ion-col>
</ion-row> </ion-row>

View File

@ -80,6 +80,7 @@ export class CoreFormatTextDirective implements OnChanges {
@Input() maxHeight?: number; @Input() maxHeight?: number;
@Output() afterRender: EventEmitter<void>; // Called when the data is rendered. @Output() afterRender: EventEmitter<void>; // Called when the data is rendered.
@Output() onClick: EventEmitter<void> = new EventEmitter(); // Called when clicked.
protected element: HTMLElement; protected element: HTMLElement;
protected showMoreDisplayed = false; protected showMoreDisplayed = false;
@ -286,6 +287,13 @@ export class CoreFormatTextDirective implements OnChanges {
// Ignore it if the event was prevented by some other listener. // Ignore it if the event was prevented by some other listener.
return; return;
} }
if (this.onClick.observers.length > 0) {
this.onClick.emit();
return;
}
if (!this.text) { if (!this.text) {
return; return;
} }

View File

@ -57,34 +57,62 @@ export class CoreLinkDirective implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.inApp = typeof this.inApp == 'undefined' ? this.inApp : CoreUtils.isTrueOrOne(this.inApp); this.inApp = typeof this.inApp == 'undefined' ? this.inApp : CoreUtils.isTrueOrOne(this.inApp);
if (this.element.tagName != 'BUTTON' && this.element.tagName != 'A') {
this.element.setAttribute('tabindex', '0');
this.element.setAttribute('role', 'button');
}
this.element.addEventListener('click', async (event) => { this.element.addEventListener('click', async (event) => {
if (event.defaultPrevented) { this.performAction(event);
return; // Link already treated, stop. });
}
let href = this.href || this.element.getAttribute('href') || this.element.getAttribute('xlink:href'); this.element.addEventListener('keydown', (event: KeyboardEvent) => {
if ((event.key == ' ' || event.key == 'Enter')) {
if (!href || CoreUrlUtils.getUrlScheme(href) == 'javascript') { event.preventDefault();
return; event.stopPropagation();
}
event.preventDefault();
event.stopPropagation();
const openIn = this.element.getAttribute('data-open-in');
if (CoreUtils.isTrueOrOne(this.capture)) {
href = CoreTextUtils.decodeURI(href);
const treated = await CoreContentLinksHelper.handleLink(href, undefined, true, true);
if (!treated) {
this.navigate(href, openIn);
}
} else {
this.navigate(href, openIn);
} }
}); });
this.element.addEventListener('keyup', (event: KeyboardEvent) => {
if ((event.key == ' ' || event.key == 'Enter')) {
this.performAction(event);
}
});
}
/**
* Perform "click" action.
*
* @param event Event.
* @returns Resolved when done.
*/
protected async performAction(event: Event): Promise<void> {
if (event.defaultPrevented) {
return; // Link already treated, stop.
}
let href = this.href || this.element.getAttribute('href') || this.element.getAttribute('xlink:href');
if (!href || CoreUrlUtils.getUrlScheme(href) == 'javascript') {
return;
}
event.preventDefault();
event.stopPropagation();
const openIn = this.element.getAttribute('data-open-in');
if (CoreUtils.isTrueOrOne(this.capture)) {
href = CoreTextUtils.decodeURI(href);
const treated = await CoreContentLinksHelper.handleLink(href, undefined, true, true);
if (!treated) {
this.navigate(href, openIn);
}
} else {
this.navigate(href, openIn);
}
} }
/** /**

View File

@ -3,7 +3,7 @@
</div> </div>
<div *ngIf="blocks && blocks.length > 0 && !hideBlocks" [class.core-hide-blocks]="hideBottomBlocks" class="core-course-blocks-side"> <div *ngIf="blocks && blocks.length > 0 && !hideBlocks" [class.core-hide-blocks]="hideBottomBlocks" class="core-course-blocks-side">
<core-loading [hideUntil]="dataLoaded" [fullscreen]="false"> <core-loading [hideUntil]="dataLoaded" [fullscreen]="false" class="margin">
<ion-list> <ion-list>
<!-- Course expand="block"s. --> <!-- Course expand="block"s. -->
<ng-container *ngFor="let block of blocks"> <ng-container *ngFor="let block of blocks">

View File

@ -7,7 +7,7 @@
</h2> </h2>
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false" class="margin">
<ion-item *ngIf="block.contents?.content" class="ion-text-wrap core-block-content"> <ion-item *ngIf="block.contents?.content" class="ion-text-wrap core-block-content">
<ion-label> <ion-label>
<core-format-text [text]="block.contents?.content" contextLevel="block" [contextInstanceId]="block.instanceid" <core-format-text [text]="block.contents?.content" contextLevel="block" [contextInstanceId]="block.instanceid"

View File

@ -1,4 +0,0 @@
.core-comments-clickable {
pointer-events: auto;
cursor: pointer;
}

View File

@ -29,7 +29,6 @@ import { ContextLevel } from '@/core/constants';
@Component({ @Component({
selector: 'core-comments', selector: 'core-comments',
templateUrl: 'core-comments.html', templateUrl: 'core-comments.html',
styleUrls: ['comments.scss'],
}) })
export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestroy { export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestroy {
@ -39,9 +38,9 @@ export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestr
@Input() itemId!: number; @Input() itemId!: number;
@Input() area = ''; @Input() area = '';
@Input() title?: string; @Input() title?: string;
@Input() displaySpinner = true; // Whether to display the loading spinner. @Output() onLoading: EventEmitter<boolean>; // Event that indicates whether the component is loading data.
@Output() onLoading: EventEmitter<boolean>; // Eevent that indicates whether the component is loading data.
@Input() courseId?: number; // Course ID the comments belong to. It can be used to improve performance with filters. @Input() courseId?: number; // Course ID the comments belong to. It can be used to improve performance with filters.
@Input() showItem = false; // Show button as an item.
commentsLoaded = false; commentsLoaded = false;
commentsCount = ''; commentsCount = '';

View File

@ -1,8 +1,27 @@
<core-loading *ngIf="!disabled" [hideUntil]="commentsLoaded || !displaySpinner"> <ng-container *ngIf="!disabled">
<div *ngIf="!countError" (click)="openComments($event)" [class.core-comments-clickable]="!disabled"> <core-loading *ngIf="!showItem" [hideUntil]="commentsLoaded" [fullscreen]="false" class="margin">
{{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }} <button *ngIf="!countError" (click)="openComments($event)" class="as-link">
</div> {{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
<div *ngIf="countError"> </button>
{{ 'core.comments.commentsnotworking' | translate }} <div *ngIf="countError">
</div> {{ 'core.comments.commentsnotworking' | translate }}
</core-loading> </div>
</core-loading>
<ion-item
*ngIf="showItem"
button
[detail]="!countError && commentsLoaded"
(click)="openComments($event)"
[disabled]="countError">
<core-loading [hideUntil]="commentsLoaded" [fullscreen]="false">
<ion-label>
<p *ngIf="!countError" class="item-heading">
{{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
</p>
<p *ngIf="countError">
{{ 'core.comments.commentsnotworking' | translate }}
</p>
</ion-label>
</core-loading>
</ion-item>
</ng-container>

View File

@ -1,5 +1,5 @@
<ion-item class="ion-text-wrap" *ngFor="let item of items" (click)="openCourse(item.courseId)" [attr.aria-label]="item.courseName" <ion-item class="ion-text-wrap" *ngFor="let item of items" (click)="openCourse(item.courseId)" [attr.aria-label]="item.courseName"
button> button detail="true">
<ion-icon name="fas-graduation-cap" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-graduation-cap" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<p class="item-heading">{{ item.courseName }}</p> <p class="item-heading">{{ item.courseName }}</p>

View File

@ -114,11 +114,12 @@
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner> <ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner>
<ion-label><h2>{{ 'core.course.downloadcourse' | translate }}</h2></ion-label> <ion-label><h2>{{ 'core.course.downloadcourse' | translate }}</h2></ion-label>
</ion-item> </ion-item>
<ion-item button (click)="openCourse()" [attr.aria-label]="course.fullname" *ngIf="!avoidOpenCourse && canAccessCourse"> <ion-item button (click)="openCourse()" [attr.aria-label]="course.fullname" *ngIf="!avoidOpenCourse && canAccessCourse"
detail="true">
<ion-icon name="fas-briefcase" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-briefcase" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'core.course.contents' | translate }}</h2></ion-label> <ion-label><h2>{{ 'core.course.contents' | translate }}</h2></ion-label>
</ion-item> </ion-item>
<ion-item [href]="courseUrl" core-link [attr.aria-label]="course.fullname"> <ion-item [href]="courseUrl" core-link [attr.aria-label]="course.fullname" button detail="false">
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'core.openinbrowser' | translate }}</h2></ion-label> <ion-label><h2>{{ 'core.openinbrowser' | translate }}</h2></ion-label>
</ion-item> </ion-item>

View File

@ -1,5 +1,5 @@
<ion-item class="ion-text-wrap" (click)="openCourse()" [class.item-disabled]="course.visible == 0" <ion-item class="ion-text-wrap" (click)="openCourse()" [class.item-disabled]="course.visible == 0"
[attr.aria-label]="course.displayname || course.fullname" detail="true" button> [attr.aria-label]="course.displayname || course.fullname" detail="true" button>
<ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon" <ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon"
[attr.course-color]="course.color ? null : course.colorNumber" [style.color]="course.color"></ion-icon> [attr.course-color]="course.color ? null : course.colorNumber" [style.color]="course.color"></ion-icon>
<ion-avatar *ngIf="course.courseImage" slot="start"> <ion-avatar *ngIf="course.courseImage" slot="start">

View File

@ -12,7 +12,7 @@
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
<ion-button [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 || downloadAllCoursesLoading" <ion-button [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 || downloadAllCoursesLoading"
(click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate"> (click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate">
<ion-icon [name]="downloadAllCoursesIcon" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon [name]="downloadAllCoursesIcon" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
<ion-spinner [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 || <ion-spinner [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 ||

View File

@ -32,7 +32,9 @@
<tbody> <tbody>
<tr <tr
*ngFor="let row of grades.rows" *ngFor="let row of grades.rows"
(click)="row.itemtype != 'category' && grades.select(row)" role="button row"
[attr.tabindex]="row.itemtype != 'category' ? 0 : null"
(ariaButtonClick)="row.itemtype != 'category' && grades.select(row)"
[class]="row.rowclass" [class]="row.rowclass"
[ngClass]='{"core-grades-grade-clickable": row.itemtype != "category"}' [ngClass]='{"core-grades-grade-clickable": row.itemtype != "category"}'
> >

View File

@ -4,13 +4,28 @@
--header-background: var(--white); --header-background: var(--white);
--odd-cell-background: var(--gray-lighter); --odd-cell-background: var(--gray-lighter);
--even-cell-background: var(--white); --even-cell-background: var(--white);
--odd-cell-hover: var(--gray-light);
--even-cell-hover: var(--gray-lighter);
--icon-color: #999999; --icon-color: #999999;
.odd {
--cell-background: var(--odd-cell-background);
--cell-hover: var(--odd-cell-hover);
}
.even {
--cell-background: var(--even-cell-background);
--cell-hover: var(--even-cell-hover);
}
} }
:host-context(body.dark) { :host-context(body.dark) {
--header-background: var(--black); --header-background: var(--black);
--odd-cell-background: var(--gray-darker); --odd-cell-background: var(--gray-darker);
--even-cell-background: var(--black); --even-cell-background: var(--black);
--odd-cell-hover: var(--gray-dark);
--even-cell-hover: var(--gray-darker);
--icon-color: #eeeeee; --icon-color: #eeeeee;
} }
@ -91,21 +106,19 @@
opacity: .7; opacity: .7;
} }
.odd { .odd, .even {
td, th, th[aria-current="page"] { td, th, th[aria-current="page"] {
background-color: var(--odd-cell-background); background-color: var(--cell-background);
}
}
.even {
td, th, th[aria-current="page"] {
background-color: var(--even-cell-background);
} }
} }
.core-grades-grade-clickable { .core-grades-grade-clickable {
cursor: pointer; cursor: pointer;
&:hover {
td, th, th[aria-current="page"] {
background-color: var(--cell-hover);
}
}
} }
} }

View File

@ -78,7 +78,7 @@
<ion-label><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-label> <ion-label><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-label>
</ion-item> </ion-item>
<ion-item button *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-icon" <ion-item button *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-icon"
(click)="oauthClicked(provider)" [attr.aria-label]="provider.name"> (click)="oauthClicked(provider)" [attr.aria-label]="provider.name" detail="false">
<img [src]="provider.iconurl" alt="" width="32" height="32" slot="start"> <img [src]="provider.iconurl" alt="" width="32" height="32" slot="start">
<ion-label>{{provider.name}}</ion-label> <ion-label>{{provider.name}}</ion-label>
</ion-item> </ion-item>

View File

@ -88,7 +88,7 @@
<ion-label><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-label> <ion-label><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-label>
</ion-item> </ion-item>
<ion-item button *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-icon" <ion-item button *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-icon"
(click)="oauthClicked(provider)"> (click)="oauthClicked(provider)" detail="false">
<img [src]="provider.iconurl" alt="" role="presentation" width="32" height="32" slot="start"> <img [src]="provider.iconurl" alt="" role="presentation" width="32" height="32" slot="start">
<ion-label>{{provider.name}}</ion-label> <ion-label>{{provider.name}}</ion-label>
</ion-item> </ion-item>

View File

@ -19,7 +19,7 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<ion-list> <ion-list>
<ion-item button (click)="login(site.id)" *ngFor="let site of sites"> <ion-item button (click)="login(site.id)" *ngFor="let site of sites" detail="true">
<ion-avatar slot="start"> <ion-avatar slot="start">
<img [src]="site.avatar" core-external-content [siteId]="site.id" <img [src]="site.avatar" core-external-content [siteId]="site.id"
alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}" onError="this.src='assets/img/user-avatar.png'"> alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}" onError="this.src='assets/img/user-avatar.png'">

View File

@ -8,90 +8,92 @@
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<ion-list> <core-loading [hideUntil]="!loggedOut">
<ion-item button *ngIf="siteInfo" class="ion-text-wrap" core-user-link [userId]="siteInfo.userid"> <ion-list>
<core-user-avatar [user]="siteInfo" slot="start"></core-user-avatar> <ion-item button *ngIf="siteInfo" class="ion-text-wrap" core-user-link [userId]="siteInfo.userid">
<ion-label> <core-user-avatar [user]="siteInfo" slot="start"></core-user-avatar>
<h2>{{siteInfo.fullname}}</h2>
<p>
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true">
</core-format-text>
</p>
<p>{{ siteUrl }}</p>
</ion-label>
</ion-item>
<core-spacer></core-spacer>
<ion-item class="ion-text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded">
<ion-label><ion-spinner></ion-spinner></ion-label>
</ion-item>
<ion-item button *ngFor="let handler of handlers" [ngClass]="['core-moremenu-handler', handler.class || '']"
(click)="openHandler(handler)" [attr.aria-label]="handler.title | translate" detail="true" detail="true">
<ion-icon [name]="handler.icon" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p class="item-heading">{{ handler.title | translate}}</p>
</ion-label>
<ion-badge slot="end" *ngIf="handler.showBadge" [hidden]="handler.loading || !handler.badge" aria-hidden="true">
{{handler.badge}}
</ion-badge>
<span *ngIf="handler.showBadge && handler.badge && handler.badgeA11yText" class="sr-only">
{{ handler.badgeA11yText | translate: {$a : handler.badge } }}
</span>
<ion-spinner slot="end" *ngIf="handler.showBadge && handler.loading"></ion-spinner>
</ion-item>
<ng-container *ngFor="let item of customItems">
<ion-item button *ngIf="item.type != 'embedded'" [href]="item.url" [attr.aria-label]="item.label" core-link
[capture]="item.type == 'app'" [inApp]="item.type == 'inappbrowser'" class="core-moremenu-customitem" detail="true">
<ion-icon [name]="item.icon" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<p class="item-heading">{{item.label}}</p> <h2>{{siteInfo.fullname}}</h2>
<p>
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true">
</core-format-text>
</p>
<p>{{ siteUrl }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item button *ngIf="item.type == 'embedded'" (click)="openItem(item)" [attr.aria-label]="item.label" <core-spacer></core-spacer>
class="core-moremenu-customitem" detail="true"> <ion-item class="ion-text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded">
<ion-icon [name]="item.icon" slot="start" aria-hidden="true"></ion-icon> <ion-label><ion-spinner></ion-spinner></ion-label>
</ion-item>
<ion-item button *ngFor="let handler of handlers" [ngClass]="['core-moremenu-handler', handler.class || '']"
(click)="openHandler(handler)" [attr.aria-label]="handler.title | translate" detail="true">
<ion-icon [name]="handler.icon" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<p class="item-heading">{{item.label}}</p> <p class="item-heading">{{ handler.title | translate}}</p>
</ion-label>
<ion-badge slot="end" *ngIf="handler.showBadge" [hidden]="handler.loading || !handler.badge" aria-hidden="true">
{{handler.badge}}
</ion-badge>
<span *ngIf="handler.showBadge && handler.badge && handler.badgeA11yText" class="sr-only">
{{ handler.badgeA11yText | translate: {$a : handler.badge } }}
</span>
<ion-spinner slot="end" *ngIf="handler.showBadge && handler.loading"></ion-spinner>
</ion-item>
<ng-container *ngFor="let item of customItems">
<ion-item button *ngIf="item.type != 'embedded'" [href]="item.url" [attr.aria-label]="item.label" core-link
[capture]="item.type == 'app'" [inApp]="item.type == 'inappbrowser'" class="core-moremenu-customitem" detail="true">
<ion-icon [name]="item.icon" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p class="item-heading">{{item.label}}</p>
</ion-label>
</ion-item>
<ion-item button *ngIf="item.type == 'embedded'" (click)="openItem(item)" [attr.aria-label]="item.label"
class="core-moremenu-customitem" detail="true">
<ion-icon [name]="item.icon" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p class="item-heading">{{item.label}}</p>
</ion-label>
</ion-item>
</ng-container>
<ion-item button *ngIf="showScanQR" (click)="scanQR()" detail="true">
<ion-icon name="fas-qrcode" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p class="item-heading">{{ 'core.scanqr' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
</ng-container> <ion-item button *ngIf="showWeb && siteInfo" [href]="siteInfo.siteurl" core-link autoLogin="yes"
<ion-item button *ngIf="showScanQR" (click)="scanQR()" detail="true"> [attr.aria-label]="'core.mainmenu.website' | translate" detail="true">
<ion-icon name="fas-qrcode" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-globe" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<p class="item-heading">{{ 'core.scanqr' | translate }}</p> <p class="item-heading">{{ 'core.mainmenu.website' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item button *ngIf="showWeb && siteInfo" [href]="siteInfo.siteurl" core-link autoLogin="yes" <ion-item button *ngIf="showHelp" [href]="docsUrl" core-link autoLogin="no"
[attr.aria-label]="'core.mainmenu.website' | translate" detail="true"> [attr.aria-label]="'core.mainmenu.help' | translate" detail="true">
<ion-icon name="fas-globe" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="far-life-ring" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<p class="item-heading">{{ 'core.mainmenu.website' | translate }}</p> <p class="item-heading">{{ 'core.mainmenu.help' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item button *ngIf="showHelp" [href]="docsUrl" core-link autoLogin="no" <ion-item button (click)="openPreferences()" [attr.aria-label]="'core.settings.preferences' | translate" detail="true">
[attr.aria-label]="'core.mainmenu.help' | translate" detail="true"> <ion-icon name="fas-wrench" slot="start" aria-hidden="true"></ion-icon>
<ion-icon name="far-life-ring" slot="start" aria-hidden="true"></ion-icon> <ion-label>
<ion-label> <p class="item-heading">{{ 'core.settings.preferences' | translate }}</p>
<p class="item-heading">{{ 'core.mainmenu.help' | translate }}</p> </ion-label>
</ion-label> </ion-item>
</ion-item> <ion-item button (click)="logout()" [attr.aria-label]="logoutLabel | translate" detail="true">
<ion-item button (click)="openPreferences()" [attr.aria-label]="'core.settings.preferences' | translate" detail="true"> <ion-icon name="fas-sign-out-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-icon name="fas-wrench" slot="start" aria-hidden="true"></ion-icon> <ion-label>
<ion-label> <p class="item-heading">{{ logoutLabel | translate }}</p>
<p class="item-heading">{{ 'core.settings.preferences' | translate }}</p> </ion-label>
</ion-label> </ion-item>
</ion-item> <core-spacer></core-spacer>
<ion-item button (click)="logout()" [attr.aria-label]="logoutLabel | translate" detail="true"> <ion-item button (click)="openSettings()" [attr.aria-label]="'core.settings.appsettings' | translate" detail="true">
<ion-icon name="fas-sign-out-alt" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-cogs" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<p class="item-heading">{{ logoutLabel | translate }}</p> <p class="item-heading">{{ 'core.settings.appsettings' | translate }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<core-spacer></core-spacer> </ion-list>
<ion-item button (click)="openSettings()" [attr.aria-label]="'core.settings.appsettings' | translate" detail="true"> </core-loading>
<ion-icon name="fas-cogs" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p class="item-heading">{{ 'core.settings.appsettings' | translate }}</p>
</ion-label>
</ion-item>
</ion-list>
</ion-content> </ion-content>

View File

@ -50,6 +50,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
docsUrl?: string; docsUrl?: string;
customItems?: CoreMainMenuCustomItem[]; customItems?: CoreMainMenuCustomItem[];
siteUrl?: string; siteUrl?: string;
loggedOut = false;
protected subscription!: Subscription; protected subscription!: Subscription;
protected langObserver: CoreEventObserver; protected langObserver: CoreEventObserver;
@ -203,6 +204,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
* Logout the user. * Logout the user.
*/ */
logout(): void { logout(): void {
this.loggedOut = true;
CoreSites.logout(); CoreSites.logout();
} }

View File

@ -17,7 +17,7 @@
<ion-list> <ion-list>
<ion-item *ngIf="siteInfo" class="ion-text-wrap"> <ion-item *ngIf="siteInfo" class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{siteInfo!.fullname}}</h2> <p class="item-heading">{{siteInfo!.fullname}}</p>
<p> <p>
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" <core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0"
[wsNotFiltered]="true"></core-format-text> [wsNotFiltered]="true"></core-format-text>
@ -33,19 +33,19 @@
<ion-icon [name]="handler.icon" slot="start" *ngIf="handler.icon" aria-hidden="true"> <ion-icon [name]="handler.icon" slot="start" *ngIf="handler.icon" aria-hidden="true">
</ion-icon> </ion-icon>
<ion-label> <ion-label>
<h2>{{ handler.title | translate}}</h2> <p class="item-heading">{{ handler.title | translate}}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-card> <ion-card>
<ion-item class="ion-text-wrap" *ngIf="spaceUsage"> <ion-item class="ion-text-wrap" *ngIf="spaceUsage">
<ion-label> <ion-label>
<h2 class="ion-text-wrap">{{ 'core.settings.spaceusage' | translate }} <ion-icon <p class="item-heading ion-text-wrap">{{ 'core.settings.spaceusage' | translate }}</p>
name="fas-info-circle" color="info" [attr.aria-label]="'core.info' | translate"
(click)="showSpaceInfo()"></ion-icon>
</h2>
<p *ngIf="spaceUsage.spaceUsage">{{ spaceUsage.spaceUsage | coreBytesToSize }}</p> <p *ngIf="spaceUsage.spaceUsage">{{ spaceUsage.spaceUsage | coreBytesToSize }}</p>
</ion-label> </ion-label>
<ion-button fill="clear" [attr.aria-label]="'core.info' | translate" (click)="showSpaceInfo()" slot="end">
<ion-icon name="fas-info-circle" color="info" slot="icon-only"></ion-icon>
</ion-button>
<ion-button fill="clear" color="danger" slot="end" (click)="deleteSiteStorage()" <ion-button fill="clear" color="danger" slot="end" (click)="deleteSiteStorage()"
[hidden]="spaceUsage.spaceUsage! + spaceUsage.cacheEntries! <= 0" [hidden]="spaceUsage.spaceUsage! + spaceUsage.cacheEntries! <= 0"
[attr.aria-label]="'core.settings.deletesitefilestitle' | translate"> [attr.aria-label]="'core.settings.deletesitefilestitle' | translate">
@ -54,13 +54,13 @@
</ion-item> </ion-item>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<h2>{{ 'core.settings.synchronizenow' | translate }} <ion-icon name="fas-info-circle" <p class="item-heading">{{ 'core.settings.synchronizenow' | translate }}</p>
color="info" [attr.aria-label]="'core.info' | translate" (click)="showSyncInfo()">
</ion-icon>
</h2>
</ion-label> </ion-label>
<ion-button fill="clear" [attr.aria-label]="'core.info' | translate" (click)="showSyncInfo()" slot="end">
<ion-icon name="fas-info-circle" color="info" slot="icon-only"></ion-icon>
</ion-button>
<ion-button fill="clear" slot="end" *ngIf="!isSynchronizing()" (click)="synchronize()" <ion-button fill="clear" slot="end" *ngIf="!isSynchronizing()" (click)="synchronize()"
[title]="siteName" [attr.aria-label]="'core.settings.synchronizenow' | translate"> [attr.aria-label]="'core.settings.synchronizenow' | translate">
<ion-icon name="fas-sync-alt" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon name="fas-sync-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
<ion-spinner slot="end" *ngIf="isSynchronizing()"></ion-spinner> <ion-spinner slot="end" *ngIf="isSynchronizing()"></ion-spinner>

View File

@ -13,7 +13,7 @@
(onClick)="filePicked(file)" (onDelete)="fileDeleted(idx)" (onRename)="fileRenamed(idx, $event)"> (onClick)="filePicked(file)" (onDelete)="fileDeleted(idx)" (onRename)="fileRenamed(idx, $event)">
</core-local-file> </core-local-file>
<ion-item button *ngIf="!file.isFile" class="ion-text-wrap item-file" (click)="openFolder(file)"> <ion-item button *ngIf="!file.isFile" class="ion-text-wrap item-file" (click)="openFolder(file)" detail="true">
<ion-thumbnail slot="start" aria-hidden="true"> <ion-thumbnail slot="start" aria-hidden="true">
<img src="assets/img/files/folder-64.png" alt=""> <img src="assets/img/files/folder-64.png" alt="">
</ion-thumbnail> </ion-thumbnail>

View File

@ -20,7 +20,7 @@
</core-context-menu-item> </core-context-menu-item>
<core-context-menu-item [hidden]="!displaySize || !size || ( <core-context-menu-item [hidden]="!displaySize || !size || (
content?.compileComponent?.componentInstance?.displaySize === false)" [priority]="500" content?.compileComponent?.componentInstance?.displaySize === false)" [priority]="500"
[content]="'core.clearstoreddata' | translate:{$a: size}" [iconDescription]="'cube'" (action)="removeFiles()" [content]="'core.clearstoreddata' | translate:{$a: size}" [iconDescription]="'fas-archive'" (action)="removeFiles()"
iconAction="fas-trash" [closeOnClick]="false"> iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item> </core-context-menu-item>
</core-context-menu> </core-context-menu>

View File

@ -1,4 +1,4 @@
<core-loading [hideUntil]="dataLoaded" [fullscreen]="false"> <core-loading [hideUntil]="dataLoaded" [fullscreen]="false" class="margin">
<core-compile-html [text]="content" [javascript]="javascript" [jsData]="jsData" [forceCompile]="forceCompile" #compile> <core-compile-html [text]="content" [javascript]="javascript" [jsData]="jsData" [forceCompile]="forceCompile" #compile>
</core-compile-html> </core-compile-html>
</core-loading> </core-loading>

View File

@ -19,7 +19,8 @@
<ion-label class="ion-text-wrap">{{ 'core.tag.warningareasnotsupported' | translate }}</ion-label> <ion-label class="ion-text-wrap">{{ 'core.tag.warningareasnotsupported' | translate }}</ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngFor="let area of areas" [attr.aria-label]="area.nameKey | translate" <ion-item class="ion-text-wrap" *ngFor="let area of areas" [attr.aria-label]="area.nameKey | translate"
(click)="openArea(area)" [attr.aria-current]="area!.id == selectedAreaId ? 'page' : 'false'" button> (click)="openArea(area)" [attr.aria-current]="area!.id == selectedAreaId ? 'page' : 'false'" button
detail="true">
<ion-label> <ion-label>
<h2>{{ area!.nameKey | translate }}</h2> <h2>{{ area!.nameKey | translate }}</h2>
</ion-label> </ion-label>

View File

@ -29,7 +29,7 @@
<ion-list *ngIf="!participants.empty"> <ion-list *ngIf="!participants.empty">
<ion-item *ngFor="let participant of participants.items" <ion-item *ngFor="let participant of participants.items"
class="ion-text-wrap" [attr.aria-current]="participants.getItemAriaCurrent(participant)" class="ion-text-wrap" [attr.aria-current]="participants.getItemAriaCurrent(participant)"
[attr.aria-label]="participant.fullname" (click)="participants.select(participant)" button> [attr.aria-label]="participant.fullname" (click)="participants.select(participant)" button detail="true">
<core-user-avatar [user]="participant" [linkProfile]="false" [checkOnline]="true" slot="start"> <core-user-avatar [user]="participant" [linkProfile]="false" [checkOnline]="true" slot="start">
</core-user-avatar> </core-user-avatar>

View File

@ -37,6 +37,7 @@ import { CoreNetworkError } from '@classes/errors/network-error';
import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip'; import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip';
import { CoreViewerImageComponent } from '@features/viewer/components/image/image'; import { CoreViewerImageComponent } from '@features/viewer/components/image/image';
import { CoreFormFields, CoreForms } from '../../singletons/form'; import { CoreFormFields, CoreForms } from '../../singletons/form';
import { CoreModalLateralTransitionEnter, CoreModalLateralTransitionLeave } from '@classes/modal-lateral-transition';
/* /*
* "Utils" service with helper functions for UI, DOM elements and HTML code. * "Utils" service with helper functions for UI, DOM elements and HTML code.
@ -1721,8 +1722,8 @@ export class CoreDomUtilsProvider {
cssClass: 'core-modal-lateral', cssClass: 'core-modal-lateral',
showBackdrop: true, showBackdrop: true,
backdropDismiss: true, backdropDismiss: true,
// @todo enterAnimation: 'core-modal-lateral-transition', enterAnimation: CoreModalLateralTransitionEnter,
// @todo leaveAnimation: 'core-modal-lateral-transition', leaveAnimation: CoreModalLateralTransitionLeave,
}); });
return await this.openModal<T>(modalOptions); return await this.openModal<T>(modalOptions);

View File

@ -78,6 +78,8 @@ core-format-text {
} }
&.core-expand-in-fullview { &.core-expand-in-fullview {
cursor: pointer;
.core-show-more { .core-show-more {
@include push-arrow-color(626262, true); @include push-arrow-color(626262, true);
@include padding-horizontal(null, 5px); @include padding-horizontal(null, 5px);

View File

@ -137,16 +137,6 @@ ion-item.ion-text-wrap ion-label {
white-space: normal !important; white-space: normal !important;
} }
// It fixes the click on links where ion-ripple-effect is present.
.ion-activatable ion-label,
.item-multiple-items ion-label {
z-index: 3;
pointer-events: none;
ion-anchor, ion-button, a, button {
pointer-events: visible;
}
}
@each $color-name, $value in $colors { @each $color-name, $value in $colors {
$value: map-get($colors, $color-name); $value: map-get($colors, $color-name);
$base: map-get($value, base); $base: map-get($value, base);
@ -195,6 +185,8 @@ ion-header ion-toolbar {
// Ionic icon. // Ionic icon.
ion-icon { ion-icon {
position: relative;
&.icon-slash::after, &.icon-slash::after,
&.icon-backslash::after { &.icon-backslash::after {
content: " "; content: " ";
@ -238,7 +230,8 @@ button,
min-width: var(--a11y-min-target-size); min-width: var(--a11y-min-target-size);
} }
[role="button"] { [role="button"],
.clickable {
cursor: pointer; cursor: pointer;
[disabled], [disabled],
@ -256,6 +249,21 @@ ion-button.core-button-as-link {
white-space: break-spaces; white-space: break-spaces;
} }
button.as-link {
display: inline;
min-height: auto;
min-width: auto;
color: var(--core-link-color);
background: none;
border: 0;
line-height: inherit;
margin: 0;
padding: 0;
text-align: start;
font-size: inherit;
}
// Ionic alert. // Ionic alert.
ion-alert.core-alert-network-error .alert-head, ion-alert.core-alert-network-error .alert-head,
div.core-iframe-network-error { div.core-iframe-network-error {
@ -658,8 +666,11 @@ audio.core-media-adapt-width {
} }
} }
// It fixes the click on links where ion-ripple-effect is present.
// Make links clickable when inside radio or checkbox items. Pointer and cursor part. // Make links clickable when inside radio or checkbox items. Pointer and cursor part.
ion-item.item-multiple-inputs { ion-item.item-multiple-inputs:not(.only-links),
ion-item.ion-activatable:not(.only-links) {
cursor: pointer; cursor: pointer;
ion-label { ion-label {
z-index: 3; z-index: 3;
@ -677,6 +688,12 @@ ion-item.item-multiple-inputs {
} }
} }
ion-item.item-multiple-inputs.only-links {
a {
cursor: pointer;
}
}
// Case with ion-input + ion-select inside. // Case with ion-input + ion-select inside.
ion-item.item-input.item-multiple-inputs { ion-item.item-input.item-multiple-inputs {
.flex-row { .flex-row {