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-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">
<img [src]="badge.badgeurl" [alt]="badge.name" core-external-content>
</ion-avatar>

View File

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

View File

@ -36,7 +36,7 @@
</core-context-menu-item>
</core-context-menu>
</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">
<!-- "Time" selector. -->
<core-combobox [label]="'core.show' | translate" [selection]="selectedFilter" (onChange)="selectedChanged($event)">

View File

@ -20,7 +20,7 @@
</core-horizontal-scroll-controls>
</div>
</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"
[message]="'addon.block_recentlyaccessedcourses.nocourses' | translate"></core-empty-box>
<!-- List of courses. -->

View File

@ -5,7 +5,7 @@
</core-horizontal-scroll-controls>
</div>
</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
[id]="scrollElementId"
[hidden]="!items || items.length === 0"

View File

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

View File

@ -20,7 +20,7 @@
</core-horizontal-scroll-controls>
</div>
</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"
[message]="'addon.block_starredcourses.nocourses' | translate"></core-empty-box>
<!-- List of courses. -->

View File

@ -9,7 +9,7 @@
</core-context-menu-item>
</core-context-menu>
</ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false">
<core-loading [hideUntil]="loaded" [fullscreen]="false" class="margin">
<div class="safe-padding-horizontal">
<core-combobox [selection]="filter" (onChange)="switchFilter($event)">
<ion-select-option class="ion-text-wrap" value="all">
@ -35,12 +35,12 @@
</ion-select-option>
</core-combobox>
</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"
(loadMore)="loadMoreTimeline()" [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events>
</core-loading>
<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-row class="ion-no-padding">
<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>
</ion-label>
</ion-item>
<ion-item *ngIf="commentsEnabled" detail="true">
<ion-label>
<core-comments [component]="this.component" [itemId]="entry.id" area="format_blog"
[instanceId]="entry.userid" contextLevel="user">
<core-comments *ngIf="commentsEnabled" [component]="this.component" [itemId]="entry.id" area="format_blog"
[instanceId]="entry.userid" contextLevel="user" [showItem]="true">
</core-comments>
</ion-label>
</ion-item>
<core-file *ngFor="let file of entry.attachmentfiles" [file]="file" [component]="this.component"
[componentId]="entry.id">
</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-item>
</ion-card-content>
<ion-row class="ion-text-center">
<ion-col *ngIf="entry.lastmodified > entry.created">
<ion-row class="ion-text-center" *ngIf="entry.lastmodified > entry.created">
<ion-col>
<ion-note>
<ion-icon name="fas-clock"
[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">
<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
[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=""
role="presentation">
<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">
<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)"
[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=""
role="presentation">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true">

View File

@ -101,7 +101,7 @@
</ng-container>
<!-- 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-down" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
@ -138,7 +138,7 @@
<ion-radio slot="end" value="0"></ion-radio>
<ion-label>{{ 'addon.calendar.durationnone' | translate }}</ion-label>
</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-label>{{ 'addon.calendar.durationuntil' | translate }}</ion-label>
<ion-datetime formControlName="timedurationuntil" [max]="maxDate" [min]="minDate"
@ -146,7 +146,7 @@
[displayFormat]="dateFormat" [disabled]="form.controls.duration.value != 1">
</ion-datetime>
</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-label>{{ 'addon.calendar.durationminutes' | translate }}</ion-label>
<ion-input type="number" name="timedurationminutes" slot="end"

View File

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

View File

@ -44,7 +44,7 @@
</ion-item-divider>
<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'"
[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=""
role="presentation">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start"

View File

@ -15,7 +15,7 @@
<ion-list>
<ion-item class="ion-text-wrap" *ngFor="let competency of competencies.items"
[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>
<p class="item-heading">{{ competency.competency.shortname }} <em>{{competency.competency.idnumber}}</em></p>
</ion-label>

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@
</core-empty-box>
<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"
(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>
<p class="item-heading">{{ plan.name }}</p>
<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"
[siteId]="siteId || null">
<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-format-text [text]="title" contextLevel="system" [contextInstanceId]="0"
(click)="showInfo && !isGroup && viewInfo()"></core-format-text>
<core-format-text [text]="title" contextLevel="system" [contextInstanceId]="0"></core-format-text>
<ion-icon *ngIf="conversation && conversation.isfavourite" name="fas-star"
[attr.aria-label]="'core.favourites' | translate">
</ion-icon>
@ -93,12 +92,16 @@
[@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
<ion-label>
<!-- User data. -->
<div class="item-heading addon-message-user" [attr.aria-label]="message.useridfrom == currentUserId ?
('addon.messages.you' | translate) : members[message.useridfrom].fullname">
<div *ngIf="message.showUserData" class="item-heading addon-message-user">
<core-user-avatar slot="start" [user]="members[message.useridfrom]" [linkProfile]="false"
*ngIf="message.showUserData"></core-user-avatar>
<div *ngIf="message.showUserData">{{ members[message.useridfrom].fullname }}</div>
aria-hidden="true">
</core-user-avatar>
{{ members[message.useridfrom].fullname }}
</div>
<div *ngIf="!message.showUserData" class="sr-only">
{{ message.useridfrom == currentUserId
? ('addon.messages.you' | translate)
: members[message.useridfrom].fullname }}
</div>
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->

View File

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

View File

@ -19,7 +19,7 @@
<core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch()"
[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">
@ -40,7 +40,7 @@
</ion-item-divider>
<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-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>
<ion-label>
<p class="item-heading">{{ result.fullname }}</p>

View File

@ -40,7 +40,8 @@
<ion-item class="ion-text-wrap">
<ion-label>
<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>
</ion-label>
</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. -->
<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. -->
<ng-container *ngFor="let submission of submissions.items">
<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>
<ion-label>
<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>
<h2>{{plugin.name}}</h2>
<core-comments contextLevel="module" [instanceId]="assign.cmid" component="assignsubmission_comments"

View File

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

View File

@ -30,7 +30,7 @@
<ion-toggle [(ngModel)]="showAll" (ionChange)="fetchSessions(true)"></ion-toggle>
</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)"
[class.addon-mod-chat-session-show-more]="session.sessionusers.length < session.allsessionusers.length">
@ -47,11 +47,10 @@
</ion-label>
</ion-item>
</ion-card-content>
<div *ngIf="session.sessionusers.length < session.allsessionusers.length">
<ion-button fill="clear" (click)="showMoreUsers(session, $event)">
<ion-button *ngIf="session.sessionusers.length < session.allsessionusers.length" fill="clear" expand="block"
(click)="showMoreUsers(session, $event)">
{{ 'core.showmore' | translate }}
</ion-button>
</div>
</ion-card>
<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">
<!-- This content will be replaced by the component if found. -->
<core-loading [hideUntil]="fieldLoaded">
</core-loading>
<core-loading [hideUntil]="fieldLoaded" [fullscreen]="false"></core-loading>
</core-dynamic-component>

View File

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

View File

@ -48,6 +48,8 @@ import { AddonModDataPrefetchHandler } from '../../services/handlers/prefetch';
import { AddonModDataComponentsCompileModule } from '../components-compile.module';
import { AddonModDataSearchComponent } from '../search/search';
const contentToken = '<!-- CORE-DATABASE-CONTENT-GOES-HERE -->';
/**
* Component that displays a data index page.
*/
@ -318,12 +320,22 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
if (!this.isEmpty) {
this.entries = (entries.offlineEntries || []).concat(entries.entries);
let entriesHTML = AddonModDataHelper.getTemplate(
let headerAndFooter = AddonModDataHelper.getTemplate(
this.database!,
AddonModDataTemplateType.LIST_HEADER,
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.
if (!this.search.searching || !this.firstEntry) {
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);
let entriesHTML = '';
const entriesById: Record<number, AddonModDataEntry> = {};
this.entries.forEach((entry, index) => {
entriesById[entry.id] = entry;
@ -349,9 +363,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
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.
this.jsData = {

View File

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

View File

@ -14,9 +14,9 @@
<ion-input type="text" [formControlName]="'f_'+field.id" [placeholder]="field.name"></ion-input>
</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/>
</span>
</button>
<img *ngIf="showMode && imageUrl" [src]="imageUrl" [alt]="title" class="core-media-adapt-width listMode_picture"
[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-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-compile-html [text]="entryHtml" [jsData]="jsData" [extraImports]="extraImports"
@ -54,13 +54,10 @@
[scaleId]="database.scale">
</core-rating-aggregate>
<ion-item *ngIf="database && database.comments && entry && entry.id > 0 && commentsEnabled">
<ion-label>
<core-comments contextLevel="module" [instanceId]="database.coursemodule" component="mod_data" [itemId]="entry.id"
area="database_entry" [displaySpinner]="false" [courseId]="courseId" (onLoading)="setLoadingComments($event)">
<core-comments *ngIf="database && database.comments && entry && entry.id > 0 && commentsEnabled"
contextLevel="module" [instanceId]="database.coursemodule" component="mod_data" [itemId]="entry.id"
area="database_entry" [courseId]="courseId" (onLoading)="setLoadingComments($event)" [showItem]="true">
</core-comments>
</ion-label>
</ion-item>
<ion-grid *ngIf="hasPrevious || hasNext">
<ion-row class="ion-align-items-center">

View File

@ -658,7 +658,7 @@ export class AddonModDataHelperProvider {
// Add core-link directive to links.
template = template.replace(
/<a ([^>]*href="[^>]*)>/ig,
(match, attributes) => '<a core-link capture="true" ' + attributes + '>',
(match, attributes) => '<button class="as-link" core-link capture="true" ' + attributes + '>',
);
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-label>
<h2>{{ 'addon.mod_forum.lockdiscussion' | translate }}</h2>
<p class="item-heading">{{ 'addon.mod_forum.lockdiscussion' | translate }}</p>
</ion-label>
</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-label>
<h2>{{ 'addon.mod_forum.unlockdiscussion' | translate }}</h2>
<p class="item-heading">{{ 'addon.mod_forum.unlockdiscussion' | translate }}</p>
</ion-label>
</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-label>
<h2>{{ 'addon.mod_forum.pindiscussion' | translate }}</h2>
<p class="item-heading">{{ 'addon.mod_forum.pindiscussion' | translate }}</p>
</ion-label>
</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-label>
<h2>{{ 'addon.mod_forum.unpindiscussion' | translate }}</h2>
<p class="item-heading">{{ 'addon.mod_forum.unpindiscussion' | translate }}</p>
</ion-label>
</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-label>
<h2>{{ 'addon.mod_forum.addtofavourites' | translate }}</h2>
<p class="item-heading">{{ 'addon.mod_forum.addtofavourites' | translate }}</p>
</ion-label>
</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-label>
<h2>{{ 'addon.mod_forum.removefromfavourites' | translate }}</h2>
<p class="item-heading">{{ 'addon.mod_forum.removefromfavourites' | translate }}</p>
</ion-label>
</ion-item>

View File

@ -23,7 +23,7 @@
(action)="prefetch($event)">
</core-context-menu-item>
<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"
(action)="removeFiles($event)">
</core-context-menu-item>

View File

@ -1,26 +1,26 @@
<core-loading [hideUntil]="loaded" [fullscreen]="false">
<ion-item class="ion-text-wrap" (click)="editPost()" *ngIf="offlinePost || (canEdit && isOnline)">
<core-loading [hideUntil]="loaded" [fullscreen]="false" class="margin">
<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-label>
<h2>{{ 'addon.mod_forum.edit' | translate }}</h2>
<p class="item-heading">{{ 'addon.mod_forum.edit' | translate }}</p>
</ion-label>
</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-label>
<h2 *ngIf="!offlinePost">{{ 'addon.mod_forum.delete' | translate }}</h2>
<h2 *ngIf="offlinePost">{{ 'core.discard' | translate }}</h2>
<p class="item-heading" *ngIf="!offlinePost">{{ 'addon.mod_forum.delete' | translate }}</p>
<p class="item-heading" *ngIf="offlinePost">{{ 'core.discard' | translate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" (click)="dismiss()" *ngIf="wordCount">
<ion-item class="ion-text-wrap" *ngIf="wordCount">
<ion-label>
<h2>{{ 'core.numwords' | translate: {'$a': wordCount} }}</h2>
<p class="item-heading">{{ 'core.numwords' | translate: {'$a': wordCount} }}</p>
</ion-label>
</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-label>
<h2>{{ 'core.openinbrowser' | translate }}</h2>
<p class="item-heading">{{ 'core.openinbrowser' | translate }}</p>
</ion-label>
</ion-item>
</core-loading>

View File

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

View File

@ -63,7 +63,7 @@
<ion-label>
<h2>{{ 'addon.mod_h5pactivity.outcome' | translate }}</h2>
<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 }}
</p>
<p *ngIf="attempt.success !== null && !attempt.success" >
@ -166,12 +166,12 @@
<!-- Template to render an answer. -->
<ng-template #answerTemplate let-answer="answer">
<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>
{{ answer.answer }}
</p>
<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>
{{ answer.answer }}
</p>
@ -179,15 +179,15 @@
{{ answer.answer }}
</p>
<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>
</p>
<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>
</p>
<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>
</p>
</ng-template>

View File

@ -99,7 +99,7 @@
[alt]="'addon.mod_h5pactivity.attempt_completion_no' | translate">
</ion-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">
</ion-icon>
<ion-icon *ngIf="attempt.success !== null && !attempt.success" name="far-circle"

View File

@ -1,6 +1,6 @@
<!-- Buttons to add to the header. -->
<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-button>
<core-context-menu>

View File

@ -12,9 +12,12 @@
<nav>
<ion-list>
<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">
<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-item>
</ion-list>

View File

@ -296,7 +296,7 @@
</ion-card-header>
<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>
<ion-label>

View File

@ -27,7 +27,7 @@
<ion-item button class="ion-text-wrap {{question.stateClass}}" *ngFor="let question of navigation"
[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>
<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"
slot="start">
</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"
[courseId]="courseId">
</core-format-text>
</a>
</button>
<span *ngIf="!sco.prereq || !sco.launch">
<core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="courseId">

View File

@ -12,7 +12,7 @@
<nav>
<ion-list>
<!-- 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-label>{{ 'addon.mod_wiki.gowikihome' | translate }}</ion-label>
</ion-item>
@ -21,7 +21,7 @@
<ion-label><h2>{{ letter.label }}</h2></ion-label>
</ion-item-divider>
<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-label>
<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 { 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 { AddonsModule } from '@/addons/addons.module';
@ -42,7 +42,11 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
imports: [
BrowserModule,
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.
TranslateModule.forRoot({
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;
font-weight: 400;
font-size: 16px;
line-height: 20px;
}
.select-text {

View File

@ -1,5 +1,11 @@
:host {
ion-list {
@import "~theme/globals";
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)">
<ion-icon *ngIf="item.iconDescription" [name]="item.iconDescription" [attr.aria-label]="item.ariaDescription" slot="start">
</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"
[class.icon-slash]="item.iconSlash" slot="end">
</ion-icon>

View File

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

View File

@ -4,35 +4,44 @@
--loading-background: var(--ion-background-color);
--loading-spinner: var(--ion-color-primary);
--loading-text-color: var(--ion-text-color);
--loading-inline-margin: 0;
--loading-inline-min-height: 28px;
position: static;
color: var(--loading-text-color);
&.margin {
--loading-inline-margin: 10px;
}
&.core-loading-loaded {
--loading-inline-margin: 0;
--loading-inline-min-height: 0;
}
ion-spinner {
--color: var(--loading-spinner);
color: var(--color);
}
> .core-loading-container {
.core-loading-container {
position: absolute;
@include position(0, 0, 0, 0);
display: table;
display: flex;
height: 100%;
width: 100%;
text-align: center;
clear: both;
justify-content: center;
align-items: center;
flex-direction: column;
z-index: 3;
margin: 0;
padding: 10px 0 0 0;
padding: 0;
background-color: var(--loading-background);
-webkit-transition: all 200ms ease-in-out;
transition: all 200ms ease-in-out;
.core-loading-spinner {
display: table-cell;
text-align: center;
vertical-align: middle;
@include core-transition(all, 200ms);
}
.core-loading-message {
@include margin(10px, 0, 0, 0);
}
.core-loading-content {
@ -48,21 +57,26 @@
}
&.core-loading-inline {
--loading-background: transparent;
position: relative;
display: block;
min-height: var(--loading-inline-min-height);
margin-top: var(--loading-inline-margin);
margin-bottom: var(--loading-inline-margin);
.core-loading-container {
padding-top: 20px;
position: relative;
}
.core-loading-message {
@include margin(0, 0, 0, 10px);
}
&.core-loading-loaded.core-loading-inline {
position: relative;
min-height: 102px;
.core-loading-container {
padding-top: 10px;
position: absolute;
flex-direction: row;
}
}
}
: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-spinner *ngIf="!hideUntil"></ion-spinner>
<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-col>
<ion-col class="ion-no-padding" size="10">
@ -41,7 +41,7 @@
</ng-container>
</ion-slides>
</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-col>
</ion-row>

View File

@ -1,7 +1,7 @@
<ion-tab-bar slot="top" class="core-tabs-bar" [hidden]="!tabs || numTabsShown <= 1" #tabBar>
<ion-spinner *ngIf="!hideUntil"></ion-spinner>
<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-col>
<ion-col class="ion-no-padding" size="10">
@ -39,7 +39,7 @@
</ng-container>
</ion-slides>
</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-col>
</ion-row>

View File

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

View File

@ -57,7 +57,36 @@ export class CoreLinkDirective implements OnInit {
ngOnInit(): void {
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.performAction(event);
});
this.element.addEventListener('keydown', (event: KeyboardEvent) => {
if ((event.key == ' ' || event.key == 'Enter')) {
event.preventDefault();
event.stopPropagation();
}
});
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.
}
@ -84,7 +113,6 @@ export class CoreLinkDirective implements OnInit {
} else {
this.navigate(href, openIn);
}
});
}
/**

View File

@ -3,7 +3,7 @@
</div>
<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>
<!-- Course expand="block"s. -->
<ng-container *ngFor="let block of blocks">

View File

@ -7,7 +7,7 @@
</h2>
</ion-label>
</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-label>
<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({
selector: 'core-comments',
templateUrl: 'core-comments.html',
styleUrls: ['comments.scss'],
})
export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestroy {
@ -39,9 +38,9 @@ export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestr
@Input() itemId!: number;
@Input() area = '';
@Input() title?: string;
@Input() displaySpinner = true; // Whether to display the loading spinner.
@Output() onLoading: EventEmitter<boolean>; // Eevent that indicates whether the component is loading data.
@Output() onLoading: EventEmitter<boolean>; // Event 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() showItem = false; // Show button as an item.
commentsLoaded = false;
commentsCount = '';

View File

@ -1,8 +1,27 @@
<core-loading *ngIf="!disabled" [hideUntil]="commentsLoaded || !displaySpinner">
<div *ngIf="!countError" (click)="openComments($event)" [class.core-comments-clickable]="!disabled">
<ng-container *ngIf="!disabled">
<core-loading *ngIf="!showItem" [hideUntil]="commentsLoaded" [fullscreen]="false" class="margin">
<button *ngIf="!countError" (click)="openComments($event)" class="as-link">
{{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
</div>
</button>
<div *ngIf="countError">
{{ 'core.comments.commentsnotworking' | translate }}
</div>
</core-loading>
</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"
button>
button detail="true">
<ion-icon name="fas-graduation-cap" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p class="item-heading">{{ item.courseName }}</p>

View File

@ -114,11 +114,12 @@
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner>
<ion-label><h2>{{ 'core.course.downloadcourse' | translate }}</h2></ion-label>
</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-label><h2>{{ 'core.course.contents' | translate }}</h2></ion-label>
</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-label><h2>{{ 'core.openinbrowser' | translate }}</h2></ion-label>
</ion-item>

View File

@ -1,5 +1,5 @@
<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"
[attr.course-color]="course.color ? null : course.colorNumber" [style.color]="course.color"></ion-icon>
<ion-avatar *ngIf="course.courseImage" slot="start">

View File

@ -32,7 +32,9 @@
<tbody>
<tr
*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"
[ngClass]='{"core-grades-grade-clickable": row.itemtype != "category"}'
>

View File

@ -4,13 +4,28 @@
--header-background: var(--white);
--odd-cell-background: var(--gray-lighter);
--even-cell-background: var(--white);
--odd-cell-hover: var(--gray-light);
--even-cell-hover: var(--gray-lighter);
--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) {
--header-background: var(--black);
--odd-cell-background: var(--gray-darker);
--even-cell-background: var(--black);
--odd-cell-hover: var(--gray-dark);
--even-cell-hover: var(--gray-darker);
--icon-color: #eeeeee;
}
@ -91,21 +106,19 @@
opacity: .7;
}
.odd {
.odd, .even {
td, th, th[aria-current="page"] {
background-color: var(--odd-cell-background);
}
}
.even {
td, th, th[aria-current="page"] {
background-color: var(--even-cell-background);
background-color: var(--cell-background);
}
}
.core-grades-grade-clickable {
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-item>
<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">
<ion-label>{{provider.name}}</ion-label>
</ion-item>

View File

@ -88,7 +88,7 @@
<ion-label><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-label>
</ion-item>
<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">
<ion-label>{{provider.name}}</ion-label>
</ion-item>

View File

@ -19,7 +19,7 @@
</ion-header>
<ion-content>
<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">
<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'">

View File

@ -8,6 +8,7 @@
</ion-toolbar>
</ion-header>
<ion-content>
<core-loading [hideUntil]="!loggedOut">
<ion-list>
<ion-item button *ngIf="siteInfo" class="ion-text-wrap" core-user-link [userId]="siteInfo.userid">
<core-user-avatar [user]="siteInfo" slot="start"></core-user-avatar>
@ -25,7 +26,7 @@
<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">
(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>
<p class="item-heading">{{ handler.title | translate}}</p>
@ -94,4 +95,5 @@
</ion-label>
</ion-item>
</ion-list>
</core-loading>
</ion-content>

View File

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

View File

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

View File

@ -13,7 +13,7 @@
(onClick)="filePicked(file)" (onDelete)="fileDeleted(idx)" (onRename)="fileRenamed(idx, $event)">
</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">
<img src="assets/img/files/folder-64.png" alt="">
</ion-thumbnail>

View File

@ -20,7 +20,7 @@
</core-context-menu-item>
<core-context-menu-item [hidden]="!displaySize || !size || (
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">
</core-context-menu-item>
</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>
</core-loading>

View File

@ -19,7 +19,8 @@
<ion-label class="ion-text-wrap">{{ 'core.tag.warningareasnotsupported' | translate }}</ion-label>
</ion-item>
<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>
<h2>{{ area!.nameKey | translate }}</h2>
</ion-label>

View File

@ -29,7 +29,7 @@
<ion-list *ngIf="!participants.empty">
<ion-item *ngFor="let participant of participants.items"
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>

View File

@ -37,6 +37,7 @@ import { CoreNetworkError } from '@classes/errors/network-error';
import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip';
import { CoreViewerImageComponent } from '@features/viewer/components/image/image';
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.
@ -1721,8 +1722,8 @@ export class CoreDomUtilsProvider {
cssClass: 'core-modal-lateral',
showBackdrop: true,
backdropDismiss: true,
// @todo enterAnimation: 'core-modal-lateral-transition',
// @todo leaveAnimation: 'core-modal-lateral-transition',
enterAnimation: CoreModalLateralTransitionEnter,
leaveAnimation: CoreModalLateralTransitionLeave,
});
return await this.openModal<T>(modalOptions);

View File

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

View File

@ -137,16 +137,6 @@ ion-item.ion-text-wrap ion-label {
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 {
$value: map-get($colors, $color-name);
$base: map-get($value, base);
@ -195,6 +185,8 @@ ion-header ion-toolbar {
// Ionic icon.
ion-icon {
position: relative;
&.icon-slash::after,
&.icon-backslash::after {
content: " ";
@ -238,7 +230,8 @@ button,
min-width: var(--a11y-min-target-size);
}
[role="button"] {
[role="button"],
.clickable {
cursor: pointer;
[disabled],
@ -256,6 +249,21 @@ ion-button.core-button-as-link {
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.
ion-alert.core-alert-network-error .alert-head,
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.
ion-item.item-multiple-inputs {
ion-item.item-multiple-inputs:not(.only-links),
ion-item.ion-activatable:not(.only-links) {
cursor: pointer;
ion-label {
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.
ion-item.item-input.item-multiple-inputs {
.flex-row {