commit
a2f49c5356
|
@ -40,7 +40,7 @@
|
|||
"@awesome-cordova-plugins/sqlite": "^6.3.0",
|
||||
"@awesome-cordova-plugins/status-bar": "^6.3.0",
|
||||
"@awesome-cordova-plugins/web-intent": "^6.3.0",
|
||||
"@ionic/angular": "^7.0.0",
|
||||
"@ionic/angular": "^7.6.1",
|
||||
"@ionic/cordova-builders": "^10.0.0",
|
||||
"@moodlehq/cordova-plugin-advanced-http": "3.3.1-moodle.1",
|
||||
"@moodlehq/cordova-plugin-camera": "6.0.0-moodle.2",
|
||||
|
@ -3449,11 +3449,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/angular": {
|
||||
"version": "7.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-7.5.6.tgz",
|
||||
"integrity": "sha512-RJDQgGiVRps/04HBfx23E8tiGCvzE2d5NpWB1Mi1CDmc0ENTSc6odb2XI45YhFxmGvQsWZ8k+H1N/8emAHPraw==",
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-7.6.1.tgz",
|
||||
"integrity": "sha512-Eh//g/bAL9se4PD6C19NqymgQbqKp4W+Ffbjo8Qnqwk02jGMs/jcMP0WVEcLNiEws2m67kIiWItrUhJjb8pplA==",
|
||||
"dependencies": {
|
||||
"@ionic/core": "7.5.6",
|
||||
"@ionic/core": "7.6.1",
|
||||
"ionicons": "^7.0.0",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.3.0"
|
||||
|
@ -3967,11 +3967,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "7.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.5.6.tgz",
|
||||
"integrity": "sha512-bYQp2twwm61uA0Q31ToVIpQWsiQ9so1dRoWZPD+l+y4fVuFmOCLYeS6XTLTm73jVBq40JfEcsac7eYC4DxoemQ==",
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.6.1.tgz",
|
||||
"integrity": "sha512-o4PSRxokfRB5H3E5DAM7xivG8XFXaXD3+U/tha0QKemiMSntqgPqy0FYX0pNEwIrV3llRzFbAGNqyvB1+BG97Q==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.7.2",
|
||||
"@stencil/core": "^4.8.2",
|
||||
"ionicons": "^7.2.1",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
|
@ -5990,9 +5990,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.7.2.tgz",
|
||||
"integrity": "sha512-sPPDYrXiTbfeUF5CCyfqysXK/yfTHC4xYR1+nHzGkS2vhRSBOLp0oPuB+xkJLKA+K2ZqDJUxpOnDxy1CLWwBXA==",
|
||||
"version": "4.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.2.tgz",
|
||||
"integrity": "sha512-KdZEAtz9VnqMtXOkf51+8mphyRt0fN/LYgtj5M8gnveGspG8KzoyTDzlWt0wsstWIsJJ21RA1yd3AgMMZiu3MA==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
"@awesome-cordova-plugins/sqlite": "^6.3.0",
|
||||
"@awesome-cordova-plugins/status-bar": "^6.3.0",
|
||||
"@awesome-cordova-plugins/web-intent": "^6.3.0",
|
||||
"@ionic/angular": "^7.0.0",
|
||||
"@ionic/angular": "^7.6.1",
|
||||
"@ionic/cordova-builders": "^10.0.0",
|
||||
"@moodlehq/cordova-plugin-advanced-http": "3.3.1-moodle.1",
|
||||
"@moodlehq/cordova-plugin-camera": "6.0.0-moodle.2",
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<ion-item *ngIf="showMyEntriesToggle">
|
||||
<ion-label>{{ 'addon.blog.showonlyyourentries' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)" slot="end" />
|
||||
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)">
|
||||
{{ 'addon.blog.showonlyyourentries' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<core-empty-box *ngIf="entries && entries.length === 0" icon="far-newspaper" [message]="'addon.blog.noentriesyet' | translate" />
|
||||
<ng-container *ngFor="let entry of entries">
|
||||
|
|
|
@ -11,17 +11,17 @@
|
|||
<ion-list>
|
||||
<ion-item *ngFor="let type of types" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+type]">
|
||||
<ion-icon [name]="typeIcons[type]" slot="start" aria-hidden="true" />
|
||||
<ion-label>{{ 'addon.calendar.' + type + 'events' | translate}}</ion-label>
|
||||
<ion-toggle [(ngModel)]="filter[type]" (ionChange)="onChange()" slot="end" />
|
||||
<ion-toggle [(ngModel)]="filter[type]" (ionChange)="onChange()">
|
||||
{{ 'addon.calendar.' + type + 'events' | translate}}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<core-spacer *ngIf="filter.course || filter.category || filter.group" />
|
||||
<ng-container *ngIf="filter.course || filter.category || filter.group">
|
||||
<ion-radio-group [(ngModel)]="courseId" (ionChange)="onChange()">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let course of sortedCourses">
|
||||
<ion-label>
|
||||
<ion-radio [value]="course.id">
|
||||
<core-format-text [text]="course.shortname" />
|
||||
</ion-label>
|
||||
<ion-radio slot="end" [value]="course.id" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ng-container>
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
<form [formGroup]="form" *ngIf="!error" #editEventForm>
|
||||
<!-- Event name. -->
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<p class="item-heading" [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-input type="text" name="name" [placeholder]="'addon.calendar.eventname' | translate" formControlName="name" />
|
||||
<core-input-errors [control]="form.controls.name" [errorMessages]="errors" />
|
||||
<ion-input labelPlacement="stacked" type="text" name="name" [placeholder]="'addon.calendar.eventname' | translate"
|
||||
formControlName="name">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</div>
|
||||
</ion-input>
|
||||
<core-input-errors [control]="form.controls.name" />
|
||||
</ion-item>
|
||||
|
||||
<!-- Date. -->
|
||||
|
@ -37,17 +37,18 @@
|
|||
</ion-datetime>
|
||||
</ng-template>
|
||||
</ion-modal>
|
||||
<core-input-errors [control]="form.controls.timestart" [errorMessages]="errors" />
|
||||
<core-input-errors [control]="form.controls.timestart" />
|
||||
</ion-item>
|
||||
|
||||
<!-- Type. -->
|
||||
<ion-item class="ion-text-wrap addon-calendar-eventtype-container">
|
||||
<ion-label>
|
||||
<ion-label *ngIf="eventTypes.length === 1">
|
||||
<p class="item-heading" [core-mark-required]="true">{{ 'addon.calendar.eventkind' | translate }}</p>
|
||||
</ion-label>
|
||||
<p *ngIf="eventTypes.length === 1" slot="end">{{eventTypes[0].name | translate }}</p>
|
||||
<ion-select *ngIf="eventTypes.length > 1" formControlName="eventtype" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'addon.calendar.eventkind' | translate}">
|
||||
<div [core-mark-required]="true" slot="label">{{ 'addon.calendar.eventkind' | translate }}</div>
|
||||
<ion-select-option *ngFor="let type of eventTypes" [value]="type.value">
|
||||
{{ type.name | translate }}
|
||||
</ion-select-option>
|
||||
|
@ -56,11 +57,9 @@
|
|||
|
||||
<!-- Category. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="typeControl.value === 'category'">
|
||||
<ion-label>
|
||||
<p class="item-heading" [core-mark-required]="true">{{ 'core.category' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-select formControlName="categoryid" interface="action-sheet" [placeholder]="'core.noselection' | translate"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.category' | translate}">
|
||||
<p [core-mark-required]="true" slot="label">{{ 'core.category' | translate }}</p>
|
||||
<ion-select-option *ngFor="let category of categories" [value]="category.id">
|
||||
{{ category.name }}
|
||||
</ion-select-option>
|
||||
|
@ -69,11 +68,9 @@
|
|||
|
||||
<!-- Course. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="typeControl.value === 'course'">
|
||||
<ion-label>
|
||||
<p class="item-heading" [core-mark-required]="true">{{ 'core.course' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-select formControlName="courseid" interface="action-sheet" [placeholder]="'core.noselection' | translate"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.course' | translate}">
|
||||
<p [core-mark-required]="true" slot="label">{{ 'core.course' | translate }}</p>
|
||||
<ion-select-option *ngFor="let course of courses" [value]="course.id">{{ course.fullname }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
@ -82,12 +79,10 @@
|
|||
<ng-container *ngIf="typeControl.value === 'group'">
|
||||
<!-- Select the course. -->
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p class="item-heading" [core-mark-required]="true">{{ 'core.course' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-select formControlName="groupcourseid" interface="action-sheet" [placeholder]="'core.noselection' | translate"
|
||||
[cancelText]="'core.cancel' | translate" (ionChange)="groupCourseSelected()"
|
||||
[interfaceOptions]="{header: 'core.course' | translate}">
|
||||
<p [core-mark-required]="true" slot="label">{{ 'core.course' | translate }}</p>
|
||||
<ion-select-option *ngFor="let course of courses" [value]="course.id">
|
||||
{{ course.fullname }}
|
||||
</ion-select-option>
|
||||
|
@ -101,11 +96,9 @@
|
|||
</ion-item>
|
||||
<!-- Select the group. -->
|
||||
<ion-item class="ion-text-wrap core-edit-set-group" *ngIf="!loadingGroups && groups.length > 0">
|
||||
<ion-label>
|
||||
<p class="item-heading" [core-mark-required]="true">{{ 'core.group' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-select formControlName="groupid" interface="action-sheet" [placeholder]="'core.noselection' | translate"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.group' | translate}">
|
||||
<p [core-mark-required]="true" slot="label">{{ 'core.group' | translate }}</p>
|
||||
<ion-select-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
@ -147,16 +140,14 @@
|
|||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-radio [value]="0">
|
||||
<p>{{ 'addon.calendar.durationnone' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="end" [value]="0" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-radio [value]="1">
|
||||
<p>{{ 'addon.calendar.durationuntil' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="end" [value]="1" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="form.controls.duration.value === 1">
|
||||
<ion-label position="stacked" />
|
||||
|
@ -171,14 +162,12 @@
|
|||
</ion-modal>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<p>{{ 'addon.calendar.durationminutes' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="end" [value]="2" />
|
||||
<ion-radio [value]="2">
|
||||
<p id="durationinminutes">{{ 'addon.calendar.durationminutes' | translate }}</p>
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="form.controls.duration.value === 2">
|
||||
<ion-label class="sr-only">{{ 'addon.calendar.durationminutes' | translate }}</ion-label>
|
||||
<ion-input type="number" name="timedurationminutes" slot="end"
|
||||
<ion-input type="number" name="timedurationminutes" labelPlacement="start" aria-labelledby="durationinminutes"
|
||||
[placeholder]="'addon.calendar.durationminutes' | translate" formControlName="timedurationminutes" />
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
|
@ -187,16 +176,13 @@
|
|||
<!-- Repeat (for new events). -->
|
||||
<ng-container *ngIf="!eventId || eventId < 0">
|
||||
<ion-item class="ion-text-wrap divider">
|
||||
<ion-label>
|
||||
<ion-checkbox labelPlacement="start" formControlName="repeat">
|
||||
<p class="item-heading">{{ 'addon.calendar.repeatevent' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-checkbox slot="end" formControlName="repeat" />
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<p class="item-heading">{{ 'addon.calendar.repeatweeksl' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-input type="number" name="repeats" formControlName="repeats" [disabled]="!form.controls.repeat.value" />
|
||||
<ion-input labelPlacement="stacked" [label]="'addon.calendar.repeatweeksl' | translate" type="number" name="repeats"
|
||||
formControlName="repeats" [disabled]="!form.controls.repeat.value" />
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
|
@ -209,16 +195,14 @@
|
|||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-radio value="1">
|
||||
<p>{{ 'addon.calendar.repeateditall' | translate:{$a: otherEventsCount} }}</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="end" value="1" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-radio value="0">
|
||||
<p>{{ 'addon.calendar.repeateditthis' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="end" value="0" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</div>
|
||||
|
@ -235,10 +219,8 @@
|
|||
|
||||
<!-- Location. -->
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<p class="item-heading">{{ 'core.location' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-input type="text" name="location" [placeholder]="'core.location' | translate" formControlName="location" />
|
||||
<ion-input type="text" name="location" [placeholder]="'core.location' | translate" [label]="'core.location' | translate"
|
||||
labelPlacement="stacked" formControlName="location" />
|
||||
</ion-item>
|
||||
</form>
|
||||
<div collapsible-footer appearOnBottom *ngIf="loaded && !error" slot="fixed">
|
||||
|
|
|
@ -69,7 +69,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
groups: CoreGroup[] = [];
|
||||
loadingGroups = false;
|
||||
courseGroupSet = false;
|
||||
errors: Record<string, string>;
|
||||
error = false;
|
||||
eventRepeatId?: number;
|
||||
otherEventsCount = 0;
|
||||
|
@ -100,9 +99,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
) {
|
||||
this.currentSite = CoreSites.getRequiredCurrentSite();
|
||||
this.remindersEnabled = CoreReminders.isEnabled();
|
||||
this.errors = {
|
||||
required: Translate.instant('core.required'),
|
||||
};
|
||||
|
||||
this.form = new FormGroup({});
|
||||
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item *ngIf="defaultTimeLabel">
|
||||
<ion-label>{{ 'addon.calendar.defaultnotificationtime' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="defaultTimeLabel" (click)="changeDefaultTime($event)">
|
||||
|
||||
<ion-select [(ngModel)]="defaultTimeLabel" (click)="changeDefaultTime($event)"
|
||||
[label]="'addon.calendar.defaultnotificationtime' | translate">
|
||||
<ion-select-option [value]="defaultTimeLabel">{{ defaultTimeLabel }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<ion-list>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let device of platform.devices" [class.item-current]="device.current">
|
||||
<ion-label>
|
||||
<p class="item-heading">
|
||||
<p class="item-heading" id="device-{{device.id}}">
|
||||
<strong>{{ device.name }} {{ device.model }}</strong> ({{platform.platform}} {{ device.version }})
|
||||
</p>
|
||||
<p *ngIf="device.current"><strong>{{ 'core.currentdevice' | translate }}</strong></p>
|
||||
|
@ -33,7 +33,8 @@
|
|||
</p>
|
||||
</ion-label>
|
||||
<core-button-with-spinner [loading]="device.updating" slot="end">
|
||||
<ion-toggle [(ngModel)]="device.enable" (ngModelChange)="enableDevice(device, device.enable)" />
|
||||
<ion-toggle [(ngModel)]="device.enable" (ngModelChange)="enableDevice(device, device.enable)"
|
||||
[attr.aria-labelledby]="'device-'+ device.id " />
|
||||
</core-button-with-spinner>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
|
|
@ -161,7 +161,8 @@
|
|||
<div class="flex-row ion-justify-content-between">
|
||||
<p class="item-heading">
|
||||
<core-format-text [text]="conversation.name" contextLevel="system" [contextInstanceId]="0" />
|
||||
<ion-icon name="fas-user-slash" *ngIf="conversation.isblocked" [title]="'addon.messages.contactblocked' | translate" />
|
||||
<ion-icon name="fas-user-slash" *ngIf="conversation.isblocked"
|
||||
[attr.aria-label]="'addon.messages.contactblocked' | translate" />
|
||||
<ion-icon *ngIf="conversation.ismuted" name="fas-volume-xmark"
|
||||
[title]="'addon.messages.mutedconversation' | translate" />
|
||||
</p>
|
||||
|
|
|
@ -22,21 +22,19 @@
|
|||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p>{{ 'addon.messages.useentertosend' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="sendOnEnter" (ngModelChange)="sendOnEnterChanged()" slot="end" />
|
||||
<ion-toggle [(ngModel)]="sendOnEnter" (ngModelChange)="sendOnEnterChanged()">
|
||||
{{ 'addon.messages.useentertosend' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-card>
|
||||
|
||||
<!-- Contactable privacy. -->
|
||||
<ion-card>
|
||||
<ion-item *ngIf="!advancedContactable">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p>{{ 'addon.messages.blocknoncontacts' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="contactablePrivacy" (ngModelChange)="saveContactablePrivacy(contactablePrivacy)" slot="end" />
|
||||
<ion-item *ngIf="!advancedContactable" class="ion-text-wrap">
|
||||
<ion-toggle [(ngModel)]="contactablePrivacy" (ngModelChange)="saveContactablePrivacy(contactablePrivacy)">
|
||||
{{ 'addon.messages.blocknoncontacts' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-list *ngIf="advancedContactable">
|
||||
|
@ -47,22 +45,19 @@
|
|||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p>{{ 'addon.messages.contactableprivacy_onlycontacts' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="onlyContactsValue" />
|
||||
<ion-radio labelPlacement="end" justify="start" [value]="onlyContactsValue">
|
||||
{{ 'addon.messages.contactableprivacy_onlycontacts' | translate }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p>{{ 'addon.messages.contactableprivacy_coursemember' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="courseMemberValue" />
|
||||
<ion-radio labelPlacement="end" justify="start" [value]="courseMemberValue">
|
||||
{{ 'addon.messages.contactableprivacy_coursemember' | translate }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="allowSiteMessaging" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p>{{ 'addon.messages.contactableprivacy_site' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="siteValue" />
|
||||
<ion-radio labelPlacement="end" justify="start" [value]="siteValue">
|
||||
{{ 'addon.messages.contactableprivacy_site' | translate }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
|
|
@ -226,10 +226,9 @@
|
|||
<!-- Submit for grading form. -->
|
||||
<ng-container *ngIf="canSubmit">
|
||||
<ion-item class="ion-text-wrap" *ngIf="submissionStatement">
|
||||
<ion-label>
|
||||
<ion-checkbox name="submissionstatement" [(ngModel)]="acceptStatement">
|
||||
<core-format-text [text]="submissionStatement" [filter]="false" />
|
||||
</ion-label>
|
||||
<ion-checkbox slot="end" name="submissionstatement" [(ngModel)]="acceptStatement" />
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
<!-- Submit button. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="!showErrorStatementSubmit">
|
||||
|
@ -277,22 +276,18 @@
|
|||
<!-- Numeric grade.
|
||||
Use a text input because otherwise we cannot readthe value if it has an invalid character. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.method === 'simple' && !grade.scale">
|
||||
<ion-label position="stacked">
|
||||
<p class="item-heading">{{ 'addon.mod_assign.gradeoutof' | translate: {$a: gradeInfo!.grade} }}</p>
|
||||
</ion-label>
|
||||
<ion-input *ngIf="!grade.disabled" type="text" [(ngModel)]="grade.grade" min="0" [max]="gradeInfo!.grade"
|
||||
[lang]="grade.lang" />
|
||||
<p *ngIf="grade.disabled">{{ 'addon.mod_assign.gradelocked' | translate }}</p>
|
||||
[lang]="grade.lang" [label]="'addon.mod_assign.gradeoutof' | translate: {$a: gradeInfo!.grade}"
|
||||
labelPlacement="stacked"
|
||||
[helperText]="grade.disabled ? ('addon.mod_assign.gradelocked' | translate) : null" />
|
||||
</ion-item>
|
||||
|
||||
<!-- Grade using a scale. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.method === 'simple' && grade.scale">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'addon.mod_assign.grade' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="grade.grade" interface="action-sheet" [disabled]="grade.disabled"
|
||||
[cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'addon.mod_assign.grade' | translate}">
|
||||
<p class="item-heading" slot="label">{{ 'addon.mod_assign.grade' | translate }}</p>
|
||||
<ion-select-option *ngFor="let grade of grade.scale" [value]="grade.value">
|
||||
{{grade.label}}
|
||||
</ion-select-option>
|
||||
|
@ -301,12 +296,10 @@
|
|||
|
||||
<!-- Outcomes. -->
|
||||
<ion-item class="ion-text-wrap" *ngFor="let outcome of gradeInfo!.outcomes">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ outcome.name }}</p>
|
||||
</ion-label>
|
||||
<ion-select *ngIf="canSaveGrades && outcome.itemNumber" [(ngModel)]="outcome.selectedId"
|
||||
interface="action-sheet" [disabled]="gradeInfo!.disabled" [cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: outcome.name }">
|
||||
<p class="item-heading" slot="label">{{ outcome.name }}</p>
|
||||
<ion-select-option *ngFor="let grade of outcome.options" [value]="grade.value">
|
||||
{{grade.label}}
|
||||
</ion-select-option>
|
||||
|
@ -353,11 +346,10 @@
|
|||
|
||||
<!--- Apply grade to all team members. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="assign!.teamsubmission && canSaveGrades">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="grade.applyToAll">
|
||||
<p class="item-heading">{{ 'addon.mod_assign.groupsubmissionsettings' | translate }}</p>
|
||||
<p>{{ 'addon.mod_assign.applytoteam' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="grade.applyToAll" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<!-- Attempt status. -->
|
||||
|
@ -380,18 +372,19 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="canSaveGrades && allowAddAttempt">
|
||||
<ion-label>{{ 'addon.mod_assign.addattempt' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="grade.addAttempt" slot="end" />
|
||||
<ion-toggle [(ngModel)]="grade.addAttempt">
|
||||
<p>{{ 'addon.mod_assign.addattempt' | translate }}</p>
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Data about the grader (teacher who graded). -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="grader" core-user-link [userId]="grader!.id" [courseId]="courseId"
|
||||
[attr.aria-label]="grader!.fullname" [detail]="true">
|
||||
<ion-item class="ion-text-wrap" *ngIf="grader" core-user-link [userId]="grader.id" [courseId]="courseId"
|
||||
[attr.aria-label]="grader.fullname" [detail]="true">
|
||||
<core-user-avatar [user]="grader" slot="start" [linkProfile]="false" />
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'addon.mod_assign.gradedby' | translate }}</p>
|
||||
<p class="item-heading">{{ grader!.fullname }}</p>
|
||||
<p class="item-heading">{{ grader.fullname }}</p>
|
||||
<p *ngIf="feedback!.gradeddate">{{ feedback!.gradeddate * 1000 | coreFormatDate }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
|
|
@ -1124,9 +1124,15 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
|
|||
return [];
|
||||
}
|
||||
|
||||
// Receved submission statement should not be undefined. It would mean that the WS is not returning the value.
|
||||
const submissionStatementMissing = !!this.assign.requiresubmissionstatement &&
|
||||
this.assign.submissionstatement === undefined;
|
||||
|
||||
// If received submission statement is empty, then it's not required.
|
||||
if(!this.assign.submissionstatement && this.assign.submissionstatement !== undefined) {
|
||||
this.assign.requiresubmissionstatement = 0;
|
||||
}
|
||||
|
||||
this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && (lastAttempt.cansubmit ||
|
||||
(this.hasOffline && AddonModAssign.canSubmitOffline(this.assign, submissionStatus)));
|
||||
|
||||
|
|
|
@ -38,10 +38,9 @@
|
|||
<form name="addon-mod_assign-edit-form" #editSubmissionForm>
|
||||
<!-- Submission statement. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="submissionStatement">
|
||||
<ion-label>
|
||||
<ion-checkbox name="submissionstatement" [(ngModel)]="submissionStatementAccepted">
|
||||
<core-format-text [text]="submissionStatement" [filter]="false" />
|
||||
</ion-label>
|
||||
<ion-checkbox slot="end" name="submissionstatement" [(ngModel)]="submissionStatementAccepted" />
|
||||
</ion-checkbox>
|
||||
<!-- ion-checkbox doesn't use an input. Create a hidden input to hold the value. -->
|
||||
<input type="hidden" [ngModel]="submissionStatementAccepted" name="submissionstatement">
|
||||
</ion-item>
|
||||
|
|
|
@ -205,6 +205,12 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
this.introAttachments = submissionStatus.assignmentdata?.attachments?.intro ?? this.assign.introattachments;
|
||||
|
||||
this.allowOffline = true; // If offline isn't allowed we shouldn't have reached this point.
|
||||
|
||||
// If received submission statement is empty, then it's not required.
|
||||
if(!this.assign.submissionstatement && this.assign.submissionstatement !== undefined) {
|
||||
this.assign.requiresubmissionstatement = 0;
|
||||
}
|
||||
|
||||
// Only show submission statement if we are editing our own submission.
|
||||
if (this.assign.requiresubmissionstatement && !this.assign.submissiondrafts && this.userId == currentUserId) {
|
||||
this.submissionStatement = this.assign.submissionstatement;
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
<core-group-selector [groupInfo]="groupInfo" [(selected)]="groupId" (selectedChange)="reloadSessions()" [courseId]="courseId" />
|
||||
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.mod_chat.showincompletesessions' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="showAll" (ionChange)="reloadSessions()" slot="end" />
|
||||
<ion-toggle [(ngModel)]="showAll" (ionChange)="reloadSessions()">
|
||||
{{ 'addon.mod_chat.showincompletesessions' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-card *ngFor="let session of sessions.items" (click)="sessions.select(session)" button
|
||||
|
|
|
@ -48,18 +48,16 @@
|
|||
<ion-card *ngIf="options.length && choice">
|
||||
<ng-container *ngIf="choice.allowmultiple">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let option of options">
|
||||
<ion-label>
|
||||
<ion-checkbox [(ngModel)]="option.checked" [disabled]="option.disabled || !canEdit">
|
||||
<ng-container *ngTemplateOutlet="optionLabelTemplate; context: {option: option}" />
|
||||
</ion-label>
|
||||
<ion-checkbox slot="end" [(ngModel)]="option.checked" [disabled]="option.disabled || !canEdit" />
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
<ion-radio-group *ngIf="!choice.allowmultiple" [(ngModel)]="selectedOption.id">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let option of options">
|
||||
<ion-label>
|
||||
<ion-radio [value]="option.id" [disabled]="option.disabled || !canEdit">
|
||||
<ng-container *ngTemplateOutlet="optionLabelTemplate; context: {option: option}" />
|
||||
</ion-label>
|
||||
<ion-radio slot="end" [value]="option.id" [disabled]="option.disabled || !canEdit" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-card>
|
||||
|
@ -147,7 +145,7 @@
|
|||
|
||||
<!-- Template to render a choice option label. -->
|
||||
<ng-template #optionLabelTemplate let-option="option">
|
||||
<p>
|
||||
<p class="item-heading">
|
||||
<core-format-text [text]="option.text" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" />
|
||||
<span *ngIf="choice!.limitanswers && option.countanswers >= option.maxanswers">
|
||||
{{ 'addon.mod_choice.full' | translate }}
|
||||
|
|
|
@ -12,20 +12,20 @@
|
|||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.mod_data.advancedsearch' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="search.searchingAdvanced" slot="end" />
|
||||
<ion-toggle [(ngModel)]="search.searchingAdvanced">
|
||||
{{ 'addon.mod_data.advancedsearch' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<form (ngSubmit)="searchEntries($event)" [formGroup]="searchForm" #searchFormEl>
|
||||
<ion-list class="ion-no-margin">
|
||||
<ion-item [hidden]="search.searchingAdvanced">
|
||||
<ion-label class="sr-only">{{ 'addon.mod_data.search' | translate}}</ion-label>
|
||||
<ion-input type="text" placeholder="{{ 'addon.mod_data.search' | translate}}" [(ngModel)]="search.text" name="text"
|
||||
formControlName="text" />
|
||||
<ion-input type="text" [attr.aria-label]="'addon.mod_data.search' | translate"
|
||||
placeholder="{{ 'addon.mod_data.search' | translate}}" name="text" formControlName="text" />
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">{{ 'core.sortby' | translate }}</ion-label>
|
||||
<ion-select interface="action-sheet" name="sortBy" formControlName="sortBy" [placeholder]="'core.sortby' | translate"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.sortby' | translate}">
|
||||
<ion-select labelPlacement="stacked" interface="action-sheet" name="sortBy" formControlName="sortBy"
|
||||
[placeholder]="'core.sortby' | translate" [cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'core.sortby' | translate}" [label]="'core.sortby' | translate">
|
||||
<optgroup *ngIf="fieldsArray.length" label="{{ 'addon.mod_data.fields' | translate }}">
|
||||
<ion-select-option *ngFor="let field of fieldsArray" [value]="field.id">{{field.name}}</ion-select-option>
|
||||
</optgroup>
|
||||
|
@ -41,14 +41,16 @@
|
|||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-list>
|
||||
<ion-radio-group [(ngModel)]="search.sortDirection" name="sortDirection" formControlName="sortDirection">
|
||||
<ion-radio-group name="sortDirection" formControlName="sortDirection">
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.mod_data.ascending' | translate }}</ion-label>
|
||||
<ion-radio slot="start" value="ASC" />
|
||||
<ion-radio value="ASC" labelPlacement="end" justify="start">
|
||||
{{ 'addon.mod_data.ascending' | translate }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.mod_data.descending' | translate }}</ion-label>
|
||||
<ion-radio slot="start" value="DESC" />
|
||||
<ion-radio value="DESC" labelPlacement="end" justify="start">
|
||||
{{'addon.mod_data.descending' | translate}}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
.addon-data-latlong {
|
||||
display: flex;
|
||||
|
||||
.input-units {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,17 @@
|
|||
<ion-select [formControlName]="'f_'+field.id" multiple="true" [placeholder]="'addon.mod_data.menuchoose' | translate"
|
||||
[cancelText]="'core.cancel' | translate" [okText]="'core.ok' | translate" [interfaceOptions]="{header: field.name}"
|
||||
interface="alert">
|
||||
<ion-select-option *ngFor="let option of options" [value]="option.value">{{option.key}}</ion-select-option>
|
||||
<ion-select-option *ngFor="let option of options" [value]="option.value">
|
||||
<core-format-text [text]="option.key" contextLevel="module" [contextInstanceId]="database?.coursemodule"
|
||||
[courseId]="database?.course" [wsNotFiltered]="true" />
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" />
|
||||
|
||||
<ion-item *ngIf="searchMode">
|
||||
<ion-label>{{ 'addon.mod_data.selectedrequired' | translate }}</ion-label>
|
||||
<ion-checkbox slot="end" [formControlName]="'f_'+field.id+'_allreq'" [(ngModel)]="searchFields!['f_'+field.id+'_allreq']" />
|
||||
<ion-item *ngIf="searchMode" class="ion-text-wrap">
|
||||
<ion-checkbox [formControlName]="'f_'+field.id+'_allreq'" [(ngModel)]="searchFields!['f_'+field.id+'_allreq']">
|
||||
{{ 'addon.mod_data.selectedrequired' | translate }}
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</span>
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
</ion-modal>
|
||||
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" />
|
||||
|
||||
<ion-item *ngIf="searchMode">
|
||||
<ion-label>{{ 'addon.mod_data.usedate' | translate }}</ion-label>
|
||||
<ion-checkbox slot="end" [formControlName]="'f_'+field.id+'_z'" [(ngModel)]="searchFields['f_'+field.id+'_z']" />
|
||||
<ion-item *ngIf="searchMode" class="ion-text-wrap">
|
||||
<ion-checkbox [formControlName]="'f_'+field.id+'_z'" [(ngModel)]="searchFields['f_'+field.id+'_z']">
|
||||
{{ 'addon.mod_data.usedate' | translate }}
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</span>
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<ng-container *ngIf="editMode">
|
||||
<span [core-mark-required]="field.required" class="core-mark-required"></span>
|
||||
<div class="addon-data-latlong">
|
||||
<div class="addon-data-latlong flex-row">
|
||||
<ion-input type="text" [formControlName]="'f_'+field.id+'_0'" maxlength="10" />
|
||||
<span class="placeholder-icon" item-right>°N</span>
|
||||
<div class="input-units">°N</div>
|
||||
</div>
|
||||
<div class="addon-data-latlong">
|
||||
<div class="addon-data-latlong flex-row">
|
||||
<ion-input type="text" [formControlName]="'f_'+field.id+'_1'" maxlength="10" />
|
||||
<span class="placeholder-icon" item-right>°E</span>
|
||||
<div class="input-units">°E</div>
|
||||
</div>
|
||||
<div class="addon-data-latlong" *ngIf="locationServicesEnabled">
|
||||
<ion-button (click)="getLocation($event)">
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
<ion-select [formControlName]="'f_'+field.id" [placeholder]="'addon.mod_data.menuchoose' | translate"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: field.name}" interface="action-sheet">
|
||||
<ion-select-option value="">{{ 'addon.mod_data.menuchoose' | translate }}</ion-select-option>
|
||||
<ion-select-option *ngFor="let option of options" [value]="option">{{option}}</ion-select-option>
|
||||
<ion-select-option *ngFor="let option of options" [value]="option">
|
||||
<core-format-text [text]="option" contextLevel="module" [contextInstanceId]="database?.coursemodule"
|
||||
[courseId]="database?.course" [wsNotFiltered]="true" />
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" />
|
||||
</span>
|
||||
|
|
|
@ -3,14 +3,18 @@
|
|||
<ion-select [formControlName]="'f_'+field.id" multiple="true" [placeholder]="'addon.mod_data.menuchoose' | translate"
|
||||
[cancelText]="'core.cancel' | translate" [okText]="'core.ok' | translate" [interfaceOptions]="{header: field.name}"
|
||||
interface="alert">
|
||||
<ion-select-option *ngFor="let option of options" [value]="option.value">{{option.key}}</ion-select-option>
|
||||
<ion-select-option *ngFor="let option of options" [value]="option.value">
|
||||
<core-format-text [text]="option.key" contextLevel="module" [contextInstanceId]="database?.coursemodule"
|
||||
[courseId]="database?.course" [wsNotFiltered]="true" />
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" />
|
||||
|
||||
|
||||
<ion-item *ngIf="searchMode">
|
||||
<ion-label>{{ 'addon.mod_data.selectedrequired' | translate }}</ion-label>
|
||||
<ion-checkbox slot="end" [formControlName]="'f_'+field.id+'_allreq'" [(ngModel)]="searchFields!['f_'+field.id+'_allreq']" />
|
||||
<ion-item *ngIf="searchMode" class="ion-text-wrap">
|
||||
<ion-checkbox [formControlName]="'f_'+field.id+'_allreq'" [(ngModel)]="searchFields!['f_'+field.id+'_allreq']">
|
||||
{{ 'addon.mod_data.selectedrequired' | translate }}
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</span>
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
[allowOffline]="true" acceptedTypes="image" [courseId]="database?.course" />
|
||||
<core-input-errors *ngIf="error" [errorText]="error" />
|
||||
|
||||
<ion-label position="stacked">{{ 'addon.mod_data.alttext' | translate }}</ion-label>
|
||||
<ion-input type="text" [formControlName]="'f_'+field.id+'_alttext'" [placeholder]=" 'addon.mod_data.alttext' | translate" />
|
||||
<ion-input [label]="'addon.mod_data.alttext' | translate" labelPlacement="stacked" type="text"
|
||||
[formControlName]="'f_'+field.id+'_alttext'" [placeholder]=" 'addon.mod_data.alttext' | translate" />
|
||||
</span>
|
||||
|
||||
<span *ngIf="searchMode && form" [formGroup]="form">
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
[cancelText]="'core.cancel' | translate" [okText]="'core.ok' | translate" [interfaceOptions]="{header: field.name}"
|
||||
interface="alert">
|
||||
<ion-select-option value="">{{ 'addon.mod_data.menuchoose' | translate }}</ion-select-option>
|
||||
<ion-select-option *ngFor="let option of options" [value]="option">{{option}}</ion-select-option>
|
||||
<ion-select-option *ngFor="let option of options" [value]="option">
|
||||
<core-format-text [text]="option" contextLevel="module" [contextInstanceId]="database?.coursemodule"
|
||||
[courseId]="database?.course" [wsNotFiltered]="true" />
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" />
|
||||
</span>
|
||||
|
|
|
@ -26,37 +26,40 @@
|
|||
<ng-container *ngIf="item.typ !== 'pagebreak'">
|
||||
<ion-item class="ion-text-wrap addon-mod_feedback-item" [color]="item.dependitem > 0 ? 'light' : ''"
|
||||
[class.core-danger-item]="item.isEmpty || item.hasError">
|
||||
<ion-label [position]="item.hasTextInput ? 'stacked' : undefined">
|
||||
<p *ngIf="item.name" [core-mark-required]="item.required">
|
||||
<span *ngIf="feedback!.autonumbering && item.itemnumber">{{item.itemnumber}}. </span>
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="item.name" contextLevel="module"
|
||||
[contextInstanceId]="cmId" [courseId]="courseId" [wsNotFiltered]="true" />
|
||||
<span *ngIf="item.postfix" class="addon-mod_feedback-postfix">{{item.postfix}}</span>
|
||||
</p>
|
||||
<ion-label *ngIf="!item.slottedLabel">
|
||||
<ng-container *ngTemplateOutlet="label; context: {item: item}" />
|
||||
<p *ngIf="item.templateName === 'label'">
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="item.presentation"
|
||||
contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true" [courseId]="courseId" />
|
||||
</p>
|
||||
</ion-label>
|
||||
|
||||
<ion-input *ngIf="item.templateName === 'textfield'" type="text" [(ngModel)]="item.value" autocorrect="off"
|
||||
name="{{item.typ}}_{{item.id}}" maxlength="{{item.length}}" [required]="item.required" />
|
||||
<ion-input labelPlacement="stacked" *ngIf="item.templateName === 'textfield'" type="text"
|
||||
[(ngModel)]="item.value" autocorrect="off" name="{{item.typ}}_{{item.id}}" maxlength="{{item.length}}"
|
||||
[required]="item.required">
|
||||
<ng-container *ngTemplateOutlet="label; context: {item: item}" />
|
||||
</ion-input>
|
||||
|
||||
<ng-container *ngIf="item.templateName === 'numeric'">
|
||||
<ion-input type="number" [(ngModel)]="item.value" name="{{item.typ}}_{{item.id}}"
|
||||
[required]="item.required" />
|
||||
<ion-input labelPlacement="stacked" type="number" [(ngModel)]="item.value" name="{{item.typ}}_{{item.id}}"
|
||||
[required]="item.required">
|
||||
<ng-container *ngTemplateOutlet="label; context: {item: item}" />
|
||||
</ion-input>
|
||||
<ion-text *ngIf="item.hasError" color="danger" class="addon-mod_feedback-item-error">
|
||||
{{ 'addon.mod_feedback.numberoutofrange' | translate }} [{{item.rangefrom}}
|
||||
<span *ngIf="item.rangefrom && item.rangeto">, </span>{{item.rangeto}}]
|
||||
</ion-text>
|
||||
</ng-container>
|
||||
|
||||
<ion-textarea *ngIf="item.templateName === 'textarea'" [required]="item.required"
|
||||
name="{{item.typ}}_{{item.id}}" [(ngModel)]="item.value" />
|
||||
<ion-textarea labelPlacement="stacked" *ngIf="item.templateName === 'textarea'" [required]="item.required"
|
||||
name="{{item.typ}}_{{item.id}}" [(ngModel)]="item.value">
|
||||
<ng-container *ngTemplateOutlet="label; context: {item: item}" />
|
||||
</ion-textarea>
|
||||
|
||||
<ion-select *ngIf="item.templateName === 'multichoice-d'" [required]="item.required"
|
||||
<ion-select labelPlacement="stacked" *ngIf="item.templateName === 'multichoice-d'" [required]="item.required"
|
||||
name="{{item.typ}}_{{item.id}}" [(ngModel)]="item.value" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: item.name}">
|
||||
<ng-container *ngTemplateOutlet="label; context: {item: item}" />
|
||||
<ion-select-option *ngFor="let option of item.choices" [value]="option.value">
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
|
||||
contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true" [courseId]="courseId" />
|
||||
|
@ -66,23 +69,21 @@
|
|||
|
||||
<ion-radio-group *ngIf="item.templateName === 'multichoice-r'" [(ngModel)]="item.value" [required]="item.required"
|
||||
name="{{item.typ}}_{{item.id}}">
|
||||
<ion-item *ngFor="let option of item.choices">
|
||||
<ion-label>
|
||||
<ion-item *ngFor="let option of item.choices" class="ion-text-wrap">
|
||||
<ion-radio [value]="option.value">
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
|
||||
contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true" [courseId]="courseId" />
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="option.value" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
|
||||
<ng-container *ngIf="item.templateName === 'multichoice-c'">
|
||||
<ion-item *ngFor="let option of item.choices">
|
||||
<ion-label>
|
||||
<ion-checkbox [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="option.checked"
|
||||
value="option.value">
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
|
||||
contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true" [courseId]="courseId" />
|
||||
</ion-label>
|
||||
<ion-checkbox [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="option.checked"
|
||||
value="option.value" />
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
|
@ -152,3 +153,13 @@
|
|||
</div>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
||||
|
||||
<ng-template #label let-item="item">
|
||||
<p *ngIf="item.name" [core-mark-required]="item.required" [slot]="item.slottedLabel ? 'label' : undefined">
|
||||
<span *ngIf="feedback!.autonumbering && item.itemnumber">{{item.itemnumber}}. </span>
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="item.name" contextLevel="module" [contextInstanceId]="cmId"
|
||||
[courseId]="courseId" [wsNotFiltered]="true" />
|
||||
<span *ngIf="item.postfix" class="addon-mod_feedback-postfix">{{item.postfix}}</span>
|
||||
</p>
|
||||
</ng-template>
|
||||
|
|
|
@ -251,7 +251,7 @@ export class AddonModFeedbackHelperProvider {
|
|||
return Object.assign(item, {
|
||||
templateName: 'label',
|
||||
value: '',
|
||||
hasTextInput: false,
|
||||
slottedLabel: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -265,7 +265,7 @@ export class AddonModFeedbackHelperProvider {
|
|||
const formItem: AddonModFeedbackFormBasicItem = Object.assign(item, {
|
||||
templateName: 'label',
|
||||
value: '',
|
||||
hasTextInput: false,
|
||||
slottedLabel: false,
|
||||
});
|
||||
|
||||
const type = parseInt(formItem.presentation, 10);
|
||||
|
@ -304,7 +304,7 @@ export class AddonModFeedbackHelperProvider {
|
|||
value: item.rawValue !== undefined ? Number(item.rawValue) : '',
|
||||
rangefrom: typeof rangeFrom == 'number' && !isNaN(rangeFrom) ? range[0] : '',
|
||||
rangeto: typeof rangeTo == 'number' && !isNaN(rangeTo) ? rangeTo : '',
|
||||
hasTextInput: true,
|
||||
slottedLabel: true,
|
||||
});
|
||||
formItem.postfix = this.getNumericBoundariesForDisplay(formItem.rangefrom, formItem.rangeto);
|
||||
|
||||
|
@ -322,7 +322,7 @@ export class AddonModFeedbackHelperProvider {
|
|||
templateName: 'textfield',
|
||||
length: Number(item.presentation.split(AddonModFeedbackProvider.LINE_SEP)[1]) || 255,
|
||||
value: item.rawValue !== undefined ? item.rawValue : '',
|
||||
hasTextInput: true,
|
||||
slottedLabel: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -336,7 +336,7 @@ export class AddonModFeedbackHelperProvider {
|
|||
return Object.assign(item, {
|
||||
templateName: 'textarea',
|
||||
value: item.rawValue !== undefined ? item.rawValue : '',
|
||||
hasTextInput: true,
|
||||
slottedLabel: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -356,7 +356,7 @@ export class AddonModFeedbackHelperProvider {
|
|||
subtype: subType,
|
||||
value: '',
|
||||
choices: [],
|
||||
hasTextInput: false,
|
||||
slottedLabel: subType === 'd',
|
||||
});
|
||||
|
||||
formItem.presentation = parts.length > 1 ? parts[1] : '';
|
||||
|
@ -411,7 +411,7 @@ export class AddonModFeedbackHelperProvider {
|
|||
const formItem: AddonModFeedbackCaptchaItem = Object.assign(item, {
|
||||
templateName: 'captcha',
|
||||
value: '',
|
||||
hasTextInput: false,
|
||||
slottedLabel: false,
|
||||
});
|
||||
|
||||
const data = <string[]> CoreTextUtils.parseJSON(item.otherdata);
|
||||
|
@ -549,7 +549,7 @@ export type AddonModFeedbackFormItem =
|
|||
export type AddonModFeedbackFormBasicItem = AddonModFeedbackItem & {
|
||||
templateName: string;
|
||||
value: AddonModFeedbackResponseValue;
|
||||
hasTextInput: boolean;
|
||||
slottedLabel: boolean;
|
||||
isEmpty?: boolean;
|
||||
hasError?: boolean;
|
||||
};
|
||||
|
|
|
@ -94,9 +94,9 @@
|
|||
</ng-container>
|
||||
|
||||
<form *ngIf="showForm" [id]="'addon-forum-reply-edit-form-' + uniqueId" #replyFormEl>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.subject' | translate }}</ion-label>
|
||||
<ion-input type="text" [placeholder]="'addon.mod_forum.subject' | translate" [(ngModel)]="formData.subject" name="subject" />
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-input labelPlacement="stacked" type="text" [placeholder]="'addon.mod_forum.subject' | translate"
|
||||
[(ngModel)]="formData.subject" name="subject" [label]="'addon.mod_forum.subject' | translate" />
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
|
@ -106,8 +106,9 @@
|
|||
[draftExtraParams]="{reply: post.id}" (contentChanged)="onMessageChange($event)" />
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="accessInfo.canpostprivatereply">
|
||||
<ion-label>{{ 'addon.mod_forum.privatereply' | translate }}</ion-label>
|
||||
<ion-checkbox slot="end" [(ngModel)]="formData.isprivatereply" name="isprivatereply" />
|
||||
<ion-checkbox [(ngModel)]="formData.isprivatereply" name="isprivatereply">
|
||||
{{ 'addon.mod_forum.privatereply' | translate }}
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
<ng-container *ngIf="forum.id && forum.maxattachments > 0">
|
||||
<ion-item button class="divider ion-text-wrap" (click)="toggleAdvanced()" [detail]="false" [attr.aria-expanded]="advanced"
|
||||
|
|
|
@ -16,9 +16,10 @@
|
|||
<core-loading [hideUntil]="groupsLoaded">
|
||||
<form *ngIf="showForm" #newDiscFormEl>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.subject' | translate }}</ion-label>
|
||||
<ion-input [(ngModel)]="newDiscussion.subject" type="text" [placeholder]="'addon.mod_forum.subject' | translate"
|
||||
name="subject" />
|
||||
<ion-input labelPlacement="stacked" [(ngModel)]="newDiscussion.subject" type="text"
|
||||
[placeholder]="'addon.mod_forum.subject' | translate" name="subject">
|
||||
<p>{{ 'addon.mod_forum.subject' | translate }}</p>
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
|
@ -38,27 +39,30 @@
|
|||
</ion-item>
|
||||
<div *ngIf="advanced" id="addon-mod-forum-new-discussion-advanced">
|
||||
<ion-item *ngIf="showGroups && groupIds.length > 1 && accessInfo.cancanposttomygroups">
|
||||
<ion-label>{{ 'addon.mod_forum.posttomygroups' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="newDiscussion.postToAllGroups" name="postallgroups" slot="end" />
|
||||
<ion-toggle [(ngModel)]="newDiscussion.postToAllGroups" name="postallgroups">
|
||||
{{ 'addon.mod_forum.posttomygroups' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="showGroups" class="core-edit-set-group">
|
||||
<ion-label>{{ 'addon.mod_forum.group' | translate }}</ion-label>
|
||||
<ion-item *ngIf="showGroups" class="core-edit-set-group ion-text-wrap">
|
||||
<ion-select [(ngModel)]="newDiscussion.groupId" [disabled]="newDiscussion.postToAllGroups" interface="action-sheet"
|
||||
name="groupid" [interfaceOptions]="{header: 'addon.mod_forum.group' | translate}"
|
||||
[cancelText]="'core.cancel' | translate" (ionChange)="calculateGroupName()">
|
||||
<p class="item-heading" slot="label">{{ 'addon.mod_forum.group' | translate }}</p>
|
||||
<ion-select-option *ngFor="let group of groups" [value]="group.id">
|
||||
<core-format-text [text]="group.name" contextLevel="course" [contextInstanceId]="courseId"
|
||||
[wsNotFiltered]="true" />
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.mod_forum.discussionsubscription' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="newDiscussion.subscribe" name="subscribe" slot="end" />
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-toggle [(ngModel)]="newDiscussion.subscribe" name="subscribe">
|
||||
{{ 'addon.mod_forum.discussionsubscription' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="canPin">
|
||||
<ion-label>{{ 'addon.mod_forum.discussionpinned' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="newDiscussion.pin" name="pin" slot="end" />
|
||||
<ion-item *ngIf="canPin" class="ion-text-wrap">
|
||||
<ion-toggle [(ngModel)]="newDiscussion.pin" name="pin">
|
||||
{{ 'addon.mod_forum.discussionpinned' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<core-attachments *ngIf="canCreateAttachments && forum && forum.maxattachments > 0" [files]="newDiscussion.files"
|
||||
[maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [component]="component" [componentId]="forum.cmid"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<ion-content class="limited-width">
|
||||
<div>
|
||||
<ion-card class="core-danger-card" *ngIf="searchBanner">
|
||||
<ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-icon name="fas-triangle-exclamation" slot="start" aria-hidden="true" />
|
||||
<ion-label>
|
||||
<core-format-text [text]="searchBanner" />
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<ion-content>
|
||||
<ion-radio-group [(ngModel)]="selectedMode" (ionChange)="modePicked()">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let mode of modes">
|
||||
<ion-label>{{ mode.langkey | translate }}</ion-label>
|
||||
<ion-radio slot="end" [value]="mode.key" />
|
||||
<ion-radio [value]="mode.key">
|
||||
{{ mode.langkey | translate }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-content>
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
<core-loading [hideUntil]="loaded">
|
||||
<form #editFormEl *ngIf="glossary">
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_glossary.concept' | translate }}</ion-label>
|
||||
<ion-input type="text" [placeholder]="'addon.mod_glossary.concept' | translate" [(ngModel)]="data.concept" name="concept" />
|
||||
<ion-input labelPlacement="stacked" type="text" [placeholder]="'addon.mod_glossary.concept' | translate"
|
||||
[(ngModel)]="data.concept" name="concept" [label]="'addon.mod_glossary.concept' | translate" />
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_glossary.definition' | translate }}</ion-label>
|
||||
|
@ -25,22 +25,20 @@
|
|||
[draftExtraParams]="editorExtraParams" />
|
||||
</ion-item>
|
||||
<ion-item *ngIf="categories.length > 0">
|
||||
<ion-label position="stacked">
|
||||
{{ 'addon.mod_glossary.categories' | translate }}
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="data.categories" multiple="true" interface="action-sheet"
|
||||
|
||||
<ion-select labelPlacement="stacked" [(ngModel)]="data.categories" multiple="true" interface="action-sheet"
|
||||
[placeholder]="'addon.mod_glossary.categories' | translate" name="categories" [cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'addon.mod_glossary.categories' | translate}">
|
||||
[interfaceOptions]="{header: 'addon.mod_glossary.categories' | translate}"
|
||||
[label]="'addon.mod_glossary.categories' | translate">
|
||||
<ion-select-option *ngFor="let category of categories" [value]="category.id">
|
||||
{{ category.name }}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="showAliases">
|
||||
<ion-label position="stacked">
|
||||
{{ 'addon.mod_glossary.aliases' | translate }}
|
||||
</ion-label>
|
||||
<ion-textarea [(ngModel)]="data.aliases" rows="1" [core-auto-rows]="data.aliases" name="aliases" />
|
||||
|
||||
<ion-textarea labelPlacement="stacked" [(ngModel)]="data.aliases" rows="1" [core-auto-rows]="data.aliases" name="aliases"
|
||||
[label]="'addon.mod_glossary.aliases' | translate" />
|
||||
</ion-item>
|
||||
<ion-item-divider>
|
||||
<ion-label>
|
||||
|
@ -56,16 +54,19 @@
|
|||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>{{ 'addon.mod_glossary.entryusedynalink' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="data.usedynalink" name="usedynalink" slot="end" />
|
||||
<ion-toggle [(ngModel)]="data.usedynalink" name="usedynalink">
|
||||
{{ 'addon.mod_glossary.entryusedynalink' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>{{ 'addon.mod_glossary.casesensitive' | translate }}</ion-label>
|
||||
<ion-toggle [disabled]="!data.usedynalink" [(ngModel)]="data.casesensitive" name="casesensitive" slot="end" />
|
||||
<ion-toggle [disabled]="!data.usedynalink" [(ngModel)]="data.casesensitive" name="casesensitive">
|
||||
{{ 'addon.mod_glossary.casesensitive' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>{{ 'addon.mod_glossary.fullmatch' | translate }}</ion-label>
|
||||
<ion-toggle [disabled]="!data.usedynalink" [(ngModel)]="data.fullmatch" name="fullmatch" slot="end" />
|
||||
<ion-toggle [disabled]="!data.usedynalink" [(ngModel)]="data.fullmatch" name="fullmatch">
|
||||
{{ 'addon.mod_glossary.fullmatch' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
<ion-button class="ion-margin" expand="block" [disabled]="!data.concept || !data.definition" (click)="save()">
|
||||
|
|
|
@ -28,11 +28,11 @@
|
|||
<ion-card *ngIf="askPassword">
|
||||
<form (ngSubmit)="submitPassword($event, passwordinput)" #passwordForm>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
|
||||
core-auto-focus #passwordinput [clearOnEdit]="false" />
|
||||
</core-show-password>
|
||||
<ion-input labelPlacement="stacked" name="password" type="password"
|
||||
placeholder="{{ 'core.login.password' | translate }}" core-auto-focus #passwordinput [clearOnEdit]="false"
|
||||
[label]="'addon.mod_lesson.enterpassword' | translate">
|
||||
<core-show-password slot="end" />
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-button expand="block" type="submit">
|
||||
{{ 'addon.mod_lesson.continue' | translate }}
|
||||
|
|
|
@ -71,10 +71,9 @@
|
|||
|
||||
<!-- Short answer. -->
|
||||
<ion-item class="ion-text-wrap" *ngSwitchCase="'shortanswer'">
|
||||
<ion-label class="sr-only" stacked />
|
||||
<ion-input [type]="question.input!.type" placeholder="{{ 'addon.mod_lesson.youranswer' | translate }}"
|
||||
[id]="question.input!.id" [formControlName]="question.input!.name" autocorrect="off"
|
||||
[maxlength]="question.input!.maxlength" />
|
||||
[attr.aria-label]="'addon.mod_lesson.youranswer' | translate" [id]="question.input!.id"
|
||||
[formControlName]="question.input!.name" autocorrect="off" [maxlength]="question.input!.maxlength" />
|
||||
</ion-item>
|
||||
|
||||
<!-- Essay. -->
|
||||
|
@ -103,22 +102,20 @@
|
|||
<!-- Single choice. -->
|
||||
<ion-radio-group *ngIf="!question.multi" [formControlName]="question.controlName">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let option of question.options">
|
||||
<ion-label>
|
||||
<ion-radio [id]="option.id" [value]="option.value" [disabled]="option.disabled">
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="option.text"
|
||||
contextLevel="module" [contextInstanceId]="lesson.coursemodule" [courseId]="courseId" />
|
||||
</ion-label>
|
||||
<ion-radio slot="end" [id]="option.id" [value]="option.value" [disabled]="option.disabled" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
|
||||
<!-- Multiple choice. -->
|
||||
<ng-container *ngIf="question.multi">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let option of question.options">
|
||||
<ion-label>
|
||||
<ion-checkbox [id]="option.id" [formControlName]="option.name">
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="option.text"
|
||||
contextLevel="module" [contextInstanceId]="lesson.coursemodule" [courseId]="courseId" />
|
||||
</ion-label>
|
||||
<ion-checkbox [id]="option.id" [formControlName]="option.name" slot="end" />
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
@ -126,14 +123,11 @@
|
|||
<!-- Matching. -->
|
||||
<ng-container *ngSwitchCase="'matching'">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let row of question.rows">
|
||||
<ion-label>
|
||||
<p>
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="row.text"
|
||||
contextLevel="module" [contextInstanceId]="lesson.coursemodule" [courseId]="courseId" />
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-select [id]="row.id" [formControlName]="row.name" [cancelText]="'core.cancel' | translate"
|
||||
interface="action-sheet">
|
||||
<core-format-text slot="label" [component]="component" [componentId]="lesson.coursemodule"
|
||||
[text]="row.text" contextLevel="module" [contextInstanceId]="lesson.coursemodule"
|
||||
[courseId]="courseId" />
|
||||
<ion-select-option *ngFor="let option of row.options" [value]="option.value">
|
||||
{{option.label}}
|
||||
</ion-select-option>
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
|
||||
<!-- Retake selector if there is more than one retake. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="student.attempts && student.attempts.length > 1">
|
||||
<ion-label>{{ 'addon.mod_lesson.attemptheader' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="selectedRetake" (ionChange)="changeRetake(selectedRetake!)"
|
||||
[cancelText]="'core.cancel' | translate" interface="action-sheet"
|
||||
[interfaceOptions]="{header: 'addon.mod_lesson.attemptheader' | translate}">
|
||||
<p slot="label" class="item-heading">{{ 'addon.mod_lesson.attemptheader' | translate }}</p>
|
||||
<ion-select-option *ngFor="let retake of student.attempts" [value]="retake.try">
|
||||
{{retake.label}}
|
||||
</ion-select-option>
|
||||
|
@ -129,7 +129,7 @@
|
|||
<!-- Truefalse or multichoice. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="answer[0].isCheckbox"
|
||||
[ngClass]="{'addon-mod_lesson-highlight': answer[0].highlight}">
|
||||
<ion-label>
|
||||
<ion-checkbox [attr.name]="answer[0].name" [ngModel]="answer[0].checked" [disabled]="true">
|
||||
<p>
|
||||
<core-format-text [component]="component" [componentId]="lesson?.coursemodule"
|
||||
[text]="answer[0].content" contextLevel="module" [contextInstanceId]="lesson?.coursemodule"
|
||||
|
@ -143,8 +143,7 @@
|
|||
<ion-badge *ngIf="answer[0].successBadge" color="success" class="addon-mod_lesson-answer-success">
|
||||
{{ answer[0].successBadge }}
|
||||
</ion-badge>
|
||||
</ion-label>
|
||||
<ion-checkbox [attr.name]="answer[0].name" [ngModel]="answer[0].checked" [disabled]="true" slot="end" />
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
|
||||
<!-- Short answer or numeric. -->
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item [formGroup]="form">
|
||||
<ion-label class="sr-only">{{ 'addon.mod_quiz.quizpassword' | translate }}</ion-label>
|
||||
<core-show-password [name]="'quizpassword'">
|
||||
<ion-input id="addon-mod_quiz-accessrule-password-input" name="quizpassword" type="password"
|
||||
placeholder="{{ 'addon.mod_quiz.quizpassword' | translate }}" [formControlName]="'quizpassword'" [clearOnEdit]="false" />
|
||||
</core-show-password>
|
||||
<ion-input id="addon-mod_quiz-accessrule-password-input" name="quizpassword" type="password"
|
||||
placeholder="{{ 'addon.mod_quiz.quizpassword' | translate }}" [formControlName]="'quizpassword'" [clearOnEdit]="false"
|
||||
[attr.aria-label]="'addon.mod_quiz.quizpassword' | translate">
|
||||
<core-show-password slot="end" />
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
|
|
|
@ -110,10 +110,10 @@
|
|||
</ion-card-header>
|
||||
<ion-list>
|
||||
<ion-item class="ion-text-wrap" *ngIf="organizations.length > 1">
|
||||
<ion-label>{{ 'addon.mod_scorm.organizations' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="currentOrganization.identifier" (ionChange)="loadOrganization()"
|
||||
[cancelText]="'core.cancel' | translate" interface="action-sheet"
|
||||
[interfaceOptions]="{header: 'addon.mod_scorm.organizations' | translate}">
|
||||
<p class="item-heading" slot="label">{{ 'addon.mod_scorm.organizations' | translate }}</p>
|
||||
<ion-select-option *ngFor="let org of organizations" [value]="org.identifier">
|
||||
{{ org.title }}
|
||||
</ion-select-option>
|
||||
|
@ -182,8 +182,9 @@
|
|||
<ng-container *ngIf="!downloading && !skip">
|
||||
<!-- Create new attempt -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="!scorm.forcenewattempt && numAttempts > 0 && !incomplete && attemptsLeft > 0">
|
||||
<ion-label>{{ 'addon.mod_scorm.newattempt' | translate }}</ion-label>
|
||||
<ion-checkbox slot="end" name="newAttempt" [(ngModel)]="startNewAttempt" />
|
||||
<ion-checkbox name="newAttempt" [(ngModel)]="startNewAttempt">
|
||||
{{ 'addon.mod_scorm.newattempt' | translate }}
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="statusMessage">
|
||||
|
|
|
@ -101,12 +101,12 @@
|
|||
</ion-row>
|
||||
|
||||
<ion-item *ngIf="question.type === 0" class="ion-text-wrap" [class.even]="isEven">
|
||||
<ion-label position="floating">
|
||||
<span [core-mark-required]="question.required">
|
||||
<ion-textarea labelPlacement="floating" [(ngModel)]="answers[question.name]" [name]="question.name"
|
||||
[required]="question.required">
|
||||
<p slot="label" [core-mark-required]="question.required">
|
||||
<strong>{{question.num}}.</strong> {{ question.text }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-textarea [(ngModel)]="answers[question.name]" [name]="question.name" [required]="question.required" />
|
||||
</p>
|
||||
</ion-textarea>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
<core-loading [hideUntil]="loaded">
|
||||
<form [formGroup]="pageForm" #editPageForm *ngIf="loaded">
|
||||
<ion-item class="ion-text-wrap" *ngIf="canEditTitle">
|
||||
<ion-label class="sr-only">{{ 'addon.mod_wiki.newpagetitle' | translate }}</ion-label>
|
||||
<ion-input name="title" type="text" [placeholder]="'addon.mod_wiki.newpagetitle' | translate" formControlName="title" />
|
||||
<ion-input [attr.aria-label]="'addon.mod_wiki.newpagetitle' | translate" name="title" type="text"
|
||||
[placeholder]="'addon.mod_wiki.newpagetitle' | translate" formControlName="title" />
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="edit && field.grades">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="true">
|
||||
<ion-select labelPlacement="stacked" [(ngModel)]="selectedValues[n].grade" [cancelText]="'core.cancel' | translate"
|
||||
interface="action-sheet" [interfaceOptions]="{header: 'addon.mod_workshop_assessment_accumulative.dimensiongradefor' |
|
||||
translate : {'$a': field.dimtitle }}">
|
||||
<div [core-mark-required]="true" slot="label">
|
||||
{{ 'addon.mod_workshop_assessment_accumulative.dimensiongradefor' | translate : {'$a': field.dimtitle } }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-select [interfaceOptions]="{header: 'addon.mod_workshop_assessment_accumulative.dimensiongradefor' |
|
||||
translate : {'$a': field.dimtitle }}" [(ngModel)]="selectedValues[n].grade" [cancelText]="'core.cancel' | translate"
|
||||
interface="action-sheet">
|
||||
</div>
|
||||
<ion-select-option *ngFor="let grade of field.grades" [value]="grade.value">{{grade.label}}</ion-select-option>
|
||||
</ion-select>
|
||||
<core-input-errors *ngIf="fieldErrors['grade_' + n]" [errorText]="fieldErrors['grade_' + n]" />
|
||||
|
@ -29,10 +27,9 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="edit">
|
||||
<ion-label position="stacked">
|
||||
{{ 'addon.mod_workshop_assessment_accumulative.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</ion-label>
|
||||
<ion-textarea [(ngModel)]="selectedValues[n].peercomment" [core-auto-rows]="selectedValues[n].peercomment" />
|
||||
<ion-textarea labelPlacement="stacked" [(ngModel)]="selectedValues[n].peercomment"
|
||||
[core-auto-rows]="selectedValues[n].peercomment"
|
||||
[label]=" 'addon.mod_workshop_assessment_accumulative.dimensioncommentfor' | translate : {'$a': field.dimtitle }" />
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!edit" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="edit">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="true">
|
||||
<ion-textarea labelPlacement="stacked" [(ngModel)]="selectedValues[n].peercomment"
|
||||
[core-auto-rows]="selectedValues[n].peercomment">
|
||||
<div [core-mark-required]="true" slot="label">
|
||||
{{ 'addon.mod_workshop_assessment_comments.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-textarea [(ngModel)]="selectedValues[n].peercomment" [core-auto-rows]="selectedValues[n].peercomment" />
|
||||
</div>
|
||||
</ion-textarea>
|
||||
<core-input-errors *ngIf="fieldErrors['peercomment_' + n]" [errorText]="fieldErrors['peercomment_' + n]" />
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!edit" class="ion-text-wrap">
|
||||
|
|
|
@ -10,32 +10,28 @@
|
|||
<ion-radio-group [(ngModel)]="selectedValues[n].grade" [name]="'grade_' + n">
|
||||
<ion-item>
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="edit">
|
||||
<p [core-mark-required]="edit">
|
||||
{{ 'addon.mod_workshop.yourassessmentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</span>
|
||||
</p>
|
||||
</ion-label>
|
||||
<core-input-errors *ngIf="edit && fieldErrors['grade_' + n]" [errorText]="fieldErrors['grade_' + n]" />
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-radio [value]="-1" [disabled]="!edit" labelPlacement="end" justify="start">
|
||||
<core-format-text [text]="field.grade0" [filter]="false" />
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="-1" [disabled]="!edit" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-radio [value]="1" [disabled]="!edit" labelPlacement="end" justify="start">
|
||||
<core-format-text [text]="field.grade1" [filter]="false" />
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="1" [disabled]="!edit" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
<ion-item *ngIf="edit">
|
||||
<ion-label position="stacked">
|
||||
{{ 'addon.mod_workshop_assessment_numerrors.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</ion-label>
|
||||
<ion-textarea [(ngModel)]="selectedValues[n].peercomment" [name]="'peercomment_' + n"
|
||||
[core-auto-rows]="selectedValues[n].peercomment" />
|
||||
<ion-textarea labelPlacement="stacked" [(ngModel)]="selectedValues[n].peercomment" [name]="'peercomment_' + n"
|
||||
[core-auto-rows]="selectedValues[n].peercomment"
|
||||
[label]="'addon.mod_workshop_assessment_numerrors.dimensioncommentfor' | translate : {'$a': field.dimtitle }" />
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!edit" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
|
|
|
@ -10,13 +10,12 @@
|
|||
<ion-list>
|
||||
<ion-radio-group [(ngModel)]="selectedValues[n].chosenlevelid" [name]="'chosenlevelid_' + n">
|
||||
<ion-item *ngFor="let subfield of field.fields">
|
||||
<ion-label>
|
||||
<ion-radio [value]="subfield.levelid" [disabled]="!edit" labelPlacement="end" justify="start">
|
||||
<p>
|
||||
<core-format-text [text]="subfield.definition" contextLevel="module" [contextInstanceId]="moduleId"
|
||||
[courseId]="courseId" />
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="subfield.levelid" [disabled]="!edit" />
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<h3 class="item-heading">{{ 'addon.mod_workshop.overallfeedback' | translate }}</h3>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item position="stacked" *ngIf="edit">
|
||||
<ion-item *ngIf="edit">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="overallFeedkbackRequired">
|
||||
{{ 'addon.mod_workshop.feedbackauthor' | translate }}
|
||||
|
@ -37,13 +37,12 @@
|
|||
[maxSize]="workshop.overallfeedbackmaxbytes" [maxSubmissions]="workshop.overallfeedbackfiles" [component]="component"
|
||||
[componentId]="componentId" [allowOffline]="true" [courseId]="workshop.course" />
|
||||
<ion-item *ngIf="edit && access && access.canallocate">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="true">
|
||||
{{ 'addon.mod_workshop.assessmentweight' | translate }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="weight" interface="action-sheet" name="weight" [cancelText]="'core.cancel' | translate"
|
||||
<ion-select labelPlacement="stacked" [(ngModel)]="weight" interface="action-sheet" name="weight"
|
||||
[cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'addon.mod_workshop.assessmentweight' | translate}">
|
||||
<div [core-mark-required]="true" slot="label">
|
||||
{{ 'addon.mod_workshop.assessmentweight' | translate }}
|
||||
</div>
|
||||
<ion-select-option *ngFor="let w of weights" [value]="w">{{w}}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
|
|
@ -58,13 +58,12 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="access?.canallocate">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">
|
||||
{{ 'addon.mod_workshop.assessmentweight' | translate }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-select formControlName="weight" required="true" interface="action-sheet" [cancelText]="'core.cancel' | translate"
|
||||
<ion-select labelPlacement="stacked" formControlName="weight" required="true" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'addon.mod_workshop.assessmentweight' | translate}">
|
||||
<div [core-mark-required]="true" slot="label">
|
||||
{{ 'addon.mod_workshop.assessmentweight' | translate }}
|
||||
</div>
|
||||
<ion-select-option *ngFor="let w of weights" [value]="w">{{ w }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
@ -75,9 +74,10 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="access?.canoverridegrades">
|
||||
<ion-label position="stacked">{{ 'addon.mod_workshop.gradinggradeover' | translate }}</ion-label>
|
||||
<ion-select formControlName="grade" interface="action-sheet" [cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'addon.mod_workshop.gradinggradeover' | translate}">
|
||||
<ion-select labelPlacement="stacked" formControlName="grade" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'addon.mod_workshop.gradinggradeover' | translate}"
|
||||
[label]="'addon.mod_workshop.gradinggradeover' | translate">
|
||||
<ion-select-option *ngFor="let grade of evaluationGrades" [value]="grade.value">
|
||||
{{grade.label}}
|
||||
</ion-select-option>
|
||||
|
|
|
@ -17,20 +17,19 @@
|
|||
<core-loading [hideUntil]="loaded">
|
||||
<form [formGroup]="editForm" *ngIf="workshop" #editFormEl>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">
|
||||
<ion-input labelPlacement="stacked" name="title" type="text"
|
||||
[placeholder]="'addon.mod_workshop.submissiontitle' | translate" formControlName="title">
|
||||
<div [core-mark-required]="true" slot="label">
|
||||
{{ 'addon.mod_workshop.submissiontitle' | translate }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-input name="title" type="text" [placeholder]="'addon.mod_workshop.submissiontitle' | translate"
|
||||
formControlName="title" />
|
||||
</div>
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="textAvailable">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="textRequired">
|
||||
<div [core-mark-required]="textRequired">
|
||||
{{ 'addon.mod_workshop.submissioncontent' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
</ion-label>
|
||||
<core-rich-text-editor [control]="editForm.controls['content']" name="content"
|
||||
[placeholder]="'addon.mod_workshop.submissioncontent' | translate" [component]="component" [componentId]="componentId"
|
||||
|
|
|
@ -100,11 +100,10 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="access.canpublishsubmissions">
|
||||
<ion-label>
|
||||
<ion-toggle formControlName="published">
|
||||
<p class="item-heading">{{ 'addon.mod_workshop.publishsubmission' | translate }}</p>
|
||||
<p>{{ 'addon.mod_workshop.publishsubmission_help' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle formControlName="published" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap">
|
||||
|
@ -114,9 +113,9 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">{{ 'addon.mod_workshop.gradeover' | translate }}</ion-label>
|
||||
<ion-select formControlName="grade" interface="action-sheet" [cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'addon.mod_workshop.gradeover' | translate}">
|
||||
<ion-select labelPlacement="stacked" formControlName="grade" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'addon.mod_workshop.gradeover' | translate}"
|
||||
[label]="'addon.mod_workshop.gradeover' | translate">
|
||||
<ion-select-option *ngFor="let grade of evaluationGrades" [value]="grade.value">
|
||||
{{grade.label}}
|
||||
</ion-select-option>
|
||||
|
|
|
@ -13,16 +13,15 @@
|
|||
<ion-content>
|
||||
<form name="itemEdit" (ngSubmit)="addNote($event)" #itemEdit>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.notes.publishstate' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="type" name="publishState" interface="popover">
|
||||
<ion-select-option value="personal">{{ 'addon.notes.personalnotes' | translate }}</ion-select-option>
|
||||
<ion-select [(ngModel)]="type" name="publishState" interface="popover" [label]="'addon.notes.publishstate' | translate">
|
||||
<ion-select-option value=" personal">{{ 'addon.notes.personalnotes' | translate }}</ion-select-option>
|
||||
<ion-select-option value="course">{{ 'addon.notes.coursenotes' | translate }}</ion-select-option>
|
||||
<ion-select-option value="site">{{ 'addon.notes.sitenotes' | translate }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label class="sr-only">{{ 'addon.notes.note' | translate }}</ion-label>
|
||||
<ion-textarea placeholder="{{ 'addon.notes.note' | translate }}" rows="5" [(ngModel)]="text" name="text" required="required" />
|
||||
<ion-textarea [attr.aria-label]="'addon.notes.note' | translate" placeholder="{{ 'addon.notes.note' | translate }}" rows="5"
|
||||
[(ngModel)]="text" name="text" required="required" />
|
||||
</ion-item>
|
||||
<div class="ion-padding">
|
||||
<ion-button expand="block" type="submit" [disabled]="processing || text.length < 2">
|
||||
|
|
|
@ -22,26 +22,22 @@
|
|||
<core-loading [hideUntil]="preferencesLoaded">
|
||||
<ion-card>
|
||||
<ion-item class="ion-text-wrap" *ngIf="preferences">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="preferences.enableall" (ngModelChange)="enableAll(preferences.enableall)">
|
||||
<p class="item-heading">{{ 'addon.notifications.allownotifications' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="preferences.enableall" (ngModelChange)="enableAll(preferences.enableall)" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="canChangeSound">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="notificationSound" (ngModelChange)="changeNotificationSound(notificationSound)">
|
||||
<p class="item-heading">{{ 'addon.notifications.playsound' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="notificationSound" (ngModelChange)="changeNotificationSound(notificationSound)" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<ion-card>
|
||||
<ion-item class="ion-text-wrap only-links" *ngIf="preferences?.processors?.length" lines="none" [button]="false">
|
||||
<ion-label class="addon-notification-type-form">
|
||||
<p class="item-heading">{{ 'addon.notifications.typeofnotification' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-item class="ion-text-wrap addon-notification-type-form" *ngIf="preferences?.processors?.length" lines="none">
|
||||
<!-- Show processor selector. -->
|
||||
<ion-select [(ngModel)]="currentProcessorName" (ionChange)="changeProcessor($event)" interface="popover">
|
||||
<p class="item-heading" slot="label">{{ 'addon.notifications.typeofnotification' | translate }}</p>
|
||||
<ion-select-option class="ion-text-wrap" *ngFor="let processor of preferences?.processors" [value]="processor.name">
|
||||
{{ processor.displayname }}
|
||||
</ion-select-option>
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
<ion-radio-group [(ngModel)]="question.behaviourCertaintySelected" [name]="question.behaviourCertaintyOptions[0].name">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let option of question.behaviourCertaintyOptions">
|
||||
<ion-label>{{ option.text }}</ion-label>
|
||||
<ion-radio slot="end" id="{{option.id}}" [value]="option.value" [disabled]="option.disabled" />
|
||||
<ion-radio id="{{option.id}}" [value]="option.value" [disabled]="option.disabled">
|
||||
{{ option.text }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
|
||||
|
|
|
@ -12,18 +12,16 @@
|
|||
</ng-container>
|
||||
|
||||
<ion-item *ngIf="question.input" class="ion-text-wrap core-{{question.input.correctIconColor}}-item">
|
||||
<ion-label position="stacked">{{ 'addon.mod_quiz.answercolon' | translate }}</ion-label>
|
||||
|
||||
<div class="flex-row">
|
||||
<div class="flex-row ion-align-items-end">
|
||||
<!-- Display unit select before the answer input. -->
|
||||
<ng-container *ngIf="question.select && question.selectFirst">
|
||||
<ng-container *ngTemplateOutlet="selectUnits" />
|
||||
</ng-container>
|
||||
|
||||
<!-- Input to enter the answer. -->
|
||||
<ion-input type="text" [attr.name]="question.input.name"
|
||||
<ion-input labelPlacement="stacked" type="text" [attr.name]="question.input.name"
|
||||
[placeholder]="question.input.readOnly ? '' : 'core.question.answer' | translate" [value]="question.input.value"
|
||||
[disabled]="question.input.readOnly" autocorrect="off" />
|
||||
[disabled]="question.input.readOnly" autocorrect="off" [label]="'addon.mod_quiz.answercolon' | translate" />
|
||||
|
||||
<!-- Display unit select after the answer input. -->
|
||||
<ng-container *ngIf="question.select && !question.selectFirst">
|
||||
|
@ -58,9 +56,10 @@
|
|||
<ng-template #radioUnits>
|
||||
<ion-radio-group [(ngModel)]="question!.unit" [name]="question!.optionsName">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let option of question!.options">
|
||||
<ion-label>{{ option.text }}</ion-label>
|
||||
<ion-radio slot="end" [value]="option.value" [disabled]="option.disabled || question!.input?.readOnly"
|
||||
[color]="question!.input?.correctIconColor" />
|
||||
<ion-radio [value]="option.value" [disabled]="option.disabled || question!.input?.readOnly"
|
||||
[color]="question!.input?.correctIconColor">
|
||||
{{ option.text }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<!-- ion-radio doesn't use an input. Create a hidden input to hold the selected value. -->
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.flex-row {
|
||||
width: 100%;
|
||||
}
|
|
@ -22,6 +22,7 @@ import { AddonModQuizCalculatedQuestion, CoreQuestionBaseComponent } from '@feat
|
|||
@Component({
|
||||
selector: 'addon-qtype-calculated',
|
||||
templateUrl: 'addon-qtype-calculated.html',
|
||||
styleUrls: ['calculated.scss'],
|
||||
})
|
||||
export class AddonQtypeCalculatedComponent extends CoreQuestionBaseComponent<AddonModQuizCalculatedQuestion> {
|
||||
|
||||
|
|
|
@ -11,19 +11,18 @@
|
|||
<ng-container *ngIf="!review">
|
||||
<!-- Textarea. -->
|
||||
<ion-item *ngIf="question.textarea && (!question.hasDraftFiles || uploadFilesSupported)">
|
||||
<ion-label class="sr-only">{{ 'core.question.answer' | translate }}</ion-label>
|
||||
<!-- "Format" and draftid hidden inputs -->
|
||||
<input *ngIf="question.formatInput" type="hidden" [name]="question.formatInput.name" [value]="question.formatInput.value">
|
||||
<input *ngIf="question.answerDraftIdInput" type="hidden" [name]="question.answerDraftIdInput.name"
|
||||
[value]="question.answerDraftIdInput.value">
|
||||
<!-- Plain text textarea. -->
|
||||
<ion-textarea *ngIf="question.isPlainText" class="core-question-textarea" [ngClass]='{"core-monospaced": question.isMonospaced}'
|
||||
placeholder="{{ 'core.question.answer' | translate }}" [attr.name]="question.textarea.name"
|
||||
[ngModel]="question.textarea.text" />
|
||||
<ion-textarea *ngIf="question.isPlainText" [attr.aria-label]="'core.question.answer' | translate" class="core-question-textarea"
|
||||
[ngClass]='{"core-monospaced": question.isMonospaced}' placeholder="{{ 'core.question.answer' | translate }}"
|
||||
[attr.name]="question.textarea.name" [ngModel]="question.textarea.text" />
|
||||
<!-- Rich text editor. -->
|
||||
<core-rich-text-editor *ngIf="!question.isPlainText" placeholder="{{ 'core.question.answer' | translate }}"
|
||||
[control]="formControl" [name]="question.textarea.name" [component]="component" [componentId]="componentId"
|
||||
[autoSave]="false" />
|
||||
<core-rich-text-editor *ngIf="!question.isPlainText" [attr.aria-label]="'core.question.answer' | translate"
|
||||
placeholder="{{ 'core.question.answer' | translate }}" [control]="formControl" [name]="question.textarea.name"
|
||||
[component]="component" [componentId]="componentId" [autoSave]="false" />
|
||||
</ion-item>
|
||||
|
||||
<!-- Draft files not supported. -->
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let row of question.rows">
|
||||
<ion-label>
|
||||
<core-format-text id="addon-qtype-match-question-{{row.id}}" [component]="component" [componentId]="componentId"
|
||||
[text]="row.text" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
<label class="accesshide" for="{{row.id}}" *ngIf="row.accessibilityLabel">
|
||||
{{ row.accessibilityLabel }}
|
||||
</label>
|
||||
</ion-label>
|
||||
<ion-select id="{{row.id}}" [name]="row.name" [(ngModel)]="row.selected" interface="action-sheet"
|
||||
[attr.aria-labelledby]="'addon-qtype-match-question-' + row.id" [disabled]="row.disabled"
|
||||
|
||||
<ion-select id="{{row.id}}" [name]="row.name" [(ngModel)]="row.selected" interface="action-sheet" [disabled]="row.disabled"
|
||||
[cancelText]="'core.cancel' | translate"
|
||||
[ngClass]="{'addon-qtype-match-correct': row.isCorrect === 1,'addon-qtype-match-incorrect': row.isCorrect === 0}">
|
||||
<div slot="label">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="row.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
<label class="accesshide" for="{{row.id}}" *ngIf="row.accessibilityLabel">
|
||||
{{ row.accessibilityLabel }}
|
||||
</label>
|
||||
</div>
|
||||
<ion-select-option *ngFor="let option of row.options" [value]="option.value">
|
||||
{{option.label}}
|
||||
</ion-select-option>
|
||||
|
|
|
@ -16,21 +16,21 @@
|
|||
<!-- Checkbox for multiple choice. -->
|
||||
<ng-container *ngIf="question.multi">
|
||||
<ion-item class="ion-text-wrap answer" *ngFor="let option of question.options">
|
||||
<ion-label [color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")' [class]="option.class">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
<div *ngIf="option.feedback" class="specificfeedback">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
<ion-checkbox [attr.name]="option.name" [(ngModel)]="option.checked" [disabled]="option.disabled"
|
||||
[color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")'>
|
||||
<div [class]="option.class">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
<div *ngIf="option.feedback" class="specificfeedback">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
</div>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ion-checkbox>
|
||||
|
||||
<ion-checkbox slot="end" [attr.name]="option.name" [(ngModel)]="option.checked" [disabled]="option.disabled"
|
||||
[color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")' />
|
||||
|
||||
<ion-icon *ngIf="option.isCorrect === 1" class="core-correct-icon" name="fas-check" color="success"
|
||||
<ion-icon slot="end" *ngIf="option.isCorrect === 1" class="core-correct-icon" name="fas-check" color="success"
|
||||
[attr.aria-label]="'core.question.correct' | translate" />
|
||||
<ion-icon *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-xmark" color="danger"
|
||||
<ion-icon slot="end" *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-xmark" color="danger"
|
||||
[attr.aria-label]="'core.question.incorrect' | translate" />
|
||||
|
||||
<!-- ion-checkbox doesn't use an input. Create a hidden input to hold the value. -->
|
||||
|
@ -41,21 +41,21 @@
|
|||
<!-- Radio buttons for single choice. -->
|
||||
<ion-radio-group *ngIf="!question.multi" [(ngModel)]="question.singleChoiceModel" [name]="question.optionsName">
|
||||
<ion-item class="ion-text-wrap answer" *ngFor="let option of question.options">
|
||||
<ion-label [class]="option.class">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
<div *ngIf="option.feedback" class="specificfeedback">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
<ion-radio [value]="option.value" [disabled]="option.disabled"
|
||||
[color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")'>
|
||||
<div [class]="option.class">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
<div *ngIf="option.feedback" class="specificfeedback">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
|
||||
</div>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ion-radio>
|
||||
|
||||
<ion-radio [value]="option.value" [disabled]="option.disabled" slot="end"
|
||||
[color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")' />
|
||||
|
||||
<ion-icon *ngIf="option.isCorrect === 1" class="core-correct-icon" name="fas-check" color="success"
|
||||
<ion-icon slot="end" *ngIf="option.isCorrect === 1" class="core-correct-icon" name="fas-check" color="success"
|
||||
[attr.aria-label]="'core.question.correct' | translate" />
|
||||
<ion-icon *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-xmark" color="danger"
|
||||
<ion-icon slot="end" *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-xmark" color="danger"
|
||||
[attr.aria-label]="'core.question.incorrect' | translate" />
|
||||
</ion-item>
|
||||
<ion-button *ngIf="!question.disabled" class="ion-text-wrap ion-margin-top" expand="block" fill="outline"
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
</ion-item>
|
||||
<ion-item *ngIf="question.input && !question.input.isInline"
|
||||
class="ion-text-wrap addon-qtype-shortanswer-input core-{{question.input.correctIconColor}}-item">
|
||||
<ion-label position="stacked">{{ 'addon.mod_quiz.answercolon' | translate }}</ion-label>
|
||||
<ion-input type="text" [placeholder]="question.input.readOnly ? '' : 'core.question.answer' | translate"
|
||||
[attr.name]="question.input.name" [value]="question.input.value" autocorrect="off" [disabled]="question.input.readOnly" />
|
||||
<ion-input labelPlacement="stacked" type="text" [placeholder]="question.input.readOnly ? '' : 'core.question.answer' | translate"
|
||||
[attr.name]="question.input.name" [value]="question.input.value" autocorrect="off" [disabled]="question.input.readOnly"
|
||||
[label]="'addon.mod_quiz.answercolon' | translate" />
|
||||
<ion-icon *ngIf="question.input.correctIcon" class="core-correct-icon" slot="end" [name]="question.input.correctIcon"
|
||||
[color]="[question.input.correctIconColor]" />
|
||||
</ion-item>
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname && form" [formGroup]="form">
|
||||
<ion-label>
|
||||
<span class="label-text" [core-mark-required]="required">
|
||||
<ion-checkbox [formControlName]="modelName" labelPlacement="start" justify="space-between">
|
||||
<span [core-mark-required]="required">
|
||||
<core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
|
||||
[courseId]="courseId" [wsNotFiltered]="true" />
|
||||
</span>
|
||||
<core-input-errors [control]="form.controls[modelName]" />
|
||||
</ion-label>
|
||||
<ion-checkbox item-end [formControlName]="modelName" />
|
||||
</ion-checkbox>
|
||||
<core-input-errors [control]="form.controls[modelName]" />
|
||||
</ion-item>
|
||||
|
|
|
@ -14,14 +14,12 @@
|
|||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname && form" class="ion-text-wrap" [formGroup]="form">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="required">
|
||||
<ion-select labelPlacement="stacked" [formControlName]="modelName" [placeholder]="'core.choosedots' | translate"
|
||||
interface="action-sheet" [cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: field.name}">
|
||||
<div [core-mark-required]="required" slot="label">
|
||||
<core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
|
||||
[courseId]="courseId" [wsNotFiltered]="true" />
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-select [formControlName]="modelName" [placeholder]="'core.choosedots' | translate" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: field.name}">
|
||||
</div>
|
||||
<ion-select-option value="">{{ 'core.choosedots' | translate }}</ion-select-option>
|
||||
<ion-select-option *ngFor="let option of options" [value]="option">{{option}}</ion-select-option>
|
||||
</ion-select>
|
||||
|
|
|
@ -14,12 +14,11 @@
|
|||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname && form" class="ion-text-wrap" [formGroup]="form">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="required">
|
||||
<ion-input labelPlacement="stacked" type="text" [formControlName]="modelName" [placeholder]="field.name">
|
||||
<div [core-mark-required]="required" slot="label">
|
||||
<core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
|
||||
[courseId]="courseId" [wsNotFiltered]="true" />
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-input type="text" [formControlName]="modelName" [placeholder]="field.name" />
|
||||
</div>
|
||||
</ion-input>
|
||||
<core-input-errors [control]="form.controls[modelName]" />
|
||||
</ion-item>
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname && form" class="ion-text-wrap" [formGroup]="form">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="required">
|
||||
<ion-input labelPlacement="stacked" [type]="inputType" [formControlName]="modelName" [placeholder]="field.name"
|
||||
maxlength="{{maxLength}}">
|
||||
<div [core-mark-required]="required" slot="label">
|
||||
<core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
|
||||
[courseId]="courseId" [wsNotFiltered]="true" />
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-input [type]="inputType" [formControlName]="modelName" [placeholder]="field.name" maxlength="{{maxLength}}" />
|
||||
</div>
|
||||
</ion-input>
|
||||
<core-input-errors [control]="form.controls[modelName]" />
|
||||
</ion-item>
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
<core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
|
||||
[courseId]="courseId" [wsNotFiltered]="true" />
|
||||
</span>
|
||||
<core-input-errors [control]="control" />
|
||||
</ion-label>
|
||||
<core-rich-text-editor [control]="control" [placeholder]="field.name" [autoSave]="true" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [elementId]="modelName" />
|
||||
<core-input-errors [control]="control" />
|
||||
</ion-item>
|
||||
|
|
|
@ -59,7 +59,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
ngOnInit(): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const win = <any> window;
|
||||
CoreDomUtils.toggleModeClass('ionic5', true, { includeLegacy: true });
|
||||
CoreDomUtils.toggleModeClass('ionic7', true, { includeLegacy: true });
|
||||
CoreDomUtils.toggleModeClass('development', CoreConstants.BUILD.isDevelopment);
|
||||
this.addVersionClass(MOODLEAPP_VERSION_PREFIX, CoreConstants.CONFIG.versionname.replace('-dev', ''));
|
||||
|
||||
CoreEvents.on(CoreEvents.LOGOUT, async () => {
|
||||
|
|
|
@ -51,7 +51,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|||
})
|
||||
export class CoreComboboxComponent implements ControlValueAccessor {
|
||||
|
||||
@ViewChild(IonSelect) select!: IonSelect;
|
||||
@ViewChild(IonSelect) select?: IonSelect;
|
||||
|
||||
@Input() interface: 'popover' | 'modal' = 'popover';
|
||||
@Input() label = Translate.instant('core.show'); // Aria label.
|
||||
|
@ -118,7 +118,7 @@ export class CoreComboboxComponent implements ControlValueAccessor {
|
|||
async openSelect(event?: UIEvent): Promise<void> {
|
||||
this.touch();
|
||||
|
||||
if (this.interface == 'modal') {
|
||||
if (this.interface === 'modal') {
|
||||
if (this.expanded || !this.modalOptions) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -7,12 +7,9 @@
|
|||
</ion-card>
|
||||
|
||||
<ion-item class="ion-text-wrap core-group-selector">
|
||||
<ion-label>
|
||||
<ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container>
|
||||
<ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="selected" (ionChange)="selectedChange.emit(selected)" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.group' | translate}">
|
||||
<ion-select [label]="(groupInfo.separateGroups ? 'core.groupsseparate': 'core.groupsvisible') | translate" [(ngModel)]=" selected"
|
||||
(ionChange)="selectedChange.emit(selected)" interface="action-sheet" [cancelText]="'core.cancel' | translate"
|
||||
[interfaceOptions]="{header: 'core.group' | translate}">
|
||||
<ion-select-option *ngFor="let group of groupInfo.groups" [value]=" group.id">
|
||||
<core-format-text [text]="group.name" contextLevel="course" [contextInstanceId]="courseId" [wsNotFiltered]="true" />
|
||||
</ion-select-option>
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
<div class="core-input-error-container" role="alert" *ngIf="(control && control.dirty && !control.valid) || errorText">
|
||||
<ng-container *ngIf="control && control.dirty && !control.valid">
|
||||
<ng-container *ngFor="let error of errorKeys">
|
||||
<div *ngIf="control.hasError(error)" class="core-input-error">
|
||||
<span *ngIf="errorMessages && errorMessages[error]">{{errorMessages[error]}}</span>
|
||||
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'max' && control.errors?.max">
|
||||
{{ 'core.login.invalidvaluemax' | translate:{$a: control.errors!.max.max} }}
|
||||
</span>
|
||||
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'min' && control.errors?.min">
|
||||
{{ 'core.login.invalidvaluemin' | translate:{$a: control.errors!.min.min} }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="control && control.dirty && !control.valid">
|
||||
<ng-container *ngFor="let error of errorKeys">
|
||||
<div *ngIf="control.hasError(error)" class="core-input-error">
|
||||
<span *ngIf="errorMessages && errorMessages[error]">{{ errorMessages[error] | translate }}</span>
|
||||
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'max' && control.errors?.max">
|
||||
{{ 'core.login.invalidvaluemax' | translate:{$a: control.errors!.max.max} }}
|
||||
</span>
|
||||
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'min' && control.errors?.min">
|
||||
{{ 'core.login.invalidvaluemin' | translate:{$a: control.errors!.min.min} }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div *ngIf="errorText" class="core-input-error" aria-live="assertive">{{ errorText }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div *ngIf="errorText" class="core-input-error" aria-live="assertive">{{ errorText }}</div>
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
:host {
|
||||
display: contents;
|
||||
.core-input-error-container {
|
||||
&.has-errors {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.core-input-error {
|
||||
padding: 4px;
|
||||
color: var(--danger);
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
.core-input-error {
|
||||
padding: 4px;
|
||||
color: var(--danger);
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
|
||||
&:first-child {
|
||||
display: block;
|
||||
}
|
||||
&:first-child {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
|
||||
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Translate } from '@singletons';
|
||||
|
||||
/**
|
||||
* Component to show errors if an input isn't valid.
|
||||
|
@ -30,8 +29,7 @@ import { Translate } from '@singletons';
|
|||
* Example usage:
|
||||
*
|
||||
* <ion-item class="ion-text-wrap">
|
||||
* <ion-label stacked core-mark-required="true">{{ 'core.login.username' | translate }}</ion-label>
|
||||
* <ion-input type="text" name="username" formControlName="username"></ion-input>
|
||||
* <ion-input type="text" name="username" formControlName="username" required="true"></ion-input>
|
||||
* <core-input-errors [control]="myForm.controls.username" [errorMessages]="usernameErrors"></core-input-errors>
|
||||
* </ion-item>
|
||||
*/
|
||||
|
@ -40,43 +38,87 @@ import { Translate } from '@singletons';
|
|||
templateUrl: 'core-input-errors.html',
|
||||
styleUrls: ['input-errors.scss'],
|
||||
})
|
||||
export class CoreInputErrorsComponent implements OnChanges {
|
||||
export class CoreInputErrorsComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input() control?: FormControl;
|
||||
@Input() errorMessages?: Record<string, string>;
|
||||
@Input() errorText?: string; // Set other non automatic errors.
|
||||
@Input() control?: FormControl; // Needed to be able to check the validity of the input.
|
||||
@Input() errorMessages: Record<string, string> = {}; // Error messages to show. Keys must be the name of the error.
|
||||
@Input() errorText = ''; // Set other non automatic errors.
|
||||
errorKeys: string[] = [];
|
||||
|
||||
protected hostElement: HTMLElement;
|
||||
|
||||
@HostBinding('class.has-errors')
|
||||
get hasErrors(): boolean {
|
||||
return (this.control && this.control.dirty && !this.control.valid) || !!this.errorText;
|
||||
}
|
||||
|
||||
@HostBinding('role') role = 'alert';
|
||||
|
||||
constructor(
|
||||
element: ElementRef,
|
||||
) {
|
||||
this.hostElement = element.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize some common errors if they aren't set.
|
||||
*/
|
||||
protected initErrorMessages(): void {
|
||||
this.errorMessages = this.errorMessages || {};
|
||||
this.errorMessages = {
|
||||
required: this.errorMessages.required || 'core.required',
|
||||
email: this.errorMessages.email || 'core.login.invalidemail',
|
||||
date: this.errorMessages.date || 'core.login.invaliddate',
|
||||
datetime: this.errorMessages.datetime || 'core.login.invaliddate',
|
||||
datetimelocal: this.errorMessages.datetimelocal || 'core.login.invaliddate',
|
||||
time: this.errorMessages.time || 'core.login.invalidtime',
|
||||
url: this.errorMessages.url || 'core.login.invalidurl',
|
||||
// Set empty values by default, the default error messages will be built in the template when needed.
|
||||
max: this.errorMessages.max || '',
|
||||
min: this.errorMessages.min || '',
|
||||
};
|
||||
|
||||
this.errorMessages.required = this.errorMessages.required || Translate.instant('core.required');
|
||||
this.errorMessages.email = this.errorMessages.email || Translate.instant('core.login.invalidemail');
|
||||
this.errorMessages.date = this.errorMessages.date || Translate.instant('core.login.invaliddate');
|
||||
this.errorMessages.datetime = this.errorMessages.datetime || Translate.instant('core.login.invaliddate');
|
||||
this.errorMessages.datetimelocal = this.errorMessages.datetimelocal || Translate.instant('core.login.invaliddate');
|
||||
this.errorMessages.time = this.errorMessages.time || Translate.instant('core.login.invalidtime');
|
||||
this.errorMessages.url = this.errorMessages.url || Translate.instant('core.login.invalidurl');
|
||||
this.errorMessages.requiredTrue = this.errorMessages.required;
|
||||
|
||||
// Set empty values by default, the default error messages will be built in the template when needed.
|
||||
this.errorMessages.max = this.errorMessages.max || '';
|
||||
this.errorMessages.min = this.errorMessages.min || '';
|
||||
this.errorKeys = Object.keys(this.errorMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being changed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const parent = this.hostElement.parentElement;
|
||||
let item: HTMLElement | null = null;
|
||||
|
||||
if (parent?.tagName === 'ION-ITEM') {
|
||||
item = parent;
|
||||
|
||||
// Get all elements on the parent and wrap them with a div.
|
||||
// This is needed because otherwise the error message will be shown on the right of the input. Or overflowing the item.
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
wrapper.classList.add('core-input-errors-wrapper');
|
||||
|
||||
Array.from(parent.children).forEach((child) => {
|
||||
if (!child.slot) {
|
||||
wrapper.appendChild(child);
|
||||
}
|
||||
});
|
||||
|
||||
parent.appendChild(wrapper);
|
||||
} else {
|
||||
item = this.hostElement.closest('ion-item');
|
||||
}
|
||||
|
||||
item?.classList.add('has-core-input-errors');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if ((changes.control || changes.errorMessages) && this.control) {
|
||||
this.initErrorMessages();
|
||||
|
||||
this.errorKeys = this.errorMessages ? Object.keys(this.errorMessages) : [];
|
||||
}
|
||||
if (changes.errorText) {
|
||||
this.errorText = changes.errorText.currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,12 +86,3 @@
|
|||
// Implicit Inline.
|
||||
@include inline();
|
||||
}
|
||||
|
||||
:host-context(.limited-width > *):not([slot]),
|
||||
:host-context(.menu > *):not([slot]) {
|
||||
&.core-loading-loaded {
|
||||
--contents-display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
min-height: 100%;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
|
||||
<!-- Form to edit the file's name. -->
|
||||
<ion-input type="text" name="filename" [placeholder]="'core.filename' | translate" autocapitalize="none" autocorrect="off"
|
||||
(click)="$event.stopPropagation()" core-auto-focus [(ngModel)]="newFileName" *ngIf="editMode" />
|
||||
(click)="$event.stopPropagation()" core-auto-focus [(ngModel)]="newFileName" *ngIf="editMode"
|
||||
[attr.aria-label]="'core.filename' | translate" />
|
||||
|
||||
<div class="buttons" slot="end">
|
||||
<ion-button fill="clear" *ngIf="isIOS && !editMode" (click)="openFile($event, true)"
|
||||
|
|
|
@ -26,7 +26,7 @@ import { Translate } from '@singletons';
|
|||
*
|
||||
* This directive should be applied in the label. Example:
|
||||
*
|
||||
* <ion-label core-mark-required="{{field.required}}">{{ 'core.login.username' | translate }}</ion-label>
|
||||
* <p slot="label" [core-mark-required]="true">Username</p>
|
||||
*/
|
||||
@Component({
|
||||
selector: '[core-mark-required]',
|
||||
|
@ -37,41 +37,43 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
|
|||
|
||||
@Input('core-mark-required') coreMarkRequired: boolean | string = true;
|
||||
|
||||
protected element: HTMLElement;
|
||||
requiredLabel?: string;
|
||||
protected hostElement: HTMLElement;
|
||||
requiredLabel = Translate.instant('core.required');
|
||||
|
||||
constructor(
|
||||
element: ElementRef,
|
||||
) {
|
||||
this.element = element.nativeElement;
|
||||
this.hostElement = element.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.requiredLabel = Translate.instant('core.required');
|
||||
this.coreMarkRequired = CoreUtils.isTrueOrOne(this.coreMarkRequired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the view is initialized.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
if (this.coreMarkRequired) {
|
||||
// Add the "required" to the aria-label.
|
||||
const ariaLabel = this.element.getAttribute('aria-label') ||
|
||||
CoreTextUtils.cleanTags(this.element.innerHTML, { singleLine: true });
|
||||
const ariaLabel = this.hostElement.getAttribute('aria-label') ||
|
||||
CoreTextUtils.cleanTags(this.hostElement.innerHTML, { singleLine: true });
|
||||
if (ariaLabel) {
|
||||
this.element.setAttribute('aria-label', ariaLabel + ' ' + this.requiredLabel);
|
||||
this.hostElement.setAttribute('aria-label', ariaLabel + '. ' + this.requiredLabel);
|
||||
}
|
||||
} else {
|
||||
// Remove the "required" from the aria-label.
|
||||
const ariaLabel = this.element.getAttribute('aria-label');
|
||||
const ariaLabel = this.hostElement.getAttribute('aria-label');
|
||||
if (ariaLabel) {
|
||||
this.element.setAttribute('aria-label', ariaLabel.replace(' ' + this.requiredLabel, ''));
|
||||
this.hostElement.setAttribute('aria-label', ariaLabel.replace('. ' + this.requiredLabel, ''));
|
||||
}
|
||||
}
|
||||
|
||||
const input = this.hostElement.closest('ion-input, ion-textarea');
|
||||
input?.setAttribute('required', this.coreMarkRequired ? 'true' : 'false');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
<form (ngSubmit)="submitPassword($event)" #passwordForm>
|
||||
<div>
|
||||
<ion-item>
|
||||
<ion-label class="sr-only">{{ placeholder | translate }}</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input class="ion-text-wrap core-ioninput-password" name="password" type="password"
|
||||
placeholder="{{ placeholder | translate }}" [(ngModel)]="password" core-auto-focus [clearOnEdit]="false" />
|
||||
</core-show-password>
|
||||
<ion-input [attr.aria-label]="placeholder | translate" class="ion-text-wrap core-ioninput-password" name="password"
|
||||
type="password" placeholder="{{ placeholder | translate }}" [(ngModel)]="password" core-auto-focus
|
||||
[clearOnEdit]="false">
|
||||
<core-show-password slot="end" />
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="error" class="ion-text-wrap ion-padding-top text-danger">
|
||||
<core-format-text [text]="error | translate" />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<ng-content></ng-content>
|
||||
<ng-content />
|
||||
<ion-button fill="clear" [attr.aria-label]="(shown ? 'core.hide' : 'core.show') | translate" core-suppress-events (onClick)="toggle($event)"
|
||||
(mousedown)="doNotBlur($event)" (keydown)="doNotBlur($event)" (keyup)="toggle($event)">
|
||||
<ion-icon [name]="shown ? 'fas-eye-slash' : 'fas-eye'" slot="icon-only" aria-hidden="true" />
|
||||
|
|
|
@ -3,30 +3,29 @@
|
|||
:host {
|
||||
display: contents;
|
||||
|
||||
ion-button {
|
||||
// Only applies to deprecated way (surrounding).
|
||||
::ng-deep ion-input + ion-button {
|
||||
background: transparent;
|
||||
padding: 0 calc(var(--padding-start) / 2);
|
||||
position: absolute;
|
||||
@include safe-area-position(null, 0px, null, null);
|
||||
padding: 0 var(--inner-padding-end) 0 4px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
z-index: 3;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
@include safe-area-position(null, 0px, null, null);
|
||||
top: 0;
|
||||
}
|
||||
|
||||
// Only applies to deprecated way (surrounding).
|
||||
::ng-deep ion-input {
|
||||
--padding-end: 56px !important;
|
||||
}
|
||||
|
||||
::ng-deep ion-input.input-label-placement-stacked + ion-button {
|
||||
top: 14px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
::ng-deep ion-input {
|
||||
--padding-end: 47px !important;
|
||||
}
|
||||
|
||||
:host-context(.md.item-label.stacked) ion-button {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
:host-context(.iositem-label.stacked) ion-button {
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
:host-context(.ios) ion-button {
|
||||
bottom: 0;
|
||||
ion-button {
|
||||
z-index: 5;
|
||||
pointer-events: visible;
|
||||
}
|
||||
|
|
|
@ -18,18 +18,31 @@ import { IonInput } from '@ionic/angular';
|
|||
import { CorePlatform } from '@services/platform';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
/**
|
||||
* Component to allow showing and hiding a password. The affected input MUST have a name to identify it.
|
||||
* This component allows to show/hide a password.
|
||||
* It's meant to be used with ion-input.
|
||||
* It's recommended to use it as a slot of the input.
|
||||
*
|
||||
* @description
|
||||
* This directive needs to surround the input with the password.
|
||||
*
|
||||
* You need to supply the name of the input.
|
||||
* There are 2 ways to use ths component:
|
||||
* - Slot it to start or end on the ion-input element.
|
||||
* - Surround the ion-input with the password with this component. This is deprecated.
|
||||
*
|
||||
* Example:
|
||||
* In order to help finding the input you can specify the name of the input or the ion-input element.
|
||||
*
|
||||
* <core-show-password [name]="'password'">
|
||||
*
|
||||
* Example of new usage:
|
||||
*
|
||||
* <ion-input type="password" name="password">
|
||||
* <core-show-password slot="end" />
|
||||
* </ion-input>
|
||||
*
|
||||
* Example deprecated usage:
|
||||
*
|
||||
* <core-show-password>
|
||||
* <ion-input type="password" name="password"></ion-input>
|
||||
* </core-show-password>
|
||||
*/
|
||||
|
@ -40,17 +53,30 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
})
|
||||
export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
||||
|
||||
@Input() name?: string; // Name of the input affected.
|
||||
@Input() initialShown?: boolean | string; // Whether the password should be shown at start.
|
||||
@ContentChild(IonInput) ionInput?: IonInput;
|
||||
|
||||
shown = false; // Whether the password is shown.
|
||||
@Input() name = ''; // Deprecated. Not used anymore.
|
||||
@ContentChild(IonInput) ionInput?: IonInput | HTMLIonInputElement; // Deprecated. Use slot instead.
|
||||
|
||||
protected input?: HTMLInputElement; // Input affected.
|
||||
protected element: HTMLElement; // Current element.
|
||||
protected input?: HTMLInputElement;
|
||||
protected hostElement: HTMLElement;
|
||||
protected logger: CoreLogger;
|
||||
|
||||
constructor(element: ElementRef) {
|
||||
this.element = element.nativeElement;
|
||||
this.hostElement = element.nativeElement;
|
||||
this.logger = CoreLogger.getInstance('CoreShowPasswordComponent');
|
||||
}
|
||||
|
||||
get shown(): boolean {
|
||||
return this.input?.type === 'text';
|
||||
}
|
||||
|
||||
set shown(shown: boolean) {
|
||||
if (!this.input) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.input.type = shown ? 'text' : 'password';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,28 +90,12 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
* @inheritdoc
|
||||
*/
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
if (this.ionInput) {
|
||||
try {
|
||||
// It's an ion-input, use it to get the native element.
|
||||
this.input = await this.ionInput.getInputElement();
|
||||
this.setData(this.input);
|
||||
} catch (error) {
|
||||
// This should never fail, but it does in some testing environment because Ionic elements are not
|
||||
// rendered properly. So in case this fails, we'll just ignore the error.
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Search the input.
|
||||
this.input = this.element.querySelector<HTMLInputElement>('input[name="' + this.name + '"]') ?? undefined;
|
||||
await this.setInputElement();
|
||||
|
||||
if (!this.input) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData(this.input);
|
||||
|
||||
// By default, don't autocapitalize and autocorrect.
|
||||
if (!this.input.getAttribute('autocorrect')) {
|
||||
this.input.setAttribute('autocorrect', 'off');
|
||||
|
@ -96,12 +106,33 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set label, icon name and input type.
|
||||
*
|
||||
* @param input The input element.
|
||||
* Set the input element to affect.
|
||||
*/
|
||||
protected setData(input: HTMLInputElement): void {
|
||||
input.type = this.shown ? 'text' : 'password';
|
||||
protected async setInputElement(): Promise<void> {
|
||||
if (!this.ionInput) {
|
||||
this.ionInput = this.hostElement.closest('ion-input') ?? undefined;
|
||||
|
||||
this.hostElement.setAttribute('slot', 'end');
|
||||
} else {
|
||||
// It's outside ion-input, warn devs.
|
||||
this.logger.warn('Deprecated CoreShowPasswordComponent usage, it\'s not needed to surround ion-input anymore.');
|
||||
}
|
||||
|
||||
if (!this.ionInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.input = await this.ionInput.getInputElement();
|
||||
} catch {
|
||||
// This should never fail, but it does in some testing environment because Ionic elements are not
|
||||
// rendered properly. So in case this fails it will try to find through the name and ignore the error.
|
||||
const name = this.ionInput.name;
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
this.input = this.hostElement.querySelector<HTMLInputElement>('input[name="' + name + '"]') ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,7 +141,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
* @param event The mouse event.
|
||||
*/
|
||||
toggle(event: Event): void {
|
||||
if (event.type == 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
|
||||
if (event.type === 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -120,13 +151,8 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
const isFocused = document.activeElement === this.input;
|
||||
this.shown = !this.shown;
|
||||
|
||||
if (!this.input) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData(this.input);
|
||||
// In Android, the keyboard is closed when the input type changes. Focus it again.
|
||||
if (isFocused && CorePlatform.isAndroid()) {
|
||||
if (this.input && isFocused && CorePlatform.isAndroid()) {
|
||||
CoreDomUtils.focusElement(this.input);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<ion-item *ngIf="sites && sites.length">
|
||||
<ion-label>{{ 'core.site' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="selectedSite" (ngModelChange)="siteSelected.emit(selectedSite)" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.site' | translate}">
|
||||
<ion-select [label]="'core.site' | translate" [(ngModel)]="selectedSite" (ngModelChange)="siteSelected.emit(selectedSite)"
|
||||
interface="action-sheet" [cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.site' | translate}">
|
||||
<ion-select-option *ngFor="let site of sites" [value]="site.id">{{ site.fullNameAndSiteName }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
|
|
@ -426,6 +426,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
const data = await CoreDomUtils.openModal<CoreCourseIndexSectionWithModule>({
|
||||
component: CoreCourseCourseIndexComponent,
|
||||
initialBreakpoint: 1,
|
||||
breakpoints: [0, 1],
|
||||
componentProps: {
|
||||
course: this.course,
|
||||
sections: this.sections,
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
</div>
|
||||
|
||||
<ion-textarea [hidden]="rteEnabled" #textarea class="core-textarea" role="textbox" [attr.name]="name" ngControl="control"
|
||||
[placeholder]="placeholder" (ionChange)="onChange()" (ionFocus)="showToolbar($event)" (ionBlur)="hideToolbar($event)" />
|
||||
[placeholder]="placeholder" [attr.aria-labelledby]="ariaLabelledBy" (ionChange)="onChange()" (ionFocus)="showToolbar($event)"
|
||||
(ionBlur)="hideToolbar($event)" />
|
||||
|
||||
<div class="core-rte-info-message" *ngIf="infoMessage">
|
||||
<ion-icon name="fas-circle-info" aria-hidden="true" />
|
||||
|
|
|
@ -231,11 +231,18 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
|||
return;
|
||||
}
|
||||
|
||||
const updateArialabelledBy = () => this.ariaLabelledBy = label.getAttribute('id') ?? undefined;
|
||||
const updateArialabelledBy = () => {
|
||||
this.ariaLabelledBy = label.getAttribute('id') ?? undefined;
|
||||
};
|
||||
|
||||
this.labelObserver = new MutationObserver(updateArialabelledBy);
|
||||
this.labelObserver.observe(label, { attributes: true, attributeFilter: ['id'] });
|
||||
|
||||
// Usually the label won't have an id, so we need to add one.
|
||||
if (!label.getAttribute('id')) {
|
||||
label.setAttribute('id', 'rte-'+CoreUtils.getUniqueId('CoreEditorRichTextEditor'));
|
||||
}
|
||||
|
||||
updateArialabelledBy();
|
||||
}
|
||||
|
||||
|
|
|
@ -18,15 +18,9 @@
|
|||
|
||||
ion-button.core-button-as-link {
|
||||
--color: var(--core-login-text-color);
|
||||
text-decoration-color: var(--core-login-text-color);
|
||||
|
||||
ion-label {
|
||||
color: var(--core-login-text-color);
|
||||
}
|
||||
text-decoration-color: var(--color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.core-login-reconnect-warning {
|
||||
margin: 0px 0px 32px 0px;
|
||||
}
|
||||
|
|
|
@ -41,19 +41,17 @@
|
|||
|
||||
<div class="core-login-methods">
|
||||
<form [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #credentialsForm *ngIf="!isBrowserSSO">
|
||||
<ion-item>
|
||||
<ion-label class="sr-only">{{ 'core.login.username' | translate }}</ion-label>
|
||||
<ion-item lines="inset">
|
||||
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}"
|
||||
formControlName="username" autocapitalize="none" autocorrect="off" autocomplete="username" enterkeyhint="next"
|
||||
required="true" />
|
||||
required="true" [attr.aria-label]="'core.login.username' | translate " />
|
||||
</ion-item>
|
||||
<ion-item class="ion-margin-bottom">
|
||||
<ion-label class="sr-only">{{ 'core.login.password' | translate }}</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
|
||||
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go"
|
||||
required="true" />
|
||||
</core-show-password>
|
||||
<ion-item class="ion-margin-bottom" lines="inset">
|
||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
|
||||
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go"
|
||||
required="true" [attr.aria-label]="'core.login.password' | translate ">
|
||||
<core-show-password slot="end" />
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-button expand="block" type="submit" [disabled]="!credForm.valid"
|
||||
class="ion-margin core-login-login-button ion-text-wrap">
|
||||
|
|
|
@ -43,18 +43,17 @@
|
|||
</ion-item-divider>
|
||||
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">{{ 'core.whatisyourage' | translate }}</span>
|
||||
</ion-label>
|
||||
<ion-input type="number" name="age" placeholder="0" formControlName="age" autocapitalize="none" autocorrect="off" />
|
||||
<ion-input labelPlacement="stacked" type="number" name="age" placeholder="0" formControlName="age"
|
||||
autocapitalize="none" autocorrect="off">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'core.whatisyourage' | translate }}</div>
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">{{ 'core.wheredoyoulive' | translate }}</span>
|
||||
</ion-label>
|
||||
<ion-select name="country" formControlName="country" [cancelText]="'core.cancel' | translate"
|
||||
[okText]="'core.ok' | translate" [placeholder]="'core.login.selectacountry' | translate">
|
||||
<ion-select labelPlacement="stacked" name="country" formControlName="country"
|
||||
[cancelText]="'core.cancel' | translate" [okText]="'core.ok' | translate"
|
||||
[placeholder]="'core.login.selectacountry' | translate">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'core.wheredoyoulive' | translate }}</div>
|
||||
<ion-select-option value="">{{ 'core.login.selectacountry' | translate }}</ion-select-option>
|
||||
<ion-select-option *ngFor="let country of countries" [value]="country.code">{{country.name}}</ion-select-option>
|
||||
</ion-select>
|
||||
|
@ -97,21 +96,20 @@
|
|||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">{{ 'core.login.username' | translate }}</span>
|
||||
</ion-label>
|
||||
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}"
|
||||
formControlName="username" autocapitalize="none" autocorrect="off" />
|
||||
<ion-input labelPlacement="stacked" type="text" name="username"
|
||||
placeholder="{{ 'core.login.username' | translate }}" formControlName="username" autocapitalize="none"
|
||||
autocorrect="off">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'core.login.username' | translate }}</div>
|
||||
</ion-input>
|
||||
<core-input-errors [control]="signupForm.controls.username" [errorMessages]="usernameErrors" />
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">{{ 'core.login.password' | translate }}</span>
|
||||
</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
|
||||
formControlName="password" [clearOnEdit]="false" autocomplete="new-password" required="true" />
|
||||
</core-show-password>
|
||||
<ion-input labelPlacement="stacked" name="password" type="password"
|
||||
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
||||
autocomplete="new-password" required="true">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'core.login.password' | translate }}</div>
|
||||
<core-show-password slot="end" />
|
||||
</ion-input>
|
||||
<p *ngIf="settings.passwordpolicy" class="core-input-footnote">
|
||||
{{settings.passwordpolicy}}
|
||||
</p>
|
||||
|
@ -125,38 +123,35 @@
|
|||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">{{ 'core.user.email' | translate }}</span>
|
||||
</ion-label>
|
||||
<ion-input type="email" name="email" placeholder="{{ 'core.user.email' | translate }}" formControlName="email"
|
||||
autocapitalize="none" autocorrect="off" />
|
||||
<ion-input labelPlacement="stacked" type="email" name="email" placeholder="{{ 'core.user.email' | translate }}"
|
||||
formControlName="email" autocapitalize="none" autocorrect="off">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'core.user.email' | translate }}</div>
|
||||
</ion-input>
|
||||
<core-input-errors [control]="signupForm.controls.email" [errorMessages]="emailErrors" />
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">{{ 'core.user.emailagain' | translate }}</span>
|
||||
</ion-label>
|
||||
<ion-input type="email" name="email2" placeholder="{{ 'core.user.emailagain' | translate }}" autocapitalize="none"
|
||||
formControlName="email2" autocorrect="off" [pattern]="escapeMail(signupForm.controls.email.value)" />
|
||||
<ion-input labelPlacement="stacked" type="email" name="email2"
|
||||
placeholder="{{ 'core.user.emailagain' | translate }}" autocapitalize="none" formControlName="email2"
|
||||
autocorrect="off" [pattern]="escapeMail(signupForm.controls.email.value)">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'core.user.emailagain' | translate }}</div>
|
||||
</ion-input>
|
||||
<core-input-errors [control]="signupForm.controls.email2" [errorMessages]="email2Errors" />
|
||||
</ion-item>
|
||||
<ion-item *ngFor="let nameField of settings.namefields" class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">{{ 'core.user.' + nameField | translate }}</span>
|
||||
</ion-label>
|
||||
<ion-input type="text" [name]="nameField" placeholder="{{ 'core.user.' + nameField | translate }}"
|
||||
formControlName="{{nameField}}" autocorrect="off" />
|
||||
<ion-input labelPlacement="stacked" type="text" [name]="nameField" formControlName="{{nameField}}" autocorrect="off"
|
||||
[placeholder]="'core.user.' + nameField | translate">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'core.user.' + nameField | translate }}</div>
|
||||
</ion-input>
|
||||
<core-input-errors [control]="signupForm.controls[nameField]" [errorMessages]="namefieldsErrors![nameField]" />
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">{{ 'core.user.city' | translate }}</ion-label>
|
||||
<ion-input type="text" name="city" placeholder="{{ 'core.user.city' | translate }}" formControlName="city"
|
||||
autocorrect="off" />
|
||||
<ion-input labelPlacement="stacked" type="text" name="city" placeholder="{{ 'core.user.city' | translate }}"
|
||||
formControlName="city" autocorrect="off" [label]="'core.user.city' | translate" />
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">{{ 'core.user.country' | translate }}</ion-label>
|
||||
<ion-select name="country" formControlName="country" [placeholder]="'core.login.selectacountry' | translate"
|
||||
[cancelText]="'core.cancel' | translate" [okText]="'core.ok' | translate">
|
||||
<ion-select labelPlacement="stacked" name="country" formControlName="country"
|
||||
[placeholder]="'core.login.selectacountry' | translate" [cancelText]="'core.cancel' | translate"
|
||||
[okText]="'core.ok' | translate" [label]="'core.user.country' | translate">
|
||||
<ion-select-option value="">{{ 'core.login.selectacountry' | translate }}</ion-select-option>
|
||||
<ion-select-option *ngFor="let country of countries" [value]="country.code">{{country.name}}</ion-select-option>
|
||||
</ion-select>
|
||||
|
@ -177,7 +172,7 @@
|
|||
<ng-container *ngIf="settings.recaptchapublickey">
|
||||
<ion-item-divider class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2><span [core-mark-required]="true">{{ 'core.login.security_question' | translate }}</span></h2>
|
||||
<h2 [core-mark-required]="true">{{ 'core.login.security_question' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<core-recaptcha [publicKey]="settings.recaptchapublickey" [model]="captcha" [siteUrl]="site.siteUrl"
|
||||
|
@ -199,11 +194,10 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<span [core-mark-required]="true">{{ 'core.login.policyacceptmandatory' | translate }}</span>
|
||||
<core-input-errors [control]="signupForm.controls.policyagreed" [errorMessages]="policyErrors" />
|
||||
</ion-label>
|
||||
<ion-checkbox slot="end" name="policyagreed" formControlName="policyagreed" />
|
||||
<ion-checkbox labelPlacement="start" justify="space-between" name="policyagreed" formControlName="policyagreed">
|
||||
<p [core-mark-required]="true">{{ 'core.login.policyacceptmandatory' | translate }}</p>
|
||||
</ion-checkbox>
|
||||
<core-input-errors [control]="signupForm.controls.policyagreed" [errorMessages]="policyErrors" />
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -105,15 +105,14 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
});
|
||||
|
||||
// Setup validation errors.
|
||||
this.usernameErrors = CoreLoginHelper.getErrorMessages('core.login.usernamerequired');
|
||||
this.passwordErrors = CoreLoginHelper.getErrorMessages('core.login.passwordrequired');
|
||||
this.emailErrors = CoreLoginHelper.getErrorMessages('core.login.missingemail');
|
||||
this.policyErrors = CoreLoginHelper.getErrorMessages('core.login.policyagree');
|
||||
this.email2Errors = CoreLoginHelper.getErrorMessages(
|
||||
'core.login.missingemail',
|
||||
undefined,
|
||||
'core.login.emailnotmatch',
|
||||
);
|
||||
this.usernameErrors = { required: 'core.login.usernamerequired' };
|
||||
this.passwordErrors = { required: 'core.login.passwordrequired' };
|
||||
this.emailErrors = { required: 'core.login.missingemail' };
|
||||
this.policyErrors = { required: 'core.login.policyagree' };
|
||||
this.email2Errors = {
|
||||
required: 'core.login.missingemail',
|
||||
pattern: 'core.login.emailnotmatch',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,7 +223,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
const namefieldsErrors = {};
|
||||
if (this.settings.namefields) {
|
||||
this.settings.namefields.forEach((field) => {
|
||||
namefieldsErrors[field] = CoreLoginHelper.getErrorMessages('core.login.missing' + field);
|
||||
namefieldsErrors[field] = { required: 'core.login.missing' + field };
|
||||
});
|
||||
}
|
||||
this.namefieldsErrors = namefieldsErrors;
|
||||
|
|
|
@ -30,18 +30,16 @@
|
|||
</ion-item-divider>
|
||||
<ion-radio-group formControlName="field">
|
||||
<ion-item>
|
||||
<ion-label>{{ 'core.login.username' | translate }}</ion-label>
|
||||
<ion-radio slot="end" value="username" />
|
||||
<ion-radio value="username">{{ 'core.login.username' | translate }}</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'core.user.email' | translate }}</ion-label>
|
||||
<ion-radio slot="end" value="email" />
|
||||
<ion-radio value="email">{{ 'core.user.email' | translate }}</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
<ion-item>
|
||||
<ion-label class="sr-only">{{ 'core.login.usernameoremail' | translate }}</ion-label>
|
||||
<ion-item lines="full">
|
||||
<ion-input type="text" name="value" placeholder="{{ 'core.login.usernameoremail' | translate }}" formControlName="value"
|
||||
autocapitalize="none" autocorrect="off" [core-auto-focus]="autoFocus" />
|
||||
autocapitalize="none" autocorrect="off" [core-auto-focus]="autoFocus"
|
||||
[attr.aria-label]="'core.login.usernameoremail' | translate" />
|
||||
</ion-item>
|
||||
<ion-button type="submit" class="ion-margin" expand="block" [disabled]="!myForm.valid">
|
||||
{{ 'core.courses.search' | translate }}
|
||||
|
|
|
@ -57,13 +57,13 @@
|
|||
|
||||
<div class="core-login-methods">
|
||||
<form *ngIf="!isBrowserSSO" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #reconnectForm>
|
||||
<ion-item class="ion-margin-bottom">
|
||||
<ion-label class="sr-only">{{ 'core.login.password' | translate }}</ion-label>
|
||||
<core-show-password name="password">
|
||||
<ion-input class="core-ioninput-password" name="password" type="password"
|
||||
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
||||
autocomplete="current-password" enterkeyhint="go" required="true" />
|
||||
</core-show-password>
|
||||
<ion-item class="ion-margin-bottom" lines="inset">
|
||||
<ion-input class="core-ioninput-password" name="password" type="password"
|
||||
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
|
||||
autocomplete="current-password" enterkeyhint="go" required="true"
|
||||
[attr.aria-label]="'core.login.password' | translate">
|
||||
<core-show-password slot="end" />
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-button type="submit" expand="block" [disabled]="!credForm.valid"
|
||||
class="ion-margin core-login-login-button ion-text-wrap">
|
||||
|
|
|
@ -22,22 +22,18 @@
|
|||
</div>
|
||||
<form [formGroup]="siteForm" (ngSubmit)="connect(siteForm.value.siteUrl, $event)" *ngIf="!fixedSites && siteForm" #siteFormEl>
|
||||
<!-- Form to input the site URL if there are no fixed sites. -->
|
||||
<ng-container *ngIf=" siteSelector==='url'">
|
||||
<ion-item>
|
||||
<ion-label position=" stacked">
|
||||
<h2>{{ 'core.login.siteaddress' | translate }}</h2>
|
||||
</ion-label>
|
||||
<ng-container *ngIf="siteSelector==='url'">
|
||||
<ion-item lines="inset">
|
||||
<ion-input name="url" type="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}"
|
||||
formControlName="siteUrl" [core-auto-focus]="showKeyboard && !showScanQR" />
|
||||
formControlName="siteUrl" [core-auto-focus]="showKeyboard && !showScanQR" labelPlacement="stacked"
|
||||
[label]="'core.login.siteaddress' | translate" [clearInput]="true" />
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="siteSelector !== 'url'">
|
||||
<ion-item>
|
||||
<ion-label position="stacked">
|
||||
<h2>{{ 'core.login.siteaddress' | translate }}</h2>
|
||||
</ion-label>
|
||||
<ion-item lines="inset">
|
||||
<ion-input name="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}" formControlName="siteUrl"
|
||||
[core-auto-focus]="showKeyboard && !showScanQR" (ionChange)="searchSite($event, siteForm.value.siteUrl)" />
|
||||
[core-auto-focus]="showKeyboard && !showScanQR" (ionChange)="searchSite($event, siteForm.value.siteUrl)"
|
||||
labelPlacement="stacked" [label]="'core.login.siteaddress' | translate" [clearInput]="true" />
|
||||
</ion-item>
|
||||
|
||||
<ion-list [class.hidden]="!hasSites && !enteredSiteUrl" class="core-login-site-list">
|
||||
|
|
|
@ -252,60 +252,6 @@ export class CoreLoginHelperProvider {
|
|||
return CoreTextUtils.treatDisabledFeatures(disabledFeatures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an object with error messages for some common errors.
|
||||
* Please notice that this function doesn't support all possible error types.
|
||||
*
|
||||
* @param requiredMsg Code of the string for required error.
|
||||
* @param emailMsg Code of the string for invalid email error.
|
||||
* @param patternMsg Code of the string for pattern not match error.
|
||||
* @param urlMsg Code of the string for invalid url error.
|
||||
* @param minlengthMsg Code of the string for "too short" error.
|
||||
* @param maxlengthMsg Code of the string for "too long" error.
|
||||
* @param minMsg Code of the string for min value error.
|
||||
* @param maxMsg Code of the string for max value error.
|
||||
* @returns Object with the errors.
|
||||
*/
|
||||
getErrorMessages(
|
||||
requiredMsg?: string,
|
||||
emailMsg?: string,
|
||||
patternMsg?: string,
|
||||
urlMsg?: string,
|
||||
minlengthMsg?: string,
|
||||
maxlengthMsg?: string,
|
||||
minMsg?: string,
|
||||
maxMsg?: string,
|
||||
): Record<string, string> {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
if (requiredMsg) {
|
||||
errors.required = errors.requiredTrue = Translate.instant(requiredMsg);
|
||||
}
|
||||
if (emailMsg) {
|
||||
errors.email = Translate.instant(emailMsg);
|
||||
}
|
||||
if (patternMsg) {
|
||||
errors.pattern = Translate.instant(patternMsg);
|
||||
}
|
||||
if (urlMsg) {
|
||||
errors.url = Translate.instant(urlMsg);
|
||||
}
|
||||
if (minlengthMsg) {
|
||||
errors.minlength = Translate.instant(minlengthMsg);
|
||||
}
|
||||
if (maxlengthMsg) {
|
||||
errors.maxlength = Translate.instant(maxlengthMsg);
|
||||
}
|
||||
if (minMsg) {
|
||||
errors.min = Translate.instant(minMsg);
|
||||
}
|
||||
if (maxMsg) {
|
||||
errors.max = Translate.instant(maxMsg);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo URL from a site public config.
|
||||
*
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<ion-item class="ion-text-wrap" *ngIf="item && (item!.canrate || item!.rating !== null) && !disabled">
|
||||
<ion-label>{{ 'core.rating.rating' | translate }}</ion-label>
|
||||
<ion-select class="ion-text-start" [(ngModel)]="rating" (ngModelChange)="userRatingChanged()" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [disabled]="!item!.canrate"
|
||||
[interfaceOptions]="{header: 'core.rating.rating' | translate}">
|
||||
[cancelText]="'core.cancel' | translate" [disabled]="!item!.canrate" [interfaceOptions]="{header: 'core.rating.rating' | translate}"
|
||||
[label]="'core.rating.rating' | translate">
|
||||
<ion-select-option *ngFor="let scaleItem of scale!.items" [value]="scaleItem.value">{{ scaleItem.name }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
|
|
@ -23,16 +23,14 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>{{ 'core.search.allcategories' | translate }}</ion-label>
|
||||
<ion-checkbox slot="end" [(ngModel)]="allSearchAreaCategories" [indeterminate]="allSearchAreaCategories === null"
|
||||
(ionChange)="allSearchAreaCategoriesUpdated()" />
|
||||
<ion-checkbox labelPlacement="start" [(ngModel)]="allSearchAreaCategories"
|
||||
[indeterminate]="allSearchAreaCategories === null" (ionChange)="allSearchAreaCategoriesUpdated()">{{
|
||||
'core.search.allcategories' | translate }}</ion-checkbox>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let searchAreaCategory of searchAreaCategories">
|
||||
<ion-label>
|
||||
<core-format-text [text]="searchAreaCategory.name" />
|
||||
</ion-label>
|
||||
<ion-checkbox slot="end" [(ngModel)]="searchAreaCategory.checked"
|
||||
(ionChange)="onSearchAreaCategoryInputChanged(searchAreaCategory)" />
|
||||
<ion-checkbox labelPlacement="start" [(ngModel)]="searchAreaCategory.checked"
|
||||
(ionChange)="onSearchAreaCategoryInputChanged(searchAreaCategory)"><core-format-text
|
||||
[text]="searchAreaCategory.name" /></ion-checkbox>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!hideCourses && courses.length > 0">
|
||||
|
@ -43,14 +41,13 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>{{ 'core.search.allcourses' | translate }}</ion-label>
|
||||
<ion-checkbox slot="end" [(ngModel)]="allCourses" [indeterminate]="allCourses === null" (ionChange)="allCoursesUpdated()" />
|
||||
<ion-checkbox labelPlacement="start" [(ngModel)]="allCourses" [indeterminate]="allCourses === null"
|
||||
(ionChange)="allCoursesUpdated()">
|
||||
{{ 'core.search.allcourses' | translate }}</ion-checkbox>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let course of courses">
|
||||
<ion-label>
|
||||
<core-format-text [text]="course.shortname" />
|
||||
</ion-label>
|
||||
<ion-checkbox slot="end" [(ngModel)]="course.checked" (ionChange)="onCourseInputChanged(course)" />
|
||||
<ion-checkbox labelPlacement="start" [(ngModel)]="course.checked"
|
||||
(ionChange)="onCourseInputChanged(course)"><core-format-text [text]="course.shortname" /></ion-checkbox>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<form (ngSubmit)="submitForm($event)" role="search" #searchForm>
|
||||
<ion-item class="search-box">
|
||||
<ion-label class="sr-only">{{ placeholder }}</ion-label>
|
||||
<ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect"
|
||||
[spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox" (ionFocus)="focus($event)" />
|
||||
<ion-input [attr.aria-label]="placeholder" type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder"
|
||||
[autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox"
|
||||
(ionFocus)="focus($event)" />
|
||||
<ion-button slot="end" fill="clear" type="submit" [attr.aria-label]="searchLabel"
|
||||
[disabled]="disabled || !searchText || (searchText.length < lengthCheck)">
|
||||
<ion-icon name="fas-magnifying-glass" slot="icon-only" aria-hidden="true" />
|
||||
|
|
|
@ -18,39 +18,33 @@
|
|||
<ion-content>
|
||||
<ion-list class="list-item-limited-width">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="rtl" (ionChange)="RTLChanged()">
|
||||
<p class="item-heading">Change text direction</p>
|
||||
<p>{{ direction }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="rtl" (ionChange)="RTLChanged()" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="forceSafeAreaMargins" (ionChange)="safeAreaChanged()">
|
||||
<p class="item-heading">Force safe area margins</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="forceSafeAreaMargins" (ionChange)="safeAreaChanged()" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="stagingSitesCount && enableStagingSites !== undefined">
|
||||
<ion-label>
|
||||
<h2>Enable staging sites ({{stagingSitesCount}})</h2>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="enableStagingSites" (ionChange)="setEnabledStagingSites($event.detail.checked)" slot="end" />
|
||||
|
||||
<ion-toggle [(ngModel)]="enableStagingSites" (ionChange)="setEnabledStagingSites($event.detail.checked)">
|
||||
<p class="item-heading">Enable staging sites <ion-badge>{{stagingSitesCount}}</ion-badge></p>
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ng-container *ngIf="siteId">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="remoteStyles" (ionChange)="remoteStylesChanged()">
|
||||
<p class="item-heading">Enable remote styles <ion-badge>{{remoteStylesCount}}</ion-badge>
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="remoteStyles" (ionChange)="remoteStylesChanged()" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="pluginStyles" (ionChange)="pluginStylesChanged()">
|
||||
<p class="item-heading">Enable site plugin styles <ion-badge>{{pluginStylesCount}}</ion-badge>
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="pluginStyles" (ionChange)="pluginStylesChanged()" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -12,11 +12,9 @@
|
|||
<ion-content>
|
||||
<ion-list class="list-item-limited-width">
|
||||
<ion-item class="ion-text-wrap" lines="none">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.settings.language' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="selectedLanguage" (ionChange)="languageChanged($event)" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [interfaceOptions]="{header: 'core.settings.language' | translate}">
|
||||
<div slot="label" class="item-heading">{{ 'core.settings.language' | translate }}</div>
|
||||
<ion-select-option *ngFor="let entry of languages" [value]="entry.code">{{ entry.name }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
@ -36,13 +34,13 @@
|
|||
</ion-segment>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap core-settings-general-color-scheme" *ngIf="colorSchemes.length > 0" lines="none">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.settings.colorscheme' | translate }}</p>
|
||||
<p *ngIf="colorSchemeDisabled" class="text-danger">{{ 'core.settings.forcedsetting' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="selectedScheme" (ionChange)="colorSchemeChanged($event)" interface="action-sheet"
|
||||
[cancelText]="'core.cancel' | translate" [disabled]="colorSchemeDisabled"
|
||||
[interfaceOptions]="{header: 'core.settings.colorscheme' | translate}">
|
||||
<div slot="label">
|
||||
<p class="item-heading">{{ 'core.settings.colorscheme' | translate }}</p>
|
||||
<p *ngIf="colorSchemeDisabled" class="text-danger">{{ 'core.settings.forcedsetting' | translate }}</p>
|
||||
</div>
|
||||
<ion-select-option *ngFor="let scheme of colorSchemes" [value]="scheme">
|
||||
{{ 'core.settings.colorscheme-' + scheme | translate }}</ion-select-option>
|
||||
</ion-select>
|
||||
|
@ -53,11 +51,10 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="richTextEditor" (ionChange)="richTextEditorChanged($event)">
|
||||
<p class="item-heading">{{ 'core.settings.enablerichtexteditor' | translate }}</p>
|
||||
<p>{{ 'core.settings.enablerichtexteditordescription' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="richTextEditor" (ionChange)="richTextEditorChanged($event)" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="displayIframeHelp">
|
||||
<ion-label>
|
||||
|
@ -69,11 +66,10 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<ion-toggle [(ngModel)]="debugDisplay" (ionChange)="debugDisplayChanged($event)">
|
||||
<p class="item-heading">{{ 'core.settings.debugdisplay' | translate }}</p>
|
||||
<p>{{ 'core.settings.debugdisplaydescription' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="debugDisplay" (ionChange)="debugDisplayChanged($event)" slot="end" />
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="analyticsAvailable">
|
||||
<ion-label>
|
||||
|
|
|
@ -24,12 +24,9 @@
|
|||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p class="item-heading">
|
||||
{{ 'core.settings.syncdatasaver' | translate }}
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-toggle slot="end" [(ngModel)]="dataSaver" (ngModelChange)="syncOnlyOnWifiChanged()" />
|
||||
<ion-toggle [(ngModel)]="dataSaver" (ngModelChange)="syncOnlyOnWifiChanged()">
|
||||
{{ 'core.settings.syncdatasaver' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-card class="core-warning-card" *ngIf="!isOnline || (dataSaver && limitedConnection)">
|
||||
|
|
|
@ -77,16 +77,25 @@ export class TestingBehatDomUtilsService {
|
|||
*
|
||||
* @param element Element.
|
||||
* @param container Container.
|
||||
* @param firstCall Whether this is the first call of the function.
|
||||
* @returns Whether the element is selected or not.
|
||||
*/
|
||||
isElementSelected(element: HTMLElement, container: HTMLElement): boolean {
|
||||
isElementSelected(element: HTMLElement, container: HTMLElement, firstCall = true): boolean {
|
||||
const ariaCurrent = element.getAttribute('aria-current');
|
||||
if (
|
||||
(ariaCurrent && ariaCurrent !== 'false') ||
|
||||
(element.getAttribute('aria-selected') === 'true') ||
|
||||
(element.getAttribute('aria-checked') === 'true')
|
||||
) {
|
||||
return true;
|
||||
const ariaSelected = element.getAttribute('aria-selected');
|
||||
const ariaChecked = element.getAttribute('aria-checked');
|
||||
|
||||
if (ariaCurrent || ariaSelected || ariaChecked) {
|
||||
return (!!ariaCurrent && ariaCurrent !== 'false') ||
|
||||
(!!ariaSelected && ariaSelected === 'true') ||
|
||||
(!!ariaChecked && ariaChecked === 'true');
|
||||
}
|
||||
|
||||
if (firstCall) {
|
||||
const inputElement = element.closest('ion-checkbox, ion-radio, ion-toggle')?.querySelector('input');
|
||||
if (inputElement) {
|
||||
return inputElement.value === 'on';
|
||||
}
|
||||
}
|
||||
|
||||
const parentElement = this.getParentElement(element);
|
||||
|
@ -94,7 +103,7 @@ export class TestingBehatDomUtilsService {
|
|||
return false;
|
||||
}
|
||||
|
||||
return this.isElementSelected(parentElement, container);
|
||||
return this.isElementSelected(parentElement, container, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -222,6 +222,10 @@ core-rich-text-editor .core-rte-editor {
|
|||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
> p:only-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid var(--stroke);
|
||||
}
|
||||
|
|
|
@ -66,77 +66,70 @@ body {
|
|||
.font-lg { font-size: 1.7rem; }
|
||||
.font-sm { font-size: 1.2rem; }
|
||||
|
||||
// Headings.
|
||||
// Item Headings.
|
||||
// Some styles taken from ion-label
|
||||
.md ion-label .item-heading,
|
||||
.ios ion-label .item-heading {
|
||||
text-overflow: inherit;
|
||||
overflow: inherit;
|
||||
--color: initial;
|
||||
color: var(--color);
|
||||
line-height: 20px;
|
||||
|
||||
&.item-heading-secondary {
|
||||
.item > ion-label,
|
||||
.fake-ion-item > ion-label,
|
||||
ion-item .in-item {
|
||||
p {
|
||||
--color: var(--subdued-text-color);
|
||||
color: var(--color);
|
||||
@include margin(2px, 0);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ios ion-label > p,
|
||||
.md ion-label > p {
|
||||
--color: var(--subdued-text-color);
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
.md ion-label .item-heading {
|
||||
@include margin(2px, 0);
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
|
||||
&.item-heading-secondary {
|
||||
.item-heading {
|
||||
@include margin(2px, 0);
|
||||
|
||||
font-size: var(--text-size);
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
text-overflow: inherit;
|
||||
overflow: inherit;
|
||||
--color: initial;
|
||||
color: var(--color);
|
||||
|
||||
.ios ion-label .item-heading {
|
||||
@include margin(0, 0, 2px);
|
||||
&.item-heading-secondary {
|
||||
@include margin(2px, 0);
|
||||
|
||||
font-size: 17px;
|
||||
font-weight: normal;
|
||||
font-size: var(--text-size);
|
||||
font-weight: normal;
|
||||
line-height: normal;
|
||||
|
||||
&.item-heading-secondary {
|
||||
@include margin(0, 0, 3px);
|
||||
|
||||
font-size: var(--text-size);
|
||||
font-weight: normal;
|
||||
|
||||
line-height: normal;
|
||||
--color: var(--subdued-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Correctly inherit ion-text-wrap onto labels.
|
||||
.item ion-label core-format-text > *:not(pre),
|
||||
.fake-ion-item core-format-text > *:not(pre) {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.item > ion-label,
|
||||
.fake-ion-item {
|
||||
core-format-text,
|
||||
core-format-text > *:not(pre) {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.item.ion-text-wrap > ion-label core-format-text > *:not(pre),
|
||||
.fake-ion-item.ion-text-wrap core-format-text > *:not(pre) {
|
||||
white-space: normal;
|
||||
overflow: inherit;
|
||||
.item.ion-text-wrap > ion-label,
|
||||
ion-item > .in-item,
|
||||
.fake-ion-item.ion-text-wrap {
|
||||
core-format-text,
|
||||
core-format-text > *:not(pre) {
|
||||
white-space: normal;
|
||||
overflow: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.item.ion-text-wrap > ion-label {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
ion-item .core-input-errors-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@each $color-name, $unused in $colors {
|
||||
.text-#{$color-name},
|
||||
p.text-#{$color-name} {
|
||||
|
@ -977,6 +970,15 @@ ion-content.limited-width > :not([slot]) {
|
|||
min-height: 100%;
|
||||
}
|
||||
|
||||
.limited-width > core-loading:not([slot]),
|
||||
.menu > core-loading:not([slot]) {
|
||||
&.core-loading-loaded {
|
||||
--contents-display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
ion-toolbar h1 img.core-bar-button-image,
|
||||
ion-toolbar h1 .core-bar-button-image img {
|
||||
padding: 4px;
|
||||
|
@ -1124,12 +1126,16 @@ input[type=checkbox] {
|
|||
}
|
||||
|
||||
// Select.
|
||||
ion-select::part(text) {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
ion-select::part(icon) {
|
||||
opacity: 1;
|
||||
ion-select {
|
||||
&::part(text) {
|
||||
white-space: normal;
|
||||
}
|
||||
&::part(icon) {
|
||||
opacity: 1;
|
||||
}
|
||||
&::part(label) {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
ion-select-popover {
|
||||
|
@ -1401,7 +1407,7 @@ audio.core-media-adapt-width {
|
|||
}
|
||||
|
||||
ion-item {
|
||||
font-size: var(--text-size);
|
||||
// font-size: var(--text-size);
|
||||
--inner-border-width: 0px;
|
||||
}
|
||||
|
||||
|
@ -1411,7 +1417,7 @@ ion-item.item-lines-full {
|
|||
}
|
||||
|
||||
ion-item.item-lines-inset {
|
||||
--inner-border-width: 1px;
|
||||
--inner-border-width: 0 0 1px 0;
|
||||
--border-width: 0px;
|
||||
}
|
||||
|
||||
|
@ -1984,3 +1990,15 @@ ion-item.item-label-stacked ion-datetime-button {
|
|||
margin-bottom: 8px;
|
||||
align-self: self-end;
|
||||
}
|
||||
|
||||
// Development styles. Most of them temporary.
|
||||
html.development {
|
||||
ion-checkbox.legacy-checkbox,
|
||||
ion-radio.legacy-radio,
|
||||
ion-select.legacy-select,
|
||||
ion-toggle.legacy-toggle,
|
||||
ion-textarea.legacy-textarea,
|
||||
ion-input.legacy-input {
|
||||
background: red !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ For more information about upgrading, read the official documentation: https://m
|
|||
- Removed CoreToLocaleStringPipe deprecated since 3.6.0
|
||||
- With the upgrade to Ionic 7 ion-slides is no longer supported and now you need to use swiper-container and swiper-slide. More info here: https://ionicframework.com/docs/angular/slides
|
||||
- With the upgrade to Ionic7 ion-datetime has changed its usage. We recommend using ion-datetime-button. More info here: https://ionicframework.com/docs/updating/6-0#datetime
|
||||
- CoreLoginHelper.getErrorMessages has been removed. Please create the messages object yourself.
|
||||
|
||||
=== 4.3.0 ===
|
||||
|
||||
|
|
Loading…
Reference in New Issue